Dojo provides a lot of power and attempts to make it digestable in layers. For server-side developers, there's "widgets without coding", for HTML+CSS devs Dojo provides wonderful facilities for quickly building template-driven widgets, and for serious JavaScript and DHTML hackers Dojo is the standard library you will wish JavaScript always had.
This book serves as a guide to these layers, introducing concepts as you need them and working downward from high-level usage to getting your hands dirty in building your own widgets, custom namespaces, and unit tests.
In the Introduction, you'll get an overview of how Dojo can help you, what problems it solves, and where in the book you might be able to best find the information you're looking for. Also, remember that because this book is maintained by the community and is online, you can search it (and the rest of the Dojo site) at any time.
Lastly, thanks for checking out Dojo and the Dojo Book. It's your applications that have inspired us to build Dojo and the stories of how people are improving experiences with the toolkit that keep us going.
Dojo is a set of layered libraries. The bottom most layer is the packaging system that enables you to customize the distribution of Dojo for your application. On top of Dojo's package system reside language libraries such as the Dojo event system and the language utilities that greatly improve and simplify the lives of JavaScript developers. Environment-specific libraries are provided in some cases, but in most uses, you'll only need to know that all of Dojo works without incident across every major browser.
The bulk of the Dojo code lives in the application support libraries, which are too numerous to display completely in the diagram. dojo.gfx provides native vector graphics support across the major browser implementations of SVG and VML. dojo.lfx is a lightweight effects library, while dojo.io is "where the ajax lives."
Most of the "action" in Dojo is in the widget toolkit, that contains a template-driven system for constructing widgets, a declarative parser for putting widgets in pages without writing JavaScript, and a set of base components that implement common-case solutions to web interaction problems that HTML+CSS alone cannot solve.
Dojo is an Open Source DHTML toolkit written in JavaScript. It builds on several contributed code bases (nWidgets, f(m) and Burstlib), which is why we refer to it sometimes as a "unified" toolkit. Dojo aims to solve some long-standing historical problems with DHTML which prevented mass adoption of dynamic web application development.
Dojo allows you to easily build dynamic capabilities into web pages and any other environment that supports JavaScript sanely. You can use the components that Dojo provides to make your web sites more useable, responsive, and functional. With Dojo you can build degradeable user interfaces more easily, prototype interactive widgets quickly, and animate transitions. You can use the lower-level APIs and compatibility layers from Dojo to write portable JavaScript and simplify complex scripts. Dojo's event system, I/O APIs, and generic language enhancement form the basis of a powerful programming environment. You can use the Dojo build tools to write command-line unit-tests for your JavaScript code. The Dojo build process helps you optimize your JavaScript for deployment by grouping sets of files together and reuse those groups through "profiles."
Dojo does all of these things by layering capabilities onto a very small core that provides the package system and little else. When you write scripts with Dojo, you can include as little or as much of the available APIs as you need to suit your needs. Dojo provides:
Dojo is being built around a single markup language that provides application authors a (more) simple way of declaring and using responsive DHTML interface components. Renderings can be made available in several rendering contexts (such as SVG, or perhaps even the desktop or Flash), but the markup language (DojoML) and scripting language (JavaScript) will not change. Better yet, the DojoML parser accepts extended HTML and SVG as valid input, and can be used to easily create Degradeable Responsive Applications.
Dojo's homepage is: http://dojotoolkit.org.
Imagine for a second that you've just come from looking through the API of Dojo. You've memorized every function, every parameter, every example. All of the API has been studied and digested, but you're still not sure how to proceed. You know that there are many ways to do event handling, but you don't know the difference between them and you certainly don't know which one is best suited for your application.
I've made this assumption in writing the book; it allows me to focus on what's important about the book, and separate it from the API. With understanding Dojo, the why of Dojo, you're now free to learn the how by looking at the API. Or, if you already know the API, you're free to learn why it exists in the first place.
Though this book is a result of many people's work, I(we)'ll be speaking in the first person throughout this book. It not only allows a more personal interaction between the reader and the author, but quite often, describing an author's action is confusing when expressed in the plural form. "We're now opening our text editor" is an awkward sentence at best.
Saying "the reader" every sentence is dumb, and since you already know that you're the one reading it, I'll just refer to you as "you".
Decisions on the coding of Dojo aren't made by a single person. Almost every significant change to Dojo is done after discussion of how it is engineered. Because of this, I'll run into situations where I want to discuss how something was decided on. In this case, I'll be using the word "we". As in, "we decided that we wanted Dojo to be awesome."
On the way to a final topic, you'll be going through some higher-level topics. In order to understand the lower-level topics, and where they fit in to Dojo, you need to have some explanation of how you got way down there. To do this, each of the points along the tree, all the way down to the edge of the branch will have a few paragraphs of explanation. These are overviews for all topics contained underneath. It's worthwhile, if you have a specific topic that you want to read about, to go down the tree, reading each of the brief descriptions, before you read the article itself.
I want this to be more a book than a user manual. Therefore, I've tried to use fun analogies and brief stories. Many topics of this book are very difficult to process, and using existing knowledge to bring you up to speed is a great way to make these easy to digest. As a general guideline, I'll try to start an article with a paragraph, followed by a brief analogy or story, flowing into content. Because I don't want you to feel overwhelmed by code, examples will not be back to back. I think that if I have multiple examples, it's important to explain why I provided each one and the differences between them.
I think that the moral of the story of Goldilocks and the Three Bears is that things should neither be lacking, nor in excess, they should be "just right". That said, there's nothing worse than sitting down to get your learn on and be constantly switching from one topic to another, from one page to another, when you want to stay focused on what originally brought you to the article to begin with. Likewise, you don't want to get halfway through an article and realize that you missed both lunch and dinner. To prevent this, as a general guideline, each article in this book should take you about twenty minutes to read. That sounds just right to me.
I discuss in the book how Dojo is a toolkit, a framework, and everything in between. It's overwhelming just to learn what Dojo can do for you, much less learn what you can do to Dojo. Because of this, the book is split into three different parts.
Part 1 is all about using Dojo, the toolkit. That is, looking at a set of functions and objects that are available to you "out of the box" that don't require any tinkering. Ideally, you should be able to call a function, or initialize and object, without providing any custom logic to it.
Part 2 is meant to focus on more than just using things. We want you to know how to build your own widgets, packages, extend CSS and HTML and get into the nitty gritty of things, such as replacing the data provider for a widget.
Part 3 is a "look into the engine" explaining not only how thing work but why the Dojo developers designed them that way. Much of Dojo's code seems to work based on voodoo. For the advanced programmer, knowing this voodoo will allow you to write tighter, better code.
On any page where the dojo.book sidebar appears (including this page) click "create new page". The new page will have the currently viewed page selected as its parent by default
Because Dojo is a toolkit, it's potential uses are unlimited. The only real restrictions are that you're using it in an envornment that uses JavaScript. Though many of you will be reading this in the context of a browser, it's important to remember that JavaScript isn't limited to just that specific use.
With "Ajax" becoming such a popular buzzword, many are looking for an end-all solution to its complexities that not only work well in existing environments, but will allow much more complicated interaction in the future, allowing for very complicated transfer of data between client and server. With the aformentioned widget functionality, and an easy way to implement your own widgets, Dojo works very well alongside HTML.
Dojo also abstracts many of the differences between browsers that have pained developers for years. Things such as differing event objects and HTTP transport systems are a worry that will be forgotten.
Dojo provides a launching point for developing code. This means that you can quickly throw a project together for a client or boss without having to reinvent the wheel. We've built Dojo to be highly reusable, so code you have created for one project can be quickly moved to another without any refactoring.
Responsive applications make many calls to the back-end of web applications which have different call and response characteristics. Standardizing on a portable set of tools for this is beneficial to understanding and predicting overall system behavior.
Because Dojo's widget system sits on top of standard HTML, designers will be able to dive right in with a very shallow learning curve. Dojo allows for designers to add degradable functionality to their (already existing) page without having to implement any logic or strange programming language.
This book describes what Dojo provides and how to use the toolkit. You will find detailed information about the Dojo APIs in the API Reference Doc.
Three words are used to describe a stereotypical developer: geek, nerd, and dork. Many people assume that these words all mean the same thing when, in fact, they each have very specific meanings. Once you learn the meanings of each of these words, you'll quickly be able to run down the list of all of your friends listening to techno in dimly lit rooms and sort them into each category. Even better, you now have the power of more accurately describing a person.
Geeks, Nerds, and Dorks: A geek has a very focused knowledge of a subject (that guy that memorized the language of myst), a nerd is a master at many subjects (that girl you go to when you need homework help), and a dork is just plain socially inept (Napoleon Dynamite).
In software development, a framework is a defined support structure in which other project can be organized and developed. A framework typically consists of several smaller components; support programs, libraries, and a scripting language. There may also be other software involved to aid in development and meshing of the different components of a project. As you can see, dojo could be part of a framework, but it isn't a framework in itself.
A library is defined as a collection of related functions and subroutines used to develop software. They are distinguished from executables in that they are not independant programs; rather, they are "helper" code that provide access to common functions in one easy to manage location. After reading this you are probably saying, "Hey! dojo is a collection of libraries!", and you would be correct; however, dojo is much more than just a collection of libraries.
Now on to toolkits. A toolkit is generally used in reference to graphical user interface (GUI) toolkits. Basically, a library that is mainly focused on creating a GUI. Yes, dojo could also fall under this category, in fact our name implies it. Why do we call dojo a toolkit? Certainly not because it focuses mainly on GUI development, right? Well quite simply, because dojo is so much more than just a collection of libraries.
The previous paragraphs have probably left you still wondering what exactly we consider dojo. Obviously it is not a framework, but is it a toolkit or a library? Let's solve this once and for all. Typically, a library is a predetermined file that you include into your application, and that is how you gain access to those functions. However, with dojo, we have wrapped a package system around our libraries. This brings a slight twist to the idea of a library.
With this system we have broken each library up into several pieces. You have the core functions, and then several sub libraries where related, but less often used, functions are stored. This helps keep dojo's footprint based entirely on your needs as a developer. More about that will be covered later in the book but, for now, know that because of this flexibility, dojo is more than just a library, which falls into the realm of a toolkit with a few added functionalities. So as the name implies, dojo is a toolkit... and yet is more.
Want to know what Dojo is and how it can help you with your javascript development? In this section we will walk through the process of putting Dojo to use with its collection of pre-packaged "Out of the Box" widgets that can get you using Dojo right away. We will focus on developing programs using existing dojo components. This section will not get into any advance features, as they are included in future chapters. If you are new to Dojo or even new to JavaScript, then this chapter is for you. If, however, you are a seasoned programmer, you can still find reading this section purposeful as it will provide a helpful overview to the Dojo toolkit.
The sample application is the familiar Hello World app however, it does introduce the main features of Dojo and will build the foundation for developing much more sophisticated web applications.
Thanks to Lance Duivenbode and Seth Fair for help in writing this chapter.
This section talks about some of the tools available to help develop JavaScript programs. With the increasing popularity ofJavaScript there surely will be alot of changes in this space in a relatively short time. In this section you will find helpful tools that may or may not be part of Dojo.
Also remember to check the Dojo FAQs especially the Common Pitfalls section.
IDEs are available which support a wide range of web development activity, including editing Javascript and HTML files, deploying code to servers, and integration with existing features like source control. In addition, some include runtime tools and browser integration to assist in debugging (for example, myeclipse and ATF)
If you use one of the JavaScriptEditors that checks your syntax as you enter your code, you can be warned immediately of simple errors and save yourself a lot of time. Some Eclipse plugins even specifically support Dojo idioms by giving you tooltips for common Dojo functions and doing more complex analysis for errors (not just normal syntax checking).
Debugging on Firefox may need you to use the djConfig flag debugAtAllCosts (see further below for information). The debugAtAllCosts flag is sometimes necessary to locate exceptions or syntax errors. Even without the debugger, Mozilla and Firefox are unable to locate a line of code loaded by dojo.require() due to a flaw in the way eval() debugging hooks are implemented in Spidermonkey. Currently, the Javascript console will report all source references as the location of the eval() call itself (in the bootstrap code), but with an additional line offset equal to the offset in the corresponding *.js file.
If you are having wierd problems with Firefox, it is often worthwhile running Firefox in safe mode.
This is because installed extensions can interfere with the DOM tree, CSS, or even with javascript. In Windows there is a shortcut to start Firefox in safe mode from within the Mozilla Firefox folder, from the Start button.
If you are using a debugger with IE, go to Tools | Options | Advanced and make sure that Disable Script Debugging is not ticked. Using debugAtAllCosts can also help significantly - read about it below.
The Safari Developer FAQ has some general information about developing with Safari, as well as instructions on how to turn on a debug menu that allows showing a Javascript console.
There is a "dom inspector" type tool, but it requires using a nightly build of Safari.
debugAtAllCosts may have unobvious side-effects - you should only use it if you are actually debugging. If you hit problems with files not loading or __package__.js then check that you are using writeIncludes() correctly, and try removing this flag to ensure that the flag is not the issue.
You generally should not use a packaged build if you want to
debug, because in a packaged build the majority of code will end up in your dojo.js file
(and usually be obfuscated due to compression).
To use it needs one more line in your page. Here's how you might use it:
<script>
djConfig = {
isDebug: true,
debugAtAllCosts: true
};
</script>
<script src="/path/to/dojo/dojo.js" />
<script>
// dojo includes here
dojo.require("dojo.myModule");
dojo.hostenv.writeIncludes(); // this is a new line
</script>
// ...
Once you've structured your code this way, if you open up your debugger, you will see all the constituent files listed and you can then set breakpoints. You may need to add in dojo.require() statements for files that you want the files to show, above the writeIncludes(). If you do not, then any javascript files that need loading after the call to writeIncludes().
You
can see an example of using debugAtAllCosts with your own widgets
within your own namespace in
dojo/src/tests/widget/test_Custom_Widget_Debugging.html
When debugAtAllCosts is set to true, then dojo.require() does not actually include the package at that point. It only gets included in the page once you call dojo.hostenv.writeIncludes().
Normally when you dojo.require() a file that is not already loaded, it is fetched using using an xmlhttprequest, and loaded into your Javascript runtime environment using an eval() statement (each of the eval items in the RHS screenshot above is actually a js file). When the file is eval()'ed, dojo.require() calls will occur, and those then recursively load any further dependencies (those which are not already loaded).
When you set debugAtAllCosts to true, the file is still fetched using xmlhttprequest but it is not eval()'ed. Instead a regexp is used to find the dojo.require() dependencies, and required files may be fetched and searched recursively for dependencies. Each of the required javascript filenames is added into a list of pending files that need to be loaded (the list is in order so that dependent files are loaded in the correct order). When you then call dojo.writeIncludes(), inside the head of the document script tags are appended with src attributes set to the js files that need to be loaded. The script tags cause the files to be loaded by the browser: files loaded this way are understood by debuggers much better than when the files are eval()'ed.
If you just need to debug one or two known files, then you can just include those specific files using script tags after dojo.js. e.g.
<script src="/dojo.js" type="text/javascript"></script>
<script src="/mine/myWidget.js" type="text/javascript"></script>
You cannot do this for files that are packaged into your
dojo.js (Of course, using packaged files makes debugging hard in other
ways too!).
Javascript is slow and you want to speed it up?
Use a ProfilingJavascript tool to help you find the functions where all the time is spent, and optimise those routines.
A great many problems can be resolved by watching the traffic between the browser and the server. If you are having any problems with Ajax calls such as bind() or with js/html/css/jpg files not loading as you would expect, this is often the best way to diagnose them quickly. Also if you are having problems with required files not loading then this is a good starting point to find out why.
Sometimes Dojo's error messages about syntax errors are really cryptic due to how dojo loads files via dojo.require(), however you can get a better message by simply directly including the js file with the [script] tag.
The debugAtAllCosts flag can also be used.
There's also a lint program that Brian has recommended.
You can also find the debug error message in the dojo source code and remove the corresponding try/catch statement so that when the error occurs you get a debugger breakpoint instead of a dojo debug message.
Javascript has a debugger keyword that forces a breakpoint to occur. Just insert debugger; and if you have a debugger for your browser, then it will stop at the debugger keyword.
This is especially useful when using Venkman, because otherwise it can be difficult to get Venkman into debugging mode (e.g. you try to click on a line of source and it puts in a [F] future breakpoint).
It also works with the Firebug extension for Firefox.
You can debug the old fashioned way (print statements in the code). There are three functions for this:
Debugging output:
Alternately, in djConfig, you can specify which element they are appended to by
providing an id of that element: i.e.:
var djConfig = {
isDebug: true,
debugContainerId : "dojoDebug"
};
and have a div
Ajax Toolkit Framework is an incubator project within the Eclipse Web Tools Project. It is also a pluggable framework for other AJAX tools. It provides Javascript syntax checking, server deployment, Mozilla embedding, runtime tools such as XHR Monitor, and a debugger based on Mozilla Spidermonkey, JSD, and the Eclipse debug UI. Eclipse integration provides access to existing plugins like ant, subclipse for SVN, server development tools, etc. AJAX "Personalities" provide the potential for tighter integration with Dojo and other toolkits, although so far very little has been done in this area.
There's visual Javascript
validation built into ATF.
It includes both basic syntax validation and optionally Jslint
validation for less obvious potential syntax problems. It works just
like MS Word as-you-type spellchecking and Eclipse as-you-type Java
validation - as you type if you make a syntax error (or just do
something somewhat sloppy) you'll get a red or yellow squiggly under
the warning/error and an explanation in the margin. A background task
will run validation against all files and place markers in the code
such that you can see errors across a project.
You
can download and use ATF and its prerequisite
Eclipse components (Eclipse SDK, WTP, EMF, GEF, JEM, xulrunner) and
choose to use all of ATF or just install the "javascript" feature which
implements this validation.
Note
- you have to fetch and manually install jslint.js, Dojo and xulrunner due to Eclipse policies - make sure to read the ATF
readme to get the full functionality of ATF.
JSEclipse
is a commercial Eclipse plugin which provides a rich Javascript editor
with support for syntax validation, code completion and Dojo idioms.
To be written...
JS-Sourceror performs syntax checking and variable type and flow analysis on JavaScript files.
See http://skrul.com/blog/projects/javascript
It also does syntax checking, as well as scope checking and structure browsing.
Aptana is an open-source, JavaScript-focused IDE, including code assist for Dojo. It works as a stand-alone app on Mac, Linux, or Windows, or as a plug-in for Eclipse.
Profiling is the term for looking at where time is spent by any Javascript code. If you have a problem with code taking too long, then it helps to use a profiling tool to diagnose exactly where all the time is being used.
When using some profiling tools you may need to use debugAtAllCosts and not a packaged version of Dojo (see DebuggingJavascript). Using debugAtAllCosts will enable the profiling tool to allocate time spent per function to the correct source file -- otherwise you will end up with the elapsed times being allocated to anonymous functions which will make it difficult for you to understand!
dojo.profile is a package that implements timing primitives for recording how much time is spent in particular functions. To learn how to use it, the best resource is to search the tests directory and look at how it is used by various tests.
Displays the time it took to load each file.
Free but a bit buggy to use. I have found it easiest to get into debugging mode with Venkman by using a debugger keyword in your sourcecode. Run the Venkman debugger, then run your code and it will stop at the breakpoint.
Venkman contains a profiling tool, although the reports are a bit difficult to use. It does work.
If you are new to Dojo or want a quick overview of the toolkit then take a look at the HelloWorld Tutorial. This tutorial describes step by step how to build a simple Dojo application. You will learn some basic concepts about widgets, events and how to connect to the server code. Each step builds on the previous until you have a working application. It takes about an hour to go through the tutorial.
Dojo offers many editions of its code base. At first, it might seem daunting to try to figure out exactly which one you need. To quickly dispel any worries, let me assure you that every single edition of dojo provides a fully functioning system. Whether you download one of our editions, or the full, uncompressed source code, you'll be able to perform any of the examples discussed in this book.
TODO: fold in information from the READMEIn order to use dojo in your HTML pages, you need three sections of code, in this order:
1. Flags
<script type="text/javascript">
djConfig = { isDebug: false };
</script>
The flags control various options of dojo; often developers will set isDebug to true in order to get debugging output on their page. (There are also some other flags related to debugging; see the Debugging section of the code for details.)
2. Include the dojo bootstrap
<script type="text/javascript" src="/path/to/dojo/dojo.js"></script>
This includes the bootstrap section of dojo, and if you are using a release build, then dojo.js will also include code for some of the dojo modules.
3. Define what resources you are using
<script type="text/javascript">
dojo.require("dojo.event.*");
dojo.require("dojo.io.*");
dojo.require("dojo.widget.*");
</script>
This section is much like java's "import" statement. You specify every resources that you are using in your code. However, note that widgets are a special case and don't need to be declared explicitly, assuming that (as is the case with the built-in dojo widgets), a manifest file defines which widget is in which resource file.
Even though Dojo is made up of many different packages, it's frequently used in very specific ways. Because of this, we've created special editions of Dojo aimed toward these users. A visit to dojo's download page will show you which editions are currently available, such as the Ajax and Widget edition.
An edition is very simple, really. The important file is dojo.js, which is created by merging the most frequently used packages and compressing the resulting code. This means that when you have a script tag that calls dojo.js, you're getting not just the basic Dojo codebase, but the additional functionality that is most pertinent to your specific use.
You might wonder why so much additional code comes in each edition. This is the full code base, and allows you to use functionality that is outside of your specific build. It means that even if you have a very specifically tailored edition of Dojo, you aren't limited to only using that featureset. If your site uses the event and I/O systems heavily, but one of your pages uses a widget, then you don't have to worry that your widget will break. This also means that any of the examples in this book will work no matter what edition you've downloaded.
Events in JavaScript or Dojo based applications are essential to making applications work. Connecting an event handler (function) to an element or an object is one of the most common things you will do when developing applications using Dojo. Dojo provides a simple API for connecting events via the dojo.event.connect() function. One important thing to note here is that events can be mapped to any property or object or element. Using this API you can wire your user interfaces together or allow for your objects to communicate. The dojo.event.connnect() API does not require that the objects be Dojo based. In other words, you can use this API with your existing interfaces.
Here we connect the event handler, helloPressed, to the onClick property of the hello button element. When the button is clicked the funtion helloPressed will be called. function helloPressed(){
alert('You pressed the button');
}
function init(){
var helloButton = dojo.widget.byId('helloButton');
dojo.event.connect(helloButton, 'onClick', 'helloPressed')
}It is also possible to use the Dojo event model to connect simple objects. To demonstrate, lets define a simple object with a couple of methods:
var exampleObj = {
counter: 0,
foo: function(){
alert("foo");
this.counter++;
},
bar: function(){
alert("bar");
this.counter++;
}
};
So lets say that I want exampleObj.bar() to get called whenever exampleObj.foo() is called. We can set this up the same way that we do with DOM events:
dojo.event.connect(exampleObj, "foo", exampleObj, "bar");
Now calling foo() will also call bar(), thereby incrementing the counter twice and alerting "foo" and then "bar". Any caller that was counting on getting the return value from foo() won't be disappointed. The source method should behave just as it always has. On the other hand, since there's no explicit caller for bar(), it's return value will be lost since there's no obvious place to put it.
In either case, each time dojo.event.connect is called with the same arguments it will result in multiple connections. Later we will discuss strategies on how to guard against this.Notice that dojo.event.connect takes a different number of arguments in the examples above. dojo.event.connect determines the types of positional arguments based on usage.
The Dojo event system allows you to connect to DOM elements or nodes or plain JavaScript objects. The API is sophisticated enough that it allows you to connect multiple listeners to a single object so you can have multiple actions as a result of a single event such as a mouse click. Of course there is an API to disconnect the listeners too. The Connecting the Pieces chapter describes the Dojo Event system in more detail.At Dojo, we're committed to making DHTML applications usable, both for authors and for users, and with a lot of help from our friends, particularly Aaron Boodman and Mark Anderson, we have come up with solutions to the usability problems outlined above. We're providing it in a single, easy to use API and a package that requires only two files to function. The dojo.io package provides portable code for XMLHTTP and other, more complicated, transport mechanisms. Additionally, the "transports" that plug into it each provide their own logic to make each of them easier to use.
Most of the magic of the dojo.io package is exposed through the bind() method. dojo.io.bind() is a generic asynchronous request API that wraps multiple transport layers (queues of iframes, XMLHTTP, mod_pubsub, LivePage, etc.). Dojo attempts to pick the best available transport for the request at hand, and in the provided package file, only XMLHTTP will ever be chosen since no other transports are rolled in. The API accepts a single anonymous object with known attributes of that object acting as function arguments. To make a request that returns raw text from a URL, you would call bind() like this:
dojo.io.bind({
url: "http://foo.bar.com/sampleData.txt",
load: function(type, data, evt){ /*do something w/ the data */ },
mimetype: "text/plain"
});
That's all there is to it. You provide the location of the data you want to get and a callback function that you'd like to have called when you actually DO get the data. But what about if something goes wrong with the request? Just register an error handler too:
dojo.io.bind({
url: "http://foo.bar.com/sampleData.txt",
load: function(type, data, evt){ /*do something w/ the data */ },
error: function(type, error){ /*do something w/ the error*/ },
mimetype: "text/plain"
});Regular web requests and Ajax requests with dojo.io.bind are much alike. Both use URL's and both use the HTTP protocol. But with browser requests, it is always clear to user when something goes wrong. You may get a 404 - Page Not ound, or a Server Unavailable, or at least something that says "Error". Ajax requests happen in the background, so when they error out the user won't know. Even worse, if the response never comes the browser may appear to lock up.
That's why it's extremely important to provide an error handler and a timeout handler with dojo.io.bind. You should consider these as critical as URL or the load function
At the very least, you should alert the user that something went wrong. Here's an example:
var kw = {
url: "/cgi-bin/timeout.cgi",
load: function(type, data, evt){
document.myForm.myBox.value = data;
dojo.byId("boxLoadTime").innerHTML = new Date();
},
error: function(type, data, evt){
alert("Holy Bomb Box, Batman! An error occurred: " + data);
},
timeoutSeconds: 2,
timeout: function(type, data, evt){
alert("I am tired of waiting.");
}
};The error() function takes the same arguments that load() does. But unlike load(), the only useful parameter is data, which contains the error message.
The timeoutSeconds and timeout parameters should be used together. timeoutSeconds defaults to 0, which means "wait forever". (In other Ajax libraries, this is called a synchronous request). 0 is not desirable. Even if you expect the request will take a long time, you should set a high value here (e.g. 3600 = 1 hour), not 0. The timeout function takes the same arguments as error() and load(), but they are rarely consulted.So let's apply this to a trivial example. Suppose you have a text file on the web server with url my_message.txt.
Hello Ajax World!
You would like to load the file contents in an INPUT box without refreshing the page. Here's how:
<html>
<head>
<title>Insert title here</title>
<script type="text/javascript" src="/js/dojo/dojo.js"></script>
<script type="text/javascript">
function loadRemotely(e) {
var kw = {
url: "my_message.txt",
load: function(type, data, evt) {
document.myForm.myBox.value = data;
dojo.byId("boxLoadTime").innerHTML = new Date();
},
method: "GET"
};
dojo.io.bind(kw);
} function initAjax() {
dojo.event.connect(dojo.byId("loadIt"), "onclick", "loadRemotely");
}
dojo.addOnLoad(initAjax);
</script>
</head>
<body>
Form loaded at:
<script type="text/javascript">document.write(new Date());</script>
<form name="myForm">
<input type="button" id="loadIt" value="Click here to load value.">
<input type="text" name="myBox" size="50" />
Text loaded at: <span id="boxLoadTime">N/A</span>
</form>
</body>
</html>Click the button and the value in my_message.txt automatically loads into the box. The date and time stamps on the page prove it does not reload when the user clicks the button.
This demonstrates the bare minimum you need for dojo.io to connect with Ajax. At the very least, you need an object of type dojo.io.Request. In our examples, this is the "kw" variable. dojo.io.Request needs the following:
There are many more optional parameters, and we'll see these in later examples.
Then just call dojo.io.bind with the dojo.io.Request variable. Here, bind means "connect this page with that URL and let things happen."
Why do all that for some static text? The answer is ... you won't. The URL is going to be a server-side program which returns content - mostly XML (the "X" in AJAX), but it could be text, HTML, or even binary data.
The Url of dojo.io.Request may contain parameters, like so:
url: 'myprogram.php?firstname=Chicken&lastname=Little&key=111111'
There are two problems: (1) it's difficult to URL encode everything, (2) it doesn't allow for dynamic parameters. The above works fine for everyone named Chicken Little, but ...
It's easier and more flexible to send an entire form of data. And you can do that with the formNode parameter of dojo.io.Request.
<script>
var kw = {
url: "myprogram.php",
load: function(type, data, evt){
document.myForm.myBox.value = data;
},
error: function(type, data, evt){
alert("Holy Bomb Box, Batman! An error occurred: " + data);
},
timeoutSeconds: 2,
timeout: function(type, data, evt){
alert("I am tired of waiting.");
}
formNode: dojo.byId("myForm");
};
dojo.io.bind(kw);
</script>
<form id="myForm">
<input type="hidden" name="key" value="111111" />
<input type="text" name="firstname" length="50" />
<input type="text" name="lastname" length="50" />
<input type="text" name="myBox" length="50" />
</form>
Dojo's code is split into logical units called modules. These are much like packages in Java, except that in Dojo a module can contain both constructors (like classes in Java) and simple functions.
For example, the "dojo.html" module contains a number of functions, such as dojo.html.getContentBox(). The "dojo.dnd" module contains a number of constructors for things like HtmlDragObject etc.
Note the naming convention - functions start with a lowercase letter, and constructors (which are technically functions but act more like classes) start with a capital letter.
Modules could be called "namespaces", except for the fact that "namespaces" has a different (but related) meaning w.r.t. widgets.
In the simple case, a Dojo module is defined in a single JavaScript file. But sometimes, a single module is split into multiple files.
For example, the dojo.html module, although originally defined in a single file, was getting too big, so we split into multiple files. This is for performance reasons, so that the browser only downloads the code it needs.
Unfortunately this implementation detail is not transparent to the Dojo user. You have to know which file contains the functions you
need, and then include that file explicitly.
Each of these files is called a resource.
dojo.require("dojo.html.extras")will include the file src/html/extras.js, which in turn defines some of the functions (but not all the functions) in the dojo.html module.
A single JavaScript file never defines multiple modules, although often a single file will define multiple constructors. In Java this would be equivalent to defining two classes in the same file.
All of this complication is for performance reasons, trying to balance
How do you know what resources to dojo.require()?
1. modules
First, find out what modules you are using. In this example we'll assume you are using dojo.lfx.html.
2. resources
By reviewin the API doc you can see that dojo.lfx.html is defined in two files:
Depending on what functions you are using, you will either do
dojo.require("dojo.lfx.html");or
dojo.require("dojo.lfx.html");
dojo.require("dojo.lfx.extras"); dojo.provide("dojo.html.extras")For historical reasons, the dojo.provide() call serves two functions:When browsing through web sites and online applications, there are hundreds of widgets that come across your screen. Each button in your web browser is a widget. Each text entry box is a widget. We all know what a limited set of widgets that standard HTML provides: an input box, a button, a hyperlink.
Dojo widgets take an item like a text input box and adds functionality of a more user friendly object, like a graphical calendar to choose a date from. And it does this without breaking the original item on which the new functionality is built on.
The first thing that you'll notice about widgets is that the are somewhat similar to a macro expansion, such as C's #define. Your source HTML is a simple
<button dojoType="Button" id="foo"> Click me </button>
and yet a pretty blue button shows up, and when you look at the generated DOM, it's a complicated tree of DOM nodes with a lot of absolute positioning and background images.
See? Click me
But, that's not all there is. For each widget, besides the visible manifestation, there's also a pure javascript object that manages that generated DOM tree.
In the above case, the generated javascript object is called, unsuprisingly, "foo". You can get it by doing:
var myButton = dojo.widget.byId("foo");A Dojo widget wraps around your HTML. It looks at how the HTML is organized, what type of tags have been specified, what attributes they have, which tags are children of what other tags. All of these different variables allow a versatility in how skeletons are laid out, in what tags they use, in how the widget chooses to interpret them.
You can think of a widget as the final form that covers a skeleton. The top widget layer reflects the structure and functionality of the skeleton it sits on rather than covering it up.The simplest example of a skeleton is a single tag. A form input box, for example, is designed to simply accept a value.
<inputvalue="default">
But what happens when we want to help the user choose from an existing list of items? Enter the ComboBox widget.
<input dojoType="ComboBox" value="default">
As you can see, this is a functional skeleton. Not only is there an input box if the user does not have JavaScript enabled, but we can use the widget as part of a normal form. After all, we've only added on to it, we haven't changed the original purpose of the input element.
But this widget provides no data for the combo box to use.
<input dojoType="ComboBox" value="default" dataUrl="comboBoxData.js">
If you want to produce valid W3C HTML, you will have to use an alternative method to building your skeletons. The dojoType attribute is not recognized by W3C and its validation tool will complain about it being their. Below are two examples that do not use invalid attributes to build on your skeletons.
<input class="dojo-ComboBox" value="default" dataUrl="comboBoxData.js">
<dojo:ComboBox value="default" dataUrl="comboBoxData.js">
When there are certain attributes (dataUrl in this example), this widget will use that information to process the information to be contained in the combo box.
Sometimes you'll have skeletons defined in code and not even know it. In fact, many of the widgets provided by dojo assume that the underlying HTML is the same that would be encountered in every day life. Making this select element into a widget is as simple as adding the dojoType attribute.
<select dojoType ="combobox">
<option value="foo">foo</option>
<option value="bar">bar</option>
<option value="baz">baz</option>
<option value="thud">thud</option>
</select>
As you can see, you've just gained a whole lot of something for nothing. And, the data that will be used in the combo box is provided in a way that any web designer would understand.
TODO: this section needs to differentiate between ComboBox and Select.
You may ask yourself, "Why would I use wigets?" I honestly couldn't have answered this a few months ago, before finding Dojo anyway. The answer is really quite simple once you see how widgets improve the functionality and appearance of your web applications, without taking a long time to implement.
Widgets "enhance the user experience". In layman's terms, that means that you can design web pages that are easier for people use, more quickly understandable, less error-prone, and flashier than web pages in plain html.
Easier to use - the Select widget for example, will narrow down the list of available choices based on keystrokes the user enters. That makes it faster to use than a normal HTML select box.
More quickly understandable - a web page with tabs will let the user easily navigate between different sections, and helps to make clear all the different sections of code that are on one page.
Less error prone - validation widgets will immediately notify users when they have entered an incorrect value, and/or automatically correct the value.
Flashier - dojo's menu code will fade in / out menus, or use some other effect, rather than a plain appear/disappear that you get with pure CSS menus
Widgets make it easy for web developers to add enhanced functionality. Here's why:
Web designers are generally very good with HTML. The really good ones are usually so involved in design that they don't even bother with learning the extra stuff that comes along with its dynamic aspect.
For these kinds of users, specifying widgets via HTML is a great solution. Not only is markup useful for being able to design with a placeholder element laying in wait, but many of the widgets actually analyze what they've laid out and use them as if properties were passed to the JavaScript object.
A great example of this is the tree widget. All that the designer has to do is lay out an HTML list, assign some attributes and they can have things going without having to touch a bit of code.
Widgets solve a bunch of issues (like cross-browser support) behind the scenes, so you don't have to worry it. See the next section for more detail about that.
Using Dojo to add dynamic capbilities to your web applications can be a little daunting at first.
Let's look at the programming model in more detail to better understand how to use Dojo to build some really cool apps. The programming model is object-oriented inspired and includes "classes" with methods and multi-level
inheritance coupled with aspect-oriented event model famous in JavaScript. You will find the API doc quite useful to determine the methods and properties that are inherited from the parent "classes".
Thanks to Eugene Lazutkin and Bill Keese for help on this chapter.
Dojo supports two programming models, declarative and programmatic. Which one you use depends on what you are doing. In most cases, the declarative model may be easier to use as it is markup but there are times when you will use the programmatic model. You can, of course, intermix the models on the same page as needed.
The best way to show the models is through the use of widgets which can be used either declaratively or programmatically. Although both models are available we will mainly use the declarative model through out this book when both are an option.
The tutorial used the declarative model to create a button on the page using the code below.
<BUTTON widgetId="helloButton" dojoType="Button">Hello World!</BUTTON>
The following declarative formats are equivalent.
<?xml:namespace prefix = dojo /><dojo:widget></dojo:widget> <DIV dojoType="widget"> <DIV class=dojo-widget></DIV></DIV>
We will discuss the declarative model in more detail shortly.
You can also declare widgets programmatically using the dojo.widget.create API as follows. When declaring widgets programmatically, the API returns the widget id which you use to call the appropriate methods.
var myTabPane= dojo.widget.createWidget("TabPane", {id: "myTabPane"}, srcDiv);
Which approach to use is discussed later in the book but for now we want to introduce the idea that you have different options.
Before we get into the declarative model let's look at the widget infrastructure to better understand what methods are available when using widgets in your pages.
In addtion to methods, each class has parameters that are useful. There are two types of parameters listed here.
dojoType instructs Dojo how to process the element when the page is loading. Keep in mind that Dojo manipulates the DOM as it renders the page so you must use the Dojo APIs to access the widget ids. Dojo keeps a reference of all widgets it has created that can be accessed with the dojo.widget.byId function - providing you specify either the widgetId or id attribute in your markup. Also you can use dojoAttachEvent using this method.
In order to make namespaces practical and easy to use, Dojo has a concept of modules and resources. This concept was introduced in Part 2 Modules, Resources and Widget Namespaces when we first saw the HelloWorld tutorial. Resources are used to define a namespace and can be dynamically loaded on demand.
In order to load a resource you should request it using dojo.require(). It takes a string of text, which denotes a downloadable component. The content of this string is interpreted and a subject of Dojo conventions. First, it is traced to a single JavaScript file on disk (on web server). There are ways to affect this interpretation but out of the box it has a very sane behavior:
dojo.xxx => dojo/src/xxx.js
dojo.xxx.yyy => dojo/src/xxx/yyy.js
dojo.xxx.yyy.zzz => dojo/src/xxx/yyy/zzz.js
and so on. For example if you see a statement like that:
dojo.require("dojo.json");
It will load a file named dojo/src/json.js. If you don't know what it contains, you can go and look it up. To sum it up: a namespace hierarchy essentially reflects a file system hierarchy. By convention, if you see somebody using dojo.foo.bar.baz(), you can find it's definition in dojo/src/foo/bar.js, or, if it is not there, in one of files of dojo/src/foo/ (more on that case later), or in dojo/src/foo.js.
Dojo allows you to define your own resources and modules. For your custom resource you define your own top-level object. I will use "example" in my examples. If the Dojo loader sees that the first component is not "dojo", it applies following rule:
example.xxx => dojo/../example/xxx.js
example.xxx.yyy => dojo/../example/xxx/yyy.js
example.xxx.yyy.zzz => dojo/../example/xxx/yyy/zzz.js
Be sure to put the dojo.provide() call in your module resource, so that Dojo knows it found the right file. For instance, xxx/yyy/zzz.js should have dojo.provide("xxx.yyy.zzz") in it.
Essentially it means that on your web server next to the "dojo" folder there is an "example" folder, which files are interpreted in the same way.
There is more information about the namespace and how it is used in the Widgets chapter.
JavaScript is at its heart an object oriented language, but it is a prototype based object oriented language which does not have the same structure as class based languages like Java.This concept can be hard for new programmers who are not familiar with its construct. Dojo brings the object orientedness into a more familiar domain by modeling concepts that can be followed from Java and letting the toolkit handle the prototyping, inheritance and odd procedures JavaScript requires to make it work. Because of this, it not only allows people to get programming in object oriented JavaScript quicker, but it makes it faster to program because you can let the toolkit handle all of the odd procedures JavaScript requires to make it work. It all begins with a simple dojo.declare() function.
Classes in Dojo are declared with a declare statement and assigning it a Class Name.Within the body can be variables, methods and constructors (know in Dojo as an initializer).
dojo.declare("ClassName",null, {//class body
});
(Note: ClassName is the basic name, but to avoid naming conflicts, use package names like my.class.ClassName. For simplicity sake, we will start out with using just the simple name.)
Let's add some more content to our class by giving it a name and showing what the initilizer can do.Following is a persons class with an initializer and a moveToNewCity() function:
dojo.declare("Person", null, {
//acts like a java constructor
initializer: function(name, age, currentResidence){
this.name=name;
this.age=age;
this.currentResidence=currentResidence;
},
moveToNewCity: function(newState)
{
this.currentResidence=newState;
}
});
To create an object of this class you use the new keyword:
//create an instance of a new person
var matt= new Person('Matt', 25, 'New Mexico');
The initializer function is called once the object is created and the arguments are passed to it initializing the object.Our Matt object who is 25 currently lives in New Mexico, but let's say he moves a little further west to California.We can set his new currentResidence with the Person class method moveToNewCity(): matt.moveToNewCity('California');
Now the current value of matt.currentResidence shows that he now lives in California.
A person can only do so much, so let's create an Employee class that extends the Person class.The second argument in the dojo.declare() function is for extending subclasses.
dojo.declare("Employee",Person, {
//acts like a constructor
initializer:function(name, age, currentResidence, position)
{
Employee.superclass.initializer(name, age, currentResidence);
this.password="";
this.position=position;
},
login: function()
{
if(this.password!="" && this.password!=null){
alert('you have successfully loged in with the password '+this.password);
}
else
{
alert('please ask the administrator for your password');
}
}});
The first line in the initializer calls Employee.superclass.initializer, the Person class constructor. Dojo handles all of the requirements for setting up the inheritance chain.Methods or variables can be overridden by setting the name to the same as it is in the parent class. The Employee class can override the Person class moveToNewCity(), perhaps by letting the company pay for moving expenses.
You initialize the sub class the same as the Person class with the new keyword.
var kathryn=new Employee(' Kathryn ', 26, 'Minnesota', 'Designer');
The Employee class passes the first three arguments down to the Person class, and sets the position.Kathryn has access to the login() function found in the Employee class, and also the moveToNewCity() function by calling kathryn.moveToNewCity(‘Texas’); Matt on the other hand, does not have access to the Employee login() function.
matt.login() // ERROR can’t log in because he is not an Employee
If your class contains arrays or other complex objects, they should be declared in the initializer, due to some subtleties of object inheritance in javascript. Note that simple types (strings, numbers) are fine to declare in the class directly.
dojo.declare("my.classes.bar", my.classes.foo, {
// coupledObjects: [1, 2, 3, 4] - doesn't do what I want;
// ends up being like a static!!
numItem : 5, // one per bar
strItem : "string", // one per bar
initializer: function() {
this.coupledObjects = [ ]; // each bar should have it's own array
this.expensiveResource = new expensiveResource(); // one per bar
}
});
On the other hand, if you want an object or array to be static (shared between all instances of my.classes.bar), then you should do something like this:
dojo.declare("my.classes.bar", my.classes.foo, {
initializer: function() {
dojo.debug("this is bar object # " + this.statics.counter++);
},
statics: { counter: 0, somethingElse: "hello" }
});
The example below inherits from my.classes.foo and then mixes in "my.mixin". This is similar to multiple inheritance but there are some subtle differences, namely that this.inherited can only reference my.classes.foo, not my.mixin.
dojo.declare("my.classes.bar", [my.classes.foo, my.mixin], { initializer: function() { my.mixin.call(this /*, args*/); // invoke some mixin constructor // (note: my.mixin.prototype is ignored) }, valueForPrototype: 3, methodForPrototype: function() { } });
However, there is an issue with respect to constructor code in any simple JavaScript inheritence system. The constructor function of an object must be executed to create a prototype object for an inheritor. Therefore the constructor function must serve as both prototype-initializer and instance-initializer. The double duty of inherited constructors can be non-obvious and lead to subtle bugs.
Given the constructor above, when a bar object is created to use as a prototype, the mixin properties and properties created in the constructor become members of the inherited object's prototype. Almost always these properties are not intended to be part of a prototype.
As a practical matter, the extra prototypical properties are usually ignored as matching instance properties are created at object-instantiation time. However, for example, having an extra expensiveResource can be costly. And errors can result if the environment is not ready to create an expensiveResource at inherits-time. Errors caused by these conditions can be hard to track down, especially if the developer is not aware of how constructors are used when inheriting prototypes.
Separating instance-initializer tasks from prototype-initializer tasks eliminates these concerns. Therefore dojo.declare creates a standard, controlled constructor and separates instance-initialization tasks into a separate, optional initializer method.
Note: dojo.declare cannot inherit from an object that has a non-trivial constructor because dojo.declare does not allow constructors to also perform instance initialization.However, you can inherit from a dojo.declare created constructor without restriction
Sometimes one wants to invoke a method on an object from an ancestor prototype. JavaScript allows any function to call any other function in any context via the call and apply built-ins. So there are techniques like:
my.classes.bar.prototype.someMethod == function() {
// invoke any function in our context
anyFunction.call(this);
// invoke inherited version of this method in our context
my.classes.foo.someMethod.apply(this, arguments);
}
(Note: in these examples, the == indicates an assertion that the named property is equivalent to the function shown. Actual assignment is done via dojo.lang.extend or dojo.declare.)
As a convenience, dojo.inherits puts a reference to the ancestor prototype into the descendent constructor, and a reference to the descendent constructor into the descendent prototype. These extra references allow a great deal of extra flexibility in general, and also allow calling ancestor methods without explicitly naming the ancestor:
// invoke inherited version of this method in our context this.constructor.superclass.someMethod.apply(this, arguments);
However, the above technique will cause an infinte loop if someMethod is once removed. E.g., if we have foo -> bar -> zot, you can run into an issue like this:
The error results because this.constructor.superclass referenced in bar's identify function refers to zot's superclass (causing bar.identify to call itself).my.classes.foo.prototype.identify == function() { return "I'm a foo"; }
my.classes.bar.prototype.identify == function() { return "I'm a bar and " + this.constructor.superclass.identify.apply(this, arguments); }
my.classes.zot.prototype.identify == function() { return "I'm a zot and " + this.constructor.superclass.identify.apply(this, arguments); } bar = new my.classes.bar(); alert(bar.identify()); // "I'm a bar and I'm a foo" zot = new my.classes.zot(); alert(zot.identify()); // stack overflow
To resolve these issues, objects created from dojo.declare constructors include a function called inherited that safely invokes an ancestor method.
inherited: function(methodName /*string*/, arguments /* arrayLike */)
inherited correctly handles the problem scenario above:
my.classes.foo.prototype.identify == function() { return "I'm a foo"; } my.classes.bar.prototype.identify == function() { return "I'm a bar and " + this.inherited('identify', arguments); } my.classes.zot.prototype.identify == function() { return "I'm a zot and " + this.inherited('identify', arguments); } bar = new my.classes.bar(); alert(bar.identify()); // "I'm a bar and I'm a foo" zot = new my.classes.zot(); alert(zot.identify()); // "I'm a zot and I'm a bar and I'm a foo"
The syntax is:
ordojo.declare(className /*string */, superClass /*function*/ [, initializer /* function*/]);
dojo.declare(className /*string */, [superClass /*function*/, mixin /* function */, ...] [, initializer /* function*/]);
Including the target className in the argument list allows the object path to be created automatically (i.e. intermediate namespaces are created as needed). Also, dojo.declare stores className in an eponymous property in the created object's prototype (e.g. my.classes.foo.prototype.className == "my.classes.foo").
Technically speaking, JavaScript does not have classes: object construction is based on prototypes. For this reason reference to the term class has been (mostly) avoided above.
It seems that the difference is not of great practical importance. It's true that constructor functions in JavaScript are actual objects, but they operate like classes in the sense that they generally have no other purpose than as a mold for object instantiation. It is noted that classes are typically compile-time (or at least meta-) constructs and JavaScript constructors exist as Objects at runtime and contain actual data.
The name dojo.declare was chosen after much debate. The name is vague but easy to remember, read, and type. The method name inherited is lifted (at least) from ObjectPascal.ÂÂ
In general, a script should do the following in the ... section:
<!-- Step 1 (Optional) Set djConfig -->
<SCRIPT type=text/javascript>
djConfig = {
debug: true
};
</SCRIPT>
<!-- Step 2: Load dojo -->
<SCRIPT src="js/dojo/dojo.js" type=text/javascript></SCRIPT>
<!-- Step 3: call dojo.require -->
<SCRIPT>
dojo.require("dojo.book.myWidget.*");
<!-- Step 4 (Optional): define initialization functions -->
function initMyStuff() {
...
}
dojo.addOnLoad(initMyStuff);
</SCRIPT>
The order is important! If you do the steps out of order, dojo may not initialize properly, and your page will be a mess.
This script element is responsible for loading the base Dojo script that provides access to all the other Dojo functionality. Following this we add the requires statements which pulls in functionality needed by the application.
Use the dojo.addOnLoad to call functions which use the widget ids because Dojo must completely load the page and finish parsing the HTML before a reference can be made to the id. So, for example, the following will not work:
<BUTTON widgetId="helloButton" dojoType="Button">Hello World!</BUTTON>
<SCRIPT>
// ILLEGAL!! helloButton does not exist yet
dojo.byId("helloButton").width2height = 0.5;
</SCRIPT>
Instead, place the script in an initialization function:
<SCRIPT src="js/dojo/dojo.js" type=text/javascript></SCRIPT>
<SCRIPT>
function initMyStuff() {
dojo.byId("helloButton").width2height = 0.5;
}
dojo.addOnLoad(initMyStuff);
</SCRIPT>
<BUTTON widgetId="helloButton" dojoType="Button">Hello World!</BUTTON>
Dojo defines a global object called "dojo" which serves as an umbrella for everything Dojo-related. It simulates a namespace and was created to prevent clashes in the global JavaScript? namespace between the code in Dojo and other toolkits or user supplied code. Unfortunately it cannot be used during a bootstrap process, so special global variables should be used. All of them are prefixed with "dj".
You will need to use exactly two top-level Dojo-defined objects: "dojo", which serves as a namespace, and "djConfig", which is used to supply initialization parameters to Dojo, and should be created before Dojo's bootstrap.
For example, to turn off global widget searching, add these lines just *before* you include dojo.js:<script type="text/javascript">
djConfig = {
parseWidgets: false
};
</script>
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
/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(); } } );
Unlike the DOM events that web programmers normally associate with the word "event", Dojo takes a broad view of events. The tools in dojo.event.* allow developers to treat any function call (DOM event or otherwise) as an event that can be listened to. Using Dojo, code can register to "hear" about any action through a uniform API.
Events are essential for Dojo based applications as they drive the user interface, result in AJAX requests, and allow widgets to interact with each other. In a sense events are the glue that ties an application together. Cross browser event handling code can difficult to write from scratch as there are many ways in JavaScript of handling events and each browser has its own quirks and issues.
Dojo abstracts the JavaScript event system in the dojo.event module and provides a few options for handling events which include simple event handlers, event listeners using before, after, and around advice, and topics. The Dojo event APIs are not mutually exclusive; in many cases you will use a combination of the APIs depending on your use cases.In this chapter we'll show you:
Here's how we'd ensure that "bar" gets alerted before "foo" when exampleObj.foo() is called:
dojo.event.connect("before", exampleObj, "foo", exampleObj, "bar");
As you can see, we just perpended our previous call to connect() with the word "before". In the other cases, the word "after" was the implied first argument, which we could have added if we wanted, but typing more isn't something any of us want, and most of the time "after" is what you want anyway.
The same connection using kwConnect() looks like:
dojo.event.kwConnect({
type: "before",
srcObj: exampleObj,
srcFunc: "foo",
targetObj: exampleObj,
targetFunc: "bar"
});
Before and after advice give us tools to handle a huge range of problems, but what about when the listener and the source functions don't have the same call signatures? Or what about when you want to change the behavior of a function from someone else's code but don't want to change their code? If we take the view that any function call in our environment is an event, then shouldn't we also have an "event object" for each of them? When using dojo.event.connect(), this is exactly what happens under the covers, and we can get access to it via "around advice". Long story short, around advice allows you to wrap any function and manipulate both it's inputs and outputs. This'll let us change both the calling signatures of functions and change arguments for listeners (among other things).
Unlike the other advice types, around advice requires a little bit more cooperation from the author of the around advice function, but since you'll probably only be using it in situations where you know that you want to explicitly change a behavior, this is isn't really a problem. This example take a function foo() which takes 2 arguments and provides a default value for the second argument if one isn't passed:
function foo(arg1, arg2){
// ...
}
function aroundFoo(invocation){
if(invocation.args.length < 2){
// note that it's a real array, not a pseudo-arr
invocation.args.push("default for arg2");
}
var result = invocation.proceed();
// we could change the result here
return result;
}
dojo.event.connect("around", "foo", "aroundFoo");
The aroundFoo() function must take only a single argument. This argument is the method-invocation object. This object has some useful properties (like args) and one method, proceed(). proceed() calls the wrapped function with the arguments packed in the args array and returns the result. At this point, you can further manipulate the result before returning it. If you don't return the result of proceed(), it will appear to the caller as though the wrapped function didn't return a value. At any point you could call another function to do things like log timing information.
Once this connection is made, every time foo() is called aroundFoo() will check it's argument and insert a default value for arg2. Around advice is kind of like goto in C and C++: if you don't know better you can make huge messes, but when you really need it, you really need it.
Despite the power of around advice, it's not very often that globally changing a function signature or return value is the best plan. More often, you'll just want to smooth over the differences in calling signatures between two functions that are being connected. As you might have come to expect by now, Dojo provides a solution for this type of impedance matching problem too.
The solution is before-around and after-around advice. These advice types apply a supplied around advice function to the listener in a connection. They only apply the around advice when the listener function is being called from the connected-to source. Put another way, it's connection-specific argument and return value manipulation.
To access before-around and after-around advice, just pass in another object/name pair to a normal "before" or "after" connection, like this:
var obj1 = {
twoArgFunc: function(arg1, arg2){
// function expects two arguments
}
};
var obj2 = {
oneArgFunc: function(arg1){
// this function expects a two-element array
// as its only parameter
}
};
// we'd probably connect the functions somewhere else. Perhaps in a
// different file entirely.
function aroundFunc(invocation){
var tmpArgs = [
invocation.args[0],
invocation.args[1]
];
invocation.args = tmpArgs;
return invocation.proceed();
}
// after-around advice
dojo.event.connect( obj1, "twoArgFunc",
obj2, "oneArgFunc",
"aroundFunc");
Each function now gets what it expects, and the code calling obj1.twoArgFunc() never need be the wiser that any of this is happening.
Multiple Listeners
Connect also transparently handles multiple listeners. They are called in the order they are registered. This would kick off two separate actions from a single onclick event:
var handlerNode = document.getElementById("handler");
dojo.event.connect(handlerNode, "onclick", object, "handler");
dojo.event.connect(handlerNode, "onclick", object, "handler2");
We didn't have to change the API we were using, rewire anything for multiple events, etc. It all just works. Now every time you click the node, and object.handler() gets called and then object.handler2() gets called.
Finally, note that connect can take an array of objects as input:
dojo.event.connect(
dojo.byId("id"),“onclick",
listenerObj, “handleOnClick");
Connecting is one thing, but what about when you want to stop listening? dojo.event.disconnect() will stop the listening arrangement between functions, but must be pass exactly the same arguments as were passed to connect in order to ensure successful disconnection.
If there's anything that can trip up new users of dojo.event.connect(), it's
inadvertently connecting multiple times. Very often, a piece of code will get
called multiple times, and it will contain a dojo.event.connect() call. The
developer is then surprised when their listener function is called multiple
times for every time the source function fires. What to do?
One option is to move your connect() call to a location that will get invoked only once, but sometimes that's just not feasible. An optional argument to connect() ensures that the same arguments to connect passed multiple times will result in only one connection between functions. Unfortunately, it's the 8th parameter. Ugh. The last thing we want to do is remember 8 different parameters. The best answer in this scenario is to use the keyword-argument version of connect, aptly named kwConnect(). To use it, we have to give the parameters we've been using so far names. Here's our object connection example using kwConnect() and the once
parameter:
dojo.event.kwConnect({
srcObj: exampleObj,
srcFunc: "foo",
targetObj: exampleObj,
targetFunc: "bar",
once: true
});
As I'm sure you've already guessed, there's an analogous kwDisconnect method. Just pass it what you pass kwConnect, naturally.
Using dojo.event also masks browser differences by normalizing the event object (for DOM node events) so you can use common event code in any browser.
Fixed event objects have these modifications:
For key events, a set of event key code aliases are installed, so you can express (e.keyCode == e.KEY_ESC). Also, a reverse key code lookup is installed, so you can express (e.revKeys[e.keyCode] == 'KEY_ESC').
These properties are made available in all browsers:
The following methods are also made available:
Additionally, event (W3) vs. window.event (IE) is taken care of: all connected event handlers get passed a fixed event object (even in IE).
As an example, the code below will work in any browser:
dojo.event.connect(dojo.byId("foo"), "onmousemove"),
function(evt){
alert("mouse at pos" + evt.pageX + "," + evt.pageY);
});A brief note about events and widgets.
dojo.event.connect() can be used with widgets just like any other objects. However, there is a shortcut for defining "after" advice on a widget.
In the above example, the alert is called after the widget's own onClick() function finishes executing.
On the other hand, in the case below:
The widget's onClick function is replaced by function foo.
This is a somewhat confusing discrepancy (the latter behavior is more consistent with widget parameter setting in general), but it's left in place for backwards compatibility.
Often you will want to schedule some code to run on page load. Traditionally, this is done like
window.onLoad = ...;
or perhaps
However, that won't work for Dojo, because Dojo needs to override window load and unload. So, you should do this:
function init(){
...
}
dojo.addOnLoad(init);
function cleanup(){
...
}
dojo.addOnUnload(cleanup);Just like the normal dojo.event.connect() call, addOnLoad() and addOnUnload() can be called multiple times without overwriting the previous values, so you don't have to worry about one piece of Javascript code affecting another.
The line dojo.addOnLoad(init); tells Dojo to call the init function when it has finished loading correctly. This is very important! If the init
function was called before Dojo has finished parsing the HTML then widget objects would not have been instantiated and so would not exist at that point in time -
causing a nasty error.
Use publish and subscribe to communicate events anonymously between widgets or any JavaScript functions of your choosing. You may also consider customizing the widget to allow the topic name to be passed in as an initialization parameter to make the widget more flexible.
The following example shows how two objects may use publish and subscribe to communicate with each other.
var foo = new function() {
this.init = function() {
dojo.event.topic.subscribe("/mytopic", this, processMessages);
}
function processMessages(message) {
alert("Message: " + message.content);
}
}
var bar = new function() {
this.showMessage = function(message) {
dojo.event.topic.publish("/mytopic", {content: message});
}
}
foo.init();
bar.showMessage("Hello Dojo Master");
In the exampe above the object foo registers with a topic called '/mytopic' when the init function is called. Bar publishes a message to the topic '/mytopic' which results in the function showMessages being called. You can create any number of topics to publish and subscribe to.
Dojo provides a means of anynonymous event communication which can be very useful to connect together widgets in a page that may have no previous knowledge of each other. This maybe done using publish/subscribe style events. Publish subscribe style events require that the components that wish to communicate information simply share the name of a topic or queue to which the events are published/subscribed to. Objects may be passed as an argument of the events which provides a powerful means of inter-object/widget communication.
The API for publishing to a topic is as follows:
dojo.event.topic.publish("/topicName", args);That
is pretty much it to publish an event. The arguments are passed as an
object literal and will be seen by all clients subscirbed to the
corresponding topic "/topicName".
The API for subscribing to a topic is as follows:
dojo.event.topic.subscribe("/scroller", targetObj, targetFunc);A more detailed example follows:
var ac;
var is;
function init() {
ac = new AccordionMenu();
ac.load();
is = new ImageScroller();
is.load();
}
function Scroller() {
this.setProducts = function(pid) {
// show the products for pid
}
this.handleEvent = function(args) {
if (args.event == 'showProducts') {
this.setProducts(args.value);
}
}
this.load = function () {
dojo.event.topic.subscribe("/scroller", this, handleEvent);
}
function Accordion() {
function expandRow(target) {
...
var link = document.createElement("a");
dojo.event.connect(link, "onclick", function(evt){
this.target = target;
dojo.event.topic.publish("/scroller", {event: "showProducts", value : target});
});
}
}An "onclick" event on the element link will cause an event to be published to the topic name "/scroller" which is shared by both the Accordion and Scroller objects. In the case of this example the "handleEvent" function of the Scroller object will be callsed with the object literal {event: "showProducts", value : target}.
As can be seen topics can be very useful. When designing widgets or objects that need to interact with widgets or objects consider using publish and subscribe style events.Events in JavaScript or Dojo based applications are essential to making applications work. Connecting an event handler (function) to an element or an object is one of the most common things you will do when developing applications using Dojo. Dojo provides a simple API for connecting events via the dojo.event.connect() function. One important thing to note here is that events can be mapped to any property or object or element. Using this API you can wire your user interfaces together or allow for your objects to communicate. The dojo.event.connnect() API does not require that the objects be Dojo based. In other words, you can use this API with your existing interfaces.
dojo.event.connect has multiple function signatures, but one of the simplest is:
dojo.event.connect(srcObj, "srcFunc", targetFunc);
The arguments are the source object, the source function (in quotes) and the target function reference or anonymous function.
Here we have a DOM node called mylink, and whenever that DOM node is clicked myHandler will be called:
var link = dojo.byId("mylink");
dojo.event.connect(link, "onclick", myHandler);function myHandler(evt) {
alert("dojo.connect handler");
}
Above the "onclick" property of link element is connnected to the function myHandler.
But what if we don't want to set up a named function for the event handler? No problem:
var link = dojo.byId("mylink");
// connect link element 'onclick' property to an anonymous function
dojo.event.connect(link, "onclick", function(evt) {
...
});
The example above shows how an anonymous function can be mapped to the "onclick" property of a link element with an existing in-lined DOM 1 style handler connected to using the "onclick" attribute of the element.
So far, though, we're not doing anything that can't be done by setting the onclick property of the DOM Node. But what about attaching a method of an object to a DOM Node's event handler? Normally, you'd have to do something like:
var handlerNode = document.getElementById("handler");
handlerNode.onclick = function(evt){
object.handler(evt);
};
Dojo simplifies it to:
var handlerNode = document.getElementById("handler");
dojo.event.connect(handlerNode, "onclick", object, "handler");
This connect() call ensures that when handlerNode.onclick() is called, object.handler() will be called with the same arguments. Language limitations of JavaScript make it impossible to pass in the object and function name together, however separating them into an object reference and function name isn't difficult.
So we've seen that connect() can handle DOM events, but what about that more expansive view of events that was mentioned earlier? To demonstrate, lets define a simple object with a couple of methods:
var exampleObj = {
counter: 0,
foo: function(){
alert("foo");
this.counter++;
},
bar: function(){
alert("bar");
this.counter++;
}
};
So lets say that I want exampleObj.bar() to get called whenever exampleObj.foo() is called. We can set this up the same way that we do with DOM events:
dojo.event.connect(exampleObj, "foo", exampleObj, "bar");
Now calling foo() will also call bar(), thereby incrementing the counter twice and alerting "foo" and then "bar". Any caller that was counting on getting the return value from foo() won't be disappointed. The source method should behave just as it always has. On the other hand, since there's no explicit caller for bar(), it's return value will be lost since there's no obvious place to put it.
We've also inadvertently demonstrated that connect() takes variable forms of arguments. So far, it's correctly handled:
This is par for the course when using connect(). Since it is used in so many places, for so many things, and in so many ways, connect() does a lot of checking and normalization of it's arguments. The connect method tries to disambiguate the types of the positional parameters based on usage. Some common usages are:
The first paramether is adviceType ("after" and "before") and is optional. If it is not supplied then it defaults to "before". In the above example, adviceType was not provided and so the default, in this case "before" is used.
srcObj - the scope (scope1) in which to locate/execute the named srcFunc. This is also optional and if it is not supplied then Dojo assumes the global object.
srcFunc - the name of the function to connect to. In the above examples it is "globalFunctionName2" or "functionName2". This is in conjunction with the srcObj parameter. Dojo will look for a function, srcFunc, in srcObj.
adviceObj - scope (scope 2) in which to locate/execute the named adviceFunc. Again this parameter is optional and if not supplied Dojo will assume the global object.
adviceFunc - name of the function ("globalFunctionName1" or "functionName1") being conected to srcObj.srcFunc
There's one more modifier up the sleeve of connect()/kwConnect(); delayed calling. The delay property in kwConnect (the 9th positional parameter for connect) is a delay in milliseconds for those platforms that support it (all browsers do).
The last problem worth mentioning is circular connections. Circular connections can occur when (perhaps even indirectly) a listener also calls the function it's listening to. The good news is that in a JavaScript interpreter, this will pretty quickly yield an exception of some sort. "Too much recursion" is a tip off that you've hit this problem. Debugging circular connections can be opaque, but tools like Venkman help.
Note: The code for this feature is available in Dojo 0.4 and later. IE 7 Support in Dojo 0.4.1 and later.
<!-- Security protection: uncomment the script tag to enable. --> comment and remove the comments from that opening script tag.<!-- Security protection: uncomment the script tag to enable. --> comment and remove the comments from that opening script tag.Most of the magic of the dojo.io package is exposed through the bind() method. dojo.io.bind() is a generic asynchronous request API that wraps multiple transport layers (queues of iframes, XMLHTTP, mod_pubsub, LivePage, etc.). Dojo attempts to pick the best available transport for the request at hand, and in the provided package file, only XMLHTTP will ever be chosen since no other transports are rolled in. The API accepts a single anonymous object with known attributes of that object acting as function arguments. To make a request that returns raw text from a URL, you would call bind() like this:
dojo.io.bind({
url: "http://foo.bar.com/sampleData.txt",
load: function(type, data, evt){ /*do something w/ the data */ },
mimetype: "text/plain"
});
That's all there is to it. You provide the location of the data you want to get and a callback function that you'd like to have called when you actually DO get the data. But what about if something goes wrong with the request? Just register an error handler too:
dojo.io.bind({
url: "http://foo.bar.com/sampleData.txt",
load: function(type, data, evt){ /*do something w/ the data */ },
error: function(type, error){ /*do something w/ the error*/ },
mimetype: "text/plain"
});
It's possible to also register just a single handler that will figure out what kind of event got passed and react accordingly instead of registering separate load and error handlers:
dojo.io.bind({
url: "http://foo.bar.com/sampleData.txt",
handle: function(type, data, evt){
if(type == "load"){
// do something with the data object
}else if(type == "error"){
// here, "data" is our error object
// respond to the error here
}else{
// other types of events might get passed, handle them here
}
},
mimetype: "text/plain"
});
One common idiom for dynamic content loading is (for performance reasons) to request a JavaScript literal string and then evaluate it. That's also baked into bind, just provide a different expected response type with the mimetype argument:
dojo.io.bind({
url: "http://foo.bar.com/sampleData.js",
load: function(type, evaldObj){ /* do something */ },
mimetype: "text/javascript"
});
And if you want to be DARN SURE you're using the XMLHTTP transport, you can specify that too:
dojo.io.bind({
url: "http://foo.bar.com/sampleData.js",
load: function(type, evaldObj){ /* do something */ },
mimetype: "text/plain", // get plain text, don't eval()
transport: "XMLHTTPTransport"
});
Being a jack-of-all-trades, bind() also supports the submission of forms via a request (with the single caveat that it won't do file upload over XMLHTTP):
dojo.io.bind({
url: "http://foo.bar.com/processForm.cgi",
load: function(type, evaldObj){ /* do something */ },
formNode: document.getElementById("formToSubmit")
});
Phew. Think that about covers the basics. Good thing you weren't planning on implementing all that stuff yourself, right?
As you have seen, Dojo provides powerful, yet simple, ways of performing a variety of I/O functions through the use of dojo.io.bind. However, during the development of a typical application, a developer will have many I/O calls to make and will typically gravitate towards a common way of making those I/O calls on both the server and the client. This will often include defining functions that take some input and perform the appropriate request, as well as hooking that request to a callback function to process the results. In effect, the developer is required to implement a way of marshaling the request to the server in a way that it expects and then to have the client receive the contents in a way it expects. Dojo's RPC service aims to make this less error prone, easy to do, and require less code.
Remote Procedure Calls (RPC), also know as Remote Method Invocations, are a mainstay of the client/server development world. Essentially, RPC allows a developer to invoke a method on a remote host. Dojo provides a basic RPC client class that has been extended to provide access to JSON-RPC services and Yahoo services. It was designed so that it is also fairly trivial to implement custom RPC services.
Let's pretend that we have a little application that we want to make some server calls with. For simplicity's sake, we'll say the methods we want the server to do are add(x,y) and subtract(x,y). Without using anything special, like an RPC client, we might do something like this:
add = function(x,y) {
request = {x: x, y: y};
dojo.io.bind({
url: "add.php",
load: onAddResults,
mimetype: "text/plain",
content: request
});
}
subtract = function(x,y) {
request = {x: x, y: y};
dojo.io.bind({
url: "subtract",
load: onSubtractResults,
mimetype: "text/plain"
content: request
});
}As you can see, this isn't particularly difficult. However, this is quite the simple application, despite our every attempt to make it complicated by having the server add or subtract two numbers instead of performing these operations in the client in the first place. What happens if our application is not so simple and has 30 different requests to make? I guess we would have to just write this same code over and over for each different request; each time making a request object, specifying URLs, potentially validating parameter types, and so on. This is simply error prone and boring to write.
Dojo's RPC clients simplify this whole process by taking a simple definition of the remote methods and application needs and generating client side functions to call these methods. A developer need only write this definition, and initialize a RPC client object and then all of these remote methods are available for the developer to use as normal.
The definition file, called a Simple Method Description (SMD) file, is a simple JSON string that defines a URL that will process the RPC requests, any methods available at that URL, and the parameters those methods take. The definition for our example above might look like this:
{
"serviceType": "JSON-RPC",
"serviceURL": "rpcProcessor.php",
"methods":[
{
"name": "add",
"parameters":[
{"name": "x"},
{"name": "y"}
]
},
{
"name": "subtract",
"parameters":[
{"name": "x"},
{"name": "y"}
]
}
]
}Once the definition has been created, the code its pretty simple. The definition can be supplied either as a URL to retrieve it, a JSON string, or a JavaScript object.
var myObject = new dojo.rpc.JsonService("http://localhost/definition.smd");
var myObject = new dojo.rpc.JsonService({smdStr: definitionJSON});
var myObject = new dojo.rpc.JsonService({smdObj: definition});
Thats it! Now all thats left is to call the method.
myObject.add(3,5);
I'll bet you are saying to yourself, "Nice try, but I want to get the results of the add method, not just call it." You are correct, but that is also simple to achieve. Recall that we are making asynchronous calls to the server. While we could make the request synchronous, it would likely provide for a bad user experience because it would block the user interface during the call. Instead, the return value of the myObject.add() call, is a deferred object. The deferred object, something that might be familiar to users of Twisted Python or MochiKit, allows a developer to attach one or more callbacks and errbacks to the resultant data event. Our simple example can be expanded as such:
var myDeferred = myObject.add(3,5);
myDeferred.addCallback(myCallbackMethod);
or more succinctly:
var myDeferred = myObject.add(3,5).addCallback(myCallbackMethod);
As you can see, we've added myCallbackMethod as a callback for the deferred object returned from myObject.add(). In this case myCallbackMethod will be called with parameter with a value of 8. Likewise, an errback method can be attached to the deferred object to process an errors returned from the server. We can add as many callbacks and errbacks to our deferred object as we want and they will be called in the order that they were connected to the deferred object.
This discussion has revolved around using dojo.rpc.JsonService, which is Dojo's JSON-RPC client. In addition to JsonService, Dojo offers an RPC client for connecting to Yahoo services, dojo.rpc.YahooService. The syntax and call structure is identical. While Dojo is currently limited to these two RPC clients, the design of the dojo.rpc.RpcService base class, which is inherited by dojo.rpc.JsonClient and dojo.rpc.YahooService allows a developer to easily customize and extend dojo.rpc.RpcService, to create services that meets their specific needs. These customizations will be discussed later in Part II when we discuss how to get the most out of Dojo.
dojo.io.bind and related functions can communicate with the server using various methods, called transports. Each has certain limitations, so you should pick the transport that works correctly for your situation.
The default transport is XMLHttp.
The IFrame I/O transport is useful because it can upload files to the server. Example usage:
<script type="text/javascript">
dojo.require("dojo.io.*");
dojo.require("dojo.io.IframeIO");
function mySubmit() {
dojo.io.bind ({
url: 'server.cfm',
handler: callBack,
mimetype: "text/plain",
formNode: dojo.byId('myForm')
});
}function callBack(type, data, evt) {
//The data object will be different
//depending on the mimetype used in the dojo.io.bind()
//call. See below for more info.
dojo.byId('result').innerHTML = data;
}
</script>
The response type from the above URL can be text, html, or JS/JSON.
IframeIO responses need to be a little different from the ones that are sent back from XMLHttpRequest responses. Because an iframe is used, the only reliable, cross-browser way of knowing when the response is loaded is to use an HTML document as the return type.
If the return type (specified by the mimetype) is text/plain, text/javascript or text/json, then the server response should be an HTML page that has a <textarea> element. The data that you want returned to the dojo.io.bind() load callback should be the text inside the textarea element. For the text/javascript or text/json return types, the text inside the textarea element will be converted to JavaScript or JSON, repectively, and that will be the data sent to the load callback.
If the return type is text/html as the return type, then the data parameter will be the complete HTML document that is in the iframe.
For IframeIO, XML responses are not supported because we can't get a nice cross-browser solution. If you want text/html as the mimetype, what you get back is the document object for the document in the iframe.
See these tests for more info:
text/plain: http://archive.dojotoolkit.org/nightly/tests/io/test_IframeIO.text.html
text/html: http://archive.dojotoolkit.org/nightly/tests/io/test_IframeIO.html.html
text/javascript: http://archive.dojotoolkit.org/nightly/tests/io/test_IframeIO.html
Due to security restrictions, XMLHttp cannot load data from another domain. The ScriptSrcIO transport is useful for doing this. Yahoo's RPC service is implemented using ScriptSrcIO.
To use ScriptSrcIO, use the following require statements
and use the normal dojo.io.bind() method.
To force a ScriptSrcTransport request, use transport: "ScriptSrcTransport" in the keyword arguments to dojo.io.bind(). The mimetype argument is also required.
Example:
dojo.require("dojo.io.*");
dojo.require("dojo.io.ScriptSrcIO");
dojo.io.bind({
url: "http://example.com/json.php",
transport: "ScriptSrcTransport",
mimetype: “application/json",
jsonParamName: "callback",
content: { ... }
});ScriptSrcIO (which provides ScriptSrcTransport) allows for four basic types of requests:
Each type uses [script src="url"][/script] to accomplish the request.
Here is a list of bind() keyword arguments that are supported for all types of requests. The four types of transport requests are:
Simply adds a script element with a src. Does not do any polling and does not expect a callback. Also does not support any timeouts. Example:
dojo.io.bind({
url: "http://the.script.url/goes/here",
transport: "ScriptSrcTransport",
mimetype: “text/javascript"
});Adds a script element with a src. It will poll to see if a typeof expression does not equal undefined. When the typeof check succeeds, a load callback is called. Timeout and error callbacks are supported with this type of request.
Example:
dojo.io.bind({
url: "http://the.script.url/goes/here",
transport: "ScriptSrcTransport",
mimetype: "text/javascript",
checkString: "foo", //This means (typeof(foo) != undefined) indicates that the script loaded.
load: function(type, data, event, kwArgs) { /* type will be "load", data and event null, , and kwArgs are the keyword arguments used in the dojo.io.bind call. */ },
error: function(type, data, event, kwArgs) { /* type will be "error", data and event will have the error, , and kwArgs are the keyword arguments used in the dojo.io.bind call. */ },
timeout: function() { /* Called if there is a timeout */},
timeoutSeconds: 10 //The number of seconds to wait until firing timeout callback in case of timeout.
});
Adds a script element with a src. This sort of usage allows using services that use the JSONP convention to specify the callback that the server will use. Specify the name of the JSONP callback parameter using jsonParamName. Yahoo! Web Services use a jsonParamName of "callback". Some other services use jsonParamName of "jsonp". Timeouts are supported with this type of request. Example for a data service that uses "callback" as the URL parameter:
dojo.io.bind({
url: "http://the.script.url/goes/here",
transport: "ScriptSrcTransport",
mimetype: "application/json",
jsonParamName: "callback",
load: function(type, data, event, kwArgs) { /* type will be "load", data will be response data, event will null, and kwArgs are the keyword arguments used in the dojo.io.bind call. */ },
error: function(type, data, event, kwArgs) { /* type will be "error", data will be response data, event will null, and kwArgs are the keyword arguments used in the dojo.io.bind call. */ },
timeout: function() { /* Called if there is a timeout */},
timeoutSeconds: 10 //The number of seconds to wait until firing timeout callback in case of timeout.
});
Here is a real example of using JSONP to look up the del.icio.us bookmarks.
<style type="text/css">
.bookmarks {
width: 300;
background: lightGray;
border-style: solid;
border-width: 2px;
border-color: black
}
</style><script type="text/javascript" src="dojo.js"></script>
<script type="text/javascript">
dojo.require("dojo.io.*");
dojo.require("dojo.io.ScriptSrcIO");
dojo.addOnLoad(getBookmarks);
function getBookmarks() {
dojo.io.bind({
url: "http://del.icio.us/feeds/json/dojomaster",
transport: "ScriptSrcTransport",
jsonParamName: "callback",
load: function(type, data, event, kwArgs){showBookmarks(data);},
mimetype: "application/json",
timeout: function() {alert('timeout');},
timeoutSeconds: 10
});
}
// The code for showing the bookmarks is courtesy of del.icio.us
// http://del.icio.us/help/json
function showBookmarks(posts) {
var ul = document.createElement('ul');
for (var i=0, post; post = posts[i]; i++) {
var li = document.createElement('li');
var a = document.createElement('a');
a.style.marginLeft = '20px';
var img = document.createElement('img');
img.style.position = 'absolute';
img.style.display = 'none';
img.height = img.width = 16;
img.src = post.u.split('/').splice(0,3).join('/')+'/favicon.ico'
img.onload = showImage(img);
a.setAttribute('href', post.u);
a.appendChild(document.createTextNode(post.d));
li.appendChild(img);
li.appendChild(a);
ul.appendChild(li);
}
document.getElementById("container").appendChild(ul);
}
function showImage(img){ return (function(){ img.style.display='inline' }) }
</script>
<div id="container" class="bookmarks"></div>To customize this scirpt simply change the URL http://del.icio.us/feeds/json/dojomaster to include your del.icio.us user name. This example shows the bookmarks for the user "dojomaster".
Adds a script element with a src. Uses the Dynamic Script Request convention to specify the callback that the server will use. Multipart requests (splitting a long request across multiple GET requests) is supported. Timeout and error callbacks are supported with this type of request. Example:
dojo.io.bind({
url: "http://the.script.url/goes/here",
transport: "ScriptSrcTransport",
mimetype: "application/json",
useRequestId: true, //adds the _dsrId to request with a generated ID. If a specific request ID is wanted, use apiId: "myId" instead
//optional: forceSingleRequest: true, //Will not segment the request to multipart requests even if it is a long URL.
constantParams: "name1=value1&name2=value2" //params to be sent with each request that is part of a multipart request. See spec.
load: function(type, data, event, kwArgs) { /* type will be "load", data will be response data, event will be onscriptload event, and kwArgs are the keyword arguments used in the dojo.io.bind call. */ },
error: function(type, data, event, kwArgs) { /* type will be "error", data will be response data, event will be onscriptload event, and kwArgs are the keyword arguments used in the dojo.io.bind call. */ },
timeout: function() { /* Called if there is a timeout */},
timeoutSeconds: 10 //The number of seconds to wait until firing timeout callback in case of timeout.
});
ScriptSrcTransport supports the following arguments across all types of requests. In general, all of these arguments have the same meaning and use in XMLHTTPTransport.
The XMLHttp transport is the default transport.
It works well in most cases, but it cannot transfer files, cannot work across domains (ie, cannot connect to another site than the current page), and doesn't work with the file:// protocol.
Example usage:
<script type="text/javascript">
dojo.require("dojo.io.*");
function mySubmit() {
dojo.io.bind ({
url: 'server.cfm',
handler: callBack,
formNode: dojo.byId('myForm')
});
}
function callBack(type, data, evt) {
dojo.byId('result').innerHTML = data;
}
</script>
When all of your testing is done and you're ready to distribute your application, you don't want to have to use a bulky source, or have the various resource used by your application scattered all over the place. As a web developer, you want a way to lower the number of file requests and the overall download size of your application code. Dojo provides a robust system that not only solves these problems, but also allows incremental loading of infrequently used resources. The following sections will show you how to build a profile that suits your needs and how to create a Dojo release to include in your HTML file.
Additionally, the build system allows for the creation of a compressed package file that contains all of the script code needed for your app, no more and no less, through an Ant build step.
The loader system allows you to list a single script include file which will then find and fetch resources as they are needed for your application from the Dojo source tree. This means in the simplest case, no build process is required and yet there is no need to include a script tag for every resource that is to be loaded.
Additional performance optimizations can be had by adjusting the web server or application code. Included in this section are some tips on both server side and client side changes that can make your application run faster.
For current svn / build notes: http://dojotoolkit.org/book/dojo-book-0-9/part-4-meta-dojo-0
The first step to creating a custom build is getting the Dojo source tree. There are several places to get the source as described below.
The latest Dojo releases can be downloaded from the download section of the dojotoolkit.org project's home page. There are several prebuilt Dojo bundles available pre release. Each bundle has been tested and believed to be pretty stable. The names of the bundles should indicate what is contained in that bundle. For example, kitchen_sink contains most of what you need in Dojo except the widgets while core contains just the core features.
NOTE: the release bundles are NOT suitable to be used for a custom build. For that, you need an original source snapshot. Starting with Dojo 0.4.2, the source snapshots are in the download directory for the release, but have "-src" in their name. For instance:
http://download.dojotoolkit.org/release-0.4.2/dojo-0.4.2-src.zip
or
http://download.dojotoolkit.org/release-0.4.2/dojo-0.4.2-src.tar.gz
Dojo is built every night and bundled into a tarball hosted at archive.dojotoolkit.org. The most recent build is always available in the nightly directory. Note that this bundle has not undergone much testing and therefore may not be stable. It does however contain the latest bug fixes and feature implementations for the current release of Dojo. There is only one bundle available in the nightly directory and it contains all the code.
Dojo source code is hosted in a Subversion repository. The repository contains the latest source code and is used to create the nightly builds. There are a couple of ways to get a copy of the most up-to-date source code.
Anonymous release checkout (change 0.4.2 to the release you want): "svn co http://http://svn.dojotoolkit.org/dojo/tags/release-0.4.2"
Anonymous 0.4 branch checkout: "svn co http://svn.dojotoolkit.org/dojo/branches/0.4"
Anonymous trunk checkout: "svn co http://svn.dojotoolkit.org/dojo/trunk"
The Build system reduces the loading time of
Javascript library files. It does this by:
Its main benefit is that it encourages developers who are using Dojo to structure their code properly without premature optimisation. The developer can write clean, well laid out and commented code knowing that it will not affect the loading time at deployment.
When developing or debugging a web site, the original source code is usually used directly. Once the site is ready to be deployed the relevant library files are compacted and packaged together. The Build system will create both compressed and uncompressed packages for distribution. For more information on how compression works see this article.
You need the following installed on your computer to run Dojo's build system:
Creating your own Dojo distribution allows you to take advantage of the build system. In the src/buildscripts/profiles directory, you will create a profile build file called foo.profile.js like this:
var dependencies = [
"dojo.io.*",
"dojo.event.*",
"dojo.xml.*",
"dojo.graphics.*",
"dojo.io.BrowserIO",
"dojo.widgets.*",
"dojo.widgets.Button",
];
load("getDependencyList.js");
This should list all the resources you are directly using.
If you want to include your own code into a build you need to make
custom resource for your code, ie,
dojo.provide("mycompany.widget.SomethingCool"), then define the profile file like this:
var dependencies = [
"dojo.widget.*",
"dojo.io.*",
"dojo.event.*"
"mycompany.widget.SomethingCool"
];
dependencies.prefixes = [
["mycompany", "../mycompany"]
];
load("getDependencyList.js");
The "../mycompany" path in the dependencies.prefixes is relative to dojo.js. It operates the same as what you use for dojo.registerModulePath() (or dojo.setModulePrefix() in 0.3.1 or earlier versions).
(available since Dojo 0.4.1)
If you subclass a Dojo widget in your code and don't make use of the original widget standalone (directly), then you may not want to include the original template file for that particular widget at all. Or you have a custom css file for a Dojo widget, and you don't want to include the original one, then you can specify internSkipList in your profile.
Let's assume you want to have a built Dojo with Editor2 in it, but you do not want to include the default EditorToolbar.html as you already have a customized one, then you can use this profile:
var dependencies = [
"dojo.widget.Editor2"
];
dependencies.internSkipList = [
"dojo.widget:templates/EditorToolbar.html"
];
load("getDependencyList.js");
Notice the "dojo.widget:" prefix to the actual path for EditorToolbar.html: it is used to avoid confliction of template file paths among different namespaces.
After specifying a profile file as shown above that statically specifies the resources you want to include, and saving it as /buildscripts/profiles/foo.profile.js, run Ant and specify the profile name as a parameter. For example, from the buildscripts directory:
ant -Dprofile=foo clean release
Some additional build options:
-DprofileFile=/path/to/myProfile.profile.js
You can use this instead of -Dprofile to specify a profile file that is not in the dojo/buildscripts/profiles folder.
intern-strings
If widgets that are built into dojo.js specify templatePath and/or templateCssPath, the intern-strings ant target will intern the HTML/CSS into dojo.js so that additional XMLHttpRequests are not needed to load the HTML and CSS later.
Another build example:
ant -DprofileFile=/path/to/myProfile.profile.js clean release intern-strings
strip-resource-comments
Available in Dojo 0.4.2+. This task will remove the comments from the files in src/. This helps the download size of modules that are not part of dojo.js and are loaded via dojo.require().
Example (for Dojo 0.4.2+):
ant -Dprofile=ajax clean release intern-strings strip-resource-comments
For Dojo 0.4.3+, it is possible to run all the files through the Dojo compressor instead of just stripping comments. To do that, specify -Dstrip_and_compress=true in the ant command:
ant -Dprofile=ajax -Dstrip_and_compress=true clean release intern-strings strip-resource-comments
Running the build generates a release directory and all that is needed to use Dojo with your application. You will distribute the contents of the release directory with your application.
Once your resources are specified, to include Dojo from the
source tree, you simply include the following tag in your html source:
Additionally, you will need to specify the resources to load as you need them loaded. I do this in a separate script file to initialize Dojo. For example, if you have code that required the Dojo event system, you would add the command:
dojo.require("dojo.event.Event");Note however, that you should still distribute the release/dojo/src directory in your distribution. Although the JavaScript?, css, and html files have been merged into dojo.js, there are still image files that are being accessed from the src/ directory.
If you want to say something like dojo.require("foo.baz.*"), to include all the resources in the acme/foo directory, then you will need to create file called
__package__.js
in the foo/baz directory, with the following format:
dojo.kwCompoundRequire({
common: ["foo.baz.resource1",
"foo.baz.resource2"] // a generic dependency
});
dojo.provide("foo.baz.*");
NOTE: For Dojo 0.3+, dojo.hostenv.conditionalLoadModule() has been replaced with dojo.kwCompoundRequire() and dojo.hostenv.moduleLoaded() has been replaced with dojo.provide(). To use the old methods with Dojo 0.3+, please use a compatibility package.
Here's the contents of dojo/src/foo/__package__.js:
dojo.kwCompoundRequire({
common: ["dojo.foo.Foo", "dojo.foo.Bar"],
browser: ["dojo.foo.Baz"]
});
dojo.provide("dojo.foo.*");
This set of definitions says that when someone writes dojo.require("dojo.foo.*"); from within a browser, Foo.js, Bar.js, and Baz.js will get included from this directory. When run from the command line, only Foo.js and Bar.js will get included.
Not every application is the same, and Dojo's package and build tools let you mix and match in order to handle these variations. In many cases, you may be able to use one of the "off the shelf" builds in order to test the performance differences.
If you've done testing and are certain that a custom build is the right soltuion for you, or you are just curious about what is happening, then the first step is to decide what modules to include in your build. All of the files from the modules you specify and their dependencies will be included in the resulting dojo.js file. It's best to start with the modules that you know are used in every page of your application, or at least the majority of them. This will be your base profile, and all of these modules will be compressed into a single file that you will specify on your HTML page.
Creating this profile is a soft science, because loading files later means that they are less compressed and the total download size of your application increases, as well as the number of files that need to be loaded later on. But this compromise means that if there are infrequently used parts of your application, the average user will have a smaller download. Also, if getting to various stages of your application happens over a long period of time, these modules might be better left out of your profile as well. The package system can be used to opportunistically load as well as optimistically load resources. Here's where tools can help sort out what's best.
Since Dojo package and resource loading generally happens via XMLHTTP requests, tools like Firebug's network visualization to determine which files are getting requested at runtime from Dojo. See the section on Profiling JavaScript for more tips on how to determine what should be included in your distribution. A poorly constructed build, or no build at all, tends to show a cascade of requests that are very clearly serialized. By noting the namespaces that are being requested and then adding them to the profile, you can construct builds that meet the needs of users and defer work until it's really necessary.
For example, if a user has to go through a log in screen before he or she is taken to the next page, it might be worth waiting for a successful log in before loading additional modules.
So you've built an application using Dojo and it "feels slow", now what?
Dojo's performance tuning centers around four areas:
If you are a widget developer, you might also be interested in Profiling Javascript.
The plan of attack for reducing page loading latency is to configure the web server sending the JavaScript files to cache them agressively and to not request even status information about them. Servers that don't send adequate cache information may find that UIs are very slow even though the content of the scripts are never sent. Instead, browsers may be checking to see if the file has changed since it was last seen. This "has it changed?" check is synchronous and serial and so we want to eliminate it if possible.
There are several things you can do to improve the performance of your application relating to the web server that are outside the scope of Dojo. Some things to look at are server side caching, data compression and even application program structure. See the following article Improving performance of Dojo-based web applications which contains useful information on these topics.
If you find IE is repeatedly downloading the same image, read this article for advice. This also helps get rid of ugly image flickering in IE.
Dojo builds or distributions contain all the files that are part of Dojo and that may seem some what large. Luckily Dojo includes a mechanism which downloads only files that you use. Creating a custom build of Dojo means identifying the files that you use which will improve the download of the page since a separate network request is not made for each module.
See Creating a Custom Distribution for more information on how to customize your Dojo build.
Page load time is proportional to the number of nodes on the page because Dojo searches all the nodes to see if they are widgets. You can avoid this in the following ways:
Add the following line in the
<script> djConfig = { parseWidgets: false, searchIds: [] }; </script> This turns off parsing so if you do include widgets in your page you will have to make the widgets known to Dojo yourself. Use searchIds.push on the global object djConfig. In the :
<div dojoType="Button" id="button1">...</div>
<script>djConfig.searchIds.push("button1");</script>
...
<div dojoType="Button" id="button2">...</div>
<script>djConfig.searchIds.push("button2");</script>
...
You can also make the parser not search underneath a given node by doing this
<div parseWidgets="false">
... no widgets in here, don't waste your time looking! ...
</div>
One little known feature about searchIds usage is that if you add at least one searchIds, then the "body" (and so the whole) of the document will not be parsed at all, even when the djConfig.parseWidget=true. (See hostenv_browser.js 's makeWidget() function) I think this is a buggy behaviour, and the whole document should still be parsed if djConfig.parseWidget=true. A use case when this is simply necessary: you have a section where you turn off the parsing using the new parseWidget="false" flag. But if you happen to have some widgets inside, those must be parsed using searchIds. But because you've added one such, the whole page will not be parsed, but those parts may contain other widgets!
It sounds obvious, but many web pages have a lot of unnecessary markup. For example, instead of
<table ...>
<tr>
<td>Hello World</td>
</tr>
</table>
just do
<div class="foo">Hello World</div>
Time is proportional to the number of widgets on the page. No advice here.
Instead of downloading your whole page at once, defer portions until the user needs them. For example:
<div dojoType="Tooltip" href="tooltip.jsp"></div>
<div dojoType="TabContainer">
<a dojoType="LinkPane" href="tab1.jsp">Tab #1</a>
<a dojoType="LinkPane" href="tab2.jsp">Tab #2</a>
</div>
FIXME: content below this break is legacy and in need of serious rework
>> On Nov 20, 2005, at 3:54 AM, Christian Boulanger wrote:
>
>>> > I am using dojo.io.bind and need a reference to the calling object
>>> > in the handler code. Not being a javascript geek, I am still
>>> > confused about the closures stuff and how much it impacts
>>> > performance. Is the following way an ok way of doing things or is
>>> > there a better way? Would it make sense to add a parameter to
>>> > dojo.io.bind which transfers the callingObject to the handler
>>> > without need to create the closure?
>
>>
>> Closures are fine to use all over the place. The only time it can
>> matter is the specific instances when they cause circular references
>> to certain kinds of objects that don't fully participate in the
>> interpreter's garbage collection scheme (most commonly DOM objects in
>> IE).
and if you use dojo.event.connect() to set up your event handlers, you
don't have to worry about it even then. In short, go nuts with the
closures, just don't manually set properties on DOM nodes that include
functions or objects.
Regards
Alex Russell
Visit this link for more information about closures and memory leaks in IE.
This feature allows hosting dojo on a separate machine from the application; usually the dojo instance is shared between multiple applications. It's especially useful for internet applications, where dojo will be cached by proxy servers close to the client, thus increasing performance. AOL hosts dojo in this way.
When the dojo instance is shared between multiple applications, the dojo.js on the shared server typically won't contain all the resources required by an individual application. Thus, when the application issues a dojo.require() call, it will download the resource (such as dojo.html.extras).
This feature allows dojo to load the resource across domains. The technique uses HTML script elements to load the resources. This technique is also referred to as xdomain loading or xdDojo.
For Dojo 0.4.2+:
An example is at the end of the Usage section.
You can optionally set a wait time in milliseconds (djConfig.xdWaitSeconds) that specifies how long the resource loader should wait for a resource to load until returning an error. Since script elements do not give information about failed or long-running requests, this timeout is used to prevent infinite waiting in the browser. An exception via dojo.raise() will be thrown to indicate a load error. The default xdWaitSeconds is 30 for Dojo versions before 0.4.2, and 15 for 0.4.2+.
In Dojo 0.4.2+, instead of calling dojo.registerModulePath() a bunch of times to register all of your custom module paths, you can use djConfig.modulePaths to set up the module to path mappings:
var djConfig = {This example shows cross-domain loading pieces that should be in the head element of an HTML page:
<script type="text/javascript">
var djConfig = {
baseScriptUri: "../path/to/local/dojo/",
useXDomain: true,
xdWaitSeconds: 10
};
</script>
<script type="text/javascript" src="http://some.domain.com/dojo/dojo.js"></script>
<script language="JavaScript" type="text/javascript">
dojo.registerModulePath("dojo", "http://some.domain.com/dojo/src");
//Now any Dojo resources that are referenced via dojo.require()
//will be xdomain loaded.
dojo.require("dojo.lang.extras");
dojo.addOnLoad(function(){ alert("Resources are loaded!");});
</script>
Note
that it is important to always follow any block of dojo.require()
statements with a dojo.addOnLoad() call to register a function that
should be called after the asynchronous package loading finishes. So,
if you had this function before using an xdomain build:
function someFunctionThatIsRunAfterPageIsFirstLoaded(){
dojo.require("dojo.html");
dojo.require("dojo.event.*");
//Use dojo immediately
dojo.event.connect(....);
}
With xdomain asynchronous loading, this becomes:
function someFunctionThatIsRunAfterPageIsFirstLoaded(){
dojo.require("dojo.html");
dojo.require("dojo.event.*");
dojo.addOnLoad(function(){
//Use dojo after all the packages are ready.
dojo.event.connect(....);
});
}
Specify a dojoLoader=xdomain parameter to the build command. For instance, to build the Ajax custom build with xdomain loading:
ant -Dprofile=ajax -DdojoLoader=xdomain -Ddocless=true clean release intern-strings
dojoLoader=xdomain will include loader_xd.js in dojo.js, and generate the *.xd.js files that are needed to enable the build to be loaded across domains. The "intern-strings" part will inject the HTML and CSS used by widgets into the widget JavaScript file so that it can be loaded across domains.
As of 0.4.2, there are two new options you can add to the ant build. Also, it is no longer necessary to specify -Ddocless=true. The task that uses the "docless" value has been removed for 0.4.2.
xd-dojo-config
This ant task will set djConfig.useXDomain = true; inside the built dojo.js, effectively making the dojo.js always want to try to do xdomain loading (you can always load some modules locally by using dojo.registerModulePath()). It will also automatically register the module path for Dojo, so that you do not have to specify dojo.registerModulePath("dojo", "[url goes here]");. In order for the xd-dojo-config task to work, you must define -DxdDojoUrl. Also, this task should be run after the release ant target. Example:
ant -Dprofile=ajax -DdojoLoader=xdomain -DxdDojoUrl=http://some.domain.com/dojo/release-0.4.2/src clean release intern-strings xd-dojo-config
If you use this task, then for pages that use this dojo.js, you do not have to explicitly set djConfig.useXDomain or call dojo.registerModulePath() for the Dojo module.
strip-resource-comments
This task will remove the comments from the files in src/. This helps the download size of modules that are not part of dojo.js and are loaded via dojo.require(). This ant task is available for regular builds too. It should be the last task in the list. Example:
ant -Dprofile=ajax -DdojoLoader=xdomain -DxdDojoUrl=http://some.domain.com/dojo/release-0.4.2/src clean release intern-strings xd-dojo-config strip-resource-comments
Dynamic web applications that avoid page refreshes are very powerful, but this normally means that the Back and Forward buttons stop working in the browser. In addition, it can be hard to give the user an URL that can be bookmarked.
However, Dojo provides a solution to these issues, allowing a web application to capture the Back and Forward button clicks, and set a unique URL in the browser's location field. The solution is to use dojo.undo.browser.
The behavior described in this document is different from the 0.2.2 Dojo release. In that release, it was not very easy to track the state that corresponded to a "page", but it was possible to register callbacks for Back and Forward through a dojo.io.bind() call. For 0.3 and later, the Back/Forward/Bookmarking support was moved to a new module, dojo.undo.browser. As a result of the changes, you may see slightly different behavior as compared with 0.2.2. The main difference is the need to call dojo.undo.browser.setInitialState(state) to set the state for the page as it is first loaded by the browser.
Dynamic web applications that use things like XMLHTTPRequest and DOM updates instead of page refreshes do not update the browser history, and they do not change the URL of the page. That means if the user clicks the Back button, they will likely jump all the way out of the web application, losing any state that they were in. It is also hard to allow a user to bookmark the web application at a certain state.
Dojo's dojo.undo.browser module will introduce browser history so that it is possible for the user to click Back and Forward without leaving the web application, and the developer can get notification of these Back and Forward events and update the web application appropriately. Browser history is generated by using a hidden IFRAME and/or adding a unique value to the fragment identifier portion of the page URL. The fragment identifier is the #value thing in a URL. For example:
http://some.domain.com/my/path/to/page.html#fragmentIdentifier
Since changing the fragment identifier does not cause the page to refresh, it is ideal for maintaining the state of the application. The developer can specify a more meaningful value for the fragment identifier to allow bookmarking.
dojo.undo.browser allows setting a state object that represents the state of the page. This state object will get callbacks when the Back or Forward button is pressed. In addition to registering state objects with dojo.undo.browser directly, the state object can be passed to dojo.io.bind() and it will be added to dojo.undo.browser for you.
The following prerequisites are needed to use dojo.undo.browser:
Register the initial state of the page by calling:
dojo.undo.browser.setInitialState(state);
This state object will be called when the user clicks Back all the way back to the start of the web application. If the user clicks Back once more, they will go back in the browser to wherever they were before loading the web application.
The state object should have the following functions defined:
Example of the a very simple state object:
var state = {
back: function() { alert("Back was clicked!"); },
forward: function() { alert("Forward was clicked!"); }
}; To register a state object that represents the result of a user action, use the following call:
dojo.undo.browser.addToHistory(state);
or, if you are using dojo.io.bind(), if the object contains the function back() or backButton() or the property changeUrl, then dojo.io.bind() will call dojo.undo.browser for you (only works with XMLHTTPTransport and ScriptSrcTransport).
To change the URL in the browser's location bar, include a changeUrl property on the state object. If this property is set to true, dojo.undo.browser will generate a unique value for the fragment identifier. If it is set to any other value (except undefined, null, 0 or empty string), then that value will be used as the fragment identifier. This will allow users to bookmark the page.
There are test pages that shows how to use all of the pieces together:
http://archive.dojotoolkit.org/dojo-2007-05-15/ajax/tests/undo/test_brow...
http://archive.dojotoolkit.org/dojo-2007-05-15/ajax/tests/undo/test_brow...
What usability problems?
Consider that most painful of topics for web application developers: the back button. Web developers armed with some sample code, a decent DOM reference, and a lot of perseverance can build a pretty decent dynamic UI in modern browsers. These UIs doesn't jarringly destroy the user's in-page experience for the most trivial of tasks, like adding an item to a list. When larger portions of an application are mediated in this way, the user naturally has more desire to "go back" to some earlier state if things aren't working out the way they had planned or if the action isn't what they expected. An example might be switching between a view and edit mode in a content editing application.
As high-gloss web applications become the norm, many interactions become intra-page and not inter-page. Programmers looking for creative solutions have chosen XMLHTTP for these scenarios, but unfortunately, XMLHTTP breaks the back button, impairing the user experience. If the back button doesn't function in a way that meets user expectations, it becomes ever easier for the user to lose work or become confused about the state of an application. To assist the user, programmer need a way to capture back-button presses and do something intelligent with them.
If you've used Google Maps and you've tried to send your directions to a friend, you know that not being able to simply copy the URL out of the address bar is significantly confusing at first. Applications that dynamically construct large sections of the UI (like Google Maps) today resort to a link in an intermediate screen that the user can click to return to their current state and then, perhaps, book mark or send to someone else. And this is if and when they consider the "bookmarkability" problem at all. More common is an application that simply refuses to acknowledge that the user might want to pass around a URL to a friend and instead builds some heavyweight and non-standard state serialization mechanism that is more akin to a desktop application's "save as" feature. "Save-as" on the web is book-marking, and usable applications recognize this (even if they don't have great solutions for it today). Regardless of what serialization mechanism is in use, being able to represent the state of the application in a URL (or a marker for serialized state) is a must. This is a hard problem to be sure, and none of the currently available tools provide simple answers.
movies.csv ==> CsvStore ==> movies.html
As of January 2007, we have five simple datastores, which are included in dojo as example datastore implementations. The four datastores are:
| dojo.data.CsvStore | a read-only store that reads tabular data from .csv format files |
| dojo.data.OpmlStore | a read-only store that reads hierarchical data from .ompl format files |
| dojo.data.YahooStore | a read-only store that fetches search results from the Yahoo search engine web service |
| dojo.data.DeliciousStore | a read-only store that fetches bookmarks from the del.icio.us web service |
| dojo.data.RdfStore | a read-write store that uses SPARQL to talk to RDF data servers including, for example, the Rhizome RDF application server |
http://dojotoolkit.org/api/#dojo.data
You can also look at the API definition files themselves to see the documentation about individual API methods:
In the dojo.data unit-tests, there's an example page that allows you to read data from any of a variety of different data sources and then display data in a few different widgets:
We also have a number of dojo.data unit-test pages, which may serve as simple examples of how to use the APIs:
The widget code itself can be independent of the dojo.data APIs, and know nothing about how data access is done. The widget binding code depends on both dojo.data APIs and on the widget itself, but the dojo.data APIs are independent of both the widget itself and the widget binding. The widget and the binding are both independent of any particular datastore implementation, which allows a single widget binding to be used with a variety of datastores implementations.
Once one person has written one binding to display dojo.data items in a FilteringTable, then data items from any datastore can be displayed in a FilteringTable. If Dojo someday has 15 datastore implementations, and has 20 data display widgets, then dojo authors will only need to write 20 bindings in order to connect all the widgets to all the datastores. Without a standard dojo.data API, we would need a different bit of intermediary code for each possible connection between a widget and a datastore -- with 15 datastores and 20 widgets, that would require 300 different pieces of intermediary data translation code.
+-----------+
Widgets Bindings | dojo.data | Datastores Data Sources
| APIs |
+------------------+ | |
| Trees | | |
| (TreeV3) |-- binding --| |
+------------------+ | | +------------+ +-----------+
| |---| CsvStore |---| .csv file |
+------------------+ | | +------------+ +-----------+
| Tables & Grids | | |
| (FilteringTable) |-- binding --| | +------------+ +-------------------+
+------------------+ | |---| YahooStore |---| Yahoo web service |
| | +------------+ +-------------------+
+------------------+ | |
| Charts & Graphs | | | +------------+ +------------+
| (Chart) |-- binding --| |---| OpmlStore |---| .opml file |
+------------------+ | | +------------+ +------------+
| |
+------------------+ | | +------------+ +------------+
| Other widgets | | |---| RdfStore |---| RDF server |
| (ComboBox) |-- binding --| | +------------+ +------------+
| (SlideShow) |-- binding --| |
| (etc.) |-- binding --| | +------------+ +--------------------+
+------------------+ | |---| Other |---| other file formats |
+-----------+ | stores | | and web services |
+------------+ +--------------------+
We are designing the data-access APIs with a wide variety of use cases in mind, and we hope that the APIs will work well with many different kinds of data: RDF, XML, SQL, CSV, OMPL, etc. Here are some of the features we've been designing the APIs to support:
We hope that the dojo.data APIs will support:
We hope to eventually have different datastore implementations that read data from a wide variety of data sources. Here's a list of some of the kinds of data sources that we've had in mind while designing the APIs:
We hope that the dojo.data APIs do not impose unnecessary limitations on the ways that datastore implementations can represent data in memory. In particular, we've designed the APIs with a few different data representations in mind:
Question: Why aren't the dojo.data APIs more object-oriented, with accessor methods available on the individual data items?
For example, using the existing dojo.data APIs, a line of code might look like this:
Why weren't the APIs designed so that line of code instead looked like this:var value = datastore.get(kermit, color);
var value = kermitItem.get(color);
Answer: Performance -- both memory use and execution speed.
If we had used an object-oriented API for the item accessor methods, that would have required every datastore implementation to have a different JavaScript object for every data item. By putting the accessor methods on the datastore object rather than on a data item object, we made it possible for different datastore implementations to use different data structures.
For example, our OpmlStore reads data from an XML text file using XMLHttpRequest via a call to dojo.io.bind. The XML text file is automatically parsed into XML DOM nodes, and the OpmlStore uses those XML DOM nodes themselves as its native data item representation, which avoids the unnecessary step of copying all the data from the DOM nodes into JavaScript objects.
For more background on different approaches we considered, see the dojo.data design page on the dojo development wiki.The dojo.data module was first envisioned back in 2005 or earlier, before Dojo release 0.1 had shipped.
The sections below show some of the idioms that deserve special mention. See the reference guide for the full list.
Often you need to pass a function pointer as an argument (like above), but the function needs to operate within the context of an object. For example, if I have a class Foo that defines bar:
bar: function() {
return this.i++;
}and then I instantiate an instance of Foo:
var fooInstance = new Foo();
and I want a pointer to fooInstance.bar, I would use:
dojo.lang.hitch(fooInstance, "bar");
Curry takes hitch one step farther, and embeds parameters into the function. If I declare an object with a function like this:
var foo = {
bar: function(arg1, arg2){
alert(arg1 + " is a " + arg2);
}
};
And then I declare a curried function:
var tmp = dojo.lang.curry(foo, “bar�, "Bill");
Then when I call tmp:
   tmp("baker")
It will throw up the alert "Bill is a baker".Dojo provides various functions to Iteration over arrays, rather than the for() loop of traditional procedural languages. A callback function is specified and called for each element of an array.
For example, this will display three alerts, one for each of the specified words:
var colors = ["red", "white", "blue"];
dojo.lang.forEach(colors, alert);
This will return an array of the squares of the first five positive integers:
var integers = [1,2,3,4,5];
var squares = dojo.lang.map(integers, function(x){ return x*x; });
There are also functions:
The iteration functions work on arrays, comma separated lists, or other array-like variables.
Here are a few more areas of dojo's dojo.lang module that deserve special attention.
Much like the prototype $() operator, dojo.byId represents the short form document.getElementById(). This will map a dom node id to a node, but if passed a node, it will return that same node. More importantly, however, is the fact that document.getElementById() is not available when using Dojo and dojo.byId() is the only way to grab nodes.
Others
if(dojo.storage.manager.isInitialized() == false){
dojo.event.connect(dojo.storage.manager,
"loaded", Moxie,
Moxie.initialize);
}else{
dojo.event.connect(dojo, "loaded",
Moxie,
Moxie.initialize);
}It's very important that your code is right for listening
to when storage is ready to be used; this is a common source of bugs. Your event listener must be at the top level and executed while the page is loading. Dojo.Storage
writes thing out into the page during page load
that won't work
afterwards (i.e. it uses document.writes). The above code should be
top-level and not be inside of a nested function; otherwise it won't
execute on page load.var results = dojo.storage.get(key);To save some value:try{
dojo.storage.put(key, value,
saveHandler);
}catch(exp){
alert(exp);
}'Value'
can be a string or even a complicated JS object; we internally JSON
everything before storing it as a flat string, and turn it back into an
object on later retrieval. Dojo.storage actually does some nifty
autodetection if it is working with strings, to avoid the JSON
performance hit of evaling() and bypass this, which gives much better
performance for storing large strings into storage, like XML files or
digital books.var saveHandler = function(status, keyName){
if(status == dojo.storage.PENDING){
// ...
}else if(status == dojo.storage.FAILED){
// ...
}else if(status == dojo.storage.SUCCESS){
// ...
}
}
try{
dojo.storage.put(key, value,
saveHandler);
}catch(exp){
alert(exp);
}There
is more to the code, but those are the important bits in terms of
understanding dojo.storage. It's pretty straightforward to use.Brad Neuberg, bkn3@columbia.edu - Module maintainer and creator
Alex Russel, alex@dojotoolkit.org - Created early version of Dojo Storage
JB Boisseau, jb.boisseau@eutech-ssii.com - Created WHAT WG storage provider for Dojo Storage
Some possibilities:
It's very important that your code is right for listening to when storage is ready to be used. Your event listener must be at the top level and executed while the page is loading. Dojo.Storage writes thing out into the page during page load that won't work afterwards (i.e. it uses document.writes). Study the source code for the Moxie example storage application carefully before asking questions; compare your code and see what is different.
See the FlashStorageProvider for an example. Basic steps:
Find the file that corresponds to your render environment; for example, if your provider will run in the standard web browser environment, then you will want to put your storage provider into src/storage/browser.js. If a file does not exist for your render environment yet, such as a Konfabulator storage provider, then you would create a new file corresponding to your environment, such as src/storage/konfabulator.js or src/storage/xpcom.js. All the providers for a given render environment must be in the same source file. If you have added a new file for a new environment, make sure to add the existence of this environment to src/storage/__package__.js so it is loaded for your new environment.
dojo.inherits(dojo.storage.browser.FlashStorageProvider, dojo.storage);
// register the existence of our storage providers
// choose What WG provider first
dojo.storage.manager.register("dojo.storage.browser.WhatWGStorageProvider",
new dojo.storage.browser.WhatWGStorageProvider());
// if not available, use Flash
dojo.storage.manager.register("dojo.storage.browser.FlashStorageProvider",
new dojo.storage.browser.FlashStorageProvider());
// if still not available, prefer IE method
dojo.storage.manager.register("dojo.storage.browser.InternetExplorerStorageProvider",
new dojo.storage.browser.InternetExplorerStorageProvider());
// now that we are loaded and registered tell the storage manager to initialize
// itself
dojo.storage.manager.initialize();
AMASS, or the Ajax Massive Storage system, is an older, proof-of-concept version of Dojo.Storage written by the same author as Dojo Storage, Brad Neuberg. Dojo.Storage replaces AMASS. It is more full featured, better tested, and is supported across more browsers than AMASS. AMASS will no longer be supported.
For offline, Julien Couvreur discovered the necessary HTTP headers. The core part of it is to use HTTP caching; your site must have the following kinds of HTTP headers on:
LoadModule expires_module modules/mod_expires.so
ExpiresActive On
ExpiresDefault "access plus 1 month"
The
page is now in the browser cache after the first access. In IE and
Firefox, the user has to go to File > Work Offline to work offline,
and then can simply just navigate to the app's URL. In Safari, this is
not necessary, and you can just go to the URL. I like to provide a link
that can be dragged to the toolbar.The Dojo.Storage presentation goes into offline access as well:
http://codinginparadise.org/weblog/2006/05/new-slides-from-ajax-experience-talk.htmlWhy Flash? Flash now has a greater installed base than Internet Explorer; Flash 6+ has a 97.1% penetration across the installed base of PCs, while IE 5, 6, and 7 have 64.7%. Flash is probably one of the most installed pieces of software on the planet.
The
Flash Storage Provider uses Flash as a hidden runtime to extend the
browser, because of its ubiquity and cross-platform/cross-browser
qualities. When browsers get native persistence support, your
dojo.storage applications will continue to run since you write them
against the generic dojo.storage APIs.
The Flash Storage Provider has exactly the same security characteristics as cookie based storage. Data is siloed by domain, so other domains can't access this data. The data is stored on the local disk, usually in the user's home directory.
When working with Dojo Storage and it's Flash Storage Provider you should take exactly the same care you would take with cookies -- i.e., don't leave yourself open to Cross Site Scripting Attacks (XSS).Using the Flash Storage Provider, this breaks down into three questions:
In 2005 sent out a call for someone to layer over the very cool Trim Path Java Script SQL layer over AMASS (an earlier version of Dojo Storage), and someone took up the call and created a prototype linking them together. You should check it out; its very cool. No one has done this with Dojo Storage, but it shouldn't be hard. This might be very useful for offline Ajax apps.
This is a hard problem that is difficult to find general solutions for. I am looking for financial sponsorship to create and open source this. Email me if you are interested.
DojoExternalInterface.initialize();
DojoExternalInterface.addCallback("put",
this, put);
DojoExternalInterface.addCallback("get",
this, get);
DojoExternalInterface.addCallback("remove",
this,
remove);
DojoExternalInterface.loaded();There are three ways to do Flash+JS communication:djConfig = {forceStorageProvider: "dojo.storage.browser.WhatWGStorageProvider"};Two
other djConfig flags exist: disableWhatWGStorage, which can be true or
false and which will disable WHAT WG storage and fall back to using a
different one; and disableFlashStorage, which can be true or false and
which will disable using the Flash Storage Provider. The last flag is
useful if you want to make sure that Flash is never used, for example
if you don't want to have Flash touch your codebase.| Author: | Brad Neuberg |
|---|---|
| Version: | 0.5 |
| Copyright: | Dojo Foundation, 2006 |
| Date: | 2006/11/18 |
There are a number of modules in dojo for UI related tasks
Dojo provides three modules with basic functions on the document tree:
All of the functions above are written to either mask browser incompatibilites, or to provide functionality that wasn't provided within the standard javascript API.
All of the functions can take either a node or an id as their first argument.
One particular set of functions to note are the sizing functions.
An HTML block element has the following format:
+-------------------------+
| margin |
| +---------------------+ |
| | border | |
| | +-----------------+ | |
| | | padding | | |
| | | +-------------+ | | |
| | | | content | | | |
| | | +-------------+ | | |
| | +-|-------------|-+ | |
| +-|-|-------------|-|-+ |
+-|-|-|-------------|-|-|-+
| | | | | | | |
| | | |<- content ->| | | |
| |<------ inner ------>| |
|<-------- outer -------->|
+-------------------------+
There are three sizes associated with this element:
Depending on browser, and mode, asking for the width/height of an element will return different results; sometimes it will give you the content size and sometimes it will give you the inner size. Therefore, it's important to always use dojo's functions for getting/setting the size;
An HTML element can have multiple classes, such as:
Dojo provides functions to handle this class string as an ordered set, so you don't need to do string manipulations to add/remove items from the class list:
There are many more DOM related functions. Please see the reference doc for details.
- dojo.html.addClass(node, className)
- dojo.html.prependClass(node, className)
- dojo.html.removeClass(node, className)
- dojo.html.replaceClass(node, className, oldClassName)
Drag and Drop (DnD)
When Microsoft and Netscape introduced Dynamic HTML (different versions of course), the drag-and-drop demos elicited much applause. You could put a shopping cart on the left and catalog items on the right, and the customer could drag items right to the shopping cart. Neato! It looked just like a client-server application.
Because of the incompabilities between DHMTL, writing a cross-platform drag-and-drop application was difficult. Dojo makes it easy by layering an easy-to-use API over the top.
To drag and drop, you need three things:
- Something to drag, called a DragSource. In Dojo, any HTML element can be one.
- Some place to drop, called a DropTarget. This too can be any HTML element.
- Something to do when the item is dropped
A Simple Example
(Under construction)
Here's a simple application of drag and drop to simulate a kitchen. You have three drop targets - Frying Pan, Pot of Boiling Water, and Oven - and two drag sources - Egg and Chicken Leg. A user can drag the word Egg to the box surrounding Pot of Boiling Water to simulate boiling the egg. You can drag the foods from their initial locations, or from one destination to another, as in dragging the Egg from the Pot of Boiling Water to the Frying Pan.
<script type="text/javascript' src="/path/to/dojo.js"></script> <script type='text/javascript"> dojo.require("dojo.dnd.*"); dojo.require("dojo.event.*"); function initKtichen() { // "pan" matches the id for the <div> tag with id="pan" below // The [] around "dest" are required, for reasons we'll see later new dojo.dnd.HtmlDropTarget(dojo.byId("pan"), ["dest"]); new dojo.dnd.HtmlDropTarget(dojo.byId("pot"), ["dest"]); new dojo.dnd.HtmlDropTarget(dojo.byId("oven"), ["dest"]); // "dest" matches the "dest" in the DropTarget's above. new dojo.dnd.HtmlDragSource(dojo.byId("egg"), "dest"); new dojo.dnd.HtmlDragSource(dojo.byId("leg"), "dest"); } dojo.addOnLoad("initKitchen"); </script> </head> <body> <H1>Food</H1> <div id="egg">Egg</div> <div id="leg">ChickenLeg</div> <H1>Destinations</H1> // This is the pan, the first drop target <div id="pan" style="border:3px soid black;width:200px"> Frying Pan </div> <div id="pot" style="border:3px solid black;width:200px"> Pot of Boiling Water </div> <div id="oven" style="border:3px soid black;width:200px"> Oven </div> <br />(Vegetarians may substitute soy products with no loss of functionality) Running this example, you notice things happen automagically:
- The item moves with your mouse pointer as you drag
- When you drop the item on a drop target, the drop target expands to hold the item
- If you try to drop an item outside of a drop target, the item snaps back to its original position.
- A horizontal line appears where the drop will occur. In this example, you'll see the top border of the drop target become "fatter".
The HTMLDragSource and HTMLDropTarget constructor calls contain two arguments:
The example does a lot with a little bit of code. In the next examples, we'll work on making the user interface discoverable, meaning the user can figure it out with visual cues.
- The component do be dragged or dropped, respectively. dojo.byId() is helpful here, and an easier-to-type shortcut for document.getElementById().
- A destination code to specify which drag sources can do to which drop targets. We'll see that work in LimitingDragAndDropOptions.
Beautification
Let's put those events to good use. When a user drags an object, they'd like to know where it's legal to drop. We can help out by visually indicating a legal drop target. Going forward with our kitchen example, when the user drags a food item to a utensil, we'll make the utensil border red. When they drop, or drag out of that utensil, we'll make it black again.
To accomplish this, the natural events to use are onDragOver and onDragOut. These act a lot like the onMouseOver and onMouseOut attributes of an HTML tag.
function initKitchen(){
dojo.declare("dojo.book.dnd.DestDropTarget",dojo.dnd.HtmlDropTarget,{
onDragOver: function(e) {
// domNode is the drop target we're over
this.domNode.style.borderColor = "red";
dojo.dnd.HtmlDropTarget.prototype.onDragOver.apply(this, arguments);
},
onDragOut: function(e) {
// this.domNode is the drop target we're leaving
this.domNode.style.borderColor = "black";
dojo.dnd.HtmlDropTarget.prototype.onDragOut.apply(this, arguments);
}
});
new dojo.book.dnd.DestDropTarget(dojo.byId("pan"), ["dest"]);
new dojo.book.dnd.DestDropTarget(dojo.byId("pot"), ["dest"]);
new dojo.book.dnd.DestDropTarget(dojo.byId("oven"), ["dest"]);
new dojo.dnd.HtmlDragSource(dojo.byId("egg"), "dest");
new dojo.dnd.HtmlDragSource(dojo.byId("leg"), "dest");
}
Drag and Drop Actions
OK, we have drag sources and drop targets. But without an action, drag and drop isn't very interesting. We want something to happen when the item is dropped.
Because Drag and Drop uses Dojo's event model, you can set up actions with very few lines of code. (If you haven't reviewed the Object Oriented Concepts section, now's a good time.) Here's a simple example, which displays an alert box when the item is dropped.
function initKitchen(){ dojo.declare(dojo.book.dnd.DestDropTarget",dojo.dnd.HtmlDropTarget,{ onDrop: function(e) { alert('Ready to cook!'); // Call the superclass method to do the actual dropping dojo.dnd.HtmlDropTarget.prototype.onDrop.apply(this, arguments); } }); new dojo.book.dnd.DestDropTarget(dojo.byId("pan"), ["dest"); new dojo.book.dnd.DestDropTarget(dojo.byId("pot"), ["dest"]); new dojo.book.dnd.DestDropTarget(dojo.byId("oven", ["dest"]); new dojo.dnd.HtmlDragSource(dojo.byId("egg"), "dest"); new dojo.dnd.HtmlDragSource(dojo.byId("leg"), "dest"); }Notice how the drop target object type is no longer HtmlDropTarget, but your new class DestDropTarget. This is simple inheritance. A DestDropTarget is a more specific kind of HtmlDropTarget. Most of the methods are delegated to the superclass, HtmlDropTarget, but our specific functionality for onDrop is added before the superclass call.
Drop Events
onDrop is the most common event to override for drop targets. But there are others. To connect an action to one of these events, simply specify the method for that event in your dojo.declare call. All events without an action will be handled by HtmlDropTarget.
- onDragOver(e) - called when the user begins dragging a source over this drop target. It is called only once when the drag source "flies over".
- onDragMove(e) - called repeatedly as the drag source is over the drop target. You shouldn't perform any long calculations here.
- onDragOut(e) - the opposite of onDragOver, this is called when the drag source leaves the drop target area without having been dropped. This is called only once.
So the events fire like this:
- For dragging across a drop target without dropping - onDragOver(), onDragMove(), onDragMove(), ... onDragMove(), onDragOut().
- For dragging into a drop target and dropping - onDragOver(), onDragMove(), onDragMove(), ... onDragMove(), onDrop().
Like all event handlers, your code must include one parameter for the event information itself. This object is of type dojo.dnd.DragEvent, because Drag events and Drop events have identical event information. DragEvent contains the following readable fields:
The onDropEvent also has onDropStart and onDropEnd events before and after, accordingly. onDropStart is a good place to verify the drop target is OK, although destinations do the job easier.
- dragObject - the object being dragged.
- dragSource - the dragSource being dragged. Note that dragSource.dragObject is the same as dragObject. (Confusing, but remember the HtmlDragSource constructor takes two parameters: the object and the possible destinations).
- target - the drop target for that drop. Null if the drag source hasn't been dropped yet.
Drag Events
Each of these events also has a DragEvent parameter with event information.
Note that not every drag ends with a drop! The user can drag a source, then leave the browser window entirely and let go of the mouse button. Nothing will be dropped, since a browser doesn't usually interact with other windows. But onDragEnd will be called nonetheless.
- onSelected(e) - called when a drag source is clicked
- onDragStart(e) - called once when dragging begins
- onDragEnd(e) - called once when dragging ends. For drops, this event is called right before onDrop.
Limiting Drag and Drop Options
Using default settings as we have, the user can drag items all over the page. They may have to try several potential drop before stumbling on the right one. You can help them out by limiting their choices.
Disabling Drop Targets for Certain Sources
Continuing from our example, suppose that chicken legs can go into all destinations: the oven, the pot, or the frying pan. But eggs can go only into the pot or pan, not in the oven. (Yes there is such a thing as baked eggs, but you're not Wolfgang Puck!)
That's where the "dest" parameter comes in. The second argument for both HtmlDragSource and HtmlDropTarget, this acts much like the name="..." parameter of radio buttons. They tie different drop sources with a common set of rules.
In the following example, we set up two destination groups: allCookingOptions, and topOfStoveOnly. The HTML portion is exactly like our first example, but we change initKitchen to:
function initKitchen(){
// A pan can be a drop target for any drag source of type topOfStoveOnly or
// allCookingOptions.
new dojo.dnd.HtmlDropTarget(dojo.byId("pan"), ["allCookingOptions","topOfStoveOnly"]);
new dojo.dnd.HtmlDropTarget(dojo.byId("pot"), ["allCookingOptions","topOfStoveOnly"]);
new dojo.dnd.HtmlDropTarget(dojo.byId("oven"), ["allCookingOptions"]);
// An egg can only be dropped on a target of type topOfStoveOnly
new dojo.dnd.HtmlDragSource(dojo.byId("egg"), "topOfStoveOnly");
new dojo.dnd.HtmlDragSource(dojo.byId("leg"), "allCookingOptions");
}You may wonder, "Why the brackets around destinations in a drop target?" Drag sources belong to one and only one group, but drop targets can belong to many groups. That's why drop targets always have an array of destinations signified by [...], even when there's only one group.
Constraining The Drag Area
To make the User Interface more discoverable, you can put a boundary around all the possible drop targets.  This prevents the drag source from leaving the area, even if the mouse is dragged outside. The technique is especially helpful if you have two or more sets of drag and drop areas. For example, if you had a drop area of domestic cities and a drop area of international cities, you could constrain domestic planes (our drag source) to the domestic cities.ÂÂ
To constrain the drag sources, you use the constrain() method. That requires first constructing the HtmlDragSource object, as we have in previous examples. Then you call constrain with the id of the bounding box. Note this can be any HTML object, and the boundaries of that object act as the constraining box. Butis a natural choice for a bounding box.ÂÂExtending our kitchen example, the user will not be able to drag the egg or chicken leg outside an imaginary box containing all three destinations.
function initKitchen(){ ... // Construct drop sources // Keep the egg from leaving the boundaries of the object kitchenDiv var eggSource = new dojo.dnd.HtmlDragSource(dojo.byId("egg"), "dest"); eggSource.constrainTo("kitchenDiv"); // Do the same with the chicken leg legSource = new dojo.dnd.HtmlDragSource(dojo.byId("leg"), "dest"); legSource.constrainTo("kitchenDiv"); } ... <H1>Destinations </H1> <div id="kitchenDiv"> <div id="pan" ...The user cannot drag either the egg or the chicken leg outside of the bounding box, although they can try to drop it (unsuccessfully) between the destinations. Later we'll see how to give more visual cues to the user as to where they can drop.
List Rearrangement
Yes, you can use Drag and Drop outside of the kitchen! One popular application is rearranging lists. You can do this by enabling list items as drag sources and the list itself as a drop target. That seems kind of wierd at first - you do not expect drag sources and drop targets to overlap, much less contain one another. Here, it helps to think of dragging and dropping as a shortcut for cut-and-paste.
In this example you can drag the elements, Jim Hendrix albums, into whatever order you wish.
<html><head> <script type="text/javascript" src="/path/to/dojo.js"></script> <script type="text/javascript"> dojo.require("dojo.dnd.*"); dojo.require("dojo.dnd.event.*"); function initList() { // Loop through all li elements of list, and make them drop targets var dl = dojo.byId("listToRearrange"); var lis = dl.getElementsByTagName("li"); for (var i=0; i<lis.length; i++) new dojo.dnd.HtmlDragSource(lis[i], "dest"); new dojo.dnd.HtmlDropTarget(dl,"dest"); } dojo.addOnLoad(initList); </script> </head> <body> <H1>Jimi Hendrix Albums</H1> <p>Arrange in order of your own preference.</p> <ul id="listToRearrange"> <li>Electric Ladyland</li> <li>Are You Experienced?</li> <li>Axis, Bold as Love</li> </ul> </body> </html>Once this is done, you can use getElementsByTagName to loop through the elements in their new order, then send them by dojo.io or a page submission.
LFX
The dojo.lfx.* module is dojo's animation system. It includes many “canned� effects:In addition, it has a powerful system for chaining together primitives:
- fadeIn, fadeShow, fadeOut, fadeHide,
- wipeIn, wipeOut
- slideTo
- explode, implode
- highlight, unhighlight
// wipe two elements out, one after
// the other, following a 300ms delayvar anim1 = dojo.lfx.wipeOut(�foo�, 300);
var anim2 = dojo.lfx.wipeOut(�bar�, 500);
var composed = dojo.lfx.chain(anim1, anim2);
composed.play(300);// fade out three nodes together, using
// accelerationdojo.lfx.fadeOut(
[�foo�, “bar�, “baz�],
300,
dojo.lfx.easeInOut
).play();
It is important that all users can access applications built using Dojo. People with disabilities access the Web in many manners. Some people rely on the keyboard only to navigate and make selections.Others use custom font and color settings, custom style sheets, screen magnification or screen readers to interact with the computer.With the addition of multimedia to the Web, captioning is important for folks with hearing loss. Those with cognitive disabilities may use a variety of assistive technology to adapt the content to a particular learning style.
In addition, many countries require that Web Content is accessible to all.Section 508 of the Disabilities Act requires the US Government to purchase accessible technology.Australia, Japan, Great Britain and the European Union all have accessibility regulations.Thus, it is important for the Dojo Toolkit to provide a way to build accessible applications.
The main accessibility concern for the Dojo Toolkit is the widget set.The widgets need to be usable by all users in order to create accessible applications using Dojo.The main issues are with color, device independent interactions and providing semantic information about these user interface (UI) components.
Many widgets were initially implemented with support only for mouse interactions and need keyboard support added.In order to create high performance, easily styled and visually appealing widgets, the toolkit makes use of CSS to create a desktop-like look and feel.These widgets may not work when CSS is turned off.
There are several issues with color.Some people have vision problems which prevent them from seeing certain colors or may need a high contrast between foreground and background colors. These people may adjust the operating system colors to meet their needs or use the high contrast settings provided by the operating system.In some cases, when a person can not distinguish the colors in an image, he may turn off images in the browser and rely on the description of the image provided via a text alternative. In HTML the text alternative is provided via the alt attribute of the img element.
In order to create high performance, easily styled and visually appealing widgets, the toolkit makes use of CSS to create a desktop-like look and feel.These widgets may not work in high contrast mode.Selecting high contrast mode in the Windows operating system forces all color and image related CSS information to be turned off in the browser.All positioning information is retained but the colors and background images are turned off.Dojo Widgets that create the visual effects using CSS background-images will not be visible in high contrast mode.In order to accommodate high contrast mode, the widgets need to use real img elements to create the visual look. Images are still displayed when high contrast mode is enabled.However, some users still may not be able to distinguish the image due to the colors used. These users may also disable images in the browser and rely on the text alternative to provide the necessary information.
More info about High Contrast Mode and Widgets
Not all users are able to interact with the computer using a mouse, thus, device independent interaction is important.This means that, at a minimum, keyboard interactions must be supported.The Dojo widgets must not work solely via the mouse.For HTML links and form controls, the keyboard is automatically supported. But, since Dojo is creating custom widgets via DHTML and scripting of elements other than links and form controls, keyboard event handling must be added.Dojo widgets created using technologies other than HTML such as SVG, must also support the keyboard or provide an alternative interface that works with the keyboard.
Accessibility application programming interfaces (APIs) for desktop graphical user interface (GUI) frameworks define a standard contract between an application component and an assistive technology (AT). The information about the type of component and its current state is provided to the AT via the accessibility programming interface. Examples of accessibility APIs are Java Accessibility API , Microsoft Active Accessibility (MSAA), Apple Accessibility for COCOA, and the Gnome Accessibility Toolkit (ATK). In the browser environment, certain HTML elements have well defined roles and states. Examples include lists, links and form elements. The browser communicates the information about these elements and the current state such as checked, unchecked, readonly, disabled, visited, etc. to assistive technology via the accessibility APIs. Now that user interface components are being created via scripted HTML elements such as div and span, the assistive technology needs additional information about the created component and its behaviors.
The W3C Web Accessibility Initiative Accessible Rich Internet Applications (WAI-ARIA) Roadmap provides specifications which describe how to provide this additional information to the assistive technology.These specifications define a set of roles and states which can be added to the created DHTML user interface components.With the addition of this information and support by the browser and assistive technology, a user of AT can get detailed information about the user interface components create for the Web.For example, a tree component will be identified as a tree and each tree item, its level in the tree, expanded and collapsed state and number of children is now available to a screen reader user.The current ARIA specifications are supported in Firefox 1.5 and later versions as well as the by recent versions of the Window-Eyes and JAWS screen readers. Support from other Windows browsers and assistive technology vendors are expected in the future.To provide the most comprehensible accessibility solution for Dojo, the ARIA techniques are being applied to the widgets.
NOTE:As of February, 2007 the High contrast mode accessibility strategy has changed. Dojo will NOT be using background images to convey information. Since Dojo will use real img elements there is not a high contrast issue and separate a11y versions of the widgets will not be necessary. This documentation will be updated when the strategy for creating multiple themes is in place. -Becky
Customizing the Dojo Widgets look and feel is very important, as is performance. The Dojo team uses background images in the creation of the widgets because it allows the look and feel of the widgets to be easily customized by modifying the CSS.In addition, using CSS allows several images to be combined into one file and then the proper subset of the image to displayed using positioning. Thus all of the images for a particular widget element can be retrieved via one HTTP request rather than a separate request for each separate image file.For these reasons, Dojo did not want to require the use of image elements when creating widgets. This presents a problem for users with high contrast mode settings because in this mode, background images are not displayed. The solution is to create an accessible version of each widget which will work in high contrast mode.
The accessible version of each widget will be derived from the original widget.Then, only the necessary methods need to be overridden and the general functionality of the widget is maintained. Dojo already has a mechanism to load a particular widget class based on the technology in use so this has been extended to accessible widgets as well. When high contrast or images mode is detected, the accessible version of a widget will be loaded if it exists.
The accessible version of a widget may use HTML img elements to replace the CSS background images used in the widget, or in some cases, it may fall back to the HTML default version of the control.The checkbox widget uses CSS background widgets to create a graphical look and feel for the Dojo checkbox.Since that graphical look and feel and coloring will be lost in high contrast mode, the solution for the checkbox is to fall back to the default HTML input type=checkbox element in high contrast or images off mode.The dojo.widget.checkbox.a11y class is defined within checkbox.js and is a subclass of dojo.widget.checkbox. It overrides only the necessary methods to implement the accessible version of the checkbox.
The TabContainer widget uses background images to create the close icon on tabs that can be closed.Since there is no HTML alternative for the TabContainer, the accessible version uses img elements in place of the CSS background images.The TabContainer widget uses a TabButton widget to create the tabs.A dojo.widget.a11y.TabButton widget creates the tabs using an img element rather than a background image.The img element will display when the system is in high contrast mode. There is still a possibility that the user may not be able to discern the colors in the close image and will turn images off.With images turned off, the alt attribute of the image element will display and allow the user to properly interpret and interact with the tab.
The main functionality of the TabContainer is maintained in that module.The section of the TabContainer that needs modification to support high contrast and images off mode, the tabs, have been separated out into a separate widget.Thus the minimal adjustments needed to support high contrast and images off mode accessibility are provided in the helper a11y.TabButton class.
One way of providing keyboard support in HTML is to use form and list elements which can accept keyboard focus by default.The user can use the tab key to navigate to these types of elements.The problem is that building sophisticated widgets using these elements in not practical. And, navigating via only the tab key can be very tedious.The solution is to provide full keyboard support within the widgets using additional keystrokes such as the arrow keys to provide more intuitive navigation.
One of the keys to supporting the keyboard is to allow focus to be set to any element.The tabindex attribute can be used to include additional elements in the tab order and to set programmatic focus to them.This was a feature implemented in Internet Explorer that has been extended to Firefox and Mozilla.The following table outlines the use of the tabindex attribute:
| Tabindex Attribute Value | Focusable via mouse or scripting via element.focus() | Tab Navigation |
| not present | Follow default behavior of element (only form controls and anchors receive focus) | Follows default behavior of element |
| zero - tabindex="0" | Yes | In tab order relative to element's position in document |
| positive - tabindex="x" (where x is a positive integer between 1 and 32768) | Yes | tabindex value directly specifies where this element is positioned in the tab order |
| negative - tabindex="-1" | Yes | Not in tab order, author must focus it with element.focus() as result of a key press. |
Adding a tabindex of -1 to an element allows the element to receive focus via JavaScript using the element.focus() method.This is used to allow arrow key navigation to elements.Each element that can be navigated to via arrow keys must have a tabindex of -1 to allow it to receive focus.A keydown event handler can determine the next object to receive focus and call that element's focus() method.In addition, the style of the element may need to be updated in order to show the focus as brower's are inconsistent in displaying focus for items that receive focus programmatically.
In order to assist with key event handling, an onkey event has been added to Dojo to normalize key events. The appropriate key event, either onkeydown or onkeypress, will be used depending upon the browser.The key codes have been normalized as well. See dojo.event.browser class in dojo.event.browser.js.
Device Independent behavior means more than just supporting the keyboard. Where ever possible use the most generic event handler possible. For example, consider a widget where the down arrow key selects an element in the widget.The selection needs to be distinguished with a specific style.Rather than modifying the style of the element when processing the down arrow key event , focus the item from the down arrow key event handler and change the style via a focus event handler. This way, if focus is set from a means other than the keyboard such as a voice input system, the styling is properly set and does not depend solely on keyboard actions.
When implementing keyboard navigation, the ideal solution is to mimic the behavior of the operating system..For example, the right and left arrow keys are used to expand and collapse nodes in a Windows tree control and the up and down arrow keys move between nodes in the control.Unfortunately it is not always possible to mimic the operating system or browser behavior because the widgets may not be able to capture the necessary keys. A group of industry representatives are working to create a style guide to describe the navigation and behaviors of Web widgets. When completed, this Style Guide will be provided to open source and Dojo plans to implement the recommendations.The Style Guide will attempt to normalize the differences between operating systems and provide a generalized solution for Web components
ARIA techniques allow creating sophisticated UI components using scripting which can be identified to assistive technology. In the future, user agents can also make use of this information to provide additional visual clues about components as well. For example, client side validation of a text entry component that was marked using the ARIA invalid attribute could be visually identified by the browser rather than requiring the developer to provide a specific style or text identification on the component.
NOTE: As of February, 2007 the High contrast mode accessibility strategy has changed. Dojo will NOT be using background images to convey information. Since Dojo will use real img elements there is not a high contrast issue and high contrast detection may not be necessary. These a11y apis are very likely to change for the 0.9 release. This documentation will be updated with the final apis are determined and the changes to a11y.js have been completed. -Becky
The dojo.a11y module contains methods to detect high contrast mode and to instruct Dojo to load the accessible versions of widgets (when an accessible version exists).
dojo.a11y.accessible is a private variable that stores the current accessibility state. The value is initialized to null when the a11y module is instantiated. This is used to prevent running the accessibility test more than once during widget instantiation or to force a particular mode. A value of true indicates that accessible versions of the widgets will be loaded. A value of false indicates that the accessible version of widgets will not be loaded. The value of this variable is normally set after running the check for high contrast mode or images off but can also be forced via the dojo.a11y.setAccessible() api.
dojo.a11y.doAccessibleCheck determines whether or not the accessibility check is performed. The default value is true – the check for high contrast mode or images off will be performed for each page load. The dojo.a11y.setCheckAccessible() method is provided to set this variable to false and prevent the accessibility check from being performed. The ability to turn off the check is provided for applications that may provide a mechanism for the user to request the accessible mode for widgets.
dojo.a11y.testAccessible always performs the check for accessibility mode. The check is currently only made for the Internet Explorer and Mozilla browsers as others do not yet support high contrast mode or do not support it in a detectable manner. Additional work is required for operating systems other than Windows. The test is performed by creating a div and assigning it a background-image. A very small image is used for the background image so that the download time is short and performance is not greatly affected. In both IE and Firefox, checking the current / computed style of this div after it has been added to the Document Object Model (DOM) will reveal whether or not the background image is visible. Both browsers will return "none" as the computed value for the background-image when high contrast mode is turned on at the operating system level. In Firefox the value returned is "url(invalid-url:)" when images are turned on. Currently images off mode is not detected in IE but further research is being conducted to find a method for determining images off in IE that does not require waiting for the image's onload or onerror events to fire.
dojo.a11y.checkAccessible will perform the accessibility check if the check has not already been performed and testing for accessibility has not been turned off via dojo.a11y.setCheckAccessible(false).
dojo.a11y.setCheckAccessible provides a mechanism to turn off checking for high contrast mode or images off. A value of true turns on accessibility checking and a value of false turns checking off. Generally an application should not turn off checking for the need to display accessible versions of the widgets as a developer can never be certain that the audience does not contain people with disabilities who rely on accessibility accommodations. However, an application may provide another mechanism for the user to request the accessible versions of the widgets and set accessible mode via the dojo.a11y.setAccessible method. In this case the accessibility check would not be required.
dojo.a11y.setAccessibleMode is used to perform the check for accessibility if it has not yet been performed and based on the results set Dojo to load the accessible version of the widgets if necessary. This method is called from the dojo.widget.buildWidgetFromParseTree method. For performance reasons this method checks whether or not the accessibility mode has been set before making any additional calls to avoid performing the accessibility test for each widget.
dojo.a11y.setAccessible is used to force accessibiliy mode on or off and prevent a check for accessibility. It sets the value of dojo.a11y.accessible variable to the value of the boolean parameter passed into the setAccessible method. Passing in true will force accessible mode on and a value of false will force it off.
dom.widget.wainames encapsulates the type of ARIA information being provided, roles and states, which are represented via waiRole and waiState respectively. The wai prefix is used to represent the W3C Web Accessibility Initiative which is hosting the ARIA specification.
The dom.widget.wai module is provided to normalize setting the roles and states. The ARIA techniques are designed to be used with XHTML via namespaces. Since a content-type of application/xhtml+xml is required to fully support namespaces an alternate solution is needed for the most commonly supported content-type of text/html. The roles and states can be manipulated using the DOM namespace apis: getAttributeNS, setAttributeNS, and removeAttributeNS. In browsers which do not support the namespace apis, the generic attribute apis, getAttribute, setAttribute, removeAttribute, are used and namespaces are simulated.
The dom.widget.wai module provides the necessary mapping of namespace information and attribute apis for each of the dom.widget.wainames. It contains two submodules, waiRole and waiState, each with the following variables.
The dom.widget.wai methods getAttr(), setAttr(), and removeAttr() are wrappers to the appropriate attribute apis for the browser in use. These apis are called with the following parameters:
The following will set a role of treeitem onto a DOM node:
dijit.util.wai.setAttr(nodeObj, "waiRole", "role", "treeitem");
This example sets the state of the treeitem to expanded:
dijit.util.wai.setAttr(nodeObj, "waiState", "expanded","true");
The role and state can also be set via the widget template using the waiRole or waiState prefix. Setting the role in the template is that same as setting it via scripting – the dijit.util.wai.setAttr() method will be called during widget instantiation. Simply add the waiRole="actualrole" or waiState="state-value" parameters into the template markup for the element. The element will be passed as the nodeObj into the dijit.util.wai.setaAttr() method. The state is specified as a state name and value pair, the state is separated from the value using the hyphen character (-): state-value. The state becomes the attribute name when dijit.util.wai.setAttr() is called. This mechanism is useful when templates are used to create the objects requiring the role value or when the state is known at creation time.
Here is an example of setting the role in the tab container template. The containerNode is given the "tabpanel" role.
<div id="${this.widgetId}" class="dojoTabContainer">
<div dojoAttachPoint="tablistNode"></div>
<div class="dojoTabPaneWrapper" dojoAttachPoint="containerNode"
dojoAttachEvent="onKey" waiRole="tabpanel"></div>
</div>
The role or state can also be specified via variables. This example shows an excerpt from the slider template which sets the tabindex, role and state for the slider component. Note that currently only one waiRole and waiState value is allowed per element (trac #2163).
<div class="sliderMain" dojoAttachPoint="focusNode" waiRole="slider"
waiState="valuenow-${this.initialValue}" dojoAttachEvent="onmousedown:_setFocus;
onkey:_handleKeyEvents; onkeyup:_buttonReleased; onmouseup:_buttonReleased;
onmousewheel:_mouseWheeled;"
tabindex="0" cols=3 cellpadding=0 cellspacing=0 style="">This is a fairly high level description of how to support device independent interaction. An understanding of general Dojo widget development and event handling within scripting and the Dojo Toolkit is assumed and not described.
Device Independent behavior means more than just supporting the keyboard. Where ever possible use the most generic event handler available. For example, consider a widget where the down arrow key selects an element in the widget. The selection needs to be distinguished with a specific style. Rather than modifying the style of the element when processing the down arrow key event, focus the item from the down arrow key event handler and change the style via a focus event handler. This way, if focus is set from a means other than the keyboard such as a voice input system, the styling is properly set and does not depend solely on keyboard actions.
Determining Key Behavior
When implementing keyboard navigation, the ideal solution is to mimic the behavior of the operating system. For example, the right and left arrow keys are used to expand and collapse nodes in a Windows tree control and the up and down arrow keys move between nodes in the control. Unfortunately it is not always possible to mimic the operating system or browser behavior because the widgets may not be able to capture the necessary keys. A group of industry representatives are working to create a style guide to describe the navigation and behaviors of Web widgets. When completed, this Style Guide will be provided to open source and Dojo plans to implement the recommendations. The Style Guide will attempt to normalize the differences between operating systems and provide a generalized solution for Web components
Within all widgets interaction with both the keyboard and the mouse is important. Users may switch between using the mouse and using the keyboard at any time. A widget author can not assume only keyboard or only mouse interaction. Thus, the widget component will generally need to store information about the current item with focus.
This can also be useful when the keyboard event handler is placed on an owning object in the component hierarchy rather than the actual element generating the event, for example on the table element rather than on each td element. Even though the event handler provides information on exactly what element generated the event, it is often necessary or easier to use the stored point of reference
In order to support both the mouse and the keyboard a component will usually have both onclick and onkey events. The onclick handler must include steps to update
the point of regard so that any keyboard actions after the onclick will continue to work. In
addition, the point of regard is often needed in order to update the style on
the element losing focus before updating the new item irregardless or whether
the mouse or the keyboard generated the event that results in a focus change.
Trapping Key Events
When implementing keyboard navigation, first determine where in the hierarchy to trap the key events. It is generally best to trap the key events at as high a level as possible and use the event object to determine that actual source of the event and perform the necessary action. This method prevents having to add a key handler to each individual element thus conserving the amount of markup to be generated. However, there may be cases where the event needs to be trapped at the level of each individual element. The actual source of the event is needed in order to determine how to process the keystroke received.
Once the component handles an event, it will usually stop that event from being propagated to other elements. For example, if the down arrow key is captured and moves focus to the next item in a tree control, the event should not propagate up to the browser where it might be interpreted as a command to scroll the page. Use the dojo.event.browser.stopEvent(event) method to stop the event.
In order to assist with key event handling, an onkey event has been added to Dojo to normalize key events. The appropriate key event, either onkeydown or onkeypress, will be used depending upon the browser. The key codes have been normalized as well. See dojo.event.browser class in dojo.event.browser.js.
Add the dojo onKey event into the widget template or via scripting using one of the dojo event connection apis.
Example using dojo.event.connect to connect the object represented by the node variable to the onKey handler function in the current object:
dojo.event.connect(node, "onkey", this, "onKey");
Example from tab container template to demonstrate adding the onkey handler and waiRole within the template:
<div id="${this.widgetId}" class="dojoTabContainer">
<div dojoAttachPoint="tablistNode"></div>
<div class="dojoTabPaneWrapper" dojoAttachPoint="containerNode"
dojoAttachEvent="onKey" waiRole="tabpanel"></div>
</div>
Tabindex and Focus
When navigating via the keyboard is it essential that the element that is navigated to receives focus. The focus should NOT be simulated via CSS - call the focus() method on the element. Styling can be used to enhance the visual focus or selection but should not replace actually setting focus on an element. A screen reader will only speak information about the element when it receives focus. Screen magnifiers rely on focus to move the zoomed viewport on the screen.
Use the tabindex value to provide direct or programmatic keyboard focus to an element. The tabindex attribute can be used to include additional elements in the tab order and to set programmatic focus to them.This was a feature implemented in Internet Explorer that has been extended to Firefox and Mozilla.The following table outlines the use of the tabindex attribute:
| Tabindex Attribute Value | Focusable via mouse or scripting via element.focus() | Tab Navigation |
| not present | Follow default behavior of element (only form controls and anchors receive focus) | Follows default behavior of element |
| zero - tabindex="0" | Yes | In tab order relative to element's position in document |
| positive - tabindex="x" (where x is a positive integer between 1 and 32768) | Yes | tabindex value directly specifies where this element is positioned in the tab order |
| negative - tabindex="-1" | Yes | Not in tab order, author must focus it with element.focus() as result of a key press. |
When adding support for keyboard navigation, consider the widget as a component. The tab key can be used to navigate from component to component on a page and then the arrow and other keys should be used to navigate within the component. Only one element in a given component should have a tabindex equal to zero at any one time. This allows the user to navigate into and set focus within in the component using the tab key. Then, trap the onkey events and use the arrow keys to navigate within the elements of the component. All of the elements within the component which can receive focus must have a tabindex equal to -1. When an element is programmatically given focus, its tabindex value is changed from -1 to 0 and the tabindex of the previous element with focus will be changed from 0 to -1. This will insure that only one element within the component is in the tab order of the page and that the element with tabindex = 0 is the most recently focused element in the component. For example, when creating a tree control, each tree item is represented by an element. The first tree item in the control will be given a tabindex of 0. All of the other elements which represent tree items and can receive focus programmatically will have a tabindex value of -1. An onkey handler will trap the keyboard events for the tree control. When a tree item element is given focus via element.focus(), the element’s tabindex will be changed from -1 to 0. and it will be put into the tab order. Now if the user moves focus out of the tree control (either via a mouse click or by tabbing to the next component on the page), when the user sets focus back into the tree control using the tab key, the last focused tree item, which was given a tabindex of 0, will receive focus.
The Accessible Rich Internet Applications Roadmap is being developed by the W3C Web Accessibility Initiative (WAI) Protocols and Formats working group. The group is creating specifications for role and state information which can be added to markup to provide semantic information about user interface components.The browsers will translate this role and state information into the accessibility api for the platform in use.Currently Firefox 1.5 and later supports this additional semantic information on the Windows platform where it converts the information into the Microsoft Active Accessibility (MSAA) api.When recent versions of the Window-Eyes and JAWS screen readers are used with Firefox, this additional information is spoken to the user.
Some HTML elements such as links and form elements have well defined roles and behaviors. Interactive controls created from generic elements can now also be identified with roles and states.When an element receives focus the role and state information provided by the developer will be made available to assistive technologies. For example, as a screen reader traverses through a Dojo Tree control using the arrow keys, as each tree item receives focus the title of the tree item will be spoken as well as its expanded or collapsed state if it has children. Likewise, a Dojo checkbox created using <div>and <pan> tags can be identified as a checkbox and its checked or unchecked state can be reported. When creating a new Dojo widget, the role of the widget must be identifed and the state of the widget must be set and updated as it changes.
As of January, 2007, the ARIA specifications are still under development; however a significant portion of the specification has been implemented in Firefox 1.5 and 2.0.
The public drafts of the specifications can be found at http://www.w3.org/wai/pf.
Accessible DHML in the Mozilla Developer Center provides additional information about using the ARIA specifications and includes a table of roles and states supported in current versions of Firefox.
Assigning Roles
Use the tabindex to provide keyboard focus or to allow programmatic focus to an object. By adding a tabindex to an element, the element will now be included in the accessibility hierarchy of the Firefox browser.Information about elements in the accessibility hierarchy will be provided to assistive technologies. If you use a tabindex attribute on a DIV, SPAN, IMG or any element which has no natural role of its own then you need to provide a role. Any element that can receive focus must have a role, either implied, such as input elements and anchors, or specified via a role attribute. For things with an implied role such as input fields and anchors, you can use tabindex=-1 to remove them from the tab order. You can also specify a different role for elements which already have an implied role.
The role and states are added to Dojo widgets within the widget template or via the dojo.widget.wai APIs as described in the Dojo Accessibility Strategy section.
Providing Hierarchical Information
In order for Firefox to determine the correct parent child relationships between objects, and to communicate this via an accessibility API to assistive technologies, it is best to create components in a hierarchical fashion. For example, when creating a menubar it is best to have the components that make up the menus and menuitems of the menubar be children of the menubar. Likewise, menuitems should be children of the owning menus. This hierarchy allows Firefox to provide menu information to the assistive technologies, and for a screen reader to speak more information about the menu such as, menu open, File, item 1 of X when the user opens a menu. Here is a simple pseudo code example demonstrating a hierarchical layout of elements for a menu control. This exmaple only shows the addition of role attributes and does not represent a complete menu widget.(Note: Attributes are not quoted in pseudo-code examples to help improve the readability):
<div role=menubar>
<div role=menuitem>A</div>
<div role=menu><divole=menuitem>A.1</div>
<div role=menuitem<A.2</div>
</div>
<div role=menuitem>B</div>
<div role=menu>
<div role=menuitem>B.2</div>
<div role=menuitem>B.2.1</div> </div>
</div>
It may not always be practical to create items via HTML in a hierarchical fashion. In that case the group role can help to associate the items properly. This is illustrated in the following simple pseudo code example of a tree hierarchy.
<div role=tree >
<div role=treeitem>Top>/div>
<div role=group>
<div role=treeitem>1>div>
<div role=group>
<div role=treeitem>1.1 </div>
<div role=treeitem<1.2>/div>
<div role=treeitem>1.3</div>
<div role=group>
<div role=treeitem>1.3.1<div>
<div role=treeitem>1.3.2>/div>
<div role=treeitem>1.3.3</div>
<div role=treeitem>1.3.4</div>
</div>
<div role=treeitem>1.4</div>
</div>
<div role=treeitem>2>/div>
<div role=treeitem>3</div>
<div role=group>
<div role=treeitem>3.1</div>
<div role=treeitem>3.2</div>
>/div>
</div>
</div>
The tree items at the same level in the hierarchy are grouped together within a DIV element identified with role=group. With this organization, the assistive technologies can be provided with the information about what level and item number a particular treeitem represents. For example, in the above tree example, with focus on item 1.3.3 a screen reader might speak, "one dot three dot three item three of four, level four" or something similar.
Other items included in the hierarchy may not be essential to the component. These items can be marked with a role of presentation to eliminate them from consideration when determining information about the component.
Using the Presentation Role
While it is preferable to use CSS for layout, tables are still used to quickly and easily arrange elements on a page. This is especially true of existing widgets which were originally created to work in older browsers. Putting information in tables can easily confuse the hierarchy of the component. If tables must be used, they can be marked with a role of presentation to eliminate them from the hierarchy. Here is a pseudo code example where the presentation role was used on tables within a tree component:
<div role=tree>
<table role=presentation>
<tr><td><div role=treeitem>Top</div>&;lt;/tdgt;</tr>
</table>
<div role=group>
<table role=presentation>
<tr><td><span role=treeitem>1</span></td></tr>
</table>
<div role=group>
<table role=presentation>
<tr><td><span role=treeitem>1.1</span&;gt;</td></tr>
</table>
<table role=presentation>
<tr><td><span role=treeitem>1.2</span></td></tr>
</table>
</div>
<table role=presentation>
<tr><td><span role=treeitem>2</span></td></tr>
</table>
</div>
</div>
Since the table is only used for layout it is identified with a role of presentation to remove if from the accessibility hierarchy so that information about the table is not provided to assistive technology.
Other elements may need to be removed from the accessibility hierarchy as well. For example, when creating a DHTML checkbox, an image may be contained within a span element that is marked with a role of checkbox and an appropriate state of checked equals true or false. The image which represents the checkbox is contained within the span and should not contain any alt text since the role and state are managed by the surrounding span. Images are considered important elements and are normally included with the accessibility hierarchy of the browser. In order to ignore this image in the accessibility hierarchy, it is marked with a role of presentation:

Assigning States
In addition to identifying the role of a widget, the state of the widget must be identified and updated. The initial state can be set within the widget template or via scripting when the widget is created. As the state changes during user interaction with the widget, the state must be updated using the dijit.util.wai apis:
dijit.util.wai.setAttr(/*DomNode*/node, /*String*/ ns, /*String*/ attr, /*String|Boolean*/value) dijit.util.wai.getAttr(/*DomNode*/ node, /*String*/ ns, /*String|Boolean*/ attr) dijit.util.wai.removeAttr(/*DomNode*/ node, /*String*/ ns, /*String|Boolean*/ attr)
The ns value passed into these function is either "waiState" or "waiRole". The dijit.util.wai functions above are wrappers to the DOM apis to set, get and remove attributes. In browsers where namespaces are supported the setAttributeNS, getAttributeNS, and removeAttributeNS, apis are called. In other browsers the setAttribute, getAttribute and removeAttribute apis are called and the namespace is simulated. The namespace information is stored in the dijit.util.wai class.
It is important to update the state information as it changes so assistive technology users can be made aware of the change. For example, when a treeitem is expanded, the state for the element that has been assigned role=treeitem, must be set to expanded=true. Likewise, when a treeitem is collapsed, the state for the element with the role=treeitem must be updated to expanded=false. Be aware that some of the boolean states imply more than just a dual state. For the state attributes checked, selected and expanded a value of false indicates that the widget is capable of being checked, selected or expanded while no attribute indicates that the element is not capable of that state. For example, a tree node with children will have either a state of expanded=true or expanded=false depending upon whether the child nodes are visible or not. An end node, with no children will have no expanded state value set.
Generally only items which have a role can have a state value. The role may be explicitly set by the author such as a treeitem or may be implicitly defined such as a form element or link. Items which have been added into the accessibility hierarchy via a tabindex attribute may also have states such as desribedby or labelled by.
NOTE:As of February, 2007 the High contrast mode accessibility strategy has changed. Dojo will NOT be using background images to convey information. Since Dojo will use real img elements there is not a high contrast issue and separate a11y versions of the widgets will not be necessary. For now, when creating new widgets do NOT use CSS backgound images to convey information - use real img elements. This documentation will be updated when the strategy for creating multiple themes is in place. -Becky
All widgets must be visible and work when the operating system is set to high contrast mode. In this mode, colors and background images set via CSS are turned off. The CSS positioning attributes are still used but any color information is overridden by the colors selected for high contrast mode. The most common high contrast modes are black on white or white on black but any color combination can be set by the user. In addition to high contrast mode, the widgets should generally be usable with the display of images turned off in the browser. When images are turned off, the alt attribute for any images in the widget should provide the same information. There may be cases, such as an analog clock, where the alt attribute information can not substitute for the actual image. In these cases there should be an alternative method of obtaining the same information. In the case of an analog clock, a text display of the time would be appropriate, since it conveys that same information, the current time.
To avoid problems with high contrast mode it is best to create HTML widgets using the img element rather than relying on CSS background images. Since the use of img elements can have a negative affect on performance, many developers prefer to use background images even though they do not display in high contrast mode. In this case, an accessible version of the widget must be created that will work in high contrast mode.
The widget author must create an additional widget that derives from the original widget to support accessibility. This widget will be named with the a11y prefix. If the original widget name is dojo.widget.MyCoolWidget, the accessible version will be named, dojo.widget.a11y.MyCoolWidget. When using namespaces other than dojo, the same naming convention must be followed of appending a11y to the actual widget name. For example, the accessible version of mynamespace.MyCoolWidget becomes mynamespace.a11y.MyCoolWidget. The preferred location for this new class is within the same file as the original widget. This will make it easier to keep the two files in sync. Although, the reason for extending the original widget class to create the accessible version is to reduce the need to make changes in two places. The majority of the behavior and logic for the widget should remain in the original class. All of the keyboard support and role and state information belong in the original widget. The only modifications for the accessible version should be to support high contrast and images off mode.
The appropriate version of the widget will be loaded and used based on a check for accessibility that is performed in dojo.widget.buildWidgetFromParseTree():
// test for accessibility mode
dojo.a11y.setAccessibleMode();
If a check has not already been performed, dojo.a11y.setAccessibleMode will test to determine if high contrast mode is set or images are turned off in the browser. This test is only performed once and the results cached to improve performance. When images are turned off or high contrast mode is enabled, the dojo.a11y.accessible variable is set to true. In addition, the a11y prefix is added to the beginning of the dojo.render.html.prefixes array. This will cause dojo to load the a11y version of a widget if it exists rather than the default version. This is the same mechanism that is used to load an svg version of a widget if the browser supports svg and an svg version of the widget exists.
If the Dojo widget has an HTML equivalent, such button or checkbox, the accessible version can use the basic HTML type. This strategy will likely involving creating a new template for the accessible version of the widget. The new template name should include "a11" in the name to distinguish it from the standard template. For example the templates for the checkbox widget are named Checkbox.html and CheckboxA11y.html. The accessible version of the widget will refer to the accessible template via the templatePath variable.
If there is not an equivalent HTML element, the accessible version of the widget can replace the use of CSS background images with img elements. When using image elements the look and feel of the widget can not easily be changed since changes to the image source are likely to occur within the scripting code and can no longer be changed via style sheets. Since the purpose of the accessible widget is to support high contrast mode and high contrast mode does not support a color theme, the image set used is not critical. Certainly, there are some high contrast uses who may not be able to interpret the images due to the colors used within the images. But, there is no universal color scheme that can be used in the images that will suit all users. Users which can not interpret the images due to color or size will turn the images off and use the alt text. Thus, it is essential that alternative text is provided for all images using the alt attribute of the img elements.
Currently the Windows operating system provides the most resources and functionality for testing for accessibility in more than one browser.Also, the majority of assistive technologies run under the Windows operating system.Test with both Firefox and Internet Explorer on at least the Windows operating system to assure at least a minimal level of accessibility.
All widgets must be tested for use with the keyboard only.The most stringent way to test for keyboard support is to remove the mouse from the system and interact with the widget. This assures that only the keyboard can be used for navigation and interaction. Test that all functionality of the widget can be accomplished using the keyboard only.The functionality does not necessarily have to be performed in exactly the same manner as with the mouse. For example, it is preferred that a slider can be adjusted by dragging with the mouse or focusing the slider and adjusting the value using the arrow keys or plus and minus characters on the keyboard. But if the value and position of a slider can be adjusted by entering a new value in an associated text field, the slider is keyboard accessible.Drag and drop operations are another example of behavior which may need to be supported via an alternative mechanism such as a menu bar or context menu.
After determining that the widget is accessible using only the keyboard, test using both the keyboard and the mouse.There is no guarantee that a user will interact only using the keyboard or only using the mouse.Make certain that both types of interaction can be used within the widget and that the focus and styles are updated appropriately.
Since not all browsers support adding the tabindex to elements to make them focusable and thus keyboard accessible, it may not be possible to provide full keyboard support in all instances.As of January 2007, Opera and Safari do not support adding tabindex to additional elements.
Resources for Navigating via the keyboard
It is important that when an element is clicked on with the mouse or navigated to via the keyboard that the element receives actual focus.Focus must not be simulated via styles since assistive technology relies on the focus event to inform the user about the element and the role and state.Focus can be visually tested since browsers will indicate focus, however the focus border may be difficult to see or have been modified with a different style.
The Microsoft Inspect Objects application from the Microsoft Active Accessibility SDK can be used on Windows to test for focus and also to verify the correct ARIA role and state has been set on the element. Test using Firefox version 1.5 or later since Firefox has the most comprehensive support for the ARIA role and state information.
Download Inspect32 from Active Accessibility 2.0 SDK Tools.Basic information on using the tool is provided at Using Inspect Objects.
Run Inspect Objects and test the widget for focus and role and state information. Turn on Highlight Focus tracking via the Inspect Objects Options menu. This mode will display a yellow rectangle around the currently focused element.Use this rectangle to verify that the expected element is actually receiving focus. The Show Information Tooltip option may also be helpful.As you set focus to an item, Inspect Objects will display information about the role and state of the object.You can verify that the object receives focus and that the role and state information is as expected.
Figure 1 is a picture of the Dojo checkbox with Inspect Objects running and the Show Information Tooltip and Turn On Highlight Focus options set.With focus on a checked Dojo checkbox, note the focus rectangle and role and state information displayed within the tooltip. The role is "checkbox" and the state is "focused, checked, marqueed, focusable". This verifies that the role of checkbox has been properly set in the Dojo checkbox code. The state information verifies that the element is focused and the checked state is set. With focus on an unchecked checkbox the state would display, "focused, marqueed, focusable", since the checked state is not set. Note that when the state of an element changes, focus must be removed from the element and then returned in order for Inspect Objects to update the information. Or, rather than change focus use the Inspect Objects Action Refresh command to update the information about the currently focused object.

Figure 1
For best results, testing with a screen reader is recommended. This section provides a brief introduction to using screen readers. Currently Window-Eyes 5.5 and JAWS 7.10 or later versions of both running with Firefox 1.5 or later will recognize ARIA role and state information. Unfortunately it takes some time and experience to become proficient using a screen reader and these programs are expensive. There are demo versions available which will run for a limited amount of time (usually 30 minutes) before a reboot of the system is required). But, note that often the demo versions are not to be used for commercial purposes.
When testing with a screen reader be careful not to use the mouse when navigating through the widget. Often a tester will miss problems with keyboard navigation or focus by using the mouse to initiate or force interaction within a widget. Not all screen reader users are blind – some may have low vision or use the screen reader to assist with cognitive disabilities, but, in order to catch all problems it is important to interact with the widget in the same manner as someone with no vision.
Screen readers work by storing the contents of the entire page into a virtual buffer. The user can move through this buffer and have the contents spoken by the screen reader. There are many mechanisms to move through the virtual buffer, by character, word, line, or from object to object. Both JAWS and Window-Eyes have different terminology for this buffer.Window-Eyes refers to it as "browse mode" and JAWS as "forms mode&quo; or via the navigation mode, "virtual PC cursor on/off". When interacting with a Web page, the screen reader can no longer rely on the virtual buffer. In Window-Eyes the user must turn browse mode off to interact with form elements and interactive widgets. In JAWS the user must enter forms mode on or virtual PC cursor mode off tointeract with form controls and interactive widgets.
When testing fully keyboard accessible Dojo widgets the screen readers must be in the "interactive mode" to announce the role and state of an element as the element receives focus. In Window-Eyes this is "browse mode off" which is toggled using the ctrl-shift-a keys.< In JAWS this is "forms mode on" which is turned on by pressing enter on a focusable element and is turned off by pressing the numpad plus key. You can also turn off the virtual PC cursor in JAWS. This setting is toggled via the ins–z key combination.
If the role and state information have been applied correctly the screen reader will hear information about the role and stated of widget elements as well as information about child objects.Figure 2 is a simple tree control with three main nodes with titles Node1 , Node 2, and Node 3. Node 1 has no children. Node 2 is expanded and has two childe nodes titled Node 2.1 and Node 2.2. Node 3 has children but is not expanded. Here is a brief example of what is spoken by Window-Eyes with focus on the Node 2 tree item within the tree control displayed in figure 2, "Node 2 expanded two items, 2 of 3"

Figure 2
The user knows which node currently has focus, Node 2; that the state of the node is expanded, it has two children and that Node 2 is the second node in a group of three nodes at the same level in the tree hierarchy. This type of information is critical for screen reader users to interact and navigate with complex widgets. The title of the Node is spoken when it receives focus. Because the tree control developer has properly set the state of Node 2 to expanded=true, that information is spoken by the screen reader. All of the nodes in the tree control have been properly organized into hierarchies and identified using the role="group" so the browser is able to provide the screen reader with information about the number of children and the position of the current node within its siblings.
When testing widgets with a screen reader interact with each element and verify that the proper role and state and grouping information is spoken by the screen reader and all functionality is accessible via only the keyboard. In addition, make certain that elements which do not provide information to the screen reader user are marked with a role="presentation" so they are ignored. In Figure 2, each tree item node which has children is preceded by an image of a plus or minus to indicate the expanded state of the item. This plus or minus image can not receive focus and it does not need focus since the keyboard user can expand and collapse the tree item using the arrow keys and the screen reader will speak the expanded or collapsed information based on the state value.
See the videos linked from Adventures in Accessibility - What is a screen reader? for more in depth information about screen readers.
Windows comes configured with default high contrast mode settings. Turn on high contrast mode in Windows XP via the Accessibility Options dialog available from the Control Panel. From the Diplay panel check the high contrast checkbox. Press the settings button to modify the display colors and font sizes. Checking the Use shortcut checkbox from the settings dialog allows toggling high contrast mode on and off using the shift-alt-printscreen key combination. Press OK to confirm the settings and then OK again to close the dialog and turn on high contrast mode. Note, putting your system in high contrast mode will likely rearrange the desktop icons on the system due to the changes in font size.
After turning on high contrast mode, test the widget in Firefox or Internet Explorer. If the widget test page was already loaded in the browser, you may need to refresh the page for the high contrast mode to take affect. Verify that all components within the widget are visible. Any components or visual effects which are improperly created via background images or color will no longer be visible in high contrast mode. Verify that the user is able to determine where current focus is within the widget. If focus is not visually evident the widget developer may have used a background color change to indicate focus (or the browser provided focus rectangle may be hard to distinguish). Fix this by using a different styled border or other mechanism to indicate focus if the browser differentiation is not sufficient. When testing in high contrast mode it is fairly evident where the problems occur since any visual effects which rely on color or images will no longer be visible.
In addition to testing for support of high contrast mode, the widgets should also work with images are turned off in the browser. Test in Firefox by turning images off via the Tools Options dialog. Note that only images loaded via http will no longer display. Changing this settig in Firefox does not prevent loading of images from the file system. Test in Internet Explorer by turning off images in the Advanced tab of the Tools Internet Options dialog.
With images turned off the widgets should still be usable. Since no images are loaded, bythe browser the alt attribute of the image elements will be displayed. Verify that the alt attribute provides sufficient information for the user to interact with the widget.
Localization is driven by a locale, a short string, defined by the host environment, which conforms to RFC 3066 used in the HTML specification. It consists of short identifiers, typically two characters long which are case-insensitive. Note that Dojo uses dash separators, not underscores like Java (e.g. "en-us", not "en_US"). Typically country codes are used in the optional second identifier, and additional variants may be specified. For example, Japanese is "ja"; Japanese in Japan is "ja-jp". Notice that the lower case is intentional -- while Dojo will often convert all locales to lowercase to normalize them, it is the lowercase that must be used when defining your resources.
The locale in the browser is typically set during install and is not easily configurable. Note that this is not the same locale in the preferences dialog which can be used to accompany HTTP requests; there is unfortunately no way to access that locale from the client without a server round-trip.
The dojo.locale property is used to specify the locale of the host environment.dojo.requireLocalization() is used to declare usage of these resources and load them in the same way that dojo.requires() pulls in Javascript packages. An optional locale argument can specify a particular translation to load; otherwise the one best matching the user agent's locale will be used. If djConfig.extraLocale is set, the localizations in that list will be loaded also. A "root" bundle is provided in the case where the requested localization or less-specific variants are not available.
Use dojo.i18n.getLocalization() to get the object (hash) representing the resources. An optional locale may be specified, otherwise the user's environment will specify the locale and the best match loaded by the "requireLocalization" step will be used. The localized values will be available as properties on the returned object. For example:
dojo.require("dojo.i18n.common");
dojo.requireLocalization("dojo.widget", "validate", "ja"); var validate = dojo.i18n.getLocalization("dojo.widget", "validate", "ja");dojo.debug(validate.invalidMessage);
The example above will display a invalidMessage message from the validate bundle in package dojo.widget.
DEBUG: * 入力��データ�該当�るも���り��ん。
All localized messages and localized content are provided in a "nls" directory (short for native language support) under the specific package. The name of the file containg the localized resources is specified as the second argument. In the example above the default messages would be in a file called src/widget/nls/validate.js, and the Japanese translation would be located at src/widget/nls/ja/validate.js.
dojo.require("dojo.date.format");
var d = new Date(2006,9,29,4,30);
dojo.date.format(d, {selector:'dateOnly'});
=> "Monday, October 30, 2006"
dojo.date.format(d, {selector:'dateOnly', locale:'zh-cn'})
=> "2006年10月30日星期一"
Other options may be specified to use a different format "length" -- a choice of "short", "medium", "long", or "full" -- or to print only the time portion of the Date object:
dojo.date.format(d, {selector:'dateOnly', formatLength:'short'});
=> "10/30/06"
dojo.date.format(d, {selector:'timeOnly', formatLength:'long', locale:'zh-cn'})
=> "上�04时30分00秒"
Also, it is possible to reverse the process and parse String objects into Dates. Running in a Dutch locale like "nl-nl", the following would produce a valid Date object:
dojo.date.parse("maandag 30 oktober 2006");
Special patterns may be specified, or additional resource bundles may be used to provide sets of localizations for custom formats. The patterns used
follow the specification and are similar to those used by the Java
dateformat class (e.g. MMddyyyy).
dojo.i18n.number is EXPERIMENTAL. The code is not complete, may not work, and APIs are likely to change.
The formatting of numbers, such as the separator and the handling of decimal places, differs among locales. Dojo will provide the facilities to properly format numbers on a localized basis.
dojo.i18n.currency is EXPERIMENTAL. The code is not complete, may not work, and APIs are likely to change.
To use these APIs to format a currency in a locale spec
manner with the correct number of decimal places and the correct
currency symbols. The currency package contains the necessary APIs to
format currencies.
dojo.require("dojo.i18n.currency.common");The following code will be used to format the number 1234.567.dojo.i18n.currency.format("1234.567", "USD");
dojo.debug(dojo.i18n.currency.format("1234.567", "USD"));
Will result in the following:
DEBUG: USD: $1,234.57The following example shows how to display currency in Euros.
dojo.debug(dojo.i18n.currency.format("1234.567", "EUR"));DEBUG: EUR: € 1,234.57
Some languages, mostly middle-eastern in origin, have text flow from the right to the left (e.g. Hebrew and Arabic) Again, the web browser generally takes care of this for us, provided the appropriate HTML attributes are set. However, sometimes the logic of an application or widget must change to accomodate bidi. For example, menu widgets drop from the upper-right hand corner of the screen and are right-justified. Some icons may also have different orientation. isLTR() can be checked to provide alternate logic for true and false results.
Dojo provides the dojo.i18n.isLTR() function to specify wether a language is left to right. Most complex widgets, such as Menu are not BiDi enabled yet.
Other encodings should be used with great care. A user agent such as one of the current generation browsers infers a page's encoding using the content-type header provided from a server or it may be picked up from a meta tag in the head of a document.
TODO: show examples of declaring/overriding locale; explain that locales will only function if part of the operating set.
TBD
Author: PellerA group of JavaScript developers starting talking about the future of DHTML. Alex Russell (original creator of NetWindows), in looking for assistance at Informatica, started talking to many of the members of the DHTML community, culminating in the April 25, 2004 email titled "Selling the future of DHTML". David Schontzler (Stilleye) spent a summer working at Informatica, and Dylan Schiemann (currently at SitePen) also joined Informatica. The first lines of code contributed to Dojo were done by Alex and Dylan with the support of Informatica. There were many other community members that were active participants in shaping the direction of Dojo, including Joyce Park (mod_pubsub), Tom Trenka (creator of f(m)), Mark Anderson (bustlib), Leonard Lin (who suggested the name Dojo), Aaron Boodman (YoungPup, before GreaseMonkey and Google), Simon Willison (before Django, now at Flickr), Cal Henderson (Flickr), and Dan Pupius (animation, now at Google).
After several months of discussions on the ng-dhtml (now dojo-developer) mailing list about licensing, choosing a name, coding conventions, build tools, server configuration, and requirements (what would this Dojo thing actually become), we began writing code and setting up a foundation to own the copyright to the Dojo code. After writing some initial specs, the first Dojo code was written and/or ported into Dojo in early September, 2004, by Alex Russell and Dylan Schiemann. In addition to new code, early contributions and inspirations were made from NetWindows, burstlib, and f(m). In March, 2005, we started to receive contribtions from other Dojo members, and by the end of 2006, we have completed four major releases, with over 300,000 downloads to date, and contributions from more than 60 developers and companies.
The Dojo Foundation was established in 2005 as a 501c(6) organization. Currently, Alex Russell serves as the president, and Dylan Schiemann is the Secretary/Treasurer. Voting of issues is done by +1/-1 by the contributor mailing list.
Over the first two years since that initial e-mail discussion, Dojo has grown in a manner that we could have only dreamed of. Through several major releases, and extensive contributions by the community, Dojo has become a much more diverse and flexible toolkit in a shorter amount of time than any of its predecessors. We were originally just hoping to build a toolkit that would learn from the limitations of toolkits of the day, make real use of DOM-capable browsers, and build something with real capabilities and a real software engineering approach. Our goal was to avoid reinventing the wheel so that we could move on and make new mistakes.
All the magic that is the dojo module system takes place in the src directory. Take a look in your distribution at the various files an directories. You'll notice that there are many directories and many js files. Now look inside the directories. Most of the directories have a file named __package__.js along with many other js files.
There are several ways to develop a module. Basically, it all has to do with how you plan to divide up your code. If you're writing a small module which needs the full source code to work, you'll want to put a js file in the base of the src directory with the same name as your module and a .js on the end. This can be called by putting the following in your source code:
dojo.require("dojo.packagename");If you want to split your module into several unrelated files (called "resources"), you'd create a directory an put each part into a seperate js file. The only way you could call this is by using a deeper require statement, as follows:
dojo.require("dojo.packagename.subpackage");If you want to split your module into smaller pieces that sometimes rely on each other, then you'll want a __package__.js file. What this file does is specifies default resources to load depending on the environment. Open up some of your files and see how it looks. There are two important functions: kwCompoundRequire and provide.ÂÂ
The first function specified which packages to load in an object with keys consisting of the different environments. An example is as follows:
dojo.kwCompoundRequire({
common: ["dojo.package.first", "dojo.package.second"],
browser: ["dojo.package.browser"]
});
The second function tells dojo that your module was loaded properly.
dojo.provide("dojo.package.*");In all the other js files, you'll need to have a require statement to inform dojo that the package pieces have been loaded. This is also true of the files in the base of the src directory:
dojo.provide("dojo.package");
The Dojo team is always looking for new code contributions. Please see the resources below for more info:
The Dojo bug database is located at http://trac.dojotoolkit.org. You need to login with your dojotoolkit.org user name and password before filing bugs. If you don't already have an account, you can sign up for one here.
You don't need to sign a Contributor License Agreementto submit bugs, although you do need to sign it before submitting patches. See this post for a fuller explanation of why we insist on CLAs for patches.
A good bug report should include:
Dojo is a community effort, and we need your help!
It's easy to get involved and there are lots of ways you can make a difference:
Inside the Dojo community, there are two "levels" of participants. The word "contributors" refers to anyone who submits documentation, bug fixes, or even bug reports and test cases. This large group of designers and developers who are using the Toolkit to improve user experience are the lifeblood of the project. You can become a contributor by sending in a Contributors License Agreement and starting to pitch in in any of the ways noted above.
"Committers" are a subset of the contributor community who have been granted the ability to make changes directly to Dojo. Committers can also vote in Toolkit and Foundation matters. With that power comes significant responsibility. Committers are required to act as the front line of defense against IP pollution, follow community development standards, merge or reject patches sent in by contributors, vote, participate in weekly IRC meetings, and work on criticial release issues. Becoming a Dojo committer is considered to be an honor as it means you've impressed a discerning group of hackers with the quality of your contributions and feels that they can get along with you.
So how do you go from "contributor" to "committer"?
The first step is to file a Contributors License Agreement (outlined below). Once it's been received, start contributing! There's no hard-and-fast rule for how many contributions it takes before you are considered for committer status, but in general you'll need to close important bugs, demonstrate that you are willing to participate in the community (mailing lists, IRC, etc.), and show that you respect community norms and standards. Committers make recommendations to the Foundation Board regarding people they think should be considered for commiter status, so working with a committer to assist him/her with issues that are important for an upcoming release is a great way to ensure that your contributions are integrated and that you will be considered for committer status in a timely fashion.
Most forms of contribution aside from providing support to other users requires that you sign and submit a Contributors License Agreement (or "CLA" for short) with the Dojo Foundation. All additions to this manual, for instance, require signed CLAs.
There are two versions of the agreement:
Summarized, the CLA asserts that when you donate fixes or documentation, you both own the code that you're submitting and that the Dojo Foundation can in turn license that code to other people. While the text on the agreement suggests that you fax it in, you can submit a valid CLA by:
Sending in a CLA is a one-time thing, and once it's done, you're in the clear to start contributing to all Dojo Foundation projects! To be effective, though, you need to know a little bit about how contributors coordinate their work.
Most decisions, support, and coordination happens on the project's mailing lists. The dojo-interest mailing list is our most active list, with over a hundred messages sent to subscribers on some days. Everything from how best to theme widgets to complex debugging of applications happens here. There are several web interfaces onto this list, including the searchable gmane archive and the Nabble interface which allows for participation via the web UI.
Project mailing lists are broken up more-or-less by function. The full list: