Dojo 1.8 and AMD

An overview with demos

Karsten Lehmann, Mindoo GmbH / @klehmann79

About us

  • IBM Business Partner and a Notes/Domino Design Partner
  • Eclipse/Expeditor plug-ins and rich client applications
  • XPages applications
  • Web application development for IBM Websphere and Oracle Glassfish
  • Karsten Lehmann and Tammo Riedinger
  • Since 2004, developer of MindPlan for Haus Weilgut GmbH, mindmapping and project management for IBM Notes
  • http://www.mindoo.de

Goals

  • Understanding concepts behind Dojo 1.8
  • Reference book for new APIs
  • Ideas and notes for individual experiments
  • Tips for using Dojo in XPages in IBM Notes/Domino R9

What is Dojo?

  • Comprehensive, freely usable web toolkit
  • APIs usable for Web and Mobile
  • One toolkit: no clear separation in Web and Mobile
  • Dojo 1.8 is part of IBM Notes/Domino R9!

What is Dojo?

An SDK with four sections:

  • dojo – general tools for JS development and Ajax
  • dijit – UI components, e.g. layout and form elements
  • dojox – additional UI components, including Mobile UI
  • util – tools for build generation and unit tests

Where do I find help?

  • Dojo API documentation (link)
    comprehensive collection of tutorials, reference and API documentation
  • Sitepen blog (link)
  • Final solution:
    Dojo source code – everything is open source!

Dojo releases

  • July 2010 - Dojo 1.5
  • March 2011 - Dojo 1.6
  • October 2011 - Lotus Notes 8.5.3 with Dojo 1.6
  • December 2011 - Dojo 1.7
  • August 2012 - Dojo 1.8
  • March 2013 - IBM Notes 9.0 with Dojo 1.8
  • May 2013 - Dojo 1.9
  • ? - Dojo 2.0 (roadmap discussion)

Differentiation between jQuery, Dojo, extJS

ExtJS / Sencha Touch

  • Strong separation between data and presentation
  • Mixed license: open source and commercial
  • Many commercial components available
  • Support by manufacturer Sencha

jQuery

  • Largest developer community
  • Visually highly appealing jQuery plug-ins
  • Syntax requires getting used to
  • Little abstraction from browser DOM

Dojo

  • Comprehensive toolkit for Web and Mobile
  • Few extensions / additional components on the net
  • Can be visually enhanced with jQuery
  • First class citizen in XPages, part of the framework

New in Dojo 1.8 - Web

dojox/calendar (demo)

(has scalability issues)

New in Dojo 1.8 - Web

dojox/dgauges (demo)

New in Dojo 1.8 - Web

dojox/treemap (demo)

New in Dojo 1.8 - Mobile

28 new widgets:
selection dialogs, progress bars, audio/video, scrolling areas inside pages, badge icons (demo)

New in Dojo 1.8

  • First components without support for Firefox 3.6 and IE6
  • XPages in the Notes Client R9 still based on the Firefox 3.6 engine!

New since Dojo 1.6 (Notes 8.5.3)

  • Even more widgets
  • Web: among others dgrid – flexible table component
  • Mobile: 21 new widgets
    e.g. Pop-up dialogs (opener, tooltip), ComboBox, SpinWheel
  • Additional information: link

New since Dojo 1.6 (Notes 8.5.3)

  • AMD: Asynchronous Module Definition
  • Loads Dojo classes and individual code, replaces dojo.require()
  • Many APIs renamed and reorganized for 2.0,
    old APIs still usable though
  • Considerably smaller Dojo core, loading additional modules on demand

Maqetta

  • Free WYSIWYG editor for Dojo applications
  • Web and Mobile
  • Available for download or to be used in the cloud (link)

Create web layouts –
declarative approach

Create web layouts –
declarative approach

  • Layout in the form of HTML tags
  • Width and shape adjustable via CSS style
  • Variety of layout containers that can
    be nested available
  • Used mainly for static contents

dojox/layout/BorderContainer

  • Well suited as basic layout
  • Five areas: top, left, right, bottom and center
  • Only center has to be filled
  • Several widgets per area through layoutPriority attribute (defines sequence)

