A common use case for DHTML/ajax is to fetch a fragment of html using XHR or some other way, and change the innerHTML of a div with that content. Problem with this is that it doesn't instanciate widgets and doesn't fire scripts. ContentPane was created to make widgets and scripts work and reduce the potential for memory leaks. ContentPane is a base widget for many (Html)widgets, it handles remote loading as well as local setting of content and instanciating widgets in that content. Think of it as islands in your page that can easily switch content using setContent() or setUrl().
Many other widgets inherits ContentPane, like Tooltip, Dialog, FloatingPane etc. That means that all the methods and properties of ContentPane also applies to them.
ContentPane is often used as children of Layout widgets like LayoutContainer, TabContainer, AccordionContainer
Dont misstake it for a Iframe though, It should not be used on very large html fragments.
Simple usage ... <div id="cpane" dojoType="contentPane" href="initialContent.html"><div> <a href="javascript:dojo.widget.byId('nextContent.html')">Goto nextPage</a> ...
Basic options
Methods apart form those provided by ContentPane's superclass HtmlWidget
Methods (Intended as event hooks using dojo.event.connect)
In order to prevent the default messages you can do something like this:
<script>
var myLoadMessage = {
show: function(event){
event.preventDefault();
... custom code here
},
hide: function(){...}
}
dojo.addOnLoad(function(){
var pane = dojo.widget.byId('myPaneId');
dojo.event.connect(pane, "onDownloadStart", myLoadmessage, "show");
});
</script>
<div dojoType="ContentPane" id="myPaneId">...startcontent...</div>
or
<div dojoType="ContentPane"
onDownloadStart="myLoadMessage.show(arguments[0]);">...startcontent...</div>
When used as a child to TabContainer, AccordionContainer or PageContainer TabContainer, AccordionContainer or PageContainer extends Widgets with these extra options
When used as a child of LayoutContainer LayoutContainer package extends Widgets with this option
FAQ
<script>
var i = 0;
function addToI( j ){
i = i + j;
return i;
}
</script>
becomes (from window scope):
(function( ){
var i = 0;
function addtoI( j ){
i = i + j;
return i;
}
})
<script>
i = 0; // note lack of var
addToI = function( j ){ // we could also do window.addToI = function( j ){ ...
i = i + j;
return i;
}
</script>
becomes (from window scope)
i = 0;
function addtoI( j ){
i = i + j;
return i;
}
<script>
this.i = 0;
this.addToI = function( j ){
this.i = this.i + j;
return this.i;
}
</script>
becomes (from window scope)
(function(){
this.i = 0; // now it is a property of this function and can be reached from the outside.
this.addToI = function( j ){
this.i = this.i + j;
return this.i;
}
})
var added = dojo.widget.byId('myPaneId').scriptScope.addToI( 10 );
dojo.debug(added) // prints 10
added = dojo.widget.byId('myPaneId').scriptScope.addToI( 10 );
dojo.debug(added); // prints 20
and so on...
Now lets say we have html that would like to alert the value of i (this.i) in plain html that would be:
<button onclick="alert(i);">Tell me i !</button> // As explained above this wont work in ContentPane(unless you set it to global by omitting var).Now if we now the ID of the contentPane that pulls in this content we could do:
<button onclick="alert(dojo.widget.byId('myPaneId').scriptScope.i);">Tell me i !</button>
// this will work in ContentPane
That is'nt very useful as we don't always know the ID of the contentPane that pulls in the html when we write the content.
<button onclick=" scriptScope.i">Tell me i !</button> // this will work in ContentPane // NOTE: Due to a bug in ContentPane 0.3.1 you need to add a extra space before the keyword // Thank you Sasha Firsov for finding that!The parent scope of scriptScope is window, the reason for that is to avoid messing with widget internals. Just imagine the disaster a redefinition of setUrl function would cause otherwise.
... some content <div dojoType="DatePicker" id="myPicker"></div> ... rest of contentA content script could look like:
<script>
var o = {
storeDate: function( ){
var datePick = dojo.widget.byId('myPicker');
var date = datePick.storedDate;
// save date somewhere
}
};
_container_.addOnLoad(function(){
var picker = dojo.widget.byId('myPicker');
dojo.event.connect(picker, "onSetDate", o, "storeDate");
});
// remember to disconnect onUnLoad, very important!!
_container_.addOnUnLoad(function(){
var picker = dojo.widget.byId('myPicker');
dojo.event.disconnect(picker, "onSetDate", o, "storeDate");
});
</script>
<html>
<head>
<script>
var djConfig = {isDebug: true};
</script>
<script src="dojo/dojo.js"></script>
<script>
var scriptScope = this;
if(typeof _container_ == 'undefined'){
var _container_ = dojo;
}
_container_.addOnLoad(function(){
dojo.debug("Successfully loaded!");
});
this.doWhenClicked = function(txt){
dojo.debug(txt);
}
</script>
<body>
<a href="javascript:scriptScope.doWhenClicked('You clicked a link!');">Click here!</a>;
</body>
</html>
*********Your mainpage***********
<html>
<head>
<script src="dojo/dojo.js"></script>
<script>
dojo.require("dojo.widget.ContentPane");
dojo.require("dojo.widget.LayoutContainer");
function changeUrlInClient(url){
var client = dojo.widget.byId("client");
client.setUrl(url);
}
</script>
</head>
<body>
<div dojoType="LayoutContainer" layoutChildPriority='none' style="border: 1px solid blue; width: 800px; height: 300px;">
<div dojoType="ContentPane" layoutAlign="left" style="width: 200px;" executeScripts="true" href="linkpage.html"></div>
<div widgetId="client" dojoType="ContentPane" layoutAlign="client" style="border:1px solid red;"></div>
</div>
</body>
</html>
*******linkpage.html************
<html>
<head>
<script>
var o = {
listen: function(evt){
// if the onclick came from a
</head>
<body>
<a href="content1.html">content1</a>
<a href="content2.html">content2</a>
<a href="content3.html">content3</a>
<a href="content4.html">content4</a>
</body>
</html>
**********mainpage************
<html>
<head>
<script src="dojo/dojo.js"></script>
<script>
dojo.require("dojo.widget.FloatingPane");
dojo.require("dojo.widget.Button");
</script>
</head>
<body>
<div dojoType="FloatingPane"
title="Login example"
style="width: 300px; height: 300px;"
executeScripts="true"
cacheContent="false"
href="login.php">
</div>
</body>
</html>
*********login.php**********
<?php
session_start();
// are we trying to login?
if(isset($_GET["login"])){
// this could of be a database instead
$users = array(
"JohnDoe"=>
array("pass"=>"foo", "id"=>1),
"JaneDoe"=>
array("pass"=>"bar", "id"=>2),
"JuniorDoe"=>
array("pass"=>"baz", "id"=>3)
);
if(isset($_POST["user"]) && isset($_POST["pass"])){
$pass = $_POST["pass"];
$user = $_POST["user"];
if(isset($users[$user]) && ($users[$user]["pass"] == $pass)){
$_SESSION["id"] = $users[$user]["id"];
exit("(true);");
}
}
//if we get here we have failed to login
exit("(false);");
}
// logout?
if(isset($_GET["logout"])){
unset($_SESSION["id"]);
}
if(isset($_SESSION["id"])){
// it is safe to show secret content
?>
<script type="text/javascript">
this.logout = function(){
_container_.setUrl("login.php?logout=true");
}
</script>
<h3>You have successfully logged in!</h3>
showing secret content here
<a href="#" onclick="scriptScope.logout();">log out</a>
<?php
}else{
//no it wasnt safe, show our login script
?>
<script type="text/javascript">
this.ok = function(){
_container_.domNode.style.cursor = "wait";
dojo.io.bind({
formNode: dojo.byId("login"),
mimetype: "text/javascript",
handler: function(type, data){dojo.debug(data);
_container_.domNode.style.cursor = "";
if(type=="load"){
if(data){
_container_.setUrl("login.php");
}else{
dojo.byId("message").innerHTML = "Wrong username or password";
}
}else{
dojo.byId("message").innerHTML = "An error occured while login, please try again.";
}
}
});
}
this.quit = function(){
_container_.hide();
}
</script>
<form name="login" id="login" method="post" action="login.php?login=true">
<div id="message" style="text-align:center; color: red;">You need to login</div>
<label for="user">Username:
<input type="text" name="user"/>
<label for="pass">Password:</label>
<input type="password" name="pass"/>
<button dojoType="Button" onClick="scriptScope.ok();"/>login</button>
<button dojoType="Button" onClick="scriptScope.quit();">quit</button>
</form>
<?php
}
?>
<html>
<head>
<script src="dojo/dojo.js"></script>
<script>
dojo.require("dojo.widget.ContentPane");
</script>
</head>
<body>
<div dojoType="ContentPane" >
<script>
// this script will fire before dojo makes our parent a ContentPane widget, so it wont be
// affected by executeScripts at all.
// i wont have a _container_ variable and scriptScope wont hide any variables
// it will work just as a inlne javascript block always has
alert("This alert will fire event if you have executeScripts=false");
</script>
</div>
</body>
</html>
Like HTML buttons, the dojo button sizes to fit its content. Usually, you will provide an onclick="..." attribute to specify what happens when the button is pressed.
This needs to be rewritten for 0.9
By default, dojo uses a blue gradient background. But you can provide your own. You will need to create three .gif images: one for the left, one for the right, and one for the center. The filenames must end with l, r, or c, respectively. You can specify image sets for four different conditions:
For example, you can use these files:
as the disabled image of your button like this:
<button dojoType="Button" disabledImg="/images/buttons/disabled" > Quit </button>
API Reference: dojo.widget.Button
See Also: DropDownButton, comboButton
A combination Button and DropDownButton. Use this for a button that has a common action (e.g. "Make Regular Dinner") and less common related actions (e.g. "Make Romantic Dinner" and "Make TV Dinner")
<button dojoType="comboButton" menuId='saveMenu'> <img src="images/editIcon.gif" width="32" height="32"> Save </button> <div dojoType="PopupMenu2" id="editMenu" toggle="wipe"> <div dojoType="MenuItem2" iconSrc="images/save.gif" caption="Save" accelKey="Ctrl+S" onclick="mySave();" /> <div dojoType="MenuItem2" iconSrc="images/saveAs.gif" caption="Save As...." accelKey="Ctrl+A" onclick="mySaveAs();" /> </div>
You can also specify your own background images, as in Button.
API Reference: dojo.widget.ComboButton
See Also: Button, DropDownButton, PopupMenu2, MenuItem2
Editor2 Widget in dojo provides a WYSIWYG editor for HTML content. The core is compact and lightweight, while a plugin framework ensures that any functionality can be achieved by plugins.
Basic html editing capacity is implemented in the core, which is the RichText widget. Currently keyboard shortcuts are also hardcoded in this widget (TODO: generalize this, or use KeyRouter instead?).
Editor2 is a subclass of RichText Widget which adds a toolbar (Editor2Toolbar Widget) to the top of the editing area.
In order to have an extensible structure, the new Editor2 introduced several new concepts.
The first and most fundamental one is call Command, which executes a specific function on the editing area. It also provides the API to retrieve the current state of the command. The base class for Command is dojo.widget.Editor2Command (defined in Editor2.js).
Each command should have a unique name and each command is a singleton object per page: no matter how many Editor2 instances there are in one page, they share the same command objects.
The toolbar (defined in Editor2Toolbar.js) for the editor2 contains serveral toolbar items. The basic class for toolbar item is dojo.widget.Editor2ToolbarButton (defined in Editor2Toolbar.js), which essentially is a simplified version of dojo widget.
Toolbar item can be of any type, besides buttons, you can have more complex items, such as a dojo combobox like item with a dropdown (see dojo.widget.Editor2ToolbarFormatBlockSelect in Editor2Toolbar.js).
| Name | Description | Features | ||
|---|---|---|---|---|
| ContextMenu | Command | ToolbarItem | ||
| ContextMenu | Context Menu Core, with menu items for builtin commands | Cut/Copy/Paste, Link/Unlink, Image properties | - | - |
| FindReplace | Implement find and replace functionalities | - | Find/Replace | Find/Replace |
| TableOperation | Support for table related operation | Insert/Delete Table | Insert/Delete Table | Insert Table |
| AlwaysShowToolbar | Ensure the toolbar is visible when scrolling the page | - | - | - |
| ToolbarDndSupport | Toolbar Set/Item drag and drop support | - | - | - |
| SimpleSignalCommands | Add simple signals to Editor2, such as save() and createLink() | - | - | - |
There are many widgets used for forms:
The main principle of these widgets is that:
All these widgets should have these attributes just like native HTML input elements. You can set them during widget construction, but after that they are read only:
And they also share some common methods:
(note: some widgets don't conform but we plan to convert them soon)
Author: Bill
<select dojoType="ComboBox"
autoComplete="true"
dataUrl="/suggest.php?match=%{searchString}"
maxListLength="15"
mode="remote"
name="myComboBox">
There are a few attributes of note here.When you enter text into the box,
Dojo tries to find matches for the text you've just entered. For
example, suppose you type "a". Because the widget's mode is set to "remote", it will fetch the dataUrl and substitute your input for the magic %{searchString} token. (That token is only valid when mode is set to "remote" or "html".) In this case, it will fetch /suggest.php?match=a
from the server. You don't have to use PHP on the server side, of
course; it's simply used as an example here. The point is that the
widget will replace the magic token in dataUrl with the user's input and fetch the resulting URL from the server.
What should the server return? In the "remote" mode, the widget expects a JSON array of entries, each entry of which contains a displayable option name and a value. For example, the server might return something like
[
[ "Alabama", "AL" ],
[ "Alaska", "AK" ],
[ "Arkansas", "AR" ]
]
Many server-side programming languages have existing libraries to output native objects in JSON form. In this case, for simplicity's sake, we'll do it by hand. Here's what an extremely simple, inefficient suggest.php might look like.
<?php
$states = array( "Alabama" => "AL",
"Alaska" => "AK",
...
);
$userInput = $_GET['match'];
$result = "[";
foreach ($states as $state => $abbreviation) {
if (strpos($state, $userInput) === 0) {
$result = $result . '[ "' . $state . '", "' .
$abbreviation . '"],';
}
}
$result = $result . ']';
print $result;
?>
FormBind allows you to quickly setup your “Web 1.0″ form for asynchronous submission. Basically it sets things up so that whenever the user hits the submit button, rather than submitting the form in the usual way, and refreshing the entire page, the contents are sent over xmlhttp (or any transport), and then the results are passed to the given callback.
How do you do it? Easy:
function magicForm() {
var x = new dojo.io.FormBind({
// reference your form
formNode: document.forms[1],
load: function(load, data, e) {
// what to do when the form finishes
// for example, populate a DIV:
dojo.byId('myDiv').innerHTML = data;
}
});
}
dojo.addOnLoad(magicForm);Note the unfortunate naming between dojo.io.bind() and dojo.io.FormBind.
dojo.io.bind() is a function that immediately sends the given info to the specified URL. (It would probably better be called something like dojo.io.send() but it isn't.)
dojo.io.FormBind(), on the other hand, doesn't send anything to the server. It just hooks up events so that when the user presses the submit button then the data is sent via dojo.io.bind(). Also note that you call "new" to make it work.
Note also that although dojo.io.bind() also takes a formNode argument, it's tricky to use and you are better off using FormBind. That's because for forms containing the Editor/Editor2 widgets, they need to serialize their data back to the [textarea] before the form is submitted, and that only happens when the form's onSumbit handler is called. Just calling dojo.io.bind() and specifying a formNode won't do that. However, with FormBind (and with an actual [input type="submit] button in in the form), everything works perfectly.
You can play with the demo to see it in action.There are three methods which you can use to validate your form data on the client side before it is sent to the server - with each having their own benefits and drawbacks. Often, the most effective validation is performed using a combination of these methods.
It is important to understand that whilst client side validation is effective, it should not be considered a replacement of server side validation techniques. In order to provide an enjoyable and secure user experience it is essential that a combination of both methods is used.
The second method of validation is known as dojo.validate.check. This function let's you setup a table of rules for checking a form's input elements.
TODO: more info on this
There are several widgets in the dojo.widget.validate module that will either correct user input (converting lowercase to uppercase, etc.), or print errors when the input doesn't match a certain pattern. A few of the widgets are:
The alternative to validating user input is to provide such an interface that the user can't enter a bogus value to begin with. For example, the DatePicker widget won't let the user input an invalid date.
Previously I talked about the widget object, that you can get access to like this:
var myButton = dojo.widget.byId("foo");
If you create a widget programatically you automatically get a pointer to the widget object:
var myButton = dojo.widget.CreateWidget("Button", {caption: "click me"});
What is myButton useful for? Calling methods on the button. For example:
myButton.setCaption("Don't press me!!");
Note that doing the following won't work, because the myButton object doesn't know that the caption variable has been changed:
myButton.caption="this won't do anything";
Also note that to disable/enable a widget, call disable()/enable(), rather than setting the disabled attribute directly. People often make that mistake.
There are some read-only variables, however, that are useful to access. Two of the most important ones are:
That reminds me. In the above example of programmatic creation, you also need a line like this:
form1.appendChild(myButton.domNode);
Consider the markup below:
<button dojoType="Button" onClick="alert('hello world')">
It looks familiar, but it's actually quite different than the normal onclick handler on the dom node.
onClick() is a method in the Button widget object. It's got a similar name to DOM node's onclick (but not identical; there's a capitalization difference). However, it's not the same. As another example, consider
<input type="Slider"
onValueChanged="alert('new value is ' + arguments[0]);">...
In this case, we are using a function of the widget called onValueChanged(newValue), that has no direct equivalent in the dom world.
in the case above, the specified code will be run in addition to the widget's original onValueChanged() method. It works the same way as dojo.event.connect(). On the other hand, if you just specify a function name like this:
<input type="Slider" onValueChanged="doit">...
Then you are overriding the widget's onValueChanged() funtion w/your own.
Usually, the widget will provide an empty function stub, so it won't matter if you connect to it or override it.
You can also do something like this, although it seems more difficult than the method above:
dojo.event.connect(myButton, onValueChanged, function(x){
alert("new val is " + x);
});
Widgets all can be hidden (made invisible) and shown:
For show and hide, there are 4 transitions available, that you set at widget creation time:
They are set like this:
<div dojoType="FloatingPane" toggle="fade" toggleDuration="250">
The explosion effect (often used for tooltips) also requires a point/square from which the element explodes out of, or implodes back into. This is set automatically when using the Toggler or TaskBar widgets.
There are two philosophies to laying out the screen. One way, the "web-way", says that everything should flow naturally from HTML, meaning basically that a bunch of stuff is in the document and if your window isn't big enough, then you use the browser's scrollbar. This is the way traditional web pages work, and is the best choice for many applications.
There other philosophy is to take the available size of the viewport (basically, the browser window), and then to partition it into smaller and smaller pieces. If you think about a mail application that splits the screen into top/left/right sections, then you are thinking about this kind of design.
Dojo provides a number of widgets for implementing the second design listed above. They fall into two basic categories.
Widgets that split the screen space between a set of widgets
Widgets that hold mulitple children but only display one at a time:
In addition, there's one widget that isn't a layout widget per se, but it is often used with the layout widgets:
These widgets can be nested to arbitrary levels, so that you could have a LayoutContainer with a top/bottom/client section, where the client section is a SplitContainer, and that SplitContainer could contain a TabContainer, which would itself contain a LayoutContainer, and so on.
The leaf nodes of this hierarchy could be any non-layout node, but often are ContentPane nodes.
Example (currently not displaying correctly. wiki needs upgrade?!):
<DIV> <DIV>hello world </DIV> <DIV> <DIV>left side of split </DIV> <DIV> <DIV>second tab </DIV> <DIV>i'm on the bottom </DIV> </DIV><DIV dojoType="LayoutContainer" > <DIV dojoType="ContentPane"> hello world </DIV> <DIV dojoType="SplitContainer"> <DIV dojoType="ContentPane"> left side of split </DIV> <DIV dojoType="TabContainer"> <div dojoType="LayoutContainer"> .. </DIV> <DIV dojoType="ContentPane"> second tab </DIV> </DIV> </DIV> <DIV dojoType="ContentPane"> i'm on the bottom </DIV> </div>
Note that all these objects are called containers because they just contain a set of other objects; they don't contain mixed content (text and nodes) like a normal <DIV>.
Also note that there is no "LayoutContainerChild" or "SplitPaneContainerChild" like node. That's to reduce the amount of markup and code required to setup a deep hierarchy of layout widgets.
Note that the example above is missing some important parameters. For one thing, it doesn't specify whether the SplitContainer arranges its children vertically or horizontally. For that we need:
<DIV dojoType="SplitContainer" orientation="horizontal">
We are also missing the labels for each of the tabs in the TabContainer, which we can add like this:
<DIV dojoType="TabContainer">
<DIV dojoType="LayoutContainer" label="Tab 1">
..
</DIV>
<DIV dojoType="ContentPane" label="Tab 2"> second tab </DIV>
</DIV></DIV>
Note that the labels are specified as parameters to the ContentPane and LayoutContainer, the children of the TabContainer, rather than as arguments to the TabContainer itself. "label" is not technically a property on those two objects, but you can still specify it,and the TabContainer will pick it up.
Similarly, for a LayoutContainer, you need to say where each child should be located:
<DIV dojoType="LayoutContainer"> <DIV dojoType="ContentPane" layoutAlign="top"> hello world </DIV> <DIV dojoType="SplitContainer" layoutAlign="client">...</DIV> <DIV dojoType="ContentPane" layoutAlign="bottom"> i'm on the bottom </DIV> </DIV>
You may freely mix sides (top, bottom, left, right) in a layout container. Sides are used from the outside in. The special side name "client" will fill in any part of the container that is not otherwise occupied. Very often you will use fixed-size side panes and a client pane that grows and shrinks as the user resizes the window, for example:
<DIV style="OVERFLOW: hidden; WIDTH: 100%; HEIGHT: 100%" dojoType="LayoutContainer">
<DIV dojoType="ContentPane" layoutAlign="top" height="2em">
Page header goes here; it stretches across the whole width of the window.
</DIV>
<DIV dojoType="ContentPane" layoutAlign="bottom" height="1em">
And a footer here, also stretching across the whole width.
</DIV>
<DIV style="WIDTH: 120px" dojoType="ContentPane" layoutAlign="left">
Some left-side navigation HTML, bounded by the header and footer
since they were already added to the layout.
</DIV>
<DIV style="WIDTH: 60px" dojoType="ContentPane" layoutAlign="right">
Some right-side navigation HTML
</DIV>
<DIV dojoType="ContentPane" layoutAlign="client">
Main page body here, bounded by all the fixed-size elements above.
</DIV>
</DIV>
For the top level layout container in a hierarchy, you need to specify a size. If you don't, the contents of the container may be displayed oddly or not at all.
<DIV style="WIDTH: 500px; HEIGHT: 500px" dojoType="LayoutContainer"></DIV>
Many web applications will want to fill the whole screen with their top level layout container. Think of a case like a mail application. For any size browser window, you want the top part to have some menu choices, and then have the bottom part be split between a tree on the left and message list/message on the right.
In this case, you need CSS like this:
html, body, #mainWindow {
width: 100%;
height: 100%;
overflow: hidden;
}
And then inside your tag you will have something like:
<DIV id=mainWindow dojoType="LayoutContainer"></DIV>
Creating a hierarchy of layout widgets programatically works the same way as normal programatic creation, except that sizing info needs to be specified in a special way.
// make a dummy div just to specify size
var div = document.createElement("div");
with(div.style){ height="500px"; width="500px"; }
// create the layout container
var lc = dojo.widget.createWidget("LayoutContainer", null, div);
// add some children for top, bottom, and center. Top and Bottom
// children also need to have a size specified, and possibly a scrollbar
var topDiv = document.createElement("div");
with(topDiv.style){ height="30px"; overflow="auto"; }
lc.addChild( dojo.widget.createWidget("ContentPane", { href: "foo/bar.html", layoutAlign: "top" }, topDiv) );
var bottomDiv = document.createElement("div");
with(bottomDiv.style){ height="30px"; overflow="auto"; }
lc.addChild( dojo.widget.createWidget("ContentPane", { href: "foo/bar.html", layoutAlign: "bottom" }, bottomDiv) );
One other thing to note in this example is that each ContentPane has two parameters. The href parameter applies to the ContentPane itself, but the layoutAlign parameter is really something that the LayoutContainer processes.
Different browsers have different capabilities when it comes to displaying (rendering) your widget. Dojo provides mechanisms that automatically detect which of these capabilities the browser offers and extends your widget using the most powerful rendering system the widget has code for.
We'll discuss how these mechanisms work, when you should use them, and how to extend widgets to support multiple renderers.
// renderer-agnostic portion
dojo.declare("my.widget.Foo"
{
initializer: function() {
// do initialization tasks, make instance properties
},
foo: 5,
doit: function() { ... },
...
}
);
// render-specific portion
dojo.widget.defineWidget("my.widget.html.Foo", [ dojo.widget.HtmlWidget, my.widget.Foo], {
initializer: function() {
// do initialization tasks, make instance properties
},
...prototypical properties (in object notation)...
}
);
dojo.widget.defineWidget("my.widget.svg.Foo", [ dojo.widget.SvgWidget, my.widget.Foo], {
initializer: function() {
// do initialization tasks, make instance properties
},
...prototypical properties (in object notation)...
}
);
// renderer-agnostic portion
// add features to my.widget.Foo, but don't explicitly extend or inherit
// my.widget.Foo properties will come in as part of my.widget.[html|svg].Foo
// do initialization tasks, make instance properties
dojo.declare("my.widget.FooPlus", my.widget.Foo, { ... });
dojo.widget.defineWidget("my.widget.html.FooPlus", [my.widget.html.Foo, my.widget.FooPlus], {
initializer: function() {
// do initialization tasks, make instance properties
},
...prototypical properties (in object notation)...
}
);
dojo.widget.defineWidget("my.widget.svg.FooPlus", [my.widget.svg.Foo, my.widget.FooPlus], {
initializer: function() {
// do initialization tasks, make instance properties
},
...prototypical properties (in object notation)...
}
);
Before you can write your own widget, you should understand how dojo's widgets are organized, both in terms of where files are and how the class hierarchy works.
The first thing to notice is the following renderer base classes.
Widget
|-- DomWidget
|-- HtmlWidget
|-- SvgWidget
Each widget implementation will extend either HtmlWidget?, SvgWidget?, or VmlWidget?, according to what browser it supports.
Technically, multiple implementations are defined using mixins, which are similar (but subtly different) than multiple-inheritance. In the above case, dojo.widget.svg.Chart extends HtmlWidget but mixes in dojo.widget.Chart base class.
Widgets can have (but are not required to have) multiple implementations, as follows:
Note that the so-called "html" version of the widget might actually run special code for IE, FF, etc., either through calls to utility functions (such as the graphics library) that branch based on browser version, or "if/else" statements, or whatever.
The HTML file just specifies the widget name, without specifying the
implementation. For example,
<div dojoType="Foo">
There are three separate modules, corresponding to the implementations above:
Examples:
1. The Button widget only has a single "html" implementation. It's defined in dojo.widget.html.ButtonHaving an object without any code to display it can be a strange idea for many people. "After all," they say, "I'll only be using my widget in an HTML environment." Such a kneejerk reaction is understandable, as many people view the decoupling of code as an effort not worth the time.
What you end up with is a method that does some logic, does some rendering, does some more logic, in a fairly long loop. Splitting these processes up is merely saying, "Let's get all of our business logic out of the way, and then we can draw the results." Even if you won't be splitting your widget into multiple files, as we'll be discussing shortly, this should still be done. The next time something isn't displaying correctly, you won't have to wade through business logic to find the problem. The next time business logic isn't working correctly, you won't have to search through display-specific code. And the most important part, you won't worry about modifying business logic or display-specific code breaking the other piece.
What you should end up with is a plain old JavaScript object that doesn't know about how it will be drawn, and doesn't care. It's the guts of your widget, and can be run in the console, in an SVG environment, in a standard HTML environment, or anywhere that Dojo currently supports or will support in the future.
When a widget is loaded, it has a lifecycle that runs calls several methods. These methods are pretty clearly separated into business logic and display-specific methods. For example, mixInProperties is business logic and setWidth is display-specific. There is no need for these methods to even interact with each other, and splitting these between multiple files make it easier to locate one from the other.
You should also end up with most reusable code. Instead of loading external data in the same method as display-specific code, you can move it to your main widget object and call it from the display-specific code. Then, when another method needs to use the same information, it's ready for you to use.
Need help updating this page. Describe the navigation widgets in Dojo including attributes that are common to all these widgets.
This documentation refers to 3rd major version of the tree widget, sometimes refered to as TreeV3.
Many mentioned classes (e.g TreeLoadingController) have V3 on the end, but that suffix is sometimes omitted, because it will be removed in dojo 0.5.
If there exist 2 same classes, but one with V3 at the end - it's the one you need.
Examples are given in tests (dojo/tests/widget/treeV3), so you might want to check them first and copy-paste exactly the things you need.
Please browse the Book,
then ask questions in dojo-interest list
Extensions are also called plugins, they can be hooked onto widgets in various combinations and provide wanted options.
Currently there is a couple of extensions
Tree extension, disables wrapping for tree nodes. Also it fixes IE bug when an 'unwrappable' node (e.g single word) will move to next line if no space left.
Tree extension, places icon to the left of a node, depening on nodeType property
Selector extension, highlights currently selected nodes
Selector extension, deselects a selected node when it is clicked. Usually, one should ctrl-click, or click another node.
Tree extension, turns labels into links, merges object property into tag
To make tree (or its elements) unselectable use dojo.html.disableSelection in nodeCreate and treeCreate hooks. Apply disableSelection to every node you want to make unselectable.
There is an "objectId" property and "object" property ready to be filled in from markup or program-way.
You may use dojo.lang.forEach(nodeOrTree.getDescendants(),function(elem) { ... }) to process all descendants, it will walk children property recursively.
The safer way would be to call TreeCommon.prototype.processDescendants(nodeOrTree, filter, func), it will process all children with func, but will not descend into nodes if filter(node) returns false. E.g see collapseAll controller method uses it to collapse all widgets, but skip non-folders and data objects.
Make a single root node with actionsDisabled="DETACH;MOVE". User will be unable to remove it, so interface will stay sane.
Also, you may want to set actionsDisabled="ADDCHILD" to tree itself, so now children can be added besides the root.There are 2 ways. The first one is to attach TreeSelector and hook on "select" event. So when a user clicks, event handler will change url to node.object.href. Of course, you should fill hrefs.
A probably more convinient path would be to employ TreeLinkExtension, which will turn your labelNodes into real links, and apply attrbutes from node object to them.
Dojo performs actions not only when a node is created, but also cleanup when a node is destroyed. Lazy features allow node creation be distributed in time, but when you navigate away from a large tree, large cleanup causes visible delay. I don't know a way to evade that.
TreeDocIconExtension handles that. You should declare nodeType for your nodes, so they'll get nodeIcon[Your type] CSS class. Default type is Document for leaves and Folder for folders.
There is also setNodeTypeClass method to update node CSS when its nodeType changes e.g programmatically.
This documentation refers to 3rd major version of the tree widget, sometimes refered to as TreeV3.
Many mentioned classes (e.g TreeLoadingController) have V3 on the end, but that suffix is sometimes omitted, because it will be removed in dojo 0.5.
If there exist 2 same classes, but one with V3 at the end - it's the one you need.
Examples are given in tests (dojo/tests/widget/treeV3), so you might want to check them first and copy-paste exactly the things you need.
Please browse the Book,
then ask questions in dojo-interest list
Note: most classes here omitt 'V3' suffix
Previous tree used a list of divs, each of them was indented with grid and spacers to right level. The new tree uses natural nested divs structure (children' divs inside parent's div). Grid is contigous and structure is displayed correctly for any node/font size
All image and size information was removed from JS code. There is a bunch of classes applied to nodes, that may denote node folder state, node type, show if there are children, etc. CSS moves this logical classes into style
Different trees be styled with different CSS class families
Want to put 2 differently styled trees on a page? Give them different classPrefix.
Rich content support was incomplete, because list-of-divs model could not handle arbitrary-sized nodes. Now you may have <br>, <p> and any other width/height
modifiers.
nodeDOMCreated event was removed. That's because listeners are bound to tree and may want to modify the new node, but that's only possible when the node is being bound to the tree, not when it was created and hanging around. afterTreeChange was introduced to help listeners to (un)bind nodes the right moment.
All events were renamed to better reflect the moment of their publishing.
afterExpand, afterCollapse events now fire when the animation (e.g fading in or out) finishes, not when the actual expand/collapse is called.
Before TreeV3, all nodes must be widgets. A node is added - hence graphical widget is created. For performance reasons that behavior was altered. Now when you add a node, you may actually add a "data object", containing node data, e.g {title:"new node"}. You may want to add a large nested branch of such data objects, like {title:"new", children:[...data objects..]}.
Data objects will become real members of children array (you may recursively search them, modify etc), but graphical widgets will be created only when visitor expands them.
The compatibility drawback of such behavior is that old code may erroneously call widget methods on data objects while recursively traversing a tree, e.g with Widget#getDescendants. You should change such code to use TreeCommon#processDescendants, or handle data objects in special way.
There are no special mechanisms to add laziliy instantiated "data objects". You may manipulate them simply modifying children array, but no events are thrown until a real widget appears on the scene. In most cases that is fine, but you are free to "disable" lazy widget creation - do not modify children directly and enable tree.eagerWidgetInstantiation
The Tree is actually a pack of loosely coupled components, connected through events. To keep things simple and also for compatibility reasons, such components(controller,selector...) were created implicitly, if not declared. But actually this proved to be a source of questions and misunderstandings. So now nothing is created implicitly, read how-to and declare things.
Old callbacks code was removed in favor to dojo.Deferred. Now all operations may be async and run your callbacks at the end.
Sounds simple enough.. Select multiple nodes with ctrl and get them with selector.selectedNodes. instead of removed selectorNode call.
Currently, multiple drag'n'drop does not work with multiple selection because of dojo bugs. Hopefully will be fixed.
If treeNode property is empty, tree will create a new node from the data returned by source.getTreeNode, then source.onDrop will be called to remove old node.
It became possible to edit nodes inline, using TreeEditor. Base variant uses RichText widget, you can make another wrapper though. Remote calls can be made on save only, or on start/cancel too e.g for locking purposes.
Author: IlyaThere are few code paths that lead to same purpose: to create a tree node. They differ in effeciency and use patterns
You specity a tree and its nodes in HTML, relying upon dojo to parse it and turn into widgets. That is a slowest way, but nice for small trees or if only tree top is specified and the rest is created later.
dojo widget parser walks DOM and creates a special structure. The next pass creates widgets from the structure.
The generic widget creation routine. It basically runs the operations in order:
Note that initialize is called in pre-order: parent is initialized before children, postInitialize is called in post-order: a child is postCreated before its parent.
If you create nodes with javascript, then you run create calls manyally. So parents are naturally created (and postCreated) before children.
There seem to be no good way to distinguish betwen markup creation and manual creation. From the one hand its seems good, because allows reuse of generic creation code. From the other hand code paths going through this code are subtly different.
The reliable thing is that initialize will process widget after its domNode is built, BUT it should not assume anything about children.
afterChangeTree event is fired on initialization also. If you want to know anything about children and do something at this point - check addChild, but not node creation.
children array may be
Tree was coded with performance in mind. Although, JavaScript itself is a slow language. Flexible model requires some code that slows it down. It's not DOM manipulations, but actually javascript that I couldn't make lighter. Being a part of dojo/widget structure implies some overhead, but also power.
Almost all operations require small constant time when single node is involved. Depending on your application you may notice slowdown when (most common) creating lots of nodes or performing other batch operations.
Creation from markup or with standard create/addChild routines is 2-3 times slower, because these routines are generic.
Fast node creation with dojo tree is 2-3 times slower than xtree 1.7, another tree widget, not so featured, but nicely optimized for performance.
The results described here refer to operations without any lazy features involved. Most of time you will use lazy creation or lazy loading, or both, and operate with thousands of "virtual" nodes with ease.
When talking about performance, one should understand, that there are single-node operations that operate on single node... These ones are fast. The examples are: create a node, delete a node, move a node along the tree.
... And there are batch operations that touch a lot of nodes. The examples are: initial tree creation, moving a node from one tree to another which has different listeners, etc.
That performance issues become noticeable at 100-300 tree nodes depending on your trees. All algorithms are linear in worst case, but JS is slow language, DOM is also not that fast.
There is a number of features one could use to get a speedup.
A node can be created with isFolder=true flag, but without children. Any node has a state, initially UNCHECKED for empty folder, and used by TreeLoadingController.
When a user presses expand, tree controller (supporting lazy loading) will send a request to server asking for nodes, and parse the answer creating children.
The benefit is obvious: you don't have to load/process whole tree at once. You can only load a single node and user will load the rest clicking "expand"
Node/tree keeps array of its children in children property. Lazy creation is somewhat a half-way approach to lazy loading. It allows you to put data objects into this array and tree will create widgets of them later, when they are expanded.
For instance, one can call node.children = [{title:'node1'},{title:'node2'}]. The objects will be set, but no widgets are created. You can also set children to nested array: node.children = [{title:'node1', children:[{title:'node2'}] }].
You can create tree on server, JSON-serialize it and put to HTML, that is gzip-compressed. Compression will be 6 times or more, so it is not that space hungry.
The benefit comes from postponing almost all real job: widget creation and attaching it to tree will happen in expansion-time.
Sometimes, lazy creation and loading may work together nicely, providing seamless increase in speed and decrease in memory footprint. For instance, server may pass a whole tree branch in JSON to lazy loading controller. Top nodes will be created right along, because user needs them, but the rest of the branch will be postponed relying on lazy creation feature.
There are operations, like "expandAll" where such lazy tricks don't help, because all graphical widgets must be processed. That is why widget creation process is well-optimized itself. createSimple is a hacky program-only way to create TreeNodes fast. setChildren is a method to assign (and create if needed) all children at once. It helps to evade some extra work happening when children are added one by one.
IE has a well-known bug. If an image was loaded dynamically - with a new Image(), or img.src= assignment, or even as a background of a new node, it will not be cached. So every time when you create a node, all needed icons get loaded from server (or requested at least). A possible solution is to put a special div into HTML (adjust src to your path):
<div style="display:none">
<!-- IE has a bug: it reloads all dynamically resolved images, no matter, is it
new Image() or CSS background. If you don't specify images like that,
it will reload them every time a new node is created -->
<img src="../../../src/widget/templates/images/TreeV3/i.gif"/>
<img src="../../../src/widget/templates/images/TreeV3/i_half.gif"/>
<img src="../../../src/widget/templates/images/TreeV3/expand_minus.gif"/>
<img src="../../../src/widget/templates/images/TreeV3/expand_plus.gif"/>
<img src="../../../src/widget/templates/images/TreeV3/expand_leaf.gif"/>
<img src="../../../src/widget/templates/images/TreeV3/i_long.gif"/>
<img src="../../../src/widget/templates/images/TreeV3/document.gif"/>
<img src="../../../src/widget/templates/images/TreeV3/open.gif"/>
<img src="../../../src/widget/templates/images/TreeV3/closed.gif"/>
</div>
Author: Ilia
To talk with server, one should use TreeLoadingControllerV3 or TreeRpcControllerV3. They inherit from TreeBasicControllerV3 and override its methods to deliver remote calls possibility.
TreeLoadingControllerV3 contains main methods for server calls, and allows dynamic node loading. TreeRpcControllerV3 adds server calls to tree manupulations like "createChild/move/edit...".
There are many classes of events, published with dojo.event.publish mechanism. Every event has a name and message object, containing more precise information about what happened. You may use events to update your data while tree changes, and to perform additional processing of involved objects.
There is a default naming scheme for an event class. E.g for a tree with widgetId='mytree', event of class afterTreeCreate will be named "mytree/afterTreeCreate". You may provide other names in eventNames property of the tree.
Event occurs after tree creation is complete. There is an alternative to hook on this action by putting your objects in "listeners" property of the tree. The difference is that listeners are guaranteed to hook before nodes get added, and afterTreeCreate is published after Tree widget is created.
references to tree
Published right before actual Tree#destroy method is called. Useful for cleanups
references to tree
Right before TreeNode#destroy is called. Node is detached after this event fired.
references to node
This event is tightly created with node creation process. It is fired when
references previous tree, null if node has been just created
new(current) tree
target node
Fires when a node obtains "folder" state. That may happen when a first child is added to a leaf, or if a node was initially created with isFolder=true
references to node
Fires when a node obtains looses "folder" state. That may happen when a last child leaves the node, and Tree.unsetFolderOnEmptyis set, or when unsetFolder is called explicitly.
references to node
These events share same arguments and fire when a node is moved. Move process is considered something special. When you move a node, no detach/addChild events get thrown. That allows to tell situations when a node leaves a tree for some time (detached then attached) from situations when a node is simply moved to another location
previous parent
previous tree
previous index among siblings
new parent
new tree
new index among siblings
target node
Published when a node is attached to parent. This may occur at the end of creation process, or when a node is lazily instantiated from data object.
Also it occurs when a detached node gets attached.
references to node
index among siblings
current parent who adopted a child
flag is set if child was laziliy instantiated. That is: it resided as data object in children array, but user expanded its parent, so node widget came to life.
Occurs when a node is detached. This may happen in the process of node destruction. Keep in mind, that detaching a node sets its parent to null, but
tree remains same.
references to node
references to old parent
references to index among children of old parent
Fire when a node is expanded/collapsed. Some togglers do nice animation hiding/showing node. This event fires when animation finishes.
target node
When a node is edited, or explicit setTitle method is called, this event helps to inform interested parts about changes.
target node
replaced node title
There are few major approaches to building dynamic trees.
1. list of idented divs
Each tree node is a div with indentation. Indentation is e.g 20px * node depth, so everything looks fine. Usually indentation is made of many quadrantic images, each of them represents empty space or grid lines, which visibly link nodes together.nested divs.
Of course, 'div' can be changed to any tag, e.g 'li'.
2. nested divs
Divs are nested same way tree nodes are nested. Can use ul/li instead of divs, there's only symantic difference, of course, if styles are same.
Each div can be idented relatively to its parent with padding/margin property, or with images.
If we use images here, then there will be lots of extra tags, so padding/margin seems better.
Let's consider a simple tree
* Node1
* Node 1.1
* Node 1.2
* Node 2
The trees we see in User Interfaces help sort out long, heirarchical lists. A file system is the classic example, with Windows using it in Explorer and Macintoshes with Finder (is it still called that???).
Nodes are the basis of a dojo tree. A node can include other nodes, and is then called a branch, container or folder. A node containing no other nodes is a leaf. Dojo does not force you to distinguish branches from leaves. It deduces the tree structure from your own code.
A dojo tree contains at least two dojo widgets:
But there are many dojo widgets to help you sculpt, mold, and connect behavior to your tree.
Here's a simple example.
<div dojoType="Tree" >
<div dojoType="TreeNode" title="Item 1">
<div dojoType="TreeNode" title="Item 1.1" ></div>
<div dojoType="TreeNode" title="Item 1.2" >
<div dojoType="TreeNode" title="Item 1.2.1" >
<div dojoType="TreeNode" title="Item 1.2.1.1" ></div>
</div>
<div dojoType="TreeNode" title="Item 1.2.2" ></div>
</div>
<div dojoType="TreeNode" title="Item 1.3" ></div>
</div>
<div dojoType="TreeNode" title="Item 2" ></div>
</div>
Which produces the following lovely tree:
SCREENSHOTYou can do open a node and show its contents by clicking the + icon, or hide them with the - icon, just like you're used to. Nice!
The problem is our tree does nothing but stand around looking beautiful. Nothing wrong with that. Normally, though, you'd want some kind of action to occur when the node is clicked. To do this, you can use the TreeSelector Widget.
TreeSelector is a widget without a UI. You use it as a placeholder for connecting the tree to various Javascript actions. This makes it easy to construct many trees, and connect them to the same actions.
<script>
dojo.addOnLoad(function() {
dojo.event.topic.subscribe("nodeSelected",
function(message) { alert(message.node.title+" selected"); }
);
});
</script>
<div dojoType="TreeSelector" widgetId="tSelector" eventNames="select:nodeSelected" ></div>
<div dojoType="Tree" selector="tSelector" >
<div dojoType="TreeNode" title="Item 1">
<div dojoType="TreeNode" title="Item 1.1" ></div>
<div dojoType="TreeNode" title="Item 2">
</div>
(Is there an easier way to do this??? -- CAR)
When you click on a node, an alert box will pop up with the name you selected.
You can make the selection event arbitrarily complex. But many times, you just want to pass the selected node along with a form. Simple!
<script>
dojo.addOnLoad(function() {
dojo.event.topic.subscribe("nodeSelected",
function(message) { document.menuForm.eatMe.value = message.node.title; }
);
});
</script>
What would you like to eat first?
<form name="myForm">
<input type="hidden" name="eatMe" value="" />
<div dojoType="TreeSelector" widgetId="tSelector" eventNames="select:nodeSelected" />
<div dojoType="Tree" selector="tSelector" >
<div dojoType="TreeNode" title="Dessert (Recommended)">
<div dojoType="TreeNode" title="Ice Cream" value="ICE76645" />
<div dojoType="TreeNode" title="Cake" value="CAK85467" />
</div>
<div dojoType="TreeNode" title="Entree">
<div dojoType="TreeNode" title="Meat Loaf" value="MTL18908" />
</div>
</div>
</form>
Clicking a node fills the value ICE76645, CAK85467, or MTL18908 into the hidden field "eatMe".
In this example, a tree is a standin for a select/options tag. For long lists, a select/option list gets too long to navigate. Humans like their information grouped and organized into smaller chunks. But databases thrive on flat namespaces, like the UPC system or Social Security Numbers. Trees give you the best of both worlds.
You can turn off gridlines at the root level and/or for the entire tree. By default, each 1st level TreeNode connects to a "phantom" root node, as in:
SCREENSHOT
You can remove the phantom Root node so the first level nodes appear with no gridlines to their left, as in:
<div dojoType="Tree" showRootGrid="false">
Or you can turn all the gridlines off, as in:
<div dojoType="Tree" showGrid="false" showRootGrid="false">
<div dojoType="TreeNode"...>
<div dojoType="TreeNode"...>
<div dojoType="TreeNode" title="<span style='background-color:yellow' >The most popular choice</span>" />
...
</div>
<div dojoType="TreeNode"...>
<div dojoType="TreeNode" title="Another Choice" />
</div>
</div>
But if this TreeNode is 3 levels down, the user will have to expand both levels above it. A better way is to pre-expand content levels. This requires the attribute "expandLevel", which means "expand all nodes that are n levels below" If n is more than 1, all levels between 1 and n are expanded, since seeing an expanded node requires seeing an expanded node above.
For example, if you added expandLevel="2" to the top TreeNode:
<div dojoType="TreeNode" expandLevel="2" ...> <div dojoType="TreeNode"...> ...then both The Most Popular Choice and Another Choice will appear. But:
<div dojoType="TreeNode" expandLevel="1" ...> <div dojoType="TreeNode" expandLevel="1" ...> ... </div> <div dojoType="TreeNode"...> </div>will only expand Most Popular Choice.
acme widgets are expected to be in acme folder next to dojo folder.<img dojoType="acme:Image" />
Loading acme.widget.Image module is the only requirement for using acme:Image in this configuration. You can load that module as part of a build, by calling dojo.require, or automatically./dojo /acme/ /acme/widget/Image.js <- defines acme.widget.Image
To customize the folder location of module acme call dojo.registerModulePath.<root>/acme/manifest.js
dojo.provide("acme.manifest");
dojo.require("dojo.string.extras");
dojo.registerNamespaceResolver("acme",
function(name){
return "acme.widget."+dojo.string.capitalize(name);
}
);The input string name will always be lower-case. So this resolver triggers loading of module acme.widget.Calendar for widget acme:calendar. dojo.registerNamespaceResolver("acme",
function(name){
return "acme.widget."+dojo.string.capitalize(name);
}
);We want to create custom modules, and decide to put them in:/dojo/dojo.js
/dojo/[whatever else is in the particular dojo install]
Note that the path to acme from dojo is:/acme
For the widget examples, let's say we made some custom widgets, including one called acme.widgets.Calendar, and put them in:../acme
/acme/widgets/variousWidgets.js
Main document
<script src="/dojo/dojo.js"></script>
<script>
dojo.require("dojo.widget.*");
</script>Include a manifest file: /acme/manifest.js
dojo.provide("acme.manifest");
dojo.registerNamespaceResolver(function(name) {
return "acme.widgets.variousWidgets";
});To support markup like so:
The acme namespace triggers require of acme.mainfest. The resolver is used to match calendar to a required module (i.e. acme.widgets.variousWidgets). Then acme.widgets module is searched for calendar implementation matching the current rendering environment.
Main document
<script src="/dojo/dojo.js"></script>
<script>
dojo.require("acme.widgets.variousWidgets");
</script>Supports markup like so:
acme.widgets module is searched for calendar implementation matching the current rendering environment.
Main document
<script src="/dojo/dojo.js"></script>
<script>
dojo.require("acme.lib");
</script>acme/lib.js file:
// ... additional code ...
With a build you can use any of these formats, but a manifest is not required.
Main document
<!-- dojo.js is a build -->
<script src="/dojo/dojo.js"></script>
<!-- dojo.require(s) can be here, although they are ignored -->Supports markup like so:
<div dojoType="acme:calendar"></div>
This section discusses the internals of widgets, and how to write your own.
TODO: moved this from "The Memo" page, where it definitely didn't belong, but it could use some expansion
This is a crucial next step for widget authors - creating widgets which themselves contain inner widgets, resulting in what we could call 'compound widgets'.
The procedure is simple, just add to your widget .js file, within the widget atributes object, the line:
widgetsInTemplate:true,
then, your subwidgets will nest perfectly within the main outer widget. You should also be able to nest to any arbitrary depth. Just remember though to abstain from setting the id or dojoId attributes in your html, rather set dojoAttachPoint instead to insert into your main outer widget a named attribute which references your subwidget. This way, you won't pollute the global element namespace. Otherwise, you'll hit problems if creating multiple instances of your compound widgets.
TODO:
- this is old info? current description is at http://dojo.jot.com/WikiHome/Modules%20%26%20Namespaces
- As indicated below, it is correct for the current stable version (0.3.1). It will need to be updated for the next release.
If you're planning on creating your own widgets then it's probably a good idea to keep your own code completely separate from the Dojo codebase. This will make life easier if/when you come to install a new version of Dojo, and also prevent any name clashes with native Dojo widgets.
First of all, you'll want to create a directory structure outside the Dojo source directory where your code will be stored. For example, let's call this new directory 'user', so that your directory structure looks something like this:
/dojo
/user
index.html
Next you need to tell Dojo that this new namespace exists and where it lives. You can do this with dojo.setModulePrefix( namespace, path ), like this:
dojo.setModulePrefix("user", "../user");
Note that the path (the second parameter) is relative to the root of the dojo source directory.
[Please note: the use of dojo.setModulePrefix() is deprecated (by Dojo version 0.5), and will be replaced with dojo.registerModulePath(), which takes the same initial parameters.]
Now since Dojo will look for widgets in a subdirectory (under '/user') called 'widget', we need to create that too:
/dojo
/user
/widg