wake-up-neo.com

Wie erstelle ich eine angleJs-Wrapper-Direktive für einen ui-bootstrap-Datepicker?

Ich verwende die Direktive ui.bootstrap.datepicker , um ein Datumsfeld anzuzeigen. Meistens brauche ich jedoch das gleiche Setup: Ich möchte, dass es mit einem Popup- und einem Popup-Button kommt und ich möchte auch deutsche Namen für die Texte. Dadurch wird derselbe Code für die Schaltfläche und die Texte und die Formatierung immer wieder erstellt. Deshalb habe ich eine eigene Anweisung geschrieben, um mich daran zu hindern, mich zu wiederholen.

Hier ist ein Plunkr mit meiner Direktive. Ich scheine es jedoch falsch zu machen. Wenn Sie mit der Datumsauswahl ein Datum mit dem Datumsauswahlfeld "Datum 1" auswählen, das meine Direktive nicht verwendet, funktioniert alles einwandfrei . Ich würde dasselbe für Datum 2 erwarten, aber anstatt das Datum gemäß der Vorlage I anzuzeigen Im Eingabefeld (oder einem anderen von mir erwarteten Wert) wird die .toString()-Darstellung des Date-Objekts angezeigt (z. B. Fri Apr 03 2015 00:00:00 GMT+0200 (CEST)).

Hier ist meine Anweisung:

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

Und so benutze ich es:

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

(Konzept wurde inspiriert von diese Antwort )

Ich verwende eckig 1.3 (der Plunker ist auf 1.2, da ich den Plunker gerade aus der angle-ui-bootstrap datepicker-Dokumentation herausgegriffen habe). Ich hoffe, das macht keinen Unterschied.

Warum ist der Text in meiner Eingabe falsch und wie wird er richtig gemacht?

Aktualisieren

In der Zwischenzeit habe ich ein bisschen Fortschritte gemacht. Nachdem ich mehr über die Details zu compile und link gelesen habe, verwende ich in this plunkr / die Compile-Funktion anstelle der Link-Funktion, um meine DOM-Manipulation durchzuführen. Ich bin immer noch etwas verwirrt von diesem Auszug aus den Dokumenten:

Hinweis: Die Vorlageninstanz und die Linkinstanz können unterschiedliche Objekte sein, wenn die Vorlage geklont wurde. Aus diesem Grund ist es nicht sicher, etwas anderes als DOM-Transformationen auszuführen, die für alle geklonten DOM-Knoten in der Kompilierungsfunktion gelten. Die Registrierung des DOM-Listeners sollte insbesondere in einer Verknüpfungsfunktion und nicht in einer Kompilierungsfunktion erfolgen.

Insbesondere frage ich mich, was mit "das gilt für alle geklonten DOM-Knoten" gemeint ist. Ich dachte ursprünglich, dies bedeutet "das gilt für alle Klone der DOM-Vorlage", aber das scheint nicht der Fall zu sein.

Wie auch immer: Meine neue Compile-Version funktioniert gut in Chrom. In Firefox muss ich zuerst ein Datum mit einer Datumsauswahl auswählen und danach funktioniert alles einwandfrei (das Problem mit Firefox löste sich, wenn ich im Datums-Parser der Datumsauswahl undefined zu null ( plunkr ändere). Das ist also auch nicht das Neueste. Und zusätzlich verwende ich ng-model2 anstelle von ng-model, den ich beim Kompilieren umbenenne. Wenn ich das nicht mache, ist immer noch alles kaputt. Immer noch keine Ahnung warum.

50
yankee

Ihre Direktive funktioniert, wenn Sie diese beiden Zeilen Ihrer Direktive-Definition hinzufügen:

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

Dies hat mit der Reihenfolge zu tun, in der Anweisungen ausgeführt werden.

Also in deinem Code

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

Es gibt zwei Anweisungen: ngModel und myDatepicker. Mit Priorität können Sie Ihre eigene Anweisung vor ngModel ausführen lassen.

9
Sander_P

Um ehrlich zu sein, ich bin nicht ganz sicher, warum es verursacht wurde und was dazu führt, dass Ihr Datum "toString-ed" wird, bevor es in der Eingabe angezeigt wird.

Ich habe jedoch Orte gefunden, um Ihre Direktive umzustrukturieren und viel unnötigen Code zu entfernen, wie $compile-Service, Attributänderungen, Vererbung des Bereichs, require in der Direktive usw. Ich habe isolierten Geltungsbereich verwendet, da ich nicht jede Verwendung von Direktiven glaube sollte den übergeordneten Bereich kennen, da dies möglicherweise zu bösartigen Fehlern führen kann. Dies ist meine geänderte Anweisung:

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

      }
  };
});

Und Ihre HTML-Verwendung wird zu:

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

Edit: id als Parameter zur Direktive hinzugefügt. Plunker wurde aktualisiert.

Plunker

17
Omri Aharon

Ich denke, die Antwort von @ omri-aharon ist die beste, aber ich möchte auf einige Verbesserungen hinweisen, die hier nicht erwähnt wurden:

Aktualisiertes Plunkr

Sie können die Konfiguration verwenden, um Ihre Optionen wie Format und Textoptionen wie folgt einheitlich festzulegen:

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

Ich finde das klarer und einfacher zu aktualisieren. Dadurch können Sie auch die Direktive, die Vorlage und das Markup erheblich vereinfachen.

Zollrichtlinie

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

      }
  };
});

Vorlage

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

Wie benutzt man es?

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

Wenn Sie die Verwendung einer deutschen Gebietsschemaformatierung erzwingen möchten, können Sie angle-locale_de.js hinzufügen. Dies gewährleistet die Einheitlichkeit bei der Verwendung von Datumskonstanten wie 'shortDate' und zwingt die Verwendung von deutschen Monats- und Tagesnamen.

4
jme11

Hier ist mein Affen-Patch deines Plunkers,

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

Im Grunde habe ich Ihr Modell, ein Datum, geändert, um eine formatierte Zeichenfolge mithilfe einer Direktive zurückzugeben

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

Sie müssen das date-format-Attribut für Ihr input-Tag übergeben.

Wenn ich Sie wäre, würde ich nicht so weit gehen, um eine komplexe Richtlinie zu erlassen. Ich füge einfach einen <datepicker> hinzu, der an Ihr input-Tag mit demselben ng-Modell angehängt wurde, und kontrolliere das Ein-/Ausblenden mit einer Schaltfläche. Sie können Ihre Option ausgehend von meinem Plunker ausprobieren.

2
allenhwkim

Wenn Sie die Direktive erstellen, um die Attribute hinzuzufügen, können Sie die 2 Direktiven zur ursprünglichen Eingabe hinzufügen:

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

Vermeiden Sie dann die mehrfach isolierten Bereiche, indem Sie scope: true in scope: false in der Anweisung myDatepicker ändern.

Dies funktioniert und ich denke, es ist vorzuziehen, eine weitere Direktive zu erstellen, um die Datumseingabe in das gewünschte Format zu ändern:

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

Warum Sie das Attribut aus der Direktive hinzufügen, verursacht dieses Problem. Ich habe keine Ahnung. Es ist fast so, als hätten Sie zwei Datumsauswahl für dieselbe Eingabe, eine mit Ihrem Format und eine mit Standard, auf die angewendet wird.

0
gonkan

Wenn sich jemand für eine TypeScript-Implementierung interessiert (lose basierend auf @ jme11-Code):

Richtlinie:

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

Vorlage: 

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

Verwendungszweck:

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

Verwenden Sie moment.js mit der ui-bootstrap-Datepicker-Komponente, um die Direktive zu erstellen, um einen umfassenden Satz von Mustern für Datumszeitformate bereitzustellen. Sie können jedes Zeitformat im isolierten Bereich akzeptieren.

0
Aditya Singh