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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/29562213/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-28 10:44:02  来源:igfitidea点击:

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

javascriptjqueryangularjsangularjs-directiveangular-ui-bootstrap

提问by yankee

I am using the ui.bootstrap.datepickerdirective to display some date field. 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.

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

Here is a plunkrwith my directive. 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. 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 (e.g. Fri Apr 03 2015 00:00:00 GMT+0200 (CEST)).

这是一个带有我的指令的 plunkr。但是我似乎做错了。如果您使用不使用我的指令的“Date 1”日期选择器选择带有日期选择器的日期,则一切正常。我希望日期 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-bootstrapdatepicker documentation). I hope this does not make any difference.

我正在使用 angular 1.3(plunker 在 1.2 上,因为我刚刚从angular-ui-bootstrapdatepicker 文档中分叉了 plunker )。我希望这没有任何区别。

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 plunkrI use the compile function rather than the link function to do my DOM manipulation. I am still a little confused by this excerpt from the docs:

与此同时,我取得了一些进展。在阅读了更多关于编译和链接的细节之后,在这个 plunkr 中,我使用编译函数而不是链接函数来进行我的 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.

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

Especially I wonder what is meant with "that apply to all cloned DOM nodes". I originally thought this means "that apply to all clones of the DOM template" but that does not seem to be the case.

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

Anyhow: My new compile version works fine in chromium. 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). So this isn't the latest thing either. And additionally I use ng-model2instead of ng-modelwhich I rename during compile. If I do not do this everything is still broken. Still no idea why.

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

采纳答案by Sander_P

Your directive will work when you add these 2 lines to your directive definition:

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

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: ngModeland myDatepicker. With priority you can make your own directive execute before ngModel does.

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

回答by Omri Aharon

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 $compileservice, attributes changes, scope inheritance, requirein 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. This is my changed directive:

但是,我确实找到了重构指令的地方,并删除了许多不必要的代码,例如$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;
          };

      }
  };
});

And your HTML usage becomes:

你的 HTML 用法变成了:

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

Edit: Added the idas a parameter to the directive. Plunker has been updated.

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

Plunker

普朗克

回答by jme11

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. This ensures uniformity in the use of date constants like 'shortDate'and forces the use of German month and day names.

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

回答by allenhwkim

Here is my monkey patch of your plunker,

这是我的猴子补丁你的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-formatattribute for your inputtag.

您需要date-formatinput标签传递属性。

If I were you, I would not go that far to make a complex directive. I would simply add a <datepicker>appended to your inputtag with the same ng-model, and control show/hide with a button. You may experiment your option starting from my plunker

如果我是你,我不会走那么远来制定复杂的指令。我会简单地使用相同的 ng-model添加<datepicker>附加到您的input标签,并使用按钮控制显示/隐藏。你可以从我的plunker开始试验你的选择

回答by gonkan

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: trueto scope: falsein the myDatepickerdirective.

然后通过在指令中更改scope: true为来避免多个隔离范围。scope: falsemyDatepicker

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 个日期选择器,一个使用你的格式,一个使用默认值,之后应用 get。

回答by Aditya Singh

Use moment.js with ui-bootstrap datepicker component to create the directive to provide a comprehensive set of patterns for date time formats. You can accept any time format within the isolated scope.

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

回答by Adam Plocher

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>

回答by ABOS

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:

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

`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 添加了监视。