Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
6 changes: 6 additions & 0 deletions apps/ngx-bootstrap-docs/src/ng-api-doc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1194,6 +1194,12 @@ export const ngdoc: any = {
"defaultValue": "false",
"type": "boolean",
"description": "<p>sets use UTC date time format</p>\n"
},
{
"name": "withTimepicker",
"defaultValue": "false",
"type": "boolean",
"description": "<p>Shows timepicker under datepicker</p>\n"
}
]
},
Expand Down
11 changes: 10 additions & 1 deletion libs/doc-pages/datepicker/src/lib/datepicker-section.list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ import { DemoDatepickerTodayButtonComponent } from './demos/today-button/today-b
import { DemoDatepickerClearButtonComponent } from './demos/clear-button/clear-button';
import { DemoDatepickerStartViewComponent } from "./demos/start-view/start-view";
import { DemoDatepickerPreventChangeToNextMonthComponent } from './demos/prevent-change-to-next-month/prevent-change-to-next-month.component';
import { DemoDatepickerWithTimepickerComponent } from './demos/with-timepicker/with-timepicker';

export const demoComponentContent: ContentSection[] = [
{
Expand Down Expand Up @@ -460,7 +461,15 @@ export const demoComponentContent: ContentSection[] = [
description: `<p>Max date range after first date selection can be added to Daterangepicker using <code>maxDateRange</code>.</p>
<p>If you also use <code>maxDate</code> property, you can't select second date, which exceeds value of <code>maxDate</code>.</p>`,
outlet: DemoDateRangePickerMaxDateRangeComponent
}
},
{
title: 'With timepicker',
anchor: 'with-timepicker',
component: require('!!raw-loader!./demos/with-timepicker/with-timepicker'),
html: require('!!raw-loader!./demos/with-timepicker/with-timepicker.html'),
description: `You can enable timepicker via <code>withTimepicker</code> config option`,
outlet: DemoDatepickerWithTimepickerComponent
},
]
},
{
Expand Down
4 changes: 3 additions & 1 deletion libs/doc-pages/datepicker/src/lib/demos/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import { DemoDatepickerTodayButtonComponent } from './today-button/today-button'
import { DemoDatepickerClearButtonComponent } from './clear-button/clear-button';
import { DemoDatepickerStartViewComponent } from "./start-view/start-view";
import { DemoDatepickerPreventChangeToNextMonthComponent } from './prevent-change-to-next-month/prevent-change-to-next-month.component';
import { DemoDatepickerWithTimepickerComponent } from './with-timepicker/with-timepicker';

export const DEMO_COMPONENTS = [
DatepickerDemoComponent,
Expand Down Expand Up @@ -87,5 +88,6 @@ export const DEMO_COMPONENTS = [
DemoDateRangePickerMaxDateRangeComponent,
DemoDatepickerPreventChangeToNextMonthComponent,
DemoDatePickerQuickSelectRangesComponent,
DemoDatepickerStartViewComponent
DemoDatepickerStartViewComponent,
DemoDatepickerWithTimepickerComponent
];
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<div class="row">
<div class="col-xs-12 col-12 col-md-4 form-group">
<input type="text"
placeholder="Datepicker"
class="form-control"
[bsConfig]="{withTimepicker: true, rangeInputFormat : 'MMMM Do YYYY, h:mm:ss a', dateInputFormat: 'MMMM Do YYYY, h:mm:ss a'}"
bsDatepicker>
</div>
<div class="col-xs-12 col-12 col-md-4 form-group">
<input type="text"
placeholder="Daterangepicker"
class="form-control"
[bsConfig]="{withTimepicker: true, rangeInputFormat : 'MMMM Do YYYY, h:mm:ss a', dateInputFormat: 'MMMM Do YYYY, h:mm:ss a'}"
bsDaterangepicker>
</div>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { Component } from '@angular/core';

@Component({
// eslint-disable-next-line @angular-eslint/component-selector
selector: 'demo-datepicker-with-timepicker',
templateUrl: './with-timepicker.html'
})

export class DemoDatepickerWithTimepickerComponent {
}
7 changes: 7 additions & 0 deletions src/datepicker/base/bs-datepicker-container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ export abstract class BsDatepickerAbstractComponent {

multipleCalendars?: boolean;

isRangePicker?: boolean;
withTimepicker?: boolean;

set minDate(value: Date|undefined) {
this._effects?.setMinDate(value);
}
Expand Down Expand Up @@ -84,6 +87,7 @@ export abstract class BsDatepickerAbstractComponent {
return this._daysCalendar$;
}

selectedTime!: Observable<Date[]|undefined>;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

make it with optional type
enforce non nullable is no go


// todo: valorkin fix
// eslint-disable-next-line @typescript-eslint/no-unused-vars,@typescript-eslint/no-empty-function
Expand All @@ -104,6 +108,9 @@ export abstract class BsDatepickerAbstractComponent {
// eslint-disable-next-line
yearHoverHandler(event: CellHoverEvent): void {}

// eslint-disable-next-line
timeSelectHandler(date: Date, index: number): void {}

// eslint-disable-next-line
daySelectHandler(day: DayViewModel): void {}

Expand Down
2 changes: 1 addition & 1 deletion src/datepicker/bs-datepicker-inline.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ export class BsDatepickerInlineDirective implements OnInit, OnDestroy, OnChanges
return;
}

if (!this._bsValue && value) {
if (!this._bsValue && value && !this._config.withTimepicker) {
const now = new Date();

value.setMilliseconds(now.getMilliseconds());
Expand Down
2 changes: 1 addition & 1 deletion src/datepicker/bs-datepicker.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ export class BsDatepickerDirective implements OnInit, OnDestroy, OnChanges, Afte
return;
}

if (!this._bsValue && value) {
if (!this._bsValue && value && !this._config.withTimepicker) {
const now = new Date();

value.setMilliseconds(now.getMilliseconds());
Expand Down
5 changes: 5 additions & 0 deletions src/datepicker/bs-datepicker.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,4 +174,9 @@ export class BsDatepickerConfig implements DatepickerRenderOptions {
* Label for 'custom range' button
*/
customRangeButtonLabel = 'Custom Range';

/**
* Shows timepicker under datepicker
*/
withTimepicker = false;
}
4 changes: 3 additions & 1 deletion src/datepicker/bs-datepicker.module.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { CommonModule } from '@angular/common';
import { ModuleWithProviders, NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { ComponentLoaderFactory } from 'ngx-bootstrap/component-loader';
import { PositioningService } from 'ngx-bootstrap/positioning';

import { TooltipModule } from 'ngx-bootstrap/tooltip';
import { TimepickerModule } from 'ngx-bootstrap/timepicker';

import { BsDatepickerInputDirective } from './bs-datepicker-input.directive';
import { BsDatepickerDirective } from './bs-datepicker.component';
Expand Down Expand Up @@ -35,7 +37,7 @@ import { BsTimepickerViewComponent } from './themes/bs/bs-timepicker-view.compon
import { BsYearsCalendarViewComponent } from './themes/bs/bs-years-calendar-view.component';

@NgModule({
imports: [CommonModule, TooltipModule],
imports: [CommonModule, TooltipModule, FormsModule, TimepickerModule.forRoot()],
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

injecting forms here seem is a bit too much, you can do it without it
plus you should not use .forRoot() here

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.forRoot() is removed. And i added TimepickerActions in providers. Or there will be errors

declarations: [
BsCalendarLayoutComponent,
BsCurrentDateViewComponent,
Expand Down
5 changes: 5 additions & 0 deletions src/datepicker/bs-datepicker.scss
Original file line number Diff line number Diff line change
Expand Up @@ -427,6 +427,11 @@
}
}

.bs-timepicker-in-datepicker-container {
display: flex;
justify-content: space-around;
}

/*.bs-datepicker-custom-range */
&-custom-range {
padding: 15px;
Expand Down
8 changes: 8 additions & 0 deletions src/datepicker/reducer/bs-datepicker.actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export class BsDatepickerActions {
static readonly SET_DATE_CUSTOM_CLASSES = '[datepicker] set date custom classes';
static readonly SET_DATE_TOOLTIP_TEXTS = '[datepicker] set date tooltip texts';
static readonly SET_LOCALE = '[datepicker] set datepicker locale';
static readonly SELECT_TIME = '[datepicker] select time';

static readonly SELECT_RANGE = '[daterangepicker] select dates range';

Expand All @@ -53,6 +54,13 @@ export class BsDatepickerActions {
};
}

selectTime(date: Date, index: number): Action {
return {
type: BsDatepickerActions.SELECT_TIME,
payload: { date, index },
};
}

changeViewMode(event: BsDatepickerViewMode): Action {
return {
type: BsDatepickerActions.CHANGE_VIEWMODE,
Expand Down
3 changes: 3 additions & 0 deletions src/datepicker/reducer/bs-datepicker.effects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,9 @@ export class BsDatepickerEffects {
return this;
}

container.selectedTime = this._store.select(state => state.selectedTime)
.pipe(filter(times => !!times));

container.daysCalendar$ = this._store.select(state => state.flaggedMonths)
.pipe(filter(months => !!months));

Expand Down
27 changes: 27 additions & 0 deletions src/datepicker/reducer/bs-datepicker.reducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,14 @@ export function bsDatepickerReducer(state: BsDatepickerState = initialDatepicker
view: state.view
};

const _time = (state.selectedTime || [])[0];
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

move this code to a helper function copyTime
const _time = (state.selectedTime || [])[0]; this is a hack around
property access is faster and memory cheaper than creating empty array each time for no good reason

    if (state.selectedTime && state.selectedTime[0]) {
        const _time = state.selectedTime[0];
        newState.selectedDate.setHours(_time.getHours());
        newState.selectedDate.setMinutes(_time.getMinutes());
        newState.selectedDate.setSeconds(_time.getSeconds());
        newState.selectedDate.setMilliseconds(_time.getMilliseconds());
      }

Copy link
Copy Markdown
Contributor Author

@ErikYu ErikYu Sep 13, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added one util function

if (_time) {
newState.selectedDate.setHours(_time.getHours());
newState.selectedDate.setMinutes(_time.getMinutes());
newState.selectedDate.setSeconds(_time.getSeconds());
newState.selectedDate.setMilliseconds(_time.getMilliseconds());
}

const mode = state.view.mode;
const _date = action.payload || state.view.date;
const date = getViewDate(_date, state.minDate, state.maxDate);
Expand All @@ -97,6 +105,13 @@ export function bsDatepickerReducer(state: BsDatepickerState = initialDatepicker
return Object.assign({}, state, newState);
}

case BsDatepickerActions.SELECT_TIME: {
const {date, index} = action.payload;
const selectedTime = [...state.selectedTime || []] ;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do proper checks and not || [] hacks please
you will get selectedTime is not iterable if it will be undefined

selectedTime[index] = date;
return Object.assign({}, state, { selectedTime });
}

case BsDatepickerActions.SET_OPTIONS: {
if (!state.view) {
return state;
Expand All @@ -115,11 +130,13 @@ export function bsDatepickerReducer(state: BsDatepickerState = initialDatepicker
// if new value is array we work with date range
if (isArray(newState.value)) {
newState.selectedRange = newState.value;
newState.selectedTime = newState.value.map((i: Date) => i);
}

// if new value is a date -> datepicker
if (newState.value instanceof Date) {
newState.selectedDate = newState.value;
newState.selectedTime = [newState.value];
}

// provided value is not supported :)
Expand All @@ -140,6 +157,16 @@ export function bsDatepickerReducer(state: BsDatepickerState = initialDatepicker
view: state.view
};

newState.selectedRange.forEach((dte: Date, index: number) => {
const _time = (state.selectedTime || [])[index];
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see above about copyTime

if (_time) {
dte.setHours(_time.getHours());
dte.setMinutes(_time.getMinutes());
dte.setSeconds(_time.getSeconds());
dte.setMilliseconds(_time.getMilliseconds());
}
});

const mode = state.view.mode;
const _date = action.payload && action.payload[0] || state.view.date;
const date = getViewDate(_date, state.minDate, state.maxDate);
Expand Down
3 changes: 3 additions & 0 deletions src/datepicker/reducer/bs-datepicker.state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ export class BsDatepickerState
selectedDate?: Date;
// daterange picker
selectedRange?: Date[];
// time picker
selectedTime?: Date[];

// initial date of calendar, today by default
view?: BsDatepickerViewState;
Expand Down Expand Up @@ -88,6 +90,7 @@ export const initialDatepickerState: BsDatepickerState = Object.assign(
locale: 'en',
view: _initialView,
selectedRange: [],
selectedTime: [],
monthViewOptions: defaultMonthOptions
}
);
11 changes: 11 additions & 0 deletions src/datepicker/testing/bs-datepicker.reducer.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,4 +73,15 @@ describe('BsDatepickerReducer.', () => {
expect(reducer.view.mode).toEqual('day');
expect((reducer.monthsModel.pop().month.toISOString() === state.flaggedMonths[1].month.toISOString())).toBeFalsy();
});

it('selectedTime: the second selectedTime should be updated ', () => {
const state = initialDatepickerState;
const action: Action = {
type: BsDatepickerActions.SELECT_TIME,
payload: { date: new Date(1630298176000), index: 1 }
};
const reducer = bsDatepickerReducer(state, action);
expect(reducer.selectedTime.length).toEqual(2);
expect(reducer.selectedTime[1].getTime()).toEqual(1630298176000);
});
});
22 changes: 22 additions & 0 deletions src/datepicker/testing/bs-datepicker.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -268,4 +268,26 @@ describe('datepicker:', () => {
expect(date).toBe(undefined);
}).unsubscribe();
});

it('should display one timepicker when withTimepicker is true', () => {
const datepickerDirective = getDatepickerDirective(fixture);
datepickerDirective.bsConfig = {
withTimepicker: true
};
showDatepicker(fixture);
const timepickerZone = document.querySelector('.bs-timepicker-in-datepicker-container');
const timepickers = document.querySelectorAll('timepicker');
expect(timepickerZone).toBeTruthy();
expect(timepickers.length).toEqual(1);
});

it('should not display timepicker when withTimepicker is true', () => {
const datepickerDirective = getDatepickerDirective(fixture);
datepickerDirective.bsConfig = {
withTimepicker: false
};
showDatepicker(fixture);
const timepickerZone = document.querySelector('.bs-timepicker-in-datepicker-container');
expect(timepickerZone).not.toBeTruthy();
});
});
21 changes: 21 additions & 0 deletions src/datepicker/testing/bs-daterangepicker.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,27 @@ describe('daterangepicker:', () => {
fixture.detectChanges();
});

it('should display timepicker when withTimepicker is true', () => {
const directive = getDaterangepickerDirective(fixture);
directive.bsConfig = {
withTimepicker: true
};
showDatepicker(fixture);
const timepickerZone = document.querySelector('.bs-timepicker-in-datepicker-container');
const timepickers = document.querySelectorAll('timepicker');
expect(timepickerZone).toBeTruthy();
expect(timepickers.length).toEqual(2);
});

it('should hide timepicker when withTimepicker is false', () => {
const directive = getDaterangepickerDirective(fixture);
directive.bsConfig = {
withTimepicker: false
};
showDatepicker(fixture);
const timepickerZone = document.querySelector('.bs-timepicker-in-datepicker-container');
expect(timepickerZone).not.toBeTruthy();
});

it('should display daterangepicker on show', () => {
const datepicker = showDatepicker(fixture);
Expand Down
6 changes: 6 additions & 0 deletions src/datepicker/themes/bs/bs-datepicker-container.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export class BsDatepickerContainerComponent extends BsDatepickerAbstractComponen

valueChange: EventEmitter<Date> = new EventEmitter<Date>();
animationState = 'void';
isRangePicker = false;

_subs: Subscription[] = [];
constructor(
Expand Down Expand Up @@ -81,6 +82,7 @@ export class BsDatepickerContainerComponent extends BsDatepickerAbstractComponen
this.clearBtnLbl = this._config.clearButtonLabel;
this.clearPos = this._config.clearPosition;
this.customRangeBtnLbl = this._config.customRangeButtonLabel;
this.withTimepicker = this._config.withTimepicker;
this._effects?.init(this._store)
// intial state options
.setOptions(this._config)
Expand Down Expand Up @@ -111,6 +113,10 @@ export class BsDatepickerContainerComponent extends BsDatepickerAbstractComponen
this._positionService.enable();
}

timeSelectHandler(date: Date, index: number) {
this._store.dispatch(this._actions.selectTime(date, index));
}

daySelectHandler(day: DayViewModel): void {
if (!day) {
return;
Expand Down
Loading