简体   繁体   English

如何为ui-bootstrap datepicker创建angularJs包装器指令?

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

I am using the ui.bootstrap.datepicker directive to display some date field. 我使用ui.bootstrap.datepicker指令来显示一些日期字段。 However most of the time I need the same setup: I want it to come along with a popup and a popup button and also I want German names for the texts. 然而,大多数时候我需要相同的设置:我希望它带有一个弹出按钮和一个弹出按钮,我也想要文本的德语名称。 That does create the same code for the button and the texts and the formatting over and over again, so I wrote my own directive to prevent myself from repeating myself. 这确实为按钮和文本以及格式反复创建了相同的代码,因此我编写了自己的指令以防止自己重复自己。

Here is a plunkr with my directive. 这是我的指令的一个plunkr However I seem to be doing it wrong. 但是我似乎做错了。 If you choose a date with the date picker using the "Date 1" datepicker that does not use my directive everything works fine. 如果您使用不使用我的指令的“日期1”日期选择器选择日期选择器的日期一切正常。 I'd expect the same for Date 2, but instead of displaying the date according to the template I supplied in the input field (or any other value I expected) it displays the .toString() representation of the date object (eg Fri Apr 03 2015 00:00:00 GMT+0200 (CEST) ). 我期望日期2相同,但不是根据我在输入字段中提供的模板(或我预期的任何其他值)显示日期,而是显示日期对象的.toString()表示(例如, Fri Apr 03 2015 00:00:00 GMT+0200 (CEST) )。

Here is my directive: 这是我的指示:

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
  };
});

And that's how I use it: 这就是我使用它的方式:

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

(Concept was inspired from this answer ) (概念灵感来自这个答案

I am using angular 1.3 (the plunker is on 1.2 because I just forked the plunker from the angular-ui-bootstrap datepicker documentation). 我正在使用角度1.3(因为我只是从angular-ui-bootstrap datepicker文档中分叉了plunker,所以它是1.2。 I hope this does not make any difference. 我希望这没有任何区别。

Why is the text output in my input wrong and how is it done correctly? 为什么输入中的文本输出错误,如何正确完成?

Update 更新

In the meantime I made a little progress. 与此同时,我取得了一些进展。 After reading more about the details about compile and link, in this plunkr I use the compile function rather than the link function to do my DOM manipulation. 在阅读了有关编译和链接的详细信息之后,在这个plunkr中我使用编译函数而不是链接函数来执行我的DOM操作。 I am still a little confused by this excerpt from the docs: 我对这些文档的摘录感到有些困惑:

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. 因此,除了适用于编译函数中所有克隆DOM节点的DOM转换之外,执行任何操作都是不安全的。 Specifically, DOM listener registration should be done in a linking function rather than in a compile function. 具体来说,DOM侦听器注册应该在链接函数中完成,而不是在编译函数中完成。

Especially I wonder what is meant with "that apply to all cloned DOM nodes". 特别是我想知道“适用于所有克隆的DOM节点”是什么意思。 I originally thought this means "that apply to all clones of the DOM template" but that does not seem to be the case. 我原本认为这意味着“适用于DOM模板的所有克隆”,但似乎并非如此。

Anyhow: My new compile version works fine in chromium. 无论如何:我的新编译版本在chrome中运行良好。 In Firefox I need to first select a date using a date picker and after that everything works fine (the problem with Firefox solved itself if I change undefined to null ( plunkr ) in the date parser of the date picker). 在Firefox中我需要首先使用日期选择器选择一个日期,之后一切正常(如果我在日期选择器的日期解析器中将 undefined更改为null( plunkr ),Firefox的问题就解决了)。 So this isn't the latest thing either. 所以这也不是最新的事情。 And additionally I use ng-model2 instead of ng-model which I rename during compile. 另外我使用ng-model2而不是ng-model ,我在编译时重命名。 If I do not do this everything is still broken. 如果我不这样做,一切都还是破了。 Still no idea why. 仍然不知道为什么。

To be honest, I'm not quite sure why it's caused and what's causing your date to be "toString-ed" before showing it in the input. 说实话,我不太清楚为什么它会导致你的日期在输入中显示之前是什么导致你的日期“toString-ed”。

However, I did find places to restructure your directive, and remove much unnecessary code, such as $compile service, attributes changes, scope inheritance, require in the directive, etc.. I used isolated scope, since I don't think every directive usage should know the parent scope as this might cause vicious bugs going forward. 但是,我确实找到了重构你的指令的地方,并删除了许多不必要的代码,例如$compile服务,属性更改,范围继承,指令中的require等。我使用了隔离范围,因为我不认为每个指令用法应该知道父范围,因为这可能会导致恶性错误。 This is my changed directive: 这是我改变的指令:

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;
          };

      }
  };
});

And your HTML usage becomes: 您的HTML使用情况变为:

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

Edit : Added the id as a parameter to the directive. 编辑 :将id作为参数添加到指令中。 Plunker has been updated. Plunker已更新。

Plunker Plunker

Your directive will work when you add these 2 lines to your directive definition: 将这两行添加到指令定义时,您的指令将起作用:

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

This has to do with the order in which directives are executed. 这与指令的执行顺序有关。

So in your code 所以在你的代码中

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

There are two directives: ngModel and myDatepicker . 有两个指令: ngModelmyDatepicker With priority you can make your own directive execute before ngModel does. 优先级可以在ngModel之前执行自己的指令。

I think the answer from @omri-aharon is the best, but I'd like to point out some improvements that haven't been mentioned here: 我认为来自@ omri-aharon的答案是最好的,但我想指出一些未在此处提及的改进:

Updated Plunkr 更新了Plunkr

You can use the config to uniformly set your options such as the format and text options as follows: 您可以使用配置统一设置选项,如格式和文本选项,如下所示:

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";
});

I find this to be clearer and easier to update. 我发现这更清晰,更容易更新。 This also allows you to vastly simplify the directive, template and markup. 这也允许您大大简化指令,模板和标记。

Custom Directive 自定义指令

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;
          };

      }
  };
});

Template 模板

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

How to Use It 如何使用它

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

Further, if you want to enforce the use of a German locale formatting, you can add angular-locale_de.js. 此外,如果要强制使用德语区域设置格式,可以添加angular-locale_de.js。 This ensures uniformity in the use of date constants like 'shortDate' and forces the use of German month and day names. 这确保了使用日期常量(如'shortDate'统一性,并强制使用德语月份和日期名称。

Here is my monkey patch of your plunker, 这是我的你的猴子补丁,

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

Basically what I did was to change your model, which is a date, to return formatted string using a directive 基本上我所做的是改变你的模型,这是一个日期,使用指令返回格式化的字符串

.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;
      });
    }
  };
});

You need to pass date-format attribute for your input tag. 您需要为input标记传递date-format属性。

If I were you, I would not go that far to make a complex directive. 如果我是你,那么我就不会做那么复杂的指令。 I would simply add a <datepicker> appended to your input tag with the same ng-model, and control show/hide with a button. 我只需添加一个<datepicker>附加到您的input标签与相同的ng模型,并控制显示/隐藏一个按钮。 You may experiment your option starting from my plunker 您可以从我的plunker开始尝试您的选项

If creating the directive is a convenience to add the attributes you can have the 2 directives on the original input: 如果创建指令是为了方便添加属性,则可以在原始输入上使用2个指令:

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

Then avoid the multiple isolate scopes by changing scope: true to scope: false in the myDatepicker directive. 然后通过更改scope: true避免多个隔离scope: truescope: false myDatepicker指令中的scope: false

This works and I think it's preferable to creating a further directive to change the date input to the desired format: 这有效,我认为最好创建一个进一步的指令来将日期输入更改为所需的格式:

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

Why you adding the attribute from within the directive causes this issue I have no idea, it's almost like you have 2 date-pickers on the same input, one with your format and one with default that get's applied after. 为什么你在指令中添加属性导致这个问题我不知道,这几乎就像你在同一个输入上有2个日期选择器,一个是你的格式,另一个是默认的,后来应用了。

Use moment.js with ui-bootstrap datepicker component to create the directive to provide a comprehensive set of patterns for date time formats. 将moment.js与ui-bootstrap datepicker组件一起使用以创建指令,以便为日期时间格式提供全面的模式集。 You can accept any time format within the isolated scope. 您可以在隔离范围内接受任何时间格式。

If anyone is interested in a Typescript implementation (loosely based on @jme11's code): 如果有人对Typescript实现感兴趣(松散地基于@jme11的代码):

Directive: 指示:

'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())

Template: 模板:

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

Usage: 用法:

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

I have tried to make this work (somewhat hack), which might not be exactly what you want, just some rough ideas. 我试图让这项工作(有些黑客),这可能不是你想要的,只是一些粗略的想法。 So you still need to tweak it a little bit. 所以你仍然需要调整一下。 The plunker is: 吸管是:

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

Basically, I changed the directive scope, and also add watch for scope var container.two. 基本上,我更改了指令范围,还添加了范围var container.two的监视。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM