Wednesday, September 12, 2012

Intro to ClojureScript - Part 2 - Getting the Stats

In this post we will flesh out the backend of the app we started in Intro to ClojureScript - Getting Started. We are going to create a web service to lookup player stats by last name. Once we have the lookup service in place we will update search.cljs to fetch the results of our query using the Google closure library’s goog.net.XhrIo. When the data is returned we will display it to the user using the hiccups library.

Since ClojureScript is a bit of a mouthful I will use the abbreviation cljs for the remainder of this post. Remember I am learning cljs as I create these posts so any and all guidance is appreciated.

The Goal

By the end of this post we will have a web based app that will lookup player stats by last name, displaying the results of our search.

The Setup

If you don’t already have cljs setup on your machine read my Getting Started post. It will walk you through the necessary steps to set up your cljs environment. I am also assuming you have the code from the first post. If not you can grab it from my cljs-intro github repo.

Grabbing the DB

We will be using a SQLite version of my hockey stats database. You can download it from my hockeydb github project. Once you have the database, create a db directory in the project’s home directory and copy the downloaded database into it.

Project.clj Changes

Since we will be interacting with a database, manipulating the page’s DOM, and creating a web ‘service’ we need to add a the following dependencies to our project:

[org.clojure/java.jdbc “0.2.3”]
[sqlitejdbc “0.5.6”]                  
[domina “1.0.0”]
[noir “1.3.0-beta0”]
[hiccups “0.1.1”]

The first two libraries are for our database interaction. The domina library allows us to manipulate the DOM from our cljs code. We will use noir to create our lookup web service. The hiccups library will be used to add and remove HTML from the web page.

The last setup step is to update the resources/public/index.html file. Most of the changes in it are for twitter bootstrap support. Adding bootstrap will make the search results a little easer to read.

The only change needed for the application is the addition of results div. As you might of guessed this is where the div that will contain the search results.

Creating the DB Access Code

All of our database related code will live in src/clj/cljs_intro/db.clj file. The code does a few simple queries to gather the player related information. I’m not going to go through this code in detail since it is ‘normal’ Clojure and not cljs. If you'd like to see the code here's the link to at github: db.clj.

In the db.clj file I’ve made the executive decision to return the stats for the first matching player. Why? I don’t wan’t to spend a bunch of time on non-cljs code so I made it simple on myself. What does that mean? It means if you search for Gretzky you’ll get Brett Gretzky instead of Wayne. I bet you didn't know there was a Brett Gretzky did ya.

Creating ‘Web Server’

Now that we have the database its time to create our simple web server. Noir provides functions that allow us to respond to HTTP requests, mapping routes to code and return JSON data. Our server will listen on the port 8888 for following two URLs:

http://localhost:8888/index.html
http://localhost:8888/player/:lastname.

The last URL has a parameter (:lastname) which will be the player’s last name entered by the user. Any other request will throw a Noir error.

Now that our server is complete we need to update the project.clj file so we can start it by running `lein run`. Add the following to the project.clj file:

:main cljs-intro.core

Lets test the server out. At the command prompt enter:

lein run

When the server starts up open your favorite browser and go to http://localhost:8888/player/ricci . If everything goes well you should see about a page full of JSON data.

Retrieving and Displaying the Data

If you go to http://localhost:8888/index.html and enter ricci in the text box and click the ‘Get Stats!’ button you’ll see an alert box with ricci in it. That was fine for part 1 but now we are ready to get the stats. In order to do that we need to update the ‘Get Stats!’ click event handler. Instead of a js/alert call we will call our player-lookup function.

The Updated Click Event Handler

The player-lookup function wraps a call to the Google Closure library’s goog.net.XhrIo.send method.

The goog.net.XhrIo.send function has two parameters, the URL to call and a callback function to process the returned data. We create the URL parameter by concatenating the base URL (/player) with the user’s input, in our example its ricci. When the call returns the data is passed to the display-results function so we can, well display the results.

display-results

The display-results converts the returned JSON data into a Clojure data structure and manages the UI updates.

The call to js->clj coverts the JSON to a Clojure data structure. A key thing to note here are the parameters :keywordize-keys true. Before I used those two parameters I was having a hell of a time getting to the data. After using the parameters I could easily access the data using keywords. I’m assuming that most of the time you use js->clj you’ll want to add :keywordize-keys true.

The next line uses the domina.xpath/xpath function to get a reference to the results div DOM object. We will use the reference to display the search results.

Before displaying the results of the current query we remove any previous query results by using the domina function called destroy-children!. The parameter for the destroy-children! function is a reference to a DOM object with the children to be removed. Now that the display area is empty we can display the results of the new search. On the last line a call is made to cljs-intro.views/show-stats which is where the HTML generation process starts. The results of that call are appended to the results div by calling domina’s append! function.

The HTML

Creating the HTML is done using the hiccups library which is a port of the Clojure hiccup library. Since I've used hiccup and noir for non-cljs web-based apps using hiccups made the HTML generation easy. The only difference I saw between hiccups and hiccup was that I had to use defhtml when creating a function that generated HTML.

