Layout in Dojo: Part 2

Exclusive offer: get 50% off this eBook here
Learning Dojo

Learning Dojo — Save 50%

A practical, comprehensive tutorial to building beautiful, scalable interactive interfaces for your Web 2.0 applications with Dijits

€16.99    €8.50
by Peter Svensson | July 2009 | Open Source

In the previous part of this article by Peter Svensson, we covered basic Dojo layout facts, ContentPane, container functions, DragPane, ExpandoPane, FloatingPane. In this part, we will focus on GridContainer, RadioGroup, RotatorContainer, ScrollPane, compound example using layout,and creating a widget.

GridContainer

There are a lot of sites available that let you add a lot of rss feeds and assorted widgets to a personal page, and which then also let you arrange them by dragging the widgets themselves around the page.

One of the most known examples is iGoogle, Google's personal homepage for users with a staggering amount of widgets that are easy to move around.

This functionality is called a GridContainer in Dojo. If you're not familiar with the concept and have never used a service which lets you rearrange widgets, it works like this:

  1. The GridContainer defines a number of different columns, called zones.
  2. Each column can contain any number of child widgets, including other containers (like AccordionContainer or BorderContainer).
  3. Each child widget becomes draggable and can be dragged into a new position within its own column, or dragged to a new position in another column.
  4. As the widget gets dragged, it uses a semi-transparent 'avatar'.
  5. As the widget gets dragged, possible target drop zones open up and close themselves dynamically under the cursor, until the widget is dropped on one of them.
  6. When a widget is dropped, the target column automatically rearranges itself to make the new widget fit.

Here is an example from test_GridContainer.html in /dojox/layout/tests/. This is what the GridContainer looks like from the beginning:

It has three columns (zones) defined which contain a number of child widgets. One of them is a Calendar widget, which is then dragged to the second column from its original position in the third:

Note the new target area being offered by the second column. This will be closed again if we continue to move the cursor over to the first column. Also, in the example above, transparency of 1.0 (none) is added to the avatar, which looks normal.

Finally, the widget is dropped onto the second column, both the source and target column arrange their widgets according to whether one has been added or removed.

The implications of this is that it becomes very simple to create highly dynamical interfaces. Some examples might be:

  1. An internal "dashboard" for management or other groups in the company which needs rearrangeable views on different data sources. Portlets done right.
  2. Using dojox.charting to create different diagrammatic views on data sources read from the server, letting the user create new diagrams and rearranging them in patterns or groups meaningful to the current viewer.
  3. A simple front-end for a CMS-system, where the editor widget is used to enter text, and the user can add, delete or change paragraphs as well as dragging them around and rearranging their order.

An example of how to create a GridContainer using markup (abbreviated) is as follows:

<div id="GC1" dojoType="dojox.layout.GridContainer"
nbZones="3"
opacity="0.7"
allowAutoScroll="true"
hasResizableColumns="false"
withHandles="true"
acceptTypes="dijit.layout.ContentPane, dijit.TitlePane,
dijit.ColorPalette, dijit._Calendar">
<div dojoType="dijit.layout.ContentPane" class="cpane" label="Content
Pane">Content Pane n?1 !</div>
<div dojoType="dijit.TitlePane" title="Ergo">
Non ergo erunt homines deliciis ...
</div>
<div dojoType="dijit.layout.ContentPane" class="cpane" label="Content
Pane">Content Pane n?2 !</div>
<div dojoType="dijit.layout.ContentPane" title="Intellectum">
Intellectum est enim mihi quidem in multis, et maxime in me ipso, sed
paulo ante in omnibus, cum M....
</div>
<div dojoType="dijit.layout.ContentPane" class="cpane" label="Content
Pane">Content Pane n?3 !</div>
<div dojoType="dijit.layout.ContentPane" class="cpane" label="Content
Pane">Content Pane n?4 !</div>
<div dojoType="dijit._Calendar"></div>
</div>

The GridContainer wraps all of its contents.These are not added is not added in a hierarchical manner, but instead all widgets are declared inside the GridContainer element. When the first column's height is filled, the next widget in the list gets added to the next column, and so on.

This is a quite unusual method of layout, and we might see some changes to this mode of layout since the GridContainer is very much beta [2008].

The properties for the GridContainer are the following:

//i18n: Object
//Contain i18n ressources.
i18n: null,

//isAutoOrganized: Boolean:
//Define auto organisation of children into the grid container.
isAutoOrganized : true,

//isRightFixed: Boolean
//Define if the right border has a fixed size.
isRightFixed:false,

//isLeftFixed: Boolean
//Define if the left border has a fixed size.
isLeftFixed:false,

//hasResizableColumns: Boolean
//Allow or not resizing of columns by a grip handle.
hasResizableColumns:true,

