| Overview We're adding templates to OpenSocial!
This document describes a proposal for how we can add templating support to OpenSocial. It's orgainzed into the following sections:
If you're interested in getting a flavor for how templates would work without reading too much, you can:
Goals The goals and requirements we are aiming to meet with the proposal:
Proposal Outline OpenSocial templates will be a feature that you can add to your gadget using <Require feature="opensocial-templates"/>. The templates are are XML fragments that can contain both raw HTML and custom tags.
<script type="text/os-template"> Welcome, ${Top.Viewer.Name} </script>
After loading the initial social data via a call to the OpenSocial APIs, you will call opensocial.template.renderAll() with the data to render all of these templates. These templates will be displayed in the same location in the document as the original <script> tags. Example: <script type="text/javascript"> opensocial.template.renderAll(socialDataFromOpenSocialApiCall); </script>
We also intend to support putting the template XML directly into the gadget spec instead of in a <script> tag. There is not a concrete proposal on this functionality yet, but one option is to add these templates as children of the <Require> tag.
Expressions Expressions can be embedded into text nodes and attributes the template XML using the syntax ${Expr} - see ${Top.Viewer.Name} above. This is the same syntax we are using for expressions in Activity Stream templates.
The variables in these expressions can be evaluated against OpenSocial objects (i.e. the Viewer) or against raw JSON objects passed into the templating system when rendering. Tag Libraries There will be a standard set of OpenSocial tags prefixed (by convention) with "os:".
However the templating system supports multiple markup libraries. It will be extensible and agnostic to the specific tags libraries used, so containers, library developers, and gadget authors can all define their own namespaced tag libraries. For example, Google may create a standard YouTube player using <goog:YouTube>, or a library author could create the same functionality using <mylib:YouTube/>.
We are working on a concrete proposal for the initial set of OpenSocial tags.
Flow Control The templating language supports showing repeated elements and showing elements conditionally.
Conditional elements use an "if" attribute on the element - <div if="${Top.Score gt $Top.OldHighScore}">You have the new high score!</div>, and repeated elements use a "repeat" attribute: <div repeat="${Top.ViewerFriends}">Your friend is: ${Cur.Name}</div>. Both HTML elements and custom tags can use these flow control constructs.
Defining Templates Templates can be defined by adding a name attribute to the <script> tag or surrounding XML element. This template can be called by using an element with the same name.
Definition: <script type="text/os-template" name="os:HelloWorld"> <div class="hello">Hello World!</div> </script>
and usage: <script type="text/os-template"> <os:HelloWorld/> </script>
Parameters can be passed into these templates, both as attributes and as child XML elements. This is discussed in more detail in Calling Templates and Passing in Content.
Examples We'll walk through some of the key proposed functionality of OpenSocial templates with the following examples:
You can install all of the sample gadgets on iGoogle. These sample gadgets are slightly modified - they use <script src=""/> to load the a demo JS file directly.
We have also created a standalone demo page with a number of examples of the types of templates we'd like to build.
1. Hello World This is the simplest possible template. It doesn't actually do anything useful, but highlights how templates can be inserted into a document.
<?xml version="1.0" encoding="UTF-8"?> <Require feature="opensocial-0.7"/> <Require feature="opensocial-templates"/> <script type="text/os-template"> <div style="font-size: 20px">Hello world!</div> </script> <script type="text/javascript"> // loadSocialData() is a function that calls OpenSocial APIs and returns your data loadSocialData(function(socialData) { opensocial.template.processAll(socialData); }); </script> ]]> </Content> </Module>
2. Variable Substitution Here's an example of passing data into templates and using variable substitution:
<?xml version="1.0" encoding="UTF-8"?> <Require feature="opensocial-0.7"/> <Require feature="opensocial-templates"/> var data = { Viewer: { Name: 'Opensocial Bob' } };opensocial.template.processAll(data); </script> ]]> </Content> </Module>
3. Using OpenSocial Tags This example shows how to call an OpenSocial tag and also shows how this tag would be implemented by a container.
<?xml version="1.0" encoding="UTF-8"?> <Require feature="opensocial-0.7"/> <Require feature="opensocial-templates"/>
<!-- Sample definition of os:ShowPerson. --> <!-- This would normally be in the container libraries --><script type="text/os-template" name="os:ShowPerson"> <img width="32" height="32" if="${My.person.ThumbnailUrl}" src="${My.person.ThumbnailUrl}" style="padding-right: 5px"/> <a href="${My.person.ProfileUrl}">${My.person.Name}</a> </script> ]]> </Content> </Module>
4. Conditional Content This example shows how to create conditional content.
<?xml version="1.0" encoding="UTF-8"?> <Require feature="opensocial-0.7"/> <Require feature="opensocial-templates"/> </Content> </Module>
5. Repeated Elements This example shows how to create repeated UI elements based on OpenSocial data or your own data.
<?xml version="1.0" encoding="UTF-8"?> <Require feature="opensocial-0.7"/> <Require feature="opensocial-templates"/> </Content> </Module>
6. Passing in Content This example shows how to pass in content into templates:
<?xml version="1.0" encoding="UTF-8"?> <Require feature="opensocial-0.7"/> <Require feature="opensocial-templates"/> </Content> </Module>
7. Custom Templates Gadget developers and library authors can define their own custom templates.
<?xml version="1.0" encoding="UTF-8"?> <Require feature="opensocial-0.7"/> <Require feature="opensocial-templates"/> </Content> </Module>
Proposal The proposal is organized into the following sections:
Basic Template Format The canonical template format is a well-formed XML document. This document can include HTML tags which will be output directly when rendering the template, and custom tags which will be evaluated when rendering the template. Example:
<Template>
A common use case will be embedding this XML into an HTML document. The following snippet can be embedded directly into a gadget <Content> section or HTML document:
<script type="text/os-template">
We will have the ability to define template markup libraries, which will map to XML namespaces. The set of common OpenSocial functionality will be implemented as an XML namespace with the common prefix "os:".
Expressions Expressions can be embedded into the template XML using the syntax ${Expr}. This is the same syntax we are using to identify expressions Activity Stream templates. Example template content with an expression:
<div>Hello ${Top.Viewer.Name}</div> Expressions are defined using JSP Expression Language, with a couple of minor modifications. This allows for expressions that are raw variable references, as in the example above, or expressions with operators:
<div>Next step is ${Top.Step + 1}</div> JSP Expression Language allows you to use most standard operators (comparison, arithmetic, etc.), although some operators have an alternate name for use in XML. ${a < b} becomes ${a lt b}, ${a > b} becomes ${a gt b}, ${a && b} becomes ${a and b}, etc.
Expressions are usually evaluated as strings. The only exception is when expressions are in attributes without additional text content, and in this case, the value of the expression is the object referenced, and this object is passed to the template for processing. In the following example, the viewer is passed in to the os:ShowPerson template.
<os:ShowPerson person="${Top.Viewer}"/>
The rules for how to evaluate variables are covered in the next section.
Variables Data is passed in to all calls to render templates - i.e.: opensocial.template.processAll(data). The data passed in can be a JSON object, or as an object with a "getProp" method (example below).
"Top" refers to the data context passed in via Javascript, so ${Top} will return the data passed into the template processing system.
Variables are accessed using Foo.Bar (or Foo.Bar.Baz) notation. ".Bar" maps to getting the property "Bar" on the JSON object, so if Top and Viewer are JSON objects, then ${Top.Viewer.Name} evaluates to data['Viewer']['Name']
If an object has a getProp method, then this method is called instead of using the property notation. So if Top and Viewer support this interface, then ${Top.Viewer.Name} evaluates to data.getProp('Viewer').getProp('Name')
OpenSocial core objects will be modified to add getProp() support so that you can access properties by name.
Open Issues:
Special Variables There are a number of special variables that are available as part of template processing.
These will be discussed in more detail where they are used, but a quick overview:
Special variable names are optional - expressions will be evaluated against each of the special variable contexts, and the first matching variable will be the result of expression evaluation. The order of precedence is $My, $Cur, $Context, and then $Top. So ${Cur.Name} == ${Name} unless there exists ${My.Name}. The following examples show why this is helpful:
Example if special variables are required <div repeat="${Top.ViewerFriends}"> <div>{$Cur.Name}"</div> <div repeat="${Cur.Phone}">${Cur.Number} of type {$Cur.Type}</div> </div>
Example if they are optional: <div>{$Name}"</div> <div repeat="${Phone}">${Number} of type {$Type}</div> </div> Calling Templates Templates can be called by other templates.
You can add a "name" attribute to any template (both on the <Template> element or on the <script> tag for embedded templates). This allows the template to be called by using an XML element of the same name.
Example of defining a template and then calling it:
<script type="text/os-template" name="myapp:HelloWorld"> <div style="font-size: 40px">Hello World</div> </script>
<script type="text/os-template"> <myapp:HelloWorld/>
Parameters can be passed into templates as XML attributes or elements. These parameters are accessed using the special variable ${My}:
<script type="text/os-template" name="myapp:HelloWorld"> <div style="color: ${My.myapp:MessageStyle.color}">Your message is: ${My.message}</div> </script>
<script type="text/os-template"> <myapp:HelloWorld message="Hello World"> <myapp:MessageStyle color="blue"/> </myapp>
${My.foo} will first look for an attribute named foo in the calling template, and next will look for an element named foo.
Content can be displayed conditionally based on evaluation of an expression.
Elements with an "if" attribute will only be displayed if the "if" attribute evaluates to true. For example, the following <div> will only be displayed if Top.YourScore == Top.HighScore: <div if="${Top.YourScore == Top.HighScore}">You have the high score of ${Top.YourScore}!</div>
The contents of an <If> element are only displayed if the "expr" attribute evaluates to true. The example above rewritten using the <If> element:
<If expr="${Top.YourScore == Top.HighScore}> </If>
The if attribute can also be used on:
Open Issues
Repeated Elements Tags can be rendered multiple times based on evaluation of an expression. Tags with an "repeat" attribute are displayed once for each item in evaluating the expression in the "repeat" attribute. The current item in the repeated list will be put into the ${Cur} variable.
Example: <div repeat="${Top.ViewerFriends}"> Your friend's name is ${Cur.Name}
In addition, developers will be able to set he current item to a different variable name, by adding a "var" attribute. Example: <div repeat="${Top.ViewerFriends}" var="Friend"> Your friend's name is ${Friend.Name}
There are two context variables available during list processing. ${Context.Count} is the count of items in the list and ${Context.Index} is the index of the current item. Example: <div repeat="${Top.ViewerFriends}"> Showing friend ${Context.Index} of ${Context.Count}: Your friend's name is ${Cur.Name}
The contents of an <Repeat> element displayed once for each item in evaluating the expression in the "expr" attribute. The current item in the repeated list will be put into the ${Cur} variable, and an @var attribute can also be used to rename the iteration variable.
<div> Your friend's name is ${Cur.Name} </Repeat> and
<Repeat expr="${Top.ViewerFriends}" var="Friend"> <div> Your friend's name is ${Friend.Name} </Repeat>
Open Issues:
Passing in Content Child elements of tags that are used to call templates are passed as parameters to the template and are accessible via the ${My} variable.
If these elements have content inside of them, this content can be rendered using <RenderAll content="${Expr}"/>, where ${Expr} evaluates to a content node. Example of defining a template that uses <RenderAll/> and calling this template:
<script type="text/template"> <script type="text/template" name="myapp:BoxWithTitle"> <div class="box-title"><RenderAll content="${My.os:Title}"/></div> <div class="box-content"><RenderAll content="${My.os:Content}"/></div>
Note that the passed in content can include other template tags. These tags are displayed as part of calling <RenderAll/>.
You can also call <RenderAll/> without a "content" attribute. This will render all children of the calling node - this is a useful shorthand for tags with only one content section.
Example:
<script type="text/template"> <script type="text/template" name="myapp:BJsLink"> <a href="#" onclick="${My.action}; return false;"><RenderAll/></a>
Template Libraries Templates can be packaged into a standalone XML file of the following format:
<?xml version="1.0" encoding="UTF-8"?>
When this template library is loaded, a template is created for each of the <Template> child nodes of the top level <Templates> element.
Containers should load the <os:*> template tags by default, and it is not required that they define this as a standalone XML file. Other tag libraries can be loaded by default in a container - for example, Google might have a set of <goog:*> tags available.
Developers can refrence additional template libraries using opensocial.template.registerTemplateLibrary(uri). The contents of these libraries are guaranteed to be available for processing by opensocial.template.processAll() - this function will wait for libraries to load before rendering the templates.
Issues:
Localization Templates will have the ability to substitute localized text content on a per-language basis.
Gadgets already have a facility for defining localized messages. In the gadget prefs, you can specify the URL for localized message bundles on a per-language basis: <ModulePrefs title="ListenToThis"> Each bundle is a list of <msg> elements with localized content inside: <messagebundle> Also note that in OpenSocial API 0.8, you will be able to inline <msg> elements into the gadget directly without creating separate files for each locale.
On processing of templates for a given locale, the content of localized tags will be replaced with matching content from the message bundle before processing. Localizable tags can also include default content - this content is displayed in development mode and also when there is no matching message in the message bundle.
There are two open issues for which we are finalizing a proposal - please feel free to comment on the spec mailing list if you have opinions:
Issues:
JavaScript in Templates Listed below are are four ways that JavaScript can be defined and called from OpenSocial templates. All JavaScript used in templates may be rewritten using Caja or ignored by the container processing the templates, both for security and for performance reasons.
1. JavaScript functions can be called in event handlers Example: <a href="#" onclick="doSomething();return false">Do something</div>.
These attributes can also use expressions, so you can have: <a href="#" onclick="${action}; return false">Do Something</a>
2. JavaScript functions can be defined in <JavaScript> tags These are functionally equivalent to <script> tags, but are needed for syntax reasons when writing templates inside of <script type="text/os-template">. Example: <script type="text/os-template name="AlertButton"> <JavaScript id="AlertButtonJs"> function showAlert() { alert('Warning! Warning! Danger! Danger!'); } </JavaScript> <button onclick="showAlert()">Click for alert</button> </script>
3. Inline JavaScript calls can be defined in <JavaScript> tags. Example: <script type="text/os-template name="RegisteredButton"> <button id="${Context/Id}">text</button> <JavaScript> callSomeFunctionThatRegistersButton('${Context/Id}'); </JavaScript> </script>
4. External JavaScript libraries can be loaded using <JavaScript> tags. Example: <script type="text/os-template name="AlertButton"> <JavaScript src="http://www.opensocial.org/showAlert.js"/> <button onclick="showAlert()">Click for alert</button> </script>
The following rules are followed when processing <JavaScript> tags.
|