如何为 ui-bootstrap 日期选择器创建 angularJs 包装器指令



我正在使用ui.bootstrap.datepicker指令来显示一些日期字段。然而,大多数时候我需要相同的设置:我希望它带有弹出窗口和弹出按钮,我还想要文本的德语名称。这确实为按钮、文本和格式一遍又一遍地创建了相同的代码,所以我编写了自己的指令以防止自己重复自己。

这是一个带有我的指示的 plunkr。但是我似乎做错了。如果您使用不使用我的指令的"日期 1"日期选择器选择日期,则一切正常。 我希望日期 2 也是如此,但它不是根据我在输入字段中提供的模板(或我期望的任何其他值)显示日期,而是显示日期对象的.toString()表示形式(例如Fri Apr 03 2015 00:00:00 GMT+0200 (CEST))。

这是我的指示:

angular.module('ui.bootstrap.demo').directive('myDatepicker', function($compile) {
var controllerName = 'dateEditCtrl';
return {
restrict: 'A',
require: '?ngModel',
scope: true,
link: function(scope, element) {
var wrapper = angular.element(
'<div class="input-group">' +
'<span class="input-group-btn">' +
'<button type="button" class="btn btn-default" ng-click="' + controllerName + '.openPopup($event)"><i class="glyphicon glyphicon-calendar"></i></button>' +
'</span>' +
'</div>');
function setAttributeIfNotExists(name, value) {
var oldValue = element.attr(name);
if (!angular.isDefined(oldValue) || oldValue === false) {
element.attr(name, value);
}
}
setAttributeIfNotExists('type', 'text');
setAttributeIfNotExists('is-open', controllerName + '.popupOpen');
setAttributeIfNotExists('datepicker-popup', 'dd.MM.yyyy');
setAttributeIfNotExists('close-text', 'Schließen');
setAttributeIfNotExists('clear-text', 'Löschen');
setAttributeIfNotExists('current-text', 'Heute');
element.addClass('form-control');
element.removeAttr('my-datepicker');
element.after(wrapper);
wrapper.prepend(element);
$compile(wrapper)(scope);
scope.$on('$destroy', function () {
wrapper.after(element);
wrapper.remove();
});
},
controller: function() {
this.popupOpen = false;
this.openPopup = function($event) {
$event.preventDefault();
$event.stopPropagation();
this.popupOpen = true;
};
},
controllerAs: controllerName
};
});

这就是我使用它的方式:

<input my-datepicker="" type="text" ng-model="container.two" id="myDP" />

(概念灵感来自这个答案)

我使用的是角度 1.3(plunker 在 1.2 上,因为我刚刚从 angular-ui-bootstrap 日期选择器文档中分叉了 plunker)。我希望这不会有任何区别。

为什么我的输入中的文本输出错误以及如何正确完成?

更新

与此同时,我取得了一点进展。在阅读了有关编译和链接的详细信息后,在此 plunkr 中,我使用编译函数而不是链接函数来执行我的 DOM 操作。我仍然对文档中的这段摘录感到有些困惑:

注: 如果模板已克隆,则模板实例和链接实例可能是不同的对象。出于这个原因,除了应用于编译函数中所有克隆的 DOM 节点的 DOM 转换之外,执行任何其他操作都是不安全的。具体来说,DOM 侦听器注册应该在链接函数中完成,而不是在编译函数中完成。

特别是我想知道"适用于所有克隆的 DOM 节点"是什么意思。我最初认为这意味着"适用于 DOM 模板的所有克隆",但事实似乎并非如此。

无论如何:我的新编译版本在铬中工作正常。在 Firefox 中,我需要首先使用日期选择器选择一个日期,然后一切正常(如果我在日期选择器的日期解析器中将 undefined 更改为 null (plunkr),Firefox 的问题自行解决)。所以这也不是最新的事情。此外,我使用ng-model2而不是我在编译过程中重命名的ng-model。如果我不这样做,一切都会坏掉。仍然不知道为什么。

老实说,在输入显示它之前,我不太确定为什么会引起它以及是什么导致您的日期被"toString-ed"。

但是,我确实找到了重构指令的地方,并删除了许多不必要的代码,例如$compile服务、属性更改、作用域继承、指令中的require等。我使用了隔离的作用域,因为我认为不是每个指令用法都应该知道父作用域,因为这可能会导致恶性错误。这是我更改的指令:

angular.module('ui.bootstrap.demo').directive('myDatepicker', function() {
return {
restrict: 'A',
scope: {
model: "=",
format: "@",
options: "=datepickerOptions",
myid: "@"
},
templateUrl: 'datepicker-template.html',
link: function(scope, element) {
scope.popupOpen = false;
scope.openPopup = function($event) {
$event.preventDefault();
$event.stopPropagation();
scope.popupOpen = true;
};
scope.open = function($event) {
$event.preventDefault();
$event.stopPropagation();
scope.opened = true;
};
}
};
});

您的 HTML 用法将变为:

<div my-datepicker model="container.two" 
datepicker-options="dateOptions" 
format="{{format}}"  
myid="myDP">
</div>

编辑:将id作为参数添加到指令中。Plunker 已更新。

普伦克

当您将以下 2 行添加到指令定义中时,您的指令将起作用:

return {
priority: 1,
terminal: true,
...
}

这与执行指令的顺序有关。

所以在你的代码中

<input my-datepicker="" type="text" ng-model="container.two" id="myDP" />

有两个指令:ngModelmyDatepicker。通过优先级,您可以在 ngModel 之前执行自己的指令。

