Skip to content

Commit

Permalink
feat(CohortDateRange): no longer blocks user from custom interaction …
Browse files Browse the repository at this point in the history
…when allowed

When `allowCustomDates` is true, the user can manually adjust an existing cohort.

ISSUES CLOSED: #1819
benjamincharity committed Dec 16, 2019

Verified

This commit was signed with the committer’s verified signature.
fabpot Fabien Potencier
1 parent d56fec8 commit 5af8868
Showing 7 changed files with 153 additions and 66 deletions.
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
<ts-card>
<form [formGroup]="myForm" novalidate>

<ts-cohort-date-range
[cohorts]="cohorts"
[allowCustomDates]="true"
(cohortDateRangeChanged)="printRange($event)"
[isDisabled]="false"
></ts-cohort-date-range>

</form>

<pre *ngIf="lastRange">
Original file line number Diff line number Diff line change
@@ -4,14 +4,17 @@ import {
FormGroup,
Validators,
} from '@angular/forms';
import {
TsCohortDateRangeChanged,
TsDateCohort,
} from '@terminus/ui/cohort-date-range';
import {
endOfDay,
startOfDay,
startOfMonth,
subDays,
subMonths,
} from 'date-fns';
import { TsCohortDateRangeChanged } from '@terminus/ui/cohort-date-range';

const currentDate: Date = new Date();