BorderContainer

design="headline", two widgets for "right" here

BorderContainer

The same layout with design="sidebar":

BorderContainer

<!DOCTYPE html>
<html>
<head>
<script type="text/javascript"
  src="/xsp/.ibmxspres/dojoroot-1.8.0-u/dojo/dojo.js"
  data-dojo-config="parseOnLoad:true"></script>
<link rel="stylesheet" type="text/css"
  href="/xsp/.ibmxspres/dojoroot-1.8.0-u/dijit/themes/claro/claro.css">
<link rel="stylesheet" type="text/css"
  href="/xsp/.ibmxspres/dojoroot-1.8.0-u/dojo/resources/dojo.css">
<style>
html,body {
  width:100%;height:100%;margin:0;overflow:hidden;
}
</style>
<script type="text/javascript">
  <!-- load required widgets -->
    require( ['dijit/layout/BorderContainer',
    'dijit/layout/ContentPane'],
      function(BorderContainer, ContentPane) {
      }
    );
</script>
</head>
<body class="claro">
<!-- BorderContainer -->
<div id="mainContainer" data-dojo-type="dijit/layout/BorderContainer"
  style="width:100%;height:100%">

  <!-- contained ContentPanes -->
  <div id="topPane" data-dojo-type="dijit/layout/ContentPane"
    data-dojo-props="region:'top'" style="height:30px">
      Top pane
  </div>

  <div id="leftPane" data-dojo-type="dijit/layout/ContentPane"
    data-dojo-props="region:'left',splitter:true" style="width:20%">
      Left pane
  </div>

  <div id="centerPane" data-dojo-type="dijit/layout/ContentPane"
    data-dojo-props="region:'center'">
      center pane
  </div>

  <div id="rightPane1" data-dojo-type="dijit/layout/ContentPane"
    data-dojo-props="region:'right',layoutPriority:1" style="width: 10%">
      1st right pane from right
  </div>

  <div id="rightPane2" data-dojo-type="dijit/layout/ContentPane"
    data-dojo-props="region:'right',layoutPriority:2" style="width: 10%">
      2nd right pane from right
  </div>

  <div id="bottomPane" data-dojo-type="dijit/layout/ContentPane"
    data-dojo-props="region:'bottom'">
      first bottom pane
  </div>

</div>
</body>
</html>

data-dojo-* attributes

  • HTML5 conform attribute names
  • data-dojo-config
    central Dojo configuration
<script type="text/javascript"
  src="/xsp/.ibmxspres/dojoroot-1.8.0-u/dojo/dojo.js"
  data-dojo-config="parseOnLoad:true"></script>

Global variable alternative:

<script type="text/javascript">
dojoConfig={
  parseOnLoad: true
}
</script>
<script type="text/javascript" src="/path/to/dojo.js"></script>

data-dojo-* attributes

  • data-dojo-type
    Class name of widget (separator " / " or " . ")
  • data-dojo-props (optional)
    Widget properties (cf. API documentation)
<div id="topPane" data-dojo-type="dijit/layout/ContentPane"
  data-dojo-props="region:'top'" style="height:30px">
    Top pane
</div>

require

  • AMD command for loading classes
  • Blocking or asynchronous depending on the mode
  • Calls up callback as soon as classes are available
  • Necessary for tags to become widgets
<script type="text/javascript">
  <!-- load required widgets -->
    require( ['dijit/layout/BorderContainer',
      'dijit/layout/ContentPane'],
        function(BorderContainer, ContentPane) {
          //Callback with access to load classes
        }
    );
</script>

Dojo Parser

  • Parser can also be called manually
  • parseOnLoad: false
  • Advantage:
    Callback call as soon as widgets are generated
<script type="text/javascript">
  require( ['dijit/layout/BorderContainer', 'dijit/layout/ContentPane',
    'dojo/parser', 'dojo/ready'],
    function(BorderContainer, ContentPane, parser, ready) {
      //dojo/ready waits for DOM to load
      ready(function() {
        parser.parse().then(function(arrWidgets) {
          //called with array of all created widgets
            for (var i=0; i<arrWidgets.length; i++) {
              console.log('Widget created: '+arrWidgets[i].id);
            }
        });
      });
  });
