Pico Engine UI -- How it works

Compatibility

The contents of this page have not been updated to properly describe the developer interface in version 1.0 of the pico engine.

This document provides a high-level view of the source code for the Pico Engine UI.

The pico engine runs as an HTTP server. Its main purpose it to transduce an HTTP request into either an event (/sky/event) or query (/sky/cloud) addressed to a particular pico.

It also provides a small set of APIs (/api) and two developer web pages. Web pages are in the public folder of the pico-engine GitHub repository.

The developer pages include "the UI" (/index.html) and the "Engine Rulesets" (/rulesets.html). This document explains how the UI page works.

HTML specifies the content

Shown here are the salient HTML tags from /index.html:

  1:<html lang="en-US">
  2:  <head>
  5:    <title>TITLE</title>
  6:    <link rel="stylesheet" href="css/index.css" />

 11:    <script src="js/handlebars.min.js"       type="text/javascript"></script>
 13:    <script src="js/index.js"                type="text/javascript"></script>

 93:    <script id="the-template"            type="text/x-handlebars-template">

137:    <script id="about-template"          type="text/x-handlebars-template">
202:    <script id="rulesets-template"       type="text/x-handlebars-template">
255:    <script id="channels-template"       type="text/x-handlebars-template">
295:    <script id="logging-template"        type="text/x-handlebars-template">
336:    <script id="testing-template"        type="text/x-handlebars-template">
404:    <script id="subscriptions-template"  type="text/x-handlebars-template">
490:    <script id="policies-template"       type="text/x-handlebars-template">
535:    <script id="agent-template"          type="text/x-handlebars-template">
608:  </head>
609:  <body>
610:    <p>Loading...</p>
611:  </body>
612:</html>

The line numbers on the left are for reference. They are also the actual line numbers from the file in the GitHub repository at the current version as of today's writing.

Notice that, when the UI is first loaded as an HTML page (lines 1-612), the body (lines 609-611) of the page consists solely of a single line "Loading..." (line 611). The JavaScript (JS) file loaded in line 13 will then be executed by the browser (as described below) to render the page content.

The role of CSS and JS

The Cascading Style Sheets (CSS) (lines 6-7) and JavaScript resources (lines 8-14) are loaded into the browser. Of particular note are the lines shown above. The engine UI's style is determined primarily by the file at /css/style.css (linked from line 6) and its behavior is determined primarily by the file at /js/index.js (loaded in line 13). An overview of the latter is given later.

Content is produced on the page dynamically, as directed by the JS program, and is based on two external libraries: Handlebars (line 11) which in turn depends on jQuery (loaded in lines 8-10 (not shown here)).

The role of Handlebars

Handlebars depends on HTML scripts which when compiled into JS functions are called templates. When a template is invoked, it is given a data structure (a JS object) and produces a snippet of HTML code based on both the template and the data. This HTML code is then placed into the page shown by the browser. Line 93-136 are the Handlebars script for the main page of the UI. The template is identified by the id attribute of its script tag. Handlebar scripts are of type="text/x-handlebars-template" (see line 93).

The main page of the UI is rendered by these two JavaScript function calls (extracted from the more detailed presentation in the next section):

  var uiTemplate = Handlebars.compile($('#the-template').html())
...
      $('body').html(uiTemplate(data))

