Skip to content

Commit

Permalink
feat(timepicker): input filter to accept only numbers (#3247)
Browse files Browse the repository at this point in the history
fixes #2334
  • Loading branch information
fbasso committed Nov 15, 2019
1 parent f4d3848 commit 25df51a
Show file tree
Hide file tree
Showing 7 changed files with 105 additions and 4 deletions.
2 changes: 2 additions & 0 deletions e2e-app/src/app/app.module.ts
Expand Up @@ -23,6 +23,7 @@ import {TooltipFocusComponent} from './tooltip/focus/tooltip-focus.component';
import {TooltipPositionComponent} from './tooltip/position/tooltip-position.component';
import {TypeaheadAutoCloseComponent} from './typeahead/autoclose/typeahead-autoclose.component';
import {TypeaheadFocusComponent} from './typeahead/focus/typeahead-focus.component';
import {TimepickerFilterComponent} from './timepicker/filter/timepicker-filter.component';
import {TimepickerNavigationComponent} from './timepicker/navigation/timepicker-navigation.component';
import {TypeaheadValidationComponent} from './typeahead/validation/typeahead-validation.component';

Expand All @@ -47,6 +48,7 @@ import {TypeaheadValidationComponent} from './typeahead/validation/typeahead-val
TypeaheadFocusComponent,
TypeaheadValidationComponent,
TypeaheadAutoCloseComponent,
TimepickerFilterComponent,
TimepickerNavigationComponent,
],
imports: [BrowserModule, FormsModule, ReactiveFormsModule, routing, NgbModule],
Expand Down
2 changes: 2 additions & 0 deletions e2e-app/src/app/app.routing.ts
Expand Up @@ -15,6 +15,7 @@ import {TooltipFocusComponent} from './tooltip/focus/tooltip-focus.component';
import {TooltipPositionComponent} from './tooltip/position/tooltip-position.component';
import {TypeaheadAutoCloseComponent} from './typeahead/autoclose/typeahead-autoclose.component';
import {TypeaheadFocusComponent} from './typeahead/focus/typeahead-focus.component';
import {TimepickerFilterComponent} from './timepicker/filter/timepicker-filter.component';
import {TimepickerNavigationComponent} from './timepicker/navigation/timepicker-navigation.component';
import {TypeaheadValidationComponent} from './typeahead/validation/typeahead-validation.component';
import {DropdownPositionComponent} from './dropdown/position/dropdown-position.component';
Expand Down Expand Up @@ -63,6 +64,7 @@ export const routes: Routes = [
path: 'timepicker',
children: [
{path: 'navigation', component: TimepickerNavigationComponent},
{path: 'filter', component: TimepickerFilterComponent},
]
},
];
Expand Down
@@ -0,0 +1,4 @@
<ngb-timepicker
[seconds]="true"
[(ngModel)]="time"
></ngb-timepicker>
@@ -0,0 +1,8 @@
import {Component} from '@angular/core';

@Component({
templateUrl: './timepicker-filter.component.html',
})
export class TimepickerFilterComponent {
time = {hour: null, minute: null, second: null};
}
69 changes: 69 additions & 0 deletions e2e-app/src/app/timepicker/filter/timepicker-filter.e2e-spec.ts
@@ -0,0 +1,69 @@
import {protractor} from 'protractor';

import {openUrl} from '../../tools.po';

import {TimepickerFilterPage} from './timepicker-filter.po';

describe('Timepicker', () => {
let page: TimepickerFilterPage;

beforeAll(() => page = new TimepickerFilterPage());
beforeEach(async() => await openUrl('timepicker/filter'));

async function expectValue(expectedValue) {
const inputs = page.getFields();

const values = [];
for (let i = 0; i < inputs.length; i++) {
values[i] = await inputs[i].getAttribute('value');
}
expect(values.join(':')).toBe(expectedValue);
}

describe('filter', async() => {
it(`should accept numbers`, async() => {
await expectValue('::'); // No starting values

const inputs = page.getFields();
await inputs[0].sendKeys('1');
await inputs[1].sendKeys('2');
await inputs[2].sendKeys('3');

await inputs[0].click();
await expectValue('01:02:03');
});

it(`shouldn't accept alpha`, async() => {
await expectValue('::'); // No starting values

const inputs = page.getFields();
await inputs[0].sendKeys('A');
await inputs[1].sendKeys('A');
await inputs[2].sendKeys('A');

await inputs[0].click();
await expectValue('::');
});

it(`shouldn accept special commands`, async() => {

const inputs = page.getFields();

await inputs[0].sendKeys('1');
await inputs[0].sendKeys(protractor.Key.chord(protractor.Key.CONTROL, 'a'));
await inputs[0].sendKeys(protractor.Key.chord(protractor.Key.CONTROL, 'c'));

await inputs[1].click();
await inputs[1].sendKeys(protractor.Key.chord(protractor.Key.CONTROL, 'v'));

await inputs[2].click();
await inputs[2].sendKeys(protractor.Key.chord(protractor.Key.CONTROL, 'v'));

await inputs[0].click();
await expectValue('01:01:01');

});

});

});
8 changes: 8 additions & 0 deletions e2e-app/src/app/timepicker/filter/timepicker-filter.po.ts
@@ -0,0 +1,8 @@
import {$} from 'protractor';

export type Field = 'hour' | 'minute' | 'second';

export class TimepickerFilterPage {
getField(field: Field) { return $(`.ngb-tp-${field} > input`); }
getFields() { return ['hour', 'minute', 'second'].map((field: Field) => this.getField(field)); }
}
16 changes: 12 additions & 4 deletions src/timepicker/timepicker.ts
Expand Up @@ -15,6 +15,8 @@ import {NgbTimepickerConfig} from './timepicker-config';
import {NgbTimeAdapter} from './ngb-time-adapter';
import {NgbTimepickerI18n} from './timepicker-i18n';

const FILTER_REGEX = /[^0-9]/g;

const NGB_TIMEPICKER_VALUE_ACCESSOR = {
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => NgbTimepicker),
Expand All @@ -38,10 +40,12 @@ const NGB_TIMEPICKER_VALUE_ACCESSOR = {
<span class="chevron ngb-tp-chevron"></span>
<span class="sr-only" i18n="@@ngb.timepicker.increment-hours">Increment hours</span>
</button>
<input type="text" class="ngb-tp-input form-control" [class.form-control-sm]="isSmallSize" [class.form-control-lg]="isLargeSize"
maxlength="2" placeholder="HH" i18n-placeholder="@@ngb.timepicker.HH"
<input type="text" class="ngb-tp-input form-control" [class.form-control-sm]="isSmallSize"
[class.form-control-lg]="isLargeSize"
maxlength="2" inputmode="numeric" placeholder="HH" i18n-placeholder="@@ngb.timepicker.HH"
[value]="formatHour(model?.hour)" (change)="updateHour($event.target.value)"
[readOnly]="readonlyInputs" [disabled]="disabled" aria-label="Hours" i18n-aria-label="@@ngb.timepicker.hours"
(input)="formatInput($event.target)"
(keydown.ArrowUp)="changeHour(hourStep); $event.preventDefault()"
(keydown.ArrowDown)="changeHour(-hourStep); $event.preventDefault()">
<button *ngIf="spinners" tabindex="-1" type="button" (click)="changeHour(-hourStep)"
Expand All @@ -60,9 +64,10 @@ const NGB_TIMEPICKER_VALUE_ACCESSOR = {
<span class="sr-only" i18n="@@ngb.timepicker.increment-minutes">Increment minutes</span>
</button>
<input type="text" class="ngb-tp-input form-control" [class.form-control-sm]="isSmallSize" [class.form-control-lg]="isLargeSize"
maxlength="2" placeholder="MM" i18n-placeholder="@@ngb.timepicker.MM"
maxlength="2" inputmode="numeric" placeholder="MM" i18n-placeholder="@@ngb.timepicker.MM"
[value]="formatMinSec(model?.minute)" (change)="updateMinute($event.target.value)"
[readOnly]="readonlyInputs" [disabled]="disabled" aria-label="Minutes" i18n-aria-label="@@ngb.timepicker.minutes"
(input)="formatInput($event.target)"
(keydown.ArrowUp)="changeMinute(minuteStep); $event.preventDefault()"
(keydown.ArrowDown)="changeMinute(-minuteStep); $event.preventDefault()">
<button *ngIf="spinners" tabindex="-1" type="button" (click)="changeMinute(-minuteStep)"
Expand All @@ -81,9 +86,10 @@ const NGB_TIMEPICKER_VALUE_ACCESSOR = {
<span class="sr-only" i18n="@@ngb.timepicker.increment-seconds">Increment seconds</span>
</button>
<input type="text" class="ngb-tp-input form-control" [class.form-control-sm]="isSmallSize" [class.form-control-lg]="isLargeSize"
maxlength="2" placeholder="SS" i18n-placeholder="@@ngb.timepicker.SS"
maxlength="2" inputmode="numeric" placeholder="SS" i18n-placeholder="@@ngb.timepicker.SS"
[value]="formatMinSec(model?.second)" (change)="updateSecond($event.target.value)"
[readOnly]="readonlyInputs" [disabled]="disabled" aria-label="Seconds" i18n-aria-label="@@ngb.timepicker.seconds"
(input)="formatInput($event.target)"
(keydown.ArrowUp)="changeSecond(secondStep); $event.preventDefault()"
(keydown.ArrowDown)="changeSecond(-secondStep); $event.preventDefault()">
<button *ngIf="spinners" tabindex="-1" type="button" (click)="changeSecond(-secondStep)"
Expand Down Expand Up @@ -245,6 +251,8 @@ export class NgbTimepicker implements ControlValueAccessor,
}
}

formatInput(input: HTMLInputElement) { input.value = input.value.replace(FILTER_REGEX, ''); }

formatHour(value: number) {
if (isNumber(value)) {
if (this.meridian) {
Expand Down

0 comments on commit 25df51a

Please sign in to comment.