Karsten Lehmann, Mindoo GmbH / @klehmann79
Ein SDK mit vier Bereichen:
ExtJS / Sencha Touch
dojox/calendar (Demo)
(noch Probleme mit der Skalierbarkeit)dojox/dgauges (Demo)
dojox/treemap (Demo)
28 neue Widgets:
Auswahldialoge, Fortschrittsbalken, Audio/Video, Scrollbereiche innerhalb von Seiten, Badge-Icons (Demo)
layoutPriority
-Attribut (definiert Reihenfolge)design="headline", hier zwei Widgets für "right"
dasselbe Layout mit design="sidebar":
<!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">
<!-- benötigte Widgets laden -->
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%">
<!-- enthaltene 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-config
<script type="text/javascript"
src="/xsp/.ibmxspres/dojoroot-1.8.0-u/dojo/dojo.js"
data-dojo-config="parseOnLoad:true"></script>
alternativ als globale Variable:
<script type="text/javascript">
dojoConfig={
parseOnLoad: true
}
</script>
<script type="text/javascript" src="/path/to/dojo.js"></script>
data-dojo-type
data-dojo-props
(optional)<div id="topPane" data-dojo-type="dijit/layout/ContentPane"
data-dojo-props="region:'top'" style="height:30px">
Top pane
</div>
<script type="text/javascript">
<!-- benötigte Widgets laden -->
require( ['dijit/layout/BorderContainer',
'dijit/layout/ContentPane'],
function(BorderContainer, ContentPane) {
//Callback hat Zugriff auf geladene Klassen
}
);
</script>
<script type="text/javascript">
require( ['dijit/layout/BorderContainer', 'dijit/layout/ContentPane',
'dojo/parser', 'dojo/ready'],
function(BorderContainer, ContentPane, parser, ready) {
//dojo/ready wartet bis DOM geladen ist
ready(function() {
parser.parse().then(function(arrWidgets) {
//aufgerufen mit Array aller erzeugten Widgets
for (var i=0; i<arrWidgets.length; i++) {
console.log('Widget erzeugt: '+arrWidgets[i].id);
}
});
});
});
</script>
dijit/layout/ContentPane
dijit/layout/AccordionPane
mehrere Bereiche untereinander, einer ist sichtbardijit/layout/TabContainer
Karteireiterdojox/layout/ExpandoPane
dojox/layout/GridContainer
Portal mit zwei 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 im Center-Bereich anzeigen -->
<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]">
<!-- Erstes Portlet -->
<div id="portlet1" data-dojo-type="dojox/widget/Portlet"
data-dojo-props="title:'Portlet 1',column:1">
Hello World.
</div>
<!-- Zweites 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>
3 Spalten, Portlets verschiebbar per Drag&Drop
require(['dijit/registry', 'dijit/layout/ContentPane'],
function(registry, ContentPane) {
//Ergänzen von Widgets mit addChild()
//main: vorhandener 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 berechnet Positionen neu
mainContainer.resize();
//Auslesen von enthaltenen Widgets mit getChildren()
var arrChildren=mainContainer.getChildren();
//Entfernen von Widgets mit removeChild()
var childWidget=arrChildren[0];
mainContainer.removeChild(childWidget);
//falls nicht mehr benötigt, Freigabe aller Ressourcen
childWidget.destroyRecursive();
}
Widgets können frei platziert werden, entweder deklarativ:
<body>
Lorem ipsum dolor sit amet.<br />
<input type="text" data-dojo-type="dijit/form/TextBox" /><br />
consetetur sadipscing elitr.
</body>
...oder programmatisch:
require(['dojo/_base/window', 'dojo/dom', 'dijit/registry'],
function(win, dom, registry) {
//Ziel-DOM-Knoten mit ID 'targetDomNodeId' finden:
var targetDomNode=dom.byId('targetDomNodeId');
//Bestehendes Widget mit ID 'myWidgetId' finden:
var myWidget=registry.byId('myWidgetId');
//Widget-Position ändern
//(first/last=erstes/letztes Kind,
//before/after=vor/nach Referenz-Knoten)
myWidget.placeAt(targetDomNode, 'first');
}
});
( Bitte alles auswendig lernen! ;-) )
Widget über dessen ID auffinden
// dijit/registry
var widget=registry.byId('widgetId1');
Lesen/Schreiben von Eigenschaften
var propValue=myWidget.get('value')
myWidget.set('value', 'xyz');
DOM-Knoten über dessen id auffinden
// dojo/dom
var nodeWithId=dom.byId('nodeId1')
Lesen und Ändern von Stilen
// dojo/dom-style
var styleValue=domStyle.get(node, 'display');
domStyle.set(node, 'backgroundColor', '#ff0000');
Lesen/Ändern von DOM-Knoten-Klassen
// dojo/dom-class
var nodeHasClass=domClass.has(node, 'myclass');
domClass.add(node, 'myclass');
domClass.remove(node, 'myclass');
Erzeugen und Platzieren von DOM-Knoten
// dojo/dom-construct
domConstruct.empty(refNode);
var createdNode=domConstruct.create('div',
{innerHTML:'<b>Test</b>'}, refNode, 'first');
domConstruct.place(refNode, otherNode, 'before');
Abmessungen von DOM-Knoten
// dojo/dom-geometry
var box=domGeom.getMarginBox(node);
//box.width, box.height, box.top, box.left
Finden von DOM-Knoten über CSS-Selector
// dojo/query
var arrNodes = query('.headline', bodyNode);
Sprachabhängige Formatierung eines Datums
// dojo/date/locale
var formattedDateStr=locale.format(myDate, {selector:'date'});
Konvertierung zwischen Date und ISO8601-Format
// dojo/date/stamp
var parsedDate=stamp.fromISOString(isoDateStr);
var dateAsString=stamp.toISOString(mydate);
Suche nach einem Wert in Arrays
// dojo/_base/array
var index=array.indexOf(myArray, valueToFind);
Parsen von JSON-Strings und Formatieren von Objekten als JSON
// dojo/json
var parsedJsonObj=JSON.parse("{'foo':'bar'}");
var jsonStr=JSON.stringify( {foo:'bar'} );
…und schließlich:
// dojo/_base/lang
// Objekt rekursiv klonen
var clonedObj=lang.clone(obj);
// Code in anderem Scope ausführen
var myfunction=lang.hitch(mygrid,
function(key, value) {this.set(key, value);} );
myfunction('store', newStore);
// Datentypprüfung:
lang.isArray(x) / lang.isFunction(x) / lang.isObject(x) /
lang.isString(x)
// Textersetzung
lang.replace("Hello {name.first} {name.last}!",
{ name: {first: "Rudi", last: "Knegt"} });
Reagieren auf Ereignisse
dojo.connect wurde aufgeteilt:
"on" im Event-Namen wird gestrichen:
//Registrieren von Event-Code für onClick-Event:
//verwendet dojo/dom und dojo/on
var myDomNode=dom.byId('nodeId');
var signal=on(myDomNode, 'click',
function(evt) {alert('Node clicked'); });
//Entfernen von Event-Code:
signal.remove();
Abfangen von 'hochbubblenden' Events in Eltern-Containern
<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>
require(['dojo/aspect', 'dojo/json', 'dojo'],
function(aspect, JSON, dojo) {
//veraltete Funktion 'dojo.xhrGet()' überschreiben
aspect.around(dojo, "xhrGet", function(originalXhr) {
return function(args) {
var t0=new Date().getTime();
//Originalfunktion aufrufen
var deferred = originalXhr(args);
//wir führen ein Callback zum Deferred-Objekt hinzu
deferred.then(function(data) {
var t1=new Date().getTime();
console.log("Data read in "+(t1-t0)+
"ms with request: "+JSON.stringify(args));
});
//und geben es an den Aufrufer weiter
return deferred;
}
});
});
require(['dojo/topic'], function(topic) {
topic.subscribe("some/topic", function(event){
// Objekt 'event' verarbeiten
});
//neues Event-Objekt publizieren an alle Subscriber
topic.publish("some/topic", {name:"My Birthday Party",
location:"Karlsruhe"});
});
Webformulare und Ajax
dijit/form/Form
vereinfacht Arbeit mit Webformularenmyform.validate()
dijit/form/Form
wird zu FORM-Tag im 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>
Formulardaten lassen sich komplett abziehen und setzen:
require(['dojo/json', 'dijit/registry'],
function(JSON, registry) {
var myform=registry.byId('myformid');
var formData=myform.get('value');
//Formulardaten als JSON-String anzeigen:
alert(JSON.stringify(formData));
//Formularfelder aktualisieren, basiert auf
//"name"-Attribute der Felder
myform.set('value', {firstname:'Rudi', lastname:'Knegt'});
//Formular validieren
if (!myform.validate())
alert('Formulardaten ungültig!');
}
});
require(["dojo/request/xhr"], function(xhr) {
xhr("myxagent.xsp", {
handleAs: "json"
method: "GET"
}).then(function(data) {
//Daten verarbeiten (hier JS-Objekt von JSON-Daten)
}, function(err) {
//Fehler melden bzw. loggen
}, function(evt){
//Fortschrittsinfo verarbeiten
//(erfordert XHR2-Support im Browser)
});
});
Asynchronous Module Definition
Soviele können das doch nicht sein!
Aktivierung über data-dojo-config:
<script type="text/javascript" src="path/to/dojo.js"
data-dojo-config="async:true"></script>
async:false (Default)
async:true
async:false
bei async:true können Legacy APIs als Modul "dojo" geladen werden:
require(['dojo'], function(dojo) {
dojo.connect(...);
});
Eigene AMD-Module definieren
Syntax analog zu require:
//Inhalt von "/subdir/db.nsf/dojo/mindoo/tools/XspUtil.js":
define(['dojo/query'], function(query) {
//einfaches JS-Objekt mit einer Methode zurückgeben
return {
//getClientId findet DOM-Knoten, deren ID mit sComponentId endet
getClientId: function(sComponentId) {
var arrNodes=query("[id$='"+sComponentId+"']");
if (arrNodes && arrNodes.length>0) {
var sId=arrNodes[0].id;
return sId;
}
}
}
});
Verwenden des definierten Moduls:
<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">
//Klassenname des neuen Modus an require übergeben:
require(['mindoo/tools/XspUtil', 'dojo/domReady!'],
function(XspUtil, domReady) {
//neu definierte Methode aufrufen:
var clientId=XspUtil.getClientId('mytextfield');
alert(clientId); // view:_id1:mytextfield
});
</script>
Vererbung von Klassen mit define:
define(['dojo/_base/declare', 'dojox/data/QueryReadStore'],
function(declare, QueryReadStore) {
//Klasse "mindoo/data/QueryReadStoreExt" von
//"dojox/data/QueryReadStore" ableiten
//1. Parameter mit Klassenname in declare() ist optional,
//wird globale Variable:
return declare("mindoo/data/QueryReadStoreExt", [QueryReadStore], {
fetch:function(request) {
request.serverQuery = {q:request.query.name};
//Methode der Elternklasse aufrufen:
return this.inherited("fetch", arguments);
}
});
});
Ergebnis kann per require geladen werden und mit new instanziiert.
require(['module', 'dojo/dom'],
function(module, dom) {
var moduleUri=module.uri;
//Pfad zu template berechnen
var templatePath=moduleUri+"/../templates/mytemplate.html";
//HTML-Datei laden
require(['dojo/text!' + templatePath], function(template) {
dom.byId('content').innerHTML = template;
});
});
Parsen von Template und Wertersetzung z.B. über
Django Template Language dojox/dtl
Demo: Activity Stream in NSF
Dojo-Option "parseOnLoad" über Dojo-Panel der XPage aktivieren:
Benötigte Dojo-Klassen im Ressourcen-Tab eintragen:
Notation mit " . " in R9 Beta hier erforderlich, um JS-Optimizer verwenden zu können
Analoges vorgehen für Felder:
Package-Location definieren über
"Resources / Add / Dojo Module Path Resource",
dann Klasse als "Dojo Module" aufnehmen
Ort der Dojo-Datei im DB-Design (Package-Explorer)
Argumente für Dojo
Dojo Features
Dojo Features
Dojo Mobile 1.7 und 1.8
Dojo 2.0
Layouts
Dojo-Forms
Event-Handling
Drag and Drop
AMD
AMD
Special effects
Mobile Scrollpane
Alternative UI-Komponenten
Custom Builds
Sonstiges