angular-ui / bootstrap

PLEASE READ THE PROJECT STATUS BELOW. Native AngularJS (Angular) directives for Bootstrap. Smaller footprint (20kB gzipped), no 3rd party JS dependencies (jQuery, bootstrap JS) required. Please read the README.md file before submitting an issue!
http://angular-ui.github.io/bootstrap/
MIT License
14.3k stars 6.74k forks source link

datepicker - initDate getting updated when navigating control #6636

Open arcdev opened 6 years ago

arcdev commented 6 years ago

The issues forum is NOT for support requests. It is for bugs and feature requests only. Please read https://github.com/angular-ui/bootstrap/blob/master/CONTRIBUTING.md and search existing issues (both open and closed) prior to opening any new issue and ensure you follow the instructions therein.

Bug description: As the user navigates the datepicker control, the initDate is updated. This causes issues if you use the the current date of one datepicker as the initDate of another.

Also, note how the Start Date control itself doesn't change, but the model value does.

Expected: initDate is essentially readonly.

Link to minimally-working plunker that reproduces the issue:

https://plnkr.co/edit/tvelT5r6ho59zcPzPvIH?p=preview

Steps to reproduce the issue:

As noted in the Plunker, simply navigate forward or backward by month in the End Date datepicker and note how the Start Date value changes.

Version of Angular, UIBS, and Bootstrap

Angular: 1.6.1

UIBS: 2.5.0

Bootstrap: 3.3.7

arcdev commented 6 years ago

It seems the root cause is that activeDate is set to initDate and since this is a reference type object (Date), any updates to activeDate also update initDate. And if initDate is the scope/model value of another control, then that other control's scope/model value is also updated.

Maybe because it's a reference type the other control doesn't get a watch fired?

arcdev commented 6 years ago

I might recommend changing the init method to this:

  this.init = function(ngModelCtrl_) {
    ngModelCtrl = ngModelCtrl_;
    ngModelOptions = extractOptions(ngModelCtrl);

    if ($scope.datepickerOptions.initDate) {
      var newInitDate = dateParser.fromTimezone($scope.datepickerOptions.initDate, ngModelOptions.getOption('timezone')) || new Date();
      self.activeDate = angular.isDate(newInitDate) ? new Date(newInitDate.getTime()) : newInitDate;
      $scope.$watch('datepickerOptions.initDate', function(initDate) {
        if (initDate && (ngModelCtrl.$isEmpty(ngModelCtrl.$modelValue) || ngModelCtrl.$invalid)) {
          var updatedInitDate = dateParser.fromTimezone(initDate, ngModelOptions.getOption('timezone'));
          self.activeDate = angular.isDate(updatedInitDate) ? new Date(updatedInitDate.getTime()) : updatedInitDate;
          self.refreshView();
        }
      });
    } else {
      self.activeDate = new Date();
    }

    var date = ngModelCtrl.$modelValue ? new Date(ngModelCtrl.$modelValue) : new Date();
    this.activeDate = !isNaN(date) ?
      dateParser.fromTimezone(date, ngModelOptions.getOption('timezone')) :
      dateParser.fromTimezone(new Date(), ngModelOptions.getOption('timezone'));

    ngModelCtrl.$render = function() {
      self.render();
    };
  };

That way activeDate is always a copy rather than a reference.

arcdev commented 6 years ago

check out pull request https://github.com/angular-ui/bootstrap/pull/6638