What is RenderJs?

RenderJs is a JavaScript library which uses jQuery, RequireJS and Jio. RenderJs provides an easy way to define gadgets (aka mashups) in pure HTML5 and does not require application server. It handles dependencies through RequireJS, caching and interaction. It is suitable for the development of mobile applications, desktop applications. It is used by OfficeJS, ERP5.

What a gadget looks like?


<html>
  <head>
 <link type="text/css" rel="stylesheet" href="say-hello.css"/>
 </head>
  <body>
    Hello from the gadget!
  </body>
</html>

Gadgets is nothing more than a HTML(5) page which includes all required CSS or JavaScript files. In order to create a gadget simply create an HTML5 page using your favorite text editor. As gadget does not require application server you can use any operating system.

Why use gadgets?

We believe it is possible to develop separate components or applications (aka gadgets) that can be used in browser ideally on any device that can show a HTML5 page. By splitting into separate gadgets we make reusable components that have their own API and can communicate each other. At the end using gadgets introduces rules which when followed stop developer from reinventing the wheel and overall save time to develop new cool features.

How to use a gadget inside a page?

See following example which uses gadget from above:

 <html>
  <head>
    <script data-main="require-renderjs.js"
            type="text/javascript"
            src="require.js"></script>
  <head>
  <body>
    <div id="say-hello"
         data-gadget="say-hello.html"
         data-gadget-cacheable="0"
         data-gadget-cache-id="say-hello"></div>
  </body>
</html>

Code above will make RenderJs load asynchronously gadget which will be identified uniquely into current page by its id="say-hello" and which will be loaded from a remote url equal to "say-hello.html". Gadget's content will not be cached so page refresh will reload gadget. 

How to create a RenderJs application ?

A RenderJs application is simply a gadget which can embed other gadgets which get rendered recursively.

See following example:

<html>
  <head>
    <script data-main="require-renderjs.js"
            type="text/javascript"
            src="require.js"></script>
  <head>
  <body>
    <div id="recursive"
         data-gadget="recursive.html"
         data-gadget-cacheable="0"
         data-gadget-cache-id="recursive"></div>
  </body>
</html>

What this example does it to define a gadget which will be loaded from remote url "recursive.html" which on its turn can contain also gadgets which can contain themselves also gadgets.

How to configure a gadget ?

RenderJs allows developer to control various aspects of a gadget through so called custom HTML5 attributes - i.e. a special type of tag attributes which have a leading "data-". These attributes are defined by developer.

Short description is available below:

  • "ID" (mandatory)- this is not a RenderJs defined attribute  but general purpose HTML one which is unique in the entire HTML page. This ID is used to address a gadget by RenderJs and it is the job of developer to make sure its unique in the page.
  • "data-gadget" (mandatory) - this is the URL of the gadget we want to render. This URL contains pure HTML which can include JavaScript  or CSS code which define how gadget behaves and how gadget appears. As a gadget can contain other gadgets RenderJs will make sure that they are rendered too recursively
  •  "data-gadget-source" (optional,default=null)  - this can be an URL which RenderJs will use to get gadget's data as a JSON based dictionary or even a hard coded JSON dictionary inside (still not implemented)
  • "data-gadget-handler" (optional, default=null) - the ID of a JavaScript function in the local name space that will receive result of "gadget:data-source" call and update gadget. When RenderJs successfully finishes loading data from  "gadget:data-source" it will pass result of "gadget:data-handler" script which can take any action desired with this data (i.e. update DOM, etc).
  • "data-gadget-property" (optional, default={}) - a general purpose gadget JSON dictionary which can be used to configure Gadget's JavaScript object
  • "data-gadget-cacheable" (optional, default=0)- a flag which indicates if gadget's content  is to be cached or not (default=0)
  • "data-gadget-cache-id" (optional, default=null)- an ID which will be used to store gadget's content into cache. It's recommended although not mandatory to use same value as gadget's ID.

How to cache gadget content ?

RenderJs provides through its attributes a way to cache locally into browser the gadget content itself. In some cases this can be a very efficient way to improve performance of a web page as rather than download gadget's HTML every time we can download it once and use many times from much faster local cache. Caching itself is implemented through plugin as RenderJs is able to detect best possible cache plugin. For example in some cases HTML5's localStorage is not available thus local name space cache plugin is used. The choice between  HTML5's localStorageor local namespace plugin is done by a RenderJs itself automatically - i.e. developer has no control over it.

