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

Commit

Permalink
refactor(datepicker): implement one time bindings
Browse files Browse the repository at this point in the history
Reducing the number of watches created for the
datepicker by implementing one time binding
on the datepicker templates. Removing aria-disabled
attribute as the ngAria module will add that
to the button with ng-disabled.

Closes #3443
  • Loading branch information
RobJacobs committed May 13, 2015
1 parent 21b2002 commit 2a2e5de
Show file tree
Hide file tree
Showing 4 changed files with 45 additions and 49 deletions.
64 changes: 30 additions & 34 deletions src/datepicker/test/datepicker.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,10 @@ describe('datepicker directive', function () {
return element.find('thead').find('tr').eq(1);
}

function getLabels() {
function getLabels(dayMode) {
var els = getLabelsRow().find('th'),
labels = [];
for (var i = 1, n = els.length; i < n; i++) {
for (var i = dayMode ? 1 : 0, n = els.length; i < n; i++) {
labels.push( els.eq(i).text() );
}
return labels;
Expand All @@ -77,7 +77,7 @@ describe('datepicker directive', function () {
return weeks;
}

function getOptions( dayMode ) {
function getOptions(dayMode) {
var tr = element.find('tbody').find('tr');
var rows = [];

Expand All @@ -91,11 +91,11 @@ describe('datepicker directive', function () {
return rows;
}

function clickOption( index ) {
function clickOption(index) {
getAllOptionsEl().eq(index).click();
}

function getAllOptionsEl( dayMode ) {
function getAllOptionsEl(dayMode) {
return element.find('tbody').find('button');
}

Expand All @@ -108,7 +108,7 @@ describe('datepicker directive', function () {
}
}

function expectSelectedElement( index ) {
function expectSelectedElement(index) {
var buttons = getAllOptionsEl();
angular.forEach( buttons, function( button, idx ) {
expect(angular.element(button).hasClass('btn-info')).toBe( idx === index );
Expand Down Expand Up @@ -153,7 +153,7 @@ describe('datepicker directive', function () {

it('shows the label row & the correct day labels', function() {
expect(getLabelsRow().css('display')).not.toBe('none');
expect(getLabels()).toEqual(['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']);
expect(getLabels(true)).toEqual(['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']);
});

it('renders the calendar days correctly', function() {
Expand Down Expand Up @@ -211,7 +211,7 @@ describe('datepicker directive', function () {
clickPreviousButton();

expect(getTitle()).toBe('August 2010');
expect(getLabels()).toEqual(['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']);
expect(getLabels(true)).toEqual(['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']);
expect(getOptions(true)).toEqual([
['01', '02', '03', '04', '05', '06', '07'],
['08', '09', '10', '11', '12', '13', '14'],
Expand All @@ -236,7 +236,7 @@ describe('datepicker directive', function () {
clickNextButton();

expect(getTitle()).toBe('October 2010');
expect(getLabels()).toEqual(['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']);
expect(getLabels(true)).toEqual(['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']);
expect(getOptions(true)).toEqual([
['26', '27', '28', '29', '30', '01', '02'],
['03', '04', '05', '06', '07', '08', '09'],
Expand All @@ -261,7 +261,7 @@ describe('datepicker directive', function () {
clickOption( 33 );
expect($rootScope.date).toEqual(new Date('October 01, 2010 15:30:00'));
expect(getTitle()).toBe('October 2010');
expect(getLabels()).toEqual(['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']);
expect(getLabels(true)).toEqual(['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']);
expect(getOptions(true)).toEqual([
['26', '27', '28', '29', '30', '01', '02'],
['03', '04', '05', '06', '07', '08', '09'],
Expand Down Expand Up @@ -735,7 +735,7 @@ describe('datepicker directive', function () {
});

it('shows the day labels rotated', function() {
expect(getLabels()).toEqual(['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']);
expect(getLabels(true)).toEqual(['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']);
});

it('renders the calendar days correctly', function() {
Expand All @@ -755,20 +755,18 @@ describe('datepicker directive', function () {
});

describe('attribute `show-weeks`', function () {
var weekHeader, weekElement;
beforeEach(function() {
$rootScope.showWeeks = false;
element = $compile('<datepicker ng-model="date" show-weeks="showWeeks"></datepicker>')($rootScope);
$rootScope.$digest();

weekHeader = getLabelsRow().find('th').eq(0);
weekElement = element.find('tbody').find('tr').eq(1).find('td').eq(0);
});

it('hides week numbers based on variable', function() {
expect(weekHeader.text()).toEqual('');
expect(weekHeader).toBeHidden();
expect(weekElement).toBeHidden();
expect(getLabelsRow().find('th').length).toEqual(7);
var tr = element.find('tbody').find('tr');
for (var i = 0; i < 5; i++) {
expect(tr.eq(i).find('td').length).toEqual(7);
}
});
});

Expand Down Expand Up @@ -1035,7 +1033,7 @@ describe('datepicker directive', function () {
});

it('shows day labels', function() {
expect(getLabels()).toEqual(['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']);
expect(getLabels(true)).toEqual(['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']);
});

it('changes the day format', function() {
Expand Down Expand Up @@ -1101,7 +1099,7 @@ describe('datepicker directive', function () {

it('changes the `starting-day` & day headers & format', function() {
expect(getLabels()).toEqual(['Saturday', 'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday']);
expect(getOptions(true)).toEqual([
expect(getOptions(false)).toEqual([
['28', '29', '30', '31', '1', '2', '3'],
['4', '5', '6', '7', '8', '9', '10'],
['11', '12', '13', '14', '15', '16', '17'],
Expand All @@ -1112,10 +1110,10 @@ describe('datepicker directive', function () {
});

it('changes initial visibility for weeks', function() {
expect(getLabelsRow().find('th').eq(0)).toBeHidden();
expect(getLabelsRow().find('th').length).toEqual(7);
var tr = element.find('tbody').find('tr');
for (var i = 0; i < 5; i++) {
expect(tr.eq(i).find('td').eq(0)).toBeHidden();
expect(tr.eq(i).find('td').length).toEqual(7);
}
});

Expand Down Expand Up @@ -1212,7 +1210,7 @@ describe('datepicker directive', function () {

it('renders the calendar correctly', function() {
expect(getLabelsRow().css('display')).not.toBe('none');
expect(getLabels()).toEqual(['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']);
expect(getLabels(true)).toEqual(['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']);
expect(getOptions(true)).toEqual([
['29', '30', '31', '01', '02', '03', '04'],
['05', '06', '07', '08', '09', '10', '11'],
Expand Down Expand Up @@ -1495,23 +1493,21 @@ describe('datepicker directive', function () {
describe('attribute `datepickerOptions`', function () {

describe('show-weeks', function(){
var weekHeader, weekElement;
beforeEach(function() {
$rootScope.opts = {
'show-weeks': false
};
var wrapElement = $compile('<div><input ng-model="date" datepicker-popup datepicker-options="opts" is-open="true"></div>')($rootScope);
$rootScope.$digest();
assignElements(wrapElement);

weekHeader = getLabelsRow().find('th').eq(0);
weekElement = element.find('tbody').find('tr').eq(1).find('td').eq(0);
});

it('hides week numbers based on variable', function() {
expect(weekHeader.text()).toEqual('');
expect(weekHeader).toBeHidden();
expect(weekElement).toBeHidden();
expect(getLabelsRow().find('th').length).toEqual(7);
var tr = element.find('tbody').find('tr');
for (var i = 0; i < 5; i++) {
expect(tr.eq(i).find('td').length).toEqual(7);
}
});
});

Expand Down Expand Up @@ -1798,10 +1794,10 @@ describe('datepicker directive', function () {
$rootScope.$digest();
assignElements(wrapElement);

expect(getLabelsRow().find('th').eq(0)).toBeHidden();
expect(getLabelsRow().find('th').length).toEqual(7);
var tr = element.find('tbody').find('tr');
for (var i = 0; i < 5; i++) {
expect(tr.eq(i).find('td').eq(0)).toBeHidden();
expect(tr.eq(i).find('td').length).toEqual(7);
}
});

Expand Down Expand Up @@ -1946,10 +1942,10 @@ describe('datepicker directive', function () {
}));

it('changes initial visibility for weeks', function() {
expect(getLabelsRow().find('th').eq(0)).toBeHidden();
expect(getLabelsRow().find('th').length).toEqual(7);
var tr = element.find('tbody').find('tr');
for (var i = 0; i < 5; i++) {
expect(tr.eq(i).find('td').eq(0)).toBeHidden();
expect(tr.eq(i).find('td').length).toEqual(7);
}
});
});
Expand Down
14 changes: 7 additions & 7 deletions template/datepicker/day.html
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
<table role="grid" aria-labelledby="{{uniqueId}}-title" aria-activedescendant="{{activeDateId}}">
<table role="grid" aria-labelledby="{{::uniqueId}}-title" aria-activedescendant="{{activeDateId}}">
<thead>
<tr>
<th><button type="button" class="btn btn-default btn-sm pull-left" ng-click="move(-1)" tabindex="-1"><i class="glyphicon glyphicon-chevron-left"></i></button></th>
<th colspan="{{5 + showWeeks}}"><button id="{{uniqueId}}-title" role="heading" aria-live="assertive" aria-atomic="true" type="button" class="btn btn-default btn-sm" ng-click="toggleMode()" ng-disabled="datepickerMode === maxMode" tabindex="-1" style="width:100%;"><strong>{{title}}</strong></button></th>
<th colspan="{{::5 + showWeeks}}"><button id="{{::uniqueId}}-title" role="heading" aria-live="assertive" aria-atomic="true" type="button" class="btn btn-default btn-sm" ng-click="toggleMode()" ng-disabled="datepickerMode === maxMode" tabindex="-1" style="width:100%;"><strong>{{title}}</strong></button></th>
<th><button type="button" class="btn btn-default btn-sm pull-right" ng-click="move(1)" tabindex="-1"><i class="glyphicon glyphicon-chevron-right"></i></button></th>
</tr>
<tr>
<th ng-show="showWeeks" class="text-center"></th>
<th ng-repeat="label in labels track by $index" class="text-center"><small aria-label="{{label.full}}">{{label.abbr}}</small></th>
<th ng-if="showWeeks" class="text-center"></th>
<th ng-repeat="label in ::labels track by $index" class="text-center"><small aria-label="{{::label.full}}">{{::label.abbr}}</small></th>
</tr>
</thead>
<tbody>
<tr ng-repeat="row in rows track by $index">
<td ng-show="showWeeks" class="text-center h6"><em>{{ weekNumbers[$index] }}</em></td>
<td ng-repeat="dt in row track by dt.date" class="text-center" role="gridcell" id="{{dt.uid}}" aria-disabled="{{!!dt.disabled}}" 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)}" ng-click="select(dt.date)" ng-disabled="dt.disabled" tabindex="-1"><span ng-class="{'text-muted': dt.secondary, 'text-info': dt.current}">{{dt.label}}</span></button>
<td ng-if="showWeeks" class="text-center h6"><em>{{ weekNumbers[$index] }}</em></td>
<td ng-repeat="dt in row track by dt.date" class="text-center" role="gridcell" id="{{::dt.uid}}" ng-class="::dt.customClass">

This comment has been minimized.

Copy link
@phrough

phrough Jul 23, 2015

::dt.customClass seems like a breaking change to me.

<button type="button" style="width:100%;" class="btn btn-default btn-sm" ng-class="{'btn-info': dt.selected, active: isActive(dt)}" ng-click="select(dt.date)" ng-disabled="dt.disabled" tabindex="-1"><span ng-class="::{'text-muted': dt.secondary, 'text-info': dt.current}">{{::dt.label}}</span></button>
</td>
</tr>
</tbody>
Expand Down
8 changes: 4 additions & 4 deletions template/datepicker/month.html
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
<table role="grid" aria-labelledby="{{uniqueId}}-title" aria-activedescendant="{{activeDateId}}">
<table role="grid" aria-labelledby="{{::uniqueId}}-title" aria-activedescendant="{{activeDateId}}">
<thead>
<tr>
<th><button type="button" class="btn btn-default btn-sm pull-left" ng-click="move(-1)" tabindex="-1"><i class="glyphicon glyphicon-chevron-left"></i></button></th>
<th><button id="{{uniqueId}}-title" role="heading" aria-live="assertive" aria-atomic="true" type="button" class="btn btn-default btn-sm" ng-click="toggleMode()" ng-disabled="datepickerMode === maxMode" tabindex="-1" style="width:100%;"><strong>{{title}}</strong></button></th>
<th><button id="{{::uniqueId}}-title" role="heading" aria-live="assertive" aria-atomic="true" type="button" class="btn btn-default btn-sm" ng-click="toggleMode()" ng-disabled="datepickerMode === maxMode" tabindex="-1" style="width:100%;"><strong>{{title}}</strong></button></th>
<th><button type="button" class="btn btn-default btn-sm pull-right" ng-click="move(1)" tabindex="-1"><i class="glyphicon glyphicon-chevron-right"></i></button></th>
</tr>
</thead>
<tbody>
<tr ng-repeat="row in rows track by $index">
<td ng-repeat="dt in row track by dt.date" class="text-center" role="gridcell" id="{{dt.uid}}" aria-disabled="{{!!dt.disabled}}">
<button type="button" style="width:100%;" class="btn btn-default" ng-class="{'btn-info': dt.selected, active: isActive(dt)}" ng-click="select(dt.date)" ng-disabled="dt.disabled" tabindex="-1"><span ng-class="{'text-info': dt.current}">{{dt.label}}</span></button>
<td ng-repeat="dt in row track by dt.date" 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)}" ng-click="select(dt.date)" ng-disabled="dt.disabled" tabindex="-1"><span ng-class="::{'text-info': dt.current}">{{::dt.label}}</span></button>
</td>
</tr>
</tbody>
Expand Down
8 changes: 4 additions & 4 deletions template/datepicker/year.html
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
<table role="grid" aria-labelledby="{{uniqueId}}-title" aria-activedescendant="{{activeDateId}}">
<table role="grid" aria-labelledby="{{::uniqueId}}-title" aria-activedescendant="{{activeDateId}}">
<thead>
<tr>
<th><button type="button" class="btn btn-default btn-sm pull-left" ng-click="move(-1)" tabindex="-1"><i class="glyphicon glyphicon-chevron-left"></i></button></th>
<th colspan="3"><button id="{{uniqueId}}-title" role="heading" aria-live="assertive" aria-atomic="true" type="button" class="btn btn-default btn-sm" ng-click="toggleMode()" ng-disabled="datepickerMode === maxMode" tabindex="-1" style="width:100%;"><strong>{{title}}</strong></button></th>
<th colspan="3"><button id="{{::uniqueId}}-title" role="heading" aria-live="assertive" aria-atomic="true" type="button" class="btn btn-default btn-sm" ng-click="toggleMode()" ng-disabled="datepickerMode === maxMode" tabindex="-1" style="width:100%;"><strong>{{title}}</strong></button></th>
<th><button type="button" class="btn btn-default btn-sm pull-right" ng-click="move(1)" tabindex="-1"><i class="glyphicon glyphicon-chevron-right"></i></button></th>
</tr>
</thead>
<tbody>
<tr ng-repeat="row in rows track by $index">
<td ng-repeat="dt in row track by dt.date" class="text-center" role="gridcell" id="{{dt.uid}}" aria-disabled="{{!!dt.disabled}}">
<button type="button" style="width:100%;" class="btn btn-default" ng-class="{'btn-info': dt.selected, active: isActive(dt)}" ng-click="select(dt.date)" ng-disabled="dt.disabled" tabindex="-1"><span ng-class="{'text-info': dt.current}">{{dt.label}}</span></button>
<td ng-repeat="dt in row track by dt.date" 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)}" ng-click="select(dt.date)" ng-disabled="dt.disabled" tabindex="-1"><span ng-class="::{'text-info': dt.current}">{{::dt.label}}</span></button>
</td>
</tr>
</tbody>
Expand Down

5 comments on commit 2a2e5de

@kevinbuhmann
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have a date picker calendar that I use to allow users to select multiple dates using custom classes to style the selected dates. This change breaks that because the custom classes need to be updated as the page runs.

I have created a simple demo of this on Plunker using ui-bootstrap version 0.13.0. If I use version 0.13.1 or greater, the custom classes don't update. Is there anyway to make this kind of thing work in newer versions of ui-bootstrap?

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

@icfantv
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kevinphelps, take a look at #4692.

@kevinbuhmann
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I got this fixed using a custom template-url. Thanks!

@icfantv
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kevinphelps, good deal, thanks.

@zsitro
Copy link

@zsitro zsitro commented on 2a2e5de Jun 16, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just $scope.$watch for value changes and broadcast a calendar redraw with help of this guide: https://gist.github.com/cgmartin/3daa01f910601ced9cd3

Please sign in to comment.