扩展 Dojo dijits 来创建自定义小部件

本文要介绍的内容是:当来自 Dojo 工具包的特定 dijit 无法全面满足您的需要,您需要创建自己的自定义小部件时,您可以怎么做。本文的最后提供了一个示例,其中包含一组需求和满足这些需求的方法,您将熟悉如何使用 dijit 和其他 Dojo 核心功能,以及如何声明自己的小部件。 本文来自于 IBM WebSphere Developer Technical Journal 中文版

Kareem Weller, 资深软件工程师, IBM

Kareem Weller 目前是 IBM Software Group 的一名资深软件工程师,来自佛罗里达州奥兰多。他在 IBM 内的多个部门内工作过,在 Web 应用程序开发方面拥有 5 年的工作经验,参与过许多使用不同 Web 技术和产品(比如 dojo 工具包、JSON、XML、IBM Web Content Management 和 Websphere Application Server)的政府和商业项目。



2013 年 2 月 18 日

简介

Dojo Toolkit 是一个强大的 JavaScript™ 库,支持 Web 开发人员使用面向对象的小部件,用最少的开发时间和工作量来创建富 Internet 应用程序。Dojo Toolkit 提供了 4 个包,它们分别是 Dojo(核心)、Dijit(UI 框架)、dojox(dojo 扩展)和 util。您可以原封不动地使用该工具包提供的功能,也可扩展它们并创建自己的小部件。提供的功能包括 DOM 操作、使用 AJAX 开发、事件、数据存储等。

Dijit(dojo 小部件)包是 dojo 自己的 UI 库,它包含一组 dojo 类,使开发人员能够以最少的工作创建功能丰富且强大的跨平台 Web 2.0 接口。这些 Dijit 小部件(或 dijit)支持一些易于操作的主题。这个包中的 dijit 示例包括按钮、文本字段、编辑器、进度条等。

举例而言,您可以使用这些 dijit 创建一个提交表单,其中包含用于名称、电子邮件地址和电话号码的文本字段,以及日期字段、复选框、按钮和验证,所有这些在几分钟即可完成,需要的 JavaScript 知识也极少。

提供的一个功能最丰富的 dijit 是 Calendar dijit,它支持在一个月的上下文中显示日历。用户可轻松地逐月、逐年导航,或者跳到同一年中任一个月,以选择特定的日期。

在开发富 Internet 应用程序 (RIA) 时,通常可以原封不动地使用 dijit。但是,有时您可能需要使用不同的样式(比如更改颜色或主题),或者需要进行更复杂的更改,其中可能需要结合使用某些功能、模板和样式更改。要满足这些需求,则需要从头创建一个新的自定义小部件,也可以创建一个扩展现有 dijit 的自定义小部件。

本文提供了一个练习,在这个练习中,您需要在自己的网站上使用 Calendar 小部件的不同变体。为了满足此需求,您需要创建一个新类。此练习使用了 Dojo version 1.7,提供了探索 Calendar dijit 的机会和以极少的改动重用现有 dijit 来节省开发时间的方式。您还会看到在 Dojo 1.7 中声明的一个新类的实用示例,探索一些 Dojo 基础功能,比如数据操作、hitch、发布和订阅等。

问题

在此练习中,您将使用 Calendar dijit 的一个具有以下需求的自定义版本:

  • 日历应仅显示当月的日期(隐藏和禁用不属于当月的日期)。
  • 日历应仅在底部显示当前年份(没有去年或明年)。
  • 日历应在日历小部件底部显示当月的名称。
  • 用户无法跳到任何其他月份(禁用顶部的月份下拉按钮)。
  • 提取日历顶部显示的用于逐月移动(向后或向前)的箭头,将日历旁边的箭头显示为 dijit 按钮。这两个新按钮是用户更改月份的惟一方式。
  • 具有最小和最大边界日期,这意味着所有在此边界外的日期都应该是禁用和不可访问的。
  • 在到达边界日期时,禁用相应的月份导航按钮。
  • 向日历中选定的日期添加特殊的样式。
  • 当用户选择某个日期时,将该日期传递给一个将处理新选择的值的函数。

解决方法是创建一个自定义小部件,这个小部件通过使用 JavaScript 和 CSS 编辑 Calendar dijit 来开发。图 1 显示了应用上面需求之前(左侧)和之后(右侧)的 Calendar 小部件。

图 1. 标准 Calendar dijit 与自定义 Calendar 小部件的对比
图 1. 标准 Calendar dijit 与自定义 Calendar 小部件的对比

为此,您将需要创建 3 个文件:

  • Dijit 模板:一个将显示自定义小部件的组件的标记文件。
  • Dijit 类:一个使用声明 (JavaScript) 创建的小部件类。
  • CSS 文件:包含所有必要的样式表类。

图 2 显示了自定义小部件的文件结构和位置。您的起点是 index.html,它在本示例中充当了小部件的控制器。simple.css 文件将包含所有样式。

图 2. 文件结构
图 2. 文件结构

创建小部件

dijit 模板

创建 3 个 JavaScript div 元素:一个用于日历,两个用于向前和向后逐月导航的箭头按钮(清单 1)。这些 div 将使用附加点 (data-dojo-attach-point) 进行引用。使用附加点比使用 id 更好,因为它使您能够在同一个页面上拥有同一个小部件的多个实例,无需担忧 id 冲突。

清单 1. dijit 模板
<div class="CalendarArrow">
<div data-dojo-attach-point="calendarPreviousMonthButtonAP"></div>
</div>

<div class="CalendarDijit">
<span data-dojo-attach-point="calendarMonthOneAttachPoint"></span>
</div>

<div class="CalendarArrow">
<div data-dojo-attach-point="calendarFollowingMonthButtonAP"></div>
</div>

dijit 类

根据应用程序需求,您需要定义以下变量:

  • selectedDate:日历的初始值。
  • currentFocusDate:一个值,日历会引用该值来了解要显示的月份;该值最初被设置为等于 selectedDate。
  • calendarInstance:Dijit 日历实例。
  • bookingWindowMaxDate:日历中允许的最晚的日期。
  • bookingWindowMinDate:日历中允许的最早的日期。
  • onValueSelectedPublishIDString:表示发布/订阅频道(或主题)的字符串。

JavaScript 函数

首先修改以下样式表元素:

  • constructor

    修改构造函数以复制从控制器传来的变量。使用 dojo/_base/lang/mixin,它将匹配变量名称,并将值复制到您的自定义小部件变量中(清单 2)。

    清单 2. 构造函数
    constructor: function (args){
    	if(args){
    		lang.mixin(this,args);
    	}
    }
  • postCreate

    所有日期都将以具有 en-us 短格式 mm/dd/yyyy 的字符串形式传递。使用 dojo/date/locale 函数将用于 selectedDate、bookingWindowMaxDate、bookingWindowMinDate 的所有日期字符串转换为日期对象(清单 3)。

    清单 3. 使用 dojo/date/locale
    this.bookingWindowMinDate = locale.parse(this.bookingWindowMinDate, {formatLength:
    'short', selector:'date', locale:'en-us'});

    创建日历对象的一个实例(清单 4)。创建的逻辑在 createCalendar 函数中。以编程方式创建一个 dijit 日历的实例,将它附加到您将使用 dojo/dom-construct(等效于老版 dojo 中的 dojo.create)创建的一个 div 上。一般来讲这是一种很好的做法,因为它允许您销毁日历而不丢失附加点。

    清单 4. 返回 dijit/Calendar 的一个实例
    return new Calendar({
    	value : selectedDate,
    	currentFocus : selectedDate	},  domConstruct.create("div", {}, 
    	calendarAttachPoint));
    }

    请注意,您在Calendar dijit 中设置了 currentFocus 值。Calendar dijit 将始终使用您本地的当前日期来显示其第一个着屏 (landing screen),所以,如果希望日历显示不同的屏幕(日期),则必须设置 currentFocus。因此,对于自定义小部件,您需要将日历的初始值和 currentFocus 设置为 selectedDate(根据需要进行设置)。对于此示例,这个日期为 2012 年 8 月中的一天。

要满足其他需求,您需要修改 Calendar dijit 中的以下 3 个函数:

  • isDisabledDate

    当 calendar dijit 加载一个视图时,它会逐个迭代当前视图中的日期(所有 42 个日期),并为每一天调用 isDisabledDate 和 getClassForDate(接下来将介绍)函数。

    isDisabledDate 函数用于禁用日历中的某些日期(清单 5)。如果函数返回 true,那么将会禁用该日期。每次日历刷新时,都会调用此函数,并且会将日历中的每一天的日期传送给它。对于自定义小部件,您需要:

    • 禁用不属于当月的任何日期:为此,需要使用 dojo/date/difference 函数,该函数基于某个间隔来对比两个日期对象,如果两个日期相等,则返回 0。您将使用月份间隔对比 currentFocusDate 变量与当前视图中的每个日期,如果它们不相等,则返回 true,以禁用该日期。
    • 禁用边界日期外部的日期:再次使用 dojo/date/difference,但将间隔设置为 “day”。如果返回值小于 bookingWindowMinDate 或大于 bookingWindowMaxDate,则返回 true,以禁用该日期。
      清单 5. 修改 isDisabledDate
      isDisabledDate: function(date) {
       //disable any day that doesn't belong to current month
      	if(dojoDate.difference(parent.currentFocusDate, date, "month")!==0){
      		return true;
      	}
      	if(dojoDate.difference(parent.bookingWindowMinDate, date, "day" || 
      dojoDate.difference(parent.bookingWindowMaxDate, date, "day")<0){
      		return true;
      	}
      	else {
      		return false;
      	}
      }
  • getClassForDate

    尽管您使用 isDisabledDate 禁用了不属于当月视图的日期,但还需要隐藏它们。可以使用 getClassForDate 函数返回一个 CSS 类名,在日历上以不同形式标记该日期。对于自定义小部件,需要突出显示 selectedDate,向该日期添加一个具有黑色边界的蓝色框(清单 6)。您还需要使用灰色突出显示在最小和最大日期边界之外的日期,隐藏不属于当月的日期。

    要识别需要设置不同样式的日期,可以使用 dojo/date/compare,它将接受两个日期值(日期对象)和天数(字符串),如果相等,则返回 0。这里,您将传递 currentFocusDate、迭代中的日期和表示天数的日期,因为您仅对对比日期感兴趣,所以无需关注时间戳。如果此对比返回 0,那么该函数将返回类 “Available”,这个类是在 CSS 文件中定义的(清单 7)。您将使用 CSS .class 选择权来定位我们希望更改的特定元素。

    清单 6. 修改 getClassForDate
    getClassForDate: function(date) {	
    	if ( dojoDate.compare(date,selectedDate,"date") === 0) {
    		return "Available";
    	} // apply special style
    }
    清单 7. 标记可用日期的 CSS 类
    .AvailabilityCalendars .Calendars .CalendarDijit .Available 
    	.dijitCalendarDateLabel
    {
            background-color: #bccedc !important;
            border: 1px solid #000000 !important;
    }

    您将使用来自 isDisabledDate 的相同的 if 条件来识别超出边界的日期和不属于当月的日期,但会返回 CSS 类名(清单 8 和清单 9)。

    清单 8. 隐藏和禁用日期
    if(dojoDate.difference(parent.currentFocusDate, date, "month")!==0){ 
    	return "HiddenDay";
    }
    if(dojoDate.difference(parent.bookingWindowMinDate, date, "day")<0 || 
    	dojoDate.difference(parent.bookingWindowMaxDate, date, "day")>0){
    	return "Disabled";
    }
    清单 9. 标记隐藏和禁用的日期的 CSS 类
     .AvailabilityCalendars .Calendars .CalendarDijit .HiddenDay 
    	.dijitCalendarDateLabel
    {
        background-color: #ffffff !important;
        border-color: #ffffff;
        color: #ffffff;
    }
    
     .AvailabilityCalendars .Calendars .CalendarDijit .Disabled 
    	.dijitCalendarDateLabel
    {
    	background-color: #9c9c9c;
    }
  • onChange

    此函数仅在为日历设置一个新值或在日历上选择一个启用值时调用(清单 10)。此函数返回所选日期的一个日期对象。您可以使用该对象将日期发布到另一个将处理该日期的方法。调用您的自定义小部件中定义的一个函数 (onValueSelected),在发布到控制器之前,您可以在其中执行任何需要的处理(清单 11)。在本示例中,您将使用 dojo/_base/connect/publish 将日期发布到控制器。频道(或主题)字符串存储在变量 onValueSelectedPublishIDString 中。

    清单 10. 使用 Calendar 的 onChange 和 hitch
    onChange : lang.hitch(this, function(date){
    	this.onValueSelected(date);
    })
    清单 11. 使用 publish
    onValueSelected : function (date){
    	connect.publish(this.onValueSelectedPublishIDString, [date]);
    }

    请注意,您使用了 dojo/_base/lang/hitch 来提供调用函数 onValueSelected 的范围(清单 10)。您的控制器(在此场景中为 index.html)将提供一个订阅该频道的订户来处理日期(清单 12)。在此示例中,您仅记录它。可将此替换为任何其他需要的逻辑。

    清单 12. 我们的发布的订户
    connect.subscribe("selectedValueID", function(date){
      //Do some processing 
      console.log("New Selected Date: ", date);
    });

Calendar dijit 在页眉中附带了一个 monthDropDownButton。这个按钮显示了所有月份的列表,允许用户跳到任何月份。为了满足需求,您需要将 monthWidget 设置为 “disabled” 来禁用此按钮(清单 13)。

清单 13. 禁用 Calendar 页眉中的下拉按钮
this.calendarInstance.monthWidget.set("disabled", true);

从实用性角度讲,您还需要隐藏箭头,使用户不会受到吸引而去单击它。为此,请添加定位您希望操作的元素的 CSS 类(清单 14)。

清单 14. 隐藏下拉按钮箭头的 CSS 类
 .AvailabilityCalendars .Calendars .CalendarDijit .dijitDropDownButton 
	.dijitArrowButtonInner
{
    visibility: hidden;
}

接下来,使用 CSS 类隐藏去年和明年的 digit,禁止将它显示在底部(清单 15)。

清单 15. 在 Calendar 底部隐藏年份 digit 的 CSS 类
 .AvailabilityCalendars .Calendars .CalendarDijit .dijitCalendarPreviousYear, 
	.dijitCalendarNextYear
    {
padding: 1px 6px;
visibility: hidden;
    }

您还将隐藏顶部的箭头,否则这些箭头允许用户逐月移动(清单 16)。

清单 16. 隐藏 Calendar 顶部的月份箭头的 CSS 类
 .AvailabilityCalendars .Calendars .CalendarDijit .dijitCalendarArrow
{
     visibility: hidden;
}

接下来,创建两个新按钮,以便允许用户导航月份。以编程方式使用 dijit/form/Button 创建这两个按钮。对于第一个按钮(后退),可以将标签设置为 “<<” 并修改 onClick 函数(清单 17)。onClick 的逻辑将包含在 goToPreviousMonth 函数中。

清单 17. 创建 dijit/form/bottom 的一个实例
this.calendarPreviousMonthButton = new Button({
       label: "<<",	
       onClick: lang.hitch(this, function(){
    	   this.goToPreviousMonth(this.calendarInstance);
       })
}, this.calendarPreviousMonthButtonAP);

您希望在用户每次单击该按钮时,日历后退一个月。在 goToPreviousMonth 中,需要先将 currentFocusDate 更改为 currentFocusDate - 1 month,然后刷新日历的视图。最后,必须检查这是否要显示的最后一个月,如果是,则禁用该按钮。

使用 dojo/date/add 函数,它接受日期对象、间隔(字符串)和数量(整数)。对于您的情形,日期应该是 currentFocusDate 对象,间隔是 “month”,数量是 -1(清单 18)。

清单 18. 日历视图减去一个月
this.currentFocusDate = dojoDate.add(this.currentFocusDate,"month",-1);
calendarInstance.set("currentFocus",this.currentFocusDate);

设置日历的新视图,将 currentFocus 设置为新的日期值。(这将自动刷新日历并显示新视图)。

最后,请检查这是否是最后一个月的视图,方法是对比 currentFocusDate 与最小值边界;如果是最后一个月,则禁用后退按钮。另外,请检查您是否应启用了前进按钮(假如您禁用了该按钮,并且现在您离开了最大值边界)(清单 19)。

清单 19. 检查我们是否应该禁用新导航按钮
if(this.isLastCalendarMonth(this.bookingWindowMinDate, this.currentFocusDate)){
	this.calendarPreviousMonthButton.set("disabled", true);
}
if(!this.isLastCalendarMonth(this.bookingWindowMaxDate, this.currentFocusDate)){
	this.calendarFollowingMonthButton.set("disabled", false);
}

第二个按钮的工作原理相同。该标签将为 “>>”,onClick 调用 goToNextMonth,后者使用了同一个函数,但您添加了一个月(清单 20)。

清单 20. 控制移动到下一个月的按钮的函数
goToNextMonth : function (calendarInstance){
	this.currentFocusDate = dojoDate.add(this.currentFocusDate,"month",1);
	calendarInstance.set("currentFocus",this.currentFocusDate);
	if(this.isLastCalendarMonth(this.bookingWindowMaxDate, this.currentFocusDate)){
		this.calendarFollowingMonthButton.set("disabled", true);
	}	
	if(!this.isLastCalendarMonth(this.bookingWindowMinDate, this.currentFocusDate)){
		this.calendarPreviousMonthButton.set("disabled", false);
	}
}

最后,清单 21 显示了应该在控制器类中的内容的示例,该类将进行相关调用,以便初始化您的新 customCalendar。

清单 21. 创建新的自定义日历小部件的实例的快照
require(["myUtil/customCalendar","dojo/_base/connect"], function(myCalendar, connect){
	var params = {
		"bookingWindowMinDate":"10/9/2011",
		"bookingWindowMaxDate":"10/9/2012",
		"selectedDate":"8/15/2012",
		"onValueSelectedPublishIDString":"selectedValueID"
	};
	var myTest = myCalendar(params);
	myTest.placeAt("nodeId", "last");
		
	connect.subscribe("selectedValueID", function(date){
	  //Do some processing 
	  console.log("I got: ", date);
	});	
	
});

如您所见,您创建了一个对象 params,其中包含需要传递给自定义日历小部件的值,并订阅了该频道。

更多函数

您可能会发现其他一些函数和属性在此场景中也很有用:

  • 当将一个日期对象和区域传递到 dojo/date/locale/isWeekend 时,如果日期是周末(对于 en-us 区域,是星期六和星期日),则会返回 true。如果需要,可以使用该属性来禁用周末日期或为周末设置不同的样式。
  • Calendar dijit 还包含一个 dayWidth 属性,该属性接受一个字符串作为值。默认情况下,会将它设置为 “narrow”,这缩短了显示的日历日期表示,例如使用 “M” 表示星期一。其他值包括表示完整日期名称显示的 “wide” 和表示缩写词的 “abbr”(比如 “Mon”)。

这些自定义小部件需求的一种变体可能要求小部件显示多个日历,然后,在用户通过单击来查看下一个月时,要求两个日历都前进一个月(参见图 3)。这很容易完成,只需更改小部件变量来支持一个数组,而不是单个值变量。

图 3. 每个视图多个日历
图 3. 每个视图多个日历

结束语

通过组合 JavaScript 和 CSS 修改,很容易创建一个自定义小部件来更好地满足项目需求。本文演示了这种做法,使用 Dojo 1.7 声明了一个类,该类扩展了 Calendar dijit 并利用了一些 dojo 功能,比如日期操作、hitch、发布和订阅,以及其他一些基础 dojo 函数。希望您将能够应用这些步骤来扩展 Dojo dijit,并创建自己的新小部件。


下载

描述名字大小
样例应用程序customCalendar17.zip4 KB

参考资料

学习

获得产品和技术

讨论

条评论

developerWorks: 登录

标有星(*)号的字段是必填字段。


需要一个 IBM ID?
忘记 IBM ID?


忘记密码?
更改您的密码

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件

 


在您首次登录 developerWorks 时,会为您创建一份个人概要。您的个人概要中的信息(您的姓名、国家/地区,以及公司名称)是公开显示的,而且会随着您发布的任何内容一起显示,除非您选择隐藏您的公司名称。您可以随时更新您的 IBM 帐户。

所有提交的信息确保安全。

选择您的昵称



当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

标有星(*)号的字段是必填字段。

(昵称长度在 3 至 31 个字符之间)

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

 


所有提交的信息确保安全。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=WebSphere, 移动开发, Web development
ArticleID=858324
ArticleTitle=扩展 Dojo dijits 来创建自定义小部件
publish-date=02182013