Requiring Closure Namespaces

Yet another post on properly using the Closure Library from within ClojureScript. This time we'll discuss how to require different namespaces from Closure and the edge-cases that may not be immediately intuitive.

Namespaces, Constructors, Constants

When requiring things from Closure you mostly deal with its namespaces. Most namespaces have functions defined in them, some also contain constructors or constants. Functions are camelCased. Constructors are Capitalized. Constants are ALL_CAPS. The line between namespaces and constructors gets a bit blurry sometimes as you'll see shortly.

Let's take goog.Timer as an example. As per the previous paragraph you can infer that Timer is a constructor. Just like in Clojure we use :import to make constructors available:

(ns my.app
  (:import [goog Timer]))

Now we may use the Timer constructor as follows:

(def our-timer (Timer. interval))

Great. We have a timer. Now we'll want to do something whenever it "ticks". The Timer instance emits events which we can listen to. Listening to events can be done with the function goog.events.listen. As you can see, this function is not part of any class instance - it just exists in the goog.events namespace. To make the listen function accessible you need to require the namespace containing it. This is similar to how we require regular ClojureScript namespaces:

(ns my.app
  (:require [goog.events :as events])
  (:import [goog Timer]))

We can refer to the function as events/listen now. To listen to specific kinds of events we need to pass an event type to this function. Many Closure namespaces define constants that you can use to refer to those event types. Internally they're often just strings or numbers but this level of indirection shields you from some otherwise breaking changes to a namespace's implementation.

Looking at the Timer docs you can find a constant TICK. Now we required the constructor and are able to use that but the constructor itself does not allow us to access other parts of the namespace. So let's require the namespace.

(ns my.app
  (:require [goog.events :as events]
            [goog.Timer :as timer]) ; <-- new
  (:import [goog Timer]))

(def our-timer (Timer. interval))

(events/listen our-timer timer/TICK (fn [e] (js/console.log e)))

Remember the blurry line mentioned earlier? We just required the goog.Timer namespace both as a constructor and as a namespace. While this example works fine now, there are two more edge cases worth pointing out.

Deeper Property Access

Closure comes with a handy namespace for keyboard shortcuts, aptly named KeyboardShortcutHandler. As you can guess, KeyboardShortcutHandler is a constructor that we can use via :import. Since it emits events, the namespace also provides an enum of events that we can use to listen for specific ones. In contrast to the timer's TICK, this enumeration is "wrapped" in goog.ui.KeyBoardShortcutHandler.EventType.

The EventType property contains SHORTCUT_PREFIX and SHORTCUT_TRIGGERED. So far we've only imported the constructor. At this point you might try this:

(:require [goog.ui.KeyBoardShortcutHandler.EventType :as event-types])

But that won't work. The EventType is not a namespace but an enum provided by the KeyboardShortcutHandler namespace. To access the enum you need to access it through the namespace providing it. In the end this will look like this:

(:require [goog.ui.KeyBoardShortcutHandler :as shortcut])

(events/listen a-shortcut-handler shortcut/EventType.SHORTCUT_TRIGGERED ,,,)

Note how the slash always comes directly after the namespace alias.

goog.string.format

Last but not least another weird one. goog.string.format is a namespace that seems to contain a single function called format. If you require the format namespace however, it turns out to contain no function of that name:

(:require [goog.string.format :as format])

(format/format ,,,) ; TypeError: goog.string.format.format is not a function

Now in cases like this it often helps to look at the source code directly. Usually Closure Library code is very readable. The format function is defined as follows:

goog.string.format = function(formatString, var_args) {

As you can see it's defined as a property of goog.string, so we can access it via goog.string/format (or an alias you might have chosen when requiring goog.string). In that sense goog.string.format is not a real namespace but rather something you require for its side effects — in this case the definition of another function in goog.string. I have no idea why they chose to split things up in that way. ¯\_(ツ)_/¯

For Reference

I scratched my head many times about one or the other aspect of this and usually ended up looking at old code. Next time I'll look at the handy list below 🙂

  • Require Google Closure namespaces just as you'd require ClojureScript namespaces
    • (:require [goog.events :as events])
  • The base goog namespace is autmatically required as if you'd have [goog :as goog] in your list of required namespaces.
    • This implies that you can refer to goog.DEBUG as goog/DEBUG. Never refer to goog through the global Javascript namespace as in js/goog.DEBUG. (CLJS-2023)
  • Require constructors using one of the two forms. In either case you may use Timer. to construct new objects.
  • Only access non-constructor parts of a namespace through a namespace that has been :required
  • Always use slash after the namespace alias, use dot for deeper property access.
  • Requiring goog.string.format will define a function format in the goog.string namespace.

Enjoy

For many of the things described here there are alternative ways to do them. We still build on Javascript after all. The ones I've chosen here are the ones that seem most idiomatic from a Clojurescript perspective.

Thanks to Paulus Esterhazy and António Monteiro for proof-reading this post and offering their suggestions.

If you feel like reading more about utilizing the Closure Library and compiler in ClojureScript I have a few more posts on those:

Other Posts

  1. Simple Debouncing in ClojureScriptApril 2017
  2. Making Remote WorkMarch 2017
  3. Just-in-Time Script Loading With React And ClojureScriptNovember 2016
  4. Props, Children & Component Lifecycle in ReagentMay 2016
  5. Om/Next Reading ListNovember 2015
  6. Parameterizing ClojureScript BuildsAugust 2015
  7. ClojureBridge BerlinJuly 2015
  8. Managing Local and Project-wide Development Parameters in LeiningenJune 2015
  9. Formal Methods at AmazonApril 2015
  10. (lisp keymap)February 2015
  11. CLJSJS - Use Javascript Libraries in Clojurescript With EaseJanuary 2015
  12. Why Boot is Relevant For The Clojure EcosystemNovember 2014
  13. S3-Beam — Direct Upload to S3 with Clojure & ClojurescriptOctober 2014
  14. Patalyze — An Experiment Exploring Publicly Available Patent DataOctober 2014
  15. Running a Clojure Uberjar inside DockerSeptember 2014
  16. Using core.async and Transducers to upload files from the browser to S3September 2014
  17. Emacs & VimJuly 2014
  18. Heroku-like Deployment With Dokku And DigitalOceanMarch 2014
  19. Woodworking MasterclassesFebruary 2014
  20. Early Adopters And Inverted Social ProofFebruary 2014
  21. Living SmallFebruary 2014
  22. Sending You a TelegramJanuary 2014
  23. Running a Marathon, Or NotJanuary 2014
  24. Code SimplicityJanuary 2014
  25. What do we need to know?December 2013
  26. Sculley's DiseaseDecember 2013
  27. A Resurrection PostDecember 2013
  28. A Trip To The USSeptember 2013
  29. Analytics DataApril 2013
  30. Asynchronous CommunicationApril 2013
  31. From Zero to Marathon in Six MonthtsMarch 2013
  32. Git Information in Fish Shell’s PromptDecember 2012
  33. When We Build StuffAugust 2012
  34. Models, Operations, Views and EventsJuly 2012
  35. The Twelve Factor AppJune 2012
  36. Paris And BackMay 2012
  37. A Friend Is Looking For A Summer InternshipMay 2012
  38. Kandan Team ChatMay 2012
  39. Startups, This Is How Design WorksMarch 2012
  40. Entypo Icon SetMarch 2012
  41. Hosting A Static Site On Amazon S3February 2012
  42. Exim4 Fix Wrongly Decoded Mail SubjectJanuary 2012