用 Dojo 1.x 开发小部件 - 标签编辑框

学习使用 Dojo JavaScript 工具箱开发 HTML 小部件的基础知识。本文为您提供了一个简介,此外,还给出了几个例子为开发过程提供帮助 — 以简单的示例小部件开始,随后是复杂一些的小部件,同时还突出介绍了在开发过程中可能遇到的一些常见问题。


简介

本文的目标是为您提供使用 Dojo JavaScript 工具箱开发 HTML 小部件的基础知识,首先从版本 1.0 开始。本文还描述了几个示例,以简单的小部件开始,之后是较为复杂的小部件,同时还会解决在小部件开发过程中经常遇到的一些常见问题。

何为 Dojo 工具箱?

Dojo 是一种基于 JavaScript 的开源工具箱,可用来开发动态 HTML Web 应用程序。借助它,可以快速构建较标准 HTML 小部件更为复杂的小部件。使用 Dojo 提供的组件可以让 Web 用户界面的可用性、响应性和功能性都有所提高。由 Dojo 提供的低层 API 和兼容性层可帮助您编写跨浏览器兼容的应用程序。

开始之前

在开始之前,需要设置开发环境。为此:

  1. 从 Dojo 项目站点下载最新版本的 Dojo 工具箱(dojo-release-1.x.x-src.zip 或 dojo-release-1.x.x-src.tar.gz)(相关链接,参见 参考资料)。
  2. 将此归档文件的内容解压缩到一个文件夹,请注意 dojo.js 文件解压缩到的位置。在 dojo.js 载入页面后,软件包系统将负责管理所有模块的加载。

完成之后,文件夹的结构应该类似图 1。

图 1. dojo 文件展开后的文件夹结构

Dijit 是位于 dojo 之上的一个小部件系统。通过自身的主题 tundra,它为其所有的小部件提供了通用的设计和颜色模式。Dojox 是一个开发包,由 Dojo 工具箱的扩展组成。它可用于开发常见集合中所没有的那些功能。

Dojo 小部件

在浏览网站时,会看到数百个小部件呈现在屏幕前。Web 浏览器的每个按钮都是一个小部件。每个文本输入框也是一个小部件。标准的 HTML 提供了小部件的有限集合:一个输入框、一个按钮和一个超级链接。

Dojo 小部件接受像文本输入框这样的条目并会添加一些函数来获得更具用户友好性的对象,比如一个便于选择日期的图形日历。并且在这个过程中,不会破坏新功能所基于的那个原始条目。

一个 Dojo 小部件封装了一些可视 Web 组件以便于重用。它由三个文件定义:

  • 一个 JavaScript 文件,内含小部件逻辑
  • 一个可选 HTML 文件,为小部件提供一个类似 HTML 的模板
  • 一个 CSS 文件,通常对所有小部件通用(主题),内含包含可应用到小部件 HTML 模板的可视样式

导入 Dojo 工具箱

清单 1 显示了可用来将小部件导入到一个常规 Web 页面的基本 HTML 骨架。

清单 1. 将小部件导入到 Web 页面所需的 HTML 代码

   
      Dojo Toolkit Test Page    
      
          
    
 
      
      
   
   
      /* Widgets definition code */
   

第一个脚本标记通过加载 dojo.js bootstrap 文件来初始化 Dojo 工具箱。djConfig 对象的 parseOnLoad 和 isDebug 属性是两个最为常见的配置选项,Dojo 会在运行时对之进行检查。parseOnLoad 在加载时处理标记(mark-up)解析,而 isDebug 启用或禁用调试消息。djConfig 对象还可以在 dojo.js 文件加载前被设置为一个全局变量:

清单 2. 用 djConfig 设置全局变量所需代码

Dojo 包系统

Dojo 是一个包系统,可用来在文件内构造应用程序类并通过 dojo.require 函数加载它们。此函数允许加载在基础 dojo.js 内尚未提供的 Dojo 工具箱的某些部分。

为了创建一个小部件,必须通过添加清单 3 所示的代码行来导入这个小部件声明。

