-
Notifications
You must be signed in to change notification settings - Fork 14
Tutorial 0.6 Forms
This tutorial will introduce some of the form features of Conjure.
The most simple form is a form with just one button. If all you need is a button, Conjure makes it easy with the button-to function. Button-to takes the same arguments as link-to, but creates a form containing a single button instead of a link.
Let’s try it out by changing our message-link function, in the index.clj view, to use button-to instead of link-to. Message-link becomes:
(defn message-link [request-map id] (list (button-to message-text request-map { :params { :id id } }) " "))
When you refresh your browser, you should see buttons instead of links for all of your messages.
Note: There is no button-to-if, so we’re not using the message-displayed? function anymore, and all of the buttons are active. Also, since forms are block tags, they stack instead of list across the page.
Wouldn’t it be nice to allow the user to enter in which message he or she wishes to display instead of clicking one of potentially hundreds of links? We can do this by creating a form where the user can enter the id of the message to display.
To create a form use the form-for function. The form-for function can take 3 arguments: request-map, options, and body. Request-map and options are the same as the last two arguments in link-to and button-to. Body is a clj-html structure describing the contents of the form.
We’ll need a few other functions later. For now, let’s add an empty form to our index.clj view. Replace the following function call:
(map #(message-link request-map (:id %)) messages)
With:
(form-for request-map {} [:p "Enter the id of the message to display:"])
We created a form which points back to the index action, and contains no inputs or buttons. If you refresh your browser, you should see just the text “Enter the id of the message to display:”.
If we want the form to show the message with id 2. we can update the map to { :params { :id 2 } }. However, it won’t do anything until we add a button. Let’s add a button now. Change the form-for function call to:
(form-for request-map { :params { :id 2 } } [:p "Enter the id of the message to display:" (form-button "Message 2")])
If we refresh our browser now, we’ll see a button with “Message 2” as it’s text. When you click on it, the message with id 2 is displayed.
This is great, but don’t we want to get the id from the user? We can have the user enter an id for the message to display in a text field.
To create a text field use the text-field function. The text-field function can take 3 arguments: record, record-name and key-name. The record is a map containing the data we want to display in the text field. Record-name is the name given to the record when it returns to the controller. Record-key is the key in the record to use as the data of the text-field.
Since we have the currently displaying message, we can use it as the text-field record. Record-name we can just set to :record, and key-name is :id which will pull the id from the message passed to text-field. Our final text-field function call looks like:
(text-field message :record :id)
Since we’re now getting the id from the user, we don’t need to use it the parameters for form-for. Since the form-for options-map will be empty, we can simply leave it off entirely. Our final form-for function call now looks like:
(form-for request-map [:p "Enter the id of the message to display:" (text-field message :record :id) (form-button "Submit")])
If we refresh, we see a text field, but if we enter in text and click submit, nothing happens.
There’s still a few things wrong. First, we haven’t updated the index binding to accept the new location in params for id. Second, we need to pass the entire message to the view in order to update the text field properly.
To fix the index binding, we need to add our record name. When the text field puts together it’s params map, it uses the record-name as a key in params for a map of the key-names to values. This allows you to collect all of the values of all of the text-fields with the same record-name into one map.
However, in a binding we need to pull that record map out of params before we can get at the key we’re looking for which, in our case, is :id. We need to change the line:
(let [id (:id (:params request-map))
To:
(let [id (:id (:record (:params request-map)))
If you refresh your browser, you can now enter in the id for any message you want. Unfortunately, our text field is not getting populated with the current message id. To populate our text field, we need to pass the entire message, not just the text, to the view.
Our final home index binding looks like:
(defbinding [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"]))))
Note: we could remove the sequence of messages since we’re not using them any more, but we’ll use them shortly so just leave them in.
If you refresh your browser, the text-field populates properly, but now our message looks funny. It’s displaying the entire message map. That’s easy to fix by updating our index.clj view to only pull out the text of the message. While we’re updating the view, lets remove the link functions which we won’t need again.
Our final index 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 (clj-html.helpers/h (:text message))] (form-for request-map [:p "Enter the id of the message to display:" (text-field message :record :id) (form-button "Submit")])]))
Having the user enter in the id for a message in to a text field is nice, but this could cause problems if the user does not know what to enter. It would be nice to limit the choices to just the ids of the messages in the database, like we did with the links, but without the potentially huge list of links.
We can limit the choices using a select-tag which will display a drop down box (select box) with all of the message ids. The select-tag function takes a record, record-name, key-name and select-options. The record, record-name, and key-name are just like the arguments for text-field.
Select-options is a map of html-options and options. Html-options are just like all the other html-options and render as attributes on the select tag. Options is a sequence of strings or maps. Since we’ll display the exact same text as the value of the option, we’ll use the most simple options which simply contains the keys for each id.
The select-options map for message 2 and 3 looks like:
{ :options [ 2 3 ] }
To generate this map from our messages sequence, we’ll use map:
{ :options (map :id messages) }
Our final select-tag function call looks like:
(select-tag message :record :id { :options (map :id messages) })
Replace the text-field function call with the new select-tag function call above and our form-for function call now looks like:
(form-for request-map [:p "Enter the id of the message to display:" (select-tag message :record :id { :options (map :id messages) }) (form-button "Submit")])
Refresh your browser and you should see a drop down box for each message in the database. When you select a message id then click the submit button, the selected message is displayed.