The Renderer's anatomy

This document provides simple instructions and a skeleton to build a Rendering Widget (ie: A Widget that displays a list of posts from an RSS feed).

SAPO::Widget namespace

This is where all the Widgets live. Each application's Widget should have it's own class and it's own method. For instance, Widgets for the  SAPO Tags service are all implemented in the SAPO::Widget.Tags class.

What's a Widget ?

We call a Widget, something that takes a data feed (ie: an RSS feed), manipulates the data and presents the information dynamically that's rendered in the user's Web browser. Widgets can either be static or have user controls (ie: A photo album Widget can have previous and next buttons).

Basics

SAPO's Widget classes are build on top our own  library. These classes often depend on other classes, depending on the functionality offered.

We can divide the Widget into 3 layers:

  • The data layer (where you get the data from)
  • The control layer (how you manipulate the data)
  • The presentation layer (how you present the data, and draw the widget)

Data layer

Data is passed to Widgets as JSON arrays. These JSON arrays are often transformed RSS feeds. Our Webservices infrastructure provides real-time RSS-to-JSON transformations so we can have JSON arrays from any kind of RSS feed we know. This is an example  JSON feed just transformed from it's original  RSS feed.

Out library provides a method SAPO::Widget.Parser->jsonRSS2 to parse an JSON-RSS structure into something more easy to handle. Many widgets use it.

Control layer

This is something we're still working on. We're adopting the  Opensearch standard.

Many control features use AJAX models but may also be simple object navigation functions (ie: next and previous photo from an already loaded photolist object).

Presentation layer

We present data by writing directly into the DOM tree and using CSS to manipulate styles.

CSS styles are dynamically set in runtime (ie: they can, but are usually not defined in the webpage source) using the  CSS-JSON approach.

Our library provides a method SAPO::Utility.CSS->set to set the CSS styles from a CSS definition in JSON.

Using dynamic CSS, we have full control over the Widget's presentation and we can provide input options to the Widget which may change the CSS styles immediately and accordingly to logical or mathematical operations.

Also, it's the perfect solution for our Snippets.

Hands on

Writing our Hello World widget under the SAPO::Widget.Hello class.

The skeleton

This is the skeleton file for our Hello World widget.

This creates the SAPO::Widget namespace if not already created.

if(!SAPO.Widget || typeof(SAPO.Widget) == 'undefined') {
    SAPO.namespace('Widget');
}

This block does two things:

  • Creates a namespace for our Exceptions engine log messages.
  • Calls the class initializer with this.init()
SAPO.Widget.Hello = function(options)
{
    if(SAPO.Exception && typeof(SAPO.Exception) != 'undefined') {
        this.exception = new SAPO.Exception('SAPO::Widget.Hello');
    } else {
        this.exception = false;
    }

    if(options != 'undefined') {
      this.init(options);
      } else {
      this.init({});
      }
};

This is the tipical class init. It has input options passed as JSON. These options may have defaults or not.

The other important thing the init() section does is to automatically create an unique object name for it's instance.

This may seem silly at first sight but it's important if you have to invoke the same Widget class many times in the same page. There's not reason why you should want to do this too but again, it's one of our magic Snippets's requirements, so no harm done.

SAPO.Widget.Hello.prototype = {

    init: function(options)
    {
      options = Object.extend({ // default options
         instance: false
         }, arguments[0] || {});
      this.options = options;

      // get an handler for this instance - eval hack
      if( this.options.instance === false ) {
        this.options.instance=Math.round(10000*Math.random());
        var cmd='hello'+this.options.instance+'=this';
        eval(cmd);
        }
    },

Now let's build our printHelloWorld method.

This method has it's own options.

It starts by encapsulating it's code in a try {} catch {} net. If an error occurs, it will be logged.

Then it defines the default options with options = Object.extend({})

These options are common to all widgets:

  • divid - optional element ID to render the widget in.
  • boxclass - optional CSS class name if you want to use a classical external definition of styles.
  • css - optional CSS JSON structure if you want to use your own.

And then it defines the default CSS styles with options.css={} . If an option css is provided, then the dynamic CSS is not set.

    printHelloWorld: function(options)
    {
      try {
        var shortname='phw';

        options = Object.extend({ // default options
          'divid': false,
          'boxclass': shortname,
          'instance':Math.round(10000*Math.random()),
          'text': shortname,
           }, arguments[0] || {});

         // default stylesheet
         if(options.css===undefined) {
           // this will be prefixed by boxclass
           options.css={
             '': {
               'font-family':'Verdana',
               },
             'h1': {
               'margin':'10px 0 0 0',
               'padding':'0',
               'background-color':'#0ff',
               'color':'#f0f'
               }
              }

            };

          SAPO.Utility.CSS.set(options.css,short,options.boxclass);

Now the method makes it's own options available to the whole class.

After that it checks if there's a Element ID provided to render things. If none was provided then it will create one with a direct document.write("<div id='zbr'></div>"); . This means the data will be rendered in the same place as the function call.

And finally it writes the DOM tree with the data (text message, in this case) and appends it to the previously created element id (divid).

          this.options.helloworld=options;

          if(options.divid===false) {
            document.write("<div class='"+options.boxclass+"' id='"+short+options.instance+"'></div>");
            divid=document.getElementById(short+options.instance);
            }
            else
            {
            divid=document.getElementById(options.divid);
            divid.setAttribute('class', options.boxclass);
            }

        head1=document.createElement('h1');
        head1.appendChild(document.createTextNode(options.text));
        divid.appendChild(head1);

        } catch(e) { // exception handling
          this.exception.log(e);
        }

      } // end printHelloWorld

};

You can check the full source here /trunk/SAPO/Widget/Hello/

And a working sample here  http://js.sapo.pt/SAPO/Widget/Hello/sample/hello.html

Attachments