Wednesday, June 15, 2011

Creating a Simple UI for the Hugo DB

In my past two posts, Parsing Web Pages with Clojure and enlive and Creating a Hugo Awards DB with Clojure and Sqlite, I parsed and stored the list of Hugo Best Novel award nominees and winners. In this, the last post on the Hugo data, I will build a very basic UI in Clojure using Swing and Java’s awt library.

The Goal

By the end of the post I will have code to display a listing of the nominees in a UI. The data will come from the sqlite database I created in my previous post.

The Setup

If you’ve been following the past few posts then you already have everything you need installed. The only thing you will need to do is grab the code from my hugoUI branch.

For those of you who haven’t been following along the first thing you will need to do is grab sqlite. Since I’m working on I windows I grabbed these two downloads: sqlite shell and sqlite-dll. After downloading the files make sure they are in your PATH.

Next, grab leiningen. Leiningen is a Clojure build tool that helps you manage your projects. I use in all of my clojure projects. The install takes no time at all, just follow the instructions on the project’s page and you will be ready for business.

Updating the the project.clj file

Since I am not going to be doing any HTML parsing I've removed the enlive dependency from my project.clj file. All other dependencies I had in the previous version still remain.

Just to be on the safe side I ran lein deps to ensure I have everything ready.

The Code

The UI app consists of four code files: cmdline.clj, main_controller.clj, sqlite.clj, and main_view.clj. The cmdline.clj file contains the main method and starts the app. The main_controller.clj file retrieves the data and calls the function that creates the view. The data is retrieved by calling the functions in the sqlite.clj file. The last file is the main_view.clj file who's job is to create the user interface and display the data.

The cmdline.clj file

The cmdline.clj file’s sole purpose is to start the application. This is done in part by using the :gen-class macro. The macro will create a java class file called cmdline.class in the project’s classes directory. Any methods in the java class will look in the source clj file for a method by the same name but preceded by a –. That is why the –main function exists in cmdline.clj. It is what is called when I execute the app outside of the REPL. Within the main function I call hugo.controller.main-controller/show-list to kick off the data retrieval and displaying of the UI.

The hugo.controllers.main-controller.clj file

The functions in this file are concerned with retrieving the data, formatting it and passing it along to the view.  Before I jump into what the functions are doing I want to point out my use of the defn- macro.  Using this macro allows me to ‘hide’ the function from anyone outside of the namespace that it was created in.  So in the hugo.controllers.main-controller namespace there is only one function that is visible outside of the namespace.  That is the show-list function.

The show-list function is a simple one.  It starts off by calling the get-nominees-data-list function which is where the majority of the data retrieval and formatting takes place.  It starts off by calling the hugo.db.sqlite/get-years function. As you might expect the get-years function retrieves all of the years that are stored in the database.  I use the map function to iterate over the returned sequence of the years.  For each year the format-data function is called.

The format-data function takes a year as its only parameter.  The year is then passed to the hugo.db.sqlite/get-nominees function which returns a sequence of maps for all nominees in the year specified.  The results of the get-nominees call are passed through their own map function that is used to format the data into a string that can be displayed in a list. Finally, the results of the get-nominees map file are concat'ed with a title string and a spacing string to create sequence that is ready to be displayed on the UI.  Now that I have all of the data retrieved and formatted its time to display it.

The hugo.views.main-view file

As I said earlier I am creating a UI using the Java UI libraries Swing and awt.  The :import macro brings in the BorderLayout and ActionListener classes.  The BorderLayout class is used for laying out the objects on the UI.  The ActionListener class is used for the event handler when the Close button is clicked.  The classes I’m bringing in from Swing are all UI objects except for the DefaultListModel class.  That class is used to manage the list of novels that will be displayed in the list box.

The hugo.controllers.main-controller/show-list function calls the create-home-view function after it has retrieved and formatted the data. Inside of create-home-view all of the UI objects are created. First I need to create the main window by creating an instance of the JFrame object passing in the title of the window. Next I create the panel that will contain all of my UI objects using JPanel. The title label and close button objects are pretty self-explanatory.

The last three lines of the let statement are all relate to creating the list. The list-model is used to manage the data in the list. Once I have a list model object I can create the JList object. When the JList object is created I passed in the list-model object to associate the list with the list-model. The last object created is the JScrollPanel object which is used to provide scrolling capabilities for the list box.

Now that I have the UI objects created its time to call the ‘private’ function add-list-items. This function’s job is to add the formatted list data to the list-model object. The add-list-items function uses doseq to loop over the data sequence.  Each item is added to the list-model object using the addElement method.  Basically, the add-list-items takes the items from the data sequence and adds them to the list box via the list-model.

Once the data has been handled its time to dress up the title-label. I can change the look and feel of the label by calling the JLabel class's setBackground, setForeground and setFont methods. I changed the look of the close button by calling the equivalent methods on the JButton class. Since I want something to happen when a user clicks the close button I added the event handler function close-button-handler by calling the addActionListener method.

After the cosmetic steps have been completed it is time to add the objects to the frame and size the main window. The last line of code makes the window visible. Now that I have everything ready its time to run the app. To run the app I used:

lein run

When the app comes up it should look like the picture below. Not too bad for my first Clojure UI.

hugo-ui Summary

Creating a user interface in Clojure is not difficult. If someone like me who makes his living in the .NET world can create a UI like this I would imagine it would be much easier for Java devs. As always I’m looking for any and all feedback on my code.  Do not hesitate to leave a comment.

Help Me Raise Money for The Leukemia and Lymphoma Society!

This November I will be running my first half marathon. I’m running as a member of the Team in Training organization who raises money for the Leukemia and Lymphoma Society. I have pledged to raise $950 that will go to research to help find a cure blood cancers. Please show your support by leaving a donation here. Any amount is greatly appreciated! Thank you for your support.

2 comments:

  1. Hey. Nice post. UI's are indeed pretty easy to build with Clojure, but still tedious. A lib like Seesaw (https://github.com/daveray/seesaw) or GUIFTW (https://github.com/santamon/GUIFTW) can make that easier. Here's your view translated to Seesaw: https://gist.github.com/1050157
    Just add [seesaw "1.0.7"] to the project deps.

    Cheers!

    Dave

    ReplyDelete
  2. Dave, Thanks for the tips on the UI libs. I like the Seesaw version you created of my UI. The seesaw version is much easier to read. I like it.

    ReplyDelete