在开始编码之前,我们必须想好组件的外观和运行方式。例如,我们想在HTML中像下面这样定义datepicker:
<input datepicker ng-model="currentDate" select="updateMyText(date)"></input>
也就是说,我们需要修改Input输入标签,新增一个datepicker属性,然后 给它扩展更多功能(例如使用模型进行数据绑定,以及当选中了日期之后能够获得通知)。那么,我们应该如何实现呢?
我们将会复用现有的控件,也就是jQuery UI提供的datepicker,而不是重新构建一个datepicker。我们只需要把它挂接到AngularJS上,然后启动它所提供的接口。
DatePickerDirective.js
angular.module('myApp.directives',[]) .directive('datepicker', function() { return { //强制AngularJS把指令限定为只支持属性 restrict: 'A', //总是和ng-model配合使用 require:'?ngModel', scope: { //此方法需要与预先定义好,然后传递给视图控制器中的指令 select: '&' //把我们所引用的select函数绑定到右边的作用域中 }, link: function(scope, element, attrs, ngModel) { if(!ngModel) return; var optionsObj = {}; optionsObj.dataFormat = 'mm/dd/yy'; var updateModel = function(dateTxt) { scope.$apply(function() { //调用AngularJS内部的工具更新双向绑定关系 ngModel.$setViewValue(dateTxt); }); }; optionsObj.onSelect = function(dateTxt, picker) { updateModel(dateTxt); if(scope.select) { scope.$apply(function() { scope.select({date: dateTxt}); }); } }; ngModel.$render = function() { //使用AngularJS内部的'binding-specific'变量 element.datepicker('setDate', ngModel.$viewValue || ''); }; element.datepicker(optionsObj); } }; });
我们还来看一看其中一些比较重要的东西。
1.ng-model
我们把一个ng-model属性传递给了指令所关联的函数。通过ng-model(它会托管指令的执行,因为所需要的属性都处于指令定义内部)可以定义,在指令的上下文中,连接到ng-model上的属性和对象应该具有何种行为。这里有两件事性需要注意:
a.ngModel.$setViewValue(dateTxt)
当AngularJS外部发生了某件事情的时候(在这个例子中是指jQuery UI datepicker的onSelect事件),需要调用此函数。这样就可以让AngularJS知道,它应该更新模型了。一般来说,当DOM事件发生的时候会调用它。
b.ngModel.$render
这是ng-model的另一个组成部分。它会告诉Angular,当模型发生变化的时候应该如何刷新视图。在这个例子中,我们只会把jQuery UI的datepicker变化之后的值传递过去。
2.绑定select
我们不会使用属性值进行编写、再根据作用域把它当做字符串来执行(在这种情况下,嵌套的函数和对象不可访问)的方式;而是会使用函数绑定技术(也就是“&”作用域绑定)。这样做在作用域上创建了一个新的函数,叫做select,它有一个参数——一个对象。这个对象中的每一个key都必须与HTML中所指定的参数一致(HTML就是使用指令的地方),key所对应的值将会被传递给函数对应的参数。这样带来的另一个优点是:控制器被解耦合了,它不再需要知道任何与DOM或者指令相关的内容。回调函数只需要执行它的动作,然后给出一些参数,而没有必要知道与数据绑定或刷新相关的内容。
3.调用select
注意,我们把一个optionOjb传递给了datepicker,同时也传递了一个onSelect函数。jQuery UI负责调用onSelect函数,它一般在AngularJS外部的执行上下文中进行调用。当然,在类似onSelect的函数被调用时,AngularJS是毫不知情的,所以我们有责任让AngularJS知道它需要进行何种操作。怎么才能做到这一点呢?使用scope.$apply即可。
现在,我们可以在scope.$apply的外部简单地执行$setViewValue并调用scope.select,然后再调用scope.$apply()。但是,这样一来,在这两个步骤中任何一个里面所发生的异常都会被默默地忽略掉。如果异常发生在scope.$apply函数内部,则它们可以被AngularJS捕获到。
DatePickerController.js
var app = angular.module('myApp', ['myApp.directives']); app.controller('MainCtrl', function($scope) { $scope.myText = 'Not Selected'; $scope.currentDate = ''; $scope.updateMyText = function(date) { $scope.myText = 'Selected'; }; });
以上代码声明了一个控制器,设置了一些作用域变量,然后创建了一个作用域方法(updateMyText),在后面我们会把这个方法绑定到datepicker的on-select事件上。
DatePickerHtml.html
<!DOCTYPE html> <html ng-app="myApp"> <head lang="en"> <meta charset="utf-8"></meta> <title>AngularJS Datepicker</title> <script src="jquery/jquery-1.8.3.js"></script> <script src="jquery-ui/js/jquery-ui-1.9.2.js"></script> <script src="angular/angular.js"></script> <link rel="stylesheet" href="jquery-ui/css/custom-theme/jquery-ui-1.9.2.css"></link> <script src="directive/DatePickerDirective.js"></script> <script src="controller/DatePickerController.js"></script> <script src="jquery-ui/js/jquery.ui.datepicker-zh-CN.js" type="text/javascript" charset="gb2312"></script> </head> <body ng-controller="MainCtrl"> <input id="dateField" datepicker ng-model="$parent.currentDate" select="updateMyText(date)"></input> <br></br> {{myText}} - {{currentDate}} </body> </html>
注意这里是如何指定select属性的。作用域中并没有"date"值,但是由于我们在指令中设置了函数绑定的方式,所以AngularJS知道这个函数将会接受一个名为"date"的参数,它就是当datepicker上的onSelect被调用时我们所指定的对象。
注意一个细节:我们把ng-model设置成了$parent.currentDate,而不是currentDate。这是为什么呢?因为我们的指令将会创建一个独立的作用域,这样就可以用它来绑定select函数了。这就导致了currentDate不再被连接到ng-model上,即使我们专门进行了设置。因此,我们需要明确地告诉AngularJS,它需要引用的currentDate并不在这个独立的作用域中,而是在父作用域中。
通过这种方式,当你把应用加载到浏览器之后,将会看到一个文本框,点击它就会打开一个jQuery UI的datepicker。选择日期之后,屏幕上的文本就会从"Not Selected"更新成"Selected",并加上你所选择的日期。同时,输入框中的日期也会被刷新。如下所示:
资料来源:《用AngularJS开发下一代Web应用》