If you wish to view the code that generates the email you can see the views.cljs. Now, when we run the search for Ricci we see the following page displayed:

Summary

The search app makes a remote call using the Google Closure library. When the data returned we were able to convert the JSON to a Clojure data structure. Once the conversion was complete we used the domina library to remove and add HTML with help from the hiccups library. Our application actually returns the stats for the player (as long as you only want the first person with the last name in question).

Whats Next?

In Intro to ClojureScript - Part 3 - Using Shoreleave - we will use the Shoreleave libraries to decouple the front end from the back end by using event based messaging. We will also change the way we handle the remote calls to use the shoreleave-remote library.

Resources

twitterbuzz and this stack overflow post implementing an ajax call in clojurescript helped me with the remote call.

ClojureScriptOne.com - used for DOM manipulation guidance.

Hockey Databank Database -- I created the database using data that originates from a Yahoo! group called hockey databank. After each season the group produces updated CSV files with the stats of the previous NHL season in addition to previous years data.

ClojureScript Experience Report - Resources Bits of information about Jason’s ClojureScript based application and his experience with ClojureScript.

ClojureScript: Up and Running - An early release book that discusses ClojureScript. I’ve read the released chapters and found it to be a good resource. Chapters 2 and 3 go over ClojureScript’s compilation, project structure, and other informative tidbits for both ClojureScript and Clojure. I’ve recommended the book to a colleague who is learning Clojure because of the way the authors describe data structures, immutability and sequences.

cljs-intro - My github repo that stores the code for this blog series. The code for Part 1 can be found on the branch Part 1. This post’s code is on the Part 2 branch.

7 comments:

  1. Hi Rob,

    Thanks for putting this tutorial together - enjoying learning as I try it out.

    I found two problems so far.

    1. In your blog entry the noir dependency is listed as [noir "1.3.0-beta0"], but it should be [noir “1.3.0-beta10”].

    2. I have been unable to get "lein run" (or loading from "lein repl") to work for your db and core in the src/clj folder, even though you have :source-path "src/clj" set in the project.clj. I cloned your repo and tried it as well and I get java.lang.ClassNotFoundException: cljs-intro.core.

    To get it run I had to prepend "clj." to the namespaces for cljs-intro.core and cljs-intro.db and change the project.clj main target to ":main clj.cljs-intro.core".

    Any idea what's going on there? I'm on a Linux machine running Clojure 1.4 and tried it with lein2.0.0-preview10 and lein2.0.0-preview7.

    ReplyDelete
  2. Michael,

    I'm not sure why you are unable to use the lein run or pull it up in the REPL. I'm using lien 1.7.1 on a mac. I'm booked solid today so I'm not sure I will be able to try it on a linux box today. As soon as I can I will attempt to duplicate your issue and let you know what I find. Thanks for catching the typo and I'm glad you are enjoying the series.

    Rob

    ReplyDelete
  3. This comment has been removed by the author.

    ReplyDelete
  4. Ah, once you said you were using lein 1, then I figured it must a change in lein 2, since lein 2 has made so many significant alterations over lein 1.

    I dug into lein 2 and figured it out: lein 2 can take an list of src paths, not just a single one, so the lein 2 project.clj for your project should look like:

    :source-paths ["src/clj"]

    It's now plural and takes an vector.

    For the :source-path target inside the :cljsbuild target I tried both the singular and plural versions and both seem to work. May need more investigation on that one.

    ReplyDelete
  5. Michael, when you get a lein 2 one working send it my way and I'll include it so others can use use it too. Thanks for checking into it.

    ReplyDelete
  6. Hi Rob,

    There were a couple of changes I had to make to get part-2 of your cljs-intro codebase to work with lein 2.

    1. lein2 :source-path is now :source-paths and takes a vector (as I mentioned before)
    2. The version of lein-cljsbuild you have is not compatible with lein 2, but the latest one is, so change project.clj to have :plugins [[lein-cljsbuild "0.2.7"]]
    3. :source-paths ["src/cljs"] inside the :cljsbuild target does NOT work, though it fails to give an error message, but it doesn't generate hockey.js. So what you have for that works fine with lein2 and lein-cljsbuild "0.2.7"


    It would also be helpful if you tell people to run "lein cljsbuild clean" and "lein cljsbuild once" before running "lein run." Took me a little while to figure that out, since I had my hockey.js from part-1 left over.

    Putting code in this comment box doesn't format well, so I've posted the lein2 compatible project.clj in an Issue to your GitHub repo for this project.

    ReplyDelete
  7. Michael,

    I'll create a lein2-project.clj file with your changes in it. I will update my version of cljs-build to the latest version. As far as the lean clean goes I typically have lein run in one terminal window and lein cljs-build auto in another so I usually don't have that problem but I will mention that someone may want to run a lein clean to clear out the previous hockey.js file.

    Thanks for all the feed back. I appreciate it.

    Rob

    ReplyDelete