As I'm progressing with Jwt app development in Clojure, I encountered the question of structuring my code. I'm not yet talking of namespaces, but simply how I should my code in functions, and how do I translate the OO code, heavily using instance variables. See this code from a WT Ruby example to get an idea.
I wanted to develop a login widget with login and password fields and a submit button. I wanted this login widget to be displayed in a modal dialog that disappears when valid credentials are submitted.
I've started with the same approach as my previous post : a function creating the WApplication instance that is passed to the servlet. In this function, I instanciate all main components of the application: the WApplication, the modal dialog, the login widget and the main application screen:
(defn make-login-app [env] (let [wapp (new WApplication env) root (.getRoot wapp) result-text (WText. "") user nil dialog (WDialog. "test") dialog-container (.getContents dialog) app-screen (make-app-screen) form (make-login-form) ] ; function code will come here ))
Creating the login form is done by the function make-login-form. This function creates the form elements and lays them out. As Jwt is a framework using the signal/slot idea to implement communication between application components, I've decided to use it with the login form. A signal is triggered when the login is successful, and another signal is triggered when the creadential provided are incorrect.
Using signal is really simple: just instanciate the Signal class and give a way to access this instance to the component wishing to connect to it. In the OO world, this is implemented by an instance variable and a get method. So, how do we translate that in Clojure?
The trick is that the make-login-form will not only return the login widget, but also both signal instances, all placed in a map. That way, inmake-login-app, we will be able to add listeners to these signals.
The signals are triggered by a listener attached to the button's clicked signal: if the credential provided are correct the loggedin-signal is trigger, else the wrong-credentials-signal is triggered.
Here's the function's code (the last line returns the map):
(defn make-login-form [] (let [layout (WGridLayout.) container (WContainerWidget.) password-field (doto (WLineEdit. container) (.setEchoMode WLineEdit$EchoMode/Password ) ) password #(.getText password-field) login-field (WLineEdit. container) login #(.getText login-field) loggedin-signal (Signal.) wrong-credentials-signal (Signal.) do-login (fn [evt] (if (authenticate (login) (password)) (.trigger loggedin-signal ) (.trigger wrong-credentials-signal) )) submit-button (WPushButton. "Login")] (-> submit-button .clicked (.addListener container ( create-listener [mouse-event] (do-login mouse-event) ))) (.addWidget layout (WLabel. "Login:") 0 0 ) (.addWidget layout login-field 0 1 ) (.addWidget layout (WLabel. "Password:") 1 0 ) (.addWidget layout password-field 1 1) (.addWidget layout submit-button 2 0 1 2) (.setLayout container layout) (.setFocus login-field) { :container container :authenticated loggedin-signal :rejected wrong-credentials-signal}))
The credentials are checked by the function authenticate which takes the login and password as argument. This naive authentication method only to be used in an example will simply check if it finds a user entry with the corresponding user and password (using clj-record):
(defn authenticate [login password] (user/find-record {:login login :password password}))
And here we see why I'm so interested in Jwt: I don't have to care about server side or client side! I just code my app and Jwt handles all the rest. I don't have to care about making a request to the server which then will check the credentials in the database, and send a response that I would need to handle at the client side. It's all Clojure code, and Jwt handles all that trasparently.
In the make-login-app I also already initialised the app's main screen, thanks to a function that simply creates a text widget that will be updated by the login widget's signal's listeners:
(defn make-app-screen [] (let [ container (WContainerWidget.) layout (WGridLayout.) text (WText. "Hello")] (.addWidget layout text 0 0) (.setLayout container layout) container))
Now that we know how all "variables" are initialised in the make-login-app function, we can take a closer look to that function's body, where we set up the listeners:
(defn make-login-app [env] (let [wapp (new WApplication env) root (.getRoot wapp) result-text (WText. "") user nil dialog (WDialog. "test") dialog-container (.getContents dialog) app-screen (make-app-screen) form (make-login-form) ] (.addListener (form :authenticated) wapp (create-listener [] (.remove dialog) (.setText (-> app-screen .getLayout (.getItemAt 0) .getWidget) "Logged in!"))) (.addListener (form :rejected) wapp (create-listener [] (.setText (-> app-screen .getLayout (.getItemAt 0) .getWidget) "Wrong credentials!"))) (.setTitle wapp "Login Example") (doto dialog (.. getContents (addWidget (form :container))) .show) (.addWidget root app-screen) wapp))
We simply add a listener to each signal, which will update the text element we placed in the app-screen. Also note the doto usage: many Jwt methods return void, and the doto macro is really a time-saviour.
Advices and remarks to improve the code are welcome!
Update
Jonathan Smith noted that the multiple calls to (.addWidget layout ...) in make-login-form could be grouped in a doto:
(doto layout (.addWidget (WLabel. "Login:") 0 0 ) (.addWidget login-field 0 1 ) (.addWidget (WLabel. "Password:") 1 0 ) (.addWidget password-field 1 1) (.addWidget submit-button 2 0 1 2))
Jonathan also mentioned the possibility of using a 'continuation passing' style, which rather than using signals, passes functions to be called when an action occurs. The first version of my code was actually like that, and I paste it here for completeness (see the anonymous functions passed to make-login-form):
(defn make-login-form [ succes-fn failure-fn] (let [layout (WGridLayout.) container (WContainerWidget.) password-field (doto (WLineEdit. container) (.setEchoMode WLineEdit$EchoMode/Password ) ) password #(.getText password-field) login-field (WLineEdit. container) login #(.getText login-field) do-login (fn [evt] (if (authenticate (login) (password)) (succes-fn) (failure-fn) )) submit-button (WPushButton. "Login")] (-> submit-button .clicked (.addListener container (create-listener [mouse-event] (do-login mouse-event)))) (.addWidget layout (WLabel. "Login:") 0 0 ) (.addWidget layout login-field 0 1 ) (.addWidget layout (WLabel. "Password:") 1 0 ) (.addWidget layout password-field 1 1) (.addWidget layout submit-button 2 0 1 2) (.setLayout container layout) (.setFocus login-field) container)) (defn make-login-app [env] (let [wapp (new WApplication env) root (.getRoot wapp) result-text (WText. "") form (make-login-form #(.setText result-text "Welcome back!") #(.setText result-text "Check your credentials!")) user nil dialog (WDialog. "test") dialog-container (.getContents dialog) ] (.setTitle wapp "Login Example") (doto dialog (.. getContents (addWidget form)) .show) (.addWidget root result-text) wapp)) </pre>
After getting the Jwt Hello world working with Clojure , I was notified by Shantanu Kumar about his Jettify library, which is a wrapper for the Jetty Servlet Container. Using it with Clojure was a breeze, but it also gave me an easy path to deploy Jruby based Jwt apps in Jetty!
And so immediately started to translated the Hello World in Jruby, getting it working nearly instantly.
It was also very easy to add listeners, thanks to what JRuby calls Closure Conversion which converts a Ruby block or closure to an appropriate Java interface .Thanks to that feature, adding the listener for the button click is as simple as this:
button.clicked.add_listener(self) do greeting_.setText(nameEdit_.getText) end
The only problem I encountered and which I have not solved yet are setting the margin of the button. This line
button.setMargin(WLength.new(5.0), Side::Left)
causes the error "no setMargin with arguments matching", which I think is due to setMargin expecting its second argument to be a EnumSet.
The other problem I still need to solve is stopping the Jetty server when the script is stopped with CTRL-C. I thought to trap the signal like this:
trap "SIGINT", proc{ instance.stop }
but JRuby warns that this signal is handled by the java platform. I'll look further to solve these 2 minor problems, but I wanted to already share this working code:
require 'java' import "eu.webtoolkit.jwt.Side" import "eu.webtoolkit.jwt.Signal" import "eu.webtoolkit.jwt.Signal1" import "eu.webtoolkit.jwt.WApplication" import "eu.webtoolkit.jwt.WBreak" import "eu.webtoolkit.jwt.WEnvironment" import "eu.webtoolkit.jwt.WLineEdit" import "eu.webtoolkit.jwt.WMouseEvent" import "eu.webtoolkit.jwt.WPushButton" import "eu.webtoolkit.jwt.WText" import "eu.webtoolkit.jwt.WLength" import "org.bitumenframework.jettify.JettyServer" import "eu.webtoolkit.jwt.WtServlet" class MyApplication < WApplication def initialize(env) super setTitle("Hello World") getRoot.addWidget(WText.new("Your name, please ? ")) nameEdit_ = WLineEdit.new(getRoot) nameEdit_.setFocus() button = WPushButton.new("Greet me.", getRoot()) getRoot().addWidget(WBreak.new) greeting_ = WText.new(getRoot()) wapp=self button.clicked.add_listener(self) do greeting_.setText(nameEdit_.getText) end nameEdit_.enterPressed.add_listener(self) do greeting_.setText(nameEdit_.getText) end end end class MyServlet < WtServlet def createApplication(env) MyApplication.new(env) end end instance = JettyServer.new instance.addServlet(MyServlet.new, "/*") instance.start
Introduction
Jwt is a "Java library for developing web applications" with a widget-centric API. Jwt is actually the result of a translation to Java of the original Wt C++ code, which was originally developed by Koen Deforche and both are now maintained by Emweb .
I've been intrigued by Wt since I saw an (was that 3 year ago?!) announcement on Ajaxian for its widget approach, but I never used it due to its C++ base. Not that C++ is bad, it certainly has its justification in the embedded space, but I got used to dynamic scripting languages.
When Koen told me some months ago a Java version was on its way, I immediately told him I was eager to test it. Not with Java the language, but on Java the platform with Clojure (which I'll cover in this post) and JRuby (maybe for a future post, but if you want to test Wt from Ruby, take a look at Richard Dale's WtRuby which uses the C++ Wt).
Clojure is a "dynamic programming language that targets the Java Virtual Machine" which is a dialect of Lisp sharing with it "the code-as-data philosophy and a powerful macro system". Clojure is a recent language with a vibrant and steadily growing community. Despite being so young, there's already a book covering Clojure by the Pragmatic Programers .
Setup
I'm using Sun's Java 6 on an Debian, and got code for Clojure, Clojure-contrib and Jwt from their respective repositories. If this is fine for Clojure and Clojure-contrib, which build flawlessly with ant giving you a usable jar file, it caused problems with Jwt as the git repo seems to be out of sync... One advice here: be sure to download the Jwt zip file (currently 2.99.3) to avoid headaches wondering why you get blank pages served!
First step
Jwt applications need to run in a servlet container, and Compojure, a Clojure web development framework provides jetty.clj module allowing to create a servlet holder in which you can add an instantiated Jwt servlet. (thanks to Adrian Cuthbertson for this info!).
So, the only thing I needed to find is how to instanciate a WtServlet. To find out I decided to translate in Clojure the HelloWorld found in Jwt's examples . My first attempt was some some of literal translation of the Java code which was not very "clojurish" nor working as I always got blank pages served. Eventually I got everything sorted out: the problem was because the git repo doesn't contain the latest release, and the code was improved by Chouser on irc to look like this:
(ns be.nsa.server (:use compojure)) (import '(eu.webtoolkit.jwt WApplication WEnvironment WtServlet WText WPushButton WLineEdit WBreak)) (defn make-hello-app [env] (let [wapp (new WApplication env) root (.getRoot wapp) line-edit (WLineEdit.) result-text (WText. "") ] (.setTitle wapp "HelloWorld") (.addWidget root (WText. "Hello there! ")) wapp)) (def my-servlet (proxy [WtServlet] [] (createApplication [env] (make-hello-app env)))) (defserver my-server {:port 8080} "/*" my-servlet )
After declaring our namespace and saying we use compojure, we import the classes needed from Jwt.
The code itself is not very complex: it creates an instance of a proxy class to WtServlet, of which we overwrite the method createApplication (which receives as argument a WEnvironment instance). This method simply return the WApplication we instanciate in make-hello-app.
Implementing listeners
The second step needed to get the working hello world application, is to add a text field and a button. This is straight-forward, but then we need to add a listener on the button, which is defined like this in Java:
b.clicked().addListener(this, new Signal1.Listener<WMouseEvent>() { public void trigger(WMouseEvent a1) { greet(); } });
The listener needs to implement the nested interface Signal1.Listener. To get access to it, you use the syntax Signal1$Listener, but it is accessible only after you have imported it! If you don't import Signal1$Listener, you'll have to reference it by its fully qualified name (even if you imported Signal1)! I got a useful help from Cark on IRC for this, thanks!
Once accessing the nested interface was sorted out, implementing the listener was easy:
(.. (WPushButton. "Greet me" root) clicked (addListener wapp (proxy [Signal1$Listener] [] (trigger [ mouse-event ] (.setText result-text (.getText line-edit)) ))))
we add a listener to the object returned by the button's clicked method. The first argument to addListener is the wapp, the second argument is the proxy class' instance of which we implement the trigger method. This code to create the widget seems to be screaming for macros, see below.
At that time I though I was done, as implementing the second listener is done by this code:
(.. line-edit enterPressed (addListener wapp (proxy [Signal$Listener] [] (trigger [] (.setText result-text (.getText line-edit))))))
But it didn't work :(
The addListener method could not be resolved. After looking at it for hours spread on multiple days with the help of clojure wizards on the #clojure IRC channel, cgrand finally identified the problem (update (20090901): this problem has been patched and will be gone in future Clojure releases): proxy objects for nested interfaces are cached and identified by the interface's name. Meaning that if you have multiple interfaces with the same name in different packages, you'll get in trouble. The solution is to call proxy with a dummy interface that we don't care about, but which will change the key used to stored it in the proxy cache... In this example I added the interface Runnable:
(.. line-edit enterPressed (addListener wapp (proxy [Signal$Listener Runnable] [] (trigger [] (.setText result-text (.getText line-edit))))))
And that's it, the first Jwt app written with Clojure is now running :-D
The code (that you'll find at the bottom of this page) is to be place in the file be/nsa/server.clj under your classpath, and can be loaded in a repl with
(use 'be.nsa.server.clj)
You can then start and stop the server with:
(compojure.server.jetty/start my-server) (compojure.server.jetty/stop my-server)
Macro
Here is a macro greatly simplifying the creation of listeners:
(defmacro create-listener [ args & body] (let [argsnum# (if (< 0 (count args)) (count args) "") ] `(proxy [ ~(symbol (str "Signal" argsnum# "$Listener")) ] [] (trigger ~args ~@body))))
It's really heloing development in our case, as the interface implemented will be determined based on the number of arguments we want trigger to have. So this call
(create-listener [a1 a2] (do-stuff a1 a2))
will implemented the interface Signal2$Listener and this call
(create-listener [] (do-stuff))
will implement Signal$Listener.
This macro is not usable in this state though, due to the Clojure bug describe above. To work around that bug, it is possible to add an argument to the macro, so say which dummy interface we want to implement:
(defmacro create-listener [ interface args & body] (let [argsnum# (if (< 0 (count args)) (count args) "") ] `(proxy [ ~(symbol (str "Signal" argsnum# "$Listener")) ~interface ] [] (trigger ~args ~@body))))
The code
This code is compatible with Clojure 1.0. See below for updates...
(ns be.nsa.server (:use compojure)) (set! *warn-on-reflection* true) (defmacro create-listener [ interface args & body] (let [argsnum# (if (< 0 (count args)) (count args) "") ] `(proxy [ ~(symbol (str "Signal" argsnum# "$Listener")) ~@interface ] [] (trigger ~args ~@body)))) (import '(eu.webtoolkit.jwt WObject WApplication WEnvironment WtServlet WText WPushButton WLineEdit WBreak Signal1 Signal1$Listener Signal Signal$Listener EventSignal)) (defn make-hello-app [env] (let [wapp (new WApplication env) root (.getRoot wapp) line-edit (WLineEdit.) result-text (WText. "") ] (.setTitle wapp "HelloWorld") (.addWidget root (WText. "Your name, please ? ")) (.. (WPushButton. "Greet me" root) clicked (addListener wapp ( create-listener [] [mouse-event] (.setText result-text (.getText line-edit))))) (.. line-edit enterPressed (addListener wapp (create-listener [Runnable] [] (.setText result-text (.getText line-edit))))) (.addWidget root (WBreak.)) (.addWidget root line-edit) (.addWidget root (WBreak.)) (.addWidget root result-text) wapp)) (def my-servlet (proxy [WtServlet] [] (createApplication [env] (make-hello-app env)))) (defserver my-server {:port 8080} "/*" my-servlet )
Update
With the Clojure bug mentioned above gone, and removing the unecessary gensym as it is outside the escaped code, the macro can be written:
(defmacro create-listener [ args & body] (let [argsnum (if (< 0 (count args)) (count args) "") ] `(proxy [ ~(symbol (str "Signal" argsnum "$Listener")) ] [] (trigger ~args ~@body))))
As I didn't find a satisfactory solution to record screencasts, I decided to write a small script tailored to my needs, which were:
- stable solution, not crashing 50% of the time
- ability to select the window to record
- ability to pause and restart recording the same area of the desktop
- ability to record sound during the capture
- work with Free/Open Source Software
That's not too much asked I guess.... Anyway, here's the solution: screencaster.rb , a 55 line ruby script using ffmpeg.
When you start, it asks you to click on the window you want to record and what's the directory name (place in /tmp) in which to store the movie(s). It then starts right away recording that region of your desktop. In the terminal where you launched screencast.rb, type p followed by Enter to pause the recording. Then to restart recording, type n followed by Enter (it records each part in separate files). To quit, enter q+Enter.
I've used it to record a screencast, and it worked flawlessly. To produce the final file, I used .Kdenlive
Note: in addition to ffmpeg, it also depends on xwininfo
Voici enfin mon site professionnel. Jusqu'à présent, je n'avais pas la moindre présence sur internet pour présenter mes activités, ce qui est un comble vu que je propose des services liés à l'internet.
Je compte poster du contenu plutôt technique sur ce blog, et principalement en anglais. Bien que je veillerai à ce que le contenu du site soit disponible en français, néerlandais et anglais, traduite tous les messages du blog demanderait beaucoup de temps....
« Previous 1 2
Add comment