These lines illustrate the two phases of Handlebars: 1) compilation of the script into a template, and 2) the use of the template (operating on data) to produce HTML which replaces the `body (line 611) with the entire UI: HTML code produced by Handlebars from the script.

The main Handlebars template

The main script (located by its id using the jQuery idiom $('#the-template')) is lines 93-136 of /index.html and is shown here (with only the most salient lines shown):

 93:    <script id="the-template" type="text/x-handlebars-template">

102:      <div id="container" style="position:relative">

108:        {{#picos}}
109:          <div class="pico" id="{{id}}" style="{{style}}" title="{{dname}}">
110:            {{dname}}
111:            <div class="resize-handle ui-resizable-handle ui-resizable-se"></div>
112:          </div>
113:          <div class="pico-edit" style="{{style}}">
114:            {{dname}}
115:            <ul class="horiz-menu">
116:              <li>About</li>
117:              <li>Rulesets</li>
118:              <li>Channels</li>
119:              <li>Policies</li>
120:              <li>Subscriptions</li>
121:              <li>Logging</li>
122:              <li>Testing</li>
123:              <li>Agent</li>
124:            </ul>
125:            <div class="pico-section"></div>
126:          </div>
127:        {{/picos}}

135:      </div>
136:    </script>

The body of the main page of the UI thus consists of a single div with id="container" (starting in line 102 and ending at line 135).

Each pico hosted by the pico engine, is represented by a div with class="pico" whose id is the pico identifier (lines 109-112. repeated for each pico). It is styled to have a rounded rectangle shape, and to have a certain color and position. Each pico has a tooltip specified by the title attribute of this div, consisting of the display name of the pico.

The black lines that represent the parent-child relationship among the picos are specified by lines 128-134 (not discussed in this document (using Scalable Vector Graphics (SVG))).

The first div for each pico is immediately followed by a second div (lines 113-126). This div is styled to take up 85% of the page, and represents the pico "opened up" so that the developer can see more information about it. This view is entitled with the pico's display name and topped by a horizontal menu styled as tabs (lines 115-124). These tabs are arranged along the top of the view. Below them is a large area reserved for the content associated with the tab. This is a div with class="pico-section".

JavaScript specifies the behavior

The display of all picos

When the UI page is loaded into the browser, the browser loads from /js/index.js (line 13). This file contains the code shown here (only the most salient lines of this file are shown):

  2:$(document).ready(function () {

 77:  var uiTemplate = Handlebars.compile($('#the-template').html())
 78:  var aboutTemplate = Handlebars.compile($('#about-template').html())

 87:  $.getJSON('/api/legacy-ui-data-dump', function (dbDump) {

351:    var renderTab = function (event) {
622:    var renderGraph = function (data) {
626:      $('body').html(uiTemplate(data))
632:      $('div.pico')
652:        .click(function () {...}
686:        .parent()
687:        .find('ul.horiz-menu li')
688:        .click(renderTab)
699:    }
727:    var doMainPage = function (ownerPico) {
823:      renderGraph(dbGraph)
833:    }

841:    doMainPage(rootPico)

875:  })
876:})

The code in line 2 identifies a JS function (lines 2-876) which will be executed when the HTML document is fully loaded into the browser.

All of the Handlebar templates are compiled (lines 75-85 (two examples shown above)).

The code then calls (line 87) an API provided by the pico engine (/api/legacy-ui-data-dump) and the engine responds with a JSON structure containing selected information about each of the picos it hosts, along with the parent child relationships among them. This JS object is known throughout the remainder of the code (lines 88-875) as dbDump.

The remaining lines of code shown here are to be read from bottom to top (as is typical of JS code). After everything is in readiness, the entire page will be shown when the function doMainPage is called in line 841. This function collects information about all of the picos into an object named dbGraph and does a number of other things, but culminates with a call to the function renderGraph in line 823.

The renderGraph function starts by producing the main display (described in the previous section) at line 626. For each pico (list obtained using jQuery code $('div.pico')), it sets up a click event handler (lines 652-683). Then, it finds the horizontal menu items, and sets each up with a click event handler which will call the renderTab function.

The display of a single pico

The main page is now displaying a graphic representation of all of the picos hosted by this pico engine. Each pico is represented by two div tags. The first appears as a rounded rectangle while the second is initially hidden. Each rounded rectangle has a click event handler (established in line 652) which will cause the second pico div to become visible.

When the developer clicks on the rounded rectangle representing a pico, the click handler invokes the function of lines 652-682, shown in a bit more detail here:

632:      $('div.pico')
652:        .click(function () {
659:          var $pediv = $(this).next('.pico-edit')
667:          $pediv.fadeIn(200)
682:        })

Among other things, this (anonymous) function locates the second div (the one with class="pico-edit" using the jQuery idiom next('.pico-edit')(line 659)). This div will be known by the name $pediv throughout the rest of the click handler function. Line 667 uses jQuery to fade in the larger rounded rectangle so that this one pico is displayed, taking up most of the screen (95% of the width and 85% of the height (specified by lines 668-676 (not shown here))).

The display of a tab

With one pico being displayed, the developer now sees it with its About tab selected. In this mode, each tab is prepared to be clicked (line 688), so that its content will be rendered (by the renderTab function of lines 351-615).

When the developer clicks on one of the tabs, the renderTab function (lines 351-615) is invoked. This function is shown in more detail here: 

351:    var renderTab = function (event) {
364:      var $theSection = $(this)
365:        .parent()
366:        .next('.pico-section')
379:      specDB(tabName, thePicoInp, thePicoInpName, function (err, theDB) {
387:        if (tabName === 'rulesets') {
388:          $theSection.html(rulesetsTemplate(theDB))
465:        } else if (tabName === 'testing') {
490:        } else if (tabName === 'about') {
491:          $theSection.html(aboutTemplate(theDB))
501:        } else if (tabName === 'channels') {
505:        } else if (tabName === 'subscriptions') {
509:        } else if (tabName === 'policies') {
513:        } else if (tabName === 'agent') {
543:        } else if (tabName === 'logging') {
572:        }
614:      })
615:    }

The renderTab function first locates the div (by referencing its class which is "pico-section" (line 125 of /index.html)) into which it can display its content. This shows in the browser as a large white-background space within the expanded pico. This div is known throughout the remainder of the function as $theSection (referring to the jQuery object). Ultimately the section will be filled with content from the Handlebars template (see examples in line 388 and 491).

But first, a function named specDB is called. Given the name of the tab, a JS object with some information about the pico, and the pico's display name, this function computes/locates pertinent information about the pico, and then calls back the function passing it an expanded JS object with all of the information needed by the Handlebars template. The callback function is in-line (lines 379-614) being the continuation of the work needed to create the UI for a specific tab for a specific pico.

Once the HTML code for a tab is in the browser, code is executed to set up event handlers for mouse clicks, for JSON links and for JSON forms (lines 572-613). The tab is now rendered and further action occurs as the developer works with the UI.

The code for the specDB function is shown here. Remember that this code will be executed before the code in lines 380-613).

102:    var specDB = function (tabName, thePicoInp, dname, callback) {
104:      if (tabName === 'about') {
159:        callback(null, thePicoOut)
160:      } else if (tabName === 'rulesets') {
185:      } else if (tabName === 'logging') {
224:      } else if (tabName === 'testing') {
241:      } else if (tabName === 'channels') {
263:      } else if (tabName === 'subscriptions') {
321:      } else if (tabName === 'policies') {
332:      } else if (tabName === 'agent') {
346:      } else {
348:      }
349:    }

The role of the specDB function is to gather and pre-process information from the given pico for the given tab name. When it has completed this task, it invokes the callback function, as shown for example, in line 159 for the About tab. Then (and only then) work for the tab resumes in lines 380-613 of the renderTab function. During this latter, the Handlebars template will show HTML for the tab UI for this pico (for example, this happens for the About tab in line 388).

Adding a new tab to the UI

If/when an additional tab is desired for the pico engine UI, adding it involves these steps:

  1. Write the HTML for the content of the new tab, as a Handlebars template, and add it to /index.html. A glance at this file will show that this has been done several times. Over time, the number of Handlebar templates has increased, and they are basically in the order they were added (as opposed to the order they appear in the horizontal tab list).
  2. Add an li entry into the horizontal menu of the UI template (still within the /index.html file). Only the tab names listed in the ul of lines 115-124 will be shown when the developer clicks on a pico.
  3. Add a line of JS to /js/index.js to compile your new Handlebars template. This will be like the existing lines 75-85.
  4. Add a branch to the if-then-else switch in the specDB function of /js/index.js including code to obtain the information you need to render your tab UI.
  5. Add a branch to the if-then-else switch in the renderTab function of /js/index.js including code to apply your Handlebars template (created in step 3) to the data (created by the code added in step 4).
  6. Add any needed styles to /css/index.css for your tab UI.

For an example, look at the few dozen commits starting with "start Agent tab" in the history of changes to /index.html. See also the recent change which made the use of Handlebars much clearer (and matches what is documented here).