1
votes

I have been searching for a suitable date picker for Angularjs and settled on using pickadate.js

http://amsul.ca/pickadate.js/

because of its beautiful mobile UI along with a custom directive to allow angular integration as shown in this blog post

http://www.codinginsight.com/angularjs-and-pickadate/

I wanted to extend the directive in the above blog post with an event to call a function in my controller when the value changes. The full code for the extended directive is shown below

angular.module('plunker').directive('pickADate', function () {
    return {
        restrict: "A",
        scope: {
            pickADate: '=',
            minDate: '=',
            maxDate: '=',
            change: '='
        },
        link: function (scope, element, attrs) {
            element.pickadate({
                onSet: function (e) {
                    if (scope.$$phase || scope.$root.$$phase) // we are coming from $watch or link setup
                        return;
                    var select = element.pickadate('picker').get('select'); // selected date
                    scope.$apply(function () {
                        if (e.hasOwnProperty('clear')) {
                            scope.pickADate = null;
                            return;
                        }
                        if (!scope.pickADate)
                            scope.pickADate = new Date(0);
                        scope.pickADate.setYear(select.obj.getFullYear());
                        // Interesting: getYear returns only since 1900. Use getFullYear instead.
                        // It took me half a day to figure that our. Ironically setYear()
                        // (not setFullYear, duh) accepts the actual year A.D.
                        // So as I got the $#%^ 114 and set it, guess what, I was transported to ancient Rome 114 A.D.
                        // That's it I'm done being a programmer, I'd rather go serve Emperor Trajan as a sex slave.
                        scope.pickADate.setMonth(select.obj.getMonth());
                        scope.pickADate.setDate(select.obj.getDate());
                    });
                },
                onClose: function () {
                    element.blur();
                }
            });
            function updateValue(newValue) {
                if (newValue) {
                    scope.pickADate = (newValue instanceof Date) ? newValue : new Date(newValue);
                    // needs to be in milliseconds
                    element.pickadate('picker').set('select', scope.pickADate.getTime());
                } else {
                    element.pickadate('picker').clear();
                    scope.pickADate = null;
                }
            }
            updateValue(scope.pickADate);
            element.pickadate('picker').set('min', scope.minDate ? scope.minDate : false);
            element.pickadate('picker').set('max', scope.maxDate ? scope.maxDate : false);
            scope.$watch('pickADate', function (newValue, oldValue) {
                if (newValue == oldValue)
                    return;
                updateValue(newValue);

                // call change function
                scope.change;
            }, true);
            scope.$watch('minDate', function (newValue, oldValue) {
                element.pickadate('picker').set('min', newValue ? newValue : false);
            }, true);
            scope.$watch('maxDate', function (newValue, oldValue) {
                element.pickadate('picker').set('max', newValue ? newValue : false);
            }, true);
        }
    };
});

I can now use this directive with change event as shown below in my HTML

<input type="text" pick-a-date="curDate" change="onChange()" />

It works but the OnChange event is fired multiple times for a single change. Any idea on how I can get it to fire only once? I have created a plunker to demonstrate the problem

http://plnkr.co/edit/3NcaAknd4GpelJxHWUyv

Click on the text field and select a date. You will see the counter variable is updated multiple times for a single date change.

2

2 Answers

1
votes

As @miqid suggested to bind a function between directive and a controller use '&' ('=' should be used only for binding scope variables between directive and controller.)

Also there is one more change which has to be done to your current code. The function scope.change in updateValue() is not getting executed as it has been referrenced as scope.change. This has to be changed to scope.change().

I have forked your plunker and modified it to fix the above changes.

Fixed working plunker : http://plnkr.co/edit/oEpOxE21dXre28FpKoMc?p=preview

The counter is getting updated only once for a change. Let me know if this helps.

1
votes

You've bound change inside your pick-a-date directive using '=' instead of '&'.

The latter isolate scope binding is recommended for expressions like callback functions.

Relevant advice from the AngularJS documentation on directives:

Best Practice: use &attr in the scope option when you want your directive to expose an API for binding to behaviors.