</script>

Other layout widgets

  • dijit/layout/ContentPane
    Container for random DOM nodes
  • dijit/layout/AccordionPane
    Several areas below each other, one visible
  • dijit/layout/TabContainer
    Tab
  • dojox/layout/ExpandoPane
    Closable sidebar
  • dojox/layout/GridContainer
    Portal layout

Portal layout

Portal with two portlets

<!DOCTYPE html>
<html>
<head>
<script type="text/javascript"
    src="/xsp/.ibmxspres/dojoroot-1.8.0-u/dojo/dojo.js"></script>
<link rel="stylesheet" type="text/css"
    href="/xsp/.ibmxspres/dojoroot-1.8.0-u/dijit/themes/soria/soria.css">
<link rel="stylesheet" type="text/css"
    href="/xsp/.ibmxspres/dojoroot-1.8.0-u/dojo/resources/dojo.css">
<link rel="stylesheet" type="text/css"
    href="/xsp/.ibmxspres/dojoroot-1.8.0-u/dojox/widget/Portlet/Portlet.css">
<link rel="stylesheet" type="text/css"
    href="/xsp/.ibmxspres/dojoroot-1.8.0-u/dojox/layout/resources/GridContainer.css">
<style>
html,body {width:100%;height:100%;margin:0;overflow:hidden}
.gridContainerTable {border:0;height:inherit}
.gridContainer > div {height:inherit}
</style>
<script type="text/javascript">
    require( ['dijit/layout/BorderContainer',
    'dijit/layout/ContentPane', 'dojox/layout/GridContainer',
    'dojo/parser', 'dojo/ready',
    'dojox/widget/Portlet'],
    
    function(BorderContainer, ContentPane, GridContainer, parser,
    ready, Portlet) {
        ready(function() {
            parser.parse();
        });
    });
</script>
</head>
<body class="soria">
<div id="mainContainer" data-dojo-type="dijit/layout/BorderContainer"
  data-dojo-props="design:'sidebar'" style="width: 100%; height: 100%">

<!-- Portal visible in center area -->
<div id="portal" data-dojo-type="dojox/layout/GridContainer"
  style="height:100%" data-dojo-props="nbZones:3,region:'center',
  hasResizableColumns:true,isAutoOrganized:false,withHandles:true,
  dragHandleClass:'dijitTitlePaneTitle',colWidths:[20,60,20]">
  
  <!-- First portlet -->
  <div id="portlet1" data-dojo-type="dojox/widget/Portlet"
  data-dojo-props="title:'Portlet 1',column:1">
    Hello World.
  </div>

  <!-- Second portlet -->
  <div id="portlet2" data-dojo-type="dojox/widget/Portlet"
  data-dojo-props="title:'Portlet 2',closable:false,column:2">
    Greetings from Entwicklercamp.
  </div>
</div>
</body>
</html>

Portal layout

3 columns, portlets movable via drag and drop

Demo: portal layout

Generating widgets in JavaScript

  • Widget generation in JavaScript
  • Very flexible in interaction with Ajax
  • Dojo widgets form a tree structure:
require(['dijit/registry', 'dijit/layout/ContentPane'],
  function(registry, ContentPane) {

  //add widgets via addChild()
  //main: existing dijit/layout/BorderContainer
  var mainContainer=registry.byId('main');
  var newPanel=new ContentPane({
    id: 'contentpane1',
    content: 'Pretty dynamic content '+(new Date()).toString(),
    region:'bottom',
    splitter: true
  });
  mainContainer.addChild(newPanel);
  //resize recalculates positions
  mainContainer.resize();

  //read contained widgets via getChildren()
  var arrChildren=mainContainer.getChildren();

  //remove widgets via removeChild()
  var childWidget=arrChildren[0];
  mainContainer.removeChild(childWidget);
  //if no longer needed, free up all resources
  childWidget.destroyRecursive();
}
					

Demo: dynamic UI

Form widgets

Form widgets

Widgets can be placed freely, either declarative:

<body>
Lorem ipsum dolor sit amet.<br />
<input type="text" data-dojo-type="dijit/form/TextBox" /><br />
consetetur sadipscing elitr.
</body>

Form widgets

...or programmatic:

require(['dojo/_base/window', 'dojo/dom', 'dijit/registry'],
  function(win, dom, registry) {
    //get target DOM node with ID 'targetDomNodeId':
    var targetDomNode=dom.byId('targetDomNodeId');
    //get existing widget with ID 'myWidgetId':
    var myWidget=registry.byId('myWidgetId');
    //Change widget position
    //(first/last=first/last child,
    //before/after=before/after reference node)
    myWidget.placeAt(targetDomNode, 'first');
  }
});

