Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding date range selection. Fix for issue # 5 #251

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,8 @@ today | `false` | Highlights the current day. | [Plunker](https://embed.plnkr.co
keyboard | `false` | Allows using the keyboard to navigate the picker. | [Plunker](https://embed.plnkr.co/OdUhHx)
show-header | `true` | Shows the header in the view. | [Plunker](https://embed.plnkr.co/PCL4mh)
additions | `{ top: undefined, bottom: undefined }` | Template url for custom contents above and below each picker views (inside the dialog). | [Plunker](https://embed.plnkr.co/CXOH5U)
range-start | | Beginning date set when rangeSelection is set to true |
range-end | | Ending date set when rangeSelection is set to true |

## Methods

Expand Down Expand Up @@ -193,6 +195,7 @@ seconds-format | `"ss"` | Seconds format in `minute` view.
seconds-step | `1` | Step between each visible second in `minute` view.
seconds-start | `0` | First rendered second in `minute` view.
seconds-end | `59` | Last rendered second in `minute` view.
rangeSelection | `false` | Allows selecting a range of dates. Set on range-start and range-end options

## Notes

Expand Down
8 changes: 8 additions & 0 deletions src/definitions.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ export interface IDirectiveScope extends ng.IScope {
};
change?: (context: any) => boolean;
selectable?: (context: any) => boolean;
rangeSelection?: boolean;
rangeStart?: moment.Moment;
rangeEnd?: moment.Moment;
}

export interface IUtility {
Expand Down Expand Up @@ -81,6 +84,7 @@ export interface IDirectiveScopeInternal extends IDirectiveScope, IProviderOptio
isAfterOrEqualMin: (value: moment.Moment, precision?: moment.unitOfTime.StartOf) => boolean;
isBeforeOrEqualMax: (value: moment.Moment, precision?: moment.unitOfTime.StartOf) => boolean;
isSelectable: (value: moment.Moment, precision?: moment.unitOfTime.StartOf) => boolean;
isRangeHighlighted: (value: moment.Moment, precision?: moment.unitOfTime.StartOf) => string;
checkValue: () => void;
checkView: () => void;
};
Expand Down Expand Up @@ -137,6 +141,10 @@ export interface IDirectiveScopeInternal extends IDirectiveScope, IProviderOptio
picker: ng.IAugmentedJQuery;
container: ng.IAugmentedJQuery;
input: ng.IAugmentedJQuery;

// range selection
rangeStart: moment.Moment;
rangeEnd: moment.Moment;
}

export interface IModelValidators extends ng.IModelValidators {
Expand Down
26 changes: 24 additions & 2 deletions src/directive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,10 @@ export default class Directive implements ng.IDirective {
showHeader: '=?',
additions: '=?',
change: '&?',
selectable: '&?'
selectable: '&?',
rangeSelection: '@?',
rangeStart: '=?rangeStart',
rangeEnd: '=?rangeEnd'
};

constructor(
Expand All @@ -53,7 +56,7 @@ export default class Directive implements ng.IDirective {
// one-way binding attributes
angular.forEach([
'locale', 'format', 'minView', 'maxView', 'startView', 'position', 'inline', 'validate', 'autoclose', 'setOnSelect', 'today',
'keyboard', 'showHeader', 'leftArrow', 'rightArrow', 'additions'
'keyboard', 'showHeader', 'leftArrow', 'rightArrow', 'additions', 'rangeSelection', 'rangeStart', 'rangeEnd'
], (attr: string) => {
if (!angular.isDefined($scope[attr])) $scope[attr] = this.provider[attr];
if (!angular.isDefined($attrs[attr])) $attrs[attr] = $scope[attr];
Expand All @@ -79,8 +82,19 @@ export default class Directive implements ng.IDirective {
} catch (e) {
this.$log.error(e);
}
if ($scope.rangeSelection && $scope.rangeStart && !$scope.rangeEnd && value.isBefore($scope.rangeStart, precision)) {
return false;
}
return $scope.limits.isAfterOrEqualMin(value, precision) && $scope.limits.isBeforeOrEqualMax(value, precision) && selectable;
},
isRangeHighlighted: (value: moment.Moment, precision?: moment.unitOfTime.StartOf) => {
return $scope.rangeSelection &&
$scope.rangeStart &&
$scope.rangeEnd &&
value.isSameOrAfter($scope.rangeStart, precision) &&
value.isSameOrBefore($scope.rangeEnd, precision)
? 'rangeHighlight' : '';
},
checkValue: () => {
if (!isValidMoment($ctrl.$modelValue) || !$scope.validate) return;
if (!$scope.limits.isAfterOrEqualMin($ctrl.$modelValue)) setValue($scope.limits.minDate, $scope, $ctrl, $attrs);
Expand Down Expand Up @@ -288,6 +302,14 @@ export default class Directive implements ng.IDirective {
if ($scope.setOnSelect) update();
if (nextView < 0 || nextView > maxView) {
if (!$scope.setOnSelect) update();
if (!$scope.rangeStart) {
$scope.rangeStart = $scope.view.moment.clone();
} else if (!$scope.rangeEnd) {
$scope.rangeEnd = $scope.view.moment.clone();
} else {
$scope.rangeStart = $scope.view.moment.clone();
$scope.rangeEnd = null;
}
if ($scope.autoclose) this.$timeout($scope.view.close);
} else if (nextView >= minView) $scope.view.selected = view;
}
Expand Down
3 changes: 2 additions & 1 deletion src/index.less
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@
&.today { background: @today-bg; color: @today-fg; text-shadow: 0 1px 0 @today-fg-shadow; }
&.selected { color: @selected-fg; text-shadow: @selected-fg-shadow; border-color: @selected-border-color; background-color: @selected-bg-color; background-image: @selected-bg-image; }
&.highlighted { background-image: @highlighted-bg-image; }
&.rangeHighlight { background-image: @selected-bg-color; }
}

// decade view, year view
Expand All @@ -110,4 +111,4 @@

// minute view
.minute-view td { height: 1.8em; }
}
}
4 changes: 3 additions & 1 deletion src/provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export interface IProviderOptions {
secondsStep?: number;
secondsStart?: number;
secondsEnd?: number;
rangeSelection?: boolean;
}

export default class Provider implements angular.IServiceProvider {
Expand Down Expand Up @@ -85,7 +86,8 @@ export default class Provider implements angular.IServiceProvider {
secondsFormat: 'ss',
secondsStep: 1,
secondsStart: 0,
secondsEnd: 59
secondsEnd: 59,
rangeSelection: false
};

public options(options: IProviderOptions): IProviderOptions {
Expand Down
3 changes: 2 additions & 1 deletion src/views/dayView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ export default class DayView implements IView {
hour: hour.hour(),
class: [
this.$scope.keyboard && hour.isSame(this.$scope.view.moment, 'hour') ? 'highlighted' : '',
!selectable ? 'disabled' : isValidMoment(this.$ctrl.$modelValue) && hour.isSame(this.$ctrl.$modelValue, 'hour') ? 'selected' : ''
!selectable ? 'disabled' : isValidMoment(this.$ctrl.$modelValue) && hour.isSame(this.$ctrl.$modelValue, 'hour') ? 'selected' : '',
this.$scope.limits.isRangeHighlighted(hour, 'hour')
].join(' ').trim(),
selectable: selectable
});
Expand Down
3 changes: 2 additions & 1 deletion src/views/decadeView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ export default class DecadeView implements IView {
year: year.year(),
class: [
this.$scope.keyboard && year.isSame(this.$scope.view.moment, 'year') ? 'highlighted' : '',
!selectable || [0, 11].indexOf(y) >= 0 ? 'disabled' : isValidMoment(this.$ctrl.$modelValue) && year.isSame(this.$ctrl.$modelValue, 'year') ? 'selected' : ''
!selectable || [0, 11].indexOf(y) >= 0 ? 'disabled' : isValidMoment(this.$ctrl.$modelValue) && year.isSame(this.$ctrl.$modelValue, 'year') ? 'selected' : '',
this.$scope.limits.isRangeHighlighted(year, 'year')
].join(' ').trim(),
selectable: selectable
});
Expand Down
3 changes: 2 additions & 1 deletion src/views/hourView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ export default class HourView implements IView {
minute: minute.minute(),
class: [
this.$scope.keyboard && minute.isSame(this.$scope.view.moment, 'minute') ? 'highlighted' : '',
!selectable ? 'disabled' : isValidMoment(this.$ctrl.$modelValue) && minute.isSame(this.$ctrl.$modelValue, 'minute') ? 'selected' : ''
!selectable ? 'disabled' : isValidMoment(this.$ctrl.$modelValue) && minute.isSame(this.$ctrl.$modelValue, 'minute') ? 'selected' : '',
this.$scope.limits.isRangeHighlighted(minute, 'minute')
].join(' ').trim(),
selectable: selectable
});
Expand Down
3 changes: 2 additions & 1 deletion src/views/minuteView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ export default class MinuteView implements IView {
second: second.second(),
class: [
this.$scope.keyboard && second.isSame(this.$scope.view.moment, 'second') ? 'highlighted' : '',
!selectable ? 'disabled' : isValidMoment(this.$ctrl.$modelValue) && second.isSame(this.$ctrl.$modelValue, 'second') ? 'selected' : ''
!selectable ? 'disabled' : isValidMoment(this.$ctrl.$modelValue) && second.isSame(this.$ctrl.$modelValue, 'second') ? 'selected' : '',
this.$scope.limits.isRangeHighlighted(second, 'second')
].join(' ').trim(),
selectable: selectable
});
Expand Down
3 changes: 2 additions & 1 deletion src/views/monthView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ export default class MonthView implements IView {
class: [
this.$scope.keyboard && day.isSame(this.$scope.view.moment, 'day') ? 'highlighted' : '',
!!this.$scope.today && day.isSame(new Date(), 'day') ? 'today' : '',
!selectable || day.month() != month ? 'disabled' : isValidMoment(this.$ctrl.$modelValue) && day.isSame(this.$ctrl.$modelValue, 'day') ? 'selected' : ''
!selectable || day.month() != month ? 'disabled' : isValidMoment(this.$ctrl.$modelValue) && day.isSame(this.$ctrl.$modelValue, 'day') ? 'selected' : '',
this.$scope.limits.isRangeHighlighted(day, 'day')
].join(' ').trim(),
selectable: selectable
};
Expand Down
3 changes: 2 additions & 1 deletion src/views/yearView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ class YearView implements IView {
month: month.month(),
class: [
this.$scope.keyboard && month.isSame(this.$scope.view.moment, 'month') ? 'highlighted' : '',
!selectable ? 'disabled' : isValidMoment(this.$ctrl.$modelValue) && month.isSame(this.$ctrl.$modelValue, 'month') ? 'selected' : ''
!selectable ? 'disabled' : isValidMoment(this.$ctrl.$modelValue) && month.isSame(this.$ctrl.$modelValue, 'month') ? 'selected' : '',
this.$scope.limits.isRangeHighlighted(month, 'month')
].join(' ').trim(),
selectable: selectable
});
Expand Down
69 changes: 69 additions & 0 deletions tests/properties/rangeSelection.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import * as moment from 'moment';
import * as test from '../utility';

describe('rangeSelection', () => {
let $scope: ng.IScope;
let $input: ng.IAugmentedJQuery;
let format = 'YYYY-MM-DD';

// init test
test.bootstrap();

describe('when rangeSelection is set to true', () => {
let date = moment(),
selectableElement;

beforeEach(inject(($rootScope: ng.IRootScopeService) => {
$scope = $rootScope.$new();
$input = test.buildTemplate('input', {
momentPicker: 'dateObj',
ngModel: date,
format: format,
maxView: 'year',
rangeSelection: true,
rangeStart: null,
rangeEnd: null
}, undefined, $scope);
selectableElement = test.getPicker($input).find('td');
}));

it('should set rangeSelection flag on $scope', () => {
expect($scope.rangeSelection).toBe(true);
});

it('should set rangeStart on first selection', () => {
selectableElement[0].click();
$scope.$apply();
expect($scope.rangeStart).toEqual(date);
});

it('should set rangeEnd on second selection', () => {
let dateStart = date.clone();

selectableElement[0].click();
$scope.$apply();

date.add(1, 'y');
selectableElement[0].click();
$scope.$apply();

expect($scope.rangeStart).toEqual(dateStart);
expect($scope.rangeEnd).toEqual(date);
});

it('should reset rangeStart and rangeEnd if rangeStart and rangeEnd are already defined', () => {
selectableElement[0].click();
$scope.$apply();

date.add(1, 'y');
selectableElement[0].click();
$scope.$apply();

date.add(1, 'y');
selectableElement[0].click();
$scope.$apply();
expect($scope.rangeStart).toEqual(date);
expect($scope.rangeEnd).toEqual(null);
});
});
});