Get Started

Alan Dipert edited this page Jan 20, 2017 · 29 revisions

Hello friend, and welcome to the world of Hoplon!

Hoplon is a set of Clojure and ClojureScript libraries, tied together with the Boot build tool, that unify some of the web platform's idiosyncrasies and present a fun way to design and build single-page web applications.

This page will walk you through installing the necessary tools and creating a toy application. Please feel free to edit this page if you notice errors or have ideas about improving it.

If you need help or have questions along the way, don't hesitate to join our community. We can be found in at least these places:

Tooling up

OS

Everything works best on OS X and Linux because these are the most popular OSes in the Boot community, and Hoplon is built atop Boot. Linux in a VM is the next best thing. You can also use Windows 10 for development, with some minor additional configuration. (See Running on Windows.)

Editor

You can use whatever editor you are most comfortable working with Clojure and ClojureScript in. However, you might need to configure your editor to recognize a few file extensions specific to Hoplon as Clojure and ClojureScript syntax. For more information see the Editors page.

Java

Hoplon works best on Java/JDK versions 8 or higher. If it's all the same to you, install Java 8 even if you already have 7 or earlier. It's better in every way. If you must use Java 7 or earlier, it's possible - but you'll need to get familiar with Boot's JVM Options.

Build tool

You definitely need to install Boot. Boot was originally developed to support Hoplon applications, but we've since split the projects and Boot has taken on a life of its own. Boot is also useful to have outside of Hoplon development and can be used for a wide range of build tasks.

If you have Boot installed already but see an error like java.lang.NoSuchMethodError: boot.App.getClojureName()Ljava/lang/String;, download the latest binary to fix the error.

Start from a template

We currently maintain two templates for Hoplon projects. One, hoplon-template, scaffolds a purely client-side project. The other, hoplon-castra-template, scaffolds an application with a server component.

For this tutorial, we'll start with the client-side-only hoplon-template by issuing the following command:

boot -d boot/new new -t hoplon -n address-book

Boot's -d option is a quick way to supply a dependency. In this case, we're specifying a dependency on seancorfield/boot-new, a library that provides a Boot task, new. new creates projects from templates.

The above command creates an address-book directory wherever you ran it. The directory has the following structure:

address-book/
├── assets
│   └── app.css
├── boot.properties
├── build.boot
├── README.md
└── src
    └── index.cljs.hl

These directories and files, from top to bottom, are:

  1. assets/ - there is nothing magical about the name of this directory; it is explicitly referenced in build.boot. "assets" such as CSS, images, or other media files can appear anywhere in a Hoplon project that boot is configured to find them, via Clojure code in build.boot
  2. assets/app.css - this is a normal CSS file that is referenced from index.cljs.hl.
  3. boot.properties - this is an optional file that "pins" the project to particular Clojure and Boot dependency versions.
  4. README.md - a regular Markdown file.
  5. src/ - another directory that is not magical. Boot is instructed specifically to look for source code in this directory in build.boot. We could keep our source code in a pants/ if we wanted by modifying build.boot. That things like source directories can be configured on a per-project basis using Clojure code is a major difference between Boot and Leiningen.
  6. src/index.cljs.hl - This is a file that the Hoplon Boot task will process and convert to ClojureScript. Its syntax is ClojureScript, but its semantics are Hoplon. More on this next.

Building

Hoplon promotes a style of application development characterized by continuous compilation and continuous browser reload. Instead of interacting with a text REPL while developing, we instead make and save changes to source files and observe the effect in a browser.

build.boot

Go ahead and take a peek at build.boot. We'll explore it a bit before moving ahead. You'll see a lot of Clojure code, including that "src" string we mentioned. You'll also see:

  1. A call to set-env! which configures the global build environment, :source-paths and :asset-paths, and brings in Maven dependencies from Clojars and elsewhere
  2. A call to Clojure's require function to compile a set of namespaces and refer some functions. These namespaces are provided by those dependencies installed by set-env!.
  3. A deftask form that creates a project task called dev. Roughly, a Boot task is a Clojure function intended to be called by Boot and composed with other tasks. deftask is a macro for easily creating tasks, and dev is the task we'll use to build the project for development.
  4. A deftask form that creates a prod task. We would use this to compile the project for deployment.

Checking the boot version

You will need an updated version of boot to follow this tutorial. To check for updates run:

boot -u

The output, if you have a binary that needs updating, will contain #New boot executable available: in the output. Follow the link on the next line of the output to get the latest binary (in this case #https://github.com/boot-clj/boot/releases/tag/2.3.0)