//nbZones: Integer
//The number of dropped zones.
nbZones:1,
//opacity: Integer
//Define the opacity of the DnD Avatar.
opacity:1,
//minColWidth: Integer
//Minimum column width in percentage.
minColWidth: 20,
//minChildWidth: Integer
//Minimun children with in pixel (only used for IE6 that doesn't
//handle min-width css property
minChildWidth : 150,
//acceptTypes: Array
//The gridcontainer will only accept the children that fit to
//the types.
//In order to do that, the child must have a widgetType or a
//dndType attribute corresponding to the accepted type.
acceptTypes: [],
//mode: String
//location to add columns, must be set to left or right(default)
mode: "right",
//allowAutoScroll: Boolean
//auto-scrolling enable inside the GridContainer
allowAutoScroll: false,
//timeDisplayPopup: Integer
//display time of popup in miliseconds
timeDisplayPopup: 1500,
//isOffset: Boolean
//if true : Let the mouse to its original location when moving
//(allow to specify it proper offset)
//if false : Current behavior, mouse in the upper left corner of
//the widget
isOffset: false,
//offsetDrag: Object
//Allow to specify its own offset (x and y) onl when Parameter
//isOffset is true
offsetDrag : {}, //
//withHandles: Boolean
//Specify if there is a specific drag handle on widgets
withHandles: false,
//handleClasses: Array
//Array of classes of nodes that will act as drag handles
handleClasses : [],

The property isAutoOrganized, which is set to true by default, can be set to false, which will leave holes in your source columns, and require you to manage the space in the target columns yourself.

The opacity variable is the opacity for the 'avatar' of the dragged widget, where 1 is completely solid, and 0 is completely transparent.

The hasResizableColumns variable also adds SplitContainer/BorderContainer splitters between columns, so that the user can change the size ratio between columns.

The minColWidt/minChildWidth variables manage the minimum widths of columns and child widgets in relation to resizing events.

The AcceptTypes variable is an important property, which lets you define which classes you allow to be dropped on a column. In the above example code, that string is set to dijit.layout.ContentPane, dijit.TitlePane, dijit.ColorPalette, dijit._Calendar. This makes it impossible to drop an AccordionContainer on a column.

The reason for this is that certain things would want to be fixed, like status bars or menus, but still inside one of the columns.

The withHandles variable can be set to true if you want each widget to get a visible 'drag handle' appended to it.

RadioGroup

The source code of dojox.layout.RadioGroup admits that it probably is poorly named, because it has little to do with radio buttons or groups of them, per se, even if this was probably the case when it was conceived.

The RadioGroup extends the StackContainer, doing something you probably had ideas about the first time you saw it – adding flashy animations when changing which child container is shown.

One example of how to use StackContainer and its derivatives is an information box for a list of friends. Each information box is created as a ContentPane which loads its content from a URL. As the user clicks on or hovers over the next friend on a nearby list, an event is triggered to show the next item (ContentPane) in the stack.

Enter the RadioGroup, which defines its own set of buttons that mirror the ContentPanes which it wraps.

The unit test dojox/layout/tests/test_RadioGroup.html defines a small RadioGroup in the following way:

<div dojoType="dojox.layout.RadioGroup" style="width:300px; 
height:300px; float:left;" hasButtons="true">
<div dojoType="dijit.layout.ContentPane" title="Dojo"
class="dojoPane" style="width:300px; height:300px; "></div>
<div dojoType="dijit.layout.ContentPane" title="Dijit"
class="dijitPane" style="width:300px; height:300px; "></div>
<div dojoType="dijit.layout.ContentPane" title="Dojox"
class="dojoxPane" style="width:300px; height:300px; "></div>
</div>

As you can see, it does not take much space. In the test, the ContentPanes are filled with only the logos for the different parts of Dojo, defined as background images by CSS classes.

The RadioGroup iterates over each child ContentPane, and creates a "hover button" for it, which is connected to an event handler which manages the transition, so if you don' t have any specific styling for your page and just want to get a quick mock-up done, the RadioGroup is very easy to work with.

The default RadioGroup works very much like its parent class, StackContainer, mostly providing a simple wrapper that generates mouseover buttons.

In the same file that defines the basic RadioGroup, there are two more widgets: RadioGroupFade and RadioGroupSlide. These have exactly the same kind of markup as their parent class, RadioGroup.

RadioGroupFade looks like this in its entirety:

dojo.declare("dojox.layout.RadioGroupFade",
dojox.layout.RadioGroup,
{
// summary: An extension on a stock RadioGroup, that fades the
//panes.
_hideChild: function(page){
// summary: hide the specified child widget
dojo.fadeOut({
node:page.domNode,
duration:this.duration,
onEnd: dojo.hitch(this,"inherited", arguments)
}).play();
},
_showChild: function(page){
// summary: show the specified child widget
this.inherited(arguments);
dojo.style(page.domNode,"opacity",0);
dojo.fadeIn({
node:page.domNode,
duration:this.duration
}).play();
}
});

As you can see, all it does is override two functions from RadioGroup which manage how to show and hide child nodes upon transitions.

The basic idea is to use the integral Dojo animations fadeIn and fadeOut for the effects.

