web-dev-qa-db-fra.com

Comment créer une directive wrapper angularJs pour un datepicker ui-bootstrap?

J'utilise la directive ui.bootstrap.datepicker pour afficher un champ de date. Cependant, la plupart du temps, j'ai besoin de la même configuration: je veux qu'elle vienne avec un menu contextuel et un bouton contextuel, ainsi que des noms en allemand pour les textes. Cela crée le même code pour le bouton et les textes et la mise en forme, encore et encore, alors j'ai écrit ma propre directive pour m'empêcher de me répéter.

Voici un plunkr avec ma directive. Cependant, il semble que je me trompe. Si vous choisissez une date avec le sélecteur de date à l'aide du sélecteur de date "Date 1" qui n'utilise pas ma directive, tout fonctionne correctement . Je m'attendrais à ce qu'il en soit de même pour Date 2, mais au lieu d'afficher la date conformément au modèle fourni dans le champ de saisie (ou toute autre valeur que j'attendais), il affiche la représentation .toString() de l'objet de date (par exemple, Fri Apr 03 2015 00:00:00 GMT+0200 (CEST)).

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

Et c'est comme ça que je l'utilise:

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

(Le concept a été inspiré de cette réponse )

J'utilise un angulaire 1.3 (le plongeur est sur 1.2 parce que je viens de le fourrer dans la documentation angular-ui-bootstrap datepicker). J'espère que cela ne fait aucune différence.

Pourquoi la sortie de texte dans mon entrée est-elle incorrecte et comment est-il effectué correctement?

Mettre à jour

En attendant, j'ai fait un petit progrès. Après avoir lu plus d'informations sur les détails de compilation et de liaison, dans this plunkr , j'utilise la fonction de compilation plutôt que la fonction de liaison pour effectuer ma manipulation DOM. Je suis encore un peu confus par cet extrait de la documentation:

Remarque: L'instance de modèle et l'instance de lien peuvent être des objets différents si le modèle a été cloné. Pour cette raison, il n’est pas prudent de faire autre chose que des transformations DOM qui s’appliquent à tous les nœuds DOM clonés au sein de la fonction de compilation. Plus précisément, l'enregistrement de l'auditeur DOM doit être effectué dans une fonction de liaison plutôt que dans une fonction de compilation.

En particulier, je me demande ce que l’on entend par "qui s’applique à tous les nœuds DOM clonés". Au départ, je pensais que cela signifiait "qui s'applique à tous les clones du modèle DOM", mais cela ne semble pas être le cas.

Quoi qu'il en soit: ma nouvelle version de compilation fonctionne très bien en chrome. Dans Firefox, je dois d'abord sélectionner une date à l'aide d'un sélecteur de date, puis tout fonctionne correctement (le problème avec Firefox s'est résolu si je modifie undefined en null ( plunkr ) dans l'analyseur de date du sélecteur de date). Donc, ce n'est pas la dernière chose non plus. Et en plus, j'utilise ng-model2 au lieu de ng-model que je renomme lors de la compilation. Si je ne fais pas cela, tout est encore cassé. Toujours pas idée pourquoi.

50
yankee

Votre directive fonctionnera lorsque vous ajouterez ces 2 lignes à votre définition de directive:

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

Cela a à voir avec l'ordre dans lequel les directives sont exécutées.

Donc dans votre code

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

Il existe deux directives: ngModel et myDatepicker. Avec priorité, vous pouvez faire exécuter votre propre directive avant ngModel.

9
Sander_P

Pour être honnête, je ne suis pas tout à fait sûr de savoir pourquoi et la cause de votre rendez-vous avant de l'afficher dans l'entrée.

Cependant, j'ai trouvé des endroits pour restructurer votre directive et supprimer beaucoup de code inutile, tel que le service $compile, les modifications d'attributs, l'héritage de la portée, la variable require, etc. devrait connaître le périmètre parent car cela pourrait causer des bugs vicieux à l'avenir. Voici ma directive modifiée:

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

      }
  };
});

Et votre utilisation du HTML devient:

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

Edit: Ajout de id en tant que paramètre de la directive. Plunker a été mis à jour.

Plunker

17
Omri Aharon

Je pense que la réponse de @ omri-aharon est la meilleure, mais j'aimerais souligner quelques améliorations qui n'ont pas été mentionnées ici:

Mise à jour Plunkr

Vous pouvez utiliser la configuration pour définir uniformément vos options telles que le format et le texte comme suit:

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

Je trouve cela plus clair et plus facile à mettre à jour. Cela vous permet également de simplifier considérablement la directive, le modèle et le balisage.

Directive personnalisée

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

      }
  };
});

Modèle

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

Comment l'utiliser

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

De plus, si vous souhaitez imposer l’utilisation d’un formatage de paramètres régionaux en allemand, vous pouvez ajouter angular-locale_de.js. Cela garantit l’uniformité dans l’utilisation des constantes de date telles que 'shortDate' et oblige à utiliser les noms de mois et de jours allemands.

4
jme11

Voici mon patch singe de votre plunker,

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

Fondamentalement, ce que j'ai fait était de changer votre modèle, qui est une date, pour renvoyer une chaîne formatée à l'aide d'une 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;
      });
    }
  };
});

Vous devez transmettre l'attribut date-format pour votre balise input.

Si j'étais vous, je n'irais pas aussi loin pour élaborer une directive complexe. Je voudrais simplement ajouter un <datepicker> ajouté à votre balise input avec le même modèle-ng et contrôler afficher/masquer avec un bouton. Vous pouvez expérimenter votre option à partir de my plunker

2
allenhwkim

Si la création de la directive est pratique pour ajouter les attributs, vous pouvez avoir les 2 directives sur l'entrée d'origine:

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

Ensuite, évitez les portées d'isolement multiples en modifiant scope: true en scope: false dans la directive myDatepicker.

Cela fonctionne et je pense qu'il est préférable de créer une directive supplémentaire pour changer l'entrée de date au format souhaité:

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

Pourquoi vous ajoutez l'attribut dans la directive provoque ce problème? Je n'en ai aucune idée, c'est presque comme si vous aviez 2 sélecteurs de date sur la même entrée, une avec votre format et l'autre avec le paramètre par défaut qui sera appliqué après.

0
gonkan

Si quelqu'un est intéressé par une implémentation TypeScript (basée vaguement sur le code de @ jme11):

Directif:

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

Modèle: 

<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>
0
Adam Plocher

Utilisez moment.js avec le composant ui-bootstrap datepicker pour créer la directive afin de fournir un ensemble complet de modèles pour les formats de date et d'heure. Vous pouvez accepter n'importe quel format d'heure dans la portée isolée.

0
Aditya Singh