#https://github.com/boot-clj/boot
#Sun Sep 27 21:30:50 BRT 2015
BOOT_CLOJURE_VERSION=1.6.0
BOOT_VERSION=2.3.0
#App version: 2.0.0
#New boot executable available:
#https://github.com/boot-clj/boot/releases/tag/2.3.0

Running on Windows

You will need to make some small changes to the generated files before Running on Windows.

Build for development

First, cd into the newly-created project's directory:

cd address-book

Then start continuous compilation and run a server locally:

boot dev

Then, open http://localhost:8000/ and see a greeting.

Build for production

Just do:

boot prod

Then, copy the static files that appeared in your project's /target dir. Host these files with your favorite hosting provider. If you want your project to be in Clojure on the server-side, you can use the hoplon-castra template

Editing

After doing boot dev it is time for live editing! Open up src/index.cljs.hl in your editor. You'll see something like this:

(page "index.html")

(html
  (head
    (link :href "app.css" :rel "stylesheet"))
  (body
    (h1 "Hello, Hoplon!")))

Philosophical aside

Weird, huh? You're looking at an unholy combination of ClojureScript and HTML. We can get away with this because Lisp is a fundamental idea that subsumes other, less-fundamental ideas... like HTML.

This view - that Lisp is the highest programming ground - is a theme in Hoplon. Whenever we don't know how to do something, we think: how would we solve this with Lisp?

This is what we did with the DOM. You see, for a long time we had difficulty building web applications we were happy with. The difficulty always seemed to be around unifying the programming environment of JavaScript with the document environment of HTML. The two seemed always at odds.

After ClojureScript came out and we had our favorite programming language in the browser, we were empowered to think: what if HTML is Lisp, and we just need to start treating it that way?

So, we did. We made a set of functions for constructing DOM nodes, with names like body and h1. Then, we started putting them together the way we would with any other Lisp program. We were then able to write ClojureScript programs that evaluated to the applications we were trying to build.

Back to reality

Wow sorry about that, let's get back to editing this file. Change the string "Hello, Hoplon" to say "Lisp Rules!" and save the file.

If your speakers were on, you just heard a noise!

It is the noise of success. We heard it because our build is configured to emit sounds depending on how compilation goes by the speak task. Having audio feedback makes it a little easier to develop if your terminal is in the background because you can hear about errors instead of having to look for them. speak is built into boot, but there's also a boot-notify task for visual notifications.

What's really happening?

Every time you edit and save the file, the following things happen:

  1. The hoplon task compiles index.cljs.hl into a regular ClojureScript file suitable for consumption by the ClojureScript compiler.
  2. The cljs task compiles ClojureScript into JavaScript.
  3. The reload task sends a message to the browser to reload JavaScript.
  4. When the JavaScript runs, the DOM nodes we constructed programmatically are inserted into the DOM.

Javelin and Cells

So far, we've applied fancy programming tools and technology to the task of creating a static document. This could have been much more easily accomplished by creating a normal HTML file.

It's time now to leverage the fact we have a JavaScript runtime available, and start building an application a user can dynamically interact with.

Dynamic interaction implies that our application will change states, and a changing of state implies some kind of mutation. Fortunately Clojure supplies a concept and set of abstractions we can use to model this mutation in the form of reference types.

In Clojure and ClojureScript, a reference type is a kind of box containing a value that might change over time. The atom is perhaps the most common reference type. All reference types implement the deref interface for seeing the value in the box, but each have different ways of modifying the value in the box.

Javelin, a ClojureScript library we use in Hoplon applications, provides a reference type -- the cell -- designed specifically to mitigate the difficulty of coordinating state transitions in a browser setting. Like all reference types, cells always have values and can be deref'd. Unlike other reference types, cells can be wired together to form state machines analagous to those one can build in spreadsheet applications like Excel.

Your first cell

Pop this into your index.cljs.hl between the page and html forms:

(def clicks (cell 0))

Congratulations, you just made your first cell! To be more specific, you created your first input cell. There are two kinds of cells in Javelin, input and formula. Input cells are for collecting values from the user or elsewhere, and formula cells are for responding to values, just like in a spreadsheet.

But there's a problem: we can't see our cell, because nothing in the DOM is looking at it.

Seeing cells

In the same way that you can attach charts to cells in a spreadsheet, you can attach DOM nodes to cells in a Hoplon application. Let's do that now: let's attach a DOM node to the clicks cell, ensuring that its text content always reflects the value in clicks.

In your index.cljs.hl, replace:

(h1 "Lisp Rules!")

with:

(h1 clicks)

After the the page reloads, you'll notice that the 0, the value of the clicks cell, is displayed. Which is still less than dynamic...

Changing input cells

Input cells are modified the same way atoms are - using swap! and reset!. Pop this between (def clicks ...) and the html form in index.cljs.hl:

(swap! clicks inc)

...which is cool, but really we want to increment the cell after something has been clicked, not just when the page loads.

To do that, we need to attach a DOM event to a function. Add this to body, right after (h1 clicks):

(button :click #(swap! clicks inc) "Click me")

After the page reloads, go ahead and click the button. What happens when you do probably won't astound you.

Hoplon adds all of the callbacks jQuery supports as attributes on DOM nodes that take function values. Whenever the event is fired, the function we supply is called with an event argument. Replace (h1 clicks) with this:

(h1 :mouseover #(swap! clicks dec) clicks)

Now, mousing over the h1 will decrement clicks, and clicking the button will increment it. Magical.

Formula cells

We've created input cells, attached them to the DOM, and collected user input, but we have yet to create our own formula cell directly. Let's make one. Right after (def clicks ...) in index.cljs.hl, type this in:

(def clicks-even? (cell= (even? clicks)))

Then, after button in body, make a new paragraph like this one:

(p (text "clicks-even? = ~{clicks-even?}"))

Now, the paragraph's content will alternate between "clicks-even? = true" and "clicks-even? = false". As you can see, the text macro supplies string interpolation. text returns a Text node containing the interpolated string.

Cell attributes

So far, we've seen attributes used to install event callbacks, and cells used to supply text node values of DOM elements, but we haven't seen a cell used as an attribute value. This is possible too.

Add this to index.cljs.hl after (def clicks-even? ...):

(def color (cell= (if clicks-even? "blue" "red")))

color is a formula cell that alternates between "blue" and "red" string values depending on whether clicks is even. Let's hook it up to a DOM node's CSS. Change our p element from this:

(p (text "clicks-even? = ~{clicks-even?}"))

to this:

(p :style (cell= (str "color:" color))
  (text "clicks-even? = ~{clicks-even?}"))

We created an anonymous formula cell over color that returns a reactive CSS fragment. As the value of clicks changes, you should see the color of the paragraph change. Epic.

Displaying collections

We've connected DOM nodes to input cells via events and cells to DOM nodes using cell attributes and text, but all of the values in play so far have been "scalars". That is, we haven't mapped a sequence or list into the DOM reactively. Let's do that now.

Making a sequence

Add the following below (def clicks ...):

(def history (cell []))
(add-watch clicks :history #(swap! history conj %3))
(cell= (print history))

This accomplishes the following

  1. An input cell, history, is created with an initial value of [].
  2. A watch is added to clicks that appends to history with the former value of clicks every time it changes.
  3. An anonymous cell continuously pretty-prints the value of history to the JavaScript console as its value changes.

Go ahead and reload the page and open up the JavaScript console. As you click the button, you'll see the new values of history being printed. cell= and print used together are an easy way to see what cells are doing without attaching them to DOM nodes. This is a great way to debug.

Notice

Sometimes the print function will look like it is doing nothing (instead of pretty-printing to the JavaScript console). This can be caused by the JavaScript REPL hijacking the print function that hoplon tries to set.

The fix for this issue is simple for the purposes of this tutorial. Open up build.boot and look for a (cljs-repl) line under the dev task. Comment it out then restart your web server using boot dev once more.

Display sequence in DOM

Now we have a vector of numbers. Let's see an ordered list of the click cell's history. Put the following in body:

(ol
  (loop-tpl :bindings [n history]
    (li (cell= (str "n was " n)))))

First, loop-tpl is a macro, and its name is an abbreviation of "loop-template". It's a macro because it accepts code -- the template -- as an argument. It's used to efficiently display sequential ClojureScript values contained in cells in the DOM. A special technique is required because DOM nodes don't behave like other objects on the JavaScript heap. They are not garbage collected.

The "template" code is what loop-tpl will base the elements it creates and manages on.

This would also work, but would cause many DOM nodes to be created that would never be reclaimed, even if history got smaller:

(ol (cell= (map (comp li #(str "n was " %)) history)))

Here is a fancier usage of loop-tpl that alternates the background colors of items in the ol:

(ol
  (loop-tpl
    :bindings [[bg n] (cell= (map vector (cycle ["#eee" "#fff"]) history))]
    (li :css (cell= {:background-color bg})
      (text "n was ~{n}"))))

Notice how destructuring is available in the :bindings vector. bg and n are cells.

The end

Congratulations, you learned almost all there is to know about Hoplon!

For more examples, check out the Hoplon demos. For a thorough introduction to Hoplon and comparison to other technologies, check out Micha Niskin's talk, "Hoplon and Javelin, WebDev Alternate Reality"