Skip to content

Commit

Permalink
Merge pull request #31 from brenovieira/master
Browse files Browse the repository at this point in the history
Fixing issue when more than one datepicker is visible
  • Loading branch information
spongessuck committed Oct 16, 2015
2 parents d378da4 + 14c160c commit ef86dc0
Show file tree
Hide file tree
Showing 5 changed files with 169 additions and 86 deletions.
4 changes: 0 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,6 @@ NOTE: Selected dates are stored as an array of time values, not date objects. An

selectedDates.push(new Date().setHours(0, 0, 0, 0));

##Issues

There is a known issue when using this directive while more than one datepicker is visible. Changing selected dates on a datepicker will *look* like it's changing dates on all others that are visible, although the models aren't actually affected. This is due to the datepicker directive's controller scope being altered in order to show the selected dates in their 'active' state, which (it seems) is shared between all instances.

##Date Ranges

You can change the selection mode from individual dates to a date range by setting the select-range attribute to a truthy value or expression (that isn't a string equal to "false"):
Expand Down
4 changes: 2 additions & 2 deletions dist/gm.datepickerMultiSelect.min.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

75 changes: 75 additions & 0 deletions examples/2-datepickers.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
<!DOCTYPE html>
<html lang="en" xmlns:ng="http://angularjs.org" id="ng-app" ng-app="app">
<head>
<meta charset="utf-8">
<title>gm.datepickerMultiSelect Example</title>

<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.7/angular.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular-ui-bootstrap/0.14.2/ui-bootstrap-tpls.min.js"></script>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" />

<!-- App files -->
<script src="../dist/gm.datepickerMultiSelect.min.js"></script>
<script src="../modules/app.js"></script>
<style>
body { padding:10px; }
td { vertical-align: top }
</style>
</head>

<body ng-controller='AppCtrl as app'>
<div class='panel'><h3>Selection Type:</h3>
<div class='btn-group'>
<button class='btn btn-primary' ng-model='app.type' uib-btn-radio='"individual"'>Individual</button>
<button class='btn btn-primary' ng-model='app.type' uib-btn-radio='"range"'>Range</button>
</div>
</div>
<table>
<tr>
<td>
<uib-datepicker ng-model='app.activeDate' multi-select='app.selectedDates' select-range='{{app.type=="range"}}'></uib-datepicker>
</td>
<td style='width:50px'>
</td>
<td>
<div>
Selected Dates:
<div class='well well-sm'>
<div ng-repeat='d in app.selectedDates'>
{{d | date : 'fullDate'}}
<button class='btn btn-xs btn-warning' style='margin:5px' ng-click='app.removeFromSelected(d)'>Remove</button>
</div>
</div>
</div>
</td>
</tr>
</table>
<hr />
<div class='panel'><h3>Selection Type 1:</h3>
<div class='btn-group'>
<button class='btn btn-primary' ng-model='app.type1' uib-btn-radio='"individual"'>Individual</button>
<button class='btn btn-primary' ng-model='app.type1' uib-btn-radio='"range"'>Range</button>
</div>
</div>
<table>
<tr>
<td>
<uib-datepicker ng-model='app.activeDate1' multi-select='app.selectedDates1' select-range='{{app.type1=="range"}}'></uib-datepicker>
</td>
<td style='width:50px'>
</td>
<td>
<div>
Selected Dates 1:
<div class='well well-sm'>
<div ng-repeat='d in app.selectedDates1'>
{{d | date : 'fullDate'}}
<button class='btn btn-xs btn-warning' style='margin:5px' ng-click='app.removeFromSelected1(d)'>Remove</button>
</div>
</div>
</div>
</td>
</tr>
</table>
</body>
</html>
8 changes: 8 additions & 0 deletions modules/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,12 @@ angular.module('app', ['gm.datepickerMultiSelect'])
this.removeFromSelected = function(dt) {
this.selectedDates.splice(this.selectedDates.indexOf(dt), 1);
}

this.activeDate1;
this.selectedDates1 = [new Date().setHours(24, 0, 0, 0)];
this.type1 = 'individual';

this.removeFromSelected1 = function(dt) {
this.selectedDates1.splice(this.selectedDates1.indexOf(dt), 1);
}
});
164 changes: 84 additions & 80 deletions src/gm.datepickerMultiSelect.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,29 +22,90 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/

(function() {
(function () {
'use strict';

angular.module('gm.datepickerMultiSelect', ['ui.bootstrap'])
.config(['$provide', '$injector', function($provide, $injector) {
var delegate = function($delegate) {
.config(['$provide', '$injector', function ($provide, $injector) {

// extending datepicker (access to attributes and app scope through $parent)
var datepickerDelegate = function ($delegate) {
var directive = $delegate[0];

/* Override compile */
// Override compile
var link = directive.link;

directive.compile = function() {
return function(scope, element, attrs, ctrls) {
directive.compile = function () {
return function (scope, element, attrs, ctrls) {
link.apply(this, arguments);

var selectedDates = [];
scope.selectedDates = [];
scope.selectRange;

scope.$parent.$watchCollection(attrs.multiSelect, function (newVal) {
scope.selectedDates = newVal || [];
});

attrs.$observe('selectRange', function (newVal) {
scope.selectRange = !!newVal && newVal !== "false";
});

/* Called when multiSelect model is updated */
scope.$on('update', function(event, newDates) {
selectedDates = newDates;
update();
scope.$parent.$watch(attrs.ngModel, function (newVal, oldVal) {
if (!newVal) return;

var dateVal = newVal.getTime(),
selectedDates = scope.selectedDates;

if (scope.selectRange) {
// reset range
if (!selectedDates.length || selectedDates.length > 1 || selectedDates[0] == dateVal)
return selectedDates.splice(0, selectedDates.length, dateVal);

selectedDates.push(dateVal);

var tempVal = Math.min.apply(null, selectedDates);
var maxVal = Math.max.apply(null, selectedDates);

// Start on the next day to prevent duplicating the first date
tempVal = new Date(tempVal).setHours(24);
while (tempVal < maxVal) {
selectedDates.push(tempVal);

// Set a day ahead after pushing to prevent duplicating last date
tempVal = new Date(tempVal).setHours(24);
}
} else {
if (selectedDates.indexOf(dateVal) < 0) {
selectedDates.push(dateVal);
} else {
selectedDates.splice(selectedDates.indexOf(dateVal), 1);
}
}
});
}
}

return $delegate;
}

if ($injector.has('datepickerDirective'))
$provide.decorator('datepickerDirective', ['$delegate', datepickerDelegate]);

/* Get dates pushed into multiSelect array before Datepicker is ready */
scope.$emit('requestSelectedDates');
if ($injector.has('uibDatepickerDirective'))
$provide.decorator('uibDatepickerDirective', ['$delegate', datepickerDelegate]);

// extending daypicker (access to day and datepicker scope through $parent)
var daypickerDelegate = function ($delegate) {
var directive = $delegate[0];

// Override compile
var link = directive.link;

directive.compile = function () {
return function (scope, element, attrs, ctrls) {
link.apply(this, arguments);

scope.$parent.$watchCollection('selectedDates', update);

/*
Fires when date is selected or when month is changed.
Expand All @@ -53,16 +114,15 @@ SOFTWARE.
see more on https://github.com/angular-ui/bootstrap/commit/44354f67e55c571df28b09e26a314a845a3b7397?diff=split#diff-6240fc17e068eaeef7095937a1d63eaeL251
and https://github.com/angular-ui/bootstrap/commit/44354f67e55c571df28b09e26a314a845a3b7397?diff=split#diff-6240fc17e068eaeef7095937a1d63eaeR462
*/
var ctrl = angular.isArray(ctrls) ? ctrls[0] : ctrls;
scope.$watch(function () {
return angular.isArray(ctrls)
? ctrls[0].activeDate.getTime()
: ctrls.activeDate.getTime();
return ctrl.activeDate.getTime();
}, update);

function update() {
angular.forEach(scope.rows, function(row) {
angular.forEach(row, function(day) {
day.selected = selectedDates.indexOf(day.date.setHours(0, 0, 0, 0)) > -1
angular.forEach(scope.rows, function (row) {
angular.forEach(row, function (day) {
day.selected = scope.selectedDates.indexOf(day.date.setHours(0, 0, 0, 0)) > -1
});
});
}
Expand All @@ -71,67 +131,11 @@ SOFTWARE.

return $delegate;
}

if ($injector.has('daypickerDirective'))
$provide.decorator('daypickerDirective', ['$delegate', delegate]);
$provide.decorator('daypickerDirective', ['$delegate', daypickerDelegate]);

if ($injector.has('uibDaypickerDirective'))
$provide.decorator('uibDaypickerDirective', ['$delegate', delegate]);
}])
.directive('multiSelect', function() {
return {
require: ['ngModel'],
link: function(scope, elem, attrs, ctrls) {
var selectedDates;
var selectRange;

/* Called when directive is compiled */
scope.$on('requestSelectedDates', function() {
scope.$broadcast('update', selectedDates);
});

scope.$watchCollection(attrs.multiSelect, function(newVal) {
selectedDates = newVal || [];
scope.$broadcast('update', selectedDates);
});

attrs.$observe('selectRange', function(newVal) {
selectRange = !!newVal && newVal !== "false";
});

scope.$watch(attrs.ngModel, function(newVal, oldVal) {
if(!newVal) return;

var dateVal = newVal.getTime();

if(selectRange) {
/* reset range */
if(!selectedDates.length || selectedDates.length > 1 || selectedDates[0] == dateVal)
return selectedDates.splice(0, selectedDates.length, dateVal);

selectedDates.push(dateVal);

var tempVal = Math.min.apply(null, selectedDates);
var maxVal = Math.max.apply(null, selectedDates);

/* Start on the next day to prevent duplicating the
first date */
tempVal = new Date(tempVal).setHours(24);
while(tempVal < maxVal) {
selectedDates.push(tempVal);
/* Set a day ahead after pushing to prevent
duplicating last date */
tempVal = new Date(tempVal).setHours(24);
}
} else {
if(selectedDates.indexOf(dateVal) < 0) {
selectedDates.push(dateVal);
} else {
selectedDates.splice(selectedDates.indexOf(dateVal), 1);
}
}
});
}
}
});
$provide.decorator('uibDaypickerDirective', ['$delegate', daypickerDelegate]);
}]);
})();

0 comments on commit ef86dc0

Please sign in to comment.