Coast uses hiccup as its rendering engine, which is pretty darn fast and comes with an elegant API to create dynamic views.
Let's start with the classic Hello World example by rendering a hiccup vector.
All views are stored alongside form submission code in the src
directory, which
is in opposition to most other frameworks where controllers and views are separate files.
Coast takes a different "separation of concerns" where the concern isn't filetypes but instead logical bits of code that fit together.
It's always nice and sometimes jarring to see the form submission code right below the code that will be called on redirect or error in the same file.
[:h1 "Hello world"]
This is a hiccup vector, clojure keywords make up the html bits, :a
, :h2
, :div
and the rest of the vector
is a map of parameters representing the html tag's attributes. The body of the tag is the last element of the vector.
Here's how it looks in practice.
Make a route that will render some html:
; src/routes.clj
(def routes
(coast/site
[:get "/" :home/index])))
; src/home.clj
(defn index [request]
[:div
[:h1 "Hello world"]])
That's all you need to render html to the client.
There are no separate templating libraries you have to learn, no special syntax (aside from hiccup's syntax), it's just clojure.
You can also render other functions that emit hiccup:
(defn goodbye [s]
[:h2 (str "Goodbye " s)])
(defn index [request])
[:div
[:h1 "Hello world"]
(goodbye "world")]
All views have access to the current request
map.
You can use request data inside your view functions like so:
[:div (str "The request URL is " (:uri request))]
The following helpers are provided by Coast
coast/css
Adds a link
tag to a CSS bundle.
Relative path (to CSS files in the public
directory):
(coast/css "bundle.css")
; assuming the assets.edn looks something like this
{"bundle.css" ["style.css"]}
The code above outputs:
<link rel="stylesheet" href="/style.css" />
coast/js
Adds a script
tag to a JS bundle
(coast/js "bundle.js")
; assuming the assets.edn looks like this
{"bundle.js" ["app.js" "app2.js"]}
The code above outputs:
<script type="text/javascript" src="/app.js"></script>
<script type="text/javascript" src="/app2.js"></script>
in development.
NOTE: In production, the assets are bundled and minified into one js file and one css file.
Returns the URL for a route.
For example, using the following example route…
[:get "/customers/:customer-id" :customer/show]
…if you pass the route name and any route parameters…
[:div
[:a {:href (url-for :customer/show {:customer/id 123})}
"View customer"]]
…the route URL will render like so:
<a href="/customers/123">
View customer
</a>
You can access the CSRF token and input field using one of the following helpers.
csrf
[:form {:action "/" :method "POST"}
(coast/csrf)]
Which renders
<form action="/" method="POST">
<input type="hidden" name="__anti-forgery-token" value="<token value>" />
</form>
You never really have to know about the csrf field itself because coast has built in form components as well
form
(coast/form (coast/action-for :customer/change {:customer/id 123})
[:input {:type "text" :name "first-name" :value ""}])
The csrf field is automatically appended to the coast/form
form.
form-for
(coast/form-for :customer/change {:customer/id 123}
[:input {:type "text" :name "first-name" :value ""}])
form-for
is a convenience function that takes a coast route name and any route parameters followed by the rest of the form.
Coast uses clojure code to insert conditional logic into hiccup vectors
Here's an example of one way to do authentication:
(defn index [request]
(let [session (:session request)]
[:div
(if session
"You are logged in!"
[:a {:href "/login"} "Click here to log in"])]))
If you need to loop through a list of things, here is one way to do it:
(ns post
(:require [coast]))
(defn index [request]
(let [posts (coast/q '[:select * :from post])]
[:ul
(for [post posts]
[:li (:post/title post)])]))
raw
Newer versions of hiccup (2.0.0 and greater) escape all html by default, if you need to render a string as html, you'll have to explicitly call raw
(coast/raw [:div "<b>is fine now</b>"])
Keeping track of hiccup code can become unwieldy if the functions get long enough, similar to HTML code.
Coast has a way of separating bits of HTML into smaller chunks that can be called on when needed. Components.
Here's a basic example:
(defn modal [title & content]
[:div {:class "modal" :tabindex "-1" :role "dialog"}
[:div {:class "modal-dialog" :role "document"}
[:div {:class "modal-content"}
[:div {:class "modal-header"}
[:h5 {:class "modal-title"} title]
[:button {:type "button" :class "close" :data-dismiss "modal" :aria-label "Close"}
[:span {:aria-hidden "true"}
"×"]
]]
[:div {:class "modal-body"} content]
[:div {:class "modal-footer"}
[:button {:type "button" :class "btn btn-primary"}
"Save changes"]
[:button {:type "button" :class "btn btn-secondary" :data-dismiss "modal"}
"Close"]
]]
]])
Use the modal
component like this:
(defn index [request]
[:div
[:a {:href "#" :id "show-modal"}]
(modal "My Modal"
[:p "My modal body goes here"]
[:div "In fact multiple things can go here"])]])
This is assuming you have the requisite js somewhere.
Layouts in coast are specified alongside the routes which makes supporting multiple layouts a little easier
Here's an example:
(defn my-layout-function [request body]
[:html
[:head
[:meta {:name "viewport" :content "width=device-width, initial-scale=1"}]
(coast/css "bundle.css")
(coast/js "bundle.js")]
[:body
body]])
(defn my-other-layout-function [request body]
[:html
[:head
[:meta {:name "viewport" :content "width=device-width, initial-scale=1"}]
(coast/css "bundle.css")
(coast/js "bundle.js")]
[:body
body]])
(def routes
(coast/routes
(coast/site
(coast/with-layout :my-layout-function
[:get "/" :home/index]
[:resource :customer]
(coast/with-layout :my-other-layout-function
[:get "/other-route" :other/route])))))
Hiccup also offers a more terse syntax in case you get tired of writing out html identifiers and class names in maps:
This in html:
<div id="my-id" class="btn btn-solid"></div>
...becomes this in normal hiccup
[:div {:id "my-id" :class "btn btn-solid"}]
...becomes this in terse hiccup
[:div#my-id.btn.btn-solid]
Can you improve this documentation?Edit on GitHub
cljdoc is a website building & hosting documentation for Clojure/Script libraries
× close