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
/widget
index.html
Now you can create our own widgets in the user/widget directory and include them using dojo.require() as usual:
dojo.require("user.widget.MyWidget");
Unfortunately, in version 0.3.1 you can't use the namespace when calling your widget (this has been fixed in newer versions). So for now, just use the name of the widget:
<div dojoType="MyWidget" some_property="Some value"/>
...or programatically...
var new_widget = dojo.widget.createWidget
(
'MyWidget',
{some_property:'Some Value'
}
);
In future versions (and current nightly/SVN builds), you would prepend the name of the widget with the namespace and a colon, for example:
TODO: correct, expand...
This chapter discusses how widget templates work.
If you remember, in a previous chapter we looked at the template for the floating pane:
<div id="" dojoAttachEvent="onMouseDown" class="dojoFloatingPane">
<div dojoAttachPoint="titleBar" class="dojoFloatingPaneTitleBar" style="display:none">
<img dojoAttachPoint="titleBarIcon" class="dojoFloatingPaneTitleBarIcon">
<div dojoAttachPoint="closeAction" dojoAttachEvent="onClick:closeWindow"
class="dojoFloatingPaneCloseIcon"></div>
<div dojoAttachPoint="restoreAction" dojoAttachEvent="onClick:restoreWindow"
class="dojoFloatingPaneRestoreIcon"></div>
<div dojoAttachPoint="maximizeAction" dojoAttachEvent="onClick:maximizeWindow"
class="dojoFloatingPaneMaximizeIcon"></div>
<div dojoAttachPoint="minimizeAction" dojoAttachEvent="onClick:minimizeWindow"
class="dojoFloatingPaneMinimizeIcon"></div>
<div dojoAttachPoint="titleBarText" class="dojoFloatingPaneTitleText">${this.caption}</div>
</div>
<div id="_container" dojoAttachPoint="containerNode" class="dojoFloatingPaneClient"></div>
<div dojoAttachPoint="resizeBar" class="dojoFloatingPaneResizebar" style="display:none"></div>
</div>
Basically, the idea is the the source HTML is replaced by this template. But there's a lot more stuff happening.
Inside of FloatingPane.js you will notice various variables that correspond to (point to) dom nodes within the instantiated template. It's easier to explain by example.
Here are some lines from the template above (note the highlighted section):
<div dojoAttachPoint="titleBar" class="dojoFloatingPaneTitleBar" style="display:none">
<img dojoAttachPoint="titleBarIcon" class="dojoFloatingPaneTitleBarIcon">
And here's the corresponding code from FloatingPane.js:
titleBar: null,
titleBarIcon: null,
...
Merely by having that code, the titleBar variable points to the dom node generated by the template. So you can do something like:
this.titleBar.style.color="red";
There's a special attach point called the "container node". Consider this source HTML:
<div dojoType="FloatingPane">
Hello world!
<button dojoType="Button"> press me </button>
</div>
This is a floating pane that contains some content, including a widget. What happens to the content when the floating pane is instantiated? It goes into containerNode. Notice the line below from the template:
<div id="_container" dojoAttachPoint="containerNode" class="dojoFloatingPaneClient"></div>
In addition, since the floating pane contains contents, we have this line in FloatingPane.js:
isContainer: true,
Another very useful feature is declarative event handling. Notice this line from the template above:
<div dojoAttachPoint="maximizeAction" dojoAttachEvent="onClick:maximizeWindow" class="dojoFloatingPaneMaximizeIcon"/>
Just by adding that line, whenever the maximize action div is clicked, the widget's maximizeWindow() function will be called.
If you don't specify a function name, it defaults to the event name. For example,
<div id="" dojoAttachEvent="onMouseDown" class="dojoFloatingPane">
due to the above highlighted code, whenever you mouse down, onMouseDown() is called.
<div dojoAttachPoint="titleBarText" class="dojoFloatingPaneTitleText">
${this.caption}
</div>
When dojo creates the widget from the template, it substitutes the value of this.caption from the object into the template.
Now we'll see how you write the javascript portion of a widget.
Previously we looked at the CSS and the HTML used to define a widget. The third and final component to widgets is a javascript class to handle widget rendering details and events on the widget.
The first step to writing the javascript for a widget is to call defineWidget. Below I'm defining a widget called my.widget.html.Foo that extends dojo.widget.HtmlWidget,the base class for most widgets. (We'll talk about different base classes and what "html" means in a later document.)
dojo.widget.defineWidget("my.widget.Foo", dojo.widget.HtmlWidget, {
function() {
// do initialization tasks, make instance properties
},
{
...prototypical properties (in object notation)...
}
);Using dojo.widget.defineWidget, the tasks below are performed automatically:
Alternately, I might want to extend an existing widget. Here I'm making an enhanced version of Foo called FooPlus:
dojo.widget.defineWidget("my.widget.FooPlus", my.widget.Foo, {
function() {
// do initialization tasks, make instance properties
},
{
...prototypical properties (in object notation)...
}
);OK, that's the skeleton for the widget, but what do we put inside? The first thing to think about are the parameters that are used when you construct the widget. Every widget can take parameters. For example:
Where are the parameters defined, and how do you set their types? Actually, they are just properties in the javascript class. In this case:
toggle: "", // string
toggleDuration: 0, // integer
onClick: function(){} // functionJavascript doesn't have types, so how do we specify the types of the parameters? By specifying an example. In the above case, 0 means integer and "" means string.
Note: don't set them to null or it won't work!
Default values:
You can also specify default values in the javascript file. If the user doesn't specify a value for a parameter the default is used. For example:
toggle: "fade"Important Properties To Set
Next, you need to set certain properties that define how the widget operates.
isContainer
True/False. Must be set to true if the widget has child HTML or child widgets
snarfChildDomOutput
True/False. Set this to true if you are making something like a container node, where the input is just a list of widgets. It resolves issues where a child's generated DOM tree cannot be put back into the same place the source dom tree was. (Because [td] cannot be a child of [div], etc.)
templatePath
The path to the template HTML file, if one exists for the widget. This needs to be a dojo URI object, and is normally one of the two options shown below:
dojo.uri.dojoUri("src/widget/templates/HtmlFloatingPane.html"),
or
dojo.uri.moduleUri("mywidgetset","widgets/html/MyWidget.html"),templateCssPath
The path to the template CSS file, if one exists for the widget. This needs to be a dojo URI object, and is normally one of the two options shown below:
dojo.uri.dojoUri("src/widget/templates/HtmlFloatingPane.css"),
or
dojo.uri.moduleUri("mywidgetset","widgets/css/MyWidget.css"),templateString / templateCssString
If the CSS or HTML for a widget is very simple, you can specify it in the javascript rather than using templatePath/templateCssPath to refer to other files. This is what the dojo build process does automatically to embed templates/CSS in your widget code when you specify the intern-strings option.
templateString: "Simple Template",
templateCssString: ".simple { color:blue; font-size:12pt; }",Initialization Methods
Inside a widget file you will notice a number of functions for initialization. The most important ones are described below in the order they are called during the widget creation process.
postMIxInProperties()
this is called after the properties (see previous section) are initialized to the user specified values, but before the HTML template is instantiated.
Typical actions to perform here are validating and adjusting parameters provided to the widget.
fillInTemplate()
This is called after the template has been instantiated, so this.domNode points to the generated DOM tree. However, the children DOM nodes (for containerNode) and widgets haven't yet been copied over, and the widget's DOM node has not yet been placed in the actual HTML document.
Typical actions to perform here include:
- Enabling or disabling parts of the widget
- Applying styles/classes/etc
- Creating widgets that attach to nodes in the template
- Setting initial state
postCreate()
This is called after the children dom nodes and widgets have been instantiated. However, for programatically created widgets, none of the children exist yet, because they are added after createWidget() finishes, via the addChild() call.
Typical actions to perform here include:
- Connecting event handlers
- Manipulating parent or child nodes (with the above caveat)
Arrays, Objects, and Statics
Widget attributes that are Arrays or Objects need to be declared in the initializer() function, rather than like other variables (numbers or strings), so that they are not inadvertently shared between other instances of the same widget.
On the other hand - to make a static variable (i.e. a variable that is shared across all instances of the widget), just take advantage of the above issue:
// static1 and static2 are shared across every Foo widget
statics: { static1: 0, static2: "" }this.statics.static1++; // increments the single copyImportant Variables
this.domNode - points to root of generated treethis.containerNode - place where child HTML was attached
this.children - array of child widgets
The Memo
Overview
This page serves as an introduction to the art and science of creating one's own dojo widgets, and invoking them in html code just like mainstream Dojo widgets.
The following walk through will take you through all the steps needed to create a simple widget, ie a widget that contains only dom elements, not any nested dojo widgets.
Following this walk through are some instructions for creating compound widgets (ie, widgets that include other dojo widgets).
Getting Started
Let's say that you want to make a memo widget. Just a yellow sticky note to remind yourself of your dentist appointment, or whatever. Something that you can put on the screen and then erase later. Something so that a call like this:
<div dojoType="Memo" title="Reminder"> Pick up milk on the way home </div>will produce something that looks like this:
ReminderXPick up milk on the way home.The Template
The first step is to write HTML and CSS that prototypes how the widget will look. You can do this in any editor of your choice. I made the prototype above using this HTML:
<div class="memo"> <div class="title">Reminder</div> <div class="close">X</div> <div class="contents">Pick up milk on the way home.</div> </div>And this CSS:
.memo { background: yellow; font-family: cursive; width: 10em; } .title { font-weight: bold; text-decoration: underline; float: left; } .close { float: right; background: black; color: yellow; font-size: x-small; cursor: pointer; } .contents { clear: both; font-style: italic; }Note how I put as much of the formatting code into the CSS. This isn't necessary, but it does make it easier for other people to customize the widget, merely by altering the CSS.
Turning it into a widget
To make this memo into a widget, you need to declare a javascript object that connects with the HTML and CSS template above. So, put the HTML in a file called Memo.html, the CSS in a file called Memo.css, and make the Memo.js file below:
dojo.widget.defineWidget( // widget name and class "acme.widget.Memo", // superclass dojo.widget.HtmlWidget, // properties and methods { templatePath: dojo.uri.dojoUri("../acme/widget/Memo.html"), templateCssPath: dojo.uri.dojoUri("../acme/widget/Memo.css") } );Contents and Parameters
The obvious problem with this widget is that no matter what is inside the source div, it always says "Pick up milk on the way home". What you need is for the contents of the source div to be inserted into the generated output. This is what the "containerNode" is for, and you use it like this...
First, in the template, get rid of the static content, and instead mark that the div should hold the content from the source.
<div class="memo"> <div class="title">Reminder</div> <div class="close">X</div> <div class="contents" dojoAttachPoint="containerNode"></div> </div>Then, in the javascript object, denote that this widget is a container:
dojo.widget.defineWidget( // widget name and class "acme.widget.Memo", // superclass dojo.widget.HtmlWidget, // properties and methods { isContainer: true, templatePath: dojo.uri.dojoUri("../acme/widget/Memo.html"), templateCssPath: dojo.uri.dojoUri("../acme/widget/Memo.css") } );OK, what about the title? The title is specified as an attribute:
<div dojoType="Memo" title="Reminder"> Pick up milk on the way home </div>That means that it's a parameter to the widget. Parameters are specified as normal widget properties. In this case, the widget properties would look like this:
// properties and methods { // parameters title: "Note", // settings isContainer: true, templatePath: dojo.uri.dojoUri("../acme/widget/Memo.html"), templateCssPath: dojo.uri.dojoUri("../acme/widget/Memo.css") }This widget now has a "title" parameter, with a default value of "Note"
How do you stick this parameter's value into the widget? Luckily widget templates have variable substitution, so no coding is necessary. Just modify the template to use this parameter:
<div class="memo"> <div class="title">${this.title}</div> <div class="close">X</div> <div class="contents" dojoAttachPoint="containerNode"></div> </div>Events
OK, the content of the widget is showing up correctly, but how to you make clicking the X cause the widget to disappear? It's pretty simple, and hardly requires any javascript. The first step is to modify the template to handle click events on the X:
<div class="memo"> <div class="title">${this.title}</div> <div class="close" dojoAttachEvent="onClick">X</div> <div class="contents" dojoAttachPoint="containerNode"></div> </div>Then you simply add a method to the widget javascript object to handle the click:
onClick: function(evt){ this.destroy(); }That's it! Your first functioning widget!
Final code
Memo.html
<div class="memo"> <div class="title">${this.title}</div> <div class="close" dojoAttachEvent="onClick">X</div> <div class="contents" dojoAttachPoint="containerNode"></div> </div>Memo.css
.memo { background: yellow; font-family: cursive; width: 10em; } .title { font-weight: bold; text-decoration: underline; float: left; } .close { float: right; background: black; color: yellow; font-size: x-small; cursor: pointer; } .contents { clear: both; font-style: italic; }Memo.js
dojo.widget.defineWidget( // widget name and class "acme.widget.Memo", // superclass dojo.widget.HtmlWidget, // properties and methods { // parameters title: "Note", // settings isContainer: true, templatePath: dojo.uri.dojoUri("../acme/widget/Memo.html"), templateCssPath: dojo.uri.dojoUri("../acme/widget/Memo.css"), // callbacks onClick: function(evt){ this.destroy(); } } );