Skip to content
macourtney edited this page Jan 23, 2012 · 23 revisions

This tutorial will introduce some of the Ajax features of Conjure.

When last we updated our hello world app, we could choose the id of a message to display from a drop down box, click submit, and the page would refresh to display the message text. In this tutorial, we’ll update the app use Ajax to retrieve data from our server and update only the message.

ajax-link-to

To start, we’ll need to back track a little. The first ajax function we’ll use is ajax-link-to, which works similarly to link-to, but calls an ajax function instead. To use ajax-link-to, we’ll have to remove our nice form and drop down box. But, don’t trash the code, we’ll create an ajax form in the next section of this tutorial.

Setting a Tag Id

The first thing we need to do is update our app to have an ajax interface. To do this, we’ll need to make a slight modification to our index.clj view and modify and add a new action to the home controller.

To update the message in the index page, we need to be able to find it in the page. We can do that by adding an id to the “p” tag surrounding our message. When the ajax call returns, we’ll need to replace the entire “p” tag.

We could use an attribute map to add an id to the “p” tag, but since adding an id is so common, hiccup has added a short cut. Simply change the line:

[:p (:text message)]

to

[:p#message (:text message)]

The “#message” as part of the “p” keyword, tells hiccup to set the id for this tag to “message”.

Ajax Message Action

Since our old code used a form we need to switch back to using the conjure.core.server.request/id call in the home index binding. The new index binding looks like:

(def-binding []
  (let [id (conjure.core.server.request/id)
        message (if id (message/get-record id) (message/find-first))] 
    (with-home-request-map
      (render-view message (message/find-records ["true"])))))

Next, we need to add an ajax message action to our home controller which will return a new “p” tag with the newly selected message. The action will simply call a new binding which will look almost identical to the index binding, but we don’t need our list of messages and we’ll have to remove something.

In the home controller, the ajax-message looks just like the index function, so go ahead and copy it.

The entire ajax-message controller action looks like:

(def-action ajax-message
  (bind))

Next, we need to make a binding for the home ajax-message action. Simply copy the bindings/home/index.clj file and change the new file’s name to ajax_message.clj (note the underscore instead of a dash).

We’ll have to change the namespace from bindings.home.index to bindings.home.ajax-message. Next we need to update the def-binding to pull the requested message and render the view. The complete ajax-message binding looks like:

(ns bindings.home.ajax-message
  (:use conjure.core.binding.base
        helpers.home-helper)
  (:require [models.message :as message]))

(def-binding []
  (let [id (conjure.core.server.request/id)
        message (if id (message/get-record id) (message/find-first))] 
    (with-home-request-map
      (render-view message))))

Now that we have our binding, we’ll need to create a view. The view is very simple, just return the “p” tag with the new message.

Normally when a view is rendered, a layout is rendered as well. In our case, the layout is what gives us the look of our page with the Bunny image, Conjure link at the top, tabs across the top, and links down the side. When using ajax, we’ll only refresh part of the page which means we don’t need all of the stuff the layout gives us. To fix this, we set no-layout to true when creating the view.

The def-view looks something like:

(def-view { :no-layout true } [message]

However, since ajax views are so common, there is a macro for ajax views called def-ajax-view. the def-ajax-view looks like:

(def-ajax-view [message]

The new ajax_message.clj view looks like:

(ns views.home.ajax-message
  (:use conjure.core.view.base))

(def-ajax-view [message]
  [:p#message (:text message)])

Don’t forget to save ajax_message.clj to your src/views/home directory.

Now we’re ready for linking.

The link

Conjure uses jQuery for all of it’s ajax code. Fortunately, Conjure simplifies most of the work for the most basic ajax tasks. In our case, we just want to replace the message in our index page with one from our ajax-message action. Since this is a common task, we only have to call Conjure functions.

Let’s just make a link to message 2, with text “Message 2”, and pointing to our new ajax-message action. We create the ajax link with the ajax-link-to function. Ajax-link-to takes the same arguments as link-to, but params must include some new keys.

The only required ajax key which we need to set is the :update key which lets ajax-link-to know what function to call when the ajax returns successfully. Luckily for us, Conjure also includes some default sucess functions, one of which does exactly what we want.

The function success-fn returns a string containing a javascript function which can be used as the update function of any ajax-link-to options map. Success-fn can take one or two parameters, the first parameter is the id of the tag you wish to update. In our case, success-id will be “message”.

The second optional parameter is the position which tells success-fn what to do when ajax returns. For example, if position is :content, success-fn will add the data returned by the ajax call as content to the tag with id success-id. By default position is set to :content, other options are :replace, :before, :after, :top, :bottom, :remove. We will use :replace.

Our entire call to ajax-link-to looks like:

(ajax-link-to "Message 2"
  { :update (success-fn "message" :replace)
    :action :ajax-message
    :params { :id 2 } })

We can now replace the form-for function call in index.clj with the above ajax-link-to call (don’t forget to save the code for later). Our new index.clj view looks like:

(ns views.home.index
  (:use conjure.core.view.base))

(def-view [message messages]
  [:div { :class "article" }
    [:h1 "Welcome to Conjure!"]
    [:p#message (:text message)]
    (ajax-link-to "Message 2"
      { :update (success-fn "message" :replace)
        :action :ajax-message
        :params { :id 2 } })])

Refresh your browser and you should see the “Message 2”. Click on it, and it will replace the currently displaying message with message 2 without refreshing the entire page.

ajax-form-for

Now that we can update a link using ajax, why don’t we bring back the drop down form and ajaxify it.

We already have the actions and views we need, but we’ll have to update them to work with our posts instead of gets. The first thing to do is change the ajax message binding to work with a record again. Simply update your ajax-message binding to:

(def-binding []
  (let [id (:id (conjure.core.server.request/record))
        message (if id (message/get-record id) (message/find-first))] 
    (with-home-request-map
      (render-view message))))

Now we can go back to our index view and add back the form-for function call.

Change index.clj back to:

(ns views.home.index
  (:use conjure.core.view.base))

(def-view [message messages]
  [:div { :class "article" }
    [:h1 "Welcome to Conjure!"]
    [:p#message (:text message)]
    (form-for
      [:p "Enter the id of the message to display:" 
        (select-tag message :record :id { :options (map :id messages) } )
        (form-button "Submit")])])

Now we can update the form-for and change it to a ajax-form-for. Ajax-form-for works just like form-for, but adds the ajax keys to options like ajax-link-to. All of the ajax-link-to keys apply.

The new ajax-form-for function looks like:

(ns views.home.index
  (:use conjure.core.view.base))

(def-view [message messages]
  [:div { :class "article" }
   [:h1 "Welcome to Conjure!"]
   [:p#message (:text message)]
   (ajax-form-for
    { :update (success-fn "message" :replace)
     :action :ajax-message }
    [:p "Enter the id of the message to display:" 
     (select-tag message :record :id { :options (map :id messages) } )
     (form-button "Submit")])])

Great, now the user can select what ever id they want and the browser will on update the part of the page with message in it.

But… What if some users have javascript disabled? Wouldn’t it be nice to allow those users to use the old way? What we would like to do is, give ajax support to those with javascript enabled, and refresh the entire page for those without it. We can do this by setting the :action key under the :html-options key.

The :html-options key lists all of the attributes we want to manually set on the form tag. By adding an :action key, we can set what the form links to when javascript is disabled. The :action key takes a url string rather than a request-map or params. Therefore, we need to create one which we can do using url-for from conjure.core.view.util.

To add our new functionality, we need to first change the index binding back to accepting data from a form. The home index binding should look like:

(def-binding []
  (let [id (:id (conjure.core.server.request/record))
        message (if id (message/get-record id) (message/find-first))] 
    (with-home-request-map
      (render-view message (message/find-records ["true"])))))

Now we can add :html-options to the ajax-form-for function call. The new ajax-form-for function call should look like:

(ajax-form-for
  { :update (success-fn "message" :replace)
    :action :ajax-message
    :html-options { :action (conjure.core.view.util/url-for { :controller :home, :action :index }) } }
  [:p "Enter the id of the message to display:" 
    (select-tag message :record :id { :options (map :id messages) } )
    (form-button "Submit")])

After you refresh the page, you’ll be able to update the message with javascript enabled or not.

PreviousTutorial Index

Clone this wiki locally