清单 3. 导入小部件声明所需代码

现在,将如下代码插入主体部分:


   
My Button

dojoType 属性的作用是使 Dojo 工具箱以一种特定的方式管理此标记。在页面加载时,Dojo 解析器会查找在 dojoType 属性内指定的小部件声明,初始化这个小部件并用所获得的小部件 DOM 节点替换此标记。

声明一个小部件

现在,让我们来看看这个 TextBox 小部件示例,定义一个 JavaScript 文件、一个模板文件和一个 CSS 样式文件。

首先,必须创建这个 JavaScript 文件 TextBox.js,内含此小部件的定义和逻辑(参见清单 4)。

清单 4. JavaScript 文件 TextBox.js 的内容
dojo.provide("widgets.TextBox");
dojo.require("dijit._Widget");
dojo.require("dijit._Templated");

dojo.declare(
    "widgets.TextBox",
    [dijit._Widget, dijit._Templated],
    {
        /** the template path */
        templatePath: dojo.moduleUrl("widgets", "templates/TextBox.html"),
        /** the input DOM node */
        inputNode: null,       
        /** the label */      
        label: "", 
        
        /** onkeyup handler */
        onKeyUp: function() {
            // give a chance to the browser to update the DOM
            setTimeout(dojo.hitch(this, this.validate), 0);
        },
        
        /** validate function */
        validate: function() {
            if ( this.inputNode.value === "Ok" ) {
                // the text is correct
                dojo.addClass(this.inputNode, "inputOk");
                dojo.removeClass(this.inputNode, "inputError");
            } else {
                 // the text is incorrect       
                 dojo.removeClass(this.inputNode, "inputOk");
                 dojo.addClass(this.inputNode, "inputError");
              }
           }
      }
);

dojo.provide() 定义新的小部件的名称并注册此类声明。注意到:

  • dijit._Widget 和 dijit._Templated 是面向 TextBox 小部件的超类
  • templatePathinputNode 和 label 是小部件的属性
  • onKeyUp() 和 validate() 是两个函数,定义了小部件的逻辑

现在,我们可以定义模板文件 TextBox.html,如清单 5 所示。

清单 5. TextBox.html 的内容
 
   ${label}: 
   
   

${label} 将由此小部件实例的 label 属性替代。

dojoAttachPoint 声明会导致 inputNode 小部件的属性被设置为这个输入标记所对应的那个 DOM 节点。

dojoAttachEvent 声明会导致 onkeyup 事件(来自输入节点)触发 onKeyUp 小部件的方法调用。

类 textBox 和 inputOk 代表的是在 TextBox.css 文件内定义的 CSS 类名。参见清单 6。

清单 6. TextBox.css 内的 CSS 类名
.ibm .textBox {
   margin: 5px;
   padding: 5px;
   background-color: #eee;
}

.ibm .inputOk {
   border: 1px solid green;
}

.ibm .inputError {
   border: 1px solid red;
}

由于类名必须在整个项目内惟一,通常它们都具有一个 CSS 选择器(在本例中为 ibm)。

最后,这个小部件可以在 HTML 页面上创建,如清单 7 所示。

清单 7. 在 HTML 页面上创建小部件所需代码

   
      
      TextBox Widget
      
      

      
      
        
      
      
   

   
   

图 2 显示了这个 TextBox 小部件。

图 2. TextBox 小部件

声明式和编程式

Dojo 支持两种编程模型:声明式和编程式。如果需要,两个模型均可用在同一个页面上。在清单 7 中,小部件由一个声明式模型创建。

同样地,这个小部件也可以用编程式模型创建,如清单 8 所示。

清单 8. 用编程式模型创建 TextBox 小部件

   
      
      TextBox Widget
      
      

      
      
        
      
      
	  
      
   

   
   

有关 Dojo 小部件的更多内容

