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

How to create an angularJs wrapper directive for a ui-bootstrap datepicker?

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

Here is a plunkr 与我的指令。但是我似乎做错了。如果您使用不使用我的指令的 "Date 1" 日期选择器选择日期选择器,则一切正常。 我希望 Date 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" />

(概念灵感来自 this answer

我正在使用 angular 1.3(plunker 在 1.2 上,因为我刚刚从 angular-ui-bootstrap datepicker 文档中分叉了 plunker)。我希望这不会造成任何影响。

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

更新

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

Note: The template instance and the link instance may be different objects if the template has been cloned. For this reason it is not safe to do anything other than DOM transformations that apply to all cloned DOM nodes within the compile function. Specifically, DOM listener registration should be done in a linking function rather than in a compile function.

特别想知道"that apply to all cloned DOM nodes"是什么意思。我最初认为这意味着 "that apply to all clones of the DOM template" 但事实并非如此。

总之:我的新编译版本在 chromium 中运行良好。在 Firefox 中,我需要首先使用日期选择器 select 一个日期,然后一切正常(如果我在日期的日期解析器中将 undefined 更改为 null (plunkr),Firefox 的问题就会自行解决选择器)。所以这也不是最新的事情。此外,我使用 ng-model2 而不是 ng-model,我在编译期间重命名了它。如果我不这样做,一切都还是坏的。仍然不知道为什么。

我已经尝试完成这项工作(有些 hack),这可能不是您想要的,只是一些粗略的想法。所以你仍然需要稍微调整一下。 plunker 是:

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

基本上,我更改了指令范围,还添加了范围变量 container.two 的监视。

老实说,我不太清楚为什么会这样,以及是什么导致你的日期在输入中显示之前 "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 已更新。

Plunker

这是我的 plunker 猴子补丁,

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

基本上我所做的是使用指令

将您的模型(日期)更改为return格式的字符串
.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 属性。

如果我是你,我不会做那么复杂的指令。我会简单地添加一个 <datepicker> 附加到具有相同 ng-model 的 input 标签,并使用按钮控制 show/hide。您可以从 my 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 个日期选择器,一个是你的格式,一个是默认值,然后应用默认值。

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

Updated Plunkr

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

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>

此外,如果您想强制使用德语语言环境格式,您可以添加 angular-locale_de.js。这确保了 'shortDate' 等日期常量的统一使用,并强制使用德语月份和日期名称。

将这两行添加到您的指令定义后,您的指令将起作用:

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

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

所以在你的代码中

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

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

使用 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>