diff --git a/src/core/App.js b/src/core/App.js index a84ad50..e53e6ff 100644 --- a/src/core/App.js +++ b/src/core/App.js @@ -422,9 +422,16 @@ export class App extends Boot { static destroyOldGlobalEvents() { if (App.section) { - window.dxOff(`${App.section}*`); - document.dxOff(`${App.section}*`); - document.body.dxOff(`${App.section}*`); + + Ut.each(App.widgetStack, wtList => wtList.forEach(wt => wt.destroy() ) ); + + App.widgetStack = {}; + + Ut.resetKey(); + + window.dxOff(`.${App.section}`); + document.dxOff(`.${App.section}`); + document.body.dxOff(`.${App.section}`); } // Destroy all TinyMce Instances diff --git a/src/core/Boot.js b/src/core/Boot.js index 62c6de3..6f4f657 100644 --- a/src/core/Boot.js +++ b/src/core/Boot.js @@ -26,11 +26,15 @@ export class Boot { // Static Section Vars static vars = {}; + // Widget Stack Object + static widgetStack = {}; + static ready(callback) { dxReady(() => { Boot.lang = document.documentElement.getAttribute('lang') ?? 'en'; + Boot.isApp = document.documentElement.classList.contains('app'); import(`/js/${Boot.lang}/interface.js`).then((mod) => { @@ -100,13 +104,13 @@ export class Boot { EvtOptions.section = id; } + /** + * Add Widget To Stack + */ + static addWidget = ref => Boot.isApp && (Boot.widgetStack[ref.constructor.name] ??= []).push(ref); + /** * App Log */ - static log(err) { - - if (Boot.DEBUG) { - console.log(err); - } - } + static log = err => Boot.DEBUG && console.log(err); } \ No newline at end of file diff --git a/src/core/Render.js b/src/core/Render.js index 4e62925..d09d54b 100644 --- a/src/core/Render.js +++ b/src/core/Render.js @@ -531,10 +531,6 @@ export class Render { input.readOnly = true; } - if (field.rewrite == '1') { - input.dataset.value = ''; - } - if (field.required == '1') { input.required = true; } diff --git a/src/core/Stack.js b/src/core/Stack.js deleted file mode 100644 index bd254a4..0000000 --- a/src/core/Stack.js +++ /dev/null @@ -1,41 +0,0 @@ -export class Stack { - - static widgets = {}; - - /** - * Registers a widget instance in the global registry. - * - * @param {Object} ref - Widget instance to register. - * @returns {void} - */ - static add(ref) { - - const name = ref.constructor.name; - - Stack.widgets[name] ??= new Map(); - - // ref.ekey = crypto.randomUUID(); - - ref.ekey = Stack.widgets[name].size; - - Stack.widgets[name].set(ref.ekey, ref); - } - - /** - * Removes a widget instance from the global registry. - * - * @param {Object} ref - Widget instance to remove. - * @returns {void} - */ - static delete(ref) { - - const name = ref.constructor.name; - - if (!this.widgets[name]?.has(ref.ekey)) { - return; - } - - this.widgets[name].delete(ref.ekey); - ref.ekey = null; - } -} \ No newline at end of file diff --git a/src/utils/Ut.js b/src/utils/Ut.js index 2332e57..b4bc060 100644 --- a/src/utils/Ut.js +++ b/src/utils/Ut.js @@ -2,8 +2,11 @@ const stamp = Date.now(); export class Ut { - static EventsUID = 'Events' + stamp; - static DataUID = 'Data' + stamp; + static EventsUID = `Events_${stamp}`; + static DataUID = `Data_${stamp}`; + static WidgetUID = `Widget_${stamp}`; + + static evKey = 0; static isSet = (val) => typeof val !== 'undefined'; static isFn = (val) => typeof val === 'function'; @@ -18,6 +21,9 @@ export class Ut { static each = (obj, callback, context) => obj && Object.keys(obj).forEach(key => callback.call(context ?? this, obj[key], key) ); static tplString = (str, context) => new Function('return `' + str + '`;').call(context || {}); + + static nextKey = () => ++Ut.evKey; + static resetKey = () => Ut.evKey = 0; static extendNode(name, value) { @@ -60,7 +66,6 @@ export class Ut { moveevent : ('ontouchstart' in window) ? 'touchmove' : 'mousemove' } - /** * Redirect */ diff --git a/src/utils/dom.js b/src/utils/dom.js index 8be9a6f..92cf404 100644 --- a/src/utils/dom.js +++ b/src/utils/dom.js @@ -139,10 +139,9 @@ events.split(' ').forEach(function(evt) { - evt = evt.split('.'); - - let eName = evt[0]; - let namespace = nsPref + (evt[1] || '*'); + const i = evt.indexOf('.'); + const eName = i === -1 ? evt : evt.slice(0, i); + const namespace = nsPref + (i === -1 ? '*' : evt.slice(i + 1) ); if (!this[ Ut.EventsUID ][ eName ]) { this[ Ut.EventsUID ][ eName ] = []; @@ -169,10 +168,9 @@ events.split(' ').forEach(function(evt) { - evt = evt.split('.'); - - let eName = evt[0]; - let namespace = nsPref + (evt[1] || '*'); + const i = evt.indexOf('.'); + const eName = i === -1 ? evt : evt.slice(0, i); + const namespace = nsPref + (i === -1 ? '*' : evt.slice(i + 1) ); if (!this[ Ut.EventsUID ][ eName ]) { this[ Ut.EventsUID ][ eName ] = []; @@ -201,9 +199,9 @@ this[ Ut.EventsUID ][ eName ].push([ namespace, selector, _handler ]); - if (eName.startsWith('swipe')) { + // if (eName.startsWith('swipe')) { // new SwipeEvents(this, eName, target, _handler, opts); - } + //} this.addEventListener(eName, _handler, opts); @@ -213,58 +211,73 @@ return this; }, - /** - * Remove an event handler. - */ - off(events, selector) { + /** + * Remove an event handler. + */ + off(events, selector) { - let nsPref = (EvtOptions.nsEvent && EvtOptions.section) ? (EvtOptions.section + '.') : ''; + const target = this; - if (!this[Ut.EventsUID]) { - return this; - } + if (!target[Ut.EventsUID]) { + return target; + } - if (!events) { - this.replaceWith(this.cloneNode(true)); - return this; - } + events = events ?? [ '' ]; - events.split(' ').forEach(function(evt) { + events.split(' ').forEach(event => { - evt = evt.split('.'); + const eventToken = event.split('.'); - let eName = evt[0]; - let namespace = nsPref + (evt[1] || '*'); + let [ eventName, eventNs ] = eventToken; - let eList = this[Ut.EventsUID][eName]; + if (eventName === '') { - if (eList) { + const eventRoot = target[Ut.EventsUID]; - let i = eList.length; + Ut.each(eventRoot, (eventList, realEventName) => { + + let i = (eventList && eventList.length) || 0; + + while (i--) { + + const eventData = eventList[i]; + + if ((eventToken.length === 1 || eventNs === eventData[0] || eventData[0].startsWith(`${eventNs}.`)) && (!selector || eventData[1] === selector) ) { + target.removeEventListener(realEventName, eventData[2]); + eventList.splice(i, 1); + } + } + }); + + return; + } + + if (EvtOptions.nsEvent && EvtOptions.section) { + eventNs = `${eventNs}.${EvtOptions.section}`; + } + + const eventList = target[Ut.EventsUID][ eventName ]; + + let i = (eventList && eventList.length) || 0; while(i--) { - let et = eList[i]; + const eventData = eventList[i]; - if ((et[0] == namespace || namespace == nsPref + '*') && (!selector || (et[1] == selector)) ) { + if ((eventToken.length === 1 || eventNs === eventData[0] || eventData[0].startsWith(`${eventNs}.`)) && (!selector || eventData[1] === selector) ) { - //if (customEvents[eName]) { - // customEvents[eName].destroy.call(this, eName, et[1]); + //if (customEvents[eventName]) { + // customEvents[eventName].destroy.call(this, eventName, eventData[1]); //} + //if (eventName.startsWith('swipe')) {} - if (eName.startsWith('swipe')) { - - } - - this.removeEventListener(eName, et[2]); - - eList.splice(i, 1); + this.removeEventListener(eventName, eventData[2]); + eventList.splice(i, 1); } - } - } - }, this); + } + }); - return this; + return target; }, /** @@ -506,6 +519,7 @@ left: rect.left + win.scrollX }; }, + /** * Store / Retreive Arbitrary Data */ @@ -521,6 +535,34 @@ return this; }, + + /** + * Store / Retreive Arbitrary Widget Data + */ + widget(data) { + + if (data) { + this[ Ut.WidgetUID ] = data; + return this; + } + + return this[ Ut.WidgetUID ] || null; + }, + + /** + * Get Widget Data Value + */ + nval() { + return this[ Ut.WidgetUID ]?.getValue(); + }, + + /** + * Remove widget data + */ + removeWidget() { + delete this[ Ut.WidgetUID ]; + return this; + }, /** * Remove Arbitrary Data @@ -994,9 +1036,12 @@ continue; } - if ('value' in target.dataset) { + const widget = domUtils.widget.call(target); + + if (widget) { + + widget.restoreValue(Ut.unescape(val)); - target.dataset.value = Ut.unescape(val); target.value = Ut.unescape(data[key + '_value'] ?? val); if (domUtils.data.call(target, 'datepicker')) { diff --git a/src/utils/form-validator/Validator.js b/src/utils/form-validator/Validator.js index f3c363e..d3c76b3 100644 --- a/src/utils/form-validator/Validator.js +++ b/src/utils/form-validator/Validator.js @@ -272,8 +272,10 @@ export class Validator { let value; let displayedVal = null; - if ('value' in el.dataset) { - value = el.dataset.value; + const widget = el.dxWidget(); + + if (widget) { + value = widget.getValue(); displayedVal = el.value.trim(); } else { diff --git a/src/widgets/autocomplete/Autocomplete.js b/src/widgets/autocomplete/Autocomplete.js index ac73fff..f654cc6 100644 --- a/src/widgets/autocomplete/Autocomplete.js +++ b/src/widgets/autocomplete/Autocomplete.js @@ -3,7 +3,6 @@ import './autocomplete.css'; import { Ut } from '../../utils/Ut' import { El } from '../../utils/dom'; import { Rc } from '../../core/Rc'; -import { Stack } from '../../core/Stack'; import { Cancelable } from '../cancelable' export class Autocomplete { @@ -16,13 +15,14 @@ export class Autocomplete { this.target = target; this.reqOpts = {}; - if (this.target.dxData('autocomplete')) { + if (this.target.dxWidget() || this.target.dxData('autocomplete')) { return; } this.target.dxData('autocomplete', this); + this.target.dxWidget(this); - Stack.add(this); + this.ekey = Ut.nextKey(); this.opts = { ... { optionHeight: 32, @@ -30,8 +30,6 @@ export class Autocomplete { selectionColor: 'green' }, ... opts }; - this.target.dataset.value = ''; - this.selected = null; this.count = 0; this.index = -1; @@ -81,7 +79,7 @@ export class Autocomplete { this.ui.wrapper.dxOn('click', '.ac-option', (e, t) => this.clickOptionHandler(e, t)); // Click outside - document.body.dxOn('click.autocomplete', e => e.target != this.target && this.clearOptions()); + document.body.dxOn('click.autocomplete', e => e.target != this.target && this.clear()); } /** @@ -89,12 +87,12 @@ export class Autocomplete { */ processData(val) { - this.clearOptions(); + this.clear(); let found = true; let matchId = ''; - for (let id in this.data) { + for (let id in this.data) { let name = this.data[id]; @@ -130,14 +128,13 @@ export class Autocomplete { if (found && !matchId) { matchId = id; - this.value = matchId; } } } - let changed = this.target.dataset.value != matchId; + let changed = this.value != matchId; - this.target.dataset.value = matchId; + this.value = matchId; if (changed) { Ut.trigger(this.opts.onChange, this); @@ -217,7 +214,7 @@ export class Autocomplete { e.stopPropagation(); if (this.selected) { - this.updateVal(this.selected.dataset.id); + this.restoreValue(this.selected.dataset.id); } } } @@ -227,28 +224,31 @@ export class Autocomplete { */ clickOptionHandler(e, t) { e.stopPropagation(); - this.updateVal(t.dataset.id); + this.restoreValue(t.dataset.id); } /** * Autocomplete, Update Val */ - updateVal(id) { + restoreValue(id) { this.value = id; - if (id == this.target.dataset.value) { - this.clearOptions(); + if (id == this.value) { + this.clear(); return; } - this.target.dataset.value = id; this.target.value = this.data[id]; Ut.trigger(this.opts.onChange, this); this.target.dxTrigger('changed'); - this.clearOptions(); + this.clear(); + } + + getValue() { + return this.value; } /** @@ -282,14 +282,14 @@ export class Autocomplete { */ updateOptions(data) { - this.clearOptions(); + this.clear(); this.data = data; } /** * Autocomplete Clear Options */ - clearOptions() { + clear() { this.selected = null; this.count = 0; @@ -308,15 +308,14 @@ export class Autocomplete { this.ui.wrapper.remove(); - delete this.target.dataset.value; + this.target.dxRemoveWidget(); document.body.dxOff('click.autocomplete' + this.ekey); this.target.dxOff('input.autocomplete' + this.ekey + ' keydown.autocomplete' + this.ekey + ' click.autocomplete' + this.ekey); - Stack.delete(this); - this.target.dxRemoveData('autocomplete'); + this.target.dxRemoveWidget(); Ut.trigger(this.opts.onDestroy, this); } diff --git a/src/widgets/cancelable/Cancelable.js b/src/widgets/cancelable/Cancelable.js index b2331b1..e51769e 100644 --- a/src/widgets/cancelable/Cancelable.js +++ b/src/widgets/cancelable/Cancelable.js @@ -48,9 +48,7 @@ export class Cancelable { this.target.value = this.emptyVal; - if ('value' in this.target.dataset) { - this.target.dataset.value = ''; - } + this.target.dxWidget()?.clear(); this.iconHandler.classList.add('hide'); diff --git a/src/widgets/counter/Counter.js b/src/widgets/counter/Counter.js index cc469f9..f37d287 100644 --- a/src/widgets/counter/Counter.js +++ b/src/widgets/counter/Counter.js @@ -1,7 +1,7 @@ import './counter.css'; import { El } from '../../utils/dom'; -import { Stack } from '../../core/Stack'; +import { Ut } from '../../utils/Ut'; //////////////////////// // Chars Counter @@ -29,14 +29,14 @@ export class Counter { this.indicator = El('div', { className: 'indicator' }); this.counter = El('span', { className: 'counter' }, '0'); - this.ui = El('div', { className: 'sp-chars-counter' }, + this.el = El('div', { className: 'sp-chars-counter' }, El('div', { className: 'progress-bar' }, this.indicator), El('div', { className: 'label' }, this.counter, ' / '+ this.opts.limit) ); - this.target.after(this.ui); + this.target.after(this.el); - Stack.add(this); + this.ekey = Ut.nextKey(); this.setEvents(); } @@ -67,16 +67,15 @@ export class Counter { setEvents() { // Input - Handler - this.target.dxOn('input.counter' + this.ekey, this.update.bind(this)); + this.target.dxOn(`input.counter.${this.ekey}`, this.update.bind(this)); } /** * Counter, Destroy */ destroy() { - - this.target.dxOff('input.counter' + this.ekey); - Stack.delete(this); - this.ui.remove(); + + this.target.dxOff(`input.counter${this.ekey}`); + this.el.remove(); } } \ No newline at end of file diff --git a/src/widgets/date-picker/DatePicker.js b/src/widgets/date-picker/DatePicker.js index c2bc51d..4271864 100644 --- a/src/widgets/date-picker/DatePicker.js +++ b/src/widgets/date-picker/DatePicker.js @@ -3,40 +3,35 @@ import './date-picker.css'; import { Ut } from '../../utils/Ut' import { El, $ } from '../../utils/dom'; import { Boot } from '../../core/Boot'; -import { Stack } from '../../core/Stack'; import { Cancelable } from '../cancelable'; -import { Popover } from '../popover'; - -//////////////////// -// DatePicker -//////////////////// export class DatePicker { - /** - * DatePicker, Constructor - */ - constructor(target, opts, events) { + #hasRendered = false; + #isOpened = false; + #isInput = false; + #isToggled = false; + #isDisabled = false; + #fadeBusy = false; + + constructor(target, opts) { - const that = this; this.target = target; - this.widget = Boot.intData?.widget?.datepicker?.children; - if (!this.widget || this.target.dxData('datepicker')) { + if (!this.widget || this.target.dxWidget() || this.target.dxData('datepicker')) { return; } this.target.dxData('datepicker', this); - Stack.add(this); + this.target.dxWidget(this); + Boot.addWidget(this); this.opts = { ... { style : 'normal', disabled : false, fullMonth : true, navDisabled : false, - multiple : false, - chipbox : true, setTime : false, defaultDate : null, minDate : null, @@ -45,15 +40,10 @@ export class DatePicker { animTime : 200, dateFormat : 'dd-mm-yyyy', valueFormat : 'dd-mm-yyyy', - datesSep : ' / ', - valuesSep : '&', locales : 'ro-RO', - cancelable : true, - hiddenMode : false + render : true }, ... opts }; - this.events = events || {}; - this.conf = { weekdays: this.widget.weekdays[0].split(',').map(function(val) { return val.trim(); @@ -63,21 +53,23 @@ export class DatePicker { }) }; - this.isDisabled = this.opts.disabled; + this.#isDisabled = this.opts.disabled; this.selectedDate = null; + this.activeCellEl = null; - if (this.target.tagName.toLowerCase() == 'input') { + if (this.target.tagName.toLowerCase() === 'input') { - this.isInput = true; + this.#isInput = true; - if (this.target.type == 'text') { + if (this.target.type === 'text') { - this.isToggled = true; - this.isOpened = false; + this.#isToggled = true; + this.#isOpened = false; this.target.setAttribute('autocomplete', 'off'); + this.target.inputmode = 'none'; this.target.readOnly = true; - } + } } if (this.opts.minDate != null) { @@ -88,35 +80,56 @@ export class DatePicker { this.#setMaxDate(this.opts.maxDate); } - this.#setTodayTime(); + this.#setTodayDate(); - this.#setCurrentDate(); + this.#setDefaultDate(); - if (this.isToggled && this.opts.cancelable) { - new Cancelable(this.target, { - onClear: _ => that.clear() - }); + if (this.#isToggled) { + new Cancelable(this.target); } this.scrollBar = this.target.dxParents('.sp-scrollbar'); - this.initFlag = false; + this.#hasRendered = false; - this.#restoreProperties(); - this.#updateInputVal(); + this.ekey = Ut.nextKey(); - if (!this.isToggled) { - this.#initUi(); + if (this.#isInput) { + + const defaultVal = this.target.value.trim(); + + if (defaultVal) { + this.#parseDate(defaultVal); + this.#setValue(); + } + + this.#setTargetEvents(); } - if (this.isInput) { - this.#setTargetEvents(); + if (!this.#isToggled && this.opts.render) { + this.#render(); + } + } + + render() { + + if (this.#hasRendered) { + return; + } + + this.#render(); + } + + #setValue() { + + this.value = this.#getFormattedDate(this.opts.valueFormat); + this.displayValue = this.#getFormattedDate(this.opts.dateFormat); + + if (this.#isInput) { + this.target.value = this.displayValue; } } - /** - * DatePicker, Set min date - */ #setMinDate(minDate) { if (minDate instanceof Date) { @@ -133,9 +146,6 @@ export class DatePicker { this.minDate.setHours(0, 0, 0, 0); } - /** - * DatePicker, Set max date - */ #setMaxDate(maxDate) { if (maxDate instanceof Date) { @@ -152,103 +162,54 @@ export class DatePicker { this.maxDate.setHours(0, 0, 0, 0); } - /** - * DatePicker, Set Today Time - */ - #setTodayTime() { + #setTodayDate() { let today = new Date(); today.setHours(0, 0, 0, 0); - + this.today = today.getTime(); + this.month = today.getMonth(); + this.year = today.getFullYear(); } - /** - * DatePicker, Set Current Date - */ - #setCurrentDate() { + #setDefaultDate() { - if (this.opts.defaultDate != null) { - - if (this.opts.defaultDate instanceof Date) { - this.defaultDate = this.opts.defaultDate; - } - else if (typeof(this.opts.defaultDate) === 'number') { - this.defaultDate = new Date(); - this.defaultDate.setDate(this.defaultDate.getDate() + this.opts.defaultDate); - } - else { - this.defaultDate = new Date(this.opts.defaultDate); - } - - this.defaultDate.setHours(0, 0, 0, 0); - } - else { - this.defaultDate = new Date(); - } - - this.month = this.defaultDate.getMonth(); - this.year = this.defaultDate.getFullYear(); - } - - /** - * DatePicker, Init Ui - */ - #initUi() { - - this.#renderContainer(); - this.#renderMonth(); - - this.#setUiEvents(); - - this.initFlag = true; - } - - /** - * DatePicker, Restore Date Properties - */ - #restoreProperties(val = null) { - - this.selected = {}; - this.initialVal = val || this.target.value?.trim(); - - this.day = null; - this.hours = 0; - this.minutes = 0; - - if (!this.initialVal) { + if (!this.opts.defaultDate) { return; } - if (this.initialVal instanceof Date) { - - this.day = this.initialVal.getDate(); - this.month = this.initialVal.getMonth(); - this.year = this.initialVal.getFullYear(); - - this.hours = this.initialVal.getHours(); - this.minutes = this.initialVal.getMinutes(); - - this.selected[ this.day + '.' + this.month + '.' + this.year ] = (this.minutes && this.hours) ? [ this.hours, this.minutes ] : true; - + if (this.opts.defaultDate instanceof Date) { + this.selectedDate = this.opts.defaultDate; + } + else if (typeof(this.opts.defaultDate) === 'number') { + this.selectedDate = new Date(); + this.selectedDate.setDate(this.selectedDate.getDate() + this.opts.defaultDate); } else { - this.initialVal.split(this.opts.valuesSep).forEach(formattedDate => this.parseDate(formattedDate)); + this.#parseDate(this.opts.defaultDate); } - // Set last selected date - if (this.day && this.month && this.year) { - this.selectedDate = new Date(this.year, this.month, this.day, this.hours, this.minutes, 0, 0); - } + this.selectedDate.setHours(0, 0, 0, 0); + + this.month = this.selectedDate.getMonth(); + this.year = this.selectedDate.getFullYear(); + this.day = this.selectedDate.getDate(); } - /** - * Parse Date - */ - parseDate(formattedDate) { + #render() { + + this.#hasRendered = true; + + this.#renderContainer(); + this.renderMonth(); + + this.#setUiEvents(); + } + + #parseDate(formattedDate) { // Check for invalid format - if (/ddd|dddd|mmm|mmmm/.test(formattedDate)) { + if (!formattedDate || /ddd|dddd|mmm|mmmm/.test(formattedDate)) { return; } @@ -256,31 +217,25 @@ export class DatePicker { let inMonth = this.month; let inYear = this.year; - let inHours = this.hours; - let inMinutes = this.minutes; - const len = formattedDate.length; this.day = null; this.month = null; this.year = null; - this.hours = 0; - this.minutes = 0; + Ut.each({ dd : 'day', d: 'day', mm: 'month', m: 'month', yyyy : 'year', yy: 'year' }, (prop, type) => { - Ut.each({ dd : 'day', d: 'day', mm: 'month', m: 'month', yyyy : 'year', yy: 'year', hh: 'hours', h: 'hours', ii: 'minutes', i: 'minutes' }, (prop, type) => { - - let index = this.opts.valueFormat.indexOf(type); + const index = this.opts.valueFormat.indexOf(type); if (index != -1 && index + type.length - 1 < len && !this[prop]) { this[ prop ] = parseInt(formattedDate.substring(index, index + type.length + 1)); - if (type == 'm' || type == 'mm') { + if (type === 'm' || type === 'mm') { this.month--; } - if (type == 'yy') { + if (type === 'yy') { this.year += 2000; } } @@ -293,26 +248,18 @@ export class DatePicker { this.day = inDay; this.month = inMonth; this.year = inYear; - - this.hours = inHours; - this.minutes = inMinutes; - - return; } - this.selected[ this.day + '.' + this.month + '.' + this.year ] = (this.minutes && this.hours) ? [ this.hours, this.minutes ] : true; + this.selectedDate = new Date(this.year, this.month, this.day, 0, 0, 0, 0); } - /** - * DatePicker, Render Ui Container - */ #renderContainer() { this.ui = { container: El('div', { className: 'sp-datepicker ui-style-'+ this.opts.style + ' hide' }) } - if (this.isDisabled) { + if (this.#isDisabled) { this.ui.container.classList.add('disabled'); } @@ -340,13 +287,9 @@ export class DatePicker { this.ui.tbody = this.ui.container.appendChild(El('div', { className: 'dp-grid' })); - if (this.opts.multiple && this.opts.chipbox) { - this.ui.chipbox = this.ui.container.appendChild( El('div', { className: 'dp-chipbox sp-scrollbar hide'})); - } + if (this.#isInput) { - if (this.isInput) { - - if (this.isToggled) { + if (this.#isToggled) { this.ui.container.classList.add('toggled', 'hide'); $('body').append(this.ui.container); } @@ -366,12 +309,9 @@ export class DatePicker { popupContainer.dxData('popup')._plugins.push(this); } - Ut.trigger(this.events.init, this); + Ut.trigger(this.opts.onInit, this); } - /** - * DatePicker, Get Month Name - */ #getMonthName(month) { return (this.opts.fullMonth ? this.conf.months[month] @@ -382,27 +322,24 @@ export class DatePicker { ); } - /** - * DatePicker, Set UI Events - */ #setUiEvents() { - if (this.isToggled) { + if (this.#isToggled) { - // Click outside Handler - document.dxOn('click.datepicker' + this.ekey, () => this.hide()); + // PointerDown Handler + document.dxOn(`pointerdown.datepicker.${this.ekey}`, e => !this.target.contains(e.target) && !this.ui.container.contains(e.target) && this.hide()); // Win Resize handler - window.dxOn('resize.datepicker' + this.ekey + ' orientationchange.datepicker' + this.ekey, () => this.resize()); + window.dxOn(`resize.datepicker.${this.ekey} orientationchange.datepicker.${this.ekey}`, () => this.#resize()); // ScrollBar Resize if (this.scrollBar) { - this.scrollBar.dxOn('scroll.datepicker' + this.ekey, () => this.resize()); - window.dxOn('scroll.datepicker' + this.ekey, () => this.resize()); + this.scrollBar.dxOn(`scroll.datepicker.${this.ekey}`, () => this.#resize()); + window.dxOn(`scroll.datepicker.${this.ekey}`, () => this.#resize()); } // Escape key handler - document.dxOn('keyup.datepicker' + this.ekey, e => e.key === 'Escape' && this.hide()); + document.dxOn(`keyup.datepicker.${this.ekey}`, e => e.key === 'Escape' && this.hide()); } // Click inside handler @@ -415,43 +352,12 @@ export class DatePicker { this.ui.nextMonth.dxOn('click', () => this.nextMonth()); // Click on day - Handler - this.ui.tbody.dxOn('click', '.dp-day', (_, target) => this.#dayClicked(target)); - - if (this.opts.multiple && this.opts.chipbox) { - this.ui.chipbox.dxOn('click', '.close', (_, target) => this.removeChipDate(target.parentElement)); - } + this.ui.tbody.dxOn('click', '.dp-day', (_, target) => this.#selectDate(target)); } - /** - * DatePicker, Remove Chip Date - */ - removeChipDate(target) { - - if (this.fadeBusy) { - return; - } - - let date = target.dataset.id; - - let tokenDate = date.split('.'); - this.day = tokenDate[0]; - this.ui.tbody.dxFind('.dp-day', cell => cell.textContent == this.day && cell.classList.remove('dp-selday') ); - - target.remove(); - - this.changeDate(date, false); - - if (Ut.isEmptyObj(this.selected)) { - this.ui.chipbox.classList.add('hide'); - } - } - - /** - * DatePicker, Next Month Nav - */ nextMonth() { - if (this.isDisabled || this.fadeBusy) { + if (this.#isDisabled || this.#fadeBusy) { return; } @@ -462,15 +368,12 @@ export class DatePicker { this.month = 0; } - this.#renderMonth(); + this.renderMonth(); } - /** - * DatePicker, Prev Month - */ prevMonth() { - if (this.isDisabled || this.fadeBusy) { + if (this.#isDisabled || this.#fadeBusy) { return; } @@ -481,240 +384,39 @@ export class DatePicker { this.month = 11; } - this.#renderMonth(); + this.renderMonth(); } - /** - * DatePicker, Day Clicked Handler - */ - #dayClicked(cell) { + #selectDate(cell) { - if (this.isDisabled || this.fadeBusy) { + if (this.#isDisabled || this.#fadeBusy) { return; } - if (this.opts.multiple) { - - if (this.opts.setTime) { - - this.#setPopover(cell, - time => this.updateCellDate(cell, time), - () => this.updateCellDate(cell, false) - ); - } - else { - - if (!cell.classList.contains('dp-selday')) { - this.updateCellDate(cell, true); - return; - } - - this.#setPopover(cell, - null, - () => this.updateCellDate(cell, false) - ); - } - + this.day = Number(cell.textContent.trim()); + if (isNaN(this.day)) { return; } - this.updateCellDate(cell, true); - } + this.selectedDate = new Date(this.year, this.month, this.day, 0, 0, 0, 0); - /** - * DatePicker, Render Cell - */ - updateCellDate(cell, checked) { - - if (!this.opts.multiple) { - this.clear(); // Clear old values - } - - this.day = cell.textContent.trim(); - - this.hours = 0; - this.minutes = 0; - - if (Array.isArray(checked)) { - this.hours = checked[0]; - this.minutes = checked[1]; - } - - cell.classList.toggle('dp-selday', !!checked); - - let date = this.day + '.' + this.month + '.' + this.year; - - if (this.opts.multiple && this.opts.chipbox) { - this.renderChipbox(date, checked); - } - - this.changeDate(date, checked); - } - - /** - * DatePicker, Render Chipbox - */ - renderChipbox(date, checked) { - - if (!checked) { - - this.ui.chipbox.dxFind('.sp-chip[data-id="'+ date +'"')?.remove(); - - if (Ut.isEmptyObj(this.selected)) { - this.ui.chipbox.classList.add('hide'); - } - - return; - } - - if (this.selected[date]) { - this.ui.chipbox.dxFind('.sp-chip[data-id="'+ date +'"] span')?.dxText(this.#getFormattedDate(date, checked, this.opts.dateFormat)); - return; - } - - this.ui.chipbox.append( El('div', { className: 'sp-chip', 'data-id': date }, - El('span', {}, this.#getFormattedDate(date, checked, this.opts.dateFormat)), - El('i', { className: 'close' }, 'x' ) - )); - - this.ui.chipbox.classList.remove('hide'); - } - - /** - * DatePicker, Set Popover - */ - #setPopover(target, callbackSet, callbackUnset) { - - let that = this; - - let date = target.textContent.trim() + '.' + this.month + '.' + this.year; - - let checked = this.selected[date]; - - let hours = '0'; - let minutes = '0'; - - // Set ui form - - let form; - - if (this.opts.setTime) { - - form = El('form', { novalidate: true, className: 'sp-form' }); - - Render.makeUiElements(Ut.objVal(this.widget.form[ 'hours' ]), form); - - if (Array.isArray(checked)) { - hours = checked[0]; - minutes = checked[1]; - } - - let selectHour = form.dxFind('select[name="hour"]'), - selectMin = form.dxFind('select[name="min"]'); - - // Select Time - Handlers - selectHour.dxOn('change', (_, t) => hours = t.value); - selectMin.dxOn('change', (_, t) => minutes = t.value); - - selectHour.value = hours; - selectMin.value = minutes; - } - else { - form = El('div', {}, - El('a', { className: 'cmd-unset' }, - El('i', { className: 'fas fa-calendar-xmark fz26 color-carbon' }) - ) - ); - } - - form.dxFind('.cmd-unset').classList.toggle('hide', !checked); - - // Set popover ui - - new Popover(target, form, { closeBtn: this.opts.setTime }, - { - init: (popover, ui) => { - - that.popover = popover; - - if (this.opts.setTime) { - - ui.dxOn('click', '.cmd-set', () => { - - popover.destroy(); - that.popover = null; - - callbackSet([ hours, minutes ]); - }); - } - - ui.dxOn('click', '.cmd-unset', () => { - - popover.destroy(); - that.popover = null; - - callbackUnset(); - }); - } - }); - } - - /** - * DatePicker, Change Date - */ - changeDate(date, checked) { - - // Update selected obj - if (checked) { - this.selected[ date ] = checked; - this.selectedDate = new Date(this.year, this.month, this.day, this.hours, this.minutes, 0, 0); - } - else { - delete this.selected[ date ]; - this.selectedDate = null; - } - - if (this.isToggled) { + if (this.#isToggled) { + this.target.dxData('cancelable')?.iconHandler.classList.remove('hide'); this.hide(); } - // Update input val - if (this.isInput) { - this.#updateInputVal(); - } + this.#setValue(); - // Trigger input event + this.activeCellEl?.classList.remove('dp-selday'); + cell.classList.add('dp-selday'); + this.activeCellEl = cell; + + Ut.trigger(this.opts.onSelect, this); this.target.dxTrigger('changed'); - - // Call onSelect event at this point - Ut.trigger(this.events.onSelect, this, date, !!checked); } - /** - * DatePicker, Update Input Value (Formated) - */ - #updateInputVal() { - - // Values List - let inputVals = []; - let displayedVals = []; - - Ut.each(this.selected, (time, date) => { - inputVals.push(this.#getFormattedDate(date, time, this.opts.valueFormat)); - displayedVals.push(this.#getFormattedDate(date, time, this.opts.dateFormat)); - }); - - this.target.dataset.value = inputVals.join(this.opts.valuesSep) - this.target.value = displayedVals.join(this.opts.datesSep); - - this.target.dxData('cancelable')?.iconHandler.classList.remove('hide'); - } - - /** - * DatePicker resize hander - */ - resize() { + #resize() { let tm = null; @@ -723,62 +425,40 @@ export class DatePicker { tm = null; } - tm = setTimeout(() => this.setUiPosition(), 100); + tm = setTimeout(() => this.#reposition(), 100); } - /** - * DatePicker, Set Ui Position - */ - setUiPosition() { + #reposition() { - let scrY = 0; + const r = this.target.getBoundingClientRect(); - if (this.scrollBar) { - scrY = window.scrollY; - } + const scrollX = window.scrollX || 0; + const scrollY = window.scrollY || 0; - let left = Math.round(this.target.dxOffset().left); - - if ((this.ui.container.clientWidth + left) > window.innerWidth) { - left = 0; - } - - this.ui.container.dxCss({ - top : (Math.round(this.target.dxOffset().top) + this.target.clientHeight + 2) + scrY + 'px', - left : left + 'px', - }); + this.ui.container.style.left = Math.round(r.left + scrollX) + 'px'; + this.ui.container.style.top = Math.round(r.bottom + scrollY + 2) + 'px'; } - /** - * DatePicker, Get Date Format - */ - #getFormattedDate(rtDate, time, format) { + #getFormattedDate(format) { - time = Array.isArray(time) ? time : [ 0, 0 ]; + if (!this.selectedDate) { + return ''; + } - let tokenDate = rtDate.split('.'); - let date = new Date(tokenDate[2], tokenDate[1], tokenDate[0], time[0], time[1], 0, 0); + const fullDay = this.selectedDate.toLocaleDateString(this.opts.locales, { weekday: 'long' }); + const shortDay = this.selectedDate.toLocaleDateString(this.opts.locales, { weekday: 'short' }); + const day = this.selectedDate.getDate().toString(); + const leadDay = day.padStart(2, '0'); - let hours = date.getHours().toString(); - let leadHours = hours.padStart(2, '0'); + const fullMonth = this.selectedDate.toLocaleString(this.opts.locales, { month: 'long' }); + const shortMonth = this.selectedDate.toLocaleString(this.opts.locales, { month: 'short' }); + const month = (this.selectedDate.getMonth() + 1).toString(); + const leadMonth = month.padStart(2, '0'); - let minutes = date.getMinutes().toString(); - let leadMinutes = minutes.padStart(2, '0'); + const fullYear = this.selectedDate.getFullYear().toString() + const year = fullYear.substring(2); - let fullDay = date.toLocaleDateString(this.opts.locales, { weekday: 'long' }); - let shortDay = date.toLocaleDateString(this.opts.locales, { weekday: 'short' }); - let day = date.getDate().toString(); - let leadDay = day.padStart(2, '0'); - - let fullMonth = date.toLocaleString(this.opts.locales, { month: 'long' }); - let shortMonth = date.toLocaleString(this.opts.locales, { month: 'short' }); - let month = (date.getMonth() + 1).toString(); - let leadMonth = month.padStart(2, '0'); - - let fullYear = date.getFullYear().toString() - let year = fullYear.substring(2); - - let formatted = format + const formatted = format .replace(/dddd/gi, fullDay) .replace(/ddd/gi, shortDay) .replace(/dd/gi, leadDay) @@ -789,121 +469,111 @@ export class DatePicker { .replace(/m/gi, month) .replace(/yyyy/gi, fullYear) .replace(/yy/gi, year) - .replace(/hh/gi, leadHours) - .replace(/h/gi, hours) - .replace(/ii/gi, leadMinutes) - .replace(/i/gi, minutes); - return formatted.charAt(0).toUpperCase() + formatted.slice(1); + return formatted.charAt(0).toUpperCase() + formatted.slice(1); } - /** - * DatePicker, Set Target Events - */ #setTargetEvents() { - let that = this; + const that = this; // Restore Selected - Handler - this.target.dxOn('restore.datepicker', () => that.restoreValue()); + this.target.dxOn('restore.datepicker', (_, t) => that.restoreValue( t.value.trim() )); - if (this.isToggled) { + if (this.#isToggled) { // Input clicked hander - this.target.dxOn('click.datepicker', e => { - + this.target.dxOn('focus.datepicker', e => { e.stopPropagation(); - - if (!that.opts.hiddenMode) { - that.show(); - } + that.show(); }); } } - /** - * DatePicker, Restore value - */ - restoreValue(val = null) { - - let month = null; - let year = null; - - if (this.month && this.year) { - month = this.month; - year = this.year; - } - - this.#restoreProperties(val); - - this.#updateInputVal(); - - if (!this.initFlag) { - return; - } - - // Same month - if (this.month == month && this.year == year) { - - // Unmark all days - this.ui.tbody.dxFind('.dp-day', cell => cell.classList.remove('dp-selday')); - } - else { - this.#renderMonth(); - } - - if (this.opts.multiple && this.opts.chipbox) { - this.ui.chipbox.dxHtml(''); - } - - // All cases (with or without time) - Ut.each (this.selected, (time, date) => { - - let token = date.split('.'); - - let day = parseInt(token[0]); - this.ui.tbody.dxFind('.dp-day', cell => cell.textContent == day && cell.classList.add('dp-selday') ); - - // Multiple values - if (this.opts.multiple && this.opts.chipbox) { - - this.ui.chipbox.append( El('div', { className: 'sp-chip', 'data-id': date }, - El('span', {}, this.#getFormattedDate(date, time, this.opts.dateFormat)), - El('i', { className: 'close' }, 'x') - )); - - this.ui.chipbox.classList.remove('hide'); - } - }, this); + getValue() { + return this.value; } - /** - * DatePicker, Show UI - */ - show() { + restoreValue(val) { - if (this.isOpened || this.fadeBusy) { + if (!val) { return; } - if (!this.initFlag) { - this.#initUi(); + const initialMonth = this.month; + const initialYear = this.year; + + if (val instanceof Date) { + + this.selectedDate = val; + this.selectedDate.setHours(0, 0, 0, 0); + + this.day = this.selectedDate.getDate(); + this.month = this.selectedDate.getMonth(); + this.year = this.selectedDate.getFullYear(); + } + else { + this.#parseDate(val); } - Stack.widgets.DatePicker.forEach(datepicker => datepicker.hide()); - Stack.widgets.Hours.forEach(hours => hours.hide()); + // Restore correct value + this.#setValue(); - this.setUiPosition(); + // Not rendered yet - stop + if (!this.#hasRendered) { + return; + } + + // Same month, Same year + if (this.month === initialMonth && this.year === initialYear) { + + this.ui.tbody.dxFind('.dp-day', cell => { + + if (Number(cell.textContent) === this.day) { + + this.#setValue(); + + if (this.#isToggled) { + this.target.dxData('cancelable')?.iconHandler.classList.remove('hide'); + } + + this.activeCellEl?.classList.remove('dp-selday'); + this.activeCellEl = cell; + this.activeCellEl.classList.add('dp-selday'); + + Ut.trigger(this.opts.onRestored, this); + this.target.dxTrigger('restored'); + } + }); + + return; + } + + // Re-render + this.renderMonth(); + } + + show() { + + if (this.#isOpened || this.#fadeBusy) { + return; + } + + if (!this.#hasRendered) { + this.#render(); + } + + this.#reposition(); if (this.opts.anim) { - this.fadeBusy = true; + this.#fadeBusy = true; this.ui.container.dxFadeIn({ duration: this.opts.animTime, complete: () => { - this.isOpened = true; - this.fadeBusy = false; + this.#isOpened = true; + this.#fadeBusy = false; } }); } @@ -912,155 +582,116 @@ export class DatePicker { } // Call onShow event at this point - Ut.trigger(this.events.onShow, this); + Ut.trigger(this.opts.onShow, this); } - /** - * DatePicker, Hide UI - */ hide() { - if (!this.isOpened || this.fadeBusy) { + if (!this.#hasRendered || !this.#isOpened || this.#fadeBusy) { return; } - this.isOpened = false; - - if (this.popover) { - this.popover.destroy(); - } + this.#isOpened = false; this.ui.container.classList.add('hide'); - Ut.trigger(this.events.onHide, this); + Ut.trigger(this.opts.onHide, this); } - /** - * DatePicker, Enable UI - */ enable() { - this.isDisabled = false; + + if (!this.#hasRendered) { + return; + } + + this.#isDisabled = false; this.ui.container.classList.remove('disabled'); } - /** - * DatePicker, Disable UI - */ disable() { - this.isDisabled = true; + + if (!this.#hasRendered) { + return; + } + + this.#isDisabled = true; this.ui.container.classList.add('disabled'); } - /** - * DatePicker, Render Mounth days - */ - #renderMonth() { + renderMonth() { - if (this.popover) { - this.popover.destroy(); + if (!this.#hasRendered) { + return; } this.ui.cMonth.textContent = this.#getMonthName(this.month) + ' ' + this.year; - let colStart = new Date(this.year, this.month, 0).getDay() + 1; + const colStart = new Date(this.year, this.month, 0).getDay() + 1; const days = [ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 ]; - this.totalDays = (this.month == 1 && !(this.year & 3) && (this.year % 1e2 || !(this.year % 4e2))) ? 29 : days[this.month]; - - this.ui.tbody.dxHtml(''); + this.totalDays = (this.month === 1 && !(this.year & 3) && (this.year % 1e2 || !(this.year % 4e2))) ? 29 : days[this.month]; - if (this.opts.multiple && this.opts.chipbox) { - this.ui.chipbox.dxHtml('').classList.add('hide'); - } + this.ui.tbody.replaceChildren(); + + const selTime = this.selectedDate?.getTime(); for (let i = 1; i <= this.totalDays; i++) { - let cell = this.ui.tbody.appendChild( El('div', { className: 'dp-day' }, i) ); - - if (i == 1) { - cell.style.gridColumnStart = colStart; - } - - let date = new Date(this.year, this.month, i); - - if (this.minDate) { - if (date < this.minDate) { - cell.className += ' disabled'; - } - } - - if (this.maxDate) { - if (date > this.maxDate) { - cell.className += ' disabled'; - } - } - - if (this.selected[ i + '.' + this.month + '.' + this.year ]) { - - cell.className += ' dp-selday'; + const cellEl = this.ui.tbody.appendChild( El('div', { className: 'dp-day' }, i) ); - if (this.opts.multiple && this.opts.chipbox) { + const cellDate = new Date(this.year, this.month, i); + const cellTime = cellDate.getTime(); - Ut.each(this.selected, (checked, date) => { - - if (this.opts.multiple && this.opts.chipbox) { - - this.ui.chipbox.append( El('div', { className: 'sp-chip', 'data-id': date }, - El('span', {}, this.#getFormattedDate(date, checked, this.opts.dateFormat)), - El('i', { className: 'close' }, 'x' ) - )); - - this.ui.chipbox.classList.remove('hide'); - } - }); - } + if (i === 1) { + cellEl.style.gridColumnStart = colStart; } - if (date.getTime() === this.today) { - cell.className += ' dp-today'; + if (this.minDate && cellDate < this.minDate) { + cellEl.className += ' disabled'; + } + + if (this.maxDate && cellDate > this.maxDate) { + cellEl.className += ' disabled'; + } + + if (cellTime === selTime) { + this.activeCellEl = cellEl; + cellEl.className += ' dp-selday'; + } + + if (cellTime === this.today) { + cellEl.className += ' dp-today'; } } } - /** - * DatePicker, Clear All Values - */ clear() { this.day = null; - - this.#setCurrentDate(); - - this.hours = 0; - this.minutes = 0; - - this.selected = {}; this.selectedDate = null; - this.initialVal = ''; - if (this.popover) { - this.popover.destroy(); - } + this.activeCellEl?.classList.remove('dp-selday'); + this.activeCellEl = null; - if (this.isToggled) { + const initialMonth = this.month; + const initialYear = this.year; + + this.#setTodayDate(); + + this.#setValue(); + + if (this.#isToggled) { + this.target.dxData('cancelable')?.iconHandler.classList.add('hide'); this.hide(); } - - this.ui.container.dxFind('.dp-selday', cell => cell.classList.remove('dp-selday')); - - if (this.isInput) { - this.target.value = ''; - this.target.dataset.value = ''; + + if (this.#hasRendered && (this.month !== initialMonth || this.year !== initialYear)) { + this.renderMonth(); } - if (this.opts.multiple && this.opts.chipbox) { - this.ui.chipbox.dxHtml('').classList.add('hide'); - } + Ut.trigger(this.opts.onClear, this); } - /** - * DatePicker, Set Date Range - */ setDateRange(opts) { opts = opts || {}; @@ -1073,9 +704,9 @@ export class DatePicker { this.#setMinDate(opts.minDate); - if (this.minDate.getFullYear() == this.year && this.minDate.getMonth() == this.month) { + if (this.minDate.getFullYear() === this.year && this.minDate.getMonth() === this.month) { for (i = 1; i < this.minDate.getDate(); i++) { - this.ui.tbody.dxFind('.dp-day', cell => cell.textContent == i && cell.classList.add('disabled') ); + this.ui.tbody.dxFind('.dp-day', cell => Number(cell.textContent) === i && cell.classList.add('disabled') ); } } } @@ -1084,43 +715,38 @@ export class DatePicker { this.#setMaxDate(opts.maxDate); - if (this.maxDate.getFullYear() == this.year && this.maxDate.getMonth() == this.month) { + if (this.maxDate.getFullYear() === this.year && this.maxDate.getMonth() === this.month) { for (i = this.maxDate.getDate() + 1; i < this.totalDays; i++) { - that.ui.tbody.dxFind('.dp-day', cell => cell.textContent == i && cell.classList.add('disabled') ); + that.ui.tbody.dxFind('.dp-day', cell => Number(cell.textContent) === i && cell.classList.add('disabled') ); } } } } - /** - * DatePicker, Destroy - */ destroy() { - if (this.popover) { - this.popover.destroy(); + if (this.#hasRendered) { + this.ui.container.remove(); } - this.ui.container.remove(); + if (this.#isToggled) { - if (this.isToggled) { - - this.target.dxOff('click.datepicker keydown.datepicker paste.datepicker restore.datepicker'); - document.dxOff('click.datepicker' + this.ekey + ' keyup.datepicker' + this.ekey); - window.dxOff('resize.datepicker' + this.ekey + ' orientationchange.datepicker' + this.ekey); - + this.target.dxOff('focus.datepicker restore.datepicker'); + + if (this.#hasRendered) { + document.dxOff(`pointerdown.datepicker.${this.ekey} keyup.datepicker.${this.ekey}`); + window.dxOff(`resize.datepicker.${this.ekey} orientationchange.datepicker.${this.ekey}`); + } if (this.scrollBar) { - this.scrollBar.dxOff('scroll.datepicker' + this.ekey); - window.dxOff('scroll.datepicker' + this.ekey); + this.scrollBar.dxOff(`scroll.datepicker.${this.ekey}`); + window.dxOff(`scroll.datepicker.${this.ekey}`); } } - Stack.delete(this); - - this.target.removeAttribute('data-value'); this.target.dxRemoveData('datepicker'); + this.target.dxRemoveWidget(); - return this.target; + Ut.trigger(this.opts.onDestroy, this); } } \ No newline at end of file diff --git a/src/widgets/dialog/Dialog.js b/src/widgets/dialog/Dialog.js index 46219f6..b3d2551 100644 --- a/src/widgets/dialog/Dialog.js +++ b/src/widgets/dialog/Dialog.js @@ -2,7 +2,7 @@ import './dialog.css'; import { El } from '../../utils/dom' import { Overlay } from '../overlay'; -import { Stack } from '../../core/Stack'; +import { Ut } from '../../utils/Ut'; //////////////////// // Dialog @@ -33,7 +33,7 @@ export class Dialog { this.events = events || {}; - Stack.add(this); + this.ekey = Ut.nextKey(); this.create(); @@ -226,8 +226,6 @@ export class Dialog { document.dxOff('keydown.dialog' + this.ekey + ' mouseup.dialog' + this.ekey); - Stack.delete(this); - that.target.dxRemoveData('dialog'); Ut.trigger(that.events.destroy); diff --git a/src/widgets/dropdown/Dropdown.js b/src/widgets/dropdown/Dropdown.js index e7e949a..77a8001 100644 --- a/src/widgets/dropdown/Dropdown.js +++ b/src/widgets/dropdown/Dropdown.js @@ -2,178 +2,180 @@ import './dropdown.css'; import { Ut } from '../../utils/Ut'; import { El } from '../../utils/dom'; - -//////////////////// -// DopDown Menu -//////////////////// +import { Boot } from '../../core/Boot'; export class Dropdown { - /** - * Dropdown Constructor - */ - constructor(target, opts) { + #isOpen = false; + #hasRendered = false; - this.target = target; + constructor(target, opts) { - if (this.target.dxData('dropdown')) { - return; - } + this.target = target; - this.target.dxData('dropdown', this); + if (this.target.dxData('dropdown')) { + return; + } - this.opts = { ... { items: [] }, ... opts }; + this.target.dxData('dropdown', this); + + Boot.addWidget(this); - this.isOpened = false; - this.isBusy = false; + this.opts = { ... { items: [] }, ... opts }; - this.create(); + this.el = null; - this.setEvents(); - } + this.ekey = Ut.nextKey(); - /** - * Dropdown, Create UI - */ - create() { + this.#bindTargetEvents(); + } - this.ui = El('div', { className: 'sp-dropdown hide' }); + #bindTargetEvents() { - if (this.opts.cls) { - this.ui.classList.add(this.opts.cls); - } + const that = this; - const list = El('ul'); + this.target.dxOn('focus.dropdown', e => { - if (this.opts.items.length) { - this.ui.append(list); - } + e.stopPropagation(); - this.items = []; + Ut.trigger(that.opts.onFocus, this); - this.opts.items.forEach(item => { - this.items.push( - list.appendChild( El('li', { className: 'color-'+ (item.color || 'black'), 'data-id': item.id }, - (item.icon ? El('i', { className: 'fa fa-'+ item.icon }) : null), - item.name - )) - ); - }); + if (!that.#hasRendered) { + this.#render(); + } - this.target.after(this.ui); + if (!that.#isOpen) { + that.show(); + return; + } + + that.hide(); + + }); + } - Ut.trigger(this.opts.onInit, this); - } + #bindUiEvents() { - /** - * Dropdown, Set UI Position - */ - setPosition() { + const that = this; + + // Click on Item Handler + this.el.dxOn('click', 'li', (_, t) => { - let left = Math.round(this.target.offsetLeft); + that.hide(); - if ((this.ui.clientWidth + left) > window.innerWidth) { - left = 0; - } + Ut.trigger(that.opts.items[ t.dxIndex(that.items) ].handler); - this.ui.style.top = (Math.round(this.target.offsetTop) + this.target.clientHeight + 2) + 'px'; - this.ui.style.left = left + 'px'; - } + Ut.trigger(that.opts.onChange, t.dataset.id, this); + }); + + // PointerDown Handler + document.dxOn('pointerdown.dropdown', e => !this.target.contains(e.target) && !this.el.contains(e.target) && this.hide() ); - /** - * Dropdown, Show UI - */ - show() { + let tm = null; - if (this.isBusy || this.isOpened) { - return; - } + // Resize & Orientation Handler + window.dxOn(`resize.dropdown.${this.ekey} orientationchange.dropdown.${this.ekey}`, _ => { - Ut.trigger(this.opts.onShow, this); + if (tm) { + clearTimeout(tm); + tm = null; + } - this.setPosition(); + tm = setTimeout(_ => that.#reposition(), 100); + }); + } - this.ui.classList.remove('hide'); - this.ui.classList.add('opened'); - this.target.classList.add('dropdown-opened'); + #render() { - this.isOpened = true; - } + this.el = El('div', { className: 'sp-dropdown hide' }); - /** - * Dropdown, Hide UI - */ - hide() { + if (this.opts.cls) { + this.el.classList.add(this.opts.cls); + } - if (!this.isOpened) { - return; - } + const list = El('ul'); - Ut.trigger(this.opts.onHide, this); + if (this.opts.items.length) { + this.el.append(list); + } - let that = this; + this.items = []; - this.ui.addEventListener('transitionend', _ => !this.ui.classList.contains('opened') && this.ui.classList.add('hide'), { once: true }); + this.opts.items.forEach(item => { + this.items.push( + list.appendChild( El('li', { className: 'color-'+ (item.color || 'black'), 'data-id': item.id }, + (item.icon ? El('i', { className: 'fa fa-'+ item.icon }) : null), + item.name + )) + ); + }); - this.ui.classList.remove('opened'); - this.target.classList.remove('dropdown-opened'); + document.body.append(this.el); - this.isOpened = false; - }; + this.#bindUiEvents(); - /** - * Dropdown, Set Evensts - */ - setEvents() { + this.#hasRendered = true; - let that = this; + Ut.trigger(this.opts.onInit, this); + } - document.body.dxOn('click.dropdown', _ => that.hide()); + #reposition() { - this.target.dxOn('click', e => { + const r = this.target.getBoundingClientRect(); - e.stopPropagation(); + const scrollX = window.scrollX || 0; + const scrollY = window.scrollY || 0; - if (!that.ui.classList.contains('opened')) { - that.show(); - } - else { - that.hide(); - } - }); + this.el.style.left = Math.round(r.left + scrollX) + 'px'; + this.el.style.top = Math.round(r.bottom + scrollY + 2) + 'px'; + } - this.ui.dxOn('click', 'li', (_, t) => { - Ut.trigger(that.opts.items[ t.dxIndex(that.items) ].handler); - Ut.trigger(that.opts.onChange, t.dataset.id, this); - }); + show() { - let tm = null; + if (this.#isOpen) { + return; + } - window.dxOn('resize.dropdown' + this.ekey + ' orientationchange.dropdown' + this.ekey, _ => { + this.#reposition(); - if (tm) { - clearTimeout(tm); - tm = null; - } + this.el.classList.remove('hide'); + this.el.classList.add('opened'); - tm = setTimeout(_ => that.setPosition(), 100); - }); - } + this.#isOpen = true; - /** - * Dropdown, Destroy - */ - destroy() { + Ut.trigger(this.opts.onShow, this); + } - this.ui.remove(); + hide() { + + if (!this.#isOpen) { + return; + } - document.body.dxOff('click.dropdown'); - window.dxOff('resize.dropdown' + this.ekey + ' orientationchange.dropdown' + this.ekey); + this.el.addEventListener('transitionend', _ => this.el.classList.add('hide'), + { once: true } + ); - this.target.dxRemoveData('dropdown'); + this.el.classList.remove('opened'); - Ut.trigger(this.events.destroy, this); + this.#isOpen = false; - return this.target; - } + Ut.trigger(this.opts.onHide, this); + }; + + destroy() { + + if (this.#hasRendered) { + + this.el.remove(); + + document.dxOff(`pointerdown.dropdown.${this.ekey}`); + window.dxOff(`resize.dropdown.${this.ekey} orientationchange.dropdown.${this.ekey}`); + } + + this.target.dxOff(`focus.dropdown.${this.ekey}`); + this.target.dxRemoveData('dropdown'); + + Ut.trigger(this.opts.onDestroy, this); + } } \ No newline at end of file diff --git a/src/widgets/dropdown/dropdown.css b/src/widgets/dropdown/dropdown.css index 87acb58..3603788 100644 --- a/src/widgets/dropdown/dropdown.css +++ b/src/widgets/dropdown/dropdown.css @@ -12,6 +12,8 @@ opacity: 0; transition: all 0.3s; transform: translateY(20px); + -webkit-user-select: none; + user-select: none; } .sp-dropdown.opened { @@ -20,7 +22,6 @@ } .sp-dropdown ul { - margin: 2px 0 0; font-size: 14px; color: #212529; background-color: #fff; @@ -41,6 +42,10 @@ cursor: pointer; } +.sp-dropdown li:hover { + background-color: #ebebeb; +} + .sp-dropdown.dp-interval li { padding: 8px 12px 8px 12px; font-size: 13px; @@ -54,8 +59,4 @@ display: block; font-size: 16px; margin-right: 8px; -} - -.sp-dropdown li:hover { - opacity: 0.75; } \ No newline at end of file diff --git a/src/widgets/hours/Hours.js b/src/widgets/hours/Hours.js index 3c283d7..587bef0 100644 --- a/src/widgets/hours/Hours.js +++ b/src/widgets/hours/Hours.js @@ -2,7 +2,6 @@ import './hours.css'; import { Ut } from '../../utils/Ut'; import { El } from '../../utils/dom'; -import { Stack } from '../../core/Stack'; import { Render } from '../../core/Render'; //////////////////// @@ -28,7 +27,7 @@ export class Hours { this.makeUi(); - Stack.add(this); + this.ekey = Ut.nextKey(); this.hour = this.ui.dxFind('select[name=hour]'); this.min = this.ui.dxFind('select[name=min]'); @@ -109,8 +108,6 @@ export class Hours { this.target.dxOn('click.hours', function(e) { e.stopPropagation(); - - Stack.widgets.DatePicker.forEach(datepicker => datepicker.hide() ); that.isVisible = true; @@ -182,7 +179,6 @@ export class Hours { document.body.dxOff('click.hours'); window.dxOff('resize.hours' + this.ekey + ' orientationchange.hours' + this.ekey + ' keyup.hours' + this.ekey); - Stack.delete(this); this.target.dxRemoveData('hours'); return this.target; diff --git a/src/widgets/interval-picker/IntervalPicker.js b/src/widgets/interval-picker/IntervalPicker.js index d2a68d4..d4089f6 100644 --- a/src/widgets/interval-picker/IntervalPicker.js +++ b/src/widgets/interval-picker/IntervalPicker.js @@ -2,235 +2,163 @@ import { Ut } from '../../utils/Ut'; import { Boot } from '../../core/Boot'; import { TwoDatePicker } from '../two-datepicker/TwoDatepicker'; import { Dropdown } from '../dropdown'; -import { Stack } from '../../core/Stack'; - + //////////////////// // IntervalPicker //////////////////// export class IntervalPicker { - /** - * Intervalpicker, Constructor - */ - constructor(target, opts) { + /** + * Intervalpicker, Constructor + */ + constructor(target, opts) { - this.target = target; + this.target = target; - this.opts = { ... { - separator : ' - ', - locales : 'ro-RO', - style : 'normal', - disabled : 1 - }, ... opts }; + this.opts = { ... { + locales : 'ro-RO', + style : 'normal', + disabled : false, + dateFormat : 'dd-mm-yyyy', + valueFormat : 'dd-mm-yyyy', + valSeparator: '-', + dateSeparator: ' - ' + }, ... opts }; - if (this.target.dxData('intervalpicker')) { - return; - } + if (this.target.dxData('intervalpicker')) { + return; + } - this.target.dxData('intervalpicker', this); + this.target.dxData('intervalpicker', this); - this.widget = Boot.intData.widget.intervalpicker.children; + this.widget = Boot.intData.widget.intervalpicker.children; - this.create(); + this.create(); + } - Stack.add(this); - } + /** + * Intervalpicker, Create + */ + create() { + + let that = this; - /** - * Intervalpicker, Date Format - */ - dateFormat(date) { - return date.toLocaleDateString(this.opts.locales, { - day : '2-digit', - month : '2-digit', - year : 'numeric' - }); - //.replace(/\./g, '/'); - }; + let options = Ut.objVal(this.widget.form).children.field.interval.option; + let items = []; + + for (let id in options) { + items.push({ id: id, name: options[id] }); + } - /** - * Intervalpicker, Create - */ - create() { - - let that = this; + this.twoDatePicker = new TwoDatePicker(this.target, { + anim: false, + render: false, + showOnClick: false, + style: this.opts.style, + disabled: this.opts.disabled, + dateFormat : 'dd-mm-yyyy', + valueFormat : 'dd-mm-yyyy', + valSeparator: '-', + dateSeparator: ' - ' + }); - let options = Ut.objVal(this.widget.form).children.field.interval.option; - let items = []; - - for (let id in options) { - items.push({ id: id, name: options[id] }); - } + this.dropdown = new Dropdown(this.target, { + items: items, + cls: 'dp-interval', + onChange: id => { - new TwoDatePicker(this.target, { - anim: false, - hiddenMode: true, - style: this.opts.style, - disabled: this.opts.disabled - }, { - - init: twoDatePicker => that.twoDatePicker = twoDatePicker, - onChange: function(twoDatePicker, val, index) { + const date = new Date(); - const firstDp = twoDatePicker.datepicker[0]; - const secondDp = twoDatePicker.datepicker[1]; + switch (id) { + case '0': + that.interval = [ date, date ]; + break; + case '1': + that.interval = [ null, new Date(date) ]; + date.setDate(date.getDate() - 1); + that.interval[0] = date; + break; + + case '2': + that.interval = [ null, new Date(date) ]; + date.setDate(date.getDate() - 7); + that.interval[0] = date; + break; + case '3': + that.interval = [ null, new Date(date) ]; + date.setDate(date.getDate() - 30); + that.interval[0] = date; + break; + case '4': + date.setDate(1); + that.interval = [ new Date(date), null ]; + date.setMonth(date.getMonth() + 1); + date.setDate(0); + that.interval[1] = date; + break; + case '5': + date.setDate(1); + date.setMonth(date.getMonth() - 1); + that.interval = [ new Date(date), null ]; + date.setMonth(date.getMonth() + 1); + date.setDate(0); + that.interval[1] = date; + break; + + case '6': + date.setDate(1); + date.setMonth(0); + that.interval = [ new Date(date), null ]; + date.setFullYear(date.getFullYear() + 1); + date.setDate(0); + that.interval[1] = date; + break; + + case '7': + date.setFullYear(date.getFullYear() - 1); + date.setMonth(0); + date.setDate(1); + that.interval = [ new Date(date), null ]; + date.setFullYear(date.getFullYear() + 1); + date.setDate(0); + that.interval[1] = date; + break; + case '8': + setTimeout(_ => that.twoDatePicker.show()); + break; + } - if (!val) { - that.interval = null; - } - - if (index == 0) { - - secondDp.clear(); - - secondDp.minDate = firstDp.selectedDate; - - if (firstDp.month != secondDp.month) { - - secondDp.year = firstDp.year; - secondDp.month = firstDp.month; - - secondDp.makeUI(); - } - else { - secondDp.setDateRange({ minDate: firstDp.selectedDate }); - } - } - else { - - if (!firstDp.selectedDate) { - return; - } - - that.target.dxVal(that.dateFormat(firstDp.selectedDate) - + that.opts.separator - + that.dateFormat(secondDp.selectedDate) - ); - - twoDatePicker.hide(); - that.target.dxTrigger('changed'); - } - }, - onHide: _ => that.dropdown.isBusy = false - }); + if (id != '8') { + that.updateInput(); + } + } + }); + } - const twoDp = this.target.dxData('two_datepicker'); + /** + * Intervalpicker, Update Input + */ + updateInput() { - new Dropdown(this.target, { - items: items, - cls: 'dp-interval', - onInit: dropdown => that.dropdown = dropdown, - onShow: _ => { + if (!this.interval || !this.interval.length) { + return; + } - Stack.widgets.IntervalPicker.forEach(intPicker => { + this.twoDatePicker.restoreValue(this.interval); - if (intPicker.dropdown.isOpened) { - intPicker.dropdown.hide(); - } + this.target.dxTrigger('changed'); + Ut.trigger(this.opts.onSelect, this); + } - if (intPicker.dropdown.isBusy) { - intPicker.twoDatePicker.hide(); - } - }); - }, - onChange: id => { + /** + * Intervalpicker, Destroy + */ + destroy() { - const date = new Date(); + this.twoDatePicker.destroy(); + this.dropdown.destroy(); - switch (id) { - case '0': - that.interval = [ date, date ]; - break; - case '1': - that.interval = [ null, new Date(date) ]; - date.setDate(date.getDate() - 1); - that.interval[0] = date; - break; - - case '2': - that.interval = [ null, new Date(date) ]; - date.setDate(date.getDate() - 7); - that.interval[0] = date; - break; - case '3': - that.interval = [ null, new Date(date) ]; - date.setDate(date.getDate() - 30); - that.interval[0] = date; - break; - case '4': - date.setDate(1); - that.interval = [ new Date(date), null ]; - date.setMonth(date.getMonth() + 1); - date.setDate(0); - that.interval[1] = date; - break; - case '5': - date.setDate(1); - date.setMonth(date.getMonth() - 1); - that.interval = [ new Date(date), null ]; - date.setMonth(date.getMonth() + 1); - date.setDate(0); - that.interval[1] = date; - break; - - case '6': - date.setDate(1); - date.setMonth(0); - that.interval = [ new Date(date), null ]; - date.setFullYear(date.getFullYear() + 1); - date.setDate(0); - that.interval[1] = date; - break; - - case '7': - date.setFullYear(date.getFullYear() - 1); - date.setMonth(0); - date.setDate(1); - that.interval = [ new Date(date), null ]; - date.setFullYear(date.getFullYear() + 1); - date.setDate(0); - that.interval[1] = date; - break; - case '8': - that.dropdown.isBusy = true; - twoDp.restoreValue(that.interval); - setTimeout(_ => twoDp.show()); - break; - } - - that.updateInput(); - } - }); - } - - /** - * Intervalpicker, Update Input - */ - updateInput() { - - if (!this.interval) { - return; - } - - const val = this.interval.map(date => this.dateFormat(date)).join(this.opts.separator); - - this.target.value = val; - - this.target.dxTrigger('changed'); - Ut.trigger(this.opts.onSelect, val, this); - } - - /** - * Intervalpicker, Destroy - */ - destroy() { - - this.twoDatePicker.destroy(); - this.dropdown.destroy(); - - Stack.delete(this); - - this.target.dxRemoveData('intervalpicker'); - } + this.target.dxRemoveData('intervalpicker'); + } } \ No newline at end of file diff --git a/src/widgets/lightbox/Lightbox.js b/src/widgets/lightbox/Lightbox.js index 908875e..97e2e79 100644 --- a/src/widgets/lightbox/Lightbox.js +++ b/src/widgets/lightbox/Lightbox.js @@ -3,7 +3,6 @@ import './lightbox.css'; import { Ut } from '../../utils/Ut'; import { $ } from '../../utils/dom'; import { Boot } from '../../core/Boot'; -import { Stack } from '../../core/Stack'; //////////////////// // LightBox @@ -162,7 +161,7 @@ export class Lightbox { this.arrowRight.classList.toggle('hide', this.square.length == this.index + 1); this.arrowLeft.classList.toggle('hide', !this.index); - Stack.add(this); + this.ekey = Ut.nextKey(); } hidden() { @@ -318,8 +317,6 @@ export class Lightbox { this.target.dxOff('click.lightbox'); document.dxOff('keyup.lightbox' + this.ekey + ' resize.lightbox' + this.ekey + ' orientationchange.lightbox' + this.ekey); - Stack.delete(this); - this.target.dxRemoveData('lightbox'); } } \ No newline at end of file diff --git a/src/widgets/popover/Popover.js b/src/widgets/popover/Popover.js index 9bb5f97..b093c0c 100644 --- a/src/widgets/popover/Popover.js +++ b/src/widgets/popover/Popover.js @@ -2,7 +2,6 @@ import './popover.css'; import { Ut } from '../../utils/Ut'; import { El, $ } from "../../utils/dom"; -import { Stack } from '../../core/Stack'; import { Overlay } from '../overlay'; export class Popover { @@ -29,15 +28,11 @@ export class Popover { closeBtn : false }, ... opts }; - Stack.widgets.Popover ??= new Map(); - if (!this.opts.multiple) { // Destroy old instances - Stack.widgets.Popover.forEach(popover => popover.destroy()); + Boot.widgetStack?.Popover.forEach(popover => popover.destroy()); } - Stack.add(this); - this.create(); this.setPosition(); @@ -166,8 +161,6 @@ export class Popover { this.target.dxRemoveData('popover'); - Stack.delete(this); - Ut.trigger(this.opts.onDestroy, this); } } diff --git a/src/widgets/popup/Popup.js b/src/widgets/popup/Popup.js index 2e072b0..00f2acc 100644 --- a/src/widgets/popup/Popup.js +++ b/src/widgets/popup/Popup.js @@ -1,9 +1,9 @@ import './popup.css'; +import { Boot } from '../../core/Boot'; import { Ut } from '../../utils/Ut'; import { El } from '../../utils/dom'; import { Overlay } from '../overlay'; -import { Stack } from '../../core/Stack'; import { Render } from '../../core/Render'; //////////////////// @@ -39,11 +39,13 @@ export class Popup { this.opened = false; + this.ekey = Ut.nextKey(); + if (!this.opts.multiple) { - Stack.widgets.Popup?.forEach((popup, key) => { + Boot.widgetStack?.Popup?.forEach(popup => { - if (key == this.ekey - 1) { + if (popup.ekey < this.ekey) { // [?] that.opened = true; popup.destroy(_ => that.init()); } @@ -64,8 +66,6 @@ export class Popup { */ init() { - Stack.add(this); - this._plugins = []; this.create(); @@ -221,8 +221,6 @@ export class Popup { that._plugins.forEach(widget => widget.destroy()); - Stack.delete(this); - that.target.dxRemoveData('popup'); Ut.trigger(callback); diff --git a/src/widgets/select-box/SelectBox.js b/src/widgets/select-box/SelectBox.js index 1e8cf3c..3e4ade3 100644 --- a/src/widgets/select-box/SelectBox.js +++ b/src/widgets/select-box/SelectBox.js @@ -1,7 +1,7 @@ import './select-box.css'; import { El } from '../../utils/dom'; -import { Stack } from '../../core/Stack'; +import { Ut } from '../../utils/Ut'; export class SelectBox { @@ -19,7 +19,7 @@ export class SelectBox { this.labels = El('div', { className: 'labels hide' }); this.choices = El('div', { className: 'choices' }); - this.ui = El('div', { className: 'sp-form-control sp-select-box' }, + this.el = El('div', { className: 'sp-form-control sp-select-box' }, this.choices, this.labels ); @@ -30,9 +30,9 @@ export class SelectBox { this.labels.append(el.parentElement); }, this); - parent.append(this.ui); + parent.append(this.el); - Stack.add(this); + this.ekey = Ut.nextKey(); this.setEvents(); } @@ -44,7 +44,7 @@ export class SelectBox { let that = this; - this.ui.dxOn('click', function(e) { + this.el.dxOn('click', function(e) { e.stopPropagation(); that.labels.classList.toggle('hide'); }); @@ -85,10 +85,7 @@ export class SelectBox { * Select Box, Destroy */ destroy() { - window.dxOff('resize.selectbox' + this.ekey + ' orientationchange.selectbox' + this.ekey); document.dxOff('click.selectbox' + this.ekey); - - Stack.delete(this); } } \ No newline at end of file diff --git a/src/widgets/two-datepicker/TwoDatepicker.js b/src/widgets/two-datepicker/TwoDatepicker.js index bdb4778..90dc52d 100644 --- a/src/widgets/two-datepicker/TwoDatepicker.js +++ b/src/widgets/two-datepicker/TwoDatepicker.js @@ -4,22 +4,26 @@ import { Ut } from "../../utils/Ut"; import { El } from "../../utils/dom"; import { Cancelable } from '../cancelable'; import { DatePicker } from '../date-picker'; -import { Stack } from '../../core/Stack'; +import { Boot } from '../../core/Boot'; export class TwoDatePicker { - /** - * TwoDatePicker Constructor - */ - constructor(target, opts, events) { + #hasRendered = false; + #isOpened = false; + #isInput = false; + #isToggled = false; + + constructor(target, opts) { this.target = target; - if (this.target.dxData('two_datepicker')) { + if (this.target.dxWidget() || this.target.dxData('two_datepicker')) { return; } this.target.dxData('two_datepicker', this); + this.target.dxWidget(this); + Boot.addWidget(this); this.opts = { ... { style : 'normal', @@ -27,320 +31,331 @@ export class TwoDatePicker { fullMonth : true, navDisabled : false, anim : true, + dateFormat : 'dd/mm/yyyy', + valueFormat : 'dd-mm-yyyy', + valSeparator: '-', + dateSeparator: ' - ', locales : 'ro-RO', - cancelable : true, - hiddenMode : false, + defaultDate : null, + render : true, + showOnClick : true }, ... opts }; - this.events = events || {}; + this.ekey = Ut.nextKey(); - this.isDisabled = this.opts.disabled; + if (this.target.tagName.toLowerCase() === 'input') { - if (this.target.tagName.toLowerCase() == 'input') { + this.#isInput = true; - this.isInput = true; + if (this.target.type === 'text') { + + this.#isToggled = true; - if (this.target.type == 'text') { - this.isToggled = true; + this.target.setAttribute('autocomplete', 'off'); + this.target.inputmode = 'none'; + this.target.readOnly = true; + + new Cancelable(this.target); } + + this.#bindTargetEvents(); } - this.create(); - - Stack.add(this); - - this.setEvents(); - - this.restoreValue(this.target.dxVal()); - } - - /** - * TwoDatePicker, Create UI - */ - create() { - - const that = this; - - this.ui = El('div', { className: 'sp-two-datepicker' }); - - let firstCol = El('div', { className: 'ui-col' }); - let secondCol = El('div', { className: 'ui-col' }); - - this.ui.append(firstCol); - this.ui.append(secondCol); - const dpOpts = { style : this.opts.style, disabled : this.opts.disabled, fullMonth : this.opts.fullMonth, navDisabled : this.opts.navDisabled, - locales : this.opts.locales + locales : this.opts.locales, + dateFormat : this.opts.dateFormat, + valueFormat : this.opts.valueFormat, + render : false }; - this.datepicker = []; + let tokenVal = []; - new DatePicker(firstCol, dpOpts, { - init: datepicker => that.datepicker[0] = datepicker, - onSelect: (_, val) => this.selectDate(0, val) + if (this.#isInput) { + + const defaultVal = this.target.value.trim(); + + if (defaultVal) { + tokenVal = defaultVal.split(this.opts.dateSeparator); + } + } + + if (this.opts.defaultDate && !tokenVal.length) { + tokenVal = this.opts.defaultDate.split(this.opts.valSeparator); + } + + const hasVal = tokenVal.length === 2; + + this.firstDp = new DatePicker(El('div', { className: 'ui-col' }), { + ... dpOpts, + defaultDate: hasVal ? tokenVal[0] : null, + onSelect: (_, val) => this.#selectDate(0, val) }); - new DatePicker(secondCol, dpOpts, { - init: datepicker => that.datepicker[1] = datepicker, - onSelect: (_, val) => this.selectDate(1, val) + this.secondDp = new DatePicker(El('div', { className: 'ui-col' }), { + ... dpOpts, + defaultDate: hasVal ? tokenVal[1] : null, + disabled: !hasVal, + onSelect: (_, val) => this.#selectDate(1, val) }); - if (this.isInput) { + if (!this.#isToggled && this.opts.render) { + this.#initUi(); + } + } - if (this.isToggled) { + render() { - this.ui.classList.add('toggled', 'hidden'); + if (this.#hasRendered) { + return; + } - if (this.opts.cancelable && !this.target.dxData('cancelable') ) { - new Cancelable(this.target); - } + this.#initUi(); + } - document.body.append(this.ui); + #bindTargetEvents() { + + const that = this; + + if (this.#isInput) { + // this.target.dxOn('keydown.two_datepicker paste.two_datepicker', e => e.preventDefault()); + this.target.dxOn('restore.two_datepicker', (_, t) => this.restoreValue(t.value, this.opts.dateSeparator)); + } + + if (this.#isToggled && this.opts.showOnClick) { + + this.target.dxOn('click.two_datepicker', e => { + + e.stopPropagation(); + + //if (!that.opts.hiddenMode) { + that.show(); + //} + }); + } + } + + #bindUiEvents() { + + const that = this; + + if (!this.#isToggled) { + return; + } + + document.dxOn(`pointerdown.twodatepicker.${this.ekey}`, e => !this.target.contains(e.target) && !this.el.contains(e.target) && this.hide() ); + + let tm = null; + + // Resize, Scroll, Orientation window Handlers + window.dxOn(`resize.two_datepicker.${this.ekey} orientationchange.two_datepicker.${this.ekey}`, _ => { + + if (tm) { + clearTimeout(tm); + tm = null; + } + + tm = setTimeout(_ => that.#reposition(), 100); + }); + } + + #initUi() { + + this.el = El('div', { className: 'sp-two-datepicker' }); + + this.firstDp.render(); + this.secondDp.render(); + + this.el.append(this.firstDp.target); + this.el.append(this.secondDp.target); + + if (this.#isInput) { + + if (this.#isToggled) { + this.el.classList.add('toggled', 'hidden'); + document.body.append(this.el); } else { - this.target.after(this.ui); + this.target.after(this.el); } this.target.setAttribute('autocomplete', 'off'); } else { - this.target.append(this.ui); + this.target.append(this.el); } + this.#bindUiEvents(); + + this.#hasRendered = true; + Ut.trigger(this.opts.onInit, this); } - /** - * TwoDatePicker, Select Value - */ - selectDate(index, val) { + #selectDate(index, val) { Ut.trigger(this.opts.onSelect, this, index, val); - const firstDp = this.datepicker[0]; - const secondDp = this.datepicker[1]; - - if (index == 0) { + if (index === 0) { - secondDp.clear(); - secondDp.minDate = firstDp.selectedDate; - - if (firstDp.month != secondDp.month) { + this.secondDp.enable(); + this.secondDp.clear(); + + this.secondDp.setDateRange({ minDate: this.firstDp.selectedDate }); - secondDp.year = firstDp.year; - secondDp.month = firstDp.month; - - secondDp.makeUI(); - } - else { - secondDp.setDateRange({ minDate: firstDp.selectedDate }); - } - return; } - if (!firstDp.selectedDate) { - this.target.value = ''; - return; - } + this.#setValue(); - this.target.value = `${this.dateFormat(firstDp.selectedDate)} - ${this.dateFormat(secondDp.selectedDate)}`; this.hide(); Ut.trigger(this.opts.onChange, this); this.target.dxTrigger('changed'); } - /** - * TwoDatepicker, Date Format - */ - dateFormat(date) { - return date.toLocaleDateString(this.opts.locales, { - day : '2-digit', - month : '2-digit', - year : 'numeric' - }); - //.replace(/\./g, '/'); - }; + #setValue() { + + const clear = !this.firstDp.selectedDate || !this.secondDp.selectedDate; + + this.value = !clear ? `${this.firstDp.value}${this.opts.valSeparator}${this.secondDp.value}` : ''; + this.displayValue = !clear ? `${this.firstDp.displayValue}${this.opts.dateSeparator}${this.secondDp.displayValue}` : ''; + + if (this.#isInput) { + this.target.value = this.displayValue; + } + } - /** - * TwoDatePicker, Show UI - */ show() { - this.ui.classList.remove('hidden'); - - if (this.opts.anim) { - this.ui.classList.add('visible'); + if (this.#isOpened) { + return; } - this.setPosition(); + if (!this.#hasRendered) { - this.isOpen = true; + this.#initUi(); + + if (this.value) { + this.secondDp.setDateRange({ minDate: this.firstDp.selectedDate }); + this.secondDp.enable(); + } + } + + this.el.classList.remove('hidden'); + + if (this.opts.anim) { + this.el.classList.add('visible'); + } + + this.#reposition(); + + this.#isOpened = true; Ut.trigger(this.opts.onShow, this); } - /** - * TwoDatePicker, Hide UI - */ hide() { - if (this.isOpen) { + if (this.#isOpened) { - this.ui.classList.add('hidden'); + this.el.classList.add('hidden'); if (this.opts.anim) { - this.ui.classList.remove('visible'); + this.el.classList.remove('visible'); } - this.isOpen = false; + this.#isOpened = false; Ut.trigger(this.opts.onHide, this); } } - /** - * TwoDatePicker, Set UI Posituion - */ - setPosition() { + #reposition() { let left = Math.round(this.target.dxOffset().left); - if ((this.ui.clientWidth + left) > window.innerWidth) { + if ((this.el.clientWidth + left) > window.innerWidth) { left = 0; } - this.ui.dxCss({ + this.el.dxCss({ top : (Math.round(this.target.dxOffset().top) + this.target.clientHeight + 2) + 'px', left : left + 'px', }); } - /** - * TwoDatePicker, Clear Val - */ clear() { + + this.firstDp.clear(); - if (this.isToggled) { + this.secondDp.clear(); + this.secondDp.disable(); - if (this.opts.cancelable) { - this.target.dxData('cancelable').iconHandler.classList.add('hide'); - } + this.#setValue(); + if (this.#isToggled) { + this.target.dxData('cancelable').iconHandler.classList.add('hide'); this.hide(); } + } - if (this.isInput) { - this.target.dxVal(''); + getValue() { + return this.value; + } + + restoreValue(val, separator = this.opts.valSeparator) { + + if (!val) { + return false; + } + + if (!Array.isArray(val)) { + val = val.split(separator); } - this.datepicker[0].clear(); - this.datepicker[1].clear(); + if (val.length != 2) { + return false; + } + + const [ firstDate, secDate ] = val; + + this.firstDp.restoreValue(firstDate); + this.secondDp.restoreValue(secDate); + + this.#setValue(); + + if (this.#hasRendered) { + this.secondDp.setDateRange({ minDate: this.firstDp.selectedDate }); + this.secondDp.enable(); + } + + Ut.trigger(this.opts.onChange, this); + this.target.dxTrigger('changed'); + + return true; } - /** - * TwoDatePicker, Restore Selected Val - */ - restoreValue(dates) { - - if (!dates || !dates.length) { - return; - } - - const [ fromDate, toDate ] = dates; - - if (fromDate.getTime() <= toDate.getTime()) { - this.datepicker[0].restoreValue(fromDate); - this.datepicker[1].restoreValue(toDate); - this.datepicker[1].setDateRange({ minDate: fromDate }); - } - } - - /** - * TwoDatePicker, Set Events - */ - setEvents() { - - let that = this; - - // Restore Selected - Handler - - if (this.isInput) { - this.target.dxOn('keydown.two_datepicker paste.two_datepicker', e => e.preventDefault()); - //this.target.dxOn('restore.two_datepicker', (_, t) => this.restoreValue(t.value)); - } - - if (this.isToggled) { - - // Click on UI - fixed Handler - - this.ui.dxOn('click', e => e.stopPropagation()); - - this.target.dxOn('click.two_datepicker', e => { - - e.stopPropagation(); - - if (!that.opts.hiddenMode) { - that.show(); - } - }); - - // Clear Icon Handler - - if (this.opts.cancelable) { - - this.target.dxOn('clear', _ => { - that.clear(); - Ut.trigger(that.opts.onChange, that, null, false); - }); - } - - // Click outside - Handler - - document.body.dxOn('click.two_datepicker' + this.ekey, _ => this.hide()); - - let tm = null; - - // Resize window Handler - - window.dxOn('scroll.two_datepicker resize.two_datepicker' + this.ekey + ' orientationchange.two_datepicker' + this.ekey, _ => { - - if (tm) { - clearTimeout(tm); - tm = null; - } - - tm = setTimeout(_ => that.setPosition(), 100); - }); - } - } - - /** - * TwoDatePicker, Destroy Widget - */ destroy() { - this.datepicker[0].destroy(); - this.datepicker[1].destroy(); + this.firstDp.destroy(); + this.secondDp.destroy(); - this.ui.remove(); + if (this.#hasRendered) { + + this.el.remove(); - if (this.isToggled) { - - this.target.dxOff('click.two_datepicker keydown.two_datepicker paste.two_datepicker restore.two_datepicker'); - - document.body.dxOff('click.two_datepicker' + this.ekey); - window.dxOff('scroll.two_datepicker resize.two_datepicker' + this.ekey + ' orientationchange.two_datepicker' + this.ekey); + if (this.#isToggled) { + document.dxOff(`pointerdown.twodatepicker.${this.ekey}`); + window.dxOff(`resize.two_datepicker.${this.ekey} orientationchange.two_datepicker.${this.ekey}`); + } } this.target.dxRemoveData('two_datepicker'); - - Stack.delete(this); + this.target.dxRemoveWidget(); Ut.trigger(this.opts.onDestroy, this); } diff --git a/src/widgets/two-datepicker/two-datepicker.css b/src/widgets/two-datepicker/two-datepicker.css index b8fa5e5..dd2c307 100644 --- a/src/widgets/two-datepicker/two-datepicker.css +++ b/src/widgets/two-datepicker/two-datepicker.css @@ -4,7 +4,7 @@ display: flex; flex-wrap: nowrap; border-radius: 4px; - overflow: hidden; + background-color: #fff; } .sp-two-datepicker .ui-col:last-child {