Still caching locally this way leaves developer without any control for how long a cache entry should exists in cache thus it's recommended that developer consider proper HTTP caching for all gadget (including its data resources) and leave caching to browser. In this case RenderJs caching properties can safely be omitted as caching is done at browser level using HTTP caching headers controlled at server side.

 <html>
  <head>
    <script data-main="require-renderjs.js"
            type="text/javascript"
            src="require.js"></script>
  <head>
  <body>
    <div id="say-hello"
         data-gadget="say-hello.html" 
         data-gadget-cacheable="1"
         data-gadget-cache-id="say_hello"></div>
  </body>
</html>

In order to cache a gadget developer must set properly "data-gadget-cacheble" and "data-gadget-cache-id" properties. If any of two is missing or "data-gadget-cacheable"=0 no caching is performed.

How to create interactions between gadgets ?

Currently all gadgets interaction are implemented through an invisible gadget placed inside HTML. Gadgets interactions are not mandatory in case gadgets do not need to interact each other. Here's an example how to define interactions between Gadgets:

<html>
  <head>
    <script data-main="require-renderjs.js"
            type="text/javascript"
            src="require.js"></script>
  <head>
  <body>
    <div id="A"
         data-gadget="A.html"
         data-gadget-cacheable="0"
         data-gadget-cache-id="A"></div>
    <div id="B"
         data-gadget="B.html"
         data-gadget-cacheable="0"
         data-gadget-cache-id="B"></div>
    <div data-gadget=""
         id="main-interactor"
         data-gadget-connection="[
          {"source": "A.jsCall1", "destination": "B.jsCall1"},
          {"source": "A.htmlEvent1", "destination": "B.htmlEvent1"},
          {"source": "A.htmlEvent1", "destination": "A.myOwnHtmlEvent1"},
          {"source": "A.htmlEvent2", "destination": "B.htmlEvent2"},
          {"source": "B.jsCall2", "destination": "A.jsCall2"}]">
    </div>
  </body>
</html> 

n this example we define number of interaction from one Gadget (source) to another Gadget (destination).

For this we use this format:

Gadget_ID.Gadget_JavaScript_Function_ID_OR_HTML_EVENT

It is important to note that we can have two types of bindings:

  • function binding - when a call on A.jsCall1 will trigger after that B.jsCall1
  • HTML (custom) event binding - we bind to an event like A.htmlEvent1 and after that function B.htmlEvent1 will get called

It is possible to have an interaction on InteractionGadget itself that can call multiple function on different gadgets. This approach extends the direct call pattern (A.function -> B.function) to a more generic one like A.function -> InteractionGadget -> B.function. Example of this can be found here.

In order for this system to work a gadgets must define respective functions in a script section like so in its HTML:

<script type="text/javascript" language="javascript">
  //<![CDATA[
     $(document).ready(function() {
         gadget = RenderJs.GadgetIndex.getGadgetById("A");
         gadget.jsCall1 = function (){alert("A.jscall1");};
         gadget.jsCall2 = function (){alert("A.jscall2");};
         gadget.myOwnHtmlEvent1 = function (){
           $("#hide").toggle();
           alert("A.myOwnHtmlEvent1");
         };
     });
  //]]>
</script>

By default RenderJs will automatically "bind" all interactions between gadgets. In cases this is not required this implicit binding can be controlled by global variable  RENDERJS_ENABLE_IMPLICIT_INTERACTION_BIND (default = true).

Why we need interactions in RenderJs?

The core idea of RenderJs is that a gadget is a JavaScript object that provides some "services" over its API (i.e. JavaScript) functions. A gadget may have or have not any relation to the underlying DOM tree which is what browser uses to visualize page to user. But this API acts as a "contract" stating what a gadget may do if properly called from another gadget or arbitrary JavaScript function. Usually this API is quite stable and do not change that often.

Having an API allows RenderJs to allow interaction between gadgets to be controlled easier usually by introducing a special type of an InteractionGadget which acts as a mediator between gadgets. The concept of InteractionGadget allows developers to create complex interactions without having to manually code these calls themselves.

For example when changing a value of a form field in gadget A, this gadget may fire a "change" event which can be caught by  InteractionGadget and passed to gadget B who on its site can acquire changed form field's value bu using Gadget A's API.

This example shows in action following patterns typical for normal object oriented languages: encapsulation, proxing and interaction at once.

How to initialize a gadget?

A gadget is considered a generic component and therefore many gadgets' instances from same origin may exists in one page. RenderJs provides an API which allows developer to initialize each gadget instance individually. This is implemented through "data-gadget-property" attribute which allows to pass a JSON dictionary which will be used to initialize a gadget instance.

Example below show how we can configure a gadget through its parent:


<html>
  <head>
    <script data-main="require-renderjs.js"
            type="text/javascript"
            src="require.js"></script>
  <head>
  <body>
    <div id="init-gadget"
         data-gadget="init-gadget.html"
         data-gadget-cacheable="0"
         data-gadget-cache-id="init-gadget"
         data-gadget-property="{"name": "Ivan"}"></div>
  </body>
