A cool angular 2+ component built with ❤︎ by Juan Camejo Alarcón
This is a datepicker calendar that lets you select a range of dates and within that range you are also able to select days when you don't want this range to be applied. For instance, if I were to create an offer, that is going to be available during a period of time, but I don't want this offer to be available on Saturdays, or the day 24th of December, this datepicker lets you select both, so within a range you will also have 'blackouts' (didn't know how to call it).
- Select the range
- Press the 'blackouts' button
- Select your blackouts
- If you want to unselect a blackout just press it again
- If you want to unselect all the blackouts press the 'clear' button
Mess with it in stackblitz: DEMO
I built this datepicker on top of the calendar already created by Bootstrap with ng-bootstrap. So the first step would be to install. ng-bootstrap
As it is done with others ng-bootstrap components create the offerdatepicker using the following code:
import {Component, ViewChild, ElementRef, Renderer2, OnInit} from '@angular/core';
import {NgbDateStruct, NgbCalendar} from '@ng-bootstrap/ng-bootstrap';
const equals = (one: NgbDateStruct, two: NgbDateStruct) =>
one && two && two.year === one.year && two.month === one.month && two.day === one.day;
const before = (one: NgbDateStruct, two: NgbDateStruct) =>
!one || !two ? false : one.year === two.year ? one.month === two.month ? one.day === two.day
? false : one.day < two.day : one.month < two.month : one.year < two.year;
const after = (one: NgbDateStruct, two: NgbDateStruct) =>
!one || !two ? false : one.year === two.year ? one.month === two.month ? one.day === two.day
? false : one.day > two.day : one.month > two.month : one.year > two.year;
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
hoveredDate: NgbDateStruct;
fromDate: NgbDateStruct;
toDate: NgbDateStruct;
blackOutDate: NgbDateStruct;
blackOutsList: Array<NgbDateStruct> = [];
blackOutModeActive: boolean = false;
@ViewChild('blackoutsDatePicker') blackoutsDatePicker: ElementRef;
constructor(calendar: NgbCalendar, private renderer: Renderer2) {
this.fromDate = calendar.getToday();
this.toDate = calendar.getNext(calendar.getToday(), 'd', 10);
}
ngOnInit() {
}
onDateSelection(date: NgbDateStruct) {
if (!this.blackOutModeActive) {
if (!this.fromDate && !this.toDate) {
this.fromDate = date;
} else if (this.fromDate && !this.toDate && after(date, this.fromDate)) {
this.toDate = date;
} else {
this.toDate = null;
this.fromDate = date;
}
} else {
if (!this.blackOutsList.length) {
this.blackOutDate = date;
this.blackOutsList.push(date);
} else {
if (!this.isInBlackOutsList(date)) {
this.blackOutDate = date;
this.blackOutsList.push(date);
} else {
const index = this.blackOutsList.indexOf(this.isInBlackOutsList(date));
if (index !== -1) this.blackOutsList.splice(index, 1);
}
}
}
}
blackoutMode() {
if (!this.blackOutModeActive) {
this.blackOutModeActive = true;
const days = this.blackoutsDatePicker.nativeElement.querySelectorAll('.ngb-dp-day');
for (const day of days) {
if (!day.childNodes[2].classList.contains('range')) {
day.style.pointerEvents = 'none';
}
}
// (Ugly programming alert)
// This function makes sure that if you click an arrow you cannot
// click days out of the selected range
for (const arrow of this.blackoutsDatePicker.nativeElement.querySelectorAll('.ngb-dp-arrow')) {
const arrowListener = this.renderer.listen(arrow, 'click', () => {
if (this.blackOutModeActive) {
const days = this.blackoutsDatePicker.nativeElement.querySelectorAll('.ngb-dp-day');
for (const day of days) {
if (!day.childNodes[2].classList.contains('range')) {
day.style.pointerEvents = 'none';
}
}
}
});
}
} else {
this.blackOutModeActive = false;
const days = this.blackoutsDatePicker.nativeElement.querySelectorAll('.ngb-dp-day');
for (const day of days) {
day.style.pointerEvents = 'auto';
}
}
}
isInBlackOutsList(date: NgbDateStruct) {
for (const blackout of this.blackOutsList) {
if (equals(date, blackout)) {
return true && blackout;
}
}
}
clearBlackoutsList() {
this.blackOutsList = [];
}
isHovered = date => this.fromDate && !this.toDate && this.hoveredDate && after(date, this.fromDate) && before(date, this.hoveredDate);
isInside = date => after(date, this.fromDate) && before(date, this.toDate);
isFrom = date => equals(date, this.fromDate);
isTo = date => equals(date, this.toDate);
// This function checks if a date is within a range
isInRange = date => this.isFrom(date) || this.isTo(date) || this.isInside(date) || this.isHovered(date);
}
<p>Example of the range selection</p>
<div #blackoutsDatePicker>
<ngb-datepicker #dp (select)="onDateSelection($event)" [displayMonths]="1" [dayTemplate]="t">
</ngb-datepicker>
<ng-template #t let-date="date" let-focused="focused">
<span class="custom-day"
[class.focused]="focused"
[class.range]="isFrom(date) || isTo(date) || isInside(date) || isHovered(date)"
[class.faded]="isHovered(date) || isInside(date)"
[class.blackOutMode]="blackOutModeActive && !isInRange(date)"
[class.blackOutHover]="blackOutModeActive && isInRange(date)"
[class.blackout]="isInBlackOutsList(date) && isInRange(date)"
(mouseenter)="hoveredDate = date"
(mouseleave)="hoveredDate = null">
{{ date.day }}
</span>
</ng-template>
</div>
<button style="font-size:70%" type="button" class="btn btn-primary btn-sm my-1"(click)="blackoutMode()">
Blackouts
</button>
<button style="font-size:70%" type="button" class="btn btn-secondary btn-sm my-1 ml-2"
(click)="clearBlackoutsList()" [class.d-none]="!blackOutModeActive">
Clear
</button>
<hr>
<pre>From: {{ fromDate | json }} </pre>
<pre>To: {{ toDate | json }} </pre>
<pre>Blackouts: {{ blackOutsList | json }} </pre>
.custom-day {
text-align: center;
padding: 0.185rem 0.25rem;
display: inline-block;
height: 2rem;
width: 2rem;
}
.custom-day.focused {
background-color: #e6e6e6;
}
.custom-day.range, .custom-day:hover {
background-color: rgb(2, 117, 216);
color: white;
}
.custom-day.faded {
background-color: rgba(2, 117, 216, 0.5);
}
.blackOutMode {
background-color: white !important;
color: white;
}
.blackOutMode:hover {
color: #f8f9fa;
}
.blackOutHover:hover {
background-color: black !important;
}
.blackout {
background-color: #B6B6B6 !important;
}