Chapter 15. Application Widgets

This chapter systematically works through all of the general-purpose application widgets provided in Dijit. In many ways, these are some of the most exciting dijits provided by the toolkit because they're not as familiar as form elements and, unlike the enabling layout dijits, they provide tremendous interactive functionality. ProgressBar, Toolbar, Editor, and Tree are just a few of the exciting dijits that are coming up. Chances are, you'll witness some of some highest quality DHTML hacking you've ever seen in this chapter—especially as we near the end of it.

Although not explicitly called out in all cases, the widgets in this chapter are fully accessible, as are all other widgets in Dijit.

Tooltip

Tooltips are a great means of providing user assistance for the context of a particular control on the page, and although the ordinary HTML title attribute is a good start for applications circa 1990, the current era of web applications calls for a richer variation of a tooltip. The Tooltip dijit does just that, providing the ability to display arbitrary HTML markup instead of a plain old snippet of text. Although you got a preview of Tooltip with ValidationTextBox and its descendants in a previous chapter, you'll be pleased to know that you can now use Tooltip as a standalone.

Consider Example 15-1, which captures some of the key features of a Tooltip, producing the results shown in Figure 15-1.

Example 15-1. Typical Tooltip usage
One <span id="one">fish</span>, two <span id="two">fish</span>.

<div dojoType="dijit.Tooltip" connectId="one,two">
    A limbless cold-blooded vertebrate...<img src='./fish.jpeg'/>
</div>
The tooltip that appears when you mouseover either of the tags containing "fish"
Figure 15-1. The tooltip that appears when you mouseover either of the tags containing "fish"

Note that the syntax for passing in multiple values for connectId is inconsistent with normal JavaScript Array syntax: you provide multiple connectId s without brackets and without embedded quotes: connectId="id1,id2". It is likely that this syntax will normalize in a future release so that this isn't an exception to the rule.

As you can see from the example, you simply provide arbitrary HTML markup for Tooltip to render. Tooltip should be used for read-only content; TooltipDialog, coming up in the next section, is particularly suited for content such as input fields and buttons that requires interaction. Table 15-1 gives a complete listing of Tooltip 's features.

Table 15-1. Tooltip API

Name

Type

Comment

connectId ("")

String

A comma-separated list of values that provides the node id values for which the Tooltip should be displayed.

label ("")

String

The text to display in the Tooltip. Although the label could include arbitrary HTML markup, it's generally better form to include HTML markup inside of the enclosing tag.

showDelay (400)

Integer

How many milliseconds to wait before displaying the Tooltip to a user.

Dialog Widgets

Dijit offers two related widgets that provide dialog functionality: Dialog, which is similar to the kind of interaction you normally have with something like an ordinary alert box (only a whole lot more aesthetically pleasing and flexible), and TooltipDialog, which is much like an ordinary tooltip except that it can render other widgets and provide for more interaction that an ordinary Tooltip.

Dialog

The Dialog dijit is conceptually like a pop up that sets up a translucent underlay below it. While it is visible, you cannot respond to anything below it, making it ideal for situations in which you need to temporarily prevent access to controls on a page or force the user to acknowledge or respond to an alert.

But in addition to the obvious uses, you might also use a Dialog for almost any situation in which the alternative would be to pop up a new window. From an implementation standpoint, using a Dialog is often easier than interacting with a separate window because everything that is contained in the Dialog is part of the current page's DOM.[25] You can query it and otherwise manipulate it like anything else on the page—even if it's currently not visible.

A Dialog may contain any DOM content you'd like to place in it, whether it is a simple HTML snippet, a complex layout dijit, or a custom widget of your own. Example 15-2 illustrates the most basic usage of a Dialog ; in this case, it is automatically displayed on page load.

As noted in the previous chapter, you may need to manually call a layout dijit's resize method to force it to redraw itself if you initially create it to be hidden—which it would be if you created it and then embedded it inside of a Dialog.

Example 15-2. Typical Dialog usage
<html>
    <head>
        <title>Fun With Dialog!</title>

        <link rel="stylesheet" type="text/css"
          href="http://o.aolcdn.com/dojo/1.1/dojo/resources/dojo.css" />
        <link rel="stylesheet" type="text/css"
          href="http://o.aolcdn.com/dojo/1.1/dijit/themes/tundra/tundra.css" />

        <script
            type="text/javascript"
            src="http://o.aolcdn.com/dojo/1.1/dojo/dojo.xd.js"
            djConfig="parseOnLoad:true">
        </script>

        <script type="text/javascript">
            dojo.require("dojo.parser");
            dojo.require("dijit.Dialog");
            dojo.addOnLoad(function(  ) {
                dijit.byId("dialog").show(  );
            });
         </script>
    </head>
    <body class="tundra">
        <div id="dialog" dojoType="dijit.Dialog">
            So foul and fair a day I have not seen...
        </div>
    </body>
</html>

Programmatically creating a Dialog is easily accomplished with Dialog 's setContent method, which can accept a DOM node. Consider this example, which forces the user to click on a Button that is placed into a Dialog —even though you've expressly told them not to do it:

dojo.addOnLoad(function(  ) {
    var d = new dijit.Dialog(  );

    //hide the ordinary close button from the user...
    dojo.style(d.closeButtonNode, "visibility", "hidden");

    var b = new dijit.form.Button({label: "Do not press this button"});
    var handle = dojo.connect(b, "onClick", function(  ) {
        d.hide(  );
        dojo.disconnect(handle);
    });
    d.setContent(b.domNode);
    d.show(  );
});

Dialog 's template contains a number of useful attach points, including the closeButtonNode attach point, which was used in the previous code example to hide the icon that normally closes a Dialog.

Like the other dijits, you'll often get by with just a few common methods and attributes, but Table 15-2 presents the rest of the story for when you need it.

Dialog inherits from ContentPane, so all of ContentPane 's attributes, methods, and extension points are also available if you need them. See Example 14-3 for that API.

Table 15-2. Dialog API that builds upon ContentPane's API

Name

Type

Comment

open

Boolean

The state of the Dialog. true if it is open, false (the default) otherwise.

duration

Integer

The duration in milliseconds it takes to fade in and fade out the Dialog. 400 by default.

hide( )

Function

Hides the Dialog.

layout( )

Function

Positions the Dialog and its underlay.

show()

Function

Display the Dialog.

TooltipDialog

TooltipDialog inherits from Dialog, but provides functionality that may sort of remind you of a menu out of a DropDownButton —except that you can interact with it. In fact, the current manifestation TooltipDialog must be housed in a DropDownButton or a ComboButton, although you could theoretically adjust the button's style to make it appear quite different. You may recall the concept of a TooltipDialog from interacting with a spreadsheet application.

To get TooltipDialog, you must do a dojo.require("dijit.Dialog") because TooltipDialog is embedded into Dialog 's resource file.

Aside from the inability to programmatically create and display a TooltipDialog as a standalone, the rest of its functional API of a TooltipDialog is quite similar to Dialog with the caveat that it does not support a show( ) method. Additionally, it offers a standard title attribute that you can fill in if you'd like to stay accessibility compliant.

A good use case for a TooltipDialog might be to provide an interactive means of tagging an image. For example, you might use a DropDownButton to provide an image via its iconClass attribute and then interactively supply the TooltipDialog when the user clicks on the image. The following snippet provides the basic outline for how you might get started with a custom image tagger, producing the results shown in Figure 15-2.

A custom image tagger built with DropDownButton and TooltipDialog
Figure 15-2. A custom image tagger built with DropDownButton and TooltipDialog
<!-- somewhere out there...
<style type="text/css">
.customImage {
                background-image : url('/static/path/to/apple.jpeg');
                backgrond-repeat : no-repeat;
                width : 120px;
                height : 120px;
            }
</style>
-->

<button dojoType="dijit.form.DropDownButton" iconClass="customImage"
  showLabel="false">
    <span>This label is hidden...</span>

    <div dojoType="dijit.TooltipDialog">
        <span>Tag this image...<span>
        <div dojoType="dijit.form.TextBox"></div>
    </div>
</button>

ProgressBar

The ProgressBar dijit behaves just like any other progress bar you've seen in an application, and it comes in both determinate and indeterminate variations. One of the greatest things about it is that there's just not that much to say. In fact, Example 15-3 should do a good job of speaking for itself.

Example 15-3. Typical indeterminate ProgressBar usage
<div dojoType="dijit.ProgressBar" indeterminate="true" style="width:300px"></div>

Of course, there will certainly be times when you'll want to fetch a real update from the server and display actual progress instead of an indeterminate indicator. Let's assume that you have a server-side routine that is returning some kind of progress indication. The following mockup simulates:

import cherrypy

config = {
    #serve up this static file...
    '/foo.html' :
    {
        'tools.staticfile.on' : True,
        'tools.staticfile.filename' : '/absolute/path/to/foo.html'
    }
}

class Content:
    def _  _init_  _(self):
        self.progress = 0

    @cherrypy.expose
    def getProgress(self):
        self.progress += 10
        return str(self.progress)

cherrypy.quickstart(Content(  ), '/', config=config)

The file foo.html that contains the ProgressBar might look like this:

<html>
    <head>
        <title>Fun with ProgressBar!</title>

        <link rel="stylesheet" type="text/css"
          href="http://o.aolcdn.com/dojo/1.1/dojo/resources/dojo.css" />
        <link rel="stylesheet" type="text/css"
          href="http://o.aolcdn.com/dojo/1.1/dijit/themes/tundra/tundra.css" />

        <script
            type="text/javascript"
            src="http://o.aolcdn.com/dojo/1.1/dojo/dojo.xd.js"
            djConfig="parseOnLoad:true">
        </script>

        <script type="text/javascript">
            dojo.require("dojo.parser");
            dojo.require("dijit.ProgressBar");

            dojo.addOnLoad(function(  ) {
                var progressInterval = setInterval(function(  ) {
                       dojo.xhrGet({
                            url : "http://localhost:8080/getProgress",
                            load : function(response, ioArgs) {
                                console.log("load", response);
                                if (response <= 100) {
                                    dijit.byId("pb").update({progress : response});
                                }
                                else  {
                                    clearInterval(progressInterval);
                                }
                            }
                        });
                }, 1000);
            });
        </script>
    </head>
    <body style="padding:100px" class="tundra">
        <div>Loading...</div>
        <div id="pb" dojoType="dijit.ProgressBar" style="width:300px"></div>
        </div>
    </body>
</html>

To summarize, every second the addOnLoad routine checks the /getProgress URL for an update and feeds it into ProgressBar via its update function. The use of JavaScript's setInterval function will be quite typical with a ProgressBar.

Don't confuse setInterval with setTimeout. The former executes a function repeatedly every time interval, while setTimeout executes a function just once, after a specified amount of time.

The full range of ProgressBar options are shown in Table 15-3.

Table 15-3. ProgressBar API

Name

Type

Comment

indeterminate

Boolean

Whether to display an indeterminate indication (an animated image) or to actually render progress as provided via the update method.

maximum

Float

The maximum possible value. Although values of 0 through 100 (the default) are common, any range could be used.

places

Number

The number of decimal places to display for a determinate ProgressBar. 0 by default.

progress

String

The initial value for the ProgressBar. You may provide a percent sign, such as "50%", to indicate a relative amount versus an absolute amount.

update(/*Object*/ progress)

Function

Used to update the progress information. You may pass in progress, maximum, and determinate to configure the ProgressBar during any update.

onChange( )

Function

Extension point that is called after each call to update.

Finally, recall that if you need to display a ProgressBar as part of a blocking event, you can always stuff it inside of a Dialog to make the user wait while something happens in the background. Something along the lines of the following example would do the trick:

var pb = new dijit.ProgressBar;
var d = new dijit.Dialog;
d.setContent(pb.domNode);
d.show(  );

ColorPalette

ColorPalette is another simple standalone widget that is helpful for providing a more visual and interactive way of allowing a user to select a color—perfect for situations in which you allow the user to customize the theme of an application, for example. By default, the palette comes in two canned sizes, 3 × 4 or 7 × 10, with pre-selected popular web colors.

You may already be wondering why you can't configure your own set of colors for the palette. As it turns out, the palettes that appear are images, not panes of HTML markup, and they were designed this way for a11y reasons, even though it does not seem ideal. Thus, if you want to extend ColorPalette to display a custom selection, it is certainly doable—you'd just have to read the source and get your hands dirty by hacking on some of the private attributes.

Using a ColorPalette in markup is quite simple; the following listing illustrates:

<div dojoType="dijit.ColorPalette">
    <script type="dojo/method" event="onChange" args="selectedColor">
        /* hide the palette, perhaps? */
        console.log(selectedColor);
    </script>
</div>

Like ProgressBar, ColorPalette is a nice and simple standalone. Table 15-4 shows the full story.

Table 15-4. ColorPalette API

Name (default)

Type

Comment

defaultTimeout

Integer

The duration before a key that is held down becomes typematic. 500 by default.

timeoutChangeRate

Number

The amount of time that is used to change the typematic rate. A value of 1.0 means that typematic events are fired at regular intervals, while values less than 1.0 mean that the typematic rate accelerates accordingly. The default value is 0.9.

palette

String

The size of the palette, which must be either 7 × 10 (the default) or 3 × 4.

onChange(/*String*/ hexColor)

Function

Extension point triggered when a color is selected.

Programmatic creation is straightforward enough:

var cp = new dijit.ColorPalette({/*attributes go here */});
/* Now stick it somewhere on the page...*/
dojo.body(  ).appendChild(cp.domNode);

Toolbar

The Toolbar is another familiar control that abbreviates the common task of providing a collection of common commands to the user. In short, the Toolbar does nothing more than house a collection of Button dijits, which when styled appropriately, can be very aesthetically pleasing. The various prepackaged themes that come with Dijit contain classes for many of the common operations such as cut/paste, bold/italic, etc., which you can provide through Button 's iconClass attribute.

The following listing illustrates placing a Toolbar on the page and then systematically wires up each of its buttons to a custom event handler.

This particular example attempts to automate the methodology for hooking up buttons and custom handlers. Note that the peculiarity of connecting to x.parentNode inside of the forEach block instead of just connecting to x is related to the way that Button is implemented. As it turns out, the icon overlay is what contains an icon node that actually receives the click; you could have debugged this by inspecting with Firebug.

<html>
    <head>
        <title>Fun with Toolbar!</title>

        <link rel="stylesheet" type="text/css"
          href="http://o.aolcdn.com/dojo/1.1/dojo/resources/dojo.css" />
        <link rel="stylesheet" type="text/css"
          href="http://o.aolcdn.com/dojo/1.1/dijit/themes/tundra/tundra.css" />

        <script
            type="text/javascript"
            src="http://o.aolcdn.com/dojo/1.1/dojo/dojo.xd.js"
            djConfig="parseOnLoad:true,isDebug:true">
        </script>

        <script type="text/javascript">
            dojo.require("dojo.parser");
            dojo.require("dijit.Toolbar");
            dojo.require("dijit.form.Button");

            dojo.addOnLoad(function(  ) {
                var bold = function(  ) {console.log("bold");}
                var italic= function(  ) {console.log("italic");}
                var underline = function(  ) {console.log("underline");}
                var superscript = function(  ) {console.log("superscript");}
                var subscript = function(  ) {console.log("subscript");}

                dojo.query(".dijitEditorIcon").forEach(function(x) {
                        if (dojo.hasClass(x, "dijitEditorIconBold"))
                            dojo.connect(x.parentNode, "onclick", bold);
                        else if (dojo.hasClass(x, "dijitEditorIconItalic"))
                            dojo.connect(x.parentNode, "onclick", italic);
                        else if (dojo.hasClass(x, "dijitEditorIconUnderline"))
                            dojo.connect(x.parentNode, "onclick", underline);
                        else if (dojo.hasClass(x, "dijitEditorIconSubscript"))
                            dojo.connect(x.parentNode, "onclick", superscript);
                        else if (dojo.hasClass(x, "dijitEditorIconSuperscript"))
                            dojo.connect(x.parentNode, "onclick", subscript);
                });
            });
        </script>
    </head>
    <body style="padding:100px" class="tundra">
        <div dojoType="dijit.Toolbar" style="width:175px">
            <button dojoType="dijit.form.Button"
               iconClass="dijitEditorIcon dijitEditorIconBold" ></button>
            <button dojoType="dijit.form.Button"
               iconClass="dijitEditorIcon dijitEditorIconItalic" ></button>
            <button dojoType="dijit.form.Button"
               iconClass="dijitEditorIcon dijitEditorIconUnderline" ></button>

            <span dojoType="dijit.ToolbarSeparator"></span>

            <button dojoType="dijit.form.Button"
               iconClass="dijitEditorIcon dijitEditorIconSubscript"></button>
            <button dojoType="dijit.form.Button"
               iconClass="dijitEditorIcon dijitEditorIconSuperscript"></button>
        </div>
    </body>
</html>

As a point of interest, Dijit themes currently define the following self-descriptive Editor-related icons (defined in the theme's stylesheet) that may be contained in Toolbar. (Editor is discussed at length in an upcoming section.)

Toolbar has a simple API, shown in Table 15-5, which is representative of a descendant of _Container.

Table 15-5. Toolbar API

Name

Type

Comment

addChild(/*Object*/ child, /*Integer?*/ insertIndex)

Function

Used to insert a dijit into the Toolbar.

getChildren( )

Function

Returns an array of the contained dijits in the Toolbar.

removeChild(/*Object*/ child)

Function

Used to remove a child from the Toolbar (removes its domNode, but does not destroy the dijit; you must call destroyRecursive() manually).

TitlePane

A TitlePane is a widget that always displays a title, but whose body may be expanded or collapsed as needed; the actual resize is done with an animated wipe-in or wipe-out. As a descendant of ContentPane, TitlePane also has access to all of the inherited methods for loading content remotely, although they are not explicitly covered again in this section. (Refer to the previous chapter for complete coverage of ContentPane.) Example 15-5 shows the elementary usage.

Example 15-5. Typical TitlePane usage
<div dojoType="dijit.TitlePane" title="Grocery list:" style="width:300px">
    <ul>
        <li>Eggs</li>
        <li>Milk</li>
        <li>Bananas</li>
        <li>Coffee</li>
    </ul>
</div>

TitlePane supports the feature set shown in Table 15-8.

Table 15-8. TitlePane API

Name

Type

Comment

title

String

The title of the pane.

open

Boolean

Whether the pane is opened or closed. true by default.

duration

Integer

The number of milliseconds the animated wipe should last. 250 by default.

setContent(/*DomNode|String*/)

Function

Used to programmatically set the contents of the pane.

setTitle(/* String */ title)

Function

Sets the title.

toggle( )

Function

If the pane is opened, this closes it. If closed, then opens it.

Although you could use TitlePane as a static artifact on your page, you might soon find interesting uses for it as a more interactive kind of control. Consider, for example, how easy it would be to use it to mimic the kind of sticky note that you see in so many applications. Getting a simple widget working is as easy as inserting something like a Textarea into TitlePane, and retitling it whenever it closes, as shown in Example 15-6.

Example 15-6. Simulating a sticky note with a TitlePane
dojo.addOnLoad(function(  ) {
    var ed = new dijit.form.Textarea({id : "titlePaneContent"});
    dijit.byId("tp").setContent(ed.domNode);
});

//And now for the ContentPane, which you might declare in markup:

<div id="tp" dojoType="dijit.TitlePane" style="width:300px">
   <script type="dojo/connect" event="toggle">
        if (!this.open) {
            var t = dijit.byId("titlePaneContent").getValue(  );
            if (t.length > 15)
                t = t.slice(0,12)+"...";
            this.setTitle(t);
        }
    </script>
</div>

A little additional styling and some drag-and-drop action takes you just about the whole way towards having a small sticky-notes application.

InlineEditBox

The InlineEditBox is often described as a wrapper widget in that it provides a marked-up static display for what is really an editable control—then, when you're ready to edit it, you do so inline by simply selecting it. For example, instead of having a fixed size, editable TextBox always visible on the screen, you could wrap it in an InlineEdit box and it would appear as ordinary markup on the screen (like a label), but when you select it, it transforms back into a TextBox for editing. When editing completes as signaled by an event, such as the Enter key being pressed, it switches back to markup.

In its simplest usage, you might simply wrap a TextBox in an InlineEditable as part of a form letter application, like the following example. Note that what would have normally appeared as a TextBox and cluttered up the display is presented just like ordinary markup, while clicking on it transforms it into an editable control:

Dear <span dojoType="dijit.InlineEditBox" autoSave="false"
  editor="dijit.form.TextBox">Valued Customer</span>:

<div>We have received your request to be removed from our spam list. Not to
worry, we'll remove you when we're good and ready. In the meanwhile, please do
not hesitate to contact us with further complaints.</div>

<div>Sincerely,</div>
<span dojoType="dijit.InlineEditBox" autosave="false"
  editor="dijit.form.TextBox">Customer Service</span>

To recap, the autosave attribute being set to false results in the control presenting Save and Cancel buttons (the text would normally have been saved as it was typed with no controls displayed at all). That's the basic concept. Now, let's expand on these concepts by trying out a different Editor.

Here's a quick example of an InlineEditBox wrapping up a Textarea. Note that the renderAsHtml allows us to provide markup and have it automatically rendered on the spot:

Dear <span dojoType="dijit.InlineEditBox" autoSave="false"
  editor="dijit.form.TextBox">Valued Customer</span>:

<div dojoType="dijit.InlineEditBox" autoSave="false" editor="dijit.form.Textarea"
  renderAsHtml="true">
    Insert<br>
    Form<br>
    Letter<br>
    Here<br>
</div>

<div>Sincerely,</div>

<span dojoType="dijit.InlineEditBox"
  autoSave="false" editor="dijit.form.TextBox">Customer Service</span>

Like the previous dijits in this chapter, the basic usage is quite simple, but Table 15-9 shows a few extra configuration items to be aware of and keep on hand.

Table 15-9. InlineEditBox API

Name

Type

Comment

editing

Boolean

The edit state of the InlineEditBox. true when it is in editing mode.

autoSave

Boolean

Whether changing the value automatically should save it without requiring any kind of explicit action. true by default.

buttonSave

String

The text string to display on the Save button. Empty by default.

buttonCancel

String

The text string to display on the Cancel button. Empty by default.

renderAsHtml

Boolean

If true, renders the InlineEditBox 's editor contents as HTML. false by default.

editor

String

The class name for the dijit that should act as the editor. dijit.form.TextBox by default.

editorParams

Object

Any parameters that should be passed in when constructing the editor for the InlineEditBox.

width

String

The width of the editor. 100% by default.

value

String

The display value of the widget when in read-only mode.

noValueIndicator

String

The placeholder that should be displayed when there is no text value (so that the user has a place to click on and trigger an edit). A wingdings placeholder is there by default.

setDisabled(/*Boolean*/disabled)

Function

Used to disable and enable the widget.

setValue(/*String*/val)

Function

Sets the value of the widget.

save

Function

Saves the contents of the editor and reverts to display mode.

cancel

Function

Discards any changes made in the editor and reverts to display mode.

onChange

Function

An extension point that can be used to be notified of changes to the value.

enableSave

Function

A user-replaceable function that can be used to enable and disable the Save button. (For example, you might disable the button because of invalid conditions in the editor.)

Tree

The Tree dijit is an amazing piece of engineering. Using completely native DHTML, it looks and acts just like you'd expect a hierarchical tree to look and act, it supports drag-and-drop operations, and it's flexible enough to bind to an arbitrary data source. Like any other complex piece of machinery, there are a few fundamentals to pick up before you get rolling with it, but they're all fairly intuitive once you've connected the dots that first time. This is one of the longer sections in the chapter because the Tree is quite powerful and offers an extensive set of features. Although we won't elaborate on a11y, you should also be cognizant that the Tree is quite accessible with the keyboard via arrow keys, the Enter key, and so on.

A good understanding of the dojo.data API is especially helpful for working with the Tree dijit. See Chapter 9 for more details.

Before reading through any code, it's helpful to be aware of at least a few things:

Trees and forests

A tree is a hierarchical data structure that contains a single root element. A forest, on the other hand, is a hierarchical structure just like a tree except that it does not have a single root node; instead, it has multiple root nodes. As we'll see, distinguishing between a tree and a forest is a common issue because many data views are conveniently expressed as a tree with a single root node even though the data that backs the view is a forest with an implied root node.

Nodes

A tree is a hierarchical organization of nodes and the linkages between them. The specific type of node that is used by dijit.Tree is dijit._TreeNode ; the leading underscore in this case signals that you'd never be using a _TreeNode outside of a Tree. There are, however, several properties of _TreeNode that are useful to manipulate directly, as we'll see in upcoming examples.

Data agnosticism

The Tree dijit is completely agnostic to the data source that backs it. Prior to version 1.1, it read directly from an implementation of the dojo.data API, which is quite flexible and provides a uniform layer for data access, but as of the 1.1 release, the enhancement of an additional intermediating layer between the dojo.data model and the Tree was added. These intermediating layers are dijit.tree.TreeStoreModel and dijit.tree.ForestStoreModel, respectively. Much of the motivation for the change was to make the Tree much more robust and amenable to drag-and-drop operations.

When you execute dojo.require("dijit.Tree") the ForestStoreModel and TreeStoreModel come along with the Tree itself.

Simple Tree

To ease in to what the Tree can do for you, assume that you have a really simple data source that serves up dojo.data.ItemFileReadStore JSON along the lines of the following:

{
    identifier : 'name',

    label : 'name',

    items : [
        {
            name : 'Programming Languages',
            children: [
                {name : 'JavaScript'},
                {name : 'Python'},
                {name : 'C++'},
                {name : 'Erlang'},
                {name : 'Prolog'}
            ]
        }
    ]
}

So far, so good. Instead of parsing the data yourself on the client, you get to use dojo.data to abstract the data for you. Hooking up an actual ItemFileReadStore is as easy as pointing it to the URL that serves the data and then querying into it. The following tag, when instantiated by the parser, would do the trick if the file were served up from the working directory as programmingLanguages.json, and it would have a global identifier of dataStore that would be accessible:

<div dojoType="dojo.data.ItemFileReadStore"
  jsId="dataStore" url="./programmingLanguages.json"></div>

Before the data gets fed into the Tree, however, it will be mediated through a TreeStoreModel. (We'll work through the implications of using a ForestStoreModel in a moment.) The complete API listing for an intermediating TreeStoreModel will be presented momentarily, but for now, all that's pertinent is that we have to point the TreeStoreModel at the ItemFileReadStore and provide a query. The following TreeStoreModel would query the dojo.data store with global identifier dataStore for all name values:

<div dojoType="dijit.tree.TreeStoreModel" jsId="model" store="dataStore"
  query="{name:'*'}"></div>

Finally, the only thing left to do is point the Tree dijit at the TreeStoreModel like so:

<div dojoType="dijit.Tree" model="model"></div>

That's it. Example 15-7 puts it all together, and Figure 15-3 shows the result.

The Tree that renders from the data store; clicking on the expando node closes it
Figure 15-3. The Tree that renders from the data store; clicking on the expando node closes it
Example 15-7. Simple Tree with a root
<html>
    <head>
        <title>Tree Fun!</title>

        <link rel="stylesheet" type="text/css"
          href="http://o.aolcdn.com/dojo/1.1/dojo/resources/dojo.css" />
        <link rel="stylesheet" type="text/css"
          href="http://o.aolcdn.com/dojo/1.1/dijit/themes/tundra/tundra.css" />

        <script
            type="text/javascript"
            src="http://o.aolcdn.com/dojo/1.1/dojo/dojo.xd.js"
            djConfig="parseOnLoad:true,isDebug:true">
        </script>

        <script type="text/javascript">
            dojo.require("dijit.Tree");
            dojo.require("dojo.data.ItemFileReadStore");
            dojo.require("dojo.parser");
        </script>
    </head>
    <body class="tundra">
       <div dojoType="dojo.data.ItemFileReadStore" jsId="dataStore"
         url="./programmingLanguages.json"></div>
       <div dojoType="dijit.tree.TreeStoreModel" jsId="model" store="dataStore"
            query="{name:'*'}"></div>
        <div dojoType="dijit.Tree" model="model"></div>
    </body>
</html>

Simple Forest

Many applications do not expressly represent a single root node, so let's adjust the previous example to work as a forest instead of a tree so that you can see the difference. First, a forest would have had a data source that didn't have a single root. Consider the following example, which lists programming languages as a forest because it does not include an explicit "programming languages" root:

{
    identifier : 'name',

    label : 'name',

    items : [
        {
            name : 'Object-Oriented',
            type : 'category',
            children: [
                {name : 'JavaScript', type : 'language'},
                {name : 'Java', type : 'language'},
                {name : 'Ruby', type : 'language'}
            ]
        },
        {
            name : 'Imperative',
            type : 'category',
            children: [
                {name : 'C', type : 'language'},
                {name : 'FORTRAN', type : 'language'},
                {name : 'BASIC', type : 'language'}
            ]
        },
        {
            name : 'Functional',
            type : 'category',
            children: [
                {name : 'Lisp', type : 'language'},
                {name : 'Erlang', type : 'language'},
                {name : 'Scheme', type : 'language'}
            ]
        }

    ]
}

With the updated JSON data, you see that there isn't a single root node, so the data is delivered such that it lends itself to a forest view. The only notable updates from Example 15-7 are that an additional parameter, showRoot, must be added to the Tree to expressly hide the root of it, the query needs to be updated to identify the top-level nodes for the tree, and the TreeStoreModel is changed to a ForestStoreModel. Example 15-8 shows the updated code with the updates emphasized.

Example 15-8. Updates to show a forest instead of a tree
<body class="tundra">
       <div dojoType="dojo.data.ItemFileReadStore" jsId="dataStore"
            url="./programmingLanguages.json"></div>
       <div dojoType="dijit.tree.ForestStoreModel" jsId="model" store="dataStore"
            query="{type:'category'}"></div>
        <div dojoType="dijit.Tree" model="model" showRoot=false></div>
</body>

Just because your data lends itself to being displayed as a forest, however, doesn't mean you can't update it to be rendered as a tree. As shown in Example 15-9, you can fabricate a root-level dojo.data item that backs a fabricated node via the rootId and rootLabel attributes on the ForestStoreModel.

Example 15-9. Updates to fabricate a root-level node so that a forest appears like a tree
<body class="tundra">
       <div dojoType="dojo.data.ItemFileReadStore" jsId="dataStore"
          url="./programmingLanguages.json"></div>
       <div dojoType="dijit.tree.ForestStoreModel" jsId="model" store="dataStore"
          query="{type:'category'}" rootId="root" rootLabel="Programming Languages"
></div>
        <div dojoType="dijit.Tree" model="model" ></div>
</body>

For all practical purposes, the fabricated root node may now be treated uniformly with a dojo.data API such as getLabel or getValue. It may not seem like much, but having this façade behind the fabricated node is very convenient because you are freed from handling it as a special case. Figure 15-4 shows a simple forest.

Left: the Tree (with a fabricated root node) that renders from the same data store; right: the Tree (without a root node) that displays as a forest
Figure 15-4. Left: the Tree (with a fabricated root node) that renders from the same data store; right: the Tree (without a root node) that displays as a forest

Responding to Click Events

Although displaying information in a tree is quite nice, wouldn't it be even better to respond to events such as mouse clicks? Let's implement the onClick extension point to demonstrate the feasibility of responding to clicks on different items. Both the actual _TreeNode that was clicked as well as the dojo.data item are passed into onClick and are available for processing. To implement click handling, you might update the example as shown in Example 15-10.

Example 15-10. Responding to clicks on a Tree
<body class="tundra">
       <div dojoType="dojo.data.ItemFileReadStore" jsId="dataStore"
          url="./programmingLanguages.json"></div>
       <div dojoType="dijit.tree.ForestStoreModel" jsId="model" store="dataStore"
          query="{type:'category'}" rootId="root" rootLabel="Programming
Languages"></div>
        <div dojoType="dijit.Tree" model="model" >
            <script type="dojo/method" event="onClick" args="item,treeNode">
                //use the item or the node at will...
                console.log("onClick:",dataStore.getLabel(item)); //display the label
            </script>
        </div>
</body>

Note that although an intervening model provides a layer of abstraction between the Tree and the dojo.data store, you still use the store directly to access the item; there's no need to have the intervening model that facilitates display provide unnecessary cruft between the dojo.data item and the usual means of accessing it.

Drag-and-Drop with the Tree

The enhancements discussed in the previous section regarding the dijit.tree.model API were in no small part implemented to make drag-and-drop operations with the Tree a lot simpler and more consistent. In general, though, drag-and-drop is not a one-size-fits-all type of operation, so expect to get your hands dirty if you want a customized implementation of any sophisticated widget that responds to drag-and-drop. It's especially important to spend sufficient time answering these common questions:

  • What happens when a drag is initiated?

  • What happens when a drop is attempted?

  • What happens when a drop is cancelled?

The current architecture for implementing drag-and-drop with the tree entails implementing much of the API as defined in the dojo.dnd module (introduced in Chapter 7) and passing it into the Tree via its dndController attribute. Because starting all of that work from scratch is a hard job, the version 1.1 release includes a dijit._tree module that contains an implementation providing a lot of the boilerplate that you can use as you see fit; you might use subclass and override parts of it, you might mix stuff into it, or you might just use it as set of guidelines that provide some inspiration for your own from-scratch implementation. So long as the ultimate artifact from the effort is a class that resembles a dojo.dnd.Source and interacts appropriately to update the dijit.tree.model implementation that backs the Tree, you should be in good shape. In particular, the Source you implement should give special consideration to and implement at least the following key methods that the Tree 's dndController expects, listed in Table 15-13.

Table 15-13. Tree dndController interface

Name

Comment

onDndDrop(/*Object*/source, /*Array*/nodes, /*Boolean*/copy)

A topic event processor for /dnd/drop that is called to finish the drop operation, which entails updating the data store items according to source and destination of the operation so that three can update itself.

onDndCancel( )

A topic event processor for /dnd/cancel that handles a cancellation of a drop.

checkAcceptance(/*Object*/source, /*Array*/nodes)

Used to check if the target can accept nodes from the source. This is often used to disallow dropping based on some properties of the nodes.

checkItemAcceptance(/*DOMNode*/target, /*Object*/source)

Used to check if the target can accept nodes from the source. This is often used to disallow dropping based on some properties of the target.

itemCreator(/*Array*/nodes)

When completing a drop onto a destination that is backed by different a data source than the one where the drag started, a new item must be created for each element in nodes for the data source receiving the drop. This method provides the means of creating those items if the source and destination are backed by different data sources.

A subtle point about the dndController functions is that if they are referenced in markup, they must be defined as global variables when the parser parses the Tree in the page; thus, they cannot be declared in the dojo.addOnLoad block because it runs after the parser finishes. You can, however, decide not to reference the dndController function at all in markup and defer wiring them up until the dojo.addOnLoad block. This is the approach that the upcoming example takes.

An incredibly important realization to make is that drag-and-drop involves DOM nodes—not _TreeNode s; however, you'll usually need a _TreeNode because it's the underlying data it provides that you're interested in, and the DOM node does not provide that information. Whenever this need occurs, such will be the case for any of the methods in Table 15-13. Use the dijit.getEnclosingWidget function, which converts the DOM node into a _TreeNode for you.

Drag-and-droppable Tree example

Because these methods are so incredibly common, they may be passed into the Tree on construction, which is especially nice because it allows you to maximize the use of the boilerplate in dijit._tree. Speaking of which, it's about time for another example.

Let's update the existing working example from Example 15-9 to be drag-and-droppable. We'll build upon the dijit._tree boilerplate to minimize the effort required. Also, note that we'll have to switch our store from an ItemFileReadStore to an ItemFileWriteStore as the very nature of drag-and-drop is not a read-only operation.

Although it might look like the Tree updates itself when you interact with it in such as way that it changes display via a drag-and-drop operation, it's important to remember that the Tree is only a view. Any updates that occur are the result of updating the data source and the data source triggering a view update.

To maintain a certain level of sanity with the example, we'll need to prevent the user from dropping items on top of other items, as items are inherently different from categories of items based upon the category of the item from our dojo.data store. Example 15-11 shows the goods, and Figure 15-5 illustrates.

Example 15-11. Simple drag-and-droppable Tree
<html>
    <head>
        <title>Drag and Droppable Tree Fun!</title>

        <link rel="stylesheet" type="text/css"
          href="http://o.aolcdn.com/dojo/1.1/dojo/resources/dojo.css" />
        <link rel="stylesheet" type="text/css"
          href="http://o.aolcdn.com/dojo/1.1/dijit/themes/tundra/tundra.css" />

        <script
            type="text/javascript"
            src="http://o.aolcdn.com/dojo/1.1/dojo/dojo.xd.js"
            djConfig="parseOnLoad:true,isDebug:true">
        </script>

        <script type="text/javascript">
            dojo.require("dijit.Tree");
            dojo.require("dojo.data.ItemFileWriteStore");
            dojo.require("dijit._tree.dndSource");
            dojo.require("dojo.parser");


            dojo.addOnLoad(function(  ) {
                //wire up the checkItemAcceptance handler...
                dijit.byId("tree").checkItemAcceptance = function(target, source) {
                    //convert the target (DOM node) to a tree node and
                    //then get the  item out of it
                    var item = dijit.getEnclosingWidget(target).item;

                    //do not allow dropping onto the top (fabricated) level and
                    //do not allow dropping onto items, only categories
                    return (item.id != "root" && item.type == "category");
                }

            });
        </script>
    </head>
    <body class="tundra">
       <div dojoType="dojo.data.ItemFileWriteStore" jsId="dataStore"
          url="./programmingLanguages.json"></div>
       <div dojoType="dijit.tree.ForestStoreModel" jsId="model" store="dataStore"
          query="{type:'category'}" rootId="root" rootLabel="Programming Languages"
></div>
        <div id="tree" dojoType="dijit.Tree" model="model"
          dndController="dijit._tree.dndSource"></div>
    </body>
</html>
Moving a programming language item to a different category
Figure 15-5. Moving a programming language item to a different category

When you find that you need a drag-and-droppable Tree implementation, it's well worth the time to carefully study the boilerplate code provided in dijit._tree. Each situation with drag-and-drop is usually specialized, so finding an out-of-the-box solution that requires virtually no custom implementation is somewhat unlikely.

Editor

At the time of this writing, the Editor and its plug-in architecture were undergoing some significant enhancements. Thus, you may find that this section is slightly more general with respect to technical details than many other sections of the book.

An increasing number applications are utilizing rich text editing capability; in fact, it's probably fair to say that if you have a slick RIA interface and then hand it to the user with an ordinary textarea element (even a Textarea dijit), it'll probably stick out like a sore thumb. Fortunately, the Editor dijit contains all of the common rich text editing functionality, with absolutely minimal overhead on your part.

You may find this reference interesting as you read the rest of this section: http://developer.mozilla.org/en/docs/Rich-Text_Editing_in_Mozilla.

Dojo builds upon native browser controls that enable content to be editable. As a little history lesson, Internet Explorer 4.0 introduced the concept of design mode, in which it became possible to edit text in a manner consistent with simple rich text editors, and Mozilla 1.3 followed suit to implement what's essentially the same API that eventually became formalized as the Midas Specification (http://www.mozilla.org/editor/midas-spec.html). Other browsers have generally followed in the same direction—with their own minor nuances. In any event, most of the heavy lifting occurs by first explicitly making a document editable and then using the JavaScript execCommand function to do the actual markup. Following the Midas Specification, something along the lines of the following would do the trick:

// Make a node editable...perhaps a div with a set height and width
document.getElementById("foo").contentDocument.designMode="on";

/* Select some text... */

// Set the selection to italic. No additional arguments are needed.
editableDocument.execCommand("Italic", false, null);

As you might imagine, you can use an arsenal of commands for manipulating content via execCommand, standardize the differences amongst browser implementation, assemble a handy toolbar, provide some nice styling, and wrap it up as a portable widget. In fact, that's exactly what Dijit's Editor does for you. Although Editor provides a slew of features that seem overwhelming at a first glance, the basic usage is quite simple. Example 15-12 illustrates an out of the box Editor from markup along with some light styling and a couple of buttons that interact with it.

Without any styling at all, the Editor has no border, spans the width of its container, and comes at a default height of 300px. The light styling here simply provides a background and adjusts the Editor 's height to slightly smaller than its container so that the content won't run out of the visible background and into the buttons.

Example 15-12. Typical Editor usage
<div style="margin:5px;background:#eee; height: 400px; width:525px">
    <div id="editor" height="375px" dojoType="dijit.Editor">
        When shall we three meet again?<br>
        In thunder, lightning, or in rain?
    </div>
</div>
<button dojoType="dijit.form.Button">Save
    <script type="dojo/method" event="onClick" args="evt">
        /* Save the value any old way you'd like */
        console.log(dijit.byId("editor").getValue(  ));
    </script>
</button>
<button dojoType="dijit.form.Button">Clear
    <script type="dojo/method" event="onClick" args="evt">
        dijit.byId("editor").replaceValue("");
    </script>
</button>

It's well worth a moment of your time to interact with the Editor and see for yourself that getting all of that functionality with such minimal effort really isn't too good to be true. Note that the Editor renders plain HTML, so saving and restoring content should not involve any unnecessary translation. Then, when you're ready to take a look at some of the many things that the Editor can do for you, skim over the feature list in Table 15-14.

The Editor API is by far the most complex in Dijit, and at the time of this writing, refactoring efforts to tame it were being seriously entertained. Thus, the following table contains a small subset of the most useful parts of the API. See the source file documentation for the complete listing if you really want to hack on the Editor.

Table 15-14. Small subset of the Editor API

Name

Type

Comment

focusOnLoad

Boolean

Whether to focus into the Editor when the page loads.

height

String

The initial height of the Editor. 300px by default.

inheritWidth

Boolean

If true, inherits the parent node's width; otherwise, spans the entire width. false by default.

minHeight

String

The minimum allowable height for the Editor. 1em by default.

name

String

If provided, the content is saved and restored when the user leaves the page and returns.

plugins

Array

The plugins that should be be loaded as a baseline for the editor. By default, common values like bold, italic, underline, and so on are included.

extraPlugins

Array

Additional plugins that should be loaded on top of the baseline defined by plugins.

getValue ( )

Function

Returns the value from the Editor.

setValue(/*String*/val)

Function

Sets the value of the Editor.

undo( )

Function

Undoes the previous action.

onDisplayChanged(/*Event*/evt)

Function

Connects to this event to perform a custom action each time the display changes.

close( )

Function

Closes the Editor and serialize back out the content to it node of origin.

contentPreFilters

Array

Functions that may be optionally applied to text content as it is deserialized before it is transformed into a DOM tree.

contentDomPreFilters

Array

Functions that may optionally be applied to the DOM tree as it is deserialized before it is loaded for editing.

contentDomPostFilters

Array

Functions that may optionally be applied to the DOM tree before it is serialized.

contentDomFilters

Array

Functions that may be optionally applied to the text content before it is serialized.

execCommand

Function

Executes a command in the rich text area. Behaves like the standard JavaScript execCommand but accounts for deviations amongst various browser implementations.

Editor Architecture

The Editor 's lifecycle supports three basic phases, shown in Figure 15-6. The following list summarizes these phases and the work involved in each:

Deserializing content

The loading phase entails loading a text stream supplied by a DOM node, converting it into a DOM tree, and placing it into the display for user interaction. Sequences of JavaScript functions may be applied to both the text stream as well as the DOM tree in order, as needed, in order to filter and convert content. Common examples of filters might entail such tasks as converting linebreaks from a plain text document into <br> tags so that the content displays as proper HTML in the editor.

Interacting with content

The interaction phase is just like any other rich text editing experience. Common operations such as markup may occur, and an undo stack is stored based on either a time interval or on the basis of every time the display changes.

Serializing content

When editing ends by way of the Editor 's close method, the contents are serialized from a DOM tree back into a text stream, which then gets written back into the node of origin. From there, an event handler might send it back to a server to persist it. Like the deserializing phase, sequences of JavaScript functions may optionally be applied to manipulate the content.

The basic phases that the Editor's architecture supports
Figure 15-6. The basic phases that the Editor's architecture supports

Editor Plug-Ins

Although the Editor provides an onslaught of highly useful features of its own, sooner or later you'll be wishing that it were possible to tightly integrate some piece of custom functionality. Its plug-in architecture is your ticket to making that happen. A plug-in is just a way of encapsulating some additional functionality that, while useful, maybe shouldn't be a stock component; it could be anything from handling some special key combinations to providing a custom menu item with some canned commands that automate part of a workflow.

Snapping a plug-in into Editor is quite simple, and you may not have realized it, but everything in the toolbar you thought was built right in is technically a plug-in with one of the following self-descriptive values.

undo

justifyLeft

redo

justifyRight

cut

delete

copy

selectAll

paste

removeFormat

insertOrderedList

bold

insertUnorderedList

italic

indent

underline

outdent

strikethrough

justifyCenter

subscript

justifyFull

superscript

You can configure plug-ins by providing either the plugins or extraPlugins attribute and give it a list of valid plug-ins that you have first dojo.require d into the page. By default, plugins contains all of the items in the toolbar that you see by default, and if you override it and provide something like plugins="['bold','italic']", then all you'd see in the toolbar is the list of plugins you provided. However, the extraPlugins attribute adds extra plugins on top of what is already configured in plugins if you want to throw in a few extras.

Several packages of prefabricated plug-ins are available with the toolkit and are commonly used as values to extraPlugins ; they are located in the dijit/_editor/plugins directory and include the following:

AlwaysShowToolbar

Shifts the contents of the toolbar, as needed, so that multiple rows of controls are displayed, and it always remains visible. (If you resize the window to be less than the width of the toolbar, the default action is to display a horizontal scrollbar and only display the portion of the toolbar that would normally be visible.) You must pass in dijit._editor.plugins.AlwaysShowToolbar to plugins or extraPlugins to enable this plug-in.

EnterKeyHandling

Provides a means of uniformly handling what happens when the Enter key is pressed amongst all browsers. For example, you can specify whether to insert a series of paragraph tags to surround the new text, a break tag, a set of DIVS tags, or not to disable the handling of the Enter key entirely. You must pass in dijit._editor.plugins.EnterKeyHandling to plugins or extraPlugins to enable this plug-in.

The Editor 's plug-in architecture needs some work, and discussions are ongoing about how to improve it. Progress is already being made, and you can track it for yourself at http://trac.dojotoolkit.org/ticket/5707. In other words, if you want to create custom plug-ins, you'll likely have to hack on the Editor.js source code a bit until the plug-in architecture is smoothed out a bit more.

Also, don't forget that you have to manually dojo.require in the plug-in that you are using. The plug-in architecture does not perform any sort of autodetection at this time.

Currently, the default means of handling the Enter key is determined by the EnterKeyHandling attribute blockNodeForEnter, which has a default value of 'P'. Currently, there isn't really a better way of changing it than by extending this plug-in's prototype and overriding it like so:

dojo.addOnLoad(function(  ) {
      dojo.extend(dijit._editor.plugins.EnterKeyHandling, {
           blockNodeForEnter : "div" // or "br" or "empty"
      });
 });
FontChoice

Provides a button with a dialog for picking a font name, font size, and format block. Arguments to plugins or extraPlugins may be fontName, fontSize, or formatBlock.

LinkDialog

Provides a button with a dialog for entering a hyperlink source and displayed value. Arguments to plugins or extraPlugins may be createLink.

TextColor

Provides options for specifying the foreground color or background color for a range of text. Arguments to plugins or extraPlugins may be foreColor or hiliteColor.

ToggleDir

Provides a means of involving the HTML dir attribute on the Editor (regardless of how the rest of the page is laid out) so that the Editor 's contents could be left-to-right or right-to-left. Arguments to plugins or extraPlugins may be toggleDir.

To make matters a little less muddy, consider the differences in the snippets of markup shown in Table 15-15 when creating an editor.

Table 15-15. Different approaches to creating an editor

Code

Effect

<div dojoType="dijit.Editor">

Creates an Editor with the default toolbar.

<div dojoType="dijit.Editor" plugins="['bold', 'italic']">

Creates an Editor with a toolbar that has only the bold and italic buttons.

<div dojoType="dijit.Editor" extraPlugins="['hiliteColor']">

Creates an Editor with a default toolbar that has an additional button for highlighting text—assuming you've issued a dojo.require("dijit._editor.plugins.TextColor") statement.

<div dojoType="dijit.Editor" plugins="['bold', 'italic']" extraPlugins="['fontName']">

Creates an Editor with a toolbar consisting of a bold and italic button along with a control for selecting a custom font (assuming you've issued a dojo.require("dijit._editor.plugins.FontChoice") statement). Note that this has the exact same effect as including all three plug-ins inside the plugin attribute.

Summary

After reading this chapter, you should be able to:

  • Understand where the general-purpose application dijits fit into the overall Dijit architecture and appreciate the special role that they play in designing a rich user experience

  • Create application dijits in markup as well as JavaScript

  • Understand the primary differences between when you should use Tooltip versus TooltipDialog

  • Use the Editor to provide a control for entering and editing rich text

  • Use Toolbar and Menu to provide a means of command and control for the user of your application

  • Embed TooltipDialog into a DropDownButton

  • Use a ProgressBar to display both determinate and indeterminate indications of progress to a user

  • Use Dialog to provide a modal alert to the user or otherwise embed arbitrary content into the Dialog for the user to interact with

  • Use InlineEditable s to empower the user with the ability to edit what otherwise appears to be plain markup on the fly

  • Use the Tree dijit to display hierarchical information via an interactive display

In the next chapter, we'll cover build tools, testing, and production considerations.



[25] In fact, some browsers will not even allow you to manipulate one window's DOM from another window—even if both windows are from the same origin.