Hantsy
Hantsy

Reputation: 9261

How to wrap the datetimepicker js into AngularJS directive

I have spent sometime on researching the existing datetime directives of angularjs.

Both ngularUI and AngularStrap do not provide a datetimepicker as I needed. Of course, I know use a datepicker and timepicker together to archive the purpose.

I have searched the related topic from internet and stackoverflow. Found some interesting and helpful info.

http://dalelotts.github.io/angular-bootstrap-datetimepicker/, there is a datetimepicker, but I dislike the usage of this directive.

connecting datetimepicker to angularjs , this topic is very helpful, I tried to wrap my datetimepicker directive following the steps.

My work is based on https://github.com/Eonasdan/bootstrap-datetimepicker, a bootstrap 3 based datetimepicker, the UI is very nice.

app.directive('datetimepicker', function() {
    return {
        restrict: 'A',
        require: 'ngModel',
        link: function(scope, element, attrs, ngModelCtrl) {
            console.log('call datetimepicker link...');
            var picker = element.datetimepicker({
                dateFormat: 'dd/MM/yyyy hh:mm:ss'
            });

            //ngModelCtrl.$setViewValue(picker.getDate());

            //model->view
            ngModelCtrl.$render(function() {
                console.log('ngModelCtrl.$viewValue@'+ngModelCtrl.$viewValue);
                picker.setDate(ngModelCtrl.$viewValue || '');
            });

            //view->model
            picker.on('dp.change', function(e) {
                console.log('dp.change'+e.date);              
                scope.$apply(function(){
                    ngModelCtrl.$setViewValue(e.date);
                });
            });
        }
    };
});

And use it in my view.

<div class="col-md-6">
  <div class="input-group date" id="endTime" data-datetimepicker  ng-model="stat.endTime" data-date-format="MM/DD/YYYY hh:mm A/PM" >
    <input class="form-control" type="text" placeholder="End"/>
    <span class="input-group-addon"><span class="glyphicon glyphicon-calendar"></span>
    </span>
  </div>
</div>

There are some problems I found.

  1. If the date is set via json before rendered in view, the initial date did not display, I can not see any log of the execution of ngModel render method.
  2. When I picked a date, it got a string based datetime to the json data, not a long format. And in other related fragment in the view, the string based date can not parsed by angular date filter.
  3. When used it in modal dialog, it's value is not cleared when the modal window is popup in the next time.

Thanks in advance.

Upvotes: 5

Views: 18940

Answers (7)

mohghaderi
mohghaderi

Reputation: 2650

I ended up putting the input inside of a div and bind model to that. It also includes bootstrapvalidator (Sorry, I didn't have time to make the code perfect, but you should get the idea)

Sample html code:

<div datetimepicker="{pickTime: false}" data-ng-model="model.Birthday"><input type="text" name="Birthday" class="form-control" data-date-format="YYYY/MM/DD" data-mask="0000/00/00" data-bv-date="true" data-bv-date-format="YYYY/MM/DD" /></div>

And javascript code:

app.directive('datetimepicker', function ($timeout) {
return {
    // Restrict it to be an attribute in this case
    restrict: 'AE',
    // optionally hook-in to ngModel's API 
    require: '?ngModel',
    // responsible for registering DOM listeners as well as updating the DOM
    link: function ($scope, element, $attrs, ngModel) {
        var $element;
        $timeout(function () {

            $element = $(element).find("input").datetimepicker($scope.$eval($attrs.datetimepicker));

            var DateTimePicker = $element.data("DateTimePicker");
            DateTimePicker.setValueAngular = function (newValue) {
                this.angularSetValue = true; // a lock object to prevent calling change trigger of input to fix the re-cursive call of changing values
                this.setDate(newValue);
                this.angularSetValue = false;
            }

            if (!ngModel) { return; }//below this we interact with ngModel's controller

            $scope.$watch($attrs['ngModel'], function (newValue) {
                if (newValue)
                    if (newValue != "Invalid date")
                    {
                        DateTimePicker.setValueAngular(newValue);
                    }
            });

            ngModel.$formatters.push(function (value) {
                // formatting the value to be shown to the user
                var format = DateTimePicker.format;
                var date = moment(value);
                if (date.isValid()) {
                    return date.format(format);
                }
                return '';
            });

            ngModel.$parsers.push(function toModel(input) {
                // format user input to be used in code (converting to unix epoch or ...)
                var modifiedInput = moment(input).format();
                return modifiedInput;
            });

            //update ngModel when UI changes
            $element.on('dp.change', function (e) {
                if (DateTimePicker.angularSetValue === true)
                    return;

                var newValue = $element[0].value;
                if (newValue !== ngModel.$viewValue)
                    $scope.$apply(function () {
                        ngModel.$setViewValue(newValue);
                    });
                //bootstrapvalidator support
                if ($element.attr('data-bv-field') !== undefined) // if the field had validation
                    $element.closest("form").bootstrapValidator('revalidateField', $element);

            });
        });
    }
};
}); // directive end

Upvotes: 0

Atais
Atais

Reputation: 11275

I really struggled long with Eonasdan datetime picker. Most of the solutions published on the web work ok or not-so ok.

In the end I merged some of the solutions I have found online. I wrapped it in a working plunker: http://plnkr.co/n8L8UZ

The directive works using ng-model in moment format, what is more it allows two functions to be passed: onDateChangeFunction and onDateClickFunction which are called respectively.

Happy using!


The directive source code:

angular
    .module('plunker')
    .directive('datetimepicker', [
      '$timeout',
      function($timeout) {
        return {
          require: '?ngModel',
          restrict: 'EA',
          scope: {
            datetimepickerOptions: '@',
            onDateChangeFunction: '&',
            onDateClickFunction: '&'
          },
          link: function($scope, $element, $attrs, controller) {
            $element.on('dp.change', function() {
              $timeout(function() {
                var dtp = $element.data('DateTimePicker');
                controller.$setViewValue(dtp.date());
                $scope.onDateChangeFunction();
              });
            });

            $element.on('click', function() {
              $scope.onDateClickFunction();
            });

            controller.$render = function() {
              if (!!controller && !!controller.$viewValue) {
                var result = controller.$viewValue;
                $element.data('DateTimePicker').date(result);
              }
            };

            $element.datetimepicker($scope.$eval($attrs.datetimepickerOptions));
          }
        };
      }
    ]);

Upvotes: 1

Muhammad Mehdi Raza
Muhammad Mehdi Raza

Reputation: 591

I am also using DateTimePicker by Angular Directive and it is working fine here. I tried it in this way:

element.datetimepicker({
                        timepicker:false,
                        format:'Y-m-d', 
                        formatDate:'Y-m-d',
                        closeOnDateSelect: true,
                        onChangeDateTime: function(dp, $input){
                            var val = $input['context']['value'];  
                            ctrl.$setViewValue(val); 
                            ctrl.$render();  
                            scope.$apply(); 
                        } 
    //                  minDate:'-1970/01/02', // yesterday is minimum date
                        //maxDate:'+1970/01/02' // and tommorow is maximum date calendar
                    });

Upvotes: 0

Siggen
Siggen

Reputation: 2177

To complete cdmckay's solution so the datetime picker widget is correctly initialized with the ng-model's value, I've added a listener on dp.show event. So, my solution is :

'use strict';

angular.module('frontStreetApp.directives', [])
  .directive('psDatetimePicker', function (moment) {
      var format = 'MM/DD/YYYY hh:mm A';

      return {
          restrict: 'A',
          require: 'ngModel',
          link: function (scope, element, attributes, ctrl) {
              element.datetimepicker({
                  format: format
              });
              var picker = element.data("DateTimePicker");

              ctrl.$formatters.push(function (value) {
                  var date = moment(value);
                  if (date.isValid()) {
                      return date.format(format);
                  }
                  return '';
              });

              /**
              * Update datetime picker's value from ng-model when opening the datetime picker's dropdown
              */
              element.on('dp.show', function() {
                  picker.setDate(ctrl.$viewValue);
              });

              /**
              * Update ng-model when  datetime picker's value changes
              */
              element.on('change', function (event) {
                  scope.$apply(function () {
                      var date = picker.getDate();
                      ctrl.$setViewValue(date);
                  });
              });
          }
      };
  });

Upvotes: 0

cdmckay
cdmckay

Reputation: 32240

I had the same issue. Here's what I ended up doing that worked well for me:

'use strict';

angular.module('frontStreetApp.directives')
    .directive('psDatetimePicker', function (moment) {
        var format = 'MM/DD/YYYY hh:mm A';

        return {
            restrict: 'A',
            require: 'ngModel',
            link: function (scope, element, attributes, ctrl) {
                element.datetimepicker({
                    format: format
                });
                var picker = element.data("DateTimePicker");

                ctrl.$formatters.push(function (value) {
                    var date = moment(value);
                    if (date.isValid()) {
                        return date.format(format);
                    }
                    return '';
                });

                element.on('change', function (event) {
                    scope.$apply(function() {
                        var date = picker.getDate();
                        ctrl.$setViewValue(date.valueOf());
                    });
                });
            }
        };
    });

Here's the HTML:

<!-- The dueDate field is a UNIX offset of the date -->
<input type="text"
       ng-model="dueDate"
       ps-datetime-picker
       class="form-control">

You can check out the gists and a bit more information in my blog post.

Upvotes: 5

Richard
Richard

Reputation: 31

A possible solution to problem 1:

You will need to set the view value to the date that is in the model right from the beginning, by doing something like this:

if (ngModel.$viewValue) {

  picker.data("DateTimePicker").setDate(new Date(ngModel.$modelValue));'

  picker.on('change', function(e) {

    ....

  });

}

Upvotes: 0

user2113422
user2113422

Reputation:

Can help you with the first point; add $watch to your element inside the link function and set

 value="{{$scope.variableWithTheInitialDate}}" 
in the text field; that way when loaded, the datetimepicker will be set already.
For the second point I'd use moment.js but haven't tried your example for telling you how :-P Good luck!

Upvotes: 0

Related Questions