在一个项目中需要一个用来输入分钟数和秒数的控件,然而调查了一些开源项目后并未发现合适的控件。在Angular Bootstrap UI中有一个类似的控件TimePicker,但是它并没有深入到分钟和秒的精度。
因此,决定参考它的源码然后自己进行实现。
最终的效果如下:
首先是该directive的定义:
app.directive('minuteSecondPicker', function() {
return {
restrict: 'EA',
require: ['minuteSecondPicker', '?^ngModel'],
controller: 'minuteSecondPickerController',
replace: true,
scope: {
validity: '='
},
templateUrl: 'partials/directives/minuteSecondPicker.html',
link: function(scope, element, attrs, ctrls) {
var minuteSecondPickerCtrl = ctrls[0],
ngModelCtrl = ctrls[1];
if(ngModelCtrl) {
minuteSecondPickerCtrl.init(ngModelCtrl, element.find('input'));
}
}
};
});
在以上的link函数中,ctrls是一个数组: ctrls[0]是定义在本directive上的controller实例,ctrls[1]是ngModelCtrl,即ng-model对应的controller实例。这个顺序实际上是通过require: ['minuteSecondPicker', '?^ngModel']定义的。
注意到第一个依赖就是directive本身的名字,此时会将该directive中controller声明的对应实例传入。第二个依赖的写法有些奇怪:"?^ngModel",?的含义是即使没有找到该依赖,也不要抛出异常,即该依赖是一个可选项。^的含义是查找父元素的controller。
然后,定义该directive中用到的一些默认设置,通过constant directive实现:
app.constant('minuteSecondPickerConfig', {
minuteStep: 1,
secondStep: 1,
readonlyInput: false,
mousewheel: true
});
紧接着是directive对应的controller,它的声明如下:
app.controller('minuteSecondPickerController', ['$scope', '$attrs', '$parse', 'minuteSecondPickerConfig',
function($scope, $attrs, $parse, minuteSecondPickerConfig) {
...
}]);
在directive的link函数中,调用了此controller的init方法:
this.init = function(ngModelCtrl_, inputs) {
ngModelCtrl = ngModelCtrl_;
ngModelCtrl.$render = this.render;
var minutesInputEl = inputs.eq(0),
secondsInputEl = inputs.eq(1);
var mousewheel = angular.isDefined($attrs.mousewheel) ?
$scope.$parent.$eval($attrs.mousewheel) : minuteSecondPickerConfig.mousewheel;
if(mousewheel) {
this.setupMousewheelEvents(minutesInputEl, secondsInputEl);
}
$scope.readonlyInput = angular.isDefined($attrs.readonlyInput) ?
$scope.$parent.$eval($attrs.readonlyInput) : minuteSecondPickerConfig.readonlyInput;
this.setupInputEvents(minutesInputEl, secondsInputEl);
};
init方法接受的第二个参数是inputs,在link函数中传入的是:element.find('input')。 所以第一个输入框用来输入分钟,第二个输入框用来输入秒。
然后,检查是否覆盖了mousewheel属性,如果没有覆盖则使用在constant中设置的默认mousewheel,并进行相关设置如下:
// respond on mousewheel spin
this.setupMousewheelEvents = function(minutesInputEl, secondsInputEl) {
var isScrollingUp = function(e) {
if(e.originalEvent) {
e = e.originalEvent;
}
// pick correct delta variable depending on event
var delta = (e.wheelData) ? e.wheelData : -e.deltaY;
return (e.detail || delta > 0);
};
minutesInputEl.bind('mousewheel wheel', function(e) {
$scope.$apply((isScrollingUp(e)) ? $scope.incrementMinutes() : $scope.decrementMinutes());
e.preventDefault();
});
secondsInputEl.bind('mousewheel wheel', function(e) {
$scope.$apply((isScrollingUp(e)) ? $scope.incrementSeconds() : $scope.decrementSeconds());
e.preventDefault();
});
};