Skip to content
macourtney edited this page Sep 13, 2010 · 1 revision

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, clj-html has added a short cut. Simply change the line:

[:p (clj-html.helpers/h (:text message))]

to

[:p#message (clj-html.helpers/h (:text message))]

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

Ajax Message Action

Since our old code used a form we need to remove the :record call in the index action in the home controller. Once it is removed, get type requests will work again. The new index action looks like:

(defn index [request-map]
  (let [id (:id (:params request-map))
        message (if id (message/find-record { :id id }) (message/find-first))]
    (render-view (home-request-map request-map) 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 look almost identical to the index action, but we don’t need our list of messages and we’ll have to remove something.

Normally when a view is rendered from the controller, 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 the layout to nil when calling render-view.

How do we do set the layout to nil? The render-view function is actually a multimethod which can take either a request-map plus the view parameters, or render parameters, request-map plus view parameters. Render-view can determine which version is called based on the contents of the first map.

To call a view without a layout and no parameters, use:

(render-view { :layout nil } request-map)

Ajax-message looks just like the index function, so go ahead and copy it. Then change the render-view call to set the layout to nil and remove the call to find-records. Since we’re not rendering the layout, we don’t need to call the home-request-map function anymore which simplifies our render-view call even more.

The entire ajax-message action looks like:

(defn ajax-message [request-map]
  (let [id (:id (:params request-map))
        message (if id (message/find-record { :id id }) (message/find-first))]
    (render-view { :layout nil } request-map message)))

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

(ns views.home.ajax-message
  (:use conjure.view.base)
  (:require [clj-html.core :as html]))

(defview [message]
  (html/html 
    [:p#message (clj-html.helpers/h (:text message))]))

Don’t forget to save ajax_message.clj to your app/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. The success function is a java-script function telling Conjure what to do when the ajax request 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 success 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" request-map
  { :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.view.base)
  (:require [clj-html.core :as html]))

(defview [message messages]
  (html/html 
    [:div { :class "article" }
      [:h1 "Welcome to Conjure!"]
      [:p#message (clj-html.helpers/h (:text message))]
      (ajax-link-to "Message 2" request-map
        { :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 in the home controller to work with a record again. Simply update your ajax-message action to:

(defn ajax-message [request-map]
  (let [id (:id (:record (:params request-map)))
        message (if id (message/find-record { :id id }) (message/find-first))]
    (render-view { :layout nil } request-map message)))

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

Change index.clj back to:

(ns views.home.index
  (:use conjure.view.base)
  (:require [clj-html.core :as html]))

(defview [message messages]
  (html/html 
    [:div { :class "article" }
      [:h1 "Welcome to Conjure!"]
      [:p#message (clj-html.helpers/h (:text message))]
      (form-for request-map
        [:p "Enter the id of the message to display:" 
          (select-tag message :record :id
            { :option-map (reduce 
                (fn [options-map message]
                  (assoc options-map (:id message) nil))
                {}
                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.view.base)
  (:require [clj-html.core :as html]))

(defview [message messages]
  (html/html 
    [:div { :class "article" }
      [:h1 "Welcome to Conjure!"]
      [:p#message (clj-html.helpers/h (:text message))]
      (ajax-form-for request-map
        { :update (success-fn "message" :replace)
          :action :ajax-message }
        [:p "Enter the id of the message to display:" 
          (select-tag message :record :id
            { :option-map (reduce 
                (fn [options-map message]
                  (assoc options-map (:id message) nil))
                {}
                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.view.util.

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

(defn index [request-map]
  (let [id (:id (:record (:params request-map)))
        message (if id (message/find-record { :id id }) (message/find-first))]
    (render-view (home-request-map request-map) 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 request-map
  { :update (success-fn "message" :replace)
    :action :ajax-message
    :html-options { :action (conjure.view.util/url-for request-map { :controller :home, :action :index }) } }
  [:p "Enter the id of the message to display:" 
    (select-tag message :record :id
      { :option-map (reduce 
         (fn [options-map message]
           (assoc options-map (:id message) nil))
         {}
         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