Composing templates with Hiccup and Compojure - clojure

I'm relatively new to Clojure and Compojure web development. The first issue that I've noticed in the toy example that I'm building is that of HTML templating. I'd like to have support for something like partials in Rails, or the templating framework that Django uses.
Currently I have:
(defn index-page []
(html5
[:head
[:title "Home | Compojure Docs"]
(include-css "/css/bootstrap.min.css")
(include-css "/css/bootstrap-responsive.min.css")]
[:body
[:div {:class "container-fluid"}
[:div {:class "row-fluid"}
[:div {:class "span2 menu"}]
[:div {:class "span10 content"}
[:h1 "Compojure Docs"]
[:ul
[:li
[:a {:href "/getting-started"} "Getting Started"]]
[:li
[:a {:href "/routes-in-detail"} "Routes in Detail"]]
[:li
[:a {:href "/destructuring-syntax"} "Destructuring Syntax"]]
[:li
[:a {:href "/nesting-routes"} "Nesting Routes"]]
[:li
[:a {:href "/api-documentation"} "API Documentation"]]
[:li
[:a {:href "/paas-platforms"} "PaaS Platforms"]]
[:li
[:a {:href "/example-project"} "Example Project"]]
[:li
[:a {:href "/example-project-on-cloudbees"} "Example Project on CloudBees"]]
[:li
[:a {:href "/interactive-development-with-ring"} "Interactive Development with Ring"]]
[:li
[:a {:href "/emacs-indentation"} "Emacs Indentation"]]
[:li
[:a {:href "/sessions"} "Sessions"]]
[:li
[:a {:href "/common-problems"} "Common Problems"]]]
(include-js "/js/jquery-1.9.1.min.js")
(include-js "/js/bootstrap.min.js")]]]]))
(defn routes-in-detail []
(html5
[:head
[:title "Routes in Detail | Compojure Docs"]
(include-css "/css/style.css")]
[:body
[:h1 "Routes in Detail"]]))
Is there a good way for me not to repeat code? I'd like the stuff in the HEAD tag to be in it's own template file or function, and then be able to include it as I go. For instance, I'd like to include it in the 'routes-in-detail' function. I've looked at Enlive, but I'm not sure how to use that with Hiccup. Any thoughts on best practices here would be appreciated.

You can pull parts of the markup out into separate vars:
(def head
[:head
[:title "Home | Compojure Docs"]
(include-css "/css/bootstrap.min.css")
... ])
(defn routes-in-detail []
(html5
head
[:body
... ]))
If you need your snippet/partial to take parameters, you can make it into a function instead, for example:
(defn head [title]
[:head
[:title title]
(include-css "/css/bootstrap.min.css")
... ])
(defn routes-in-detail []
(html5
(head "Routes in detail")
... ))
Sometimes you'll want your "snippet" to consist of multiple top-level elements rather than a single one. In that case you can wrap them in a list - hiccup will expand it inline:
(defn head-contents [title]
(list [:title title]
(include-css "/css/bootstrap.min.css")
... )))
(defn routes-in-detail []
(html5
[:head (head-contents "Routes in detail")]
[:body ... ]))
Once you realize the fact that hiccup markup is made out of plain clojure data structures, you'll find that manipulating/building it with functions is easy and flexible.

There's a new templating library called clabango which is modelled after the Django templating library, it may be what you're looking after: https://github.com/danlarkin/clabango

Related

Clojure sending data with nested vector to function and use the items