The other class, RadioGroupSlide, is a little bit longer, but not by much. It goes beyond basic animations and uses a specific easing function. In the beginning of its definition is this variable:

// easing: Function
// A hook to override the default easing of the pane slides.
easing: "dojo.fx.easing.backOut",

Later on, in the overridden _hide and _showChild functions, this variable is used when creating a standalone animation:

...
this._anim = dojo.animateProperty({
node:page.domNode,
properties: {
left: 0,
top: 0
},
duration: this.duration,
easing: this.easing,
onEnd: dojo.hitch(page,function(){
if(this.onShow){ this.onShow(); }
if(this._loadCheck){ this._loadCheck(); }
})
});
this._anim.play();

 

What this means is that it is very simple to change (once again) what kind of animation is used when hiding the current child and showing next, which can be very usable.

Also, you can see that it is very simple to create your own subclass widget out of RadioGroup which can use custom actions when child nodes are changed.

ResizeHandle

The ResizeHandle tucks a resize handle, as the name implies, into the corner of an existing element or widget.

Layout in Dojo: Part 2

The element which defines the resize handle itself need not be a child element or even adjacent to the element which is to receive the handle. Instead the id of the target element is defined as an argument to the ResizeHandle as shown here:

<div dojoType="dijit.layout.ContentPane" title="Test window"
style="width: 300px; height: 200px; padding:10px; border: 1px solid
#dedede; position: relative; background: white;" id="testWindow">
...
<div id="hand1" dojoType="dojox.layout.ResizeHandle"
targetId="testWindow"></div>
</div>

In this example, a simple ContentPane is defined first, with some custom styling to make it stand out a little bit. Further on in the same pages comes a ResizeHandle definition which sets the targetId property of the newly created ResizeHandle to that of the ContentPane ('testWindow').

The definition of the ResizeHandle class shows some predictable goodies along with one or two surprises:

//targetContainer: DomNode
//over-ride targetId and attch this handle directly to a
//reference of a DomNode
targetContainer: null,
//resizeAxis: String
//one of: x|y|xy limit resizing to a single axis, default to xy ...
resizeAxis: "xy",
//activeResize: Boolean
//if true, node will size realtime with mouse movement,
//if false, node will create virtual node, and only resize target
//on mouseUp.
activeResize: false,
//activeResizeClass: String
//css class applied to virtual resize node.
activeResizeClass: 'dojoxResizeHandleClone',
//animateSizing: Boolean
//only applicable if activeResize = false. onMouseup, animate the
//node to the new size.
animateSizing: true,
//animateMethod: String
//one of "chain" or "combine" ... visual effect only.
combine will "scale"
//node to size, "chain" will alter width, then height
animateMethod: 'chain',
//animateDuration: Integer
//time in MS to run sizing animation. if animateMethod="chain",
//total animation playtime is 2*animateDuration.
animateDuration: 225,
//minHeight: Integer
//smallest height in px resized node can be
minHeight: 100,
//minWidth: Integer
//smallest width in px resize node can be
minWidth: 100,

As could be expected, it is simple to change if the resizing is animated during mouse move or afterwards (activeResize: true/false). If afterwards, the animateDuration declares in milliseconds the length of the animation.

A very useful property is the ability to lock the resizing action to just one of the two axes. The resizeAxis property defaults to xy, but can be set to only x or only y as well. Both restricts resizing to only one axis and also changes the resize cursor to show correct feedback to which axis is 'active' at the moment.

If you at any point want to remove the handle, calling destroy() on it will remove it from the target node without any repercussions.

Learning Dojo A practical, comprehensive tutorial to building beautiful, scalable interactive interfaces for your Web 2.0 applications with Dijits
Published: November 2008
eBook Price: €16.99
Book Price: €26.99
See more
Select your format and quantity:

RotatorContainer

The RotatorContainer is yet another useful derivative of StackContainer, which implements a slideshow-like functionality. Each child (typically, ContentPanes) can set a custom transitionDelay property, which changes the time it is shown. Otherwise, the centrally configured transitionDelay on the RotatorContainer itself controls how long each child is shown.

Layout in Dojo: Part 2

The RotatorContainer is created in the following way:

<div dojoType="dojox.layout.RotatorContainer" id="myRotator" 
showTabs="true" autoStart="true" transitionDelay="5000">
<div id="pane1" dojoType="dijit.layout.ContentPane" title="1">
Pane 1!
</div>
<div id="pane2" dojoType="dijit.layout.ContentPane" title="2">
Pane 2!
</div>
<div id="pane3" dojoType="dijit.layout.ContentPane"
title="3" transitionDelay="10000">
Pane 3 with overrided transitionDelay!
</div>
</div>

The file the defines the RotatorContainer also defines a widget called RotatorPager, which can act as a kind of remote-control for a given RotatorContainer.

In the picture above, there are two RotatorPagers, each with a different look. The undermost is defined like this:

<div dojoType="dojox.layout.RotatorPager" class="rotatorIcons"
rotatorId="myRotator">
<button dojoType="dijit.form.Button" iconClass="previous"
dojoAttachPoint="previous">Prev</button>
<button dojoType="dijit.form.ToggleButton"
iconClass="playPause" dojoAttachPoint="playPause">
</button>
<button dojoType="dijit.form.Button" iconClass="next"
dojoAttachPoint="next">Next</button>
<span dojoAttachPoint="current"></span> /
<span dojoAttachPoint="total"></span>
</div>

The important thing here is the rotatorId property, which attaches the RotatorPager to a specific RotatorContainer.

Also, very good use has been made of dojoAttachPoint. Instead of using dojo/connect script tags or more convoluted schemes of attaching behavior to controls, the widget simply uses the same kind of association used inside a template. But turning it inside out, allowing the user of the widget an elegant way to make almost any element a controller for a specific activity.

The possible attachPoints are (from source):

//The pager can contain the following components:
//* Previous button
// - Must be a dijit.form.Button
// - dojoAttachPoint must be named "previous"
//* Next button
// - Must be a dijit.form.Button
// - dojoAttachPoint must be named "next"
//* Play/Pause toggle button
// - Must be a dijit.form.ToggleButton
// - dojoAttachPoint must be named "playPause"
// - Use iconClass to specify toggled state
//* Current child #
// - dojoAttachPoint must be named "current"
//* Total # of children
// - dojoAttachPoint must be named "total"


The last two attachPoints define spots where the RotatorContainer will display the current child being viewed, and the total number of children, respectively.

ScrollPane

The ScrollPane is again a variant of using mouseover to change widget state in a very intuitive manner.

Many times you have a list of things that isn't broad or multifaceted enough to warrant a table-like Grid or a Tree, but you still want to present it somewhat orderly. Also, the list might be quite long and you would like to make it easy for people to browse around in it.

One simple solution is to use a regular div with overflow visible and a fixed height. The ScrollPane does away with a lot of details and just lets you present things and browse them by just moving the mouse over either of the active ends. If there are more items to show, the ScrollPane will scroll the remaining items in a natural speed into the viewing area.

Layout in Dojo: Part 2

As you see in the picture above, the ScrollPane has a small helper line, which helps the user see which part of the list of items they are currently browsing.

The ScrollPane is very simple to use and can be created vertically as well.

In the future, we might see ScrollPanes that have separators based on the first letter of the title of items, and also creates a helper bar which is ordered in the same way.

The ScrollPane is created like this:

<div dojoType="dojox.layout.ScrollPane" style="width:100px;
height:300px; border:1px solid #b7b7b7">
<ol class="list" id="vertList">
<li id="cloneMe"><a href="#"><span>testItem</span></a></li>
</ol>
</div>

To get a horizontal ScrollPane, use the property orientation set to "horizontal".

 

Compound example using layout

The following is a more complicated example which is a small information box for a fictitious adventure game. A lot of information is to be displayed in a small area:

  • Adventurer status
  • Characteristics
  • Inventory
  • Quests

Using the BorderLayout container for overall layout, it consists of three parts: a toolbar, an accordioncontainer, and a horizontal ScrollPane top display the inventory.

Layout in Dojo: Part 2

The toolbar is not particularly active in the example, but shows how to use a simple toolbar menu in conjunction with a BorderLayout container.

The source code for the example is here:

  <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/
TR/html4/strict.dtd">
<html>
<head>
<title>Chapter 6 Layout Example</title>
<style>
@import "dojo-1.1.0/dojo/resources/dojo.css";
@import "dojo-1.1.0/dijit/tests/css/dijitTests.css";
@import "dojo-1.1.0/dijit/themes/tundra/tundra.css";
@import "dojo-1.1.0/dojox/widget/Rating/Rating.css";
</style>
<script type="text/javascript" src='//dgdsbygo8mp3h.cloudfront.net/sites/default/files/blank.gif' data-original="dojo-1.1.0/dojo/dojo.js"
djConfig="isDebug: true, parseOnLoad: true"></script>
<script type="text/javascript">
dojo.require("dijit.Menu");
dojo.require("dijit.form.CheckBox");
dojo.require("dijit.form.TextBox");
dojo.require("dijit.layout.BorderContainer");
dojo.require("dijit.layout.ContentPane");
dojo.require("dijit.form.Button");
dojo.require("dijit.Toolbar");
dojo.require("dojox.widget.Rating");
dojo.require("dojox.layout.ScrollPane");
dojo.require("dijit.layout.AccordionContainer");
dojo.require("dojo.parser");
// scan page for widgets and instantiate them
</script>
<script>
function handleMenu(m)
{
console.log("handleMenu called for menu '"+m+"'");
}
</script>
</head>
<body class="tundra">
<div dojoType="dijit.layout.BorderContainer" style="border:
1px solid black; height: 300px; width:300px;padding: 10px;">
<div dojoType="dijit.layout.ContentPane" region="top">
<div id="menubar" dojoType="dijit.Toolbar" class="menuBar">
<div dojoType="dijit.form.DropDownButton">
<span>File</span>
<div dojoType="dijit.Menu">
<div dojoType="dijit.MenuItem" onClick="handleMenu('New profile')">
New Profile</div>
<div dojoType="dijit.MenuItem" onClick="handleMenu('Open
Profile')">Open Profile</div>
<div dojoType="dijit.MenuSeparator"></div>
<div dojoType="dijit.MenuItem" iconClass="dijitEditorIcon
dijitEditorIconSave" onClick="handleMenu('Save Profile')">Save
Profile</div>
<div dojoType="dijit.MenuItem" onClick="handleMenu('Save Profile
As..')">Save Profile As...</div>
</div>
</div>
<div dojoType="dijit.form.DropDownButton">
<span>Edit</span>
<div dojoType="dijit.Menu">
<div dojoType="dijit.MenuItem" iconClass="dijitEditorIcon
dijitEditorIconCut" onClick="handleMenu('Cut')">Cut</div>
<div dojoType="dijit.MenuItem" iconClass="dijitEditorIcon
dijitEditorIconCopy" onClick="handleMenu('Copy')">Copy</div>
<div dojoType="dijit.MenuItem" iconClass="dijitEditorIcon
dijitEditorIconPaste" onClick="handleMenu('Paste')">Paste</div>
</div>
</div>
</div>
</div>
<div dojoType="dijit.layout.ContentPane" region="center">
<div dojoType="dijit.layout.AccordionContainer" style="height:
210px; overflow: hidden">
<div dojoType="dijit.layout.AccordionPane" title="Overview">
Profile: <b>Foo Barsson</b><br/>
Status: <b>Cool</b>
</div>
<div dojoType="dijit.layout.AccordionPane" title="Quests">
<bl>
<li>Defeat Giant Mouse in the Cellar of Hierloom House</li>
<li>Find the Apple of Perfume for Fiona the Enchantress</li>
<li>Collect six Emerald Centipedes</li>
</bl>
</div>
<div dojoType="dijit.layout.AccordionPane" title="Charateristics">
<span id="rating0" dojoType="dojox.widget.Rating" numStars="5" value
= "5" onChange="dojo.query('#rating0Value')[0].innerHTML =
this.value"></span>
Strength: <b><span id="rating0Value">5</span></b>
<br/>
<span id="rating1" dojoType="dojox.widget.Rating" numStars="5" value
= "4" onChange="dojo.query('#rating1Value')[0].innerHTML =
this.value"></span>
Intelligence: <b><span id="rating1Value">5</span></b>
<br/>
<span id="rating2" dojoType="dojox.widget.Rating" numStars="5" value
= "2" onChange="dojo.query('#rating2Value')[0].innerHTML = this.
value"></span>
Wisdom: <b><span id="rating2Value">2</span></b>
<br/>
<span id="rating3" dojoType="dojox.widget.Rating" numStars="5"
value = "5" onChange="dojo.query('#rating3Value')[0].innerHTML =
this.value"></span>
Dexterity: <b><span id="rating3Value">5</span></b>
<br/>
<span id="rating4" dojoType="dojox.widget.Rating" numStars="5"
value = "4" onChange="dojo.query('#rating4Value')[0].innerHTML =
this.value"></span>
Charisma: <b><span id="rating4Value">4</span></b>
<br/>
</div>

</div>
<div dojoType="dojox.layout.ScrollPane" region="bottom"
orientation="horizontal" style=" border:1px solid #b7b7b7">
<table class="list" id="vertList">
<tr>
<td style="border:1px solid black;">Dagger</td>
<td style="border:1px solid black;">Banded Mail</td>
<td style="border:1px solid black;">Potion</td>
<td style="border:1px solid black;">Short Sword</td>
<td style="border:1px solid black;">Pointed Hat</td>
<td style="border:1px solid black;">Leather Boots</td>
<td style="border:1px solid black;">Small Wooden Box</td>
<td style="border:1px solid black;">Steel Helmet</td>
<td style="border:1px solid black;">6 Wooden Arrows</td>
<td style="border:1px solid black;">Orc Teeth</td>
<td style="border:1px solid black;">Wooden Staff</td>
<td style="border:1px solid black;">Zircon Ring</td>
</tr>
</table>
</div>
</div>
</div>
</body>
</html> 

 

The Toolbar

The Toolbar is made up of two DropDown buttons, which add simple onclick handlers which call back to a single handler called handleMenu. As you can see, it is not particularly complicated, only logging the button which was clicked.

When using the Toolbar, it is very important to remember to use the 'tundra' class on the body element (or any other Dojo theme), since the toolbar effect is heavily dependent on the right classes.

The BorderContainer

When beginning a layout, it's a good idea to begin with the container and the constituent ContentPanes, setting heights and widths as good as you can. It's often hard to get the ratios right the very first time, so it's beneficial using a 'scaffolding' layout with a minimum of other components while trying out which stylings are needed.