如之前所述,一个 Dojo 小部件就是一个继承自某个特定类(dijit._Widget 类)的 Dojo 类声明。该类定义了小部件的基本行为,提供了由所有小部件实现所共享的一些常用函数。由 _Widget 类实现的最为重要的任务是:

  • 定义 create 方法,在小部件实例化时自动调用;此方法执行所有所需的创建步骤
  • 定义一些 template 方法,提供面向小部件实现者的特殊 hook,使它们能够在某个特定的创建阶段实现特定的初始化动作
  • 定义大量 destroy 方法,用来清除所分配的小部件资源
  • 定义 event management 方法,用来关联小部件方法与 DOM 节点/方法调用事件

小部件创建阶段

create 方法执行如下的基本步骤来进行小部件初始化:

如果没有 ID,就为这个小部件创建一个惟一 ID。

所有 Dojo 小部件都必须由惟一 ID 标识。在小部件被初始化时,就可以提供小部件 ID。如果不提供,Dojo 就会创建一个具有如下格式的 ID:

packageName_className_number

例如,一个按扭可以有这样一个自动生成的 ID:dijit_form_Button_0。

packageName 是小部件包,点被换成了下划线(例如,dijit_form),而 number 是一个在小部件的基础上不断变大的数字。

在全局小部件注册表中注册这个小部件

随后,可以用 dijit.byId 方法来查找小部件,查找将返回与给定 ID 相对应的小部件对象。注册表是一个存在于 dijit.registry_hash 中的全局空间的一个 Object。使用类似 FireBug(参见 参考资料)这样的调试工具深入此注册表可能会对跟踪(比如跟踪漏掉的小部件)有用。

映射属性

小部件属性可以被重新映射到小部件模板的用户定义节点。要映射的属性及映射的目标可在 attributeMap 对象内列出。这些对象的每个属性都以如下方法定义映射:

  1. 属性名称标识包含将要设置的值的小部件属性名
  2. 属性值标识包含目标 DOM 节点的小部件属性名。如果指定的是一个空字符串,那么就会使用 domNode

清单 9 给出了一个示例

