bulma-extensions
Version:
Set of extensions for Bulma.io CSS Framework
782 lines (701 loc) • 24.5 kB
JavaScript
import * as utils from './utils/index';
import * as types from './utils/type';
import * as dateFns from 'date-fns';
import EventEmitter from './utils/events';
import datePicker from './datePicker';
import timePicker from './timePicker';
import defaultOptions from './defaultOptions';
import template from './templates';
import templateselection from './templates/header';
import templateFooter from './templates/footer';
export default class bulmaCalendar extends EventEmitter {
constructor(selector, options = {}) {
super();
this.element = types.isString(selector) ? document.querySelector(selector) : selector;
// An invalid selector or non-DOM node has been provided.
if (!this.element) {
throw new Error('An invalid selector or non-DOM node has been provided.');
}
this._clickEvents = ['click', 'touch'];
this._supportsPassive = utils.detectSupportsPassive();
// Use Element dataset values to override options
const elementConfig = this.element.dataset ? Object.keys(this.element.dataset)
.filter(key => Object.keys(defaultOptions).includes(key))
.reduce((obj, key) => {
return {
...obj,
[key]: this.element.dataset[key]
};
}, {}) : {};
// Set default options - dataset attributes are master
this.options = {
...defaultOptions,
...options,
...elementConfig
};
switch (this.element.getAttribute('type')) {
case 'date':
this.options.type = 'date';
break;
case 'time':
this.options.type = 'time';
break;
default:
this.options.type = 'datetime';
break;
}
this._id = utils.uuid('datetimePicker');
this.onToggleDateTimePicker = this.onToggleDateTimePicker.bind(this);
this.onCloseDateTimePicker = this.onCloseDateTimePicker.bind(this);
this.onDocumentClickDateTimePicker = this.onDocumentClickDateTimePicker.bind(this);
this.onValidateClickDateTimePicker = this.onValidateClickDateTimePicker.bind(this);
this.onTodayClickDateTimePicker = this.onTodayClickDateTimePicker.bind(this);
this.onClearClickDateTimePicker = this.onClearClickDateTimePicker.bind(this);
this.onCancelClickDateTimePicker = this.onCancelClickDateTimePicker.bind(this);
this.onSelectDateTimePicker = this.onSelectDateTimePicker.bind(this);
// Initiate plugin
this._init();
}
/**
* Initiate all DOM element containing datePicker class
* @method
* @return {Array} Array of all datePicker instances
*/
static attach(selector = 'input[type="date"]', options = {}) {
let instances = new Array();
const elements = types.isString(selector) ? document.querySelectorAll(selector) : Array.isArray(selector) ? selector : [selector];
[].forEach.call(elements, element => {
if (typeof element[this.constructor.name] === 'undefined') {
const instance = new bulmaCalendar(element, options);
element.bulmaCalendar = instance;
instances.push(instance);
} else {
instances.push(element[this.constructor.name]);
}
});
return instances;
}
/****************************************************
* *
* GETTERS and SETTERS *
* *
****************************************************/
/**
* Get id of current instance
*/
get id() {
return this._id;
}
// Set language
set lang(lang = 'en') {
try {
this._locale = require('date-fns/locale/' + lang);
} catch (e) {
lang = 'en';
this._locale = require('date-fns/locale/' + lang);
} finally {
this._lang = lang;
this.datePicker.lang = lang;
this.timePicker.lang = lang;
return this;
}
}
// Get current language
get lang() {
return this._lang;
}
get locale() {
return this._locale;
}
// Set format (set to yyyy-mm-dd HH:mm:ss by default)
set format(format) {
this._format = format;
return this;
}
// Get format
get format() {
return this._format;
}
/**
* * Date setter and getter
*/
set date(date = null) {
this.datePicker.date = date;
return this;
}
// Get date object
get date() {
return this.datePicker.date;
}
set startDate(date = undefined) {
this.datePicker.start = date;
return this;
}
get startDate() {
return this.datePicker.start;
}
set endDate(date = undefined) {
this.datePicker.end = date;
return this;
}
get endDate() {
return this.datePicker.end;
}
/**
* minDate getter and setters
*/
set minDate(date = undefined) {
this.datePicker.minDate = date;
return this;
}
// Get minDate
get minDate() {
return this.datePicker.minDate;
}
// Set maxDate
set maxDate(date = undefined) {
this.datePicker.maxDate = date;
return this;
}
// Get maxDate
get maxDate() {
return this.datePicker.maxDate;
}
// Set dateFormat (set to yyyy-mm-dd by default)
set dateFormat(dateFormat) {
this.datePicker.dateFormat = dateFormat;
return this;
}
// Get dateFormat
get dateFormat() {
return this.datePicker.dateFormat;
}
/**
* * Time setter and getter
*/
set time(time = null) {
this.timePicker.time = time;
return this;
}
// Get time object
get time() {
return this.timePicker.time;
}
set startTime(time = undefined) {
this.timePicker.start = time;
return this;
}
get startTime() {
return this.timePicker.start;
}
set endTime(time = undefined) {
this.timePicker.end = time;
return this;
}
get endTime() {
return this.timePicker.end;
}
/**
* minTime getter and setters
*/
set minTime(time = undefined) {
this.timePicker.minTime = time;
return this;
}
// Get minTime
get minTime() {
return this.timePicker.minTime;
}
// Set maxTime
set maxTime(time = undefined) {
this.timePicker.maxTime = time;
return this;
}
// Get maxTime
get maxTime() {
return this.timePicker.maxTime;
}
// Set timeFormat (set to HH:MM:SS by default)
set timeFormat(timeFormat) {
this.timePicker.dateFormat = timeFormat;
return this;
}
// Get timeFormat
get timeFormat() {
return this.timePicker.timeFormat;
}
/****************************************************
* *
* EVENTS FUNCTIONS *
* *
****************************************************/
onSelectDateTimePicker(e) {
this.refresh();
this.save();
if (e.type === 'select' && this.options.closeOnSelect && this.options.displayMode !== 'inline') {
this.hide();
}
this.emit(e.type, this);
}
onDocumentClickDateTimePicker(e) {
if (!this._supportsPassive) {
e.preventDefault();
}
e.stopPropagation();
// Check is e.target not within datepicker element
const target = e.target || e.srcElement;
if (!this._ui.wrapper.contains(target) && this.options.displayMode !== 'inline' && this._open) {
this.onCloseDateTimePicker(e);
}
}
onToggleDateTimePicker(e) {
if (!this._supportsPassive) {
e.preventDefault();
}
e.stopPropagation();
if (this._open) {
this.hide();
} else {
this.show();
}
}
onValidateClickDateTimePicker(e) {
if (!this._supportsPassive) {
e.preventDefault();
}
e.stopPropagation();
this.save();
this.emit('select', this);
if (this.options.displayMode !== 'inline') {
this.onCloseDateTimePicker(e);
}
}
onTodayClickDateTimePicker(e) {
if (!this._supportsPassive) {
e.preventDefault();
}
e.stopPropagation();
this.datePicker.value(new Date());
this.datePicker.refresh();
this.timePicker.value(new Date());
this.timePicker.refresh();
// TODO: check if closeOnSelect
this.save();
}
onClearClickDateTimePicker(e) {
if (!this._supportsPassive) {
e.preventDefault();
}
e.stopPropagation();
this.clear();
}
onCancelClickDateTimePicker(e) {
if (!this._supportsPassive) {
e.preventDefault();
}
e.stopPropagation();
if (this._snapshots.length) {
this.datePicker = this._snapshots[0].datePicker;
this.timePicker = this._snapshots[0].timePicker;
}
this.save();
if (this.options.displayMode !== 'inline') {
this.onCloseDateTimePicker(e);
}
}
onCloseDateTimePicker(e) {
if (!this._supportsPassive) {
e.preventDefault();
}
e.stopPropagation();
this.hide();
}
/****************************************************
* *
* PUBLIC FUNCTIONS *
* *
****************************************************/
isRange() {
return this.options.isRange;
}
/**
* Returns true if datetime picker is open, otherwise false.
* @method isOpen
* @return {boolean}
*/
isOpen() {
return this._open;
}
/**
* Get / Set datetimePicker value
* @param {*} date
*/
value(value = null) {
if (value) {
this.datePicker.value(value);
this.timePicker.value(value);
} else {
let string = '';
switch (this.options.type) {
case 'date':
string = this.datePicker.value();
break;
case 'time':
string = this.timePicker.value();
break;
case 'datetime':
let start = this.datePicker.start ? dateFns.getTime(dateFns.addMinutes(dateFns.addHours(this.datePicker.start, dateFns.getHours(this.timePicker.start)), dateFns.getMinutes(this.timePicker.start))) : undefined;
let end = this.datePicker.end ? dateFns.getTime(this.datePicker.end) : undefined;
if (end && this.options.isRange) {
end = dateFns.getTime(dateFns.addMinutes(dateFns.addHours(this.datePicker.end, dateFns.getHours(this.timePicker.end)), dateFns.getMinutes(this.timePicker.end)));
}
string = start ? dateFns.format(new Date(start), this.format, {
locale: this.locale
}) : '';
if (end) {
string += ` - ${end ? dateFns.format(new Date(end), this.format, { locale: this.locale }) : ''}`;
}
break;
}
return string;
}
}
refresh() {
this._ui.header.start.day.innerHTML = this.datePicker.start ? dateFns.format(this.datePicker.start, 'DD', {
locale: this.locale
}) : '--';
this._ui.header.start.month.innerHTML = this.datePicker.start ? dateFns.format(this.datePicker.start, 'MMMM YYYY', {
locale: this.locale
}) : '';
if (this.datePicker.start) {
this._ui.header.start.weekday.classList.remove('is-hidden');
this._ui.header.start.weekday.innerHTML = this.datePicker.start ? dateFns.format(this.datePicker.start, 'dddd', {
locale: this.locale
}) : '';
} else {
this._ui.header.start.weekday.classList.add('is-hidden');
}
if (this._ui.header.start.hour) {
this._ui.header.start.hour.innerHTML = this.timePicker.start ? dateFns.format(this.timePicker.start, 'HH:mm', {
locale: this.locale
}) : '--:--';
}
if (this._ui.header.end) {
this._ui.header.end.day.innerHTML = this.options.isRange && this.datePicker.end ? dateFns.format(this.datePicker.end, 'DD', {
locale: this.locale
}) : '--';
this._ui.header.end.month.innerHTML = this.options.isRange && this.datePicker.end ? dateFns.format(this.datePicker.end, 'MMMM YYYY', {
locale: this.locale
}) : '';
if (this.datePicker.end) {
this._ui.header.end.weekday.classList.remove('is-hidden');
this._ui.header.end.weekday.innerHTML = this.datePicker.end ? dateFns.format(this.datePicker.end, 'dddd', {
locale: this.locale
}) : '';
} else {
this._ui.header.end.weekday.classList.add('is-hidden');
}
if (this._ui.header.end && this._ui.header.end.hour) {
this._ui.header.end.hour.innerHTML = this.timePicker.end ? dateFns.format(this.timePicker.end, 'HH:mm', {
locale: this.locale
}) : '--:--';
}
}
this.emit('refresh', this);
}
clear() {
this.datePicker.clear();
this.timePicker.clear();
this.refresh();
this.element.value = '';
this._ui.dummy.dummy_1.value = '';
if (this._ui.dummy.dummy_2) {
this._ui.dummy.dummy_2.value = '';
}
this.emit('clear', this);
}
/**
* Show datePicker HTML Component
* @method show
* @return {void}
*/
show() {
this._snapshots = [];
this.snapshot();
if (this.element.value) {
this.datePicker.value(this.element.value);
this.timePicker.value(this.element.value);
}
this.datePicker.show();
this.timePicker.hide();
if (this._ui.modal) {
this._ui.modal.classList.add('is-active');
}
this._ui.wrapper.classList.add('is-active');
this._open = true;
this.emit('show', this);
}
/**
* Hide datePicker HTML Component
* @method hide
* @return {void}
*/
hide() {
this._open = false;
this._focus = false;
if (this._ui.modal) {
this._ui.modal.classList.remove('is-active');
}
this._ui.wrapper.classList.remove('is-active');
this.emit('hide', this);
}
// Set element value to datetime selected based on format
save() {
const date = this.value();
const [start, end] = date.split(' - ');
this.element.value = date;
this._ui.dummy.dummy_1.value = start ? start : '';
if (this._ui.dummy.dummy_2) {
this._ui.dummy.dummy_2.value = end ? end : '';
}
}
snapshot() {
// this._snapshots.push([
// ...this.datePicker,
// ...this.timePicker
// ]);
}
/**
* Destroy datePicker
* @method destroy
* @return {[type]} [description]
*/
destroy() {
this._ui.wrapper.remove();
}
/****************************************************
* *
* PRIVATE FUNCTIONS *
* *
****************************************************/
/**
* Initiate plugin instance
* @method _init
* @return {datePicker} Current plugin instance
*/
_init() {
this._open = false;
this._snapshots = []; // Use to revert selection
// Set component type (date / time / datetime)
this.options.type = (['date', 'time', 'datetime'].indexOf(this.element.getAttribute('type').toLowerCase()) > -1) ? this.element.getAttribute('type').toLowerCase() : this.options.type;
this._type = (['date', 'time', 'datetime'].indexOf(this.options.type.toLowerCase()) > -1) ? this.options.type.toLowerCase() : 'date';
// Change element type to prevent browser default type="date" behavior
this.element.setAttribute('type', 'text');
this.datePicker = new datePicker({
...this.options,
lang: this.lang
});
this.timePicker = new timePicker({
...this.options,
lang: this.lang
});
if (this.element.value) {
this.datePicker.value(this.element.value);
this.timePicker.value(this.element.value);
}
this.lang = this.options.lang;
// Set export format based on component type
this.format = this._type === 'date' ? this.options.dateFormat : (this._type === 'time' ? this.options.timeFormat : `${this.options.dateFormat} ${this.options.timeFormat}`);
// Force dialog display mode on mobile devices
if (this.options.displayMode === 'default' && window.matchMedia('screen and (max-width: 768px)').matches) {
this.options.displayMode = 'dialog';
}
if (window.matchMedia('screen and (max-width: 768px)').matches) {
if (this.options.displayMode === 'default') {
this.options.displayMode = 'dialog';
}
this.options.displayDual = false;
}
this._build();
this._bindEvents();
this.save();
if (types.isFunction(this.options.onReady)) {
this.on('ready', this.options.onReady);
}
this.emit('ready', this);
}
/**
* Build datePicker HTML component and append it to the DOM
* @method _build
* @return {datePicker} Current plugin instance
*/
_build() {
const headerNode = document.createRange().createContextualFragment(templateselection({
...this.options,
type: this._type,
datePicker: this.options.type !== 'time',
timePicker: this.options.type !== 'date'
}));
const footerNode = document.createRange().createContextualFragment(templateFooter(this.options));
const datetimePickerNode = document.createRange().createContextualFragment(template({
...this.options,
id: this.id
}));
// Save pointer to each datePicker element for later use
this._ui = {
modal: datetimePickerNode.querySelector('.modal'),
wrapper: datetimePickerNode.querySelector('.datetimepicker'),
container: datetimePickerNode.querySelector('.datetimepicker-container'),
dummy: {
container: datetimePickerNode.querySelector('.datetimepicker-dummy'),
wrapper: datetimePickerNode.querySelector('.datetimepicker-dummy-wrapper'),
dummy_1: datetimePickerNode.querySelector('.datetimepicker-dummy .datetimepicker-dummy-input:nth-child(1)'),
dummy_2: datetimePickerNode.querySelector('.datetimepicker-dummy .datetimepicker-dummy-input:nth-child(2)'),
clear: datetimePickerNode.querySelector('.datetimepicker-dummy .datetimepicker-clear-button')
},
calendar: datetimePickerNode.querySelector('.datetimepicker'),
overlay: this.options.displayMode === 'dialog' ? {
background: datetimePickerNode.querySelector('.modal-background'),
close: datetimePickerNode.querySelector('.modal-close')
} : undefined,
header: {
container: headerNode.querySelector('.datetimepicker-header'),
start: {
container: headerNode.querySelector('.datetimepicker-selection-start'),
day: headerNode.querySelector('.datetimepicker-selection-start .datetimepicker-selection-day'),
month: headerNode.querySelector('.datetimepicker-selection-start .datetimepicker-selection-month'),
weekday: headerNode.querySelector('.datetimepicker-selection-start .datetimepicker-selection-weekday'),
hour: headerNode.querySelector('.datetimepicker-selection-start .datetimepicker-selection-hour'),
empty: headerNode.querySelector('.datetimepicker-selection-start .empty')
},
end: this.options.isRange ? {
container: headerNode.querySelector('.datetimepicker-selection-end'),
day: headerNode.querySelector('.datetimepicker-selection-end .datetimepicker-selection-day'),
month: headerNode.querySelector('.datetimepicker-selection-end .datetimepicker-selection-month'),
weekday: headerNode.querySelector('.datetimepicker-selection-end .datetimepicker-selection-weekday'),
hour: headerNode.querySelector('.datetimepicker-selection-end .datetimepicker-selection-hour'),
empty: headerNode.querySelector('.datetimepicker-selection-start .empty')
} : undefined
},
footer: {
container: footerNode.querySelector('.datetimepicker-footer'),
validate: footerNode.querySelector('.datetimepicker-footer-validate'),
today: footerNode.querySelector('.datetimepicker-footer-today'),
clear: footerNode.querySelector('.datetimepicker-footer-clear'),
cancel: footerNode.querySelector('.datetimepicker-footer-cancel'),
}
};
if (!this.options.showFooter) {
this._ui.footer.container.classList.add('is-hidden');
}
if (!this.options.showTodayButton) {
this._ui.footer.today.classList.add('is-hidden');
}
if (!this.options.showClearButton) {
this._ui.footer.clear.classList.add('is-hidden');
}
if (this.options.closeOnSelect && this._ui.footer.validate) {
this._ui.footer.validate.classList.add('is-hidden');
}
this._ui.container.appendChild(headerNode);
switch (this._type) {
case 'date':
this._ui.container.appendChild(this.datePicker.render());
break;
case 'time':
this._ui.container.appendChild(this.timePicker.render());
if (this._ui.footer.validate) {
this._ui.footer.validate.classList.remove('is-hidden');
}
if (this._ui.footer.today) {
this._ui.footer.today.classList.add('is-hidden');
}
break;
case 'datetime':
this.options.closeOnSelect = false;
if (this._ui.footer.validate) {
this._ui.footer.validate.classList.remove('is-hidden');
}
this._ui.container.appendChild(this.datePicker.render());
this._ui.container.appendChild(this.timePicker.render());
break;
}
this._ui.wrapper.appendChild(footerNode);
this._ui.wrapper.classList.add(`is-${this.options.color}`);
this._ui.dummy.container.classList.add(`is-${this.options.color}`);
// Add datepicker HTML element to Document Body
this.element.parentNode.insertBefore(datetimePickerNode, this.element.nextSibling);
this._ui.dummy.wrapper.appendChild(this.element);
this.element.classList.add('is-hidden');
// this.element.style.visibility = 'hidden';
// this.element.style.position = 'absolute';
if (this.options.displayMode === 'inline') {
this._ui.wrapper.classList.add('is-active');
} else {
this._ui.dummy.container.classList.remove('is-hidden');
this._ui.wrapper.style.position = 'absolute';
this._ui.wrapper.classList.add('is-datetimepicker-default');
}
this.refresh();
}
/**
* Bind all events
* @method _bindEvents
* @return {void}
*/
_bindEvents() {
this._clickEvents.forEach(clickEvent => {
document.body.addEventListener(clickEvent, this.onDocumentClickDateTimePicker);
});
this.datePicker.on('select', this.onSelectDateTimePicker);
this.datePicker.on('select:start', this.onSelectDateTimePicker);
this.datePicker.on('select:end', this.onSelectDateTimePicker);
this.timePicker.on('select', this.onSelectDateTimePicker);
this.timePicker.on('select:start', this.onSelectDateTimePicker);
this.timePicker.on('select:end', this.onSelectDateTimePicker);
// Bind event to element in order to display/hide datePicker on click
if (this.options.toggleOnInputClick === true) {
this._clickEvents.forEach(clickEvent => {
this._ui.dummy.wrapper.addEventListener(clickEvent, this.onToggleDateTimePicker);
this.element.addEventListener(clickEvent, this.onToggleDateTimePicker);
});
}
if (this.options.displayMode === 'dialog' && this._ui.overlay) {
// Bind close event on Close button
if (this._ui.overlay.close) {
this._clickEvents.forEach(clickEvent => {
this.this._ui.overlay.close.addEventListener(clickEvent, this.onCloseDateTimePicker);
});
}
// Bind close event on overlay based on options
if (this.options.closeOnOverlayClick && this._ui.overlay.background) {
this._clickEvents.forEach(clickEvent => {
this._ui.overlay.background.addEventListener(clickEvent, this.onCloseDateTimePicker);
});
}
}
if (this._ui.footer.validate) {
this._clickEvents.forEach(clickEvent => {
this._ui.footer.validate.addEventListener(clickEvent, this.onValidateClickDateTimePicker);
});
}
if (this._ui.footer.today) {
this._clickEvents.forEach(clickEvent => {
this._ui.footer.today.addEventListener(clickEvent, this.onTodayClickDateTimePicker);
});
}
if (this._ui.footer.clear) {
this._clickEvents.forEach(clickEvent => {
this._ui.footer.clear.addEventListener(clickEvent, this.onClearClickDateTimePicker);
});
}
this._clickEvents.forEach(clickEvent => {
this._ui.dummy.clear.addEventListener(clickEvent, this.onClearClickDateTimePicker);
});
if (this._ui.footer.cancel) {
this._clickEvents.forEach(clickEvent => {
this._ui.footer.cancel.addEventListener(clickEvent, this.onCancelClickDateTimePicker);
});
}
}
}