Web Dev Rapid Recipe - Add Clojurescript to your Luminus project
Table of Contents
This article is for when you already have a Luminus web-app using Selmer templating, and you want to add ClojureScript to it.
This doesn't cover front-end frameworks, it just puts you in a position where you can use ClojureScript to manipluate the DOM (as one might do with JQuery).
1 Update project.clj
First, add ClojureScript to your project.clj dependencies. The snippet to do this is documented here: https://github.com/clojure/clojurescript
Now add lein-cljsbuild to the project.clj plugins (not dependencies). Snippet here: https://github.com/emezeske/lein-cljsbuild
Then, from that same page, copy+paste the :cljsbuild {}
part (from
the "Basic Configuration" section). You will want to change:
:source-paths
tosrc/cljs
. This is just so it's in src where it belongs.:output-to
totarget/cljsbuild/public/js/app.js
.
Finally:
- edit
:resource-paths
, adding"target/cljsbuild"
- add the cljsbuild build hooks:
:hooks [leiningen.cljsbuild]
2 Add your code
Edit one of your HTML templates. Put this snippet where you want the script tag to be templated in:
{% script "/js/app.js" %}
ⓘ the Selmer script tag is documented here: https://github.com/yogthos/Selmer#script
For your Hello World, add an empty div that you can add content to:
<div id="dynamiccontent"></div>
Now make the directory for your Clojurescript code (edit "appname"):
mkdir -p src/cljs/appname/
Now create a new file core.cljs
and write your Hello World code
(edit "appname"):
(ns appname.core) (let [container (.getElementById js/document "dynamiccontent") paragraph (.createElement js/document "p")] (set! (.-textContent paragraph) "Ultimate World!") (.appendChild container paragraph))
3 Run it
Restart your development server if it's running.
And run in an extra terminal:
lein cljsbuild auto
Now when you load your site in browser you should see your Hello World!
4 How to call out to a function?
In the above example, the javascript just directly runs when the script loads.
However if you want to define functions that are called on certain events you can. There are two ways to do that:
4.1 specify namespace
You can directly call to a function if you prefix the function name with it's namespace.
Here is an example using onclick
(edit "appname"):
<div id="functiontest"> <input id="mynumber" type="number"> <button onclick="appname.core.timesfive()">times 5!</button> <p id="equals"></p> </div>
(defn timesfive "multiplies the supplied number by 5 and renders it onto the page" [] (let [number (.-value (.getElementById js/document "mynumber")) paragraph (.getElementById js/document "equals")] (set! (.-innerHTML paragraph) (* number 5))))
4.2 attach listener
This way we attach a listener to the button from code using
.addEventListener
:
<div id="functiontest"> <input id="mynumber" type="number"> <button id="mybutton">times 5!</button> <p id="equals"></p> </div>
(defn timesfive "multiplies the supplied number by 5 and renders it onto the page" [] (let [number (.-value (.getElementById js/document "mynumber")) paragraph (.getElementById js/document "equals")] (set! (.-innerHTML paragraph) (* number 5)))) (let [button (.getElementById js/document "mybutton")] (.addEventListener button "click" timesfive))
5 How to share code between server and client?
So you want to write some code which will be available to both server and client.
This is fairly simple, we just make a new source directory and add
it to both the Clojure and ClojureScript source-paths
.
The convention for naming is:
extension | type |
---|---|
clj | Clojure |
cljs | Clojurescript |
cljc | Platform-independant code |
Generally when writing cljc, you avoid using platform-specific code. But if you have to, you may use Reader Conditionals.
For the example, it's quite a lot so I just show you the git change: https://codeberg.org/xylon/cccl2/commit/a0d533578ff7c5da51331140c3c181c3772c8fc4
6 How to AJAX?
For AJAX you may use cljs-alax. I will let the project documentation speak for itself https://github.com/JulianBirch/cljs-ajax
Note that, if using Luminus, you don't need to manually convert to/from JSON, as the middleware will take care of that automatically.
7 Export more than one script
Look back at the :cljsbuild
map you added to project.clj
.
:builds
is a list, so you may simply add more builds.
You will have to add a :main
key to control the entry-point of
each script. Docs for compiler options here.
8 General notes on ClojureScript
Miscelaneous notes.
8.1 REPL
If you want a REPL for experimenting with, you may either
- use clojurescript.io
- set one up manually according to ClojureScript Quick Start
8.2 Interop
This example demonstrates calling a function, reading a property, and setting a property:
(let [myinput (.getElementById js/document "myinput") myvalue (.-value myinput)] (set! myvalue "foobar"))
n.b. there are multiple ways to do everything. See this article for more examples: ClojureScript <-> JavaScript Interop
8.3 this
If you need to access javascript's "this" object, just wrap it with:
(this-as this )
8.4 cheat-sheet
This is helpful: https://cljs.info/cheatsheet/