清单 9. 声明一个 dojo 小部件
dojo.declare(
 "MyWidget", 
   dijit._Widget, 
 {
 // the attribute to map (name and value)
 title: "myTitle",

 // the DOM node to be used to set the 
 // title attribute to the "myTitle" value
 titleNode: null,

 // the attribute map 
 // the title attribute is mapped to the titleNode
      attributeMap: {title: "titleNode"},
// the template string
      templateString: "
",

得到的小部件模板将是:

请注意,由于 base _Widget 类已经映射了某些基本的 HTML 属性(如 ID、类、样式),因此实现者不能覆盖 attributeMap 属性。相反,它们必须混合基础类 attributeMap 属性与它们的值。清单 10 显示了从 _FormWidget 类中检索出来的一个示例。

清单 10. 混合基础类 attributeMap 属性与定制值
attributeMap: 
 dojo.mixin(
 dojo.clone(dijit._Widget.prototype.attributeMap),
 {id:"focusNode", tabIndex:"focusNode", alt:"focusNode"}
),

模板方法

在小部件创建阶段,我们要在某些特定的初始化阶段调用一些方法来为实现者提供一个执行一些操作的机会。下面的方法会被调用:

  1. postMixInProperties:这个方法会在所有小部件参数均被设置(混合入)为小部件实例属性后调用。如果基于的是由实例化所提供的那些属性,此时非常适合进行进一步的属性初始化。
  2. buildRendering:在需要为小部件生成呈现时,调用此方法。这个 hook 由 dijit._Templated 混入类实现,用 HTML 模板扩展原始的小部件。实现者可潜在地提供它们的实现来完善 _Templated 类作业。
  3. postCreate:在小部件的 DOM 准备好并被插入到页面后,调用此方法。此时,这个实现者可以在小部件的 DOM 结构之上操作。子小部件尚未启动。
  4. startup:在小部件上操作的最后时机。此时,子小部件已经启动。
  5. uninitialize:在小部件被消毁时调用。实现者有机会执行一些非自动的清理。

模板方法可以由实现者提供,但必须注意的一点是超类有可能已经实现了其模板方法。要确保对链中所有类的正确初始化,实现者必须手动编写超类方法调用的代码,如清单 11 所示。

清单 11. 超类方法调用的示例
startup: function()
{
 // call the superclass' method
   this.inherited("startup", arguments);
 // your code here
}

作为一个通用约定,对于所有创建方法(除 uninitialize 以外的所有模板方法),超类的方法应第一个被调用以确保在处理超类的条目前它们已被正确地初始化。

uninitialize 方法则要遵照相反的规则。

最后一个要注意的是 startup 方法。别忘了,若小部件通过声明被创建,startup 方法是通过 dojo.parser.parse 方法自动调用的。倘若小部件是由编程方式创建,情况就不一样了,必须像下面例子所示的那样手动完成。

var w = new MyWidget({});
w.startup();

小部件销毁阶段

创建后,小部件将一直存在,直到执行一个显式的销毁请求。实现者负责管理小部件的整个生命周期,包括小部件的销毁。否则,将会导致有遗漏的小部件存在,直到整个页面清理发生。这个小部件提供了四种销毁方法:

  • destroyRendering:这个方法用来清理小部件的呈现条目。除非实现者需要的是部分清理,否则该方法往往由其他摧毁方法调用。
  • destroyDescendants:销毁所有的子小部件。除非实现者需要的是部分清理,否则该方法由 destroyRecursive 方法调用。
  • destroy:销毁所有的小部件条目(不包括子小部件)。
  • destroyRecursive:销毁小部件条目及子小部件。如果小部件包含有内部小部件,那么必须调用此方法。

在本示例中,小部件包含有内部小部件(例如:在其模板中有一些小部件),所以实现者必须要记得触发子小部件,以便销毁这些子小部件并且无需让小部件用户决定调用何种销毁方法。清单 12 给出了实现此目的一种可能方式。

清单 12. 触发小部件销毁的示例
uninitialize: function()
{
 // destroy the descendants
 this.destroyDescendants();
 // call the superclass' method
   this.inherited("uninitialize", arguments);
}

事件管理

Dojo 小部件可以对来自于 DOM 节点或对象的外部事件做出反应。这类事件可通过使用小部件的 connect 方法被手动连接,该方法具有如下所示的签名(非常类似于 dojo.connect 方法):

connect: function(/*Object|null*/ obj, /*String*/ event, /*String|Function*/ method);

或者,也可以通过使用 dojoAttachEvent 属性在模板中声明此连接的方法进行自动连接。在这两种情况下,此连接都可以由小部件在 _connects 数组内内部跟踪。所有连接在销毁时都将会自动断开。这样,小部件与 DOM 节点间的相互引用就被打破了。

如果,在小部件生命周期内一些连接已经不再需要,那么实现者就可以通过调用 disconnect 方法来手动断开这些连接以减少事件的处理。

小部件模板

如前面看到的,小部件必须继承自 dijit._Widget 类,它定义并提供了 Dojo 小部件的基本行为。这样一个基类定义了负责构建小部件呈现元素的 buildRendering 方法。比如,一个小部件实现者可以用这种方法创建小部件标记并将其设置到小部件 DOM 节点。另一种方案是使用 DOM API 创建小部件结构。在这两种情况下,实现者都必须以某种方式编程实现 buildRendering 方法。

Dojo 提供了一个强大的抽象,即 dijit­._Templated 混入类,可以将小部件呈现定义与小部件行为实现分离开来。想要开发这样一个抽象,实现者需要从 _Templated 类继承,如清单 13 所示。

清单 13. 从 _Templated 类继承
dojo.declare(
 "widgets.MyWidget", 
   dijit._Widget, dijit._Templated
 {
 templatePath: dojo.moduleUrl("widgets", "templates/MyWidget.html"),
 }
);

_Templated 类提供了它自身的 buildRendering 实现,该实现利用了一个类似 HTML 的定义。这个定义有可能存在于两个不同的地方。

  1. 一个外部文件。在这种情况下,该文件由 templatePath 属性引用。
  2. 一个内部字符串属性。在这种情况中,模板直接通过 templateString 属性在小部件内定义。如果指定了 templateString,那么templatePath 可被忽略,即便指定,也是如此。

第一种选择是一种组织小部件源代码的最干净的方法,因为这个标记可被写入一个不同的文件,也可被以一种可读的方式格式化,同时不必费心于字符串的串联,并且字符串分界符不可转义。而第二种选择要更好一些,因为无需从浏览器载入第二个文件。然而,您不必担心,因为 Dojo 提供了一个构建工具,能将外部模板内部化到小部件源代码。

一个模板可以通过引用小部件属性而被参数化。在小部件公开某些能直接影响此标记的配置参数时,或是这个标记必须要依照外部首选项而有条件地生成时,这一点将会很有用。小部件属性通过使用这样的语法引用:${propertyName}

模板可以包含其他的小部件声明。不过,为了将它们考虑进去,小部件开发人员必须将 widgetsInTemplate 属性设为 true,该属性默认情况下被设为 false 以便跳过并非必需的处理。

模板可以包含如下两种特殊的属性声明:

  • dojoAttachPoint:如果指定,这个标记属性必须被设置为一个小部件属性名。对应于此标记的 DOM 将被设置为此小部件的属性。模板内的几个标记可以有一个或多个附加点。
  • dojoAttachEvent:这个标记属性会列出在触发 DOM 事件时必须被回调的那些小部件方法。

清单 14 给出了一个模板示例。

清单 14. 特殊属性声明的模板示例

 
 
  
 

Tivoli Dynamic Workload Console 与 Dojo

Tivoli Dynamic Workload Console(TDWC)是 Tivoli Workload Scheduler(TWS)的图形用户界面。这个小节将展示 TDWC v.8.5 是如何利用 Dojo 的能力的。

Tivoli Workload Scheduler 简介

TWS 是一个生产自动化的解决方案,被设计用来在当今繁杂的操作环境里帮助管理工作量。主要的调度元素包括作业和作业流。作业 代表的是一个任务,例如一个可执行的文件、程序或命令。作业流 代表的是相关作业的容器,并按运行时、顺序、并行限制和重复性组织这些作业。TWS 用来帮助计划作业的执行,解决相互依赖性,启动并跟踪每个作业。一旦作业的依赖性得到满足,这些作业就会开始;这样,就最小化了空闲时间,并显著改进了吞吐量。作业永远不会打破顺序运行,并且如果一个作业失败了,TWS 可以在操作员少量参予甚至不参予的情况下对之进行恢复。

使用 Dojo

TDWC v.8.5 包括一个功能完善的工作量编辑器,这个编辑器是使用 Dojo Toolkit 完全以 JavaScript 编写的。这种实现使 “智能” 和行为更加贴近用户,让代码尽可能多地在浏览器内运行。

让我们看一个由 TDWC 显示的 TWS 作业流的示例属性面板。图 3 显示了针对作业流 PAYROLL 定义的通用属性的 Web 面板。

图 3. 作业流 PAYROLL 的 Web 面板

在前一篇文章中(“The Abstract User Interface Markup Language Web Toolkit: An AUIML renderer for JavaScript and Dojo”,参见 参考资料),我们介绍了如何用 AUIML 工具箱设计面板以及如何用 AUIML Web Toolkit(AWT)在 JavaScript 内实现逻辑代码。图 4 显示了 AUIML 面板:

图 4. AUIML 面板

让我们着重看看 Valid from 字段。它已经在 AUIML 编辑器中被定义为 Edit Box, Date;AWT 通过清单 15 所示的 HTML 代码连接此元素。

清单 15. 用来连接元素的 HTML 代码


清单 16 显示了小部件 ajaxcommon.widgets.DateInputBox 是如何定义的:

清单 16. 定义 ajaxcommon.widgets.DateInputBox 所需代码
dojo.provide("ajaxcommon.widgets.DateInputBox"); 

dojo.require("ajaxcommon.resources.Images");
dojo.require("ajaxcommon.widgets._DateTimePicker");
dojo.require("ajaxcommon.widgets.picker.PickerInputBox");
dojo.require("ajaxcommon.widgets.picker.DatePicker");
dojo.declare(
   "ajaxcommon.widgets.DateInputBox",
     [ajaxcommon.widgets.picker.PickerInputBox, 
      ajaxcommon.widgets._DateTimePicker],
     {
      /** the picker icon */
      pickerIcon: ajaxcommon.resources.Images.get()["CALENDAR_ICON"],
      /** the picker disabled icon */
      pickerDisabledIcon: ajaxcommon.resources.Images.get()["DISABLED_CALENDAR_ICON"],
      /** the picker icon title */
      pickerIconTitle: ajaxcommon.resources.Labels.get()["PICK_DATE"],
      /** constraints */
      constraints: {selector: "date", formatLength: "short", wideYear: true},
      
      /**
       * Constructor.
       */
      constructor: function()
      {
         // even if the format length is short, ensure the wide year (yyyy)
         // by overriding the datePattern
         if (this.constraints.formatLength === "short" && this.constraints.wideYear) {
            this.constraints.datePattern = this._getWideDatePattern();
         }
         // set the regex for the text
         this.textRegExp = "^(" + dojo.date.locale.regexp(this.constraints) + “){0,1}$";
         // set the regex message for the text
         this.textRegExpMessage = 
            this._labels.format("DATE_INVALID_VALUE", {example: this._getExampleValue()});
         // set the picker class
         this.pickerClass = "ajaxcommon.widgets.picker.DatePicker";
      },
      
      /**
       * Returns the date pattern with the wide-year (yyyy)  
       */
      _getWideDatePattern: function()
      {
         // get the bundle
         var bundle = dojo.date.locale._getGregorianBundle();
         // get the pattern
           var pattern = bundle["dateFormat-short"];
           // replace the yy to yyyy if not yet yyyy
           if ( pattern.search(/yyyy/gi) === -1 ) {
              // the year is not in the wide form
              pattern = pattern.replace(/yy/gi, "yyyy");
           }
           return pattern;
      },
     /** 
       * Returns a string containing an example of accepted value. 
       */      
      _getExampleValue: function()
      {
         // get a sample date object (my birthday!!!)
         var d = new Date();
         d.setDate(15);
         d.setMonth(4);
         d.setYear(1971);
         // format the date in the current locale and return 
         return dojo.date.locale.format(d, this.constraints);
      }
   }      
);

这个构造函数(在 清单 17 中定义)允许初始化在超类小部件 ajaxcommon.widgets.picker.PickerInputBox 内所声明的小部件的属性。

清单 18 给出了相关的小部件模板。

清单 18. 面向 ajaxcommon.widgets.picker.PickerInputbox 的小部件模板

        

图 5 显示了将小部件逻辑委派到浏览器端的好处。这个小部件能够显示相关的错误消息,如果在设定其值时发生错误,还能改变观感。

图 5. 显示了浏览器端小部件逻辑的面板

结束语

本文介绍了使用 Dojo JavaScript 工具箱开发 HTML 小部件的基本概念,从版本 1.0 和一些简单的小部件开始,逐渐过渡到较为复杂的小部件(比如在 TDWC v.8.5 内实现的小部件)。本文还着重强调了 Dojo 工具箱的几个要点。

在下一篇文章中,我们将讨论在开发富 Internet 应用程序期间经常遇到的一些缺陷,并将展示如何使用我们所开发的最佳实践(主要基于 Dojo 工具箱) 来解决这些缺陷。

参考资料

学习

  • developerWorks Web development 专区:通过专门关于 Web 技术的文章和教程,扩展您在网站开发方面的技能。
  • developerWorks 技术活动和网络广播:随时关注 developerWorks 技术活动和网络广播。

获得产品和技术

  • 下载最新版本的 Dojo 工具箱(dojo-release-1.x.x-src.zip 或 dojo-release-1.x.x-src.tar.gz)。
  • 从 项目网站 下载 FireBug。


来源:http://www.ibm.com/developerworks/cn/web/wa-aj-dojotool/


你可能感兴趣的:(所有,Web前端)