Demo: form widgets overview

link

A collection of important APIs:

(Please learn all of them by heart! ;-) )

Important widget APIs

Widget can be located via its ID

// dijit/registry
var widget=registry.byId('widgetId1');

Reading/writing of properties

var propValue=myWidget.get('value')
myWidget.set('value', 'xyz');

Important DOM APIs

DOM node can be located via its ID

// dojo/dom
var nodeWithId=dom.byId('nodeId1')

Reading and changing styles

// dojo/dom-style
var styleValue=domStyle.get(node, 'display');
domStyle.set(node, 'backgroundColor', '#ff0000');

Important DOM APIs

Reading/writing DOM node classes

// dojo/dom-class
var nodeHasClass=domClass.has(node, 'myclass');
domClass.add(node, 'myclass');
domClass.remove(node, 'myclass');

Generating and placing DOM nodes

// dojo/dom-construct
domConstruct.empty(refNode);
var createdNode=domConstruct.create('div',
  {innerHTML:'<b>Test</b>'}, refNode, 'first');
domConstruct.place(refNode, otherNode, 'before');

Dimensions of DOM node

// dojo/dom-geometry
var box=domGeom.getMarginBox(node);
//box.width, box.height, box.top, box.left

Important DOM APIs

Finding DOM nodes via CSS Selector

// dojo/query
var arrNodes = query('.headline', bodyNode);

Wichtige Dojo-APIs

Language-dependant date formatting

// dojo/date/locale
var formattedDateStr=locale.format(myDate, {selector:'date'});

Conversion between date and ISO8601 format

// dojo/date/stamp
var parsedDate=stamp.fromISOString(isoDateStr);
var dateAsString=stamp.toISOString(mydate);

Searching a value in arrays

// dojo/_base/array
var index=array.indexOf(myArray, valueToFind);

Important Dojo APIs

Parsing JSON strings and formatting objects as JSON

// dojo/json
var parsedJsonObj=JSON.parse("{'foo':'bar'}");
var jsonStr=JSON.stringify( {foo:'bar'} );

…and finally:

// dojo/_base/lang
// clone object recursively
var clonedObj=lang.clone(obj);

// run code in different scope
var myfunction=lang.hitch(mygrid,
  function(key, value) {this.set(key, value);} );
myfunction('store', newStore);

// check for data type:
lang.isArray(x) / lang.isFunction(x) / lang.isObject(x) /
lang.isString(x)

// replacing text
lang.replace("Hello {name.first} {name.last}!",
  { name: {first:  "Rudi", last: "Knegt"} });

Registering events

Reacting to events

The new dojo/connect

dojo.connect was divided:

  • dojo/on - DOM events
  • dojo/aspect - hooks for class methods

dojo/on

"on" in the event name is cut out:

//Register event code for onClick event:
//uses dojo/dom and dojo/on
var myDomNode=dom.byId('nodeId');
var signal=on(myDomNode, 'click',
  function(evt) {alert('Node clicked'); });
  
//unregister event code:
signal.remove();

Event delegation

Intercepting 'bubbling’ events in parent containers

<div id="parentDiv">
  <button id="button1" class="clickMe">Click me</button>
  <button id="button2" class="clickMe">Click me also</button>
  <button id="button3" class="clickMe">Click me too</button>
  <button id="button4" class="clickMe">Please click me</button>
</div>
<script>
require(["dojo/on", "dojo/dom", "dojo/domReady!"],
  function(on, dom, domReady) {
 
    var myObject = {
      id: "myObject",
      onClick: function(evt){
        alert("The scope of this handler is " + this.id);
      }
    };
    var div = dom.byId("parentDiv");
    on(div, ".clickMe:click", myObject.onClick);
  });
