@lemonadejs/calendar
Version:
LemonadeJS reactive JavaScript calendar plugin
1,160 lines (1,015 loc) • 40.4 kB
JavaScript
if (! lemonade && typeof (require) === 'function') {
var lemonade = require('lemonadejs');
}
if (! Modal && typeof (require) === 'function') {
var Modal = require('@lemonadejs/modal');
}
if (! utils && typeof (require) === 'function') {
var utils = require('@jsuites/utils');
}
const Helpers = utils.Helpers;
const Mask = utils.Mask;
; (function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define(factory) :
global.Calendar = factory();
}(this, (function () {
class CustomEvents extends Event {
constructor(type, props, options) {
super(type, {
bubbles: true,
composed: true,
...options,
});
if (props) {
for (const key in props) {
// Avoid assigning if property already exists anywhere on `this`
if (! (key in this)) {
this[key] = props[key];
}
}
}
}
}
// Dispatcher
const Dispatch = function(method, type, options) {
// Try calling the method directly if provided
if (typeof method === 'function') {
let a = Object.values(options);
return method(...a);
} else if (this.tagName) {
this.dispatchEvent(new CustomEvents(type, options));
}
}
// Translations
const T = function(t) {
if (typeof(document) !== "undefined" && document.dictionary) {
return document.dictionary[t] || t;
} else {
return t;
}
}
const filterData = function(year, month) {
// Data for the month
let data = {};
if (Array.isArray(this.data)) {
this.data.map(function (v) {
if (!v || typeof v !== 'object' || typeof v.date !== 'string') {
return;
}
let d = year + '-' + Helpers.two(month + 1);
if (v.date.substring(0, 7) === d) {
if (!data[v.date]) {
data[v.date] = [];
}
data[v.date].push(v);
}
});
}
return data;
}
// Get the short weekdays name
const getWeekdays = function(firstDayOfWeek) {
const reorderedWeekdays = [];
for (let i = 0; i < 7; i++) {
const dayIndex = (firstDayOfWeek + i) % 7;
reorderedWeekdays.push(Helpers.weekdays[dayIndex]);
}
return reorderedWeekdays.map(w => {
return { title: w.substring(0, 1) };
});
}
const Views = function(self) {
const view = {};
// Create years container
view.years = [];
view.months = [];
view.days = [];
view.hours = [];
view.minutes = [];
for (let i = 0; i < 16; i++) {
view.years.push({
title: null,
value: null,
selected: false,
});
}
for (let i = 0; i < 12; i++) {
view.months.push({
title: null,
value: null,
selected: false,
});
}
for (let i = 0; i < 42; i++) {
view.days.push({
title: null,
value: null,
selected: false,
});
}
for (let i = 0; i < 24; i++) {
view.hours.push({
title: Helpers.two(i),
value: i
});
}
for (let i = 0; i < 60; i++) {
view.minutes.push({
title: Helpers.two(i),
value: i
});
}
view.years.update = function(date) {
let year = date.getUTCFullYear();
let start = year - (year % 16);
for (let i = 0; i < 16; i++) {
let item = view.years[i];
let value = start + i;
item.title = value
item.value = value;
if (self.cursor.y === value) {
item.selected = true;
// Current item
self.cursor.current = item;
} else {
item.selected = false;
}
}
}
view.months.update = function(date) {
let year = date.getUTCFullYear();
for (let i = 0; i < 12; i++) {
let item = view.months[i];
item.title = Helpers.months[i].substring(0,3);
item.value = i;
if (self.cursor.y === year && self.cursor.m === i) {
item.selected = true;
// Current item
self.cursor.current = item;
} else {
item.selected = false;
}
}
}
view.days.update = function(date) {
let year = date.getUTCFullYear();
let month = date.getUTCMonth();
let data = filterData.call(self, year, month);
// First day
let tmp = new Date(Date.UTC(year, month, 1, 0, 0, 0));
let firstDayOfMonth = tmp.getUTCDay();
let firstDayOfWeek = self.startingDay ?? 0;
// Calculate offset based on desired first day of week. firstDayOfWeek: 0 = Sunday, 1 = Monday, 2 = Tuesday, etc.
let offset = (firstDayOfMonth - firstDayOfWeek + 7) % 7;
let index = -1 * offset;
for (let i = 0; i < 42; i++) {
index++;
// Item
let item = view.days[i];
// Get the day
tmp = new Date(Date.UTC(year, month, index, 0, 0, 0));
// Day
let day = tmp.getUTCDate();
// Create the item
item.title = day;
item.value = index;
item.number = Helpers.dateToNum(tmp.toISOString().substring(0, 10));
// Reset range properties for each item
item.start = false;
item.end = false;
item.range = false;
item.last = false;
item.disabled = false;
item.data = null;
// Check selections
if (tmp.getUTCMonth() !== month) {
// Days are not in the current month
item.grey = true;
} else {
// Check for data
let d = [ year, Helpers.two(month+1), Helpers.two(day) ].join('-');
if (data && data[d]) {
item.data = data[d];
}
item.grey = false;
}
// Month
let m = tmp.getUTCMonth();
// Select cursor
if (self.cursor.y === year && self.cursor.m === m && self.cursor.d === day) {
item.selected = true;
// Current item
self.cursor.current = item;
} else {
item.selected = false;
}
// Valid ranges
if (self.validRange) {
if (typeof self.validRange === 'function') {
let ret = self.validRange(day,m,year,item);
if (typeof ret !== 'undefined') {
item.disabled = ret;
}
} else {
let current = year + '-' + Helpers.two(m+1) + '-' + Helpers.two(day);
let test1 = !self.validRange[0] || current >= self.validRange[0].substr(0, 10);
let test2 = !self.validRange[1] || current <= self.validRange[1].substr(0, 10);
if (! (test1 && test2)) {
item.disabled = true;
}
}
}
// Select range
if (self.range && self.rangeValues) {
// Only mark start/end if the number matches
item.start = self.rangeValues[0] === item.number;
item.end = self.rangeValues[1] === item.number;
// Mark as part of range if between start and end
item.range = self.rangeValues[0] && self.rangeValues[1] && self.rangeValues[0] <= item.number && self.rangeValues[1] >= item.number;
}
}
}
return view;
}
const isTrue = function(v) {
return v === true || v === 'true';
}
const isNumber = function (num) {
if (typeof(num) === 'string') {
num = num.trim();
}
return !isNaN(num) && num !== null && num !== '';
}
const Calendar = function(children, { onchange, onload, track }) {
let self = this;
// Event
let change = self.onchange;
self.onchange = null;
// Coerce startingDay to a number so string inputs ('1') don't trigger string concat in modulo math
if (typeof self.startingDay !== 'number') {
self.startingDay = Number(self.startingDay) || 0;
}
// Weekdays
self.weekdays = getWeekdays(self.startingDay);
// Cursor
self.cursor = {};
// Time
self.time = !! self.time;
// Range values
self.rangeValues = null;
// Calendar date
let date = new Date();
// Views
const views = Views(self);
const hours = views.hours;
const minutes = views.minutes;
// Initial view
self.view = 'days';
// Auto Input
if (self.input === 'auto') {
self.input = document.createElement('input');
self.input.type = 'text';
}
// Get the position of the data based on the view
const getPosition = function() {
let position = 2;
if (self.view === 'years') {
position = 0;
} else if (self.view === 'months') {
position = 1;
}
return position;
}
const setView = function(e) {
if (typeof e === 'object') {
e = this.getAttribute('data-view');
}
// Valid views
const validViews = ['days', 'months', 'years'];
// Define new view
if (validViews.includes(e) && self.view !== e) {
self.view = e;
}
}
const reloadView = function(reset) {
if (reset) {
// Update options to the view
self.options = views[self.view];
}
// Update the values of hte options of hte view
views[self.view]?.update.call(self, date);
}
const getValue = function() {
let value = null;
if (isTrue(self.range)) {
if (Array.isArray(self.rangeValues)) {
if (isTrue(self.numeric)) {
value = self.rangeValues;
} else {
value = [
Helpers.numToDate(self.rangeValues[0]).substring(0, 10),
Helpers.numToDate(self.rangeValues[1]).substring(0, 10)
];
}
}
} else {
value = getDate();
if (isTrue(self.numeric)) {
value = Helpers.dateToNum(value);
}
}
return value;
}
const setValue = function(v) {
let d = new Date();
if (v) {
// Accept native Date objects by converting to ISO string
if (v instanceof Date) {
v = v.toISOString().substring(0, 10);
}
if (isTrue(self.range)) {
if (v) {
if (! Array.isArray(v)) {
v = v.toString().split(',');
}
self.rangeValues = [...v];
if (v[0] && typeof (v[0]) === 'string' && v[0].indexOf('-')) {
self.rangeValues[0] = Helpers.dateToNum(v[0]);
}
if (v[1] && typeof (v[1]) === 'string' && v[1].indexOf('-')) {
self.rangeValues[1] = Helpers.dateToNum(v[1]);
}
v = v[0];
}
} else if (typeof v === 'string' && v.includes(',')) {
v = v.split(',')[0];
}
if (v) {
v = isNumber(v) ? Helpers.numToDate(v) : v;
d = new Date(v + ' GMT+0');
}
// if no date is defined
if (! Helpers.isValidDate(d)) {
d = new Date();
}
}
// Update the internal calendar date
setDate(d, true);
// Update the view
reloadView();
}
const getDate = function() {
let v = [ self.cursor.y, self.cursor.m, self.cursor.d, self.hour, self.minute ];
let d = new Date(Date.UTC(...v));
// Update the headers of the calendar
if (self.time) {
return d.toISOString().substring(0, 19).replace('T', ' ');
} else {
return d.toISOString().substring(0, 10);
}
}
const setDate = function(d, update) {
if (Array.isArray(d)) {
d = new Date(Date.UTC(...d));
} else if (typeof(d) === 'string') {
d = new Date(d);
}
// Update the date
let value = d.toISOString().substring(0,10).split('-');
let month = Helpers.months[parseInt(value[1])-1];
let year = parseInt(value[0]);
if (self.month !== month) {
self.month = month;
}
if (self.year !== year) {
self.year = year;
}
// Update the time
let time = d.toISOString().substring(11,19).split(':');
let hour = parseInt(time[0]);
let minute = parseInt(time[1]);
if (self.hour !== hour) {
self.hour = hour;
}
if (self.minute !== minute) {
self.minute = minute;
}
// Update internal date
date = d;
// Update cursor information
if (update) {
updateCursor();
}
}
const updateCursor = function() {
self.cursor.y = date.getUTCFullYear();
self.cursor.m = date.getUTCMonth();
self.cursor.d = date.getUTCDate();
}
const resetCursor = function() {
// Remove selection from the current object
let current = self.cursor.current;
// Current item
if (typeof current !== 'undefined') {
current.selected = false;
}
}
const setCursor = function(s) {
// Reset current visual cursor
resetCursor();
// Update cursor based on the object position
if (s) {
// Update current
self.cursor.current = s;
// Update selected property
s.selected = true;
}
updateCursor();
// Update range
if (isTrue(self.range)) {
updateRange(s)
}
Dispatch.call(self, self.onupdate, 'update', {
instance: self,
value: date.toISOString(),
});
}
const select = function(e, s) {
if (self.disabled === true) {
return;
}
// Get new date content
let d = updateDate(s.value, getPosition());
// New date
setDate(new Date(Date.UTC(...d)))
// Based where was the click
if (self.view !== 'days') {
// Back to the days
self.view = 'days';
} else if (! s.disabled) {
if (isTrue(self.range)) {
// Start a new range
if (self.rangeValues && (self.rangeValues[0] >= s.number || self.rangeValues[1])) {
destroyRange();
}
// Range
s.range = true;
// Update range
if (! self.rangeValues) {
s.start = true;
self.rangeValues = [s.number, null];
} else {
s.end = true;
self.rangeValues[1] = s.number;
}
setCursor(s);
} else {
setCursor(s);
update(e);
}
}
}
// Update Calendar
const update = function(e) {
self.setValue(getValue());
if (! (e && e.type === 'click' && e.target.tagName === 'DIV' && self.time === true)) {
self.close({ origin: 'button' });
}
}
const reset = function() {
self.setValue('');
self.close({ origin: 'button' });
}
const updateDate = function(v, position) {
// Current internal date
let value = [date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), self.hour, self.minute, 0];
// Update internal date
value[position] = v;
// Return new value
return value;
}
const move = function(direction) {
// Reset visual cursor
resetCursor();
// Value
let value;
// Update the new internal date
if (self.view === 'days') {
// Select the new internal date
value = updateDate(date.getUTCMonth()+direction, 1);
} else if (self.view === 'months') {
// Select the new internal date
value = updateDate(date.getUTCFullYear()+direction, 0);
} else if (self.view === 'years') {
// Select the new internal date
value = updateDate(date.getUTCFullYear()+(direction*16), 0);
}
// Update view
setDate(value);
// Reload content of the view
reloadView();
}
const getJump = function(e) {
if (e.key === 'ArrowUp' || e.key === 'ArrowDown') {
return self.view === 'days' ? 7 : 4;
}
return 1;
}
const prev = function(e) {
if (e && e.type === 'keydown') {
// Current index
let total = self.options.length;
let position = self.options.indexOf(self.cursor.current) - getJump(e);
if (position < 0) {
// Next month
move(-1);
// New position
position = total + position;
}
// Update cursor
setCursor(self.options[position])
} else {
move(-1);
}
}
const next = function(e) {
if (e && e.type === 'keydown') {
// Current index
let total = self.options.length;
let position = self.options.indexOf(self.cursor.current) + getJump(e);
if (position >= total) {
// Next month
move(1);
// New position
position = position - total;
}
// Update cursor
setCursor(self.options[position])
} else {
move(1);
}
}
const getInput = function() {
let input = self.input;
if (input && input.current) {
input = input.current;
} else {
if (self.input) {
input = self.input;
}
}
return input;
}
const updateRange = function(s) {
if (self.range && self.view === 'days' && self.rangeValues) {
// Creating a range
if (self.rangeValues[0] && ! self.rangeValues[1]) {
let number = s.number;
if (number) {
// Update range properties
for (let i = 0; i < self.options.length; i++) {
let v = self.options[i].number;
// Update property condition
self.options[i].range = v >= self.rangeValues[0] && v <= number;
self.options[i].last = (v === number);
}
}
}
}
}
const destroyRange = function() {
if (self.range) {
for (let i = 0; i < self.options.length; i++) {
if (self.options[i].range !== false) {
self.options[i].range = false;
}
if (self.options[i].start !== false) {
self.options[i].start = false;
}
if (self.options[i].end !== false) {
self.options[i].end = false;
}
if (self.options[i].last !== false) {
self.options[i].last = false;
}
}
self.rangeValues = null;
}
}
const render = function(v) {
if (v) {
if (! Array.isArray(v)) {
v = v.toString().split(',');
}
v = v.map(entry => {
return Mask.render(entry, self.format || 'YYYY-MM-DD');
}).join(',');
}
return v;
}
const normalize = function(v) {
if (v instanceof Date) {
v = Helpers.dateToString ? Helpers.dateToString(v) : v.toISOString().substring(0, 10);
}
if (! Array.isArray(v)) {
v = v.toString().split(',');
}
return v.map(item => {
if (item instanceof Date) {
return Helpers.dateToString ? Helpers.dateToString(item) : item.toISOString().substring(0, 10);
}
if (Number(item) == item) {
return Helpers.numToDate(item);
} else {
if (Helpers.isValidDateFormat(item)) {
return item;
} else if (self.format) {
let tmp = Mask.extractDateFromString(item, self.format);
if (tmp) {
return tmp;
}
}
}
})
}
const extractValueFromInput = function() {
let input = getInput();
if (input) {
let v;
if (input.tagName === 'INPUT' || input.tagName === 'TEXTAREA') {
v = input.value;
} else if (input.isContentEditable) {
v = input.textContent;
}
if (v) {
return normalize(v).join(',');
}
return v;
}
}
const onopen = function() {
let isEditable = false;
let value = self.value;
let input = getInput();
if (input) {
if (input.tagName === 'INPUT' || input.tagName === 'TEXTAREA') {
isEditable = !input.hasAttribute('readonly') && !input.hasAttribute('disabled');
} else if (input.isContentEditable) {
isEditable = true;
}
let ret = extractValueFromInput();
if (ret && ret !== value) {
value = ret;
}
}
if (! isEditable) {
self.content.focus();
}
// Update the internal date values
setValue(value);
// Open event
Dispatch.call(self, self.onopen, 'open', {
instance: self
});
}
const onclose = function(modal, origin) {
// Cancel range events
destroyRange();
// Close event
Dispatch.call(self, self.onclose, 'close', {
instance: self,
origin: origin,
});
}
const dispatchOnChangeEvent = function() {
// Destroy range
destroyRange();
// Update the internal controllers
setValue(self.value);
// Events
Dispatch.call(self, change, 'change', {
instance: self,
value: self.value,
});
// Update input
let input = getInput();
if (input) {
// Update input value
input.value = render(self.value);
// Dispatch event
Dispatch.call(input, null, 'change', {
instance: self,
value: self.value,
});
}
}
const events = {
focusin: (e) => {
if (self.modal && self.isClosed()) {
self.open();
}
},
focusout: (e) => {
if (self.modal && ! self.isClosed()) {
if (! (e.relatedTarget && self.modal.el.contains(e.relatedTarget))) {
self.modal.close({ origin: 'focusout' });
}
}
},
click: (e) => {
if (e.target.classList.contains('lm-calendar-input')) {
self.open();
}
},
keydown: (e) => {
if (self.modal) {
if (e.code === 'ArrowUp' || e.code === 'ArrowDown') {
if (! self.isClosed()) {
self.content.focus();
} else {
self.open();
}
} else if (e.code === 'Enter') {
if (! self.isClosed()) {
update(e);
} else {
self.open();
}
} else if (e.code === 'Escape') {
if (! self.isClosed()) {
self.modal.close({origin: 'escape'});
}
}
}
},
input: (e) => {
let input = e.target;
if (input.classList.contains('lm-calendar-input')) {
if (! isTrue(self.range)) {
// TODO: process with range
// Apply mask
if (self.format) {
Mask.oninput(e, self.format);
}
let value = null;
// Content
let content = (input.tagName === 'INPUT' || input.tagName === 'TEXTAREA') ? input.value : input.textContent;
// Check if that is a valid date
if (Helpers.isValidDateFormat(content)) {
value = content;
} else if (self.format) {
let tmp = Mask.extractDateFromString(content, self.format);
if (tmp) {
value = tmp;
}
}
// Change the calendar view
if (value) {
setValue(value);
}
}
}
}
}
// Onload
onload(() => {
if (self.type !== "inline") {
// Create modal instance
self.modal = {
width: 300,
closed: true,
focus: false,
onopen: onopen,
onclose: onclose,
position: 'absolute',
'auto-close': false,
'auto-adjust': true,
};
// Generate modal
Modal(self.el, self.modal);
}
let ret;
// Create input controls
if (self.input && self.initInput !== false) {
if (! self.input.parentNode) {
self.el.parentNode.insertBefore(self.input, self.el);
}
let input = getInput();
if (input && input.tagName) {
input.classList.add('lm-input');
input.classList.add('lm-calendar-input');
input.addEventListener('click', events.click);
input.addEventListener('input', events.input);
input.addEventListener('keydown', events.keydown);
input.addEventListener('focusin', events.focusin);
input.addEventListener('focusout', events.focusout);
if (self.placeholder) {
input.setAttribute('placeholder', self.placeholder);
}
if (self.onChange) {
input.addEventListener('change', self.onChange);
}
// Retrieve the value
if (self.value) {
input.value = render(self.value);
} else {
let value = extractValueFromInput();
if (value && value !== self.value) {
ret = value;
}
}
}
}
// Update the internal date values
if (ret) {
self.setValue(ret);
} else {
setValue(self.value);
}
// Reload view
reloadView(true);
/**
* Handler keyboard
* @param {object} e - event
*/
self.el.addEventListener('keydown', function(e) {
let prevent = false;
if (e.key === 'ArrowUp' || e.key === 'ArrowLeft') {
if (e.target !== self.content) {
self.content.focus();
}
prev(e);
prevent = true;
} else if (e.key === 'ArrowDown' || e.key === 'ArrowRight') {
if (e.target !== self.content) {
self.content.focus();
}
next(e);
prevent = true;
} else if (e.key === 'Enter') {
if (e.target === self.content) {
// Item
if (self.cursor.current) {
// Select
select(e, self.cursor.current);
prevent = true;
}
}
} else if (e.key === 'Escape') {
if (! self.isClosed()) {
self.close({ origin: 'escape' });
prevent = true;
}
}
if (prevent) {
e.preventDefault();
e.stopImmediatePropagation();
}
});
/**
* Mouse wheel handler
* @param {object} e - mouse event
*/
self.content.addEventListener('wheel', function(e){
if (self.wheel !== false) {
if (e.deltaY < 0) {
prev(e);
} else {
next(e);
}
e.preventDefault();
}
}, { passive: false });
/**
* Range handler
* @param {object} e - mouse event
*/
self.content.addEventListener('mouseover', function(e){
let parent = e.target.parentNode
if (parent === self.content) {
let index = Array.prototype.indexOf.call(parent.children, e.target);
updateRange(self.options[index]);
}
});
// Create event for focus out
self.el.addEventListener("focusout", (e) => {
let input = getInput();
if (e.relatedTarget !== input && ! self.el.contains(e.relatedTarget)) {
self.close({ origin: 'focusout' });
}
});
});
onchange((prop) => {
if (prop === 'view') {
reloadView(true);
} else if (prop === 'startingDay') {
if (typeof self.startingDay !== 'number') {
self.startingDay = Number(self.startingDay) || 0;
}
self.weekdays = getWeekdays(self.startingDay);
} else if (prop === 'value') {
dispatchOnChangeEvent();
}
})
// Tracking variables
track('value');
// Public methods
self.open = function(e) {
if (self.modal) {
if (self.type === 'auto') {
self.type = window.innerWidth > 640 ? self.type = 'default' : 'picker';
}
self.modal.open();
}
}
self.close = function(options) {
if (self.modal) {
if (options && options.origin) {
self.modal.close(options)
} else {
self.modal.close({ origin: 'button' })
}
}
}
self.isClosed = function() {
if (self.modal) {
return self.modal.isClosed();
}
}
self.getValue = function() {
return self.value;
}
self.setValue = function(v) {
// Update value
if (v) {
let ret = normalize(v);
if (isTrue(self.numeric)) {
ret = ret.map(entry => {
return Helpers.dateToNum(entry);
})
}
if (! Array.isArray(v)) {
ret = ret.join(',');
}
if (ret == Number(ret)) {
ret = Number(ret);
}
v = ret;
}
// Events
if (v !== self.value) {
self.value = v;
}
}
self.onevent = function(e) {
if (events[e.type]) {
events[e.type](e);
}
}
self.update = update;
self.next = next;
self.prev = prev;
self.reset = reset;
self.setView = setView;
self.helpers = Helpers;
self.helpers.getDate = Mask.getDate;
return render => render`<div class="lm-calendar" data-grid="{{self.grid}}" data-type="{{self.type}}" data-disabled="{{self.disabled}}" data-starting-day="{{self.startingDay}}">
<div class="lm-calendar-options">
<button type="button" onclick="${reset}">${T('Reset')}</button>
<button type="button" onclick="${update}">${T('Done')}</button>
</div>
<div class="lm-calendar-container" data-view="{{self.view}}">
<div class="lm-calendar-header">
<div>
<div class="lm-calendar-labels"><button type="button" onclick="${setView}" data-view="months">{{self.month}}</button> <button type="button" onclick="${setView}" data-view="years">{{self.year}}</button></div>
<div class="lm-calendar-navigation">
<button type="button" class="lm-calendar-icon lm-ripple" onclick="${prev}" tabindex="0">expand_less</button>
<button type="button" class="lm-calendar-icon lm-ripple" onclick="${next}" tabindex="0">expand_more</button>
</div>
</div>
<div class="lm-calendar-weekdays" :loop="self.weekdays"><div>{{self.title}}</div></div>
</div>
<div class="lm-calendar-content" :loop="self.options" tabindex="0" :ref="self.content">
<div data-start="{{self.start}}" data-end="{{self.end}}" data-last="{{self.last}}" data-range="{{self.range}}" data-event="{{self.data}}" data-grey="{{self.grey}}" data-bold="{{self.bold}}" data-selected="{{self.selected}}" data-disabled="{{self.disabled}}" onclick="${select}">{{self.title}}</div>
</div>
<div class="lm-calendar-footer" data-visible="{{self.footer}}">
<div class="lm-calendar-time" data-visible="{{self.time}}"><select :loop="${hours}" :bind="self.hour" class="lm-calendar-control"><option value="{{self.value}}">{{self.title}}</option></select>:<select :loop="${minutes}" :bind="self.minute" class="lm-calendar-control"><option value="{{self.value}}">{{self.title}}</option></select></div>
<div class="lm-calendar-update"><input type="button" value="${T('Update')}" onclick="${update}" class="lm-ripple lm-input"></div>
</div>
</div>
</div>`
}
// Register the LemonadeJS Component
lemonade.setComponents({ Calendar: Calendar });
// Register the web component
lemonade.createWebComponent('calendar', Calendar);
return function (root, options) {
if (typeof (root) === 'object') {
lemonade.render(Calendar, root, options)
return options;
} else {
return Calendar.call(this, root)
}
}
})));