Tutorial Linking

macourtney edited this page Jan 20, 2012 · 21 revisions
Clone this wiki locally

This tutorial will introduce the linking features of Conjure.

Making the index linkable.

We can now read a message from the database and display it as long as it’s the first message in the database. What if you have more than one message in the database and want to display what ever the user chooses. We could do it by passing the id of the message to display to the index action, then show the appropriate message.

We’ve already passed parameters from the controller to the view. How do we pass parameters from the user’s browser to an action? We can pass parameters using the web standards for passing parameters via get and post requests. Conjure will take care of much of the work for us.

Let’s say you want to display the message with id 2 (likely your “Hello World!” message id). The url for passing the parameter would look like:

http://localhost:8080/home/index?id=2

Now we need to pull the parameter from the url and get the appropriate message. We can do this by updating our home index binding. Conjure nicely adds all of the parameters under the key :params in the request-map passed to all bindings. You can get the parameters from the request-map by using the parameters function in conjure.core.server.request. Since getting the id is very common, there is a special id function in conjure.core.server.request which will return the id or nil if it is not set.

To get the id passed to your action use:

(conjure.core.server.request/id)

We can then call message/get-record passing the id in a map to get the corresponding message:

(message/get-record (conjure.core.server.request/id))

Since we can’t be sure the user will always pass an id to the index action, we should use our find-first function if no id exists. Our final 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 (:text message)))))

I’ve added a let to break out each step and make the code more readable.

Now, if you use the url above, you should see your “Hello World!” message again. To prove the id works, click on the messages tab and add another message, “Goodbye World!” The id for “Goodbye World!” will likely be 3. Using 3 as the id (replace 3 with your actual id) enter the following url to show “Goodbye World!”:

http://localhost:8080/home/index?id=3

Since accessing rows in the database by id is so common, Conjure allows you to shorten your url by simply adding the id as the last dir in the url path. Showing “Hello World!” again using a short url looks like:

http://localhost:8080/home/index/2

Now that we can pass the id of the message to show, let’s make some links to avoid having to type out those long urls all the time.

Link-to

We want to create a link tag to link to the first message which looks like:

<a href="http://localhost:8080/home/index/2">Message 2</a>

We could manually create a link using hiccup which would look like:

[:a { :href "http://localhost:8080/home/index/2" } "Message 2"]

Or we could use Conjure’s built in link function called link-to:

(link-to "Message 2" { :params { :id 2 } })

We don’t save a lot of typing, but it’s much easier to read than the hiccup way. Let’s add it to our index.clj view which will look like:

(def-view [message]
  [:div { :class "article" }
    [:h1 "Welcome to Conjure!"]
    [:p message]
    (link-to "Message 2" { :params { :id 2 } })])

You can refresh your browser to see the new link. Click on it and you will see the “Hello World!” message again. You can change the id to 3 to show the “Goodbye World!” message.

Link-to-if

The link is great, but useless if we are already looking at the “Hello World!” message. Let’s disable it if the message is already the one showing.

We can disable a link based on a condition using the link-to-if function. Link-to-if works just like link-to, but adds a condition parameter. If condition evaluates to true, then the link is enabled. If condition is false, the link is disabled.

We know the “Hello World!” message is displayed if (conjure.core.server.request/id) returns “2”. Let’s create a function called message-displayed? which takes the id of a message, and returns true if the message with the given id is displayed. The function looks like:

(defn
  message-displayed? [id]
  (= (conjure.core.server.request/id) (str id)))

Add the function into your view before the def-view call. Then update your link-to to the new link-to-if call:

(link-to-if (not (message-displayed? 2)) "Message 2" { :params { :id 2 } })

Since, we want to disable the link if the message is not displayed we add the call to not around message-displayed?.

Your complete index.clj file should look like:

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

(defn
  message-displayed? [id]
  (= (conjure.core.server.request/id) (str id)))

(def-view [message]
  [:div { :class "article" }
    [:h1 "Welcome to Conjure!"]
    [:p message]
    (link-to-if (not (message-displayed? 2)) "Message 2" { :params { :id 2 } })])

If you refresh your browser and point it to the “Hello World!” message, the “Message 2” link will be disabled. Point your browser to the “Goodbye World!” message, and the “Message 2” link will be enabled.

Link-to-if with text function

Instead of passing text to either link-to functions, we can pass a function which returns a string to use as the text of the link. The function will use the request-map in conjure.core.server.request but it is not the request-map given to the view. Instead it’s one created by combining the request-map and the parameters given to the link-to function.

For example, if the view’s request-map is something like { :controller “home”, :action “index”, :params { :id “3” } } and the parameters passed to the link-to function is { :params { :id 2 } }, then the request-map passed to the text function looks like { :controller “home”, :action “index”, :params { :id 2 } }.

In our link-to-if function call, we could create a text function which pulls the message id from the request-map and adds “Message”. Our text function would look like:

(defn
  message-text []
  (str "Message " (conjure.core.server.request/id)))

We can then replace “Message 2” with our text function in our call to link-to-if which will then look like:

(link-to-if (not (message-displayed? 2)) message-text { :params { :id 2 } })

Note, we’re not calling message-text, but passing the function itself.

Putting it all together

Our link-to-if function call is getting kinda long, let’s turn it into a function which takes the id of the message to link to. Our message link function looks like:

(defn
  message-link [id]
  (link-to-if (not (message-displayed? id)) message-text { :params { :id id } }))

We can replace the link-to-if call inside our defview with:

(message-link 2)

If you refresh your server, everything looks exactly the same. Why did we do this? Well, if we now pass all of the messages from the database to our view, we can link to all of them.

First let’s update our home index binding. The new index.clj looks like:

(ns bindings.home.index
  (: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 (:text message) (message/find-records ["true"])))))

Note the added (message/find-records [“true”]) to get all of the records in the database.

Next we need to add a parameter to our view to catch all of the messages. Our new view definition looks like:

(def-view [message messages]

Finally, we need to loop through each message in messages, and show a link. We can do this by pulling out the id of each message and mapping it to our message link function. The new call to message-link looks like:

(map message-link (map :id messages))

Unfortunately, this results in all of the message links getting crammed together. We can easily fix this by adding non breaking spaces between each link. The easiest way to add the spaces is to add them to the message-link function. The new message link function looks like:

(defn
  message-link [id]
  (list
    (link-to-if (not (message-displayed? id)) message-text { :params { :id id } })
    (nbsp)))

Putting it all together, our final index.clj looks like:

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

(defn
  message-displayed? [id]
  (= (conjure.core.server.request/id) (str id)))

(defn
  message-text []
  (str "Message " (conjure.core.server.request/id)))

(defn
  message-link [id]
  (list
    (link-to-if (not (message-displayed? id)) message-text { :params { :id id } })
    "&nbsp;"))

(def-view [message messages]
  [:div { :class "article" }
    [:h1 "Welcome to Conjure!"]
    [:p message]
    (map message-link (map :id messages))])

Now we can click to show any message in the system. When a message is displayed, it’s link is disabled. Try it out for yourself.

PreviousTutorial IndexNext