One of the first things i wanted to do when i saw Angular UI directives like Datepicker is wrap them into my own. If you look at the example of Datepicker on their site, especially the one
for datepicker popup with a button, you will notice it is extremely involving and spread all over your controller.
var DatepickerDemoCtrl = function ($scope) { $scope.today = function() { $scope.dt = new Date(); }; $scope.today(); $scope.clear = function () { $scope.dt = null; }; // Disable weekend selection $scope.disabled = function(date, mode) { return ( mode === 'day' && ( date.getDay() === 0 || date.getDay() === 6 ) ); }; $scope.toggleMin = function() { $scope.minDate = $scope.minDate ? null : new Date(); }; $scope.toggleMin(); $scope.open = function($event) { $event.preventDefault(); $event.stopPropagation(); $scope.opened = true; }; $scope.dateOptions = { formatYear: 'yy', startingDay: 1 }; $scope.initDate = new Date('2016-15-20'); $scope.formats = ['dd-MMMM-yyyy', 'yyyy/MM/dd', 'dd.MM.yyyy', 'shortDate']; $scope.format = $scope.formats[0]; };
ALL that for just one date picker? Really? You can check out the HTML part on the UIs page, but it’s also pretty large. Granted, you can definitely delete a bunch of these functions and tags like initDate etc, but you still have a lot going on there just to hookup a datepicker with a button. So i decided to wrap those into my own directive and get rid of that code smell, and here’s what i ended up with:
(function() { 'use strict'; var module = angular.module('altinet.directives', []); module.directive('altiDatepicker', [altiDatePicker]); function altiDatePicker() { var mprefix = 'altidatePicker1234'; var linkFunc= function(scope, elem, attrs) { scope[mprefix+'open'] = function ($event) { $event.preventDefault(); $event.stopPropagation(); scope[mprefix+'opened'] = true; }; }; return { restrict: 'AE', replace: true, scope:true, compile: function (element, attrs) { var html = '<p class="input-group">'+ '<input type="text" class="form-control" datepicker-popup="dd.MM.yyyy" ng-model="'+attrs.ngModel+'" is-open="'+mprefix+'opened" ng-required="true" date-disabled="disabled(date, mode)" ng-required="true" close-text="Close" />' +'<span class="input-group-btn">' + '<button type="button" class="btn btn-default" ng-click="'+mprefix+'open($event)"><i class="glyphicon glyphicon-calendar"></i></button></span></p>'; var e = angular.element(html); element.replaceWith(e); return linkFunc; } } } })();
Lets’ go through it step by step:
1. first few lines is about declaring a directive, you can find a lot about that in the docs, but what you have to keep mind is that if you name your directive ‘altiDatepicker’, you will use it with a ‘data-alti-datepicker’ tag (see what they did there?).
2. every directive function has to return an object which can contain properties like “restrict” (which tells how you use the directive), “replace” (does it replace the current code),
“compile” (a function which will be executed and where you do all the magic), or “link” (a function used for hooking up).
3. Beware, if you already have “compile” defined, your “link” function WILL NOT BE EXECUTED unless you return it as a RESULT of your compile function (like in my example).
4. The compile function is the critical part, what i basically did there is took the html of a datepicker and of a button, i concatenated attribute value “ngModel”, i ran that html string through angular.element(html) function, and than replaced the directive with the resulting code. If you want, you can use the same principal and have some other attributes passed down to your custom datepicker.
5. At the end of the “compile” function, I return the linkFunc defined above. The linkFunc defines how i link this directive to scope. I use the linkFunc to add a popup opening function to the scope. Notice that i put a prefix on the functions name, the same way i do it in the html code. Why is that? No reason actually, it can also work without prefix. Actually there is one but i’ll explain later on.
6. The trickies part? The scope. It is defined scope:false by default, which means the directive uses the parent’s scope (which means all datepicker directives on the same controller (eg. form or page) share the same properties and methods). I won’t go deep into scope explanation, but what would happen when you would press any button on any picker, all popups would open.
How did i solve that problem? I defined scope:true. What does that do? It defines that this directive has it’s own child scope and it’s own method “open” attached to that scope. But since it’s a child scope, it also has access to all the parent scope’s properties which are not defined in the child, so i can easily access the model on the parrent scope. But what if I had a property “open” and i wanted to access that in directive? That’s why i have the prefix, i can now have a property “open” on the parent scope and still be able to access it. The only property on parent scope i can’t access is altidatePicker123open, but i doubt i’m going to use that:). There are other ways of dealing with this problem (like isolated scope) but I honestly couldn’t bother right now to see how that works. This was quick, dirty and it works:).
7. Finally, my usage of the directive went from:
<p class="input-group"> <input type="text" class="form-control" datepicker-popup="{{format}}" ng-model="dt" is-open="opened" min-date="minDate" max-date="'2015-06-22'" datepicker-options="dateOptions" date-disabled="disabled(date, mode)" ng-required="true" close-text="Close" /> <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>
and all that code in the controller (which would multiple with every datepicker used), to….
<input type="text" data-alti-datepicker ng-model="vm.template.testDate" />
With no extra methods on the controller whatsoever:). Now that is what i call a good encapsulation!
Mat Dec 05 , 2014 at 10:18 am /
This just saved my day!
Just yesterday I looked at the datepicker on the UI Bootstrap page and thought exactly the same thing you did: “Why the hell is the popup datepicker so cluttered and all over my controller? For me this should be a blackbox that I don’t have to care about too much like every other input field!”
Thanks to your example I could now solve this in a much nicer way without having to go all the way and importing a dozen other JS/CSS files just to get another datepicker implementation working.
Thanks again!
Reginald Sophomore Oct 21 , 2016 at 6:19 am /
my husband was looking for Cash Receipt several days ago and learned about an online platform with lots of sample forms . If people need to fill out Cash Receipt as well , here’s a
https://goo.gl/zaZRhK