</html>

.... and gadget's content (init-gadget.html) itself ....
<html>
  <head><head>
  <body>
    <p>Hello to <span id="name"></span> from the gadget which can be 
    initialized via data-gadget-property attribute from parent gadget!</p>

    <script type="text/javascript" language="javascript">
      //<![CDATA[
      name = RenderJs.GadgetIndex.getGadgetById("init-gadget").name;
      $("#name").html(name);
      //]]>
    </script></body>
</html>

It's important to note that RenderJs will automatically "bind" all interactions between gadgets. In cases this is not required this implicit binding can be controlled by global variable  RENDERJS_ENABLE_IMPLICIT_INTERACTION_BIND (default = true)

How to get a gadget instance ?

One important aspect of RenderJs is that it tries to use DOM tree as little as possible. DOM tree is read usually only at initial page loading time and after that all interactions and manipulations should be implemented within the respective JavaScript object representing the Gadget itself. For this RenderJs will take care to create a Gadget instance for every Gadget it find on the page. This Gadget instance then can be prototyped itself by JavaScript thus creating a of an API for the Gadget. In order to implement this developer should be able to get a gadget instance.

This is the job of a special RenderJs object called GadgetIndex which has two  important methods:

  • RenderJs.GadgetIndex.getGadgetById  - returns gadget by its ID (which is a mandatory attribute of a Gadget set by its author)
  • RenderJs.GadgetIndex.getGadgetList  - return list of all Gadgets in the page

Additionally developer can get hold on current gadget being rendered using RenderJs.getSelfGadget(). This function is also applicable when a gadget route is followed inside handler function.

How to configure implicit gadget rendering, interaction binding and route creation?

By default when loaded RenderJs will examine current DOM tree and load all gadget being defined there. If required it will load all contained gadgets recursively. In some cases this is not desirable as third party libraries may want to do this job. In order to control this behavior  RenderJs use a global variable called RENDERJS_ENABLE_IMPLICIT_GADGET_RENDERING (default=true) whose value can turn off / on this feature.

In similar way the by default RenderJs will bind all available InteractionGadget instances and create routes for all RouteGadget instances available in page. In order to turn this feature on / off developer can set accordingly RenderJs global varibles:

  • RENDERJS_ENABLE_IMPLICIT_INTERACTION_BIND (default=true)
  • RENDERJS_ENABLE_IMPLICIT_ROUTE_CREATE (default=true)

How execute custom code when all gadget are loaded ?

RenderJs use GadgetIndex object to know what gadgets were loaded and their status (i.e. if their HTML code has been loaded or not). To do this RenderJs will "register" all new gadgets it finds and update their loading status using GadgetIndex API (see details in code). When all gadgets' HTML has been loaded into a HTML page RenderJs will trigger a "ready" event. This event allows developers to "bind" to it and place their custom JavaScript code.

<html>
  <head>
    <script data-main="require-renderjs.js"
            type="text/javascript"
            src="require.js"></script>
  <head>
  <body>
    <div id="A"
         data-gadget="A.html"
         data-gadget-cacheable="0"
         data-gadget-cache-id="A"></div>
    <div id="B"
         data-gadget="B.html"
         data-gadget-cacheable="0"
         data-gadget-cache-id="B"></div>
    <script type="text/javascript" language="javascript">
      //<![CDATA[
        $(document).ready(function() {
        RenderJs.bindReady(function () {
          // place  javascript code here to be executed when all gadgets
          // are rendered.
          });
        });
      //]]>
    </script>
  </body>
</html>

How to handle dependencies with RenderJs?

RenderJs use requirejs to load external libraries. One way of doing is to customize "require-renderjs.js" file as shown below:

    <script data-main="require-renderjs.js"
            type="text/javascript"
            src="require.js"></script>

How to use jQuery Mobile with RenderJs

It is possible to use jQuery Mobile with RenderJs to render nice UI around a gadgets. Example can be found here

Still there are few caveat which should be taken care of.

  1. It's impossible to use require.js to load JQM so in example we do not use require.js in example but instead load scripts manually (this issue is work in progress)
  2. As some gadgets are loaded asynchronously we need to let JQM know and render them accordingly as by default it will render properly only synchronously received HTML. In order to do so for a root document that uses asynchronous gadget which use JQM themselves we need to execute following code:
$(document).ready(function() {
      // explicitly call required as requirejs call not working
      RenderJs.init();
      // when all gadgets are loaded make sure JQM renders them
      RenderJs.bindReady(
                  function () {
                    $("[data-gadget]").trigger('create');
                  });
    });

As we currently due to issue #1 don't have require.js which would call 'RenderJs.init()' we need to do it manually now.

Additionally we need when all gadgets are loaded (RenderJs.bindReady) manually "ask" JQM style contents of these gadgets.

GadgetCatalog

RenderJs provides a RenderJs.GadgetCatalog that allows to maintain list of gadget repositories and get list of gadgets that can provide a service (or an interface).

A gadget repository currently is a JSON file (see structure below) that can be placed on a server. This file is accessed by RenderJs using Jio library through a webdav storage. This file has following self explanatory format:

{
  "gadget_list":[ {"title": "HTML WYSIWYG",
                   "description": "A simple HTML editor",
                   "url": "http://example.com/html-editor.html",
                   "service_list": ["edit_html", "view_html"]},
                  {"title": "SVG WYSIWYG",
                   "description": "A simple SVG editor",
                   "url": "http://example.com/svg-editor.html",
                   "service_list": ["edit_svg", "view_svg"]}
                ]
}

In this case it defines that this repository has two gadgets with respective titles and location which provide respective services. This file can be generated by script at server side or created manually.

RenderJs.GadgetCatalog exposes following API:

  • updateGadgetIndex - update local gadget index from all configured remote gadget repositories
  • setGadgetIndexUrlList - set list of Gadget Index repositories (accepts list of URL pointing to file locations)
  • getGadgetIndexUrlList - get list of configured remote gadget repositories
  • getGadgetListThatProvide - return list of all gadgets that provide a service or an interface (it read local gadget index)

It is important to note that RenderJs doesn't enforce any restrictions on what is considered a service or an interface in the context of GadgetCatalog or how it is expressed. It is job of developer to make a decision how to structure these.

For example in tests we have following services ["edit_svg", "view_svg"] but this can as well be represented with ["IEditor", "IViewer", "ISVGContentAware"] if we want to follow a more re-usable patterns. For now service_list is just a list of string.

RouteGadget

RenderJs provides a routing capabilities (similar to HTML5's history API but with context awareness) using a route.js library (developed by Romain Courteaud). This is implemented through a special type of RouteGadget which defines possible routes between gadgets. This gadget is similar to InteractionGadget except that instead of interactions it uses URLs (routes) which an user can following using browser. It also uses a new html attribute "data-gadget-route" which defines mapping between route (URL) and handling gadget.

There are some pre conditions that must be there to enable gadgets routing:

  1. Routing happens between a virtual route (i.e. '/color-picker/') and a gadget's method which is expected to handle it
  2. It's expected that when a route is followed (i.e. '/color-picker/') gadget is already loaded into DOM(i.e. available by RenderJs.GadgetIndex.getGadgetById). So a gadget can define it's structure (which can be cached as well) when page is initially loaded and leave expensive calculation which really render it respective function
  3. A gadget must define 'handler' function which is the entry point being called when a route is followed by browser

Example how to define simple routes inside a client based application:

<div data-gadget=""
         id="main-router"
         data-gadget-route="[
          {"source": "/gadget-one/", "destination": "main-router.gadget_one"},
          {"source": "/gadget-two/", "destination": "main-router.gadget_two"}]"></div>

In this case when an URL is followed like "/gadget-one/" which is actually hashed as "#gadget-one/" by route.js main-router's function "gadget_one" will be called. In this function we can place code that renders gadget. We can also using InteractionGadget automatically call other functions after this one is executed (see InteractionGadget).

Example how to define / encapsulate function for a gadget

<script type="text/javascript" language="javascript">
      //<![CDATA[
        $(document).ready(function() {
            gadget = RenderJs.GadgetIndex.getGadgetById("gadget-color-picker");
            gadget.render = function (){
              // place code here
            }
      //]]>
    </script>  

Supported browsers

RenderJs work in any standards compliant browser. It has been successfully tested with Firefox, Opera, Chrome and IE 9.0 (see "btoa is underined" thread)

Download and testing

Current considered "stable" version is 0.2 which can be downloaded from link below:

http://git.erp5.org/gitweb/renderjs.git/blob/HEAD:/renderjs.js?js=1

RenderJs use qUnit JavaScript testing framework so it is possible to run tests inside browser from here (some tests like GadgetCatalog are expected to fail when run directly from git web so it is best setup Apache front end for fully functional test runner).

You can do also:

  > git clone https://git.erp5.org/repos/renderjs.git

  > make (will produce a minified version for now, requires nodejs installed as well its uglifyjs module)

  > make lint (will jslint check renderjs)

One can see examples of RenderJs from here or directly run:

Applications using RenderJs

Success Stories

RenderJs has been successfully used for by ERP5, OfficeJs and SANEF.

Articles

RenderJs know issues

RenderJs Roadmap