View on Github

Portal.js

A JS library to build portals ...

How to write a widget

A widget is a small javascript application than can be rendered individually in a portal. This kind of applet is ideal for data mashups. A widget is a simple thing with user scoped preferences (a javascript object) that can be edited. A widget can be rendered in two modes :

at each step, the widget know where it should render itself with a container param (the id of the DOM node for rendering), its preferences and its current userId.

Use the function Portal.Widget(opts) to create a widget class. Options are :

{
    name: 'widget', // the name of the widget as visible in default catalog
    description: 'A widget', // the description of the widget as visible in the default catalog
    defaultPrefs: {}, // default prefs of a widget
    hasPrefs: false, // if the widget has prefs
    alwaysShowPrefs: false, // if prefs are visible even for non admin
    cssAssets: [], // array of css assets urls to inject in the current page 
    jsAssets: [], // array of js assets urls to inject in the current page 
    init: function(container, prefs, userId, savePrefsCallback) {},
    unmount: function(container, prefs, userId) {},
    willMount: function(container, prefs, userId) {},
    mounted: function(container, prefs, userId) {},
    render: function(container, prefs, userId) {},
    renderEdit: function(container, preferences, savePrefsCallback, cancelCallback, userId) {}
}

Each widget follows a well defined lifecycle each time a widget instance (you can create multiple instance of the same widget in one portal tab) has to be rendered in the UI (of you each widget can render it's inner UI any time it wants). The UI is re-rendered for instance at bootstrap, when the current tab is switched, etc ... The lifecycle of a widget in render mode is :

When the widget is in edit mode, the lifecycle is :

Each widget provide and extend({ ... }) function allowing to create widget class (sub-class ?) that extends another widget classes. You can find out more about it here.

Each widget provide asset dependencies if you need to import external CSS or JS resources that are not in the current page. Those properties (cssAssets and jsAssets) are arrays filled with the urls of the assets. The library will ensure that assets are only inserted once per page.

Here is a simple example of a widget :

Portal.registerWidget('Iframe', Portal.Widget({
    name: 'iFrame',
    description: 'Display URL in an iframe',
    hasPrefs: true,
    alwaysShowPrefs: true,
    render: function(container, prefs, userId) {
        var html = '' +
            '<iframe ' +
                'src="' + (prefs.url || 
                    'http://fr.wikipedia.org/wiki/Wikip%C3%A9dia:Accueil_principal') + '" ' +
                    'frameBorder="0" width="100%" </iframe>';
        $('#' + container).html(html);
    },
    renderEdit: function(container, prefs, save, cancel, userId) {
        $('#' + container).html('' +
          '<div>' +
            '<div class="row-fluid">' +
              'URL : <input type="text" id="' + (container + '_iframetext') 
                    + '"class="largeText span12" value="' +  prefs.url + '"/>' +
            '</div>' +
            '<div class="row-fluid" style="margin-top: 10px">' +
              '<div class="btn-group pull-right">' +
                '<button type="button" id="' + (container + '_mdcancel') 
                            + '" class="btn btn-small btn-danger">Cancel</button>' +
                '<button type="button" id="' + (container + '_mdok') 
                            + '" class="btn btn-small btn-primary">Ok</button>' +
              '</div>' +
            '</div>' +
          '</div>'
        );
        $('#' + container + '_mdcancel').click(cancel);
        $('#' + container + '_mdok').click(function(e) {
            prefs.url = $('#' + container + '_iframetext').val();
            save(prefs);
        });
    }
}));

You can then use the Portal.registerWidget(uniqueId, Widget) so the widget is available in the widget store. You should use a generated unique ID to avoid collision of name is the widget namespace.

The widget context

If you're not really into lifecycle functions with a lot of parameters, you can use the widget context. The widget context is available inside each widget instance using this.context. The API of the context is the following :

{
    containerId: function()     // return the current container id
    containerEditId: function() // return the current container id for edit mode
    prefs: function()           // return the current prefs
    userId: function()          // return the current user id
    eventBus: function()        // return the current portal instance event bus
    savePrefs: function(prefs)  // return a function to save prefs at any time
}