Layout in Dojo: Part 2

0
98
11 min read

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.

LEAVE A REPLY

Please enter your comment!
Please enter your name here