分享

Dojo Tips and Tricks - InsideRIA

 黑传说 2010-05-15

This article provides 10 tips and best practices (in no particular order) for maximizing the benefits that Dojo can bring to your next project. For a more thorough introduction to Dojo, see the article Dojo: The JavaScript Toolkit with Industrial Strength Mojo or pick up a copy of Dojo: The Definitive Guide.

1 - Use AOL's Content Delivery Network to get up and running quickly(利用AOL或者google的js相关服务)

Although you'll likely want to download the entire toolkit at some point, using AOL's CDN is perhaps the simplest ways to get started. In addition to fostering rapid prototyping by giving you access to the entire toolkit at the cost of inserting a SCRIPT tag, it can also streamline some of the configuration management for a project. For example, if you don't want to check a specific version of Dojo into your own project's repository or don't want to require other members on your team to have to go through the extra steps of downloading, configuring, and maintaining a development environment just yet, it's the way to go. It works well for dabbling and production alike.

Here's an example template that loads the latest 1.2 release from the CDN, which comes across the wire gzipped at just under 30KB:

<html>
<head>
<title>Dojo ala CDN from AOL</title>

<script type="text/javascript"
src="http://o./dojo/1.2/dojo/dojo.xd.js">
</script>

<script type="text/javascript">
dojo.addOnLoad(function() {
/* Do stuff with dojo.* in here! */
});
</script>
</head>
<body>
<a href="http://">Dojo</a>
</body>
</html>

In short, the dojo.xd.js file is a special XDomain build of Base, the kernel of the toolkit, and defines the global identifer dojo. On that note, it's a good habit to confine your use of the toolkit within the dojo.addOnLoad callback that fires when the toolkit has loaded in order to prevent race conditions that may arise when using an XDomain build.

2 - Use dojo.query for find-it-and-do-something-with-it operations(使用dojo.query查某样东西/做某项操作)

The dojo.query function allows you to provide a CSS selector to find nodes in the DOM and then do something with them. For example, to find all of the hyperlinks on a page, you'd simply do a call to dojo.query("a"). The result of a dojo.query operation is a NodeList of DOM elements, and a NodeList is essentially an Array with some handy extensions that provide the syntactic sugar of chaining together results. For example, consider the following block of code that finds all of the hyperlinks in a page with rel attribute of "special", adds a foo class to them and sets up a click handler:

dojo.query("a[rel=special]")
.addClass("foo")
.onclick(function(evt) {
console.log("Just added the foo class to the following anchor tag", evt.target);
});

It is also noteworthy that most of the methods provided by NodeList such as addClass, removeClass, style, and so forth are just simple wrappers around handy utility functions already provided by Base. Hopefully, it's not very hard to imagine all of the code you can save yourself from writing and maintaining with a friend like dojo.query in your corner.

3 - Use Deferreds to simplify I/O handling

Any Web 2.0 is undoubtedly going to be performing a lot of server communication, so the better the abstraction for handling I/O requests, the better. Dojo provides a godsend called Deferred (originating from MochiKit, which in turn borrowed the concept from Twisted) that provides a uniform interface for handling synchronous and asynchronous requests alike while also eliminating the need to keep track of whether or not a request is in flight or already completed. Deferreds provide the basis for Dojo's entire I/O subsystem, so commonly used functions like dojo.xhrGet return Deferreds, although you may not have even realized it. As an example, consider the following boilerplate for an asynchronous XHR request(最初起源于MochiKit,借用的是Twisted的概念。):

var dfd = dojo.xhrGet({
url : "/example",
handleAs : "json", // interpret the response as JSON vs text
load : function(response, ioArgs) {
/* Do something with the response object */
return response;
},
error : function(response, ioArgs) {
/* Handle any errors that occur */
return response;
}
});

/* Regardless of the state of the request, the Deferred is available
right now for chaining more callbacks or errbacks. Regardless of
whether the Deferred has already returned, failed, or still in flight,
the dfd still provides the same uniform interface */

dfd.addCallback(function(response) {
/* If the request is successful, this callback fires
after the first one (defined in "load") finishes. Always return the
response so it can propagate into the next callback */
return response;
});
dfd.addErrback(function(response) {
/* Likewise, this would be the second errback to fire
(right after the one defined in "error") if something goes awry. */
return response;
});

With Deferreds, life becomes considerably more simple when doing lots of I/O because you have to do very little thinking. Just make a request, and then add callbacks and errbacks whenever you need to. The status of the request has no bearing on the way you treat the Deferred that's returned to you.

4 - Use dojo.data to work with "local" data(操作本地数据)

