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.
Tip
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, “Typical Tooltip usage”, which
captures some of the key features of a Tooltip, producing the results shown in
Figure 15.1, “The tooltip that appears when you mouseover either of the
tags containing "fish"”.
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>Warning
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, “Tooltip API” gives a complete listing of Tooltip 's features.
Table 15.1. Tooltip API
|
Name |
Type |
Comment |
|---|---|---|
|
|
String |
A comma-separated list
of values that provides the node |
|
|
String |
The text to display in
the |
|
|
Integer |
How many milliseconds
to wait before displaying the |
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, “Typical Dialog usage” illustrates the most basic
usage of a Dialog ; in this case,
it is automatically displayed on page load.
Warning
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( );
});Tip
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, “Dialog API that builds upon ContentPane's API” presents the rest of the story for when you need it.
Tip
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, “Programmatically creating a BorderContainer” for that API.
Table 15.2. Dialog API that builds upon ContentPane's API
|
Name |
Type |
Comment |
|---|---|---|
|
|
Boolean |
The state of the
|
|
|
Integer |
The duration in
milliseconds it takes to fade in and fade out the |
|
|
Function |
Hides the |
|
|
Function |
Positions the
|
|
|
Function |
Display the |
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.
Warning
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”.
<!-- 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, “Typical indeterminate ProgressBar usage” 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.
Warning
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, “ProgressBar API”.
Table 15.3. ProgressBar API
|
Name |
Type |
Comment |
|---|---|---|
|
|
Boolean |
Whether to display an
indeterminate indication (an animated image) or to actually
render progress as provided via the |
|
|
Float |
The maximum possible value. Although values of 0 through 100 (the default) are common, any range could be used. |
|
|
Number |
The number of decimal
places to display for a determinate |
|
|
String |
The initial value for
the |
|
|
Function |
Used to update the
progress information. You may pass in |
|
|
Function |
Extension point that is
called after each call to |
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.
Tip
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, “ColorPalette API” shows the full
story.
Table 15.4. ColorPalette API
|
Name (default) |
Type |
Comment |
|---|---|---|
|
|
Integer |
The duration before a
key that is held down becomes typematic. |
|
|
Number |
The amount of time that
is used to change the typematic rate. A value of |
|
|
String |
The size of the palette, which must be either 7 × 10 (the default) or 3 × 4. |
|
|
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.
Tip
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, “Toolbar API”, which is representative of a descendant of
_Container.
Table 15.5. Toolbar API
|
Name |
Type |
Comment |
|---|---|---|
|
|
Function |
Used to insert a dijit
into the |
|
|
Function |
Returns an array of the
contained dijits in the |
|
|
Function |
Used to remove a child
from the |
Menu
Menu models the kind of
contextual menu that appears when you right-click on an application
icon in a desktop environment. A Menu contains MenuItem widgets, which are bona fide menu
items that may be selected, or PopupMenuItem widgets, which can provide
another layer of menu items (similar to the Windows Start menu). The
child of a PopupMenuItem is just
another Menu. Although it would not
be wise from a usability standpoint, you could theoretically embed an
arbitrary number of PopupMenuItem
and MenuItem dijits.
To start out, let's take a look at a simple Menu that contains only MenuItem children, as shown in Example 15.4, “Typical Menu usage”.
Tip
By issuing a dojo.require("dijit.Menu") you also get
MenuItem and PopupMenuItem.
Warning
Like Tooltip, the list of
values passed in for Menu is a
comma-separated string that does not include enclosing brackets that
would be required of an ordinary JavaScript Array. A future release may very well
standardize this anomaly.
As you can see, there's not much to it, and defining in markup
makes matters even simpler. The one emphasized line of code that sets
the display to be none is important
because it may be the case that your Menu will initially be visible without this
cue.
Now, suppose you wanted to make baz be a PopupMenuItem and you wanted the Menu to be contextual for the entire window.
You could do it thusly:
<div dojoType="dijit.Menu" style="display:none"contextualMenuForWindow="true">
<div dojoType="dijit.MenuItem">foo
<script type="dojo/method" event="onClick" args="evt">
console.log("foo");
</script>
</div>
<div dojoType="dijit.MenuItem">bar
<script type="dojo/method" event="onClick" args="evt">
console.log("bar");
</script>
</div>
<div dojoType="dijit.PopupMenuItem">
<span>baz</span>
<div dojoType="dijit.Menu">
<!-- define onClick handlers as needed for each item -->
<div dojoType="dijit.MenuItem">yabba</div>
<div dojoType="dijit.MenuItem">dabba</div>
<div dojoType="dijit.MenuItem">doo</div>
</div>
</div>
</div>Hopefully, the only remotely tricky part about installing the
PopupMenuItem was that there needed
to be an explicit node set, its first child to be specific, which
provides the title.
To round off this section on Menu, Table 15.6, “Menu API” provides a
listing of the remaining API. Note that as a _Container descendant, Menu has token methods for adding, removing,
and getting children, just like Toolbar and the others offer. The API for
MenuItem and PopupMenuItem are shown in Table 15.7, “MenuItem and PopupMenuItem API”.
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, “Typical TitlePane usage” 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, “TitlePane API”.
Table 15.8. TitlePane API
|
Name |
Type |
Comment |
|---|---|---|
|
|
String |
The title of the pane. |
|
|
Boolean |
Whether the pane is
opened or closed. |
|
|
Integer |
The number of milliseconds the animated wipe should last. 250 by default. |
|
|
Function |
Used to programmatically set the contents of the pane. |
|
|
Function |
Sets the title. |
|
|
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, “Simulating a sticky note with a TitlePane”.
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, “InlineEditBox API” shows a few extra configuration items to be aware of and keep on hand.
Table 15.9. InlineEditBox API
|
Name |
Type |
Comment |
|---|---|---|
|
|
Boolean |
The edit state of the
|
|
|
Boolean |
Whether changing the
value automatically should save it without requiring any kind
of explicit action. |
|
|
String |
The text string to display on the Save button. Empty by default. |
|
|
String |
The text string to display on the Cancel button. Empty by default. |
|
|
Boolean |
If |
|
|
String |
The class name for the
dijit that should act as the editor. |
|
|
Object |
Any parameters that
should be passed in when constructing the editor for the
|
|
|
String |
The width of the editor. 100% by default. |
|
|
String |
The display value of the widget when in read-only mode. |
|
|
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. |
|
|
Function |
Used to disable and enable the widget. |
|
|
Function |
Sets the value of the widget. |
|
|
Function |
Saves the contents of the editor and reverts to display mode. |
|
|
Function |
Discards any changes made in the editor and reverts to display mode. |
|
|
Function |
An extension point that can be used to be notified of changes to the value. |
|
|
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.
Tip
A good understanding of the dojo.data API is especially helpful for
working with the Tree dijit. See
Chapter 9, Data Abstraction 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.Treeisdijit._TreeNode; the leading underscore in this case signals that you'd never be using a_TreeNodeoutside of aTree. There are, however, several properties of_TreeNodethat are useful to manipulate directly, as we'll see in upcoming examples.- Data agnosticism
The
Treedijit is completely agnostic to the data source that backs it. Prior to version 1.1, it read directly from an implementation of thedojo.dataAPI, 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 thedojo.datamodel and theTreewas added. These intermediating layers aredijit.tree.TreeStoreModelanddijit.tree.ForestStoreModel, respectively. Much of the motivation for the change was to make theTreemuch more robust and amenable to drag-and-drop operations.
Tip
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, “Simple Tree with a root” puts it all together, and Figure 15.3, “The Tree that renders from the data store; clicking on the expando node closes it” shows the result.
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, “Simple Tree with a root” 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, “Updates to show a forest instead of a tree” 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, “Updates to fabricate a root-level node so that a forest
appears like a tree”, 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, “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” shows a simple 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, “Responding to clicks on a Tree”.
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.
Tree-Related APIs
If you've followed along with the examples and have a solid
understanding of the dojo.data
APIs, then you know a lot more about the Tree than you might think at this point.
Still, Table 15.10, “Tree API”'s more formal API listing makes
for a good reference and is helpful to skim over before we enter the
next section, which covers drag-and-drop for the Tree. As you'll see, the Tree itself really just has a few simple
attributes. Most of the heavy lifting is tucked away into the
dijit.tree.model APIs or behind
the scenes entirely.
Tip
As of version 1.1, it is technically still possible to wire
up a Tree directly to a
dojo.data store; however,
because it is quite likely that this pattern may be removed in
version 2.0 and complicates the pattern for using a Tree, it is not presented in this
chapter or included in the following API listing.
Table 15.10. Tree API
|
Name |
Type |
Comment |
|---|---|---|
|
|
|
Interface for uniformly accessing data. |
|
|
Object |
The data store query
that returns the top-level item(s) for the tree. If the
query returns exactly one item, use the |
|
|
Boolean |
Whether to display
the root of the |
|
|
Array |
A collection of
|
|
|
Boolean |
If set to |
|
|
Boolean |
Uses cookies to save
state of nodes being expanded or collapsed. |
|
|
Function |
An extension point
for handling a click (as well as an Enter key press) on an
item. Both the |
Next up is the dijit.Tree.model API, shown in Table 15.11, “dijit.Tree.TreeStoreModel API”. Anything that presents
this interface is just a valid model as the TreeStoreModel used in the previous
example. As would be the case with any other API, this means you can
essentially create whatever abstraction you need to populate a
Tree as long as it meets the
spec—regardless of the underlying data source—whether it be a
dojo.data API, some other open
API, or a completely proprietary API.
Table 15.11. dijit.Tree.TreeStoreModel API
|
Name |
Comment |
|---|---|
|
Used for traversing
the |
|
|
Used for traversing
the |
|
|
Used for traversing
the |
|
|
Used for inspecting
items. Returns the identity for an |
|
|
Used for inspecting items. Returns the label for an item. |
|
Part of the |
|
Part of the |
|
|
Callback used to
update a label or icon. Changes to an item's children or
parent(s) trigger |
|
Callback used for responding to newly added, updated, or deleted items. |
|
|
Destroys the object and releases connections to the store so that garbage collection can occur. |
On top of the TreeStoreModel, the ForestStoreModel (documented in Table 15.12, “dijit.tree.ForestStoreModel API additions”) provides two
additional functions that respond to events related to the
fabricated root-level node; namely, adding and removing items from
the top level. These functions are needed to adjust the query
criteria so that the top level of the tree remains valid when
changes occur. As a data agnostic view, the Tree itself has no responsibility for
updating or manipulating items; the burden is on the application
programmer to ensure that the query criteria remains satisfied.
Hence, the reason these additional functions exist is to enable that
to happen.
To update Example 15.9, “Updates to fabricate a root-level node so that a forest
appears like a tree”,
adjusting an item to meet the top-level query criteria might be as simple as
adjusting its type to be
"category" instead of "language". For example, you might move "Java"
to the top level, update its type to "category" and then provide an
operation for adding specific Java implementations (having a
type of "language") as children.
As you'll see in the next section, the most common use case for
needing to meet these stipulations probably involves
drag-and-drop.
Table 15.12. dijit.tree.ForestStoreModel API additions
|
Name |
Comment |
|---|---|
|
|
Called when an
|
|
|
Called when an
|
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, Drag-and-Drop) 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, “Tree dndController interface”.
Table 15.13. Tree dndController interface
|
Name |
Comment |
|---|---|
|
|
A topic event
processor for |
|
|
A topic event
processor for |
|
|
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. |
|
|
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. |
|
|
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. |
Warning
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, “Tree dndController interface”. 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, “Updates to fabricate a root-level node so that a forest
appears like a tree” 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.
Tip
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, “Simple drag-and-droppable Tree” shows the goods,
and Figure 15.5, “Moving a programming language item to a different
category”
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>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
Warning
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.
Tip
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, “Typical Editor usage”
illustrates an out of the box Editor from markup along with some light
styling and a couple of buttons that interact with it.
Tip
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, “Small subset of the Editor API”.
Tip
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
Editor Architecture
The Editor 's lifecycle
supports three basic phases, shown in Figure 15.6, “The basic phases that the Editor's architecture
supports”. 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'sclosemethod, 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.
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.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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.AlwaysShowToolbartopluginsorextraPluginsto 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
DIVStags, or not to disable the handling of the Enter key entirely. You must pass indijit._editor.plugins.EnterKeyHandlingtopluginsorextraPluginsto enable this plug-in.
Warning
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
pluginsorextraPluginsmay befontName,fontSize, orformatBlock.-
LinkDialog Provides a button with a dialog for entering a hyperlink source and displayed value. Arguments to
pluginsorextraPluginsmay becreateLink.-
TextColor Provides options for specifying the foreground color or background color for a range of text. Arguments to
pluginsorextraPluginsmay beforeColororhiliteColor.-
ToggleDir Provides a means of involving the HTML
dirattribute on theEditor(regardless of how the rest of the page is laid out) so that theEditor's contents could be left-to-right or right-to-left. Arguments topluginsorextraPluginsmay betoggleDir.
To make matters a little less muddy, consider the differences in the snippets of markup shown in Table 15.15, “Different approaches to creating an editor” when creating an editor.
Table 15.15. Different approaches to creating an editor
|
Code |
Effect |
|---|---|
|
|
Creates an |
|
|
Creates an |
|
|
Creates an |
|
|
Creates an |
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
TooltipversusTooltipDialogUse the
Editorto provide a control for entering and editing rich textUse
ToolbarandMenuto provide a means of command and control for the user of your applicationEmbed
TooltipDialoginto aDropDownButtonUse a
ProgressBarto display both determinate and indeterminate indications of progress to a userUse
Dialogto provide a modal alert to the user or otherwise embed arbitrary content into theDialogfor the user to interact withUse
InlineEditables to empower the user with the ability to edit what otherwise appears to be plain markup on the flyUse the
Treedijit 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.











Add a comment



Add a comment