Another good idea is to use different glaring background colors for all ContentPanes, possibly also solid borders. This makes it easier to spot bugs in the design as quickly as possible.

The design used here uses only three parts: one "top" for the Toolbar, a "center" for the AccordionContainer, and a "bottom" for the Inverntory ScrollPane.

Ideally, the Inventory would have been a fourth tab on the AccordionPane, but since AccordionPane does not support other Layout Containers as children, it had to have its own ContentPane parallel to the AccordionContainer instead.

The AccordionContainer

Three different AccordionPanes were used, only one showing at a time, with a nice animation between changes. The central pane "Characteristics" uses a lot of Rating widgets. These are very effective at displaying values at a glance, and the ratings provide a simple example showing how to collect the changes to each widget.

The ScrollPane

The ScrollPane is like the AccordionContainer, a very good way to tuck away a lot of information that is still browseable in an intuitive manner. The adventurer has collected quite a few items, all of which do not fit in the allotted width for the BorderContainer. Moving the cursor to the right or left of the ScrollPane will pan hidden items into sight automatically.

Note that when using a "horizonal" layout for the ScrollPane, you must also provide the elements horizontally. If a list element would have been used instead of a table, the information would have been displayed vertically regardless.

One step forward—creating a widget

Now that the BorderContainer display works fine, we suddenly get a change of plans. No longer it is sufficient to display only one adventurer, instead we need to create any number of adventurers, possibly as separate windows.

Clearly, this is possible given the widgets we've already discussed in this article. But to create any number of adventurer profiles on screen, we need to revisit how to create a Dijit.

Why a Dijit? Because we need to leverage Dojo to create a template out of the design above. This way we can create as many copies of it as we would like, without having to take care of specific ids, colliding global variables, and the like. Fortunately, creating a Dojo Widget out of a single mockup page is fairly simple.

First, we create a subdirectory for our project, which might contain more than one widget when we're done. The best choice is to put this directory parallel to the other Dojo directories so that we can use dojo.require() for out new widget.

We will use the following structure:

dojo
dijit
dojox
adventure
profile.js
profile.html
profile_test.html

So we create a directory called adventure and call the widget profile, with the definition residing inside the file profile.js. That file, in turn, points to the file profile.html as containing the html template for the widget.

The test file is a regular html file which contains an example test of the new widget:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://
www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<title>Adventure Profile Test</title>
<style>
@import "../dojo/resources/dojo.css";
@import "../dijit/themes/dijit.css";
@import "../dijit/themes/tundra/tundra.css";
@import "../adventure/profile.css";
@import "../dojox/widget/Rating/Rating.css";
</style>
<script>
djConfig=
{
isDebug: true,
parseOnLoad: true
}
</script>
<script type="text/javascript" src='//dgdsbygo8mp3h.cloudfront.net/sites/default/files/blank.gif' data-original="../dojo/dojo.js">
</script>
<script type="text/javascript">
dojo.require("dojo.parser");
// scan page for widgets and instantiate them
dojo.require("adventure.profile");
</script>
</head>
<body class="tundra">
<div dojoType="adventure.profile"
name = "Foo Barsson"
status = "OK"
questinfo = "Defeat Giant Mouse in the Cellar
of Hierloom House,Find the Apple of Perfume for
Fiona the Enchantress,Collect six Emerald Centipedes"
strength = "5"
intelligence="4"
wisdom="4"
dexterity="5"
charisma="4"
inventory="Dagger,Banded Mail,Potion,Short Sword,Pointed
Hat,Leather Boots,Small Wooden Box,Steel Helmet,6 Wooden
Arrows,Orc Teeth,Wooden Staff,Zircon Ring" ></div>
<br/>
<div dojoType="adventure.profile"
name = "Fighter Fightersson"
status = "OK"
questinfo = "Find Orcs,Kill Orcs,Find Entrance to
Dungeon,Find Loot"
strength = "5"
intelligence="2"
wisdom="3"
dexterity="5"
charisma="3"
inventory="Cheese, Nails,Banded Mail,Potion,Long Sword,
Brass Gauntlets,Leather Boots,Mithril Armor,Steel Helmet,
Flask of Oil,Torch,Holy Handgrenade,Robe of Invisibility,
Ruby Ring">
</div>
</body>
</html>

The first step in creating a widget of a mockup page is to remove all data and either put it in separate files, to be loaded from data-driven components, or given as arguments as we see above. Two instances of the newly created adventure.profile widget are put on the page, each with varying arguments. These will be inserted into the widget's variables with the same names automagically.

Since the mockup used some static tables to lay out the data, I've opted to put the lists of things (like quests) into comma-separated strings. These are then parsed when the widget starts up and used as content of the requisite kind of markup created on the fly and added to parent elements in the template.

The template for the widget is where most of the markup has gone. It looks mostly the same, with some crucial differences:

  1. All static ids on elements have either been removed, or augmented with ${id}. When adding ${id}, you take advantage of Dojo's common templating mechanisms to insert the current Dom id of the widget's top element, making the id unique in the page, regardless of how many instances of the widget are present.
  2. When possible, dojoAttachPoints have been added, where before unique id references were used to refer to elements and/or sub-widgets. When defining a dojoAttachPoint="xxx" on an element, that name will then be accessible inside the JavaScript widget definition as this.xxx.
  3. All parts of the widget that display contents of variables replace the hard-coded values of the mockup with a ${} variable.

To illustrate the last point, the AccordionPane that shows the name and status of the adventurer now looks like this:

<div dojoType="dijit.layout.AccordionPane" title="Overview">
Profile: <b>${name}</b><br/>
Status: <b>${status}</b>
</div>

where name and status are arguments given in the markup creation of the widget.

An example of how to use attach points can be seen in the former ScrollPane declaration in the mockup, which now is just an ordinary element:

<div dojoAttachPoint="inventorypane">
<table>
<tr dojoAttachPoint="inventoryRow">
</tr>
</table>
</div>

Here is the complete template for the widget:

<div>
<div dojoType="dijit.layout.BorderContainer" style="border:
1px solid black; height: 300px; width:300px;padding: 10px;">
<div dojoType="dijit.layout.ContentPane" region="top">
<div dojoType="dijit.Toolbar" class="menuBar">
<div dojoType="dijit.form.DropDownButton">
<span>File</span>
<div dojoType="dijit.Menu">
<div dojoType="dijit.MenuItem" dojoAttachPoint=
"new_profile">New Profile</div>
<div dojoType="dijit.MenuItem" dojoAttachPoint="open_profile">
Open Profile</div>
<div dojoType="dijit.MenuSeparator"></div>
<div dojoType="dijit.MenuItem" iconClass="dijitEditorIcon
dijitEditorIconSave" dojoAttachPoint="save_profile">Save Profile
</div>
<div dojoType="dijit.MenuItem" dojoAttachPoint="save_profile_as">
Save Profile As...</div>
</div>
</div>
<div dojoType="dijit.form.DropDownButton">
<span>Edit</span>
<div dojoType="dijit.Menu">
<div dojoType="dijit.MenuItem" iconClass="dijitEditorIcon
dijitEditorIconCut" dojoAttachPoint="cut">Cut</div>
<div dojoType="dijit.MenuItem" iconClass="dijitEditorIcon
dijitEditorIconCopy" dojoAttachPoint="copy">Copy</div>
<div dojoType="dijit.MenuItem" iconClass="dijitEditorIcon
dijitEditorIconPaste" dojoAttachPoint="paste">Paste</div>
</div>
</div>
</div>
</div>
<div dojoType="dijit.layout.ContentPane" region="center">
<div dojoType="dijit.layout.AccordionContainer"
style="height: 210px; overflow: hidden">
<div dojoType="dijit.layout.AccordionPane" title="Overview">
Profile: <b>${name}</b><br/>
Status: <b>${status}</b>
</div>
<div dojoType="dijit.layout.AccordionPane" title="Quests">
<bl dojoAttachPoint="quests"></bl>
</div>
<div dojoType="dijit.layout.AccordionPane" title="Charateristics">
<span id="${id}rating0" dojoType="dojox.widget.Rating" numStars="5"
value = "${strength}" onChange="dojo.byId('${id}rating0Value').
innerHTML = this.value"></span>
Strength: <b><span id="${id}rating0Value">${strength}</span></b>
<br/>
<span id="${id}rating1" dojoType="dojox.widget.Rating" numStars="5"
value = "${intelligence}" onChange="dojo.byId('${id}rating1Value').
innerHTML = this.value"></span>
Intelligence: <b><span id="${id}rating1Value">${intelligence}
</span></b>
<br/>
<span id="${id}rating2" dojoType="dojox.widget.Rating" numStars="5"
value = "${wisdom}" onChange="dojo.byId('${id}rating2Value').
innerHTML = this.value"></span>
Wisdom: <b><span id="${id}rating2Value">${wisdom}</span></b>
<br/>
<span id="${id}rating3" dojoType="dojox.widget.Rating" numStars="5"
value = "${dexterity}" onChange="dojo.byId('${id}rating3Value').
innerHTML = this.value"></span>
Dexterity: <b><span id="${id}rating3Value">${dexterity}</span></b>
<br/>
<span id="${id}rating4" dojoType="dojox.widget.Rating" numStars="5"
value = "${charisma}" onChange="dojo.byId('${id}rating4Value').
innerHTML = this.value"></span>
Charisma: <b><span id="${id}rating4Value">${charisma}</span></b>
<br/>
</div>
</div>
<div dojoAttachPoint="inventorypane">
<table>
<tr dojoAttachPoint="inventoryRow">
</tr>
</table>
</div>
</div>
</div>
</div>

The rating widgets are a bit verbose in their definitions. One alternative would have been to create a separate customized subclass of the Rating widget, which managed its own update, to also show a number to the side.

We could also, of course, have managed the changes inside the logic of this widget as well.