First, I'm sorry if my question is something simple or not clear. I'm almost new to Clojure and I need help.
I want to create a function to generate some HTML link for a menu. This is what I have now and it works fin:
(defn test-function [title-id title-name]
[:span {:onclick #(.scrollIntoView (.getElementById js/document title-id))
:style {:color :red} title-name])
[:li (test-function "title-1-id" "title-1-name")]
[:li (test-function "title-2-id" "title-2-name")]
But as I have many li tags, I want to have something like this to send to the function, and the function can generate the links for me, exactly like what the current code do for me. But I didn't find how to do that yet, and have no idea if I should use vector or something else.
[["title-1-id" "title-1-name"] ["title-2-id" "title-2-name"]]
Thank you in advance.
You can use for, then destructure each element into id and name (I just renamed name to li-name to avoid shadowing function name) and wrap whole for in [:ul ... ]:
[:ul
(for [[li-id li-name] [["id1" "Scroll to red"] ["id2" "Scroll to pink"]]]
[:li {:style {:cursor :pointer
:color :red}
:on-click #(.scrollIntoView (.getElementById js/document li-id))}
li-name])]
[:div
[:div [:img#id1 {:src "" :style {:width 600 :height 1000 :background "red"}}]]
[:div [:img#id2 {:src "" :style {:width 600 :height 1000 :background "pink"}}]]
[:div [:img#id3 {:src "" :style {:width 600 :height 1000 :background "yellow"}}]]]
Note that you even don't need Javascript to scroll to target element- [:a ... ] can point to the object on the same page:
[:ul
(for [[li-id li-name] [["id1" "Scroll to red"] ["id2" "Scroll to pink"]]]
[:a {:href (str "#" li-id)
:style {:color :red
:cursor :pointer
:text-decoration :none}}
[:li li-name]])]
[:div
[:div [:img#id1 {:src "" :style {:width 600 :height 1000 :background "red"}}]]
[:div [:img#id2 {:src "" :style {:width 600 :height 1000 :background "pink"}}]]
[:div [:img#id3 {:src "" :style {:width 600 :height 1000 :background "yellow"}}]]]

Simple Clojurescript form

I'm working with Reagent and CLJS, familiar with React and Clojure, less so CLJS. I'd like to make a simple form, but it's not obvious to me in CLJS.
(defn form []
[:div
[:input {:type "text" :name "first-name" :id "first-name"}]
[:button {:on-click (fn [e] (test-func "hello"))}
"Click me!"]
])
I want to grab the value of that input, and pass it to a function when the button is clicked. How do I get that input's value into my on-click function?
The idiomatic and technically correct way is to avoid keeping any state in DOM and accessing it directly. You shouldn't rely on the input's value. Keep the state as Reagent's atom. Then you can do anything with it.
(def first-name (r/atom ""))
(defn form []
[:div
[:input {:type "text"
:value #first-name
:on-change #(reset! first-name (.-value (.-target %)))
}]
[:button {:on-click #(test-func #first-name)} "Click me!"]])
You can grab the element's value like this: (.-value (.getElementById js/document "first-name"))
(defn form []
[:div
[:input {:type "text" :name "first-name" :id "first-name"}]
[:button {:on-click (fn [e] (test-func (.-value (.getElementById js/document "first-name"))))}
"Click me!"]
])
If there is a better answer out there, please share. :)

reagent forms multi-select not working for list

I was following the sample code from http://yogthos.github.io/reagent-forms-example.html and was attempting the use the multi-select option for a list.
(defn select-item [item]
(go
(reset! current-selection item)
(let [response (<! (check-for-response))]
(reset! current-response response)
(reset! past-response response))))
;;batch
(defn item-list []
[:div#items-list
[items-list-header]
[:ul.list-group.items {:field :multi-select :id :pick-a-few}
(if (pos? (count #items))
(doall (for [item #items]
^{:key (item "upc")}
[:li.list-group-item [:a {:class (set-item-class item) :on-click #(select-item item) :href "#"}
(item "description")]]))
[:li [:a "No Items For This Department"]])]])
(defn product-component []
[:div
[item-list]
[product-response]
;[bind-fields item-list items]
;[bind-fields item-list product-response]
])
Does anyone know why I am unable to multi-select? The logic in select-item will change, but I can't seem to see the multi-select in the UI
I've been messing around with bind-fields in my product component with no success.

on-click handler for a list item reagent clojurescript

I want to add a on-click handler for each item in my list.
(defonce selected-department (atom "department!"))
(defn sidebar []
[:div#sidebar-wrapper
[:ul.sidebar-nav
[:li.sidebar-brand [:a {:href "#"} "Departments"]]
;;[:li [:a {:on-click (reset! selected-department "test!")} "Dairy"]]
[:li [:a {:href "#"} "Dairy"]]
[:li [:a {:href "#"} "Deli"]]
[:li [:a {:href "#"} "Grocery"]]]])
Then selected-department is a label which I want to show/use the data
(defn response-box []
[:div#form_comparison
[:label#dlabel #selected-department]])
The commented out piece of code doesn't work. Is there a way to make this work?
Your example is not working because you have to pass a function to :on-click like this :
[:li [:a {:on-click #(reset! selected-department "test!")} "Dairy"]]
So the only thing that you need to change is to add # before the (reset! ...
It is the equivalent for
[:li [:a {:on-click (fn [_] (reset! selected-department "test!"))} "Dairy"]]
Edit :
This is the full code that I tested and works fine for me :
(defonce selected-department (atom "department!"))
(defn sidebar []
[:div#sidebar-wrapper
[:ul.sidebar-nav
[:li.sidebar-brand [:a {:href "#"} "Departments"]]
[:li [:a {:on-click #(reset! selected-department "Dairy") :href "#"} "Dairy"]]
[:li [:a {:on-click #(reset! selected-department "Deli") :href "#"} "Deli"]]
[:li [:a {:on-click #(reset! selected-department "Grocery") :href "#"} "Grocery"]]]
[:label #selected-department]])
(reagent/render-component [sidebar] (.getElementById js/document "app"))
The label at the bottom is updated when an item in the list is clicked.

Number format exception in compojure

As total clojure noob, I am trying to start one small tutorial app, in order to get familiar with compojure. It's a small application which lets user add two numbers, and after clicking on button displays their sum on the other page. I followed instruction from Mark McGranaghan blog. Everything seems ok, until I try to get sum of two numbers I have entered, instead of getting result, I am redirected to the same page (so basically I am stuck on first step of this tutorial). After checking the code, it seems that NumberFormatException is triggered when input parsing takes place (for some reason). In all my tests, I have tried to input all kinds of number format , but with no success. Here is the simplest code version , for which author said should work (I have tried the latest version from github site- same scenario: NFE):
(ns adder.core
(:use compojure.core)
(:use hiccup.core)
(:use hiccup.page-helpers))
(defn view-layout [& content]
(html
(doctype :xhtml-strict)
(xhtml-tag "en"
[:head
[:meta {:http-equiv "Content-type"
:content "text/html; charset=utf-8"}]
[:title "adder"]]
[:body content])))
(defn view-input []
(view-layout
[:h2 "add two numbers"]
[:form {:method "post" :action "/"}
[:input.math {:type "text" :name "a"}] [:span.math " + "]
[:input.math {:type "text" :name "b"}] [:br]
[:input.action {:type "submit" :value "add"}]]))
(defn view-output [a b sum]
(view-layout
[:h2 "two numbers added"]
[:p.math a " + " b " = " sum]
[:a.action {:href "/"} "add more numbers"]))
(defn parse-input [a b] ;; this is the place where problem occures
[(Integer/parseInt a) (Integer/parseInt b)])
(defroutes app
(GET "/" []
(view-input))
(POST "/" [a b]
(let [[a b] (parse-input a b)
sum (+ a b)]
(view-output a b sum)))
Can anyone tell me better way to pars the input values, in order to avoid this exception?I have tried couple of techniques , but nothing worked for me. I am using Leningen v1.7.1 with clojure 1.3 on win 7 machine.
Here is content of my project.clj file:
(defproject adder "0.0.1"
:description "Add two numbers."
:dependencies
[[org.clojure/clojure "1.3.0"]
[org.clojure/clojure-contrib "1.1.0"]
[ring/ring-core "1.0.2"]
[ring/ring-devel "1.0.2"]
[ring/ring-jetty-adapter "1.0.2"]
[compojure "1.0.1"]
[hiccup "0.3.8"]]
:dev-dependencies
[[lein-run "1.0.0"]])
and run.clj script:
(use 'ring.adapter.jetty)
(require 'adder.core)
(let [port (Integer/parseInt (get (System/getenv) "PORT" "8080"))]
(run-jetty #'adder.core/app {:port port}))
Thanks.
You are using compojure 1.0.1, the example in the blog you are following is using compojure 0.4.0.
As of version 0.6.0, Compojure no longer adds default middleware to routes. This means you must explicitly add the wrap-params and wrap-cookies middleware to your routes.
Source: https://github.com/weavejester/compojure
So you need to explicitly add the wrap-params middleware. So the following changes are required...
(ns adder.core
(:use ; change to idiomatic usage of :use
[compojure.core]
[hiccup.core]
[hiccup.page-helpers]
[ring.middleware.params :only [wrap-params]])) ; add middleware for params
(defn view-layout [& content]
(html
(doctype :xhtml-strict)
(xhtml-tag "en"
[:head
[:meta {:http-equiv "Content-type"
:content "text/html; charset=utf-8"}]
[:title "adder"]]
[:body content])))
(defn view-input []
(view-layout
[:h2 "add two numbers"]
[:form {:method "post" :action "/"}
[:input.math {:type "text" :name "a" :id "a"}] [:span.math " + "]
[:input.math {:type "text" :name "b" :id "a"}] [:br]
[:input.action {:type "submit" :value "add"}]]))
(defn view-output [a b sum]
(view-layout
[:h2 "two numbers added"]
[:p.math a " + " b " = " sum]
[:a.action {:href "/"} "add more numbers"]))
(defn parse-input [a b]
[(Integer/parseInt a) (Integer/parseInt b)])
(defroutes main-routes ; needs to be renamed
(GET "/" []
(view-input))
(POST "/" [a b]
(let [[a b] (parse-input a b)
sum (+ a b)]
(view-output a b sum))))
(def app (wrap-params main-routes)) ; wrap the params to allow destructuring to work