AngularJS - jQuery UI - binding issue

I am currently porting a large application over to a HTML5 based web app - I have started building the app in AngularJS and enjoying the power of the AngularJS framework - I have one issue standing in my way currently:

I have a directive that gives me a jQuery Datepicker however the binding to the model does not seem to be working.

http://jsfiddle.net/9BRNf/

I am probably misunderstanding the way directives work and would like to see if I can patch this part of my understanding of the framework. I have gone through loads of examples (including the angularui project on github but still not making sense of why the binding is not happening)

any assistance will be greatly appreciated.

For those Googling this issue (as I was), a simpler way of tying in the jQuery UI datepicker with Angular is to do this...

$.datepicker.setDefaults({
    // When a date is selected from the picker
    onSelect: function(newValue) {
        if (window.angular && angular.element)
            // Update the angular model
            angular.element(this).controller("ngModel").$setViewValue(newValue);
    }
});

Just place it prior to your .datepicker() initialisation code.

(Taken from another answer I posted here: http://stackoverflow.com/a/17206242/195835)

First off, it's great that you are using angularjs, its a sweet framework. An offshoot project was started awhile back to deal with things like wrapping jquery-ui and creating ui modules.

Below is link to Peter Bacon Darwin's implementation.

https://github.com/angular-ui/angular-ui/tree/master/modules/directives/date

--dan

The angular-ui datepicker wasn't working with Angular 1.0.0, so I rewrote it. My fork gives you the ability to set how the date is formatted inside the input and how it gets saved back to the model.

Code: https://gist.github.com/2967979 jsFiddle: http://jsfiddle.net/m8L8Y/8/ (It's missing jquery-ui styles but works just the same)


// Code inspired by angular-ui https://github.com/angular-ui/angular-ui/blob/master/modules/directives/date/src/date.js
/*
Features:
* via the ui-date attribute:
    * Ability to say how model is parsed into a date object
    * Ability to say how input's value is parsed into a date object
    * Ability to say how a date object is saved to the model
    * Ability to say how a date object is displayed in the input
* via the ui-date-picker attribute
    * Ability to directly configure the jQuery-ui datepicker
*/


angular.module('ui.directives', [])
    .directive('uiDate', function () {
        return {
            require: '?ngModel',
            //scope: {},
            link: function ($scope, element, attrs, ngModel) {
                // Date Handling Functions
                var dateHandler = $.extend({ model: {}, view: {} }, $scope.$eval(attrs.uiDate));

                // This will attempt to use preferredParser to parse a date.
                function defaultDateParse(date, preferredParser) {
                    if (!preferredParser)
                        return new Date(date);
                    return preferredParser(date);
                }

                // This will attempt to use preferredFormatter to format a date, otherwise use 'mm/dd/yy'.
                function defaultDateFormatter(date, preferredFormatter) {
                    if (!preferredFormatter)
                        preferredFormatter = "mm/dd/yy";

                    if (typeof preferredFormatter == 'string')
                        return $.datepicker.formatDate(preferredFormatter, date);
                    else
                        return preferredFormatter(date);
                }

                // Functions for Parsing & Formatting on the Model & View
                function parseDateFromModel(date) {
                    return defaultDateParse(date, dateHandler.model.parse)
                }
                function parseDateFromView(date) {
                    return defaultDateParse(date, dateHandler.view.parse)
                }
                function formatDateForModel(date) {
                    return defaultDateFormatter(date, dateHandler.model.format)
                }
                function formatDateForView(date) {
                    return defaultDateFormatter(date, dateHandler.view.format)
                }

                var defaultDateViewFormat = (
                    typeof dateHandler.view.format == 'string'
                    ? dateHandler.view.format
                    : 'mm/dd/yy'
                )

                // Initialize the jQuery-ui datePicker
                var datePickerSettings = $.extend({ dateFormat: defaultDateViewFormat }, $scope.$eval(attrs.uiDatePicker))
                var oldOnSelect = datePickerSettings.onSelect;
                datePickerSettings.onSelect = function (dateVal) {
                    $scope.$apply(function () {
                        element.focus().val(dateVal);
                        updateModel();
                    })
                    if (oldOnSelect)
                        oldOnSelect.apply(this, arguments)
                }

                element.datepicker(datePickerSettings);

                if (ngModel) {
                    // Specify how UI should be updated
                    ngModel.$render = function () {
                        element.val(ngModel.$viewValue || '');
                    };

                    // Listen for change events to enable binding
                    element.bind('blur keyup change', function () {
                        $scope.$apply(updateModel);
                    });

                    // Write data to the model
                    function updateModel() {
                        ngModel.$setViewValue(element.val());
                    }

                    // Convert the model into a string value
                    ngModel.$formatters.push(function (v) {
                        if (v != "" && v != null)
                            return formatDateForView(parseDateFromModel(v));
                        return null;
                    });

                    // Convert the string value into the model
                    ngModel.$parsers.push(function (v) {
                        if (v != "" && v != null)
                            return formatDateForModel(parseDateFromView(v))
                        return null;
                    });
                }

            }
        };
    })

Similar to praveepd (using their's as a base), but this will include deep model selection.

http://jsfiddle.net/c8PMa/

var myApp = angular.module('myApp', ['myApp.directives']);

function MainCtrl($scope) {

    $scope.deepValue = {
        fromDate: null,
        toDate: null
    }

}

angular.module('myApp.directives', [])
  .directive('myDatepicker', function() {
    return function(scope, element, attrs) {     

       element.datepicker({
         changeYear : true,
         changeMonth : true,
         appendText : '(yyyy-mm-dd)',
         dateFormat : 'yy-mm-dd', 
                onSelect: function(dateText) {                    
                    var mdlAttr = $(this).attr('ng-model').split(".");

                    if (mdlAttr.length > 1) {

                        var objAttr = mdlAttr[mdlAttr.length-1];
                        var s = scope[mdlAttr[0]];

                        for (var i=0; i < mdlAttr.length-2; i++) {
                            s = s[mdlAttr[i]];
                        }

                        s[objAttr] = dateText;
                    } else {
                        scope[mdlAttr[0]] = dateText;
                    }

                    scope.$apply();
                }                
        });

    }
});​

I had just trimd the code,Have a look in to this. http://jsfiddle.net/YU5mV/

Old question, but this was the first hit for me in google search for this. Anyways, I used dual datepickers working together using jquery and angular directives, so I thought I'd share to help anyone else trying to do this.

Here's the plunker for it:

http://plnkr.co/edit/veEmtCM3ZnQAhGTn5EGy?p=preview

Basically it initializes the form using json. The datepickers have their own conditions like mindate's, etc. The first select box if true = disables sundays on the calendars, else enables them.

The viewmodel get's updates when 'done' is clicked. Here's a bit of the code for one of the datepickers:

Html:

<input id="StartDate" data-ng-model="viewModel.startdate" date-from />

Directive:

app.directive('dateFrom', function() {
    return function (scope, element, attrs) {
        var doDate = $('#EndDate');
        element.datepicker({
          dateFormat: 'dd-M-yy', showOtherMonths: true, 
          selectOtherMonths: true, minDate: '0',
          beforeShowDay: function (date) {
              var day = date.getDay();
              console.log(scope.nosunday);
              if (scope.nosunday === 'true') return [(day !== 0), '']; // disable sundays
              else return [true, ''];
          },
          onSelect: function (selectedDate) {
              var toDate = new Date(element.datepicker("getDate"));
              toDate.setDate(toDate.getDate() + 1);
              doDate.datepicker('option', 'minDate', toDate);
              scope.viewModel.startdate = selectedDate;
              scope.viewModel.enddate = doDate.val();
          }
        });
    }
})

Feel free to optimize it further. Post a comment with a forked plunk if you do :)

http://jsfiddle.net/9BRNf/74/ here is the solution :)

code:

     var myApp = angular.module('myApp', ['myApp.directives']);


    function MainCtrl() {

    }

    angular.module('myApp.directives', [])
      .directive('myDatepicker', function() {
          return {
             require: '?ngModel',
                link: function (scope, element, attrs, ngModelCtrl) {
          element.datepicker({
             changeYear : true,
             changeMonth : true,
             appendText : '(yyyy-mm-dd)',
             dateFormat : 'yy-mm-dd',
              onSelect: function(date) {
                  ngModelCtrl.$setViewValue(date);
                            scope.$apply();
              }
          });
        }
      }

  });