From 14c160c6d6fe15934001121a9dfb1aca31c97d27 Mon Sep 17 00:00:00 2001 From: Breno Vieira Date: Thu, 15 Oct 2015 17:34:29 -0300 Subject: [PATCH] Fixing issue when more than one datepicker is visible --- README.md | 4 - dist/gm.datepickerMultiSelect.min.js | 4 +- examples/2-datepickers.html | 75 ++++++++++++ modules/app.js | 8 ++ src/gm.datepickerMultiSelect.js | 164 ++++++++++++++------------- 5 files changed, 169 insertions(+), 86 deletions(-) create mode 100644 examples/2-datepickers.html diff --git a/README.md b/README.md index cbaed00..8b33a3e 100644 --- a/README.md +++ b/README.md @@ -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"): diff --git a/dist/gm.datepickerMultiSelect.min.js b/dist/gm.datepickerMultiSelect.min.js index 6f7c88c..6c33594 100644 --- a/dist/gm.datepickerMultiSelect.min.js +++ b/dist/gm.datepickerMultiSelect.min.js @@ -1,2 +1,2 @@ -/*! gm.datepickerMultiSelect 2015-10-13 */ -!function(){angular.module("gm.datepickerMultiSelect",["ui.bootstrap"]).config(["$provide","$injector",function(a,b){var c=function(a){var b=a[0],c=b.link;return b.compile=function(){return function(a,b,d,e){function f(){angular.forEach(a.rows,function(a){angular.forEach(a,function(a){a.selected=g.indexOf(a.date.setHours(0,0,0,0))>-1})})}c.apply(this,arguments);var g=[];a.$on("update",function(a,b){g=b,f()}),a.$emit("requestSelectedDates"),a.$watch(function(){return angular.isArray(e)?e[0].activeDate.getTime():e.activeDate.getTime()},f)}},a};b.has("daypickerDirective")&&a.decorator("daypickerDirective",["$delegate",c]),b.has("uibDaypickerDirective")&&a.decorator("uibDaypickerDirective",["$delegate",c])}]).directive("multiSelect",function(){return{require:["ngModel"],link:function(a,b,c,d){var e,f;a.$on("requestSelectedDates",function(){a.$broadcast("update",e)}),a.$watchCollection(c.multiSelect,function(b){e=b||[],a.$broadcast("update",e)}),c.$observe("selectRange",function(a){f=!!a&&"false"!==a}),a.$watch(c.ngModel,function(a,b){if(a){var c=a.getTime();if(f){if(!e.length||e.length>1||e[0]==c)return e.splice(0,e.length,c);e.push(c);var d=Math.min.apply(null,e),g=Math.max.apply(null,e);for(d=new Date(d).setHours(24);g>d;)e.push(d),d=new Date(d).setHours(24)}else e.indexOf(c)<0?e.push(c):e.splice(e.indexOf(c),1)}})}}})}(); \ No newline at end of file +/*! gm.datepickerMultiSelect 2015-10-15 */ +!function(){"use strict";angular.module("gm.datepickerMultiSelect",["ui.bootstrap"]).config(["$provide","$injector",function(a,b){var c=function(a){var b=a[0],c=b.link;return b.compile=function(){return function(a,b,d,e){c.apply(this,arguments),a.selectedDates=[],a.selectRange,a.$parent.$watchCollection(d.multiSelect,function(b){a.selectedDates=b||[]}),d.$observe("selectRange",function(b){a.selectRange=!!b&&"false"!==b}),a.$parent.$watch(d.ngModel,function(b,c){if(b){var d=b.getTime(),e=a.selectedDates;if(a.selectRange){if(!e.length||e.length>1||e[0]==d)return e.splice(0,e.length,d);e.push(d);var f=Math.min.apply(null,e),g=Math.max.apply(null,e);for(f=new Date(f).setHours(24);g>f;)e.push(f),f=new Date(f).setHours(24)}else e.indexOf(d)<0?e.push(d):e.splice(e.indexOf(d),1)}})}},a};b.has("datepickerDirective")&&a.decorator("datepickerDirective",["$delegate",c]),b.has("uibDatepickerDirective")&&a.decorator("uibDatepickerDirective",["$delegate",c]);var d=function(a){var b=a[0],c=b.link;return b.compile=function(){return function(a,b,d,e){function f(){angular.forEach(a.rows,function(b){angular.forEach(b,function(b){b.selected=a.selectedDates.indexOf(b.date.setHours(0,0,0,0))>-1})})}c.apply(this,arguments),a.$parent.$watchCollection("selectedDates",f);var g=angular.isArray(e)?e[0]:e;a.$watch(function(){return g.activeDate.getTime()},f)}},a};b.has("daypickerDirective")&&a.decorator("daypickerDirective",["$delegate",d]),b.has("uibDaypickerDirective")&&a.decorator("uibDaypickerDirective",["$delegate",d])}])}(); \ No newline at end of file diff --git a/examples/2-datepickers.html b/examples/2-datepickers.html new file mode 100644 index 0000000..c49dd3c --- /dev/null +++ b/examples/2-datepickers.html @@ -0,0 +1,75 @@ + + + + + gm.datepickerMultiSelect Example + + + + + + + + + + + + +

Selection Type:

+
+ + +
+
+ + + + + + +
+ + + +
+ Selected Dates: +
+
+ {{d | date : 'fullDate'}} + +
+
+
+
+
+

Selection Type 1:

+
+ + +
+
+ + + + + + +
+ + + +
+ Selected Dates 1: +
+
+ {{d | date : 'fullDate'}} + +
+
+
+
+ + \ No newline at end of file diff --git a/modules/app.js b/modules/app.js index 107edf2..e7d6811 100644 --- a/modules/app.js +++ b/modules/app.js @@ -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); + } }); \ No newline at end of file diff --git a/src/gm.datepickerMultiSelect.js b/src/gm.datepickerMultiSelect.js index e4a45ec..412239e 100644 --- a/src/gm.datepickerMultiSelect.js +++ b/src/gm.datepickerMultiSelect.js @@ -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. @@ -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 }); }); } @@ -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]); + }]); })();