Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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). 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. 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. Specifically, DOM listener registration should be done in a linking function rather than in a compile function.

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.

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-model2 instead of ng-model which I rename during compile. If I do not do this everything is still broken. Still no idea why.

like image 261
yankee Avatar asked Apr 10 '15 13:04

yankee


People also ask

How to make datepicker with angular UI bootstrap?

Angular UI Bootstrap is an Angular JS framework created by Angular UI developers for providing better UI which can be used easily. First, add Angular UI bootstrap scripts needed for your project. Make datepicker with its UIBootStrap classes which will set the UI look for the datepickers.

How to use Bootstrap 3 and Bootstrap 4 with angular?

All bootstraps 3 and bootstrap 4 native Angular directives such as buttons, datepicker, model, pagination, etc can be provided by the Ng bootstrap. Using the Ng Bootstrap, bootstrap UI can be easily used. To add this, we are going to create one field of input with datepicker, and it will be later used in angular application.

How to create a wrapper directive around bootstrap-DateTimePicker component?

A wrapper directive around the bootstrap-datetimepicker component. 1- Install the directive via bower (or download it manually, as you prefer) 2- Load all required scripts by eonasdan-bootstrap-datetimepicker .

How to use datepicker directive in HTML?

5- You can start using the directive in your HTML in several ways like: c) Set the default options with the provider and use the datepicker with those default options. All options are named identically with the same letter case. All of them :) If you find anyone that is causing havoc file an issue.


1 Answers

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.

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

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

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

Plunker

like image 148
Omri Aharon Avatar answered Sep 22 '22 10:09

Omri Aharon