</script>

dojo/aspect

  • Aspect-oriented programming
  • aspect.before, aspect.after and aspect.around
require(['dojo/aspect', 'dojo/json', 'dojo'],
  function(aspect, JSON, dojo) {
  	
  //overwrite old method 'dojo.xhrGet()'
  aspect.around(dojo, "xhrGet", function(originalXhr) {
    return function(args) {
      var t0=new Date().getTime();
      
      //call original method
      var deferred = originalXhr(args);

      //we add a callback to the deferred object
      deferred.then(function(data) {
        var t1=new Date().getTime();
        console.log("Data read in "+(t1-t0)+
          "ms with request: "+JSON.stringify(args));
      });

      //and forward it to the caller
      return deferred;
    }
  });
});

dojo/topic

  • Publish/subscribe architecture
  • Decouples message sender from recipient
  • used for drag and drop system:
    /dnd/start, /dnd/cancel, /dnd/drop
require(['dojo/topic'], function(topic) {
  topic.subscribe("some/topic", function(event){
    // process object 'event'
  });
  //publish new event object to all subscribers
  topic.publish("some/topic", {name:"My Birthday Party",
    location:"Karlsruhe"});
});

Working with data

Web forms and Ajax

Dojo forms

  • dijit/form/Form simplifies working with web forms
  • Reading and writing form data as a JS object
  • Validating the form with myform.validate()

Dojo forms

dijit/form/Form becomes FORM tag in DOM:

<div id="myformid" data-dojo-type="dijit/form/Form">
  <table>
    <tr>
      <td>Firstname</td>
      <td><input type="text" name="firstname"
        data-dojo-type="dijit/form/TextBox" /></td>
    </tr>
    <tr>
      <td>Lastname</td>
      <td><input type="text" name="lastname"
        data-dojo-type="dijit/form/TextBox" /></td>
    </tr>
  </table>
</div>

Dojo forms

Form data can be extracted and set:

require(['dojo/json', 'dijit/registry'],
  function(JSON, registry) {
    var myform=registry.byId('myformid');
    var formData=myform.get('value');
    
    //show form data as JSON string:
    alert(JSON.stringify(formData));

    //update form fields based on
    //"name" attribute of fields
    myform.set('value', {firstname:'Rudi', lastname:'Knegt'});

    //validate form
    if (!myform.validate())
      alert('Form content invalid!');
  }
});

Demo: reading/writing form data

Ajax requests

dojo/request/xhr

  • Replaces dojo.xhrGet() / .xhrPost()
  • xhr package also comprises cross domain protocols like IFrame/JSONP
require(["dojo/request/xhr"], function(xhr) {
  xhr("myxagent.xsp", {
    handleAs: "json"
    method: "GET"
  }).then(function(data) {
        //process data (here JS object of JSON data)
      }, function(err) {
        //report/log error
      }, function(evt){
        //process progress info
        //(requires browser support for XHR2)
      });
});

Demo: Notes views with LazyTreeGrid

AMD

Asynchronous Module Definition

AMD

  • Class loader for Dojo classes and individual code
  • Experimental in Dojo 1.6, standard since 1.7
  • New asynchronous mode generates script tags instead of xhr
  • Better performance in browser than with xhr
  • New API distribution leads to smaller code units
  • More compact custom builds, but also more HTTP requests without optimization

More HTTP requests?

Surely, it can’t be that many!

That was our grid example :-)

Solutions

  • Custom builds (tutorial)
  • XPages runtime optimizer:
    "Use runtime optimized JavaScript and CSS resources"
  • Combines Dojo classes and dependencies

Asynchronous class loading

Activation with data-dojo-config

<script type="text/javascript" src="path/to/dojo.js"
  data-dojo-config="async:true"></script>
  • async:false (Default)
    Loading of Legacy APIs, e.g. dojo.connect, dojo.xhrGet etc.
  • async:true
    Loading only dojo nano kernel (small)
  • With XPages in R9, unfortunately always async:false

Legacy APIs

With async:true, legacy APIs can be loaded as "dojo" module:

require(['dojo'], function(dojo) {
  dojo.connect(...);
});

define

Defining individual AMD modules

define

Syntax analogous to require:

  • Integrating dependencies
  • Callback function called after loading process
  • The difference: We give something in return!
//Content of "/subdir/db.nsf/dojo/mindoo/tools/XspUtil.js":
					
define(['dojo/query'], function(query) {
  //return simple JS object with one method
  return {
    //getClientId finds DOM nodes with ID ending with sComponentId
    getClientId: function(sComponentId) {
      var arrNodes=query("[id$='"+sComponentId+"']");
      if (arrNodes && arrNodes.length>0) {
        var sId=arrNodes[0].id;
        return sId;
      }
   }
  }
});

require

Using the defined module:

  • Define the package location
  • Load modules/classes
<script type="text/javascript">
dojoConfig={
  packages:[
    { name:'mindoo', location:'/subdir/db.nsf/dojo/mindoo' }
  ]
}
</script>
<script type="text/javascript" src="path/to/dojo.js"></script>
<script type="text/javascript">
  //use classname of new module in require call:
  require(['mindoo/tools/XspUtil', 'dojo/domReady!'],
    function(XspUtil, domReady) {
      //call new defined method:
      var clientId=XspUtil.getClientId('mytextfield');
      alert(clientId); // view:_id1:mytextfield
  });
</script>

define for advanced users

Inheritance of classes with define:

define(['dojo/_base/declare', 'dojox/data/QueryReadStore'],
  function(declare, QueryReadStore) {
    //create class "mindoo/data/QueryReadStoreExt" as subclass
    //of "dojox/data/QueryReadStore"
    //optional 1st parameter with classname in declare() call,
    //becomes global variable:
    return declare("mindoo/data/QueryReadStoreExt", [QueryReadStore], {

       fetch:function(request) {
         request.serverQuery = {q:request.query.name};
        
         //call superclass method:
         return this.inherited("fetch", arguments);
       }
    });
});

The result can be loaded via require and instantiated with new.

Loading files

  • AMD special module "module" delivers path to current class
  • AMD plugin "dojo/text" loads files via Ajax
require(['module', 'dojo/dom'],
  function(module, dom) {
    var moduleUri=module.uri;
  
     //compute template path
     var templatePath=moduleUri+"/../templates/mytemplate.html";
   
     //load HTML file
     require(['dojo/text!' + templatePath], function(template) {
        dom.byId('content').innerHTML = template;
     });
});

Parsing of template and value replacement e.g. with
Django template language dojox/dtl

Now there is potential!

Demo: Activity stream in NSF

Integration in XPages

Using widgets

Activating Dojo option "parseOnLoad" with the Dojo panel of the XPage:

Using widgets

Entering required Dojo classes in the resource tab:

Notation with " . " in R9 Beta is required here to be able to use JS Optimizer

Using widgets

  • Equipping tags with Dojo type and attributes
  • For layout widgets either with xp:div or xp:panel

Using widgets

Analog approach for fields:


Using individual classes

Defining package location with
"Resources / Add / Dojo Module Path Resource",
then adding class as "Dojo module"

Using individual classes

Location of Dojo file in DB Design (package explorer)

Partial-refresh-resistant surfaces

  • Partial refresh sends field contents to the server that recalculates the page area
  • Returned HTML is injected into the page and parsed for Dojo widgets mark-up
  • May lead to a reset of widget properties:
    active tabs, scroll position in BorderContainer etc.
  • Solution: using partial refresh selectively or not at all

Finally readable Dojo sources!

  • New feature in Notes/Domino R9:
    "Use uncompressed resource files (CSS & Dojo)"
  • Worth a mint for the debugging of problems

Thank you!

Time for questions!

Resources

Dojo is great because:

Dojo features

Resources

Dojo features

Dojo Mobile 1.7 and 1.8

Resources

Dojo 2.0

Layouts

Dojo forms

Resources

Event handling

Drag and Drop

AMD

Resources

AMD

Special effects

Mobile Scrollpane

Resources

Alternative UI components

Custom Builds

Resources

Miscellaneous