In the file that defines the widget, the startup function reads both the quests and the inventory strings, which are split into an array and then used to populate the two parts of the widget that show variable amounts of data.

Here is the JavaScript for the widget definition:

dojo.provide("adventure.profile");
dojo.require("dijit._Templated");
dojo.require("dijit._Widget");
dojo.require("dijit.Dialog");
dojo.require("dijit.Menu");
dojo.require("dijit.layout.BorderContainer");
dojo.require("dijit.layout.ContentPane");
dojo.require("dijit.form.Button");
dojo.require("dijit.Toolbar");
dojo.require("dojox.widget.Rating");
dojo.require("dojox.layout.ScrollPane");
dojo.require("dijit.layout.AccordionContainer");
dojo.declare("adventure.profile", [ dijit._Widget,
dijit._Templated],
{
templatePath: dojo.moduleUrl("adventure","profile.html"),
widgetsInTemplate: true,
name: "none",
status: "undefined",
questinfo: "",
strength: 0,
intelligence: 0,
wisdom: 0,
dexterity: 0,
charisma: 0,
inventory: "",
handleMenu: function(e)
{
var t = e.currentTarget.textContent;
console.log("adventure.profile handleMenu called for "+t);
},
startup: function()
{
console.log("startup for adventure.profile called.");
dojo.connect(this.new_profile, "onClick", this.handleMenu);
dojo.connect(this.open_profile, "onClick", this.handleMenu);
dojo.connect(this.save_profile, "onClick", this.handleMenu);
dojo.connect(this.save_profile_as, "onClick", this.handleMenu);
dojo.connect(this.cut, "onClick", this.handleMenu);
dojo.connect(this.copy, "onClick", this.handleMenu);
dojo.connect(this.paste, "onClick", this.handleMenu);
var q = this.questinfo.split(",");
for(var quest in q)
{
var li = dojo.doc.createElement('li');
var span = dojo.doc.createElement('span');
li.appendChild(span)
span.innerHTML=q[quest];
this.quests.appendChild(li);
};
var i = this.inventory.split(",");
for(var it in i)
{
var td = dojo.doc.createElement('td');
td.innerHTML=i[it];
td.style.height = "30px;";
td.style.border = "1px solid black";
this.inventoryRow.appendChild(td);
};
var pane = new dojox.layout.ScrollPane(
{
region: "bottom",
orientation: "horizontal"
}, this.inventorypane);
pane.startup();
},

postCreate: function()
{
console.log("postCreate for adventure.profile called.");
}
});

Note that we were able to move all the specific require statements from the html file where the widget is created, to the definition of the widget. This makes the use of the widget much simpler.

On the other hand, all specific CSS that the constituent parts of the widget need still needs to be included in the file where the widget is to be created by markup, so true transparency of resources is still a bit away.

Another important thing to reemphasize is that all arguments to be passed as variables from markup to widget need also be defined in the widget. If the widget variable name was to be deleted for some reason, that argument would not be carried over from markup to widget and will look "undefined" in the widget upon creation.

Summary

In this part, we covered:

  • The RadioGroup LayoutContainer is a good component for simple site navigation, showing one HTML snippet at a time, using animated transitions.
  • RotatorContainer, a useful derivative of StackContainer, implements a slideshow-like functionality
  • Code to create ScrollPane
  • Example of a small information box for a fictitious adventure game
  • Creation of a widget
Learning Dojo A practical, comprehensive tutorial to building beautiful, scalable interactive interfaces for your Web 2.0 applications with Dijits
Published: November 2008
eBook Price: €16.99
Book Price: €26.99
See more
Select your format and quantity:

 

 

About the Author :


Peter Svensson

Peter Svensson is an Ajax evangelist and front-end architect at Nethouse AB. He is
the instigator of the Thin Server Architecture Working Group and a Dojo contributor.
He's an avid JavaScript aficionado who really likes dynamic languages and would
very much appreciate if the rest of the world would catch up really soon.

Books From Packt

Drools JBoss Rules 5.0 Developer's Guide
Drools JBoss Rules 5.0 Developer's Guide

Plone 3 Theming
Plone 3 Theming

WordPress 2.7 Cookbook
WordPress 2.7 Cookbook

Building Enterprise Ready Telephony Systems with sipXecs 4.0
Building Enterprise Ready Telephony Systems with sipXecs 4.0

PHP and script.aculo.us Web 2.0 Application Interfaces
PHP and script.aculo.us Web 2.0 Application Interfaces

Drupal 6 JavaScript and jQuery
Drupal 6 JavaScript and jQuery

AJAX and PHP: Building Responsive Web Applications
    AJAX and PHP: Building Responsive Web Applications

ICEfaces 1.8: Next Generation Enterprise Web Development [RAW]
ICEfaces 1.8: Next Generation Enterprise Web Development [RAW]

Code Download and Errata
Packt Anytime, Anywhere
Register Books
Print Upgrades
eBook Downloads
Video Support
Contact Us
Awards Voting Nominations Previous Winners
Judges Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software
Resources
Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software