While functions like dojo.xhrGet make it easy to communicate with the server on a per-request basis, the dojo.data module takes streamlining interaction with server-side data sets to a whole new level by providing you with the abstraction of a data store that implements a robust API for reading, writing, and handling events as they occur on data items. While the dojox.data module includes many full blown implementations of useful stores for interacting with Google web services, OPML data, Wikipedia, and lots more, a basic demonstration of the ItemFileWriteStore, which implements all four of the dojo.data APIs (Read, Identity, Write, and Notification) should give you a good idea of how this works.

/* Assuming you wanted to plug this example into the template
for using AOL's CDN, recall the importance of confining
the use of the toolkit to the dojo.addOnLoad block */
dojo.require("dojo.data.ItemFileWriteStore");

dojo.addOnLoad(function() {

/* Create a store with inline JavaScript data. We could have
fetched the exact same data just as easily from a server
by passing in a url to the ItemFileWriteStore constructor */
var store = new dojo.data.ItemFileWriteStore({
data : {
identifier : "id",
items : [
{"id" : 1, "label" : "apple"},
{"id" : 2, "label" : "banana"},
{"id" : 3, "label" : "orange"}
]
}
});

store.onNew(function(item) {
/* Callback for doing something whenever an item is added */
});

store.onDelete(function(item) {
/* Callback for doing something whenever an item is deleted */
});

/* Add an item to the store - a synchronous operation */
store.newItem({"id" : 4, "label" : "pear"});

/* Fetch the pear - an asynchronous operation */
store.fetch({
query : {label : "pear"}, //The Identity API also allows for querying by identifier
onItem : function(item) {

/* Do something with the item, like delete it - a synchronous operation */
store.deleteItem(item);

/* Save the results */
store.save();

/* A call to store.revert(); can revert a store to its last saved state */
}
});

});

While just a glimpse of what dojo.data can do, you hopefully get a idea of how convenient it can be to write application logic that leverages about a local data store (or at least the appearance of one) that conforms to a set a well-defined APIs versus store you had to hack together yourself or no store at all.

Encapsulate reusable UI components into widgets

Dojo provides extensive machinery for building widgets. In short, anything that descends from dijit._Widget is a "dijit" (a Dojo widget) where dijit._Widget itself builds upon the basis provided by dojo.declare for simulating class-based inheritance. To oversimplify things a bit, dijit._Widget provides an infrastructure and lifecycle hooks for achieving specific functionality during the creation and destruction; dijit._Templated further builds on what is provided by dijit._Widget to trivialize the effort required to give a widget a visible DOM manifestation. All of Dijit, Dojo's trove of accessible turn-key widgets, builds upon this fabric, so you're in good company if you use it to build your own widgets.

For a very minimal widget example that exposes just a bit of this fabric, consider the following example widget that would be encapsulated into a file called Dog.js enclosed in a folder named example , which maps the namespace and name of the widget:

/* Enable consumers of the widget to dojo.require it */
dojo.provide("example.Dog");

/* Pull in infrastructure */
dojo.require("dijit._Widget");
dojo.require("dijit._Templated");

/* "Inherit from" _Widget and _Templated to provide a widget */
dojo.declare("example.Dog", [dijit._Widget, dijit._Templated], {

templateString : '<div dojoAttachPoint="messageNode"></div>',

message : '',

postCreate : function() {

/* The dojoAttachPoint attribute in markup makes "messageNode"
available as a property of "this" widget */
this.messageNode = message;
}
});

Dynamically creating the widget and inserting it into the page may be done by simply passing in any applicable construction properties and the ID of a node on the page:

var _widget = new example.Dog({message : "Ruff, ruff."}, "someExistingNodeID");

Alternatively, you could omit the node ID and use the resulting _widget object's domNode property to insert it into the page:

var _widget = new example.Dog({message : "Ruff, ruff."});
dojo.body().appendChild(_widget.domNode);

In order to include the widget in an HTML page and have it automatically parsed and rendered on page load, you would need to pass in some flags to djConfig to specify that the page should be scanned for widgets specified by dojoType tags and automatically parsed as well as provide a base URL and path that maps the example namespace to a physical disk path (relative to the containing HTML document.)

<html>
<head>
<title>Parsing Custom Widgets on Page Load (with XDomain)</title>

<script type="text/javascript"
src="http://o./dojo/1.2/dojo/dojo.xd.js"
djConfig="parseOnLoad:true, baseUrl:'./', modulePaths:{example:'relative/path/to/example/from/this/page'}">

</script>

<script type="text/javascript">
dojo.require("dojo.parser");
dojo.require("example.Dog");
</script>
</head>
<body>

<div dojoType="example.Dog" message="Ruff, ruff."></div>
</body>
</html>

Use dijit.Declaration for markup-driven development

Although the norm is to create widgets in JavaScript code, dijit.Declaration provides the ability to accomplish the very same things in markup. While this approach can be great for prototyping even if you are a JavaScripter, designers may prefer a markup-driven approach all of the time simply because it may seem more natural and less cumbersome. The general idea behind dijit.Declaration and markup-driven development with Dojo is pretty intuitive: you create a DOM structure for the widget and then use dojo/method and dojo/connect SCRIPT tags to define, connect to, or override methods. Here's a very simple example adapted from Dojo: The Definitive Guide that illustrates the concept.

<html> 
<head>
<title>Markup-driven Development with dijit.Declaration</title>

<link
rel="stylesheet"
type="text/css"
href="http://o./dojo/1.2/dojo/resources/dojo.css">
</link>

<!-- define any CSS inline -->
<style type="text/css">
span.hello_class {
color: #009900;
cursor: pointer;
}
</style>

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

<script type="text/javascript">
dojo.require("dijit.Declaration");
dojo.require("dojo.parser");
</script>
</head>
<body>
<!-- delcare a widget completely in markup -->
<div
dojoType="dijit.Declaration"
widgetClass="dtdg.HelloWorld"
defaults="{greeting:'Hello World'}">
<span dojoAttachEvent='onmouseover:onMouseOver, onmouseout:onMouseOut'>
${greeting}
</span>
<script type="dojo/method" event="onMouseOver" args="evt">
dojo.addClass(evt.target, 'hello_class');
</script>
<script type="dojo/method" event="onMouseOut" args="evt">
dojo.removeClass(evt.target, 'hello_class');
</script>
</div>
<!-- now include it into the page like usual -->
<div dojoType="dtdg.HelloWorld"></div>
</body>

</html>

The example does nothing more than present a "Hello World" message enclosed in a node that responds to mouse events to provide highlighting, but hopefully, it gives you the general idea of how you'd go about implementing a design. Note that inside of dojo/method tags, you can reference the enclosing widget through the keyword this just like you would in methods that are defined in JavaScript.

At first thought, this approach seems to do little to encourage code reuse since you don't end up with a collection of reusable JavaScript files, but it's totally conceivable that you could create a robust system that consolidates markup-driven widgets into physical templates or isolated PHP files. The technique of designing widgets with dijit.Declaration is perhaps one of the most overlooked aspects of the toolkit, which is unfortunate given how much it can lower the barrier for designers and programmers.

Skin your widgets with themes(给所用的部件使用皮肤)

Dijit comes packed with three great looking themes out of the box: Tundra, Soria, and Nihilo. (The previous links take you to Dijit's theme tester where you can explore the skins for yourself.) In case you're wondering, Tundra's inspiration is the tundra landscape and exhibits light blue and grey hues, Soria is predominantly a glossy looking blue on blue theme (rumored to have been inspired from a view of the sky taken around Soria, Spain), and Nihilo (playing off of the  ex nihilo concept) is an extremely minimalistic theme with thin grey outlines and a touch of blue and yellow.

Putting a baked in theme to work is as simple as including the appropriate CSS files and declaring a class on the BODY tag. For example, the following example applies Soria to the page. Using Tundra or Nihilo would simply entail doing a global replacement of the word "soria" to "tundra" or "nihilo".


Buttons styled with Dojo's Built-in Themes

<html>
<head>

<title>Putting Built-in Dijit Themes To Work</title>

<!-- Always pull in dojo.css first 需要先导入全局性的的dojo.css-->
<link
rel="stylesheet"
type="text/css"
href="http://o./dojo/1.2/dojo/resources/dojo.css">
</link>

<!-- Now grab the Soria theme 导入所需的皮肤-->
<link
rel="stylesheet"
type="text/css"
href="http://o./dojo/1.2/dijit/themes/soria/soria.css">
</link>

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

</script>

<script type="text/javascript">
dojo.require("dojo.parser");
dojo.require("dijit.form.Button");
</script>
</head>
<!-- Apply the soria class to the whole page -->

<body class="soria">
<div dojoType="dijit.form.Button">A Soria Button</div>
</body>
</html>

Use gfx for two-dimensional cross-browser drawing(跨浏览器的2维绘图部件)

The dojox.gfx is a really nice piece of engineering and popular subproject of DojoX, a rich ecosystem of specialized and experimental subprojects. gfx (pronounced "graphics" or "g-f-x") module exposes a common 2D drawing API that is loosely based on  SVG and comes stock with several back end implementations that cover all of the common bases. For Firefox or Safari, an SVG back end is used for drawing; in Internet Explorer  VML is the default, but Silverlight may be used if it is available; a  canvas implementation is also provided. Other back end renderers such as Flash could be implemented as well if someone wanted to take a bit of time to hack out the Flash-JavaScript bridge and implement the API. Regardless, the great thing about gfx is that since you're programming against a uniform API, code you write is portable and will "just work" on any modern browser that it runs on.(dojox.gfx是dojox的一个子项目,目前更多的是试验性。基于SVG,后端根据各个浏览器而不同,比如firefox safari,使用的是svg;ie默认使用vml,需要silverlight;html5之后,会变得相同,都使用canvas标签。……)

The following example introduces some of the API with self-descriptive drawing operations. Although this example doesn't illustrate arbitrary 3x3 matrix operations, the gfx API does provide the ability to manipulate arbitrary 3x3 matrices, so you have full spectrum control over the rotation, translation, and scaling of objects that you draw.

<html>
<head>
<title>2D Drawing with dojox.gfx</title>

<script
type="text/javascript"
src="http://o./dojo/1.2/dojo/dojo.xd.js">
</script>
<script type="text/javascript">
dojo.require("dojox.gfx");
dojo.addOnLoad(function() {
var node = dojo.byId("surface");
var surface = dojox.gfx.createSurface(node, 600, 600);
//椭圆形
surface.createEllipse({
cx : 300,//坐标位置
cy : 300,
rx : 50,
ry : 100
})
.setFill("yellow")
;//颜色填充
//方形
surface.createRect({
x : 90,
y : 90,
width : 50,
height : 170
})
.setFill([255,0,0,0.5]) //rgba tuple
;
//圆圈
surface.createCircle({
cx : 400,
cy : 200,
r : 50//半径
})
.setFill([255,0,0,0.5]);

surface.createCircle({
cx : 425,
cy : 225,
r : 50
})
.setFill([0,255,0,0.5])
;

surface.createCircle({
cx : 425,
cy : 175,
r : 50
})
.setFill([0,0,255,0.5])
;
//折线
surface.createPolyline([
100,400,
200,300,
350,350,
500,350
])
.setStroke({
width : 10,
join : "round",
cap : "round"
})
;

surface.createCircle({
r : 50,
cx : 200,
cy: 200
})
.setFill({
type: "radial",
cx : 200,
cy: 200,
r:50,
colors: [
{color:"white",offset:0},
{color:"red",offset:1}]
})
;
});
</script>
</head>
<body>

<div style="width:600;height:600;border:solid 1px" id="surface"></div>
</body>
</html>

Shapes
A very small sampling of some basic shapes drawn with dojox.gfx

The next time you're faced with the task of visualizing some data, consider giving gfx a shot before resorting to Flash. Or at least know that you have the option if you want it.

Use the DataGrid to display large data sets without pagination(无分页,逐步载入大数据)

Although paginating large data sets has become somewhat of an established pattern in the unwritten web development handbook, it's not really the most appealing interface from the standpoint of a user. On the desktop, you just scroll through large data sets most of the time, so why should your web experience have to be any different? Fortunately, the Ajax revolution has provided options for killing pagination dead, and Dojo's terrific DataGrid widget gives it to you as a built-in luxury that you'll soon be taking for granted.(桌面上,比如doc文件,只需要滚动,而没有分页。当然,坏处就是无法快速定位到所需的页面了)

Employing the DataGrid is drastically simpler than it was back in the 1.1, so if it's been a while since you've taken a look at the grid, you're in for a real treat. For most scenarios, you simply point the grid to one of the many stores that implement the dojo.data APIs, specify a minimal configuration and you're done. Most of this can be accomplished in markup, so there's very little learning curve to get a basic grid up and running. The following example illustrates:

<html>
<head>
<title>Employing the DataGrid Dojo Widget</title>

<link rel="stylesheet" type="text/css" href="http://o./dojo/1.2/dojo/resources/dojo.css" />
<link rel="stylesheet" type="text/css" href="http://o./dojo/1.2/dijit/themes/tundra/tundra.css" />
<link rel="stylesheet" type="text/css" href="http://o./dojo/1.2/dojox/grid/resources/Grid.css" />
<link rel="stylesheet" type="text/css" href="http://o./dojo/1.2/dojox/grid/resources/tundraGrid.css" />

<style type="text/css">

#gridNode {
width: 200px;
height: 200px;
}
</style>

<script
type="text/javascript"
src="http://o./dojo/1.2/dojo/dojo.xd.js">
</script>

<script type="text/javascript">
dojo.require("dojo.data.ItemFileReadStore");
dojo.require("dojox.grid.DataGrid");

dojo.addOnLoad(function() {

/* Create a store with inline JavaScript data */
var gridStore = new dojo.data.ItemFileReadStore({
data : {
identifier : "id",
items : [
{"id" : 1, "label" : "Apple"},
{"id" : 2, "label" : "Banana"},
{"id" : 3, "label" : "Orange"}
]
}
});

/* A simple layout that specifies column headers and
mappings to fields in the store */
var gridLayout = [
{name : "ID", field : "id", width : "50%"},
{name : "Label", field : "label", width : "50%"}
];

/* Programatically construct a data grid */
var grid = new dojox.grid.DataGrid({
store : gridStore,
structure : gridLayout
}, "gridNode");

/* Tell the grid to lay itself out since
it was programatically constructed */
grid.startup();
});
</script>

</head>
<body class="tundra">
<div id="gridNode"></div>
</body>
</html>


A few rows loaded into Dojo's DataGrid

In short, you give the grid a store along with a layout that provides a mapping of fields in the store to column names, and that's it. Once constructed, the DataGrid provides fairly fine-grained control over managing the selection, firing event handlers when cells are clicked, and even editing data in the grid. After all, the grid is really nothing more than an extremely fancy view on top of some data.

Use the build system to improve performance(使用构建系统来提升性能)

For development, it's great to be able to throw in a dojo.require statement and slurp in resources when you need them. Unfortunately, however, each of those dojo.require statements incur a round trip to the server, and the latency really adds up -- especially for non-trivial applications with lots of JavaScript goodness inside. Dojo's build system provides the ability to create what's known as a "build profile" that allows you to specify layers for your application and create a consolidated JavaScript file that is a conglomerate of all of the various module dependencies on the page.

For example, if you had an application with two primary views that each had their own dependencies on various Dojo resources, you could use the build system to generate a single JavaScript file that roll up those all of those dependencies. The end result is that you may end up with as few as two I/O request for JavaScript files for each "layer" in your app: one for dojo.js and one for your layer file. While it's technically possible to roll up Base (all of the stuff in dojo.js ) into a layer file, it's usually not advantageous to take this approach since dojo.js is a dependency for any layer file you'd create and can almost always be cached after the first time it is downloaded.

But that's not all. The build system can also minify your JavaScript code by running it through ShrinkSafe. Basically, "minifying" JavaScript code means reducing overall file size by doing things like replacing symbol names with shorter symbol names and eliminating whitespace in the code. While the concept may sound deceptively simple, the end result can make quite a difference on the user experience. While the build system offers a vast number of other useful options that can be rather overwhelming, the most common usage is to simply define a build profile that ensures that the JavaScript code is run through ShrinkSafe, the CSS files are consolidated, and template strings are interned.

Consider the following simple build profile bundled with the toolkit and located at util/buildscripts/profiles/fx.profile.js , which specifies a layer that bundles up some handy animation utilities that take advantage of dojo.fx.

dependencies = {
layers: [
{
name: "../dojox/fx.js",
dependencies: [
"dojo.NodeList-fx",
"dojox.fx.ext-dojo.NodeList",
"dojox.fx",
"dojo.fx.easing"
]
}
],

prefixes: [
[ "dijit", "../dijit" ],
[ "dojox", "../dojox" ]
]
};

To deconstruct that a bit, the profile specifies that a layer named fx.js should be produced (which will be created in the top level dojox folder), which rolls up everything in the the dojo.NodeList-fx, dojox.fx.ext-dojo.NodeList, dojox.fx, and dojo.fx.easing modules.

Before you can actually put that profile to work, however, you should actually know that in order to get the build tools, you'll need to check out the code from Subversion because the build tools aren't included in a typical download. The repository can be found at http://svn./src/, and if you wanted to check out the entire toolkit, you'd want to execute the following command, which uses svn externals to simplify matters a bit:

http://svn./src/view/anon/all/trunk/.

After downloading the build tools, you might invoke the build.sh or build.bat scripts as follows:

bash build.sh optimize=shrinksafe action=release profile=fx releaseName=fx cssOptimize=comments copyTests=false

Invoking build.sh or build.bat script without any arguments provides a complete list of options, but in short, the example given produces build of the toolkit that includes a minified version of everything in dojo.* and dojox.* along with a layer file called fx.js that "rolls" up the dependencies outlined in the build profile.

Beyond

There's only so much ground to be had in a short article, so be sure to pick up a copy of Dojo: The Definitive Guide for more complete coverage of the toolkit. In the meanwhile, why not download Dojo and put some of this newly found knowledge to work.


    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多