In-depth understanding of DOJO’s core interface

Original link: http://www.ibm.com/developerworks/cn/web/1303_zhouxiang_dojocore/#ibm-pcon

Dojo core

< p>With the gradual popularity of Web2.0 applications, a good Web front-end framework that supports Web2.0 development becomes more and more important. There are many web application development frameworks on the market, and Dojo is one of the most outstanding front-end web application development frameworks. The power of Dojo lies not only in the various controls it provides, but also in the object-oriented development model it provides, as well as various application-level frameworks. In addition, it is worth mentioning that Dojo provides many very powerful core interfaces. Based on these interfaces, we can efficiently and quickly implement various logics and algorithms required in the application. These interfaces support various browsers, so that we no longer need to consider various implementation issues caused by browser differences.

These Dojo interfaces greatly simplifies the complexity of our Web front-end development, allowing us to in a shorter time Achieve more feature-rich applications. This article will focus on the various conveniences that Dojo’s core interface brings to Web development engineers and some of its use skills.

Introduction to Dojo core interface

The core interface of Dojo is mainly located in Dojo’s three major libraries (“dojo”, “dijit” and “dojox”). In the “dojo” package, it includes some of the most common components and modules in daily Web development, and the coverage is also very wide, including AJAX, DOM operations, object-oriented model development, events, Deferred, data (data stores), drag and drop ( DND) and international components and so on. Many very powerful and practical core interfaces are used in these common components, which can bring considerable convenience to our daily Web development. In this article, we will introduce these core interfaces in detail (the code examples in the article are mainly based on Dojo version 1.7 and later).

Since the core interface of Dojo is more complex and contains a lot of content, this article divides the core interface of Dojo into two parts: core basic interface and core function interface. Let’s first take a look at the core basic interface of Dojo.

Core basic interface

The core basic interface is the most common set of interfaces in Dojo, but any Web based on Dojo will basically involve these interfaces.

Kernel interface (dojo/_base/kernel)

The Kernal component contains some of the most basic features in the core of Dojo. This component is often not developed by It is introduced directly by the personnel, but is introduced in time by introducing some other common core components.

List 1. Kernel code example

 require(["dojo/_base/ kernel"], function(kernel){ kernel.deprecated("dijit.layout.SplitContainer", "Use dijit.layout.BorderContainer instead", "2.0"); kernel.experimental("acme.MyClass"); var currentLocale = kernel.locale; query(".info").attr("innerHTML", kernel.version); });

First of all, the deprecated method of Kernel is used in The console outputs related warning messages, such as some packages or methods have been deleted, modified, or the user is using an old version of a method, etc. The information output through deprecated will only be output to the console when “isDebug” is set to true in dojoConfig.

Then, the experimental method is the same as deprecated, but it is mainly used to remind users that some modules or methods are experimental, please use it with caution.

Finally, the “locale” and “version” attributes represent internationalization information and Dojo version information, respectively.

Kernel also has a global attribute, which is an alias for the window object in the browser:

Listing 2. Kernel’s global code example

 require(["dojo/_base/kernel", "dojo/on"], function(kernel, on){ on(kernel.global , "click", function(e){ console.log("clicked: ", e.target); }); });

The operation in Listing 2 is equivalent Yu tied a “click” event to the window object.

Dojo.config interface (dojo/_base/config)

The Config interface should be the interface we are most familiar with. When we introduced Dojo Will do some global configuration first, using Dojo’s Config interface.

List 3. Config basic code example

 // Via "data-dojo -config" attribute configuration     Dojo dojo.config Tutorial    

...

// Configure via JavaScript variables Dojo dojoConfig Tutorial

...

< /html>

It can be seen that there are two ways to configure Config, the “data-dojo-config” attribute or the “dojoConfig” variable, and the effect is the same.

Next, let’s take a closer look at the meaning of these configuration parameters:

isDebug: Set to “true” to enter Dojo’s debug mode, at this time, no matter what browser you use , You will see Dojo’s extended debugging console, and be able to type and run any code you want to run in it. At the same time, you can also output debugging information through the Console tool that comes with Dojo. If it is in debug mode, you can also see many other debugging information, such as warning messages output by “deprecated” or “experimental”.

debugContainerId: Specify the ID of an element on the page. Dojo will place the extended debug console in this element.

locale: Dojo will determine the value of locale according to the browser’s locale, but here we can also force it.

addOnLoad: The function is the same as “dojo.ready()” and “dojo.addOnLoad”. The usage is as follows:

djConfig.addOnLoad = [myObject, “functionName”] or djConfig.addOnLoad = [myObject, function(){}];

require: When “dojo. After js” is loaded, the module set by require will be loaded immediately.

An array of module names to be loaded immediately after dojo.js has been included in a page.

dojoBlankHtmlUrl: The default value is “dojo/resources/blank.html”. When we use some cross-domain Ajax request components of dojo.io.iframe, dojo.back or dojox, we need to temporarily create an empty page for the iframe to use, and it will find this blank page through the value of dojoBlankHtmlUrl. The user can set it to any path you want.

useCustomLogger: Whether to use a custom console.

transparentColor: Define the transparent color in dojo.Color, the default is [255,255,255].

defaultDuration: Set the default duration of the animation (dijit.defaultDuration)

The above are the configuration parameters of dojoConfig, we can also add our own custom parameters for our application Program usage:

List 4. Config custom parameter code example

 var dojoConfig = {parseOnLoad:true, myCustomVariable:true} require(["dojo/_base/declare", "dojo/_base/config"], function(declare, config){ declare("my.Thinger", null, {thingerColor: (config.myCustomVariable? "wasTrue": "wasFalse"), constructor: function(){ if(config.myCustomVariable){ ...}} }); });

< p>Visible, here we only need to add our custom variable “myCustomVariable” to dojoConfig, and then it can be used in subsequent applications through config.myCustomVariable.

We can also turn on or off certain functions through the “has” parameter:

List 5. Config has code example

 

As you can see, here we have enabled Dojo’s default debugging tool and “deprecated” and “experimental” messages are displayed , But turned off the AMD module scanning function.

Finally, let’s take a look at how to relocate modules through Dojo’s Config:

Listing 6. Config’s module relocation code example

< p>

 

As you can see from Listing 6, here we can use the “packages” parameter to set The correspondence table between the module and the actual location, we can also define the correspondence table between different addresses through the “paths” parameter. aliases is also a very useful parameter: set aliases for modules or files. When some modules in our project need to adjust the path, but do not want to affect those application units that are using the module, we can use aliases to achieve a seamless upgrade. There are also many parameters, such as: “cacheBust” and “waitSeconds”, etc., which are also more useful attributes. Readers can pay attention to them.

Dojo’s loader related interface

Dojo’s loader module is specially used for loading Dojo components, and it contains various methods for loading components.

First, let’s take a look at the “dojo/ready” related interface:

List 7. Loader’s dojo/ready module code example

 // Method 1: require(["dojo/ready", "dojo/dom", "dojo/parser", "dijit/form/Button" ], function(ready, dom){ ready(80, window, function(){ dom.byId("myWidget").innerHTML = "A different label!"; }); }); // Method 2: require( ["dojo/domReady!"], function(){ // After the DOM is loaded, start executing if(dayOfWeek == "Sunday"){ document.musicPrefs.other.value = "Afrobeat";} });

Method 1 may make some readers feel awkward, because when we use the “ready” method, most of us only pass in a callback function. In fact, the “ready” method can accept three parameters: ready (priority, context, callback), but we usually only pass in the third parameter. priority means priority, here the default is 1000, if we pass in 80, it means that the callback function will be triggered when the DOM is loaded but the “dojo/parser” is not completed. context Here represents the context of the callback method set. Of course, we can also use method 2 to save a lot of code.

Let’s take a look at the “dojo/_base/unload” module related interface, which is also a member of the loader module. Let’s take a look at the “addOnUnload” method first. This method is triggered based on “window.onbeforeunload”. Because it is executed before the page is unloaded, the document and other objects of the page are not destroyed at this time. Therefore, at this time, we can still perform some DOM operations or access JavaScript Object attributes:

List 8. Loader’s dojo/_base/unload module code example

 require(['dojo/_base/unload','dojo/_base/xhr'], function(baseUnload, xhr){ baseUnload.addOnUnload(function(){ console.log("unloading..."); alert("unloading..."); }); baseUnload.addOnUnload(function(){ // Try to use synchronous AJAX request when unload xhr("POST",{ url: location.href, sync: true, handleAs: " text", content:{ param1:1 }, load:function(result){ console.log(result);} }); }); // Similarly, you can also bind the object method window.unLoad=function() {console.log("an unload function"); return "This is a message that will appear on unLoad."; }; baseUnload.addOnUnload(window, "unLoad"); });

< p>

Note that we can add more A “unload” callback function.

Let’s take a look at “addOnWindowUnload” again. This method is triggered based on “window.onunload”. Therefore, at this time, it is strongly recommended that readers perform some DOM operations or access JavaScript object properties in the callback function, because at this time These DOM and JavaScript objects are probably no longer available.

Dojo’s loader module also contains many backward compatible interfaces, such as the familiar “dojo.provide”, “dojo.require”, “dojo.requireIf”, and “dojo.platformRequire” , “Dojo.declare”, “dojo.registerModulePath” (new method: require({paths:…}) or paths configuration parameter in config) and so on. Corresponding to these interfaces is the AMD interface. AMD is defined in “dojo.js” to support asynchronous module definition and loading, mainly the “define” and “require” interfaces. Let’s take a look at the “require” interface first, the practical way is very simple.

Listing 9. AMD’s require code example

 require( configuration, // Configuration parameters, such as paths:["myapp", "../../js/com/myapp"] dependencies, // ID of the requested module (Module) callback // callback function after the module is loaded)- > The return value is undefined // Use example 1 require([ "my/app", "dojo" ], function(app, dojo){ // your code}); // Use example 2 require( moduleId // module ID (string)) -> any // Usage example 3 require(["http://acmecorp.com/stuff.js"], function(){ // simply execute stuff.js code}); 

It can be seen that the require interface is very convenient to use. Usually we mainly use it in the way of example 1. Example 2 is also a way to use it, mainly by passing in the ID of the module To return this module, but this mode needs to ensure that the module has been defined and loaded. Usage example 3 shows how to load remote non-module scripts.

Similarly, defining the AMD module is very simple:

Listing 10. AMD’s define code example

 define( moduleId, // defines the ID dependencies of the module, // defines the pre-loaded module factory // the content of the module, or a function that returns the content of the module) // usage example 1 define( ["dijit/layout/TabContainer", "bd/widgets/stateButton"], definedValue ); // Usage example 2 define( ["dijit/layout/TabContainer", "bd/widgets/stateButton"], function (TabContainer, stateButton){ // do something with TabContainer and stateButton... return definedValue;} );

Using example 1 shows the contents of the simplest module, using Example 2 is the function we usually use to return the contents of the module. For simple module definitions, you can choose the method of example 1, but in general, it is recommended that you use the method of example 2 to build your own custom modules.

AMD also includes some gadgets:

List 11. AMD’s gadget code example

 // conversion of module path to actual path require.toUrl( id // module ID or resource identification prefixed by module ID) -> return specific path (string) define( ["require", ...], function(require, ...){ ... require.toUrl("./images/foo.jpg") ...} // relative module ID ---> absolute Module ID require.toAbsMid( moduleId // Module ID) -> Absolute module ID (string) // unregister module require.undef( moduleId // module ID) // output log require.log( // log content) 

It can be seen that these gadgets of AMD are very useful, and readers are recommended to pay attention to it.

Dojo’s AMD loader also has an event interface, It can monitor and respond to some loader-specific events, such as: error messages, configuration changes, tracking records, etc.:

Listing 12. AMD’s micro-event code example

 require.on = function( eventName, // event name listener // trigger function) var handle = require.on("config", function(config, rawConfig){ if(config.myApp.myConfigVar){ // When config changes, process related affairs} }); // Error event function handleError(error){ console.log(error.src, error. id);} require.on("error", handleError); 

Listing 12 shows the loader's micro-event interface description and corresponding usage examples. These contents are easy to be ignored by us, but in fact, they can often be used in some cases. Useful, I hope readers will pay attention to it.

Next, let’s look at a few loader plug-ins, the first is the i18n plug-in, which is specifically used to load and use internationalized files:

List 13. Loader I18n plugin

 require(["dojo/i18n!../../_static/dijit/nls/common.js ", "dojo/dom-construct", "dojo/domReady!"], function(common, domConst){ domConst.place("
    " + "
  • buttonOk: "+ common.buttonOk + "" + "
  • buttonCancel: "+ common.buttonCancel + "
  • " + "
  • buttonSave:" + common.buttonSave + "
  • " + "
  • itemClose: "+ common.itemClose + "
", "output" ); }); define({ root: {buttonOk: "OK", buttonCancel: "Cancel" ........} de : true, "de-at": true });

The way to use internationalized files is very simple, just prefix the file path with "dojo/i18n!" .

Similarly, the "dojo/text" plugin is the same, the difference is that it is used to load the content of the file:

List 14. Loader’s text plugin

h5>

 define(["dojo/_base/declare", "dijit/_Widget", "dojo/text!dijit/templates/Dialog. html"], function(declare, _Widget, template){ return declare(_Widget, {templateString: template }); }); require(["dojo/text!/dojo/helloworld.html", "dojo/dom-construct ", "dojo/domReady!"], function(helloworld, domConst){ domConst.place(helloworld, "output"); });

Listing 14 The usage pattern of the "dojo/text" plug-in is listed, which is basically similar to the i18n plug-in, but the variable it returns is not an object, but a string of file content.

Let’s take a look at the "dojo/has" plugin again. This plugin should be used very frequently in our daily development: feature detection. It is mainly used to detect whether certain functions are available, or whether the function has been loaded and ready. You can even select and load conditionally when you require:

List 15. Loader's has plug-in

 require(["dojo/has", "dojo/has!touch?dojo/touch:dojo/mouse", "dojo/dom", "dojo/domReady!"], function(has, hid, dom){ if(has("touch")){ dom.byId("output").innerHTML = "You have a touch capable device and so I loaded dojo/touch."; }else {dom.byId("output").innerHTML = "You do not have a touch capable device and so I loaded dojo/mouse.";} });

< /p>

Here we see "dojo/has!touch?dojo/touch:dojo/mouse", which is what we call conditional selection loading. If touch is true (the program runs on a touch screen device), then Load the "dojo/touch" module, otherwise load the "dojo/mouse" module. Similarly, the callback function is also judged by has("touch").

In addition to "dojo/has", "dojo/sniff" also belongs to one of them. It is mainly used to detect the relevant features of the browser.

List 16. The sniff module for feature detection

 require(["dojo/ has", "dojo/sniff"], function(has){ if(has("ie")){ // IE browser special processing} if(sniff("ie"){ // IE browser special processing} ); if(has("ie") <= 6){ // IE6 and before} if(has("ff") <3){ // Firefox3 before} if(has("ie") == 7){ // IE7} });

The sniff object can be used for detection here, and the has object can also be used for detection.

There is also a "dojo/node" plugin, which is specifically used to load Node.js modules in Dojo. The usage is similar to the i18n and text plugins, so I won’t discuss it further here.

Basic lang related interfaces

"dojo/base/lang" contains many practical basic interfaces. If we use the old synchronous loading method "async : false", the module will be loaded automatically. If it is asynchronous, you need to display the quote:

Listing 17. Quote the lang module

 require(["dojo/_base/lang"], function(lang){ // introduce lang module});

Next, let’s take a look at its main Functional interface, first is the clone interface, which is used to clone objects or arrays. The usage is as follows:

List 18. Clone interface of module lang

 require(["dojo/_base/lang", "dojo/dom", "dojo/dom-attr"], function(lang, dom, attr){ // Clone object var obj = {a:"b", c:"d" }; var thing = lang.clone(obj); // clone array var newarray = lang.clone(["a", "b", " c"]); // Clone node var node = dom.byId("someNode"); var newnode = lang.clone(node); attr.set(newnode, "id", "someNewId"); });< /pre> 

It can be seen that the use of the clone interface here is very simple, but you must note: This interface needs to be paid attention to in the daily development of the Web. JavaScript is very important for arrays. The operation with objects is usually to pass references. Assigning the same object to different variables actually points to the same object. If there are two different pieces of logic that need to operate on this object, just using two variables is not feasible! We need to make a clone to avoid errors caused by operating the same object.

Let’s look at the delegate interface again:

Listing 19. The delegate interface of module lang

 require(["dojo/_base/lang", function(lang){ var anOldObject = {bar: "baz" }; var myNewObject = lang.delegate(anOldObject, {thud: "xyzzy"} ); myNewObject.bar == "baz"; // proxy anOldObject object anOldObject.thud == undefined; // thud is only a member of proxy object myNewObject.thud == "xyzzy"; // thud is only a member of proxy object anOldObject. bar = "thonk"; myNewObject.bar == "thonk"; // With the change of the anOldObject property, the myNewObject property changes accordingly. This is the proxy, and it will always be a reference to the proxy object});

I believe the reader will be able to fully understand it by referring to the code comments in Listing 19. Next we are looking at an interface: replace, which is mainly used for string replacement. In fact, we know that JavaScript itself has this kind of basic interface, but the interface provided by the module lang is more powerful:

Listing 20. Replace interface of the module lang

require(["dojo/_base/array", "dojo/_base/lang", "dojo/dom", "dojo/domReady!"], function(array, lang, dom){ function sum(a){ var t = 0; array.forEach(a, function(x){ t += x; }); return t;} dom.byId("output" ).innerHTML = lang.replace("{count} payments averaging {avg} USD per payment.", lang.hitch( {payments: [11, 16, 12] }, function(_, key){ switch(key) {case "count": return this.payments.length; case "min": return Math.min.apply (Math, this.payments); case "max": return Math.max.apply (Math, this.payments) ; Case "sum": return sum (this.payments); sum (this.payments) return sum (this.payments) case "avg" his.payments) / this.payments.length; It is not the same as the second parameter here, and the second method used here is not the same as our second place parameter;

A function, that is, we can implement some of our custom complex conversion logic through the function.

Next, there are interfaces such as extend, mixin, exists, getObject, setObject, hitch, partial, etc. We can't get familiar with these interfaces anymore, and we won't go deep, but here is a reminder that extend and mixin are very similar , The main difference is that extend mainly operates on prototype, while mixin only targets objects. The difference between hitch and partial is mainly in the context of function execution.

In addition, the following functions are also very useful, I hope you will pay attention:

isString(): Determine whether it is a string.

isArray(): Determine whether it is an array.

isFunction(): Determine whether it is a function object.

isObject(): Determine whether it is an object.

isArrayLike(): Determine whether it is an array.

isAlien(): Determine whether it is a basic JavaScript function.

declare interface

Everyone should be familiar with this interface. Its function is similar to the "dojo.declare" in the old version of Dojo. It is mainly used For declaring and defining "classes", only the way of using them is slightly changed:

List 21. declare interface

 define(["dojo/_base/declare"], function(declare){ var VanillaSoftServe = declare(null, {constructor: function(){ console.debug ("adding soft serve");} }); var OreoMixin = declare(null, {constructor: function(){ console.debug("mixing in oreos"); }, kind: "plain" }); var CookieDoughMixin = declare(null, {constructor: function( ){ console.debug("mixing in cookie dough"); }, chunkSize: "medium" }); }; return declare([VanillaSoftServe, OreoMixin, CookieDoughMixin], {constructor: function(){ console.debug("A blizzard with "+ this.kind +" oreos and "+ this.chunkSize + "-sized chunks of cookie dough." );} }); });

This is a simple multiple inheritance example, through "return declare([VanillaSoftServe, OreoMixin, CookieDoughMixin]......;" to return the class (widget) we defined, and then In other places, "require" or "define" is used to indicate that the variable refers to this class (widget). The object "[VanillaSoftServe, OreoMixin, CookieDoughMixin]" in the array is the base class of the custom class. It should be emphasized that the declare here omits the first variable: the name of the class, namely: "declare("pkg.MyClassName", [VanillaSoftServe, OreoMixin, CookieDoughMixin]......;",

< p>If you set this first variable, it will store this string in the "declaredClass" variable of the class, and will use "pkg.MyClassName" as a global variable to facilitate the construction of objects of this class in the future .

There is another interface that needs to be emphasized: safeMixin(), which is an interface method defined in dojo/declare, specifically used to add additional methods to existing classes. Its function and lang .mixin is the same, but in addition to merging methods or attributes, it also ensures that the incorporated method is compatible with the class defined by declare. Refer to the following example:

Listing 22. declare SafeMixin interface

 require(["dojo/_base/declare", "dojo/_base/lang"], function(declare , lang){ var A = declare(null, {m1: function(){ /*...*/ }, m2: function(){ /*...*/ }, m3: function(){ /* ...*/ }, m4: function(){ /*...*/ }, m5: function(){ /*...*/} }); var B = declare(A, {m1: function (){ // The declare method also ensures that the definition method is compatible with the class itself. For example, we can directly call // this.inherited(arguments) return this.inherited(arguments); // call A.m1} }) ; B.extend({ m2: function(){ // The extend method of the class also ensures that the definition method is compatible with the class itself retu rn this.inherited(arguments); // call A.m2} }); lang.extend(B, {m3: function(){ // lang’s extend method cannot guarantee that the method of the definition is compatible with the class itself, so To add the method "m3" itself return this.inherited("m3", arguments); // call A.m3 }); var x = new B(); declare.safeMixin(x, {m4: function(){ / /declare's safeMixin can ensure that the definition method is compatible with the class itself return this.inherited(arguments); // call A.m4 })); lang.mixin(x, {m5: function(){ // ordinary Mixin is not guaranteed to be compatible with return this.inherited("m5", arguments); // call A.m5 }); });

Readers can refer to the list 22 comments in the code, and focus on the "declare.safeMixin" method, don't confuse it with the normal lang.mixin method.

After introducing the core basic interface of Dojo, we should have a general impression of the core interface of Dojo. These basic interfaces seem to have simple functions, but they are an indispensable part of our daily Web development, especially for the development of complex RIA rich client applications, these interfaces are even more important.

Next, we are going to start to understand the core function interface of Dojo, which is different from the core basic interface, we will focus on the function. You will see a lot of powerful and practical class objects and their interfaces encapsulated by Dojo. These interfaces will help us solve many problems we encounter in daily Web development, thereby greatly accelerating our development efficiency.

Core function interface

Understanding the core basic interface of Dojo, we can switch to the core function interface of Dojo. Dojo includes a large number of powerful core functions, which bring considerable help and convenience to our daily development. However, it is precisely because of the completeness and richness of Dojo that many readers have no time to take care of all aspects of it in the process of using it. Many very practical interfaces are still unknown and familiar to most people. Next, we will skip some of the functional interfaces that everyone is more familiar with (such as forEach, addClass, etc.), and select some very useful core interfaces that may be ignored by readers, and introduce them in depth. Hope readers will have Harvested.

Deferreds 和 Promises

Deferreds 和 Promises 主要的目的在于让用户更为方便的开发异步调用的程序,该核心功能接口中包含了很多用于管理异步调用机器回调函数的接口,使用起来非常简单,对开发 Web2.0 应用的帮助也非常大。

先来看看 dojo/promise/Promise,这其实是一个抽象的基类,我们熟悉的 dojo/Deferred 类其实是它的一个具体的实现。

清单 23. dojo/promise/Promise 抽象基类实现

 define(["dojo/promise/Promise", "dojo/_base/declare"], function(Promise, declare){   return declare([Promise], {     then: function(){       // 实现 .then() 方法    },     cancel: function(){       // 实现 .cancel() 方法    },     isResolved: function(){       // 实现 .isResolved() 方法    },     isRejected: function(){       // 实现 .isRejected() 方法    },     isFulfilled: function(){       // 实现 .isFulfilled() 方法    },     isCanceled: function(){       // 实现 .isCanceled() 方法    }   });  });

这里我们加入这段示例代码的目的是让读者们对 Promise 可用的接口有一个整体的认识,后面我们会用不同的示例来分别详细介绍这些接口。

之前我们介绍了,dojo/Deferred 类是 dojo/promise/Promise 的一个具体的实现,那么基于 dojo/Deferred 我们肯定可以实现异步调用的管理。这里我们用 setTimeout 来模拟异步调用,参见如下接口:

清单 24. dojo/Deferred 的简单使用

 require(["dojo/Deferred", "dojo/dom", "dojo/on", "dojo/domReady!"],  function(Deferred, dom, on){   function asyncProcess(){     var deferred = new Deferred();    dom.byId("output").innerHTML = "I'm running...";     setTimeout(function(){       deferred.resolve("success");    }, 1000);     return deferred.promise;  }   on(dom.byId("startButton"), "click", function(){    var process = asyncProcess(); process.then(function(results){     dom.byId("output").innerHTML = "I'm finished, and the result was: " + results;    });  });  });

这里我们先构建了一个 dojo/Deferred 对象:“var deferred = new Deferred()”,然后在 asyncProcess 的末尾返回了 deferred.promise 对象。在后面的脚本中,我们使用了这个返回的 promise 对象的 then 方法:"process.then(function(results){...}"。好了,这里要注意了,then 方法是这个 promise 的关键,它是由之前的“deferred.resolve”这个方法触发的,也就是说,当 deferred 对象的 resolve 方法调用的时候,就会触发 deferred.promise 对象的 then 方法,这个 then 方法会调用传给它的回调函数,就是我们代码中最后面看到的“function(results){...}”。这就构成了一个异步调用的管理。试想这样一个场景,这里我们不是 setTimeout,而是一个异步向后台取数据的 AJAX 请求,而我们又不知道当我们点击“startButton”时数据是否返回,所以这里使用 Deferred 和 Promise 是再为合适不过了。

dojo/Deferred 不仅能处理正常返回的情况,也能处理进行中和出错等情况,参见代码如下:

清单 25. dojo/Deferred 的进阶使用

 require(["dojo/Deferred", "dojo/dom", "dojo/on", "dojo/domReady!"],  function(Deferred, dom, on){   function asyncProcess(msg){     var deferred = new Deferred();     dom.byId("output").innerHTML += "
I'm running..."; setTimeout(function(){ deferred.progress("halfway"); }, 1000); setTimeout(function(){ deferred.resolve("finished"); }, 2000); setTimeout(function(){ deferred.reject("ooops"); }, 1500); return deferred.promise; } on(dom.byId("startButton"), "click", function(){ var process = asyncProcess(); process.then(function(results){ dom.byId("output").innerHTML += "
I'm finished, and the result was: " +results; }, function(err){ dom.byId("output").innerHTML += "
I errored out with: " + err; }, function(progress){ dom.byId("output").innerHTML += "
I made some progress: " + progress; }); }); });

很明显,这里除了 resolve 方法,还有 progress 和 reject。 progress 代表进行中,reject 代表出问题,同样,then 方法中,根据参数顺序分别是:resolve 的回调函数,reject 的回调函数,progress 的回调函数。我们可以根据需要做相应的回调处理。

接下来我们来看看 dojo/promise/all,它取代了原先 dojo/DeferredList ,相信熟悉 DeferredList 的读者应该知道它的主要功能了。

dojo/promise/all 同 DeferredList 一样,主要为了处理多个异步请求的情况。假如我们初始化时需要向后台的多个服务发起异步请求,并且我们只关心最迟返回的请求,返回后然后再做相应处理。面对这种情况,dojo/promise/all 是我们的不二选择。

清单 26. dojo/promise/all 的使用

 require(["dojo/promise/all", "dojo/Deferred", "dojo/dom", "dojo/on", "dojo/json",     "dojo/domReady!"],  function(all, Deferred, dom, on, JSON){   function googleRequest(){     var deferred = new Deferred();     setTimeout(function(){       deferred.resolve("foo");     }, 500);     return deferred.promise;   }   function bingRequest(){     var deferred = new Deferred();     setTimeout(function(){       deferred.resolve("bar");     }, 750);     return deferred.promise;   }   function baiduRequest(){     var deferred = new Deferred();     setTimeout(function(){       deferred.resolve("baz");     }, 1000);     return deferred.promise;   }   on(dom.byId("startButton"), "click", function(){     dom.byId("output").innerHTML = "Running...";     all([googleRequest(), bingRequest(), baiduRequest()]).then(function(results){      dom.byId("output").innerHTML = JSON.stringify(results);     });   });  });

这里我们同样还是用 setTimeout 来模拟异步调用,注意最后的“all([googleRequest(), bingRequest(), baiduRequest()]).then(function(results){......}”,这里的 then 就是等待着三个异步调用全部返回的时候才触发的,并且回调函数里的传入的实参是这三个异步调用返回值的共和体。

还有一个类似的处理多个异步调用的类是:dojo/promise/first,它的原理和 dojo/promise/all 正好相反,它自关注第一个返回的请求:

清单 27. dojo/promise/first 的使用

 require(["dojo/promise/first", "dojo/Deferred", "dojo/dom", "dojo/on", "dojo/json",    "dojo/domReady!"],  function(first, Deferred, dom, on, JSON){   function googleRequest(){     var deferred = new Deferred();     setTimeout(function(){       deferred.resolve("foo");     }, 500);     return deferred.promise;   }   function bingRequest(){     var deferred = new Deferred();     setTimeout(function(){       deferred.resolve("bar");     }, 750);     return deferred.promise;   }   function baiduRequest(){     var deferred = new Deferred();     setTimeout(function(){       deferred.resolve("baz");     }, 1000);     return deferred.promise;   }   on(dom.byId("startButton"), "click", function(){     dom.byId("output").innerHTML = "Running...";     first([googleRequest(), bingRequest(), baiduRequest()]).t hen(function(result){      dom.byId("output").innerHTML = JSON.stringify(result);     });   });  });

读者可以看到,这里代码和之前的 dojo/promise/all 示例的代码几乎相同,区别只是:这里是 first,并且回调函数的实参“result”只是这三个异步请求中最早返回的那个异步请求的返回值,有可能是 googleRequest,bingRequest 和 baiduRequest 中的任意一个。

最后我们来看看 dojo/when,它的出现主要用于同时处理同步和异步的请求。设想您并不确定某些方式是否一定是执行了异步调用,并返回了 promise 对象,那么这个时候,then 方法在这里就不可行了。因为如果该函数由于传入参数的不同而执行了同步请求,或者根本没有执行任何请求,并且只返回了一个数值,而不是一个 promise 对象,那么 then 方法在这里是根本不能用的。但是没关系,我们还有 dojo/when:

清单 28. dojo/when 的使用

 require(["dojo/when", "dojo/Deferred", "dojo/dom", "dojo/on", "dojo/domReady!"],  function(when, Deferred, dom, on){   function asyncProcess(){     var deferred = new Deferred();     setTimeout(function(){       deferred.resolve("async");     }, 1000);     return deferred.promise;   }   function syncProcess(){     return "sync";   }   function outputValue(value){     dom.byId("output").innerHTML += "
completed with value: " + value; } on(dom.byId("startButton"), "click", function(){ when(asyncProcess(), outputValue);when(syncProcess(), outputValue); }); });

注意,其实 dojo/when 在这里的作用同之前的 promise 是类似的,asyncProcess 如果正确返回,则会执行后面的 outputValue 函数。但是同时,它也支持 syncProcess,即只返回数值的情况,数值返回后,它同样会执行后面的 outputValue。这么一来,我们的代码将会变得非常简单,我们不用再为处理各种不同返回值的情况而增加大量的额外的代码,dojo/when 已经帮我们全部考虑了。

Events 和 Connections

事件处理也是我们日常开发中必不可少的一个环节,Dojo 在这方面也是不断的优化和推陈出新,希望能提供给开发者们一个强大且使用方便的事件处理组件。

我们先来看看 dojo/on,这是新版 Dojo 主推的一个事件处理接口,它不仅包含了 Dojo 之前版本的所有功能,还提供了很多新的接口,无论从使用的便利性还是从性能上都大大优于之前。

清单 29. dojo/on 的简单使用

 require(["dojo/on", "dojo/_base/window"], function(on, win){   var signal = on(win.doc, "click", function(){     // 解除监听     signal.remove();     // TODO   });  });

可见,绑定事件非常简单,解除绑定也只需 remove 即可。

再来看看 emit() 方法,该方法用于触发事件,类似 fireEvent() 的功能。

清单 30. dojo/on 的 emit 方法

 require(["dojo/on"], function(on){   on(target, "event", function(e){     // 事件处理代码  });   on.emit(target, "event", {     bubbles: true,     cancelable: true   });  });

可以看到,这里我们通过 emit 触发之前绑定的事件,bubbles 这里表示事件按照正常顺序触发,即从底向上。先是元素本身,然后是其父层节点,最后一直到整个页面的顶层根节点(除非在这之间有 listener 调用 event.stopPropagation())。 cancelable 表示该事件是可以被 cancel 的,只要有 listener 调用 event.preventDefault() 便会 cancel 该 event 的事件链。

接下来我们看几个高级用法,首先是 pausable 接口,该接口用于建立可暂停的事件监听器:

清单 31. dojo/on 的 pausable 接口

 require(["dojo/on"], function(on){   var buttonHandler = on.pausable(button, "click", clickHandler);   on(disablingButton, "click", function(){     buttonHandler.pause();   });   on(enablingButton, "click", function(){     buttonHandler.resume();   });  });

很明显,pausable 的使用方式同 on 基本一样,不同的是它的返回值“buttonHandler”有“pause”和“resume”这两个方法。 “buttonHandler.pause()”会保证之前的 listener 不会被触发,而“buttonHandler.resume()”会恢复 listener 的功能。

同样,once 也是一个很实用的接口,该接口保证绑定的事件只会被触发一次,之后就会自动解除事件的监听:

清单 32. dojo/on 的 once 接口

 require(["dojo/on"], function(on){   on.once(finishedButton, "click", function(){     // 只触发一次 ...   });  });

可见,其使用方式同 on。

多事件监听也是 Dojo 的事件机制里面比较有特点的一个功能,它可以将多个事件直接绑定到同一个方法:

清单 33. dojo/on 的多事件监听

 require("dojo/on", function(on){   on(element, "dblclick, touchend", function(e){     // 判断具体触发了哪个事件,并作出相应处理  });  });

这种模式不仅可以节省大量代码,也便于我们管理多事件。

dojo/on 的事件代理功能也是值得我们关注的特性之一,它能够通过 CSS 的 Selector 去定位元素并绑定事件,这是的我们能够非常方便的批量绑定和和处理事件:

清单 34. dojo/on 的事件代理

 require(["dojo/on", "dojo/_base/window", "dojo/query"], function(on, win){   on(win.doc, ".myClass:click", clickHandler);  });  on(document, "dblclick, button.myClass:click", clickHandler);  require(["dojo/on", "dojo/query"], function(on){   on(myTable, "tr:click", function(evt){     console.log("Clicked on node ", evt.target, " in table row ", this);   });  });

清单 34 中可以看出,我们能够通过诸如“:”的方式定位元素并绑定事件,如".myClass:click",即所有 Class 属性中包含 myClass 的节点,同样,它也支持多事件绑定:on(document, "dblclick, button.myClass:click", clickHandler),该行代码表示绑定 document 的 dblclick 事件,以及绑定其下所有子节点中标签为“button”且 Class 属性包含“myClass”的所有节点的“click”事件。

dojo/on 甚至支持自定义的事件监听。如 dojo/mouse 的自定义鼠标事件:

清单 35. dojo/on 监听自定义事件

 require(["dojo/on", "dojo/mouse"], function(on, mouse){   on(node, mouse.enter, hoverHandler);  });

这里就是监听自定义的 mouseenter 事件。

最后,我们来看一个 dojo/on 和 query 协同工作的示例:

清单 36. dojo/on 同 query 协同

 require([   'dojo/on',   'dojo/dom-class',   'dojo/dom-attr',   'dojo/query',    'dojo/domReady!' ], function(on, domClass, domAttr, query) {   var highlighter = {     setCol: function(cellIdx, classStr, tbl) {       var i = 0, len = tbl.rows.length;       for (i; i < len; i++) {         var cell = tbl.rows[i].cells[cellIdx];         if (cell && !domAttr.has(cell, 'colspan')) {            domClass.toggle(cell, classStr)         }       }     },     highlightCol: function(cssQuery, classStr) {       var self = this;    query(cssQuery).on('td:mouseover, td:mouseout', function(evt) {   self.setCol(this.cellIndex, classStr, evt.currentTarget);  });    },     highlightRow: function(cssQuery, classStr) {     query(cssQuery).on('tr:mouseover, tr:mouseout', function() {   domClass.toggle(this, classStr);});    },     highlightBoth: function(cssQuery, cla ssStrRow, classStrCol){       var self = this;       query(cssQuery).on('td:mouseover, td:mouseout', function(evt) {          var tbl = evt.currentTarget;          var tr = evt.target.parentNode;          var td = evt.target;self.setCol(td.cellIndex, classStrCol, tbl);domClass.toggle(tr, classStrRow);});    }   };   highlighter.highlightBoth('#tbl', 'tdHover', 'trHover');  });

读者可以关注一下代码中加粗的三个 query 方法,它们的返回值是直接支持用 on 来批量绑定事件的,并且事件本身也支持事件代理,即基于 CSS 的 Selector 的批量元素绑定事件。通过这个示例,我们可以看到:基于 dojo/on 模块,我们几乎可以随心所欲的管理各种复杂和批量的事件。

关于 dojo/_base/connect(connect & subscribe),dojo/_base/event,dojo/Evented(自定义事件基类)都是大家再为熟悉不过的接口,这里不再介绍。

最后,我们来看看 dojo/behavior。 dojo/behavior 主要模式是定义并添加行为(dojo.behavior.add),然后触发行为(dojo.behavior.apply),使用方式相当简单:

清单 37. dojo/behavior 使用示例

 require(["dojo/behavior"], function(behavior){     // 定义行为    var myBehavior = {        // 所有  节点 :        "a.noclick" : {             // 一旦找到符合条件节点,便绑定 onclick 事件            onclick: function(e){                e.preventDefault(); // stop the default event handler                console.log('clicked! ', e.target);             }         },         // 所有  节点        "span" : {             // 一旦找到符合条件节点,便触发 found 事件            found: function(n){                 console.log('found', n);             }          }     };     // 添加行为    behavior.add(myBehavior);     // 触发行为    behavior.apply();  });

读者可参考注释,这里我们通过 add 添加行为,apply 触发行为。这是 behavior 同事件协同工作的示例,其实 behavior 也能够同 topic 协同工作:

清单 38. dojo/behavior 协同 topic 使用示例

 require(["dojo/behavior", "dojo/topic"], function(behavior, topic){     behavior.add({         "#someUl > li": "/found/li"    });     topic.subscribe("/found/li", function(msg){         console.log('message: ', msg);     });     behavior.apply();  });

这里我们主要关注一下"/found/li"这个 topic,当 behavior 调用 apply 以后,一旦找到符合“#someUl > li”的节点,便会 publish 这个"/found/li"的 topic,此时便会触发我们这里 subscribe 的函数。

Requests

顾名思义,Requests 主要就是指我们常用的 XHR 请求模块,新版 Dojo 中主要是指 dojo/request 对接口做出了一些调整,使得我们使用起来更加方便了。

先来看一个简单的示例:

清单 39. dojo/request 简单示例

 require(["dojo/request", "dojo/dom", "dojo/dom-construct", "dojo/json", "dojo/on", "dojo/domReady!"],  function(request, dom, domConst, JSON, on){   on(dom.byId("startButton"), "click", function(){     domConst.place("

Requesting...

", "output"); request("request/helloworld.json", options).then(function(text){ domConst.place("

response: " + text + "", "output"); }); }); });

大家主要关注一下“request”这一段代码,可以看到,它是和“then”方法一起使用的,options 中传入相关定制参数(如:handleAs,timeout 等等),功能上同之前的 dojo.xhrGet/Post 基本类似,但是这种编程模式比之前的 dojo.xhrPost 的模式更加清晰易懂了。

同样,dojo/request/xhr 接口替代了原有的 dojo/_base/xhr 接口,使用方式如下:

清单 40. dojo/request/xhr 简单示例

 require(["dojo/request/xhr", "dojo/dom", "dojo/dom-construct", "dojo/json", "dojo/on", "dojo/domReady!"],  function(xhr, dom, domConst, JSON, on){   on(dom.byId("startButton"), "click", function(){     domConst.place("

Requesting...

", "output"); xhr("helloworld.json",{ query: {key1: "value1",key2: "value2"},method: "POST", handleAs: "json" }).then(function(data){ domConst.place("

response: " + JSON.stringify(data) + "

", "output"); }); }); });

这里我们注意一下它的参数定制,query 负责传递实参,method 负责定义请求模式,这里是 POST。它支持 4 种模式 GET,POST,PUT 和 DEL。

dojo/request/node 模块是我们能够在 Dojo 中使用 Node.js 的模块发送 AJAX 请求,这里不深入,有兴趣的读者可以研究一下。

再来看看 dojo/request/iframe 模块,该模块主要通过 iframe 发送请求,它取代了 dojo/io/iframe 接口。它除了能够发送基本的 AJAX 请求外,还能够发送跨域的请求,并且可以通过 iframe 实现文件的异步上传,看一个简单的示例:

清单 41. dojo/request/iframe 简单示例

 require(["dojo/request/iframe", "dojo/dom", "dojo/dom-construct", "dojo/json", "dojo/on", "dojo/domReady!"],  function(iframe, dom, domConst, JSON, on){   on(dom.byId("startButton"), "click", function(){     domConst.place("

Requesting...

", "output"); iframe("helloworld.json.html",{form: "theForm",handleAs: "json"}).then(function(data){ domConst.place("

data: " + JSON.stringify(data) + "

","output");  }); }); });

注意,这里我们可以通过 form 参数来指定我们要提交的表单。

同 dojo/request/iframe 一样,dojo/request/script 取代了 dojo/io/script。它主要通过动态 define(["dojo/query!slick/Source/slick"], function(query){ query(".someClass:custom-pseudo").style("color", "red"); });

由此可见,关于 Selector 的模式的定义是非常灵活的,可扩展性非常强。

Parser

Parser 是 Dojo 的解释器,专门用于解析 Dojo 的 widgets。其实平常我们基本不会涉及到使用 dojo/parser,但是,在某些特殊情况下,dojo/parser 可能会带给我们意想不到的便利。并且,它的一些配置参数也是非常值得我们注意的。

其实我们都知道,如何加载和运行 dojo/parser:

清单 50. dojo/parser 的简单示例

 require(["dojo/parser"], function(parser){   parser.parse();  });  require(["dojo/parser", "dojo/ready"], function(parser, ready){   ready(function(){     parser.parse();   });  });  

清单 50 中的三种方式都是可行的,可能最后一种方式是我们用的最多的。

如果单纯调用 parser.parse(),dojo/parser 会解析整个页面,其实我们也能给它限定范围:

清单 51. dojo/parser 的进阶示例

 require(["dojo/parser", "dojo/dom"], function(parser, dom){   parser.parse(dom.byId("myDiv"));  });  require(["dojo/parser", "dojo/dom"], function(parser, dom){   parser.parse({     rootNode: dom.byId("myDiv");   });  });

清单 51 中的代码就将 dojo/parser 限定在了 ID 为“myDiv”的节点内部。dojo/parser 甚至都能改变解析的属性:

清单 52. dojo/parser 的 Scope 示例
 require(["dojo/parser", "dojo/dom"], function(parser, dom){   parser.parse({  scope: "myScope"}); });  
Button 1

很明显,当我们设定了“scope”为“myScope”之后,其解析的属性由“data-dojo-type”变为“data-myScope-type”。

但是仅仅停留在这样对 dojo/parser 的简单的使用模式上,我们永远成不了高手,dojo/parser 还有更多的功能:

清单 53. dojo/parser 编程

 require(["dojo/parser", "dojo/_base/array"], function(parser, array){   parser.parse().then(function(instances){     array.forEach(instances, function(instance){       // 处理扫描到的所有 widget 实例    });   });  });

可见,通过 dojo/parser,我们是可以基于它的返回值继续进行编程开发的,而不仅仅是利用它简单的解析 Dojo 的 widget。这里我们可以拿到所有解析出来的 widget 实例对象,并做出相应处理。

同样,我们还能直接调用“instantiate”方法实例化类:

清单 54. dojo/parser 的 instantiate 方法

 
require(["dojo/parser", "dojo/dom"], function(parser, dom){ parser.instantiate([dom.byId("myDiv")], { data-dojo-type: "my/custom/type"}); });

这种做法和您在页面上写好 {data-dojo-type: "my/custom/type"},然后调用 parser.parse() 的效果是一样的。

既然说到了 dojo/parser,我们也要了解一些关于 dojo/parser 的默认解析行为,让我们看一个下面的例子:

清单 55. dojo/parser 解析行为

 //JavaScript 代码:定义类并解析 require(["dojo/_base/declare", "dojo/parser"], function(declare, parser){   MyCustomType = declare(null, {     name: "default value",     value: 0,     when: new Date(),     objectVal: null,     anotherObject: null,     arrayVal: [],     typedArray: null,     _privateVal: 0   });   parser.parse();  });  //HTML 代码:使用 MyCustomType 类 

这里我们先定义了一个 MyCustomType 类,并声明了它的属性,然后在后面的 HTML 代码中使用了该类。现在我们来看看 dojo/parser 对该类的变量的解析和实例化情况:

name: "nm", // 简单字符串

value: 5, // 转成整型

when: dojo.date.stamp.fromISOString("2008-1-1"); // 转成 date 类型

objectVal: {a: 1, b:'c'}, // 转成对象类型

anotherObject: dojo.getObject("namedObj"), // 根据字符串的特点转成对象类型

arrayVal: ["a", "b", "c", "1", "2"], // 转成数组类型

typedArray: ["a", "b", "c", 1, 2] // 转成数组类型

可见,成员变量的实例化和成员变量最初的初始化值有着密切联系,dojo/parser 会智能化的做出相应的处理,以达到您最想要的结果。注意,这里 _privateVal 的值没有传入到对象中,因为以“_”开头的变量会被 Dojo 理解成私有变量,所以其值不会被传入。另外,anotherValue 也不会实例化,因为该成员变量不存在。

当然,如果我们不喜欢 dojo/parser 的默认行为,我们可以在类里面实现“markupFactory”方法,这个方法专门用来实现自定义的实例化。

Browser History

顾名思义,这一小节主要是关于如何处理浏览器历史。在 Web2.0 应用中,越来越多的使用单页面模式了(没有页面跳转),即用户的所有操作以及该操作所带来的界面的变化都放生的同一个页面上,这样一来,浏览器默认就不会有任何的操作历史记录,也就是说,浏览器上的“前进”和“后退”按钮会永远处于不可用的状态。这个时候,如果我们还想记录用户的一些复杂操作的历史,并能通过浏览器的“前进”和“后退”按钮来重现这些历史,我们就必须借助浏览器的历史管理功能了,Dojo 中就有这样的接口能够方便的让我们管理浏览器的历史:dojo/back。接下来,我们就来看看如何使用该接口:

清单 56. dojo/back 示例

       // body 的其它代码 

可见,首先通过 back.init() 初始化,然后定义一个状态对象,并调用 setInitialState 方法初始化当前历史状态。最后,如果需要再次记录进行一些列操作后的状态到历史状态,调用 addToHistory 即可。这里 state 对象中定义的 back 和 forward 方法,就是为了响应当前历史状态下用户点击“后退”和“前进”的动作。

既然说到了 dojo/back,我们也顺便提一下 dojo/hash,顾名思义,该接口主要用来管理浏览器 URL 的 hash 的历史状态:

清单 57. dojo/hash 示例

 require(["dojo/hash", "dojo/io-query"], function(hash, ioQuery){     connect.subscribe("/dojo/hashchange", context, callback);     function callback(hash){         // hashchange 事件 !         var obj = ioQuery.queryToObject(hash);         if(obj.firstParam){             // 处理代码        }     }  });  require(["dojo/hash", "dojo/io-query"], function(hash, ioQuery){     var obj = ioQuery.queryToObject(hash());  // 取 hash 值    obj.someNewParam = true;     hash(ioQuery.objectToQuery(obj));  // 设置 hash 值 });

这里列举了比较典型的 dojo/hash 用法:

1. 我们能够通过监听“/dojo/hashchange”的 topic 来监听 URL 的 hash 值的变化,并作出相应处理。

2. 我们也能够通过 hash() 取得当前 URL 的 hash 值,同样也能通过 hash(ioQuery.objectToQuery(obj)) 去设定当前 URL 的 hash 值。

Dojo 中还有一个 dojo/router 模块与 hash 有关,它专门用来有条件的触发 hashchange 事件。我们先来看一个示例:

清单 58. dojo/router 示例

 require(["dojo/router", "dojo/dom", "dojo/on", "dojo/request", "dojo/json", "dojo/domReady!"],  function(router, dom, on, request, JSON){   router.register("/foo/bar", function(evt){    evt.preventDefault();     request.get("request/helloworld.json", {       handleAs: "json"    }).then(function(response){       dom.byId("output").innerHTML = JSON.stringify(response);     });   });   router.startup();   on(dom.byId("changeHash"), "click", function(){     router.go("/foo/bar");   });  });

这里我们重点关注一下 router.register 方法,第一个参数其实是用于匹配的 hash 值,它也可以 hash 的一个 pattern,即 RegExp。一旦它匹配了当前的 hash 值,便会触发它的回调函数(第二个参数)。router.startup 用于 router 的初始化,最后的 router.go("/foo/bar") 用于更新当前的 hash 值,所以 router.go 之后便会触发 router 的回调函数。

Mouse, Touch 和 Keys

本小节我们主要来讲讲 Dojo 的一些特殊的事件处理,Dojo 基于标准的 Web 事件机制,对某些事件做了一些封装和优化,解决了交互开发中的很多令人苦恼的问题。先来看看 dojo/mouse,该模块是专门用来优化和管理鼠标事件的,先来看一个示例:

清单 59. dojo/mouse 示例

 require(["dojo/mouse", "dojo/dom", "dojo/dom-class", "dojo/on", "dojo/domReady!"],  function(mouse, dom, domClass, on){   on(dom.byId("hoverNode"), mouse.enter,function(){     domClass.add("hoverNode", "hoverClass");   });   on(dom.byId("hoverNode"), mouse.leave,function(){     domClass.remove("hoverNode", "hoverClass");   });  });

这里我们主要关注一下 mouse.enter 和 mouse.leave。可以看到,我们绑定的事件不再是“onmouseover/onmouseenter”和“onmouseout/onmouseleave”,而是 mouse.enter 和 mouse.leave,这是 Dojo 对 Web 标准事件的一个扩展。很多用过 onmouseover 和 onmouseout 事件的读者可能有所体会,这两个事件其实是非常不完美的:当绑定 onmouseover 事件的节点,它还有很多内部节点时,鼠标在该节点内部悬停或者移动时往往会触发很多次没有意义的 onmouseout 或者 onmouseover,这也是我们不希望看到的。而并不是像我们想象的那样:只有当鼠标移出该节点是才会触发 onmouseout 事件。这一点 IE 浏览器的 onmouseenter/onmouseleave 做的就不错。Dojo 的 mouse.enter 和 mouse.leave 也是基于 IE 的 onmouseenter/onmouseleave 事件的原理所做的 Web 基础事件的扩展,使得所有的浏览器都能使用类似 IE 的 onmouseenter/onmouseleave 事件。

同样,dojo/mouse 还有很多其它功能接口:

清单 60. dojo/mouse 功能接口示例

 require(["dojo/mouse", "dojo/on", "dojo/dom"], function(mouse, on, dom){   on(dom.byId("someid"), "click", function(evt){     if (mouse.isLeft(event)){       // 处理鼠标左键点击事件    }else if (mouse.isRight(event)){       // 处理鼠标右键点击事件    }   });  });

基于代码注释可以看到,mouse.isLeft/Right 用于判断鼠标的左右键。基于这些接口,我们就不用去考虑底层不同浏览器的差异,而只西药关注我们自己的代码逻辑了。

再来看看 dojo/touch,这个接口更是实用了,它可以让一套代码同时支持桌面 Web 应用和触摸屏应用。先来看一个简单的示例:

清单 61. dojo/touch 功能接口示例

 require(["dojo/touch", "dojo/on"], function(touch){   on(node, touch.press, function(e){     // 处理触摸屏的 touchstart 事件,或者桌面应用的 mousedown 事件  });  });  require(["dojo/touch"], function(touch){   touch.press(node, function(e){     // 处理触摸屏的 touchstart 事件,或者桌面应用的 mousedown 事件  });  });

可见,其使用方式非常简单,同 dojo/mouse 一样。这里列出了两种用法,通过 dojo/on 绑定事件或者直接通过 touch.press 绑定均可。

我们可以参照如下的事件对照表:

dojo/touch 事件 桌面 Web 浏览器 触摸屏设备(ipad, iphone)
touch.press mousedown touchstart
touch.release mouseup touchend
touch.over mouseover 合成事件
touch.out mouseout 合成事件
touch.enter dojo/mouse::enter 合成事件
touch.leave dojo/mouse::leave 合成事件
touch.move mousemove 合成事件
touch.cancel mouseleave touchcancel

最后我们来看看 dojo/keys,这个就非常简单了,它存储了所有键盘按键对应的常量。基于这个接口,我们的代码会非常的通俗易懂。参见以下示例:

清单 62. dojo/keys 功能接口示例

 require(["dojo/keys", "dojo/dom", "dojo/on", "dojo/domReady!"],  function(keys, dom, on){   on(dom.byId("keytest"), "keypress", function(evt){     var charOrCode = evt.charCode || evt.keyCode,         output;     switch(charOrCode){       case keys.LEFT_ARROW:       case keys.UP_ARROW:       case keys.DOWN_ARROW:       case keys.RIGHT_ARROW:         output = "You pressed an arrow key";         break;       case keys.BACKSPACE:         output = "You pressed the backspace";         break;       case keys.TAB:         output = "You pressed the tab key";         break;       case keys.ESCAPE:         output = "You pressed the escape key";         break;       default:         output = "You pressed some other key";     }     dom.byId("output").innerHTML = output;   });  });

这里的 keys.LEFT_ARROW,keys.UP_ARROW 等等分别对应着键盘上的“左”键和“上”键等等。这些接口看似功能简单,但是对于我们的代码维护和管理是非常有帮助的。

还有 dojo/aspect,dojo/Stateful,dojo/store ,dojo/d ata,dojo/dom 相关,dojo/html ,dojo/window,dojo/fx,dojo/back,dojo/h ash,dojo/router ,dojo/cookie,dojo/string,dojo/json,dojo/colors,dojo/date,dojo/rpc,dojo/robot 等等功能接口,这些接口大家都比较熟悉,而且很多接口在我之前发表的文章中已经专题讨论并详细介绍过了,这里就不在深入了。其实关于 Dojo 的核心接口还有很多,将来也会不断丰富和完善,这些接口能大大便利我们的日常开发,希望读者们能够了解并早日熟悉起来。

结束语

这篇文章介绍了 Dojo 的一些核心接口及其使用方法,从核心基础接口,如:dojo/_base/kernel,dojo/_base/config,loader 相关等等,到核心功能接口,如:dojo/promise/Promise,dojo/Deferred,dojo/request,dojo/query 等等,依次介绍了影响我们日常 Web 开发的各种接口。不仅介绍了这些接口的用途和优缺点,也给出了很多使用示例来帮助读者们理解和掌握这些接口。针对一些比较重要的接口,还给出了相关的原理的剖析和与现有原始接口的比较,充分揭示了 Dojo 在这些领域的优势。本文主要是基于实际的代码示例来说明这些接口用法,简明直观,推荐大家在日常开发中多参考。

周 翔, 软件工程师, IBM

2013 年 3 月 07 日

随着 Web2.0 应用的逐渐普及,一个好的支持 Web2.0 开发的 Web 前端框架变得越来越重要。现在市面上有很多的 Web 应用开发框架,Dojo 便是其中最为出色的 Web 应用前端开发框架之一。Dojo 的强大不仅仅在于它提供的各种控件,还在于它提供的面向对象的开发模式,以及各种应用级别的框架。除此以外,更值得一提的是:Dojo 提供了很多很强大的核心接口,基于这些接口,我们可以高效快捷的实现应用中所需要的各种逻辑和算法。这些接口支持各种浏览器,使得我们不用再去考虑浏览器差别所带来的各种实现问题。

随着 Web2.0 应用的逐渐普及,一个好的支持 Web2.0 开发的 Web 前端框架变得越来越重要。现在市面上有很多的 Web 应用开发框架,Dojo 便是其中最为出色的 Web 应用前端开发框架之一。Dojo 的强大不仅仅在于它提供的各种控件,还在于它提供的面向对象的开发模式,以及各种应用级别的框架。除此以外,更值得一提的是:Dojo 提供了很多很强大的核心接口,基于这些接口,我们可以高效快捷的实现应用中所需要的各种逻辑和算法。这些接口支持各种浏览器,使得我们不用再去考虑浏览器差别所带来的各种实现问题。

 require(["dojo/parser", "dojo/dom"], function(parser, dom){   parser.parse({  scope: "myScope"}); });  
Button 1

Leave a Comment

Your email address will not be published.