我认为@omri-aharon的答案是最好的,但我想指出一些这里没有提到的改进:

更新的普伦克尔

您可以使用配置统一设置选项,例如格式和文本选项,如下所示:

angular.module('ui.bootstrap.demo', ['ui.bootstrap'])
.config(function (datepickerConfig, datepickerPopupConfig) {
datepickerConfig.formatYear='yy';
datepickerConfig.startingDay = 1;
datepickerConfig.showWeeks = false;
datepickerPopupConfig.datepickerPopup = "shortDate";
datepickerPopupConfig.currentText = "Heute";
datepickerPopupConfig.clearText = "Löschen";
datepickerPopupConfig.closeText = "Schließen";
});

我发现这更清晰,更容易更新。 这也允许您大大简化指令、模板和标记。

自定义指令

angular.module('ui.bootstrap.demo').directive('myDatepicker', function() {
return {
restrict: 'E',
scope: {
model: "=",
myid: "@"
},
templateUrl: 'datepicker-template.html',
link: function(scope, element) {
scope.popupOpen = false;
scope.openPopup = function($event) {
$event.preventDefault();
$event.stopPropagation();
scope.popupOpen = true;
};
scope.open = function($event) {
$event.preventDefault();
$event.stopPropagation();
scope.opened = true;
};
}
};
});

模板

<div class="row">
<div class="col-md-6">
<p class="input-group">
<input type="text" class="form-control" id="{{myid}}" datepicker-popup ng-model="model" is-open="opened" ng-required="true"  />
<span class="input-group-btn">
<button type="button" class="btn btn-default" ng-click="open($event)"><i class="glyphicon glyphicon-calendar"></i></button>
</span>
</p>
</div>
</div> 

如何使用它

<my-datepicker model="some.model" myid="someid"></my-datepicker>

此外,如果要强制使用德语区域设置格式,可以添加角度locale_de.js。 这确保了'shortDate'等日期常量的使用统一性,并强制使用德语月份和日期名称。

这是我的猴子补丁,

http://plnkr.co/edit/9Up2QeHTpPvey6jd4ntJ?p=preview

基本上我所做的是更改您的模型(即日期)以使用指令返回格式化字符串

.directive('dateFormat', function (dateFilter) {
return {
require:'^ngModel',
restrict:'A',
link:function (scope, elm, attrs, ctrl) {
ctrl.$parsers.unshift(function (viewValue) {
viewValue.toString = function() {
return dateFilter(this, attrs.dateFormat);
};
return viewValue;
});
}
};
});

您需要为input标记传递date-format属性。

如果我是你,我不会走那么远来制定一个复杂的指令。我只需添加一个附加到具有相同ng模型的input标签的<datepicker>,并使用按钮控制显示/隐藏。你可以从我的 plunker 开始尝试你的选择

如果创建指令是为了方便添加属性,则可以在原始输入上使用 2 个指令:

<input my-datepicker="" datepicker-popup="{{ format }}" type="text" ng-model="container.two" id="myDP" />

然后,通过在myDatepicker指令中将scope: true更改为scope: false来避免多个隔离作用域。

这有效,我认为最好创建一个进一步的指令将日期输入更改为所需的格式:

http://plnkr.co/edit/23QJ0tjPy4zN16Sa7svB?p=preview

为什么从指令中添加属性会导致这个问题我不知道,这几乎就像您在同一输入上有 2 个日期选择器,一个具有您的格式,另一个具有默认值,之后应用。

使用 moment.js 和 ui-bootstrap 日期选取器组件来创建指令,为日期时间格式提供一组全面的模式。可以接受隔离范围内的任何时间格式。

如果有人对 Typescript 实现感兴趣(松散地基于 @jme11 的代码):

命令:

'use strict';
export class DatePickerDirective implements angular.IDirective {
restrict = 'E';
scope={
model: "=",
myid: "@"
};
template = require('../../templates/datepicker.tpl.html');
link = function (scope, element) {
scope.altInputFormats = ['M!/d!/yyyy', 'yyyy-M!-d!'];
scope.popupOpen = false;
scope.openPopup = function ($event) {
$event.preventDefault();
$event.stopPropagation();
scope.popupOpen = true;
};
scope.open = function ($event) {
$event.preventDefault();
$event.stopPropagation();
scope.opened = true;
};
};
public static Factory() : angular.IDirectiveFactory {
return () => new DatePickerDirective();
}
}
angular.module('...').directive('datepicker', DatePickerDirective.Factory())

模板:

<p class="input-group">
<input type="text" class="form-control" id="{{myid}}"
uib-datepicker-popup="MM/dd/yyyy" model-view-value="true"
ng-model="model" ng-model-options="{ getterSetter: true, updateOn: 'blur' }"
close-text="Close" alt-input-formats="altInputFormats"
is-open="opened" ng-required="true"/><span class="input-group-btn"><button type="button" class="btn btn-default" ng-click="open($event)"><i
class="glyphicon glyphicon-calendar"></i></button>
</span>
</p>

用法:

<datepicker model="vm.FinishDate" myid="txtFinishDate"></datepicker>

我试图使这项工作(有点黑客),这可能不完全是你想要的,只是一些粗略的想法。所以你仍然需要稍微调整一下。plunker 是:

`http://plnkr.co/edit/aNiL2wFz4S0WPti3w1VG?p=preview'

基本上,我更改了指令范围,并为范围 var container.two 添加了监视。

最新更新