RenderJS Home RenderJS

    renderJS

    renderJS is a javascript library used to create reusable HTML5 components (called gadgets) for single-page web applications.
    It is also used to quickly integrate existing 3rd party libraries into existing applications. 

    It is developed and maintained by Nexedi to reduce the code duplication between the ERP5 interface and as a basis for applications in app stores such as OfficeJS.

    Concepts

    Gadget definition

    The definition of a renderJS gadget consists of a single HTML file and its JS/CSS dependencies defined in the DOM header.

    This definition is referenced by the URL of the HTML document.

    HTML can also contain a link to the gadget interface URL, which acts a documentation.

    Minimal example:

    <html>
      <head>
        <script src="rsvp.js"></script>
        <script src="renderjs.js"></script>
      </head>
      <body>
    A minimal renderJS gadget
      </body>
    </html>

    Gadget definition loading

    When renderJS loads a new definition URL, it:

    1. fetches the HTML and parse it
    2. keep a copy of the BODY, to clone it on every instance
    3. loads all JS, CSS defined in the HEAD
    4. generates the "class" methods defined by .declareMethod and .declareJob

    Gadget's logic

    The gadget logic is configurable in javascript only. It requires you to create a file referenced by the HTML.

    <html>
      <head>
        <script src="rsvp.js"></script>
        <script src="renderjs.js"></script>
        <script src="logic.js"></script>
      </head>
      <body></body>
    </html>
    

    In the logic.js file, the gadget's definition is accessible by calling: rJS(window).

    (function (rJS) {
      "use strict";
      var gadget_definition = rJS(window);
    })(rJS)
    

    The gadget's definition object API is:

    • .ready
    • .declareMethod
    • .declareService
    • .declareJob
    • .onEvent
    • .onLoop
    • .declareAcquiredMethod
    • .allowPublicAcquisition
    • .setState
    • .onStateChange

    Root gadget

    Any gadget definition should be loadable in the browser URL bar.

    When done, renderJS:

    1. generate the gadget definition
    2. Instanciate the gadget

    Gadget instanciation

    1. Loads the "class" definition if not already loaded
      • if declareGadget(url1) is called twice, it only loads HTML, JS, CSS once
      • First instanciation is slow. Second one is fast.
    2. Instanciate child gadgets defined in the BODY
    3. Copy the BODY on gadget .element property
    4. Calls .setState callbacks
    5. Calls .ready callbacks
    6. When gadget .element is added to the DOM, declareServiceonEventonLoop are started
    7. When gadget .element is removed from the DOM, declareServiceonEventonLoop are cancelled

    Gadget API

    The API available on a gadget is:

    • .declareGadget
    • .getDeclaredGadget
    • .dropGadget
    • .changeState
    • + all declared methods and jobs
    • .getInterfaceList
    • .getMethodList
    • .getRequiredCSSList
    • .getRequiredJSList
    • .getPath
    • .getTitle

    A gadget has 2 built in attributes by default

    • .element
    • .state

    Automatically triggering code on gadget

    There are 3 ways to trigger code execution after a gadget has been created.

    .ready

    The callback is executed during the creation of the gadget.

    Do not run slow/blocking code here, as it will also block the parent gadget creation.

    .declareService

    The callback is executed when the gadget is attached to the document DOM.

    Multiple concurrent services can be declared and executed if needed.

    .onEvent

    The callback is in fact a service started when a specific DOM event (click, submit, change, input, ...) is triggered. 

    DOM management

    A gadget is not allowed to modify the document DOM globally.

    It should only manage its .element attribute.

    It must not manage its child gadget DOM.

    Preventing DOM write concurrency

    Managing the DOM of the gadget is HARD.

    Due to the asynchronous logic of javascript, the DOM can be concurrently modified by:

    • declareMethod callback
    • declareService
    • onEvent
    • onLoop
    • acquireMethod

    changeState is a mutex on the gadget level:

    • changeState is executed only when the previous call is resolved/rejected
    • sequential

    All DOM modifications MUST be done in onStateChange

    gadget.state attribute: dict like object

    changeState take dict as parameter and update all gadget.state keys

    onChangeState callback is ONLY called by changeState AND ONLY if there was a modification on 1 key

    It prevents DOM change if not needed

    Default state is defined by .setState

    Gadget's tree

    • One gadget can have many child gadgets (of different URL)
    • One gadget may have one parent gadget

    An application is a tree of gadgets

    Gadget's reference

    Gadget's reference is available with:

    • declareGadget / getDeclaredGadget, which return a child instance
    • on every renderJS callback, the this variable is the self instance

    You can NEVER get the parent gadget reference. You don't know the parent gadget class.

    Creating gadget's child

    A gadget can create as many child gadget it needs.

    All gadgets COULD be created inside an iframe to provide "isolation", to prevent 3rd party JS/CSS to destroy/pollute the global window/DOM

    There are 2 ways to create a gadget child

    Declarative child loading from the DOM

    <html>
      <head>
        <script src="rsvp.js"></script>
        <script src="renderjs.js"></script>
      </head>
      <body>
        <div data-gadget-url="url_of_the_gadget_definition"></div>
      </body>
    </html>

    Calling .declareGadget method

    Interacting with a gadget's child

    A gadget should only call child methods which have been defined by .declareMethod

    Interacting with a gadget ancestor 

    As it is not possible to get access to the parent gadget reference, it is only possible to call a gadget method defined with .declareAcquiredMethod.

    This will check all gadget ancestor until renderJS found one which defined a allowPublicAcquisition for this method.

    If no matching ancestor is found, a AcquisitionError is thrown.

    Installing

    RenderJS is easy to set up and get working.

    Download the files directly:

    API - Quickguide

    Below is a list of all methods provided by RenderJS, followed by a more detailed explanation in later sections.

    Gadget's definition API

    All methods return the gadget definition. And so, they are chainable.

    Callback this context is the gadget instance

    Do what? Do this! Explanation
    Ready Handler
    rJS(window).ready(callback)

    The ready callback is triggered automatically when all gadget dependencies have loaded.

    Callback this context is the gadget instance

    Declare Method for parent gadget
    rJS(window).declareMethod("methodName", callback, options)

    Declaring methods is the most common way of adding functionality to a gadget.

    Only declare methods which require the this context or which should be accessible by other gadgets.

    Callback this context is the gadget instance

    options can be used to setup a mutex on the callback to prevent concurrent execution: {mutex: "bar" / "changeState"}

    Declare Service
    rJS(window).declareService(callback)

    Callback automatically trigger as soon as the gadget is loaded into the DOM.

    Callback is cancelled as soon as the gadget is removed from the DOM.

    Callback this context is the gadget instance

    There can be multiple declareService handlers, which all trigger concurrently.

    Loop
    rJS(window).onLoop(callback, delay)

    .onLoop callback has the same behaviour than a service, but browser tab must also be visible to trigger it.

    When the callback is over, it is executed again after the delay is over.

    Declare Job
    rJS(window).declareJob("methodName", callback)

    When the methodName is called on a gadget, it spawns a service executing the callback.

    Like a service, its execution starts when the gadget is in the DOM.

    Callback this context is the gadget instance

    However, calling a job cancels the previous call of the job if it hasn't finished.

    Bind Event
    rJS(window).onEvent(type, callback, use_capture, prevent_default)

    Create an event listener for the given event on the gadget .element

    Callback this context is the gadget instance

    Callback take the DOM event as first argument

    Set Initial State
    rJS(window).setState(options)

    The gadget's state should be set once when initialising the gadget.

    The state should contain key/value pairs, but the state is just an ordinary JavaScript object with no hard restrictions.

    Change State Callback
    rJS(window).onStateChange(callback)

    callback is executed after changeState call on a gadget, whenever the gadget state changes.

    Comparison of gadget state is done with json.stringify

    Callback this context is the gadget instance

    Callback take a modification_dict as first argument, which contains all the modified state parameters

    Callback is protected by the changestate mutex

    Publish Method form child gadget
    rJS(window).allowPublicAcquisition("acquisition_name", callback)

    Publish a method to allow children to acquire it.

    Callback this context is the gadget instance

    Callback take the acquired method argument_list as first argument

    Callback take the child gadget scope as second argument

    Acquire Method
    rJS(window).declareAcquiredMethod("methodName", "acquisition_name")

    Acquire a method from a parent gadget

    By default, there are 2 acquired methods which can be automatically called by renderJS internally:

    • reportServiceError: used to catch child gadget error in their services
    • reportGadgetDeclarationError: used to catch gadget loading error when declared in the DOM 

    Gadget API

    Do what? Do this! Explanation
    Declare Gadget (HTML)
    <div data-gadget-url="gadget_example.html"
      data-gadget-scope="example"
      data-gadget-sandbox="public">
    </div>

    Only data-gadget-url is required. Set data-gadget-scope to access the gadget by that scope name in JavaScript.

    Set data-gadget-sandbox to be public, to wrap the gadget in a <div> directly in the DOM, or iframe, to wrap the gadget in an <iframe>.

    Declare Gadget (JS)
    gadget.declareGadget(gadget_url, options);

    return an RSVP.Queue resolved with the child gadget reference.

    The options exactly correspond to those when declaring the gadget in HTML, with the addition of element, to specify an element to wrap the gadget in, rather than the default auto-generated <div>.

    Get an existing gadget
    gadget.getDeclaredGadget(scope);

    return an RSVP.Queue resolved with the child gadget reference.

    throw a ScopeError if the scope is unknown

    Drop an existing gadget
    gadget.dropGadget(scope);

    return an RSVP.Queue resolved with undefined.

    throw a ScopeError if the scope is unknown

    Change State
    gadget.changeState(state);

    return an RSVP.Queue resolved with undefined, when the onStateChange callback is resolved

    Change the state by passing in a new key-value pair, which only overwrites the keys provided in the changeState call, and only if the current and new values are different. All other keys remain unchanged.

    Declared Method call
    gadget.declaredMethodName(arguments)

    return an RSVP.Queue resolved with the result of the .declareMethod callback

    Declared Job call
    gadget.declaredJobName(arguments)

    return undefined

    Acquired Method call
    gadget.acquiredMethodName(arguments)

    return an RSVP.Queue resolved with the result of the ancestor .allowPublicAcquisition callback

    Recursively go up in the parent tree until one "ancestor" allowPublicAcquisition the "parentPublicAcquisition"

    If no ancestor handle it, throw AcquisitionError

    Contributing

    The RenderJS source code is available on GitLab, with a mirror on GitHub.

    HowTo

    Guideline

    FAQ

    Licence

    RenderJS is Free Software, licensed under the terms of the GNU GPL v3 (or later). For rationale, please see Nexedi licensing.

    Examples

    Most of the front end solutions created by Nexedi are based on RenderJS and jIO. For ideas and inspiration check out the following examples:

    • OfficeJS - Office Productivity App Store (Chat client, task managers, various editors).