Guide to using Google Closure Templates in Jive 5.0 / 6.0

Version 4

    Introduction

    In an attempt to standardize how we do javascript templating in the code base (there are currently four different means of accomplishing this), we have decided to use google closure templates going forward.  This document attempts to get you up to speed quickly and answer common questions regarding setup and use of google closure templates.

     

    Client-side templating is similar to server-side templating, but is processed on the client instead of the server.  There are many solutions available, google closure templates being one of them.  They are especially useful for separating the HTML from a javascript file, no more need for embedding the view in a javascript object. They are also useful for reusing code in cases where the ui is dynamic and makes ajax requests that require ui view updates.  Additionally they can be used to offload server-side template language processing to the client.  Here is snippet from the closure templates site that does a good job describing the library:

     

    "What are Closure Templates?
    Closure Templates are a client- and server-side templating system that helps you dynamically build reusable HTML and UI elements. They have a simple syntax that is natural for programmers, and you can customize them to fit your application's needs. In contrast to traditional templating systems, in which you must create one monolithic template per page, you can think of Closure Templates as small components that you compose to form your user interface. You can also use the built-in message support to easily localize your applications.
    Closure Templates are implemented for both JavaScript and Java, so that you can use the same templates on both the server and client side. They use a data model and expression syntax that work for either language. For the client side, Closure Templates are precompiled into efficient JavaScript.
    What are the benefits of using Closure Templates?
    Convenience. Closure Templates provide an easy way to build pieces of HTML for your application's UI and help you separate application logic from display.
    Language-neutral. Closure Templates work with JavaScript or Java. You can write one template and share it between your client- and server-side code.
    Client-side speed. Closure Templates are compiled to efficient JavaScript functions for maximum client-side performance.
    Easy to read. You can clearly see the structure of the output HTML from the structure of the template code. Messages for translation are inline for extra readability.
    Designed for programmers. Templates are simply functions that can call each other. The syntax includes constructs familiar to programmers. You can put multiple templates in one source file.
    A tool, not a framework. Works well in any web application environment in conjunction with any libraries, frameworks, or other tools.
    Battle-tested. Closure Templates are used extensively in some of the largest web applications in the world, including Gmail and Google Docs."


    The Closure Templates site has a wealth of information including tutorials and reference material, it can be found at http://code.google.com/closure/templates/


    Templates

    Templates consist of soy files, these files contain one or more template definitions (similar to a macro definitions in ftl) and end with the extension .soy.  They can be found in subversion for sbs under /src/resources/resources/soy/. Note that each one should be stored in it's own namespace folder.  For example the soy files used for microblogging are stored in /src/resources/resources/soy/microblogging, currently this consists of just status-drop-down.soy.  Soy files are used to generate either JavaScript or Java files that do the work of instantiating the templates.  The generation of these files can be done using either using SoyToJsSrcCompiler.jar or programatically in Java. We'll be compiling them programatically, which just requires that you include your soy file resources using the resource.template macro (similar to how we use the resource.javascript macro), an example of this is <@resource.template file="/resources/soy/microblogging/status-drop-down.soy"/>.  This will automatically compile the soy file listed into javascript and include it with the necessary  javascript dependencies.

     

    The following is an example borrrowed from http://code.google.com/closure/templates/docs/helloworld_js.html, it's suggested you look at the examples listed on that page when getting started:

     

    {namespace examples.simple}
    /**
    * Greets a person using "Hello" by default.
    * @param name The name of the person.
    * @param? greetingWord Optional greeting word to use instead of "Hello".
    */
    {template .helloName}
      {if not $greetingWord}
        Hello {$name}!
      {else}
        {$greetingWord} {$name}!
      {/if}
    {/template}

    This template renders "Hello SomeName" by default, where SomeName would be whatever value was passed to the parameter name. If the optional parameter greetingWord was set the output would be "SomeGreetingWord SomeName" where SomeGreetingWord would be whatever value was passed to the parameter greetingWord and SomeName would again be the value held by parameter name.  Some things to note about this example are template commands are surrounded by braces, templates should be namespaced using the namespace command, template names should start with a '.' character (failure to do this can result in some baffling bugs), and variables and parameters are referenced using $paramName.  You read about all the valid commands, concepts, and methods at http://code.google.com/closure/templates/docs/commands.html and http://code.google.com/closure/templates/docs/concepts.html.  Note how the params are specified in the comments prior to each template definition, failure to add params used in the template here will cause your template to fail to compile.  Also note that "param?" indicates a parameter that is optional, as opposed to just "param".

     

    Generated Javascript

    This template is then used to generate javascript, the result of which looks like:

     

     

    // This file was automatically generated from simple.soy.
    // Please don't edit this file by hand.

    if (typeof examples == 'undefined') { var examples = {}; }
    if (typeof examples.simple == 'undefined') { examples.simple = {}; }


    examples
    .simple.helloWorld = function(opt_data, opt_sb) {
     
    var output = opt_sb || new soy.StringBuilder();
      output
    .append('Hello world!');
     
    if (!opt_sb) return output.toString();
    };


    examples
    .simple.helloName = function(opt_data, opt_sb) {
     
    var output = opt_sb || new soy.StringBuilder();
      output
    .append((! opt_data.greetingWord) ? 'Hello ' + soy.$$escapeHtml(opt_data.name) + '!' :
          soy
    .$$escapeHtml(opt_data.greetingWord) + ' ' + soy.$$escapeHtml(opt_data.name) + '!');
     
    if (!opt_sb) return output.toString();
    };

     

    When generating the source code the compiler will halt on any syntax errors, these will be bubbled up to the console output where you can see them or displayed when using SoyToJsSrcCompiler.jar.  The errors can sometimes be a bit cryptic and other times the compiler will happily compile templates that have errors in them, hopefully google will address these issues in the future.

     

    Template Use

    Usage of templates in javascript is pretty straight forward.  You make a call the template function using the proper namespacing and handing it a parameter object.  A paramter object is just a javascript object that consists of key value pairs, where the values can be other objects, arrays, or scalar values (very much in the spirit of json notation).  The function returns a string that can be inserted into the dom any manner you feel fit.  In the example above we could write the string into the document like so:

     

     

    document.write('<hr>' + examples.simple.helloName({name: 'Brian', greetingWord:'Yo yo yo'}));

     

     

    or insert it into a dom element like so:

     

    $j.('body').append(examples.simple.helloName({name: 'Brian', greetingWord:'Yo yo yo'}));

     

    You can of course do all kind of other stuff like call another template from within a template, escape output, and call various commands.  Check out the docs at http://code.google.com/closure/templates/ for more info.

     

     

    Importing Templates (<#include> and <@resource.template> equivalent)

     

    Server side templates may need to include other server side templates. This is currently done using the freemarker tag <#include>.

    Futhermore, a template executed on the server-side which renders html and javscript back to the client may also need to include additional soy templates that are intended to be executed on the client. This is currently done in freemarker using the <@resource.template> tag.

    Those client side templates may themselves also need to include other client side templates.

     

    The jive soy framework provides a simple dependency mechanism to import other templates, both for use in server side and client side.

     

    For example, in order import the template bar, which resides in the namespace foo, add this in the SoyDoc of any given template, or at the head of the Soy file if the dependencies are shared for every template:

     

    @depends template=foo.bar

     

    Or you may use a wildcard to import all templates within the given namespace:@depends template=foo.*
    

     

    The dependency framework will traverse the dependencies of all included templates, so it is not necessary to figure out the entire dependency graph for a given template; you only need to understand the immediate dependencies for your template.

     

     

    You can pass additional parameters.  The full set of supported parameters is:

     

    @depends template=<yourname-space>.your-template id=<identifier> header=[true|false] plugin=pluginname scope=[client|server]

     

    All parameters are optional except for path.  If scope is not specified, the resource will be included in both the compilation (server side) as well as to javascript (client side).  If server side scope is specified, then the 'id', 'header', and 'plugin' parameters are meaningless.

     

     

    Callling another template dynamically

     

    The Soy framework only supports calling a template with a hardcoded name. We have had to add a plugin function in order to execute a template with the template's name from a soy variable. For example, to execute a template whose name is stored in viewbean:

     

        {render($activity.content.activityTemplate, $activity) | noAutoescape}

     

    In order for this to work in the client, the template will need to be imported. However, at compile time you may not know the names of all the possible templates, especially if these templates are pluggable templates provided by a plugiin. To do this you must @depends clause with a wildcard on the namespace.

     

    For example, to import all activity templates to fulfill the above add the following to the soy javasdoc:

     

      @depends template=jive.eae.*

     

    The render() function takes two or more arguments.  When called with two arguments, the first argument is the template name and the second is a data object.  All data members of the object put into a map, allowing them to be declared and referenced individually in the template:

     

    /**
    * Renders a single thumbnail item from a {@link BrowseViewItemBean} instance.
    *
    * @param link A relative link to the item.
    * @param iconCss An icon representing the item.
    * @param subject The item subject.
    * @param author The item author.
    * @param modificationDate The item modification date.
    */
    {template .thumbnailContentItem}  ...
        {$link}
    
    
    

     

    When called with more than two parameters, all parameters after the first are treated as name/object pairs.  The name/object pairs are put into a map and passed to the template.

     

            {render($template, 'item1', $item1, 'item2', $item2) | noAutoescape}

     

    In this case, template parameters would be declared and referenced like this:

     

    * @param item1   The first item.
    
    {$item1.someparam}
    
    
    
    

     

    Alternatively you can use the Map() function described below to pass multiple parameters to the template.

     

    If you need to call the function with both an object and a series of name/object pairs (much like the "call" directive when used with both the 'data' attribute and 'param' parameters), you can use the reserved argument keyword '_data'.  An example of this is below.

     

     

            {render($template, '_data', $object, 'item1', $item1, 'item2', $item2, ...) | noAutoescape}

     

     

    The name/object pairs will be merged with the object to create the template context.

     

    Finally, you can also call the render function with only a template name as a parameter.  This can be useful in prototyping or for fully static templates:

     

         {render($template) | noAutoescape}

     

     

    Importing Javascript Resources (<@resource.javascript file=''> equivalent)

     

    The <@resource.javascript file=> tag in freemarker allows you to import javascript files into a page using jive's resource marshaller mechanism. The resource marshaler will combine all requests to javascript files into a single request for one large combined javascript file.

     

    To achieve the following in freemarker

     

    <@resource.javascript file='/path/to/your-js.js' />

     

    add this in the SoyDoc of any given template, or at the head of the Soy file if the dependencies are shared for every template:

     

    @depends path=/path/to/your-js.js

     

    You can add additional parameters to do stuff like group dependencies into resource bundles.  The full set of supported parameters is:

     

    @depends path=/path/to/your-js.js id=core header=[true|false] plugin=pluginname

     

    All parameters are optional except for path.  If no grouping id is required then leave off the parameter.

     

    Here is what a JavaScript import looks like in context:

     

    /**
    * Runs the greeting app.
    *
    * @param name your name
    *
    * @depends path=/resources/scripts/apps/greeting_app/main.js
    */
    {template .greeting}
        <h1 id="greeting-app-message">Hello, {$name}!</h1>
    {/template}
    
    
    

     

    Importing Inline Javascript Resources (<@resource.javascript> equivalent)

     

    The <@resource.javascript> tag (no file attribute) in freemarker allows you to add inline javascript into jive's resource marshaller mechanism. This main purpose of this is to 1) group all inline javascript into one block and 2) output the block in one go much later in the output stream. Outputting the javascript much later in the page rendering is necessary if the code relies on other javascript components or DOM elements to be present. Typically javascript code is inlined (as opposed to residing in it's own file) if the code needs to be constructed dynamically using template variables.

     

    To achieve the following in freemarker

     

    <@resource.javascript>var a = $somevar;</@resource.javascript>

     

    Use:

     

    {call jive.shared.soy.resourceInlineJs}{param code}var a = 'Hello, Mark!';{/param}{/call}
    Note that any {curly braces} in your inline JavaScript code will have to be escaped.  Instead of { use {lb} and instead of } use {rb}.
    You can pass additional parameters in case you need to do something like associate the script with a specific resource bundle.  Pass "id" and "header" parameters like this:

     

    {call jive.shared.soy.resourceInlineJs}{param code}var a = 'Hello, Mark!';{/param}{param id}core{/param}{param header}true{/param}{/call}

     

    The id and header params are optional.

     

    Note this function only has an effect when the template is rendered server side. It will no-op and render an empty string when executed on the client.

     

    Here is what inline JavaScript looks like in context:

     

    /**
    * Runs the greeting app.
    *
    * @param name your name
    *
    * @depends path=/resources/scripts/apps/greeting_app/main.js
    */
    {template .greeting}
        <h1 id="greeting-app-message"></h1>
    
        {call jive.shared.soy.resourceInlineJs}
            {param code}
                $j(document).ready(function{lb}
                    var greetingApp = new jive.GreetingApp.Main({lb}
                        name: "{$name}"
                    {rb});
                {rb});
            {/param}
        {/call}
    {/template}
    
    
    

     

     

     

    Rendering a Soy Template from within a Freemarker Template

     

    The <@soy.render> tag allows you to call and render a soy template from within a freemarker template. Previously the only way to include a soy template from freemarker was to use an Action include where the Action would return a soy result. The tag allows any object to be passed - the object will be the model that the soy template requires. The syntax of the tag looks like this:

     

    <@soy.render template="<template name>" data=<object from freemarker model> />

     

     

     

    Importing DWR Client Side Scripts (<@resource.dwr> equivalent)

     

    To achieve the following in freemarker

     

    <@resource.dwr file="YourDWRComponent" />

    add this in the SoyDoc of any given template, or at the head of the Soy file if the dependencies are shared for every template:

     

    @depends dwr=YourDWRComponent

     

    You can pass additional parameters.  The full set of supported parameters is:

     

    @depends dwr=<DWR Component Name> id=<identifier> header=[true|false]

    All parameters are optional except for dwr.

     

     

    Flushing Marshalled Resources to output stream (<@resource.xxxx output='true'> equivalent)

     

    Each of the <@resource.xxx> tags in freemarker have an output attribute which instructs the resource marshaller to immediately flush it's contents to the output stream. In order to achieve the same behavior in soy templates use the outputResources function.

     

    E.g.

     

    <@resource.template output='true' id='core' header='false' />
    <@resource.script output='true' id='core' header='false' />
    <@resource.css output='true' id='core' header='false' />
    

     

    can be achieved with:

     

    {outputResources('template','core',false')|noAutoescape}
    {outputResources('script','core',false')|noAutoescape}
    {outputResources('css','core',false')|noAutoescape}

     

    Constructing Static Resource URLs (<@resource.url> equivalent)

     

    The <@resource.url> tag in freemarker allows you to construct a URL for referencing static resources. The main benefits is that it automatically handles versioning and correctly resolves to the location where the static resources reside (which may be outside the application, i.e. hosted through apache).

     

    To achieve the following in freemarker

     

    <@resource.url value='images/myimage.png' />

     

    Use:

     

    {resourceUrl('/images/myimage.png')}

    This function will work with both server and client side executed templates.

     

     

    Constructing Application Resource URLs (<@s.url> equivalent)

     

    The <@s.url> tag in freemarker allows you construct a URL to a resource in the application. This is almost always a URL to execute an action, but there may be other resources that you might want to access. The tag also allows you to add parameters which will be added to the URL as GET query parameters.

     

    To achieve the following in freemarker

     

    <@s.url value='/resource'><@s.param name='param1'>value1</@s.param><@s.param name='param2'>value2</@s.param></@s.url>

     

    Use:

     

    {buildUrl('/resource','param1','value1','param2', 'value2') |noAutoescape}

    Alternatively this function can take a map as it's second parameter, as such:

    {buildUrl('/resource', $myParameterMap) |noAutoescape}

     

    This function will work with both server and client side executed templates.

     

     

    Displaying i18n Text

     

    The Jive Closure Template framework provides a plugin function for displaying i18n messages from our resource bundle according to the user's current locale. This plugin works for both server side and client side templates. The function takes the key of the message as the first argument, and optionally arguments for the placeholder subsitution.

     

    {i18nText('your.key','first placeholder','second placeholder')}{i18nText('another.key',$somevariable)}
    
    

    The soy framework automatically parses the template file to determine all the i18n keys being referenced then adds these keys as depenencies. When the request is processed the dependent keys are resolved according to the locale and phrase subsitutions in place. However, if the key passed to the i18nText function is not a literal but rather a variable which is resolved at runtime then the framework is not able to determine the dependency. Therefore you must explicitly declare the i18n dependency yourself. You can do this using the @depends annotation. e.g.

     

         @depends i18nKeys=your.key

     

    You can also use wildcards to declare dependencies multiple keys at once, e.g. the following will declare a dependency on all keys that begin with "activity.type."

     

         @depends i18nKeys=activity.type.*

     

     

    When using wildcards it is important to carefully consider your i18n key naming to appropriately limit the number of messages downloaded. A value of i18nkeys = * will download every i18n message defined in the system!

     

    Calling the i18nText function directly has a couple of limitations. First all the parameters passed will be autoescaped. Secondly you might want the value of these parameters to come from the result of executing another template. Therefore a helper template has been provided:

     

        {call jive.shared.soy.i18nHelper}{param i18nKey}your.key{/param}{param arg0}first argument - could be a call to another template{/param}{param arg1}another arg{/param}{param noAutoEscape}true{/param}{/call}

     

    When using this template you must expicitly declare your key using the @depends annotation since the key is essentially a variable that is evaluated at runtime.

     

    Remember: You only need to explicitly import the key with @depends if the key parameter is not a literal but rather a variable or if you are using the i18nHelper template.

     

     

    Working with Collections

     

    We have some plugins for creating lists or maps within a template and for manipulating lists and maps.  To create a list use the List() function:

     

    {foreach $n in List(1, 1, 2, 3, 5)}
        fib: {$n}
    {/foreach}
    
    
    

     

    Remember that you cannot assign a list that you create to a variable - you can only pass it to a function or to another template.

     

    You can concatenate lists, reverse lists, or get the index of a certain value in a list:

     

    {concat(List(1, 2), List(3, 4))}  /* results in List(1, 2, 3, 4) */
    {concat(List(1, 2), 5)}  /* results in List(1, 2, 5) */
    {reverse(List(1, 2, 3))}  /* results in List(3, 2, 1) */
    {indexOf(List(1, 2, 3), 3)}  /* results in 2 */
    {indexOf(List(1, 2, 3), 5)}  /* results in -1 */
    
    
    

     

    You can also construct maps.  This can be particularly helpful when you are using the render() function: if you want to pass more than one top-level parameter to a dynamically rendered template constructing a map is the only way to do that.  To create a map use the Map() function in combination with the p() function:

     

    {render($myTemplate, Map(p('someParam', $someParam), p('anotherParam', $anotherParam)))}
    
    
    

     

    p() is shorthand for "pair" - it creates a two element list to group each key with its value.  The first element in the pair should be your key and the second element should be the corresponding value.

     

    Any type of value may be used as a map key but that value will be coerced to a string.  So make sure that your map keys do not have colliding string representations.

     

    There is one map-manipulating function available: extend().  This function takes two or more maps as arguments and returns a new map that combines all of the mappings of the arguments.  If any of the given maps have the same keys then the rightmost argument takes precedence.  extend() behaves much like jQuery.extend() does - in fact the JavaScript implementation of extend() is implemented using jQuery.extend().  But unlike jQuery.extend() the soy version will not modify any of its arguments in place.

     

    Here is an example of extend() in action:

     

    {extend(Map(p('foo', 1)), Map(p('bar', 2)))}  /* results in Map(p('foo', 1), p('bar', 2)) */
    
    
    

     

    You can get the length of a collection with

     

    length($collection)

    Escaping and Unescaping

     

    By default, any dynamic output in soy templates is HTML-escaped.  Effectively the |escapeHtml filter is implicitly added everywhere where no other filter is specified.  If you want to output HTML you can use the |noAutoescape filter - though in Jive 6.0 or later it is preferable to use the SanitizedHtml type where possible.

     

    Anything that is output in a JavaScript string should be escaped using the |escapeJs filter.  And any values that appear in URL parameters should be escaped with the |escapeUri filter:

     

    <script>
        var someData = '{$someData |escapeJs}';
        $.post('/resource?q={$someParam |escapeUri}', someData);
    </script>
    
    
    
    
    
    

     

    A complete list of print directives is available from Google: http://code.google.com/closure/templates/docs/commands.html#print

     

    In the latest version of soy you can get more powerful escaping features using the autoescape="contextual" feature.  Jive is using this version of soy in versions 6.0 and up.  To enable contextual autoescaping, add the appropriate attribute to your soy namespace declarations:

     

    {namespace my.soy.namespace autoescape="contextual"}
    
    
    
    
    
    

     

    When contextual autoescaping is enabled the soy compiler will detect the context in which values are output and will apply the appropriate filter automatically.  Text output in HTML content will be HTML escaped.  Text output in HTML attribute values will be escaped differently: any HTML tags will be stripped out and special HTML characters, like quotes will be escaped.  Text in the special HTML attribute values "src", "href", and any attribute with a name of the form "data-whatever-url", will be URI-escaped.  Soy will even detect whether your output appears in the URL path or in a query string component and will act accordingly.

     

    But note that even with contextual autoescaping you will need to escape content in JavaScript strings explicitly.  Since we use a helper to output JavaScript instead of regular script tags, soy cannot detect which portions of a template are JavaScript code.

     

    For more information about escaping in soy, see the soy security documentation: http://code.google.com/closure/templates/docs/security.html

     

    Formatting Dates

     

    This function formats epoch date (milliseconds) into preset formats.  The format masks are "short", "medium", "long" (default).   The date time mask values are "date", "time", or "datetime" (default).  The second and third arguments are optional.

     

     

    {formatDate('102937846823','long','date')}

     

    This function will work with both server and client side executed templates.

     

     

    Outputting JSON

     

    This function outputs JSON from a passed object.

     

     

    {buildJson($object) | noAutoescape}

     

    This function will work with both server and client side executed templates.

     

     

    Generating Random Strings

     

    This function outputs random strings.  This is useful for creating unique dom ids.

     

     

    {randomString()}

     

     

    This function will work with both server and client side executed templates.

     

     

    Converting Strings To Integers

     

    This function converts Strings to Integers.  Serverside compiled soy does not support conversion of strings to ints, you can use this function to get around that roadblock.

     

     

    {stringToInt($stringObj)}

     

     

    This function will work with both server and client side executed templates.

     

     

    Converting Strings To Floats

     

    This function converts Strings to Floats.  Serverside compiled soy does not support conversion of strings to floats, you can use this function to get around that roadblock.

     

     

    {stringToFloat($stringObj)}

     

     

    This function will work with both server and client side executed templates.

     

     

    Intellij File Type

    Intellij doesn't really come with a file type that is ideal for use with soy files.  I ended up creating my own custom file type that is better than nothing, but I don't know how to export it.  You can accomplish the same by opening File -> settings -> File Types (under IDE Settings on the left).  Click "Add.." in the recognized file types section and insert the values from this screen shot:

     

    Download this Soy Files.xml file and copy to filetypes folder in your Intellij config folder. On my Ubuntu machine this folder lives in my home folder at: ~/.IntelliJIdea80/config/filetypes

     

    Restart Intellij and go to File -> settings -> File Types (under IDE Settings on the left) and you should see this file type registered.

     

    soy_file_type_add.png

    You should be able to paste the following keywords into the Keywords area:

     

     

    call

    case

    css

    default

    else

    elseif

    for

    foreach

    if

    ifempty

    literal

    msg

    namespace

    param

    print

    switch

    template

     

    Click "OK" and make sure Soy Files is selected from the "Recognized File Types" section.  Click "Add" in the "Registered Patterns" and enter "*.soy".

     

    Troubleshooting

    When things go wrong there a number of steps you can take to determine what is causing the issue:


    1. Check the console output to see if there were any errors when compiling your template.
      Often a tiny syntax mistake can result in your template not compiling correcting and any javascript using that template will likely raise an exception.
    2. Check the generated javascript for errors.
      Looking at the generated javascript and setting breakpoints as necessary can also be helpful in determining what went wrong.
    3. Talk to a developer familar with closure templates.
      Feel free to talk to Jesse Hallett, Mark Williams or Dolan Halbrook if you are still having issues