50Ply Blog

What I'm up to

Extending Closure Classes From Clojurescript

| Comments

Here’s a quick macro to make extending google closure style classes from Clojurescript a bit easier:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
(ns move.macros
  (:require [cljs.compiler :as compiler]
            [cljs.core :as cljs]))

(defn- to-property [sym]
  (symbol (str "-" sym)))

(defmacro goog-extend [type base-type ctor & methods]
  `(do
     (defn ~type ~@ctor)

     (goog/inherits ~type ~base-type)

     ~@(map
        (fn [method]
          `(set! (.. ~type -prototype ~(to-property (first method)))
                 (fn ~@(rest method))))
        methods)))

And, here’s how you use it. In this example, I’m extending goog.ui.tree.TreeControl so that I can override handleKeyEvent and provide a bit richer interaction with my tree.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
(ns move.views
  (:require [goog.ui.tree.TreeControl :as TreeControl])
  (:use-macros [move.macros :only [goog-extend]]
               [cljs.core :only [this-as]]))
(goog-extend
 MyTree goog/ui.tree.TreeControl
 ([name config]
    (this-as this
      (goog/base this name config)))

 (handleKeyEvent
  [e]
  (this-as this
    (goog/base this "handleKeyEvent" e)
    ;; my special code to handle the key event
    )))

When this is compiled, it expands to this javascript:

1
2
3
4
5
6
7
8
9
10
11
12
move.views.MyTree = function MyTree(name, config) {
  var this__44428 = this;
  return goog.base(this__44428, name, config)
};
goog.inherits(move.views.MyTree, goog.ui.tree.TreeControl);

move.views.MyTree.prototype.handleKeyEvent = function(e) {
  var this__44429 = this;
  goog.base(this__44429, "handleKeyEvent", e);

  // my special code to handle the key event
};

Cool! That’s pretty close to the code I need to write by hand when extending google closure classes in my javascript code.

This code will work great if we compile with standard optimizations but we get into trouble in advanced mode. The Closure compiler does more error checking in advanced mode and wrongly flags our call to goog.base for not having this as its first argument. That’s unfortunate but not a big deal. We just have to write some less idiomatic code to fix it:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
(ns move.views
  (:require [goog.ui.tree.TreeControl :as TreeControl])
  (:use-macros [move.macros :only [goog-extend]]
               [cljs.core :only [this-as]]))
(goog-extend
 MyTree goog/ui.tree.TreeControl
 ([name config]
  (goog/base (js* "this") name config))

 (handleKeyEvent
  [e]
  (goog/base (js* "this") "handleKeyEvent" e)
    ;; my special code to handle the key event
    )))

This will generate the code that the advanced compiler is looking for.

This code is part of my Move library.

EDIT: Corrected errors wrt. advanced mode.

Comments