calendar_events-1.0.0-beta1/js/pickmeup.js
js/pickmeup.js
/** * @package PickMeUp * @author Nazar Mokrynskyi <nazar@mokrynskyi.com> * @author Stefan Petre <www.eyecon.ro> * @license 0BSD */ (function (root, factory) { if (typeof define === 'function' && define.amd) { // AMD //noinspection JSUnresolvedFunction define(factory); } else if (typeof exports === 'object') { // CommonJS module.exports = factory(); } else { // Browser globals root.pickmeup = factory(); } }(this, function () { /** * Functions prefixed with `dom_` are simple convenient wrappers for various operations with DOM */ /** * @param {(Element|NodeList)} element * @param {Function} callback * @param {*} [args=[]] */ function dom_for_collection (element, callback, args) { args = args || []; if (element instanceof Element) { callback.apply(callback, [element].concat(args)); } else { var elements, i; elements = element.length; for (i = 0; i < elements; ++i) { callback.apply(callback, [element[i]].concat(args)); } } } /** * @param {(Element|Element[]|NodeList)} element */ function dom_remove (element) { dom_for_collection(element, function (element) { element.parentElement.removeChild(element); }); } /** * @param {Element} element * @param {string} selector * * @returns {Element} */ function dom_closest_parent (element, selector) { var parent = element; do { parent = parent.parentElement; } while (parent && !dom_matches(parent, selector)); return parent; } /** * @param {Element} element * @param {string} selector * * @returns {boolean} */ function dom_matches (element, selector) { return (element.matches || element.webkitMatchesSelector || element.msMatchesSelector).call(element, selector); } /** * @param {Element} element * @param {string} class_name * * @returns {boolean} */ function dom_has_class (element, class_name) { return element && element.classList.contains(class_name); } /** * @param {Element} element * @param {string} class_name */ function dom_add_class (element, class_name) { element.classList.add(class_name); } /** * @param {Element} element * @param {string} class_name */ function dom_remove_class (element, class_name) { element.classList.remove(class_name); } /** * @param {Element} element * @param {string} selector * * @returns {Element} */ function dom_query (element, selector) { return element.querySelector(selector); } /** * @param {Element} element * @param {string} selector * * @returns {Element[]} */ function dom_query_all (element, selector) { return Array.prototype.slice.call(element.querySelectorAll(selector)); } /** * @param {Element} target * @param {(Element|Window)} element * @param {string} event * @param {Function} callback */ function dom_on (target, element, event, callback) { if (event.indexOf(' ') !== -1) { var events = event.split(' '), events_number = events.length, i; for (i = 0; i < events_number; ++i) { dom_on(target, element, events[i], callback); } } else { target.__pickmeup.events.push([element, event, callback]); element.addEventListener(event, callback); } } /** * @param {Element} target * @param {(Element|Window)} [element=undefined] * @param {string} [event=''] * @param {Function} [callback=undefined] */ function dom_off (target, element, event, callback) { var events, events_number, i; if (event && event.indexOf(' ') !== -1) { events = event.split(' '); events_number = events.length; for (i = 0; i < events_number; ++i) { dom_off(target, element, events[i], callback); } } else { events = target.__pickmeup.events; events_number = events.length; for (i = 0; i < events_number; ++i) { if ( (element && element !== events[i][0]) || (event && event !== events[i][1]) || (callback && callback !== events[i][2]) ) { continue; } events[i][0].removeEventListener(events[i][1], events[i][2]); } } } /** * @param {Element} element * * @returns {{top: number, left: number}} */ function dom_offset (element) { var rect = element.getBoundingClientRect(); return { top : rect.top + window.pageYOffset - document.documentElement.clientTop, left : rect.left + window.pageXOffset - document.documentElement.clientLeft }; } /** * @param {Element} element * @param {string} event * @param {Object} [detail=undefined] * * @return {boolean} */ function dom_dispatch_event (element, event, detail) { var e = document.createEvent('Event'); if (detail) { e.detail = detail; } e.initEvent('pickmeup-' + event, false, true); return element.dispatchEvent(e); } /** * Functions prefixed with `date_` are simple convenient wrappers for various operations with dates */ /** * @param {Date} date * * @returns {number} */ function date_get_max_days (date) { var tmpDate = new Date(date), d = 28, m = tmpDate.getMonth(); while (tmpDate.getMonth() === m) { ++d; tmpDate.setDate(d); } return d - 1; } /** * @param {Date} date * @param {number} number_of_days */ function date_add_days (date, number_of_days) { date.setDate(date.getDate() + number_of_days); } /** * @param {Date} date * @param {number} number_of_months */ function date_add_months (date, number_of_months) { var day = date.getDate(); date.setDate(1); date.setMonth(date.getMonth() + number_of_months); date.setDate(Math.min(day, date_get_max_days(date))); } /** * @param {Date} date * @param {number} number_of_years */ function date_add_years (date, number_of_years) { var day = date.getDate(); date.setDate(1); date.setFullYear(date.getFullYear() + number_of_years); date.setDate(Math.min(day, date_get_max_days(date))); } /** * @param {Date} date * * @returns {number} */ function date_get_day_of_the_year (date) { var now = new Date(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0); var then = new Date(date.getFullYear(), 0, 0, 0, 0, 0); var time = now - then; return Math.floor(time / (24 * 60 * 60 * 1000)); } /** * @param {Element} target */ function fill (target) { var root_element = target.__pickmeup.element, options = target.__pickmeup.options, current_cal = Math.floor(options.calendars / 2), actual_date = options.date, current_date = options.current, min_date = options.min ? new Date(options.min) : null, max_date = options.max ? new Date(options.max) : null, local_date, header, instance, shown_date_from, shown_date_to, tmp_date; if (min_date) { min_date.setDate(1); date_add_months(min_date, 1); date_add_days(min_date, -1); } if (max_date) { max_date.setDate(1); date_add_months(max_date, 1); date_add_days(max_date, -1); } /** * Remove old content except header navigation */ dom_remove(dom_query_all(root_element, '.pmu-instance > :not(nav)')); /** * If several calendars should be shown */ for (var i = 0; i < options.calendars; i++) { local_date = new Date(current_date); reset_time(local_date); instance = dom_query_all(root_element, '.pmu-instance')[i]; if (dom_has_class(root_element, 'pmu-view-years')) { date_add_years(local_date, (i - current_cal) * 12); header = (local_date.getFullYear() - 6) + ' - ' + (local_date.getFullYear() + 5); } else if (dom_has_class(root_element, 'pmu-view-months')) { date_add_years(local_date, i - current_cal); header = local_date.getFullYear(); } else if (dom_has_class(root_element, 'pmu-view-days')) { date_add_months(local_date, i - current_cal); if (typeof options.title_format === 'function') { header = options.title_format(local_date, options.locales[options.locale]); } else { header = format_date(local_date, options.title_format, options.locales[options.locale]); } } if (!shown_date_to) { if (max_date) { // If all dates in this month (months in year or years in years block) are after max option - set next month as current // in order not to show calendar with all disabled dates tmp_date = new Date(local_date); if (options.select_day) { date_add_months(tmp_date, options.calendars - 1); } else if (options.select_month) { date_add_years(tmp_date, options.calendars - 1); } else { date_add_years(tmp_date, (options.calendars - 1) * 12); } if (tmp_date > max_date) { --i; date_add_months(current_date, -1); shown_date_to = undefined; continue; } } } shown_date_to = new Date(local_date); if (!shown_date_from) { shown_date_from = new Date(local_date); // If all dates in this month are before min option - set next month as current in order not to show calendar with all disabled dates shown_date_from.setDate(1); date_add_months(shown_date_from, 1); date_add_days(shown_date_from, -1); if (min_date && min_date > shown_date_from) { --i; date_add_months(current_date, 1); shown_date_from = undefined; continue; } } dom_query(instance, '.pmu-month').innerHTML = header; var is_year_selected = function (year) { return ( options.mode === 'range' && year >= new Date(actual_date[0]).getFullYear() && year <= new Date(actual_date[1]).getFullYear() ) || ( options.mode === 'multiple' && actual_date.reduce(function (prev, current) { prev.push(new Date(current).getFullYear()); return prev; }, []).indexOf(year) !== -1 ) || new Date(actual_date).getFullYear() === year; }; var is_months_selected = function (year, month) { var first_year = new Date(actual_date[0]).getFullYear(), lastyear = new Date(actual_date[1]).getFullYear(), first_month = new Date(actual_date[0]).getMonth(), last_month = new Date(actual_date[1]).getMonth(); return ( ( options.mode === 'range' && ( (year > first_year && year < lastyear) || (year > first_year && year === lastyear && month <= last_month) || (year === first_year && year < lastyear && month >= first_month) || (year === first_year && year === lastyear && month >= first_month && month <= last_month) ) ) || ( options.mode === 'multiple' && actual_date.reduce(function (prev, current) { current = new Date(current); prev.push(current.getFullYear() + '-' + current.getMonth()); return prev; }, []).indexOf(year + '-' + month) !== -1 ) || ( new Date(actual_date).getFullYear() === year && new Date(actual_date).getMonth() === month ) ); }; (function () { var years_elements = [], start_from_year = local_date.getFullYear() - 6, min_year = new Date(options.min).getFullYear(), max_year = new Date(options.max).getFullYear(), year, year_element, j; for (j = 0; j < 12; ++j) { year = start_from_year + j; year_element = document.createElement('div'); year_element.textContent = year; year_element.__pickmeup_year = year; if ( (options.min && year < min_year) || (options.max && year > max_year) ) { dom_add_class(year_element, 'pmu-disabled'); } else if (is_year_selected(year)) { dom_add_class(year_element, 'pmu-selected'); } years_elements.push(year_element); } instance.appendChild(options.instance_content_template(years_elements, 'pmu-years')); })(); (function () { var months_elements = [], current_year = local_date.getFullYear(), min_year = new Date(options.min).getFullYear(), min_month = new Date(options.min).getMonth(), max_year = new Date(options.max).getFullYear(), max_month = new Date(options.max).getMonth(), month, month_element; for (month = 0; month < 12; ++month) { month_element = document.createElement('div'); month_element.textContent = options.locales[options.locale].monthsShort[month]; month_element.__pickmeup_month = month; month_element.__pickmeup_year = current_year; if ( ( options.min && ( current_year < min_year || ( month < min_month && current_year === min_year ) ) ) || ( options.max && ( current_year > max_year || ( month > max_month && current_year >= max_year ) ) ) ) { dom_add_class(month_element, 'pmu-disabled'); } else if (is_months_selected(current_year, month)) { dom_add_class(month_element, 'pmu-selected'); } months_elements.push(month_element); } instance.appendChild(options.instance_content_template(months_elements, 'pmu-months')); })(); (function () { var days_elements = [], current_month = local_date.getMonth(), today = reset_time(new Date).valueOf(), day, day_element, from_user, val, disabled, selected; // Correct first day in calendar taking into account the first day of the week (Sunday or Monday) (function () { local_date.setDate(1); var day = (local_date.getDay() - options.first_day) % 7; date_add_days(local_date, -(day + (day < 0 ? 7 : 0))); })(); for (day = 0; day < 42; ++day) { day_element = document.createElement('div'); day_element.textContent = local_date.getDate(); day_element.__pickmeup_day = local_date.getDate(); day_element.__pickmeup_month = local_date.getMonth(); day_element.__pickmeup_year = local_date.getFullYear(); if (current_month !== local_date.getMonth()) { dom_add_class(day_element, 'pmu-not-in-month'); } if (local_date.getDay() === 0) { dom_add_class(day_element, 'pmu-sunday'); } else if (local_date.getDay() === 6) { dom_add_class(day_element, 'pmu-saturday'); } from_user = options.render(new Date(local_date)) || {}; // We only reset time for this value in order to deal with Summer/Winter time, but changing `local_date` itself will break days incrementing val = reset_time(new Date(local_date)).valueOf(); disabled = (options.min && options.min > local_date) || (options.max && options.max < local_date); selected = options.date.valueOf() === val || ( options.date instanceof Array && options.date.reduce(function (prev, date) { return prev || val === date.valueOf(); }, false) ) || ( options.mode === 'range' && val >= options.date[0] && val <= options.date[1] ); if (from_user.disabled || (!('disabled' in from_user) && disabled)) { dom_add_class(day_element, 'pmu-disabled'); } else if (from_user.selected || (!('selected' in from_user) && selected)) { dom_add_class(day_element, 'pmu-selected'); } if (val === today) { dom_add_class(day_element, 'pmu-today'); } if (from_user.class_name) { from_user.class_name.split(' ').forEach( dom_add_class.bind(day_element, day_element) ); } days_elements.push(day_element); // Move to the next day date_add_days(local_date, 1); } instance.appendChild(options.instance_content_template(days_elements, 'pmu-days')); })(); } shown_date_from.setDate(1); shown_date_to.setDate(1); date_add_months(shown_date_to, 1); date_add_days(shown_date_to, -1); var prev = dom_query(root_element, '.pmu-prev'), next = dom_query(root_element, '.pmu-next'); if (prev) { prev.style.visibility = options.min && options.min >= shown_date_from ? 'hidden' : 'visible'; } if (next) { next.style.visibility = options.max && options.max <= shown_date_to ? 'hidden' : 'visible'; } dom_dispatch_event(target, 'fill'); } function parse_date (date, options) { var format = options.format, separator = options.separator, locale = options.locales[options.locale], i; if (date instanceof Date || typeof date === 'number') { return reset_time(new Date(date)); } else if (!date) { return reset_time(new Date); } else if (date instanceof Array) { date = date.slice(); for (i = 0; i < date.length; ++i) { date[i] = parse_date(date[i], options); } return date; } var splitted_date = date.split(separator); if (splitted_date.length > 1) { splitted_date.forEach(function (element, index, array) { array[index] = parse_date(element.trim(), options); }); return splitted_date; } separator = [].concat(locale.daysShort, locale.daysMin, locale.days, locale.monthsShort, locale.months); separator = separator.map(function (item) { return '(' + item + ')'; }); separator = new RegExp('[^0-9a-zA-Z' + separator.join('') + ']+'); var parts = date.split(separator), against = format.split(separator), d, m, y, h, min, now = new Date(); for (i = 0; i < parts.length; i++) { switch (against[i]) { case 'b': m = locale.monthsShort.indexOf(parts[i]); break; case 'B': m = locale.months.indexOf(parts[i]); break; case 'd': case 'e': case 't': d = parseInt(parts[i], 10); break; case 'm': m = parseInt(parts[i], 10) - 1; break; case 'Y': case 'y': y = parseInt(parts[i], 10); y += y > 100 ? 0 : (y < 29 ? 2000 : 1900); break; case 'H': case 'I': case 'k': case 'l': h = parseInt(parts[i], 10); break; case 'P': case 'p': if (/pm/i.test(parts[i]) && h < 12) { h += 12; } else if (/am/i.test(parts[i]) && h >= 12) { h -= 12; } break; case 'M': min = parseInt(parts[i], 10); break; } } var parsed_date = new Date( y === undefined ? now.getFullYear() : y, m === undefined ? now.getMonth() : m, d === undefined ? now.getDate() : d, h === undefined ? now.getHours() : h, min === undefined ? now.getMinutes() : min, 0 ); if (isNaN(parsed_date * 1)) { parsed_date = new Date; } return reset_time(parsed_date); } function reset_time (date) { date.setHours(0, 0, 0, 0); return date; } function format_date (date, format, locale) { var m = date.getMonth(); var d = date.getDate(); var y = date.getFullYear(); var w = date.getDay(); var hr = date.getHours(); var pm = (hr >= 12); var ir = (pm) ? (hr - 12) : hr; var dy = date_get_day_of_the_year(date); if (ir === 0) { ir = 12; } var min = date.getMinutes(); var sec = date.getSeconds(); var parts = format.split(''), part; for (var i = 0; i < parts.length; i++) { part = parts[i]; switch (part) { case 'a': part = locale.daysShort[w]; break; case 'A': part = locale.days[w]; break; case 'b': part = locale.monthsShort[m]; break; case 'B': part = locale.months[m]; break; case 'C': part = 1 + Math.floor(y / 100); break; case 'd': part = (d < 10) ? ("0" + d) : d; break; case 'e': part = d; break; case 'H': part = (hr < 10) ? ("0" + hr) : hr; break; case 'I': part = (ir < 10) ? ("0" + ir) : ir; break; case 'j': part = (dy < 100) ? ((dy < 10) ? ("00" + dy) : ("0" + dy)) : dy; break; case 'k': part = hr; break; case 'l': part = ir; break; case 'm': part = (m < 9) ? ("0" + (1 + m)) : (1 + m); break; case 'M': part = (min < 10) ? ("0" + min) : min; break; case 'p': case 'P': part = pm ? "PM" : "AM"; break; case 's': part = Math.floor(date.getTime() / 1000); break; case 'S': part = (sec < 10) ? ("0" + sec) : sec; break; case 't': // Calculate the last day of the month part = new Date(y, m + 1, 0).getDate(); break; case 'u': part = w + 1; break; case 'w': part = w; break; case 'y': part = ('' + y).substr(2, 2); break; case 'Y': part = y; break; } parts[i] = part; } return parts.join(''); } /** * @param {Element} target * @param {Date} new_date */ function update_date (target, new_date) { var options = target.__pickmeup.options, i; reset_time(new_date); (function () { var new_value; switch (options.mode) { case 'multiple': new_value = new_date.valueOf(); for (i = 0; i < options.date.length; ++i) { if (options.date[i].valueOf() === new_value) { options.date.splice(i, 1); return; } } options.date.push(new_date); break; case 'range': if (!options.lastSel) { options.date[0] = new_date; } if (new_date <= options.date[0]) { options.date[1] = options.date[0]; options.date[0] = new_date; } else { options.date[1] = new_date; } options.lastSel = !options.lastSel; break; default: options.date = new_date.valueOf(); break; } })(); var prepared_date = prepare_date(options); if (dom_matches(target, 'input')) { //noinspection JSUndefinedPropertyAssignment target.value = options.mode === 'single' ? prepared_date.formatted_date : prepared_date.formatted_date.join(options.separator); } dom_dispatch_event(target, 'change', prepared_date); if ( !options.flat && options.hide_on_select && ( options.mode !== 'range' || !options.lastSel ) ) { options.bound.hide(); } } /** * @param {Element} target * @param {Event} event * * @returns {boolean} */ function click (target, event) { //noinspection JSValidateTypes /** * @type {Element} */ var element = event.target; if (!dom_has_class(element, 'pmu-button')) { element = dom_closest_parent(element, '.pmu-button'); } if (!dom_has_class(element, 'pmu-button') || dom_has_class(element, 'pmu-disabled')) { return false; } event.preventDefault(); event.stopPropagation(); var options = target.__pickmeup.options, instance = dom_closest_parent(element, '.pmu-instance'), root = instance.parentElement, instance_index = dom_query_all(root, '.pmu-instance').indexOf(instance); if (dom_matches(element.parentElement, 'nav')) { if (dom_has_class(element, 'pmu-month')) { date_add_months(options.current, instance_index - Math.floor(options.calendars / 2)); if (dom_has_class(root, 'pmu-view-years')) { // Shift back to current date, otherwise with min value specified may jump on few (tens) years forward if (options.mode !== 'single') { options.current = new Date(options.date[options.date.length - 1]); } else { options.current = new Date(options.date); } if (options.select_day) { dom_remove_class(root, 'pmu-view-years'); dom_add_class(root, 'pmu-view-days'); } else if (options.select_month) { dom_remove_class(root, 'pmu-view-years'); dom_add_class(root, 'pmu-view-months'); } } else if (dom_has_class(root, 'pmu-view-months')) { if (options.select_year) { dom_remove_class(root, 'pmu-view-months'); dom_add_class(root, 'pmu-view-years'); } else if (options.select_day) { dom_remove_class(root, 'pmu-view-months'); dom_add_class(root, 'pmu-view-days'); } } else if (dom_has_class(root, 'pmu-view-days')) { if (options.select_month) { dom_remove_class(root, 'pmu-view-days'); dom_add_class(root, 'pmu-view-months'); } else if (options.select_year) { dom_remove_class(root, 'pmu-view-days'); dom_add_class(root, 'pmu-view-years'); } } } else { if (dom_has_class(element, 'pmu-prev')) { options.bound.prev(false); } else { options.bound.next(false); } } } else { if (dom_has_class(root, 'pmu-view-years')) { options.current.setFullYear(element.__pickmeup_year); if (options.select_month) { dom_remove_class(root, 'pmu-view-years'); dom_add_class(root, 'pmu-view-months'); } else if (options.select_day) { dom_remove_class(root, 'pmu-view-years'); dom_add_class(root, 'pmu-view-days'); } else { options.bound.update_date(options.current); } } else if (dom_has_class(root, 'pmu-view-months')) { options.current.setMonth(element.__pickmeup_month); options.current.setFullYear(element.__pickmeup_year); if (options.select_day) { dom_remove_class(root, 'pmu-view-months'); dom_add_class(root, 'pmu-view-days'); } else { options.bound.update_date(options.current); } // Move current month to the first place (needed for multiple calendars) date_add_months(options.current, Math.floor(options.calendars / 2) - instance_index); } else { var new_date = new Date(options.current); new_date.setYear(element.__pickmeup_year); new_date.setMonth(element.__pickmeup_month); new_date.setDate(element.__pickmeup_day); options.bound.update_date(new_date); } } options.bound.fill(); return true; } function prepare_date (options) { var result; if (options.mode === 'single') { result = new Date(options.date); return { formatted_date : format_date(result, options.format, options.locales[options.locale]), date : result }; } else { result = { formatted_date : [], date : [] }; options.date.forEach(function (val) { var date = new Date(val); result.formatted_date.push(format_date(date, options.format, options.locales[options.locale])); result.date.push(date); }); return result; } } /** * @param {Element} target * @param {boolean} [force=false] */ function show (target, force) { var root_element = target.__pickmeup.element, value; if (force || dom_has_class(root_element, 'pmu-hidden')) { var options = target.__pickmeup.options, position = dom_offset(target), viewport = { l : window.pageXOffset, t : window.pageYOffset, w : document.documentElement.clientWidth, h : document.documentElement.clientHeight }, top = position.top, left = position.left; options.bound.fill(); if (dom_matches(target, 'input')) { value = target.value; if (value) { options.bound.set_date(value); } dom_on( target, target, 'keydown', function (e) { if (e.which === 9) { options.bound.hide(); } } ); options.lastSel = false; } if (!dom_dispatch_event(target, 'show')) { return; } if (!options.flat) { dom_remove_class(root_element, 'pmu-hidden'); if (options.position instanceof Function) { position = options.position.call(target); left = position.left; top = position.top; } else { switch (options.position) { case 'top': top -= root_element.offsetHeight; break; case 'left': left -= root_element.offsetWidth; break; case 'right': left += target.offsetWidth; break; case 'bottom': top += target.offsetHeight; break; } if (top + root_element.offsetHeight > viewport.t + viewport.h) { top = position.top - root_element.offsetHeight; } if (top < viewport.t) { top = position.top + target.offsetHeight; } if (left + root_element.offsetWidth > viewport.l + viewport.w) { left = position.left - root_element.offsetWidth; } if (left < viewport.l) { left = position.left + target.offsetWidth; } left += 'px'; top += 'px'; } root_element.style.left = left; root_element.style.top = top; setTimeout(function () { dom_on(target, document.documentElement, 'click', options.bound.hide); dom_on(target, window, 'resize', options.bound.forced_show); }); } } } /** * @param {Element} target * @param {Event} event */ function hide (target, event) { var root_element = target.__pickmeup.element, options = target.__pickmeup.options; //noinspection JSBitwiseOperatorUsage,JSCheckFunctionSignatures if ( !event || !event.target || //Called directly ( event.target !== target && //Clicked not on element itself !(root_element.compareDocumentPosition(event.target) & 16) //And not on its children ) ) { if (dom_dispatch_event(target, 'hide')) { dom_add_class(root_element, 'pmu-hidden'); dom_off(target, document.documentElement, 'click', options.bound.hide); dom_off(target, window, 'resize', options.bound.forced_show); options.lastSel = false; } } } /** * @param {Element} target */ function update (target) { var options = target.__pickmeup.options; dom_off(target, document.documentElement, 'click', options.bound.hide); dom_off(target, window, 'resize', options.bound.forced_show); options.bound.forced_show(); } /** * @param {Element} target */ function clear (target) { var options = target.__pickmeup.options; if (options.mode !== 'single') { options.date = []; options.lastSel = false; options.bound.fill(); } } /** * @param {Element} target * @param {boolean} [fill=true] */ function prev (target, fill) { if (typeof fill == 'undefined') { fill = true; } var root_element = target.__pickmeup.element; var options = target.__pickmeup.options; if (dom_has_class(root_element, 'pmu-view-years')) { date_add_years(options.current, -12); } else if (dom_has_class(root_element, 'pmu-view-months')) { date_add_years(options.current, -1); } else if (dom_has_class(root_element, 'pmu-view-days')) { date_add_months(options.current, -1); } if (fill) { options.bound.fill(); } } /** * @param {Element} target * @param {boolean} [fill=true] */ function next (target, fill) { if (typeof fill == 'undefined') { fill = true; } var root_element = target.__pickmeup.element; var options = target.__pickmeup.options; if (dom_has_class(root_element, 'pmu-view-years')) { date_add_years(options.current, 12); } else if (dom_has_class(root_element, 'pmu-view-months')) { date_add_years(options.current, 1); } else if (dom_has_class(root_element, 'pmu-view-days')) { date_add_months(options.current, 1); } if (fill) { options.bound.fill(); } } /** * @param {Element} target * @param {boolean} [formatted=true] */ function get_date (target, formatted) { var options = target.__pickmeup.options, prepared_date = prepare_date(options); if (typeof formatted === 'string') { var date = prepared_date.date; if (date instanceof Date) { return format_date(date, formatted, options.locales[options.locale]); } else { return date.map(function (value) { return format_date(value, formatted, options.locales[options.locale]); }); } } else { return prepared_date[formatted ? 'formatted_date' : 'date']; } } /** * @param {Element} target * @param {(Date|Date[]|number|number[]|string|string[])} date * @param {Date} [current=undefined] */ function set_date (target, date, current) { var options = target.__pickmeup.options, i; if (!(date instanceof Array) || date.length > 0) { options.date = parse_date(date, options); if (options.mode !== 'single') { if (options.date instanceof Array) { options.date[0] = options.date[0] || parse_date(new Date, options); if (options.mode === 'range') { options.date[1] = options.date[1] || parse_date(options.date[0], options); } } else { options.date = [options.date]; if (options.mode === 'range') { options.date.push(parse_date(options.date[0], options)); } } for (i = 0; i < options.date.length; ++i) { options.date[i] = correct_date_outside_of_limit(options.date[i], options.min, options.max); } } else { if (options.date instanceof Array) { options.date = options.date[0]; } options.date = correct_date_outside_of_limit(options.date, options.min, options.max); } } else { options.date = []; } if (!options.select_day) { if (options.date instanceof Array) { for (i = 0; i < options.date.length; ++i) { options.date[i].setDate(1); } } else { options.date.setDate(1); } } // Remove duplicates if (options.mode === 'multiple') { for (i = 0; i < options.date.length; ++i) { if (options.date.indexOf(options.date[i]) !== i) { options.date.splice(i, 1); --i; } } } if (current) { options.current = parse_date(current, options); } else { current = options.mode === 'single' ? options.date : options.date[options.date.length - 1]; options.current = current ? new Date(current) : new Date; } options.current.setDate(1); options.bound.fill(); if (dom_matches(target, 'input') && options.default_date !== false) { var prepared_date = prepare_date(options), current_value = target.value, new_value = options.mode === 'single' ? prepared_date.formatted_date : prepared_date.formatted_date.join(options.separator); if (!current_value) { dom_dispatch_event(target, 'change', prepared_date); } if (current_value !== new_value) { //noinspection JSUndefinedPropertyAssignment target.value = new_value; } } } /** * @param {Element} target */ function destroy (target) { var root_element = target.__pickmeup.element; dom_off(target); dom_remove(root_element); delete target.__pickmeup; } function correct_date_outside_of_limit (date, min, max) { if (min && min > date) { return new Date(min); } else if (max && max < date) { return new Date(max); } return date; } /** * @param {(Element|string)} target * @param {Object} [initial_options={}] * * @return {(Object|null)} Object with useful methods on success, `null` otherwise */ function pickmeup_init (target, initial_options) { if (typeof target == 'string') { target = document.querySelector(target); } if (!target) { return null; } if (!target.__pickmeup) { var i, option, options = {}; initial_options = initial_options || {}; for (i in pickmeup_init.defaults) { options[i] = i in initial_options ? initial_options[i] : pickmeup_init.defaults[i]; } for (i in options) { option = target.getAttribute('data-pmu-' + i); if (option !== null) { options[i] = option; } } // 4 conditional statements in order to account all cases if (options.view === 'days' && !options.select_day) { options.view = 'months'; } if (options.view === 'months' && !options.select_month) { options.view = 'years'; } if (options.view === 'years' && !options.select_year) { options.view = 'days'; } if (options.view === 'days' && !options.select_day) { options.view = 'months'; } options.calendars = Math.max(1, parseInt(options.calendars, 10) || 1); options.mode = /single|multiple|range/.test(options.mode) ? options.mode : 'single'; if (options.min) { options.min = parse_date(options.min, options); if (!options.select_day) { options.min.setDate(1); } } if (options.max) { options.max = parse_date(options.max, options); if (!options.select_day) { options.max.setDate(1); } } var element = document.createElement('div'); //noinspection JSUndefinedPropertyAssignment target.__pickmeup = { options : options, events : [], element : element }; element.__pickmeup_target = target; dom_add_class(element, 'pickmeup'); if (options.class_name) { dom_add_class(element, options.class_name); } options.bound = { fill : fill.bind(target, target), update_date : update_date.bind(target, target), click : click.bind(target, target), show : show.bind(target, target), forced_show : show.bind(target, target, true), hide : hide.bind(target, target), update : update.bind(target, target), clear : clear.bind(target, target), prev : prev.bind(target, target), next : next.bind(target, target), get_date : get_date.bind(target, target), set_date : set_date.bind(target, target), destroy : destroy.bind(target, target) }; dom_add_class(element, 'pmu-view-' + options.view); var content_template = options.instance_template(options), content = ''; for (i = 0; i < options.calendars; ++i) { content += content_template; } element.innerHTML = content; dom_on(target, element, 'click', options.bound.click); dom_on( target, element, 'onselectstart' in Element.prototype ? 'selectstart' : 'mousedown', function (e) { e.preventDefault(); }); if (options.flat) { dom_add_class(element, 'pmu-flat'); target.appendChild(element); } else { dom_add_class(element, 'pmu-hidden'); document.body.appendChild(element); dom_on(target, target, 'click', show.bind(target, target, false)); dom_on(target, target, 'input', options.bound.update); dom_on(target, target, 'change', options.bound.update); } options.bound.set_date(options.date, options.current); } options = target.__pickmeup.options; return { hide : options.bound.hide, show : options.bound.show, clear : options.bound.clear, update : options.bound.update, prev : options.bound.prev, next : options.bound.next, get_date : options.bound.get_date, set_date : options.bound.set_date, destroy : options.bound.destroy }; } pickmeup_init.defaults = { current : null, date : new Date, default_date : new Date, flat : false, first_day : 1, prev : '◀', next : '▶', mode : 'single', select_year : true, select_month : true, select_day : true, view : 'days', calendars : 1, format : 'd-m-Y', title_format : 'B, Y', position : 'bottom', class_name : '', separator : ' - ', hide_on_select : false, min : null, max : null, render : function () { }, locale : 'en', locales : { en : { days : ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'], daysShort : ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'], daysMin : ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'], months : ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'], monthsShort : ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] } }, /** * @param {Object} options * * @returns {string} */ instance_template : function (options) { var days_of_week = options.locales[options.locale].daysMin.slice(); // If Monday is the first day of the week if (options.first_day) { days_of_week.push(days_of_week.shift()); } return '<div class="pmu-instance">' + '<nav>' + '<div class="pmu-prev pmu-button">' + options.prev + '</div>' + '<div class="pmu-month pmu-button"></div>' + '<div class="pmu-next pmu-button">' + options.next + '</div>' + '</nav>' + '<nav class="pmu-day-of-week"><div>' + days_of_week.join('</div><div>') + '</div></nav>' + '</div>'; }, /** * @param {Element[]} elements * @param {string} container_class_name * * @returns {Element} */ instance_content_template : function (elements, container_class_name) { var root_element = document.createElement('div'); dom_add_class(root_element, container_class_name); for (var i = 0; i < elements.length; ++i) { dom_add_class(elements[i], 'pmu-button'); root_element.appendChild(elements[i]); } return root_element; } }; return pickmeup_init; }));