October 7, 2015

Remember the Closure Library

Don't forget that the Clojurescript has access to a ton of functionality provided by the Google Closure Library by default. DOM manipulation, a solid event system, String manipulation, css class manipulation, http request management, i18n, animation effects, dates and times, browser history, and a lot more. Even functions to help with spell checking!

If you've included clojurescript in your project, then you have access to the entire closure library. The totally amazing part is that the closure compiler only includes the absolute bare minimum for the pieces you use. There's zero cost if you don't use any of it.

Being more familiar with java than javascript, it took me a little while to get the hang of calling closure from clojurescript. Here's a few notes that I hope will help anyone looking to take advantage of all this closure goodness.

The closure library is made up of both functions and classes. If you want to use a function, such as dom.getElementsByTagNameAndClass, then all you need to do is require the package containing the function:

(require '[goog.dom :as dom])
(dom/getElementsByTagNameAndClass #js "body")

If you want to take advantage of a class, such as FadeInAndShow, then use import:

(import [goog.fx.dom FadeInAndShow])
(def animation (goog.fx.dom.FadeInAndShow.
  (dom/getElementsByTagNameAndClass #js "body")
  3000))

As you get into the Google Closure Library, you might want to create some custom stuff. Here's how to create and use a custom closure class inside a clojurescript project.

First, I created a simple Closure Class inside src/js/custom.js:

/**
 * @fileoverview Contains Example Closure Class Definition
 * @author upgradingdave@gmail.com (Dave Paroulek)
 */
goog.provide('example.Person');

/**
 * Create a new Person
 * @param {string} name Name of this Person
 * @param {number=} numberofHats Defaults to 1
 */
example.Person = function(name, numberOfHats) {

  /**
   * @type {string}
   * @private
   */
  this.name_ = name;

  if (goog.isDef(numberOfHats)) {
    this.numberOfHats_ = numberOfHats;
  }

  /** 
   * @type {number}
   * @private 
   */
  example.Person.prototype.numberOfHats_ = 1;

  /** @return {string} */
  example.Person.prototype.getName = 
    function() {
      return this.name_;
    };

  /** @return {number} */
  example.Person.prototype.getNumberOfHats = 
    function() {
      return this.numberOfHats_;
    };
}

Next, I needed to let the clojurescript compiler know about my custom stuff. I configured clojurescript compiler :libs options to point to the src/js directory. I'm using boot and the boot-cljs task and so I set it up like this:

(deftask dev
  (comp
    ;; ... other stuff
    (adzerk.boot-cljs/cljs :compiler-options {:libs ["src/js"]})
  ))

If you're using lein and cljsbuild, then I think you'll need to add something like the following to your project.clj:

:cljsbuild {
  :builds [{
    ;; ... other stuff
    :compiler {
      ;; ... other stuff
      :libs ["src/js"]
    }}]}

Now, I can use my cool new class like so:

(let [o (Person. "Dave")]
    (js/alert (str "Name: "             (.getName o)
                   ", Number of Hats: " (.getNumberOfHats o))))

I think it's actually easier to make use of closure from clojure(script) than it would be without all the niceties that the clojure(script) environment brings (like lien and boot, and repl, etc). Pretty neat, eh?

Tags: clojure cljs software tech