@@ -35,25 +38,22 @@ export class CohortDateRangeComponent {
],
}),
});
public cohorts = [{
display: 'Last 90 days',
range: {
start: startOfDay(subDays(new Date(), 90)),
end: currentDate,
},
}, {
display: 'Last full month',
range: {
start: startOfDay(subMonths(startOfMonth(currentDate), 1)),
end: endOfDay(subDays(startOfMonth(currentDate), 1)),
public cohorts: TsDateCohort[] = [
{
display: 'Last 90 days',
range: {
start: startOfDay(subDays(new Date(), 90)),
end: currentDate,
},
},
}, {
display: 'Custom dates',
range: {
start: '',
end: '',
{
display: 'Last full month',
range: {
start: startOfDay(subMonths(startOfMonth(currentDate), 1)),
end: endOfDay(subDays(startOfMonth(currentDate), 1)),
},
},
}];
];
public lastRange: TsCohortDateRangeChanged | undefined;


@@ -64,7 +64,7 @@ export class CohortDateRangeComponent {


public printRange(value: TsCohortDateRangeChanged): void {
console.log('DEMO: formValue: ', value);
// console.log('DEMO: formValue: ', value);
this.lastRange = value;
}

Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<ts-date-range
class="ts-cohort-date-range__date-range"
[dateFormGroup]="dateRangeFormGroup"
[isDisabled]="!allowCustomDates || isDisabled || disableDateRange"
[isDisabled]="!allowCustomDates || isDisabled"
(dateRangeChange)="cohortDateRangeChange($event)"
></ts-date-range>

@@ -18,4 +18,12 @@
>
{{ option.display }}
</ts-option>

<ts-option
*ngIf="allowCustomDates"
[value]="customDateCohort.range"
[option]="customDateCohort"
>
{{ customDateCohort.display }}
</ts-option>
</ts-select>
24 changes: 7 additions & 17 deletions terminus-ui/cohort-date-range/src/cohort-date-range.component.md
Original file line number Diff line number Diff line change
@@ -7,7 +7,7 @@
- [Event driven](#event-driven)
- [Inputs to the component](#inputs-to-the-component)
- [allowCustomDates](#allowcustomdates)
- [Disable the component](#disable-the-component)
- [Disable](#disable)
- [Test Helpers](#test-helpers)

<!-- END doctoc generated TOC please keep comment here to allow auto update -->
@@ -50,7 +50,7 @@ cohorts: TsDateCohort[] = [
];
```

NOTE: The keys inside the passed in `cohorts` object defined as `TsDateCohort` interface has to be in the form of
The keys inside the passed in `cohorts` object must match the `TsDateCohort` interface:

```typescript
{
@@ -62,25 +62,13 @@ NOTE: The keys inside the passed in `cohorts` object defined as `TsDateCohort` i
}
```

NOTE: For any custom dates which allows user to select a date from date range selection would need to pass in start and end date with empty string:

```typescript
{
display: 'Custom Dates',
range: {
startDate: '',
endDate: '',
}
}
```

## Event driven

Anytime the date range is changed, `cohortDateRangeChanged` is emitted.

```html
<ts-cohort-date-range
[cohorts]="cohorts"
[cohorts]="myCohorts"
(cohortDateRangeChanged)="printRange($event)"
></ts-cohort-date-range>
```
@@ -90,7 +78,7 @@ Anytime the date range is changed, `cohortDateRangeChanged` is emitted.

### allowCustomDates

`allowCustomDates` defaults to `true`. When set to `false`, date range is readonly.
When `allowCustomDates` is set to `false`, the date range is readonly (this defaults to `true`).

```html
<ts-cohort-date-range
@@ -99,7 +87,9 @@ Anytime the date range is changed, `cohortDateRangeChanged` is emitted.
></ts-cohort-date-range>
```

### Disable the component
When set to `true`, a `Custom Dates` option will be added to the dropdown.

### Disable

The entire component can be disabled:

Original file line number Diff line number Diff line change
@@ -39,7 +39,7 @@ function createComponent<T>(component: Type<T>, providers: Provider[] = [], impo

describe(`TsCohortDateRangeComponent`, () => {
let fixture: ComponentFixture<TsCohortDateRangeTestComponent>;
let hostInstance;
let hostInstance: TsCohortDateRangeTestComponent;
let startInputInstance: TsInputComponent;
let formFieldElement: HTMLElement;
let cohortDebugElement: DebugElement;
@@ -56,12 +56,12 @@ describe(`TsCohortDateRangeComponent`, () => {
cohortInstance = cohortDebugElement.componentInstance;
}

describe(`allowCustomsDate`, () => {
describe(`allowCustomDates`, () => {
beforeEach(() => {
setupComponent(testComponents.Basic);
});

test(`should have date range readonly if allowCustomsDate is false`, () => {
test(`should have date range readonly if false`, () => {
hostInstance.allowCustomDates = false;
fixture.detectChanges();
expect(formFieldElement.classList).toContain('ts-form-field--disabled');
@@ -76,6 +76,39 @@ describe(`TsCohortDateRangeComponent`, () => {
});
});

describe(`updateSelectOnRangeChange`, () => {

test(`should do nothing if no cohorts exist`, () => {
setupComponent(testComponents.Basic);
const debug = getCohortDebugElement(fixture);
const instance: TsCohortDateRangeComponent = debug.componentInstance;
instance.selectComponent.options.toArray = jest.fn();
instance.cohorts = undefined as any;

instance.dateRangeFormGroup.setValue({
startDate: new Date(),
endDate: new Date(),
});
fixture.detectChanges();

expect(instance.selectComponent.options.toArray).not.toHaveBeenCalled();
});

test(`should set the select to the custom cohort when the range is manually changed`, () => {
setupComponent(testComponents.Basic);
const debug = getCohortDebugElement(fixture);
const instance: TsCohortDateRangeComponent = debug.componentInstance;
const options = instance.selectComponent.options.toArray();
const lastOption = options[options.length - 1];
lastOption.select = jest.fn();
instance.dateRangeFormGroup.patchValue({ startDate: new Date() });
fixture.detectChanges();

expect(lastOption.select).toHaveBeenCalled();
});

});

describe(`select emitters`, function() {
let trigger: HTMLElement;
let event: KeyboardEvent;
@@ -94,19 +127,16 @@ describe(`TsCohortDateRangeComponent`, () => {
fixture.detectChanges();

expect(hostInstance.cohortDateRangeChanged).toHaveBeenCalled();
expect(cohortInstance.disableDateRange).toBeTruthy();
});

test(`should not emit change event and set date range enabled when there is no start/end date passed in`, () => {
setupComponent(testComponents.Standard);
cohortInstance.cohortDateRangeChange = jest.fn();
cohortInstance.disableDateRange = true;
trigger = getSelectTriggerElement(fixture);
event = createKeydownEvent(KEYS.DOWN_ARROW);
trigger.dispatchEvent(event);

expect(cohortInstance.cohortDateRangeChange).not.toHaveBeenCalled();
expect(cohortInstance.disableDateRange).toBeFalsy();
});

test(`should emit change event if date range has any changes`, () => {
99 changes: 79 additions & 20 deletions terminus-ui/cohort-date-range/src/cohort-date-range.component.ts
Original file line number Diff line number Diff line change
@@ -3,14 +3,24 @@ import {
Component,
EventEmitter,
Input,
OnDestroy,
OnInit,
Output,
ViewChild,
ViewEncapsulation,
} from '@angular/core';
import {
AbstractControl,
FormBuilder,
} from '@angular/forms';
import { untilComponentDestroyed } from '@terminus/ngx-tools';
import { coerceDateProperty } from '@terminus/ngx-tools/coercion';
import { TsOption } from '@terminus/ui/option';
import {
TsSelectChange,
TsSelectComponent,
} from '@terminus/ui/select';


/**
* Represents date cohort object that passed in
@@ -69,13 +79,19 @@ export class TsCohortDateRangeChanged {
encapsulation: ViewEncapsulation.None,
exportAs: 'tsCohortDateRange',
})
export class TsCohortDateRangeComponent {
export class TsCohortDateRangeComponent implements OnInit, OnDestroy {
/**
* Define whether date range is disabled
* Define the custom date cohort
*
* @internal
*/
public disableDateRange = false;
public customDateCohort: TsDateCohort = {
display: 'Custom dates',
range: {
start: null,
end: null,
},
};

/**
* Initialize the date range with empty start and end date
@@ -98,14 +114,22 @@ export class TsCohortDateRangeComponent {
return this.formGroup.get('dateRange') as AbstractControl;
}

/**
* Define a reference to the {@link TsSelectComponent}
*
* @internal
*/
@ViewChild(TsSelectComponent, { static: true })
public selectComponent: TsSelectComponent;

/**
* Define whether custom date is allowed
*/
@Input()
public allowCustomDates = true;

/**
* Cohort dates
* Date cohorts
*/
@Input()
public cohorts!: ReadonlyArray<TsDateCohort>;
@@ -127,6 +151,15 @@ export class TsCohortDateRangeComponent {
public formBuilder: FormBuilder,
) { }

public ngOnInit(): void {
this.updateSelectOnRangeChange();
}

/**
* Needed for untilComponentDestroyed
*/
public ngOnDestroy(): void {}

/**
* Emit the change event
*
@@ -143,24 +176,17 @@ export class TsCohortDateRangeComponent {
* @internal
* @param event - TsSelectChangeEvent
*/
public selectionChange(event): void {
const startCtrl = this.formGroup.get('dateRange.startDate');
const endCtrl = this.formGroup.get('dateRange.endDate');
public selectionChange(event: TsSelectChange<TsCohortDateChangeEvent>): void {
const dateRange = this.formGroup.get('dateRange');
const newValues = {
startDate: event.value.start,
endDate: event.value.end,
};

// istanbul ignore else
if (startCtrl && endCtrl) {
const startValue = event.value.start;
const endValue = event.value.end;

if (startValue && endValue) {
startCtrl.setValue(startValue);
endCtrl.setValue(endValue);
// Make date range readonly when selection provides start and end date.
this.disableDateRange = true;
this.cohortDateRangeChange(event.value);
} else {
this.disableDateRange = false;
}
if (newValues.startDate && newValues.endDate) {
dateRange.setValue(newValues);
this.cohortDateRangeChange(event.value);
}
}

@@ -174,4 +200,37 @@ export class TsCohortDateRangeComponent {
public trackByFn(index): number {
return index;
}

/**
* Update the select when the date is manually changed to not match a cohort
*/
private updateSelectOnRangeChange(): void {
this.formGroup.get('dateRange').valueChanges.pipe(untilComponentDestroyed(this)).subscribe(results => {
if (!this.cohorts) {
return;
}
const resultsStartTime = coerceDateProperty(results.startDate).getTime();
const resultsEndTime = coerceDateProperty(results.endDate).getTime();

const matchedCohorts = this.cohorts.filter(cohort => {
const cohortStartTime = coerceDateProperty(cohort.range.start).getTime();
const cohortEndTime = coerceDateProperty(cohort.range.end).getTime();
const cohortStartMatches = resultsStartTime === cohortStartTime;
const cohortEndMatches = resultsEndTime === cohortEndTime;

// istanbul ignore else
if (cohortStartMatches && cohortEndMatches) {
return cohort;
}
});

// NOTE: Since we are adding the custom cohort after the user cohorts, we know that the custom cohort will always be last.
// istanbul ignore else
if (matchedCohorts.length === 0) {
const options = this.selectComponent.options.toArray();
const option = options[options.length - 1];
option.select();
}
});
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { FlexLayoutModule } from '@angular/flex-layout';

import { ReactiveFormsModule } from '@angular/forms';
import { TsDateRangeModule } from '@terminus/ui/date-range';
import { TsOptionModule } from '@terminus/ui/option';
import { TsSelectModule } from '@terminus/ui/select';

import { TsCohortDateRangeComponent } from './cohort-date-range.component';

export * from './cohort-date-range.component';
@@ -17,6 +18,7 @@ export * from './cohort-date-range.component';
TsDateRangeModule,
TsSelectModule,
TsOptionModule,
ReactiveFormsModule,
],
exports: [
TsCohortDateRangeComponent,

0 comments on commit 5af8868

Please sign in to comment.