Skip to content
This repository has been archived by the owner on May 29, 2019. It is now read-only.

Commit

Permalink
fix(datepicker): remove use of ng-class to reduce watchers
Browse files Browse the repository at this point in the history
Now use at most one watcher for determining which date should have what
class. ng-class adds a watcher per element containing date, while this
uses one watcher per class for all elements.
  • Loading branch information
chrisirhc committed Jun 8, 2015
1 parent 2113aec commit b23213d
Show file tree
Hide file tree
Showing 4 changed files with 124 additions and 13 deletions.
122 changes: 112 additions & 10 deletions src/datepicker/datepicker.js
Expand Up @@ -59,14 +59,6 @@ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootst
this.activeDate = new Date();
}

$scope.isActive = function(dateObject) {
if (self.compare(dateObject.date, self.activeDate) === 0) {
$scope.activeDateId = dateObject.uid;
return true;
}
return false;
};

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

Expand All @@ -92,7 +84,11 @@ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootst

this.refreshView = function() {
if ( this.element ) {
$scope.selectedDt = null;
this._refreshView();
if ($scope.activeDt) {
$scope.activeDateId = $scope.activeDt.uid;
}

var date = ngModelCtrl.$viewValue ? new Date(ngModelCtrl.$viewValue) : null;
ngModelCtrl.$setValidity('date-disabled', !date || (this.element && !this.isDisabled(date)));
Expand All @@ -101,14 +97,21 @@ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootst

this.createDateObject = function(date, format) {
var model = ngModelCtrl.$viewValue ? new Date(ngModelCtrl.$viewValue) : null;
return {
var dt = {
date: date,
label: dateFilter(date, format),
selected: model && this.compare(date, model) === 0,
disabled: this.isDisabled(date) || false,
current: this.compare(date, new Date()) === 0,
customClass: this.customClass(date) || null
};

if (model && this.compare(date, model) === 0) {
$scope.selectedDt = dt;
}
if (self.activeDate && this.compare(dt.date, self.activeDate) === 0) {
$scope.activeDt = dt;
}
return dt;
};

this.isDisabled = function( date ) {
Expand Down Expand Up @@ -728,6 +731,105 @@ function ($compile, $parse, $document, $position, dateFilter, dateParser, datepi
};
}])

// Avoiding use of ng-class as it creates a lot of watchers when a class is to be applied to
// at most one element.
.directive('datepickerIsClass', [
'$animate',
function ($animate) {
// 11111111 22222222
var ON_REGEXP = /^\s*([\s\S]+?)\s+on\s+([\s\S]+?)\s*$/;
// 11111111 22222222
var IS_REGEXP = /^\s*([\s\S]+?)\s+for\s+([\s\S]+?)\s*$/;

var dataPerTracked = {};

return {
restrict: 'A',
compile: function (tElement, tAttrs) {
var linkedScopes = [];
var instances = [];
var expToData = {};
var lastActivated = null;
var onExpMatches = tAttrs.datepickerIsClass.match(ON_REGEXP);
var onExp = onExpMatches[2];
var expsStr = onExpMatches[1];
var exps = expsStr.split(',');

return linkFn;

function linkFn(scope, element, attrs) {
linkedScopes.push(scope);
instances.push({
scope: scope,
element: element
});

exps.forEach(function (exp, k) {
addForExp(exp, scope);
});

scope.$on('$destroy', removeScope);
}

function addForExp(exp, scope) {
var matches = exp.match(IS_REGEXP);
var clazz = scope.$eval(matches[1]);
var compareWithExp = matches[2];
var data = expToData[exp];
if (!data) {
var watchFn = function (compareWithVal) {
var newActivated = null;
instances.some(function (instance) {
var thisVal = instance.scope.$eval(onExp);
if (thisVal === compareWithVal) {
newActivated = instance;
return true;
}
});
if (data.lastActivated !== newActivated) {
if (data.lastActivated) {
$animate.removeClass(data.lastActivated.element, clazz);
}
if (newActivated) {
newActivated.element.attr('has-been-activated', 'true');
$animate.addClass(newActivated.element, clazz);
}
data.lastActivated = newActivated;
}
};
expToData[exp] = data = {
lastActivated: null,
scope: scope,
watchFn: watchFn,
compareWithExp: compareWithExp,
watcher: scope.$watch(compareWithExp, watchFn)
};
}
data.watchFn(scope.$eval(compareWithExp));
}

function removeScope(e) {
var removedScope = e.targetScope;
var index = linkedScopes.indexOf(removedScope);
linkedScopes.splice(index, 1);
instances.splice(index, 1);
if (linkedScopes.length) {
var newWatchScope = linkedScopes[0];
angular.forEach(expToData, function (data) {
if (data.scope === removedScope) {
data.watcher = newWatchScope.$watch(data.compareWithExp, data.watchFn);
data.scope = newWatchScope;
}
});
}
else {
expToData = {};
}
}
}
};
}])

.directive('datepickerPopupWrap', function() {
return {
restrict:'EA',
Expand Down
5 changes: 4 additions & 1 deletion template/datepicker/day.html
Expand Up @@ -17,7 +17,10 @@
id="{{::dt.uid}}"
ng-class="::dt.customClass">
<button type="button" style="width:100%;" class="btn btn-default btn-sm"
ng-class="{'btn-info': dt.selected, active: isActive(dt)}"
datepicker-is-class="
'btn-info' for selectedDt,
'active' for activeDt
on dt"
ng-click="select(dt.date)"
ng-disabled="::dt.disabled"
tabindex="-1"><span
Expand Down
5 changes: 4 additions & 1 deletion template/datepicker/month.html
Expand Up @@ -10,7 +10,10 @@
<tr ng-repeat="row in rows track by $index">
<td ng-repeat="dt in row" class="text-center" role="gridcell" id="{{::dt.uid}}">
<button type="button" style="width:100%;" class="btn btn-default"
ng-class="{'btn-info': dt.selected, active: isActive(dt)}"
datepicker-is-class="
'btn-info' for selectedDt,
'active' for activeDt
on dt"
ng-click="select(dt.date)"
ng-disabled="::dt.disabled"
tabindex="-1"><span
Expand Down
5 changes: 4 additions & 1 deletion template/datepicker/year.html
Expand Up @@ -10,7 +10,10 @@
<tr ng-repeat="row in rows track by $index">
<td ng-repeat="dt in row" class="text-center" role="gridcell" id="{{::dt.uid}}">
<button type="button" style="width:100%;" class="btn btn-default"
ng-class="{'btn-info': dt.selected, active: isActive(dt)}"
datepicker-is-class="
'btn-info' for selectedDt,
'active' for activeDt
on dt"
ng-click="select(dt.date)"
ng-disabled="::dt.disabled"
tabindex="-1"><span
Expand Down

0 comments on commit b23213d

Please sign in to comment.