(function ($, undefined) { var ms = $.mobiscroll, datetime = ms.datetime, date = new Date(), defaults = { startYear: date.getFullYear() - 100, endYear: date.getFullYear() + 1, separator: ' ', // Localization dateFormat: 'mm/dd/yy', dateOrder: 'mmddy', timeWheels: 'hhiiA', timeFormat: 'hh:ii A', dayText: 'Day', monthText: 'Month', yearText: 'Year', hourText: 'Hours', minuteText: 'Minutes', ampmText: ' ', secText: 'Seconds', nowText: 'Now' }, /** * @class Mobiscroll.datetime * @extends Mobiscroll * Mobiscroll Datetime component */ preset = function (inst) { var that = $(this), html5def = {}, format; // Force format for html5 date inputs (experimental) if (that.is('input')) { switch (that.attr('type')) { case 'date': format = 'yy-mm-dd'; break; case 'datetime': format = 'yy-mm-ddTHH:ii:ssZ'; break; case 'datetime-local': format = 'yy-mm-ddTHH:ii:ss'; break; case 'month': format = 'yy-mm'; html5def.dateOrder = 'mmyy'; break; case 'time': format = 'HH:ii:ss'; break; } // Check for min/max attributes var min = that.attr('min'), max = that.attr('max'); if (min) { html5def.minDate = datetime.parseDate(format, min); } if (max) { html5def.maxDate = datetime.parseDate(format, max); } } // Set year-month-day order var i, k, keys, values, wg, start, end, hasTime, mins, maxs, orig = $.extend({}, inst.settings), s = $.extend(inst.settings, ms.datetime.defaults, defaults, html5def, orig), offset = 0, validValues = [], wheels = [], ord = [], o = {}, innerValues = {}, f = { y: getYear, m: getMonth, d: getDay, h: getHour, i: getMinute, s: getSecond, u: getMillisecond, a: getAmPm }, invalid = s.invalid, valid = s.valid, p = s.preset, dord = s.dateOrder, tord = s.timeWheels, regen = dord.match(/D/), ampm = tord.match(/a/i), hampm = tord.match(/h/), hformat = p == 'datetime' ? s.dateFormat + s.separator + s.timeFormat : p == 'time' ? s.timeFormat : s.dateFormat, defd = new Date(), steps = s.steps || {}, stepH = steps.hour || s.stepHour || 1, stepM = steps.minute || s.stepMinute || 1, stepS = steps.second || s.stepSecond || 1, zeroBased = steps.zeroBased, mind = s.minDate || new Date(s.startYear, 0, 1), maxd = s.maxDate || new Date(s.endYear, 11, 31, 23, 59, 59), minH = zeroBased ? 0 : mind.getHours() % stepH, minM = zeroBased ? 0 : mind.getMinutes() % stepM, minS = zeroBased ? 0 : mind.getSeconds() % stepS, maxH = getMax(stepH, minH, (hampm ? 11 : 23)), maxM = getMax(stepM, minM, 59), maxS = getMax(stepM, minM, 59); format = format || hformat; if (p.match(/date/i)) { // Determine the order of year, month, day wheels $.each(['y', 'm', 'd'], function (j, v) { i = dord.search(new RegExp(v, 'i')); if (i > -1) { ord.push({ o: i, v: v }); } }); ord.sort(function (a, b) { return a.o > b.o ? 1 : -1; }); $.each(ord, function (i, v) { o[v.v] = i; }); wg = []; for (k = 0; k < 3; k++) { if (k == o.y) { offset++; values = []; keys = []; start = s.getYear(mind); end = s.getYear(maxd); for (i = start; i <= end; i++) { keys.push(i); values.push((dord.match(/yy/i) ? i : (i + '').substr(2, 2)) + (s.yearSuffix || '')); } addWheel(wg, keys, values, s.yearText); } else if (k == o.m) { offset++; values = []; keys = []; for (i = 0; i < 12; i++) { var str = dord.replace(/[dy]/gi, '').replace(/mm/, (i < 9 ? '0' + (i + 1) : i + 1) + (s.monthSuffix || '')).replace(/m/, i + 1 + (s.monthSuffix || '')); keys.push(i); values.push(str.match(/MM/) ? str.replace(/MM/, '' + s.monthNames[i] + '') : str.replace(/M/, '' + s.monthNamesShort[i] + '')); } addWheel(wg, keys, values, s.monthText); } else if (k == o.d) { offset++; values = []; keys = []; for (i = 1; i < 32; i++) { keys.push(i); values.push((dord.match(/dd/i) && i < 10 ? '0' + i : i) + (s.daySuffix || '')); } addWheel(wg, keys, values, s.dayText); } } wheels.push(wg); } if (p.match(/time/i)) { hasTime = true; // Determine the order of hours, minutes, seconds wheels ord = []; $.each(['h', 'i', 's', 'a'], function (i, v) { i = tord.search(new RegExp(v, 'i')); if (i > -1) { ord.push({ o: i, v: v }); } }); ord.sort(function (a, b) { return a.o > b.o ? 1 : -1; }); $.each(ord, function (i, v) { o[v.v] = offset + i; }); wg = []; for (k = offset; k < offset + 4; k++) { if (k == o.h) { offset++; values = []; keys = []; for (i = minH; i < (hampm ? 12 : 24); i += stepH) { keys.push(i); values.push(hampm && i === 0 ? 12 : tord.match(/hh/i) && i < 10 ? '0' + i : i); } addWheel(wg, keys, values, s.hourText); } else if (k == o.i) { offset++; values = []; keys = []; for (i = minM; i < 60; i += stepM) { keys.push(i); values.push(tord.match(/ii/) && i < 10 ? '0' + i : i); } addWheel(wg, keys, values, s.minuteText); } else if (k == o.s) { offset++; values = []; keys = []; for (i = minS; i < 60; i += stepS) { keys.push(i); values.push(tord.match(/ss/) && i < 10 ? '0' + i : i); } addWheel(wg, keys, values, s.secText); } else if (k == o.a) { offset++; var upper = tord.match(/A/); addWheel(wg, [0, 1], upper ? [s.amText.toUpperCase(), s.pmText.toUpperCase()] : [s.amText, s.pmText], s.ampmText); } } wheels.push(wg); } function get(d, i, def) { if (o[i] !== undefined) { return +d[o[i]]; } if (innerValues[i] !== undefined) { return innerValues[i]; } if (def !== undefined) { return def; } return f[i](defd); } function addWheel(wg, k, v, lbl) { wg.push({ values: v, keys: k, label: lbl }); } function step(v, st, min, max) { return Math.min(max, Math.floor(v / st) * st + min); } function getYear(d) { return s.getYear(d); } function getMonth(d) { return s.getMonth(d); } function getDay(d) { return s.getDay(d); } function getHour(d) { var hour = d.getHours(); hour = hampm && hour >= 12 ? hour - 12 : hour; return step(hour, stepH, minH, maxH); } function getMinute(d) { return step(d.getMinutes(), stepM, minM, maxM); } function getSecond(d) { return step(d.getSeconds(), stepS, minS, maxS); } function getMillisecond(d) { return d.getMilliseconds(); } function getAmPm(d) { return ampm && d.getHours() > 11 ? 1 : 0; } function getDate(d) { if (d === null) { return d; } var year = get(d, 'y'), month = get(d, 'm'), day = Math.min(get(d, 'd', 1), s.getMaxDayOfMonth(year, month)), hour = get(d, 'h', 0); return s.getDate(year, month, day, get(d, 'a', 0) ? hour + 12 : hour, get(d, 'i', 0), get(d, 's', 0), get(d, 'u', 0)); } function getMax(step, min, max) { return Math.floor((max - min) / step) * step + min; } function getClosestValidDate(d, dir) { var next, prev, nextValid = false, prevValid = false, up = 0, down = 0; // Normalize min and max dates for comparing later (set default values where there are no values from wheels) mind = getDate(getArray(mind)); maxd = getDate(getArray(maxd)); if (isValid(d)) { return d; } if (d < mind) { d = mind; } if (d > maxd) { d = maxd; } next = d; prev = d; if (dir !== 2) { nextValid = isValid(next); while (!nextValid && next < maxd) { next = new Date(next.getTime() + 1000 * 60 * 60 * 24); nextValid = isValid(next); up++; } } if (dir !== 1) { prevValid = isValid(prev); while (!prevValid && prev > mind) { prev = new Date(prev.getTime() - 1000 * 60 * 60 * 24); prevValid = isValid(prev); down++; } } if (dir === 1 && nextValid) { return next; } if (dir === 2 && prevValid) { return prev; } return down <= up && prevValid ? prev : next; } function isValid(d) { if (d < mind) { return false; } if (d > maxd) { return false; } if (isInObj(d, valid)) { return true; } if (isInObj(d, invalid)) { return false; } return true; } function isInObj(d, obj) { var curr, j, v; if (obj) { for (j = 0; j < obj.length; j++) { curr = obj[j]; v = curr + ''; if (!curr.start) { if (curr.getTime) { // Exact date if (d.getFullYear() == curr.getFullYear() && d.getMonth() == curr.getMonth() && d.getDate() == curr.getDate()) { return true; } } else if (!v.match(/w/i)) { // Day of month v = v.split('/'); if (v[1]) { if ((v[0] - 1) == d.getMonth() && v[1] == d.getDate()) { return true; } } else if (v[0] == d.getDate()) { return true; } } else { // Day of week v = +v.replace('w', ''); if (v == d.getDay()) { return true; } } } } } return false; } function validateDates(obj, y, m, first, maxdays, idx, val) { var j, d, v; if (obj) { for (j = 0; j < obj.length; j++) { d = obj[j]; v = d + ''; if (!d.start) { if (d.getTime) { // Exact date if (s.getYear(d) == y && s.getMonth(d) == m) { idx[s.getDay(d) - 1] = val; } } else if (!v.match(/w/i)) { // Day of month v = v.split('/'); if (v[1]) { if (v[0] - 1 == m) { idx[v[1] - 1] = val; } } else { idx[v[0] - 1] = val; } } else { // Day of week v = +v.replace('w', ''); for (k = v - first; k < maxdays; k += 7) { if (k >= 0) { idx[k] = val; } } } } } } } function validateTimes(vobj, i, v, temp, y, m, d, target, valid) { var dd, ss, str, parts1, parts2, prop1, prop2, v1, v2, j, i1, i2, add, remove, all, hours1, hours2, hours3, spec = {}, steps = { h: stepH, i: stepM, s: stepS, a: 1 }, day = s.getDate(y, m, d), w = ['a', 'h', 'i', 's']; if (vobj) { $.each(vobj, function (i, obj) { if (obj.start) { obj.apply = false; dd = obj.d; ss = dd + ''; str = ss.split('/'); if (dd && ((dd.getTime && y == s.getYear(dd) && m == s.getMonth(dd) && d == s.getDay(dd)) || // Exact date (!ss.match(/w/i) && ((str[1] && d == str[1] && m == str[0] - 1) || (!str[1] && d == str[0]))) || // Day of month (ss.match(/w/i) && day.getDay() == +ss.replace('w', '')) // Day of week )) { obj.apply = true; spec[day] = true; // Prevent applying generic rule on day, if specific exists } } }); $.each(vobj, function (x, obj) { add = 0; remove = 0; i1 = 0; i2 = undefined; prop1 = true; prop2 = true; all = false; if (obj.start && (obj.apply || (!obj.d && !spec[day]))) { // Define time parts parts1 = obj.start.split(':'); parts2 = obj.end.split(':'); for (j = 0; j < 3; j++) { if (parts1[j] === undefined) { parts1[j] = 0; } if (parts2[j] === undefined) { parts2[j] = 59; } parts1[j] = +parts1[j]; parts2[j] = +parts2[j]; } parts1.unshift(parts1[0] > 11 ? 1 : 0); parts2.unshift(parts2[0] > 11 ? 1 : 0); if (hampm) { if (parts1[1] >= 12) { parts1[1] = parts1[1] - 12; } if (parts2[1] >= 12) { parts2[1] = parts2[1] - 12; } } // Look behind for (j = 0; j < i; j++) { if (validValues[j] !== undefined) { v1 = step(parts1[j], steps[w[j]], mins[w[j]], maxs[w[j]]); v2 = step(parts2[j], steps[w[j]], mins[w[j]], maxs[w[j]]); hours1 = 0; hours2 = 0; hours3 = 0; if (hampm && j == 1) { hours1 = parts1[0] ? 12 : 0; hours2 = parts2[0] ? 12 : 0; hours3 = validValues[0] ? 12 : 0; } if (!prop1) { v1 = 0; } if (!prop2) { v2 = maxs[w[j]]; } if ((prop1 || prop2) && (v1 + hours1 < validValues[j] + hours3 && validValues[j] + hours3 < v2 + hours2)) { all = true; } if (validValues[j] != v1) { prop1 = false; } if (validValues[j] != v2) { prop2 = false; } } } // Look ahead if (!valid) { for (j = i + 1; j < 4; j++) { if (parts1[j] > 0) { add = steps[v]; } if (parts2[j] < maxs[w[j]]) { remove = steps[v]; } } } if (!all) { // Calculate min and max values v1 = step(parts1[i], steps[v], mins[v], maxs[v]) + add; v2 = step(parts2[i], steps[v], mins[v], maxs[v]) - remove; if (prop1) { i1 = getValidIndex(target, v1, maxs[v], 0); } if (prop2) { i2 = getValidIndex(target, v2, maxs[v], 1); } } // Disable values if (prop1 || prop2 || all) { if (valid) { $('.dw-li', target).slice(i1, i2).addClass('dw-v'); } else { $('.dw-li', target).slice(i1, i2).removeClass('dw-v'); } } } }); } } function getIndex(t, v) { return $('.dw-li', t).index($('.dw-li[data-val="' + v + '"]', t)); } function getValidIndex(t, v, max, add) { if (v < 0) { return 0; } if (v > max) { return $('.dw-li', t).length; } return getIndex(t, v) + add; } function getArray(d, fillInner) { var ret = []; if (d === null || d === undefined) { return d; } $.each(['y', 'm', 'd', 'a', 'h', 'i', 's', 'u'], function (x, i) { if (o[i] !== undefined) { ret[o[i]] = f[i](d); } if (fillInner) { innerValues[i] = f[i](d); } }); return ret; } function convertRanges(arr) { var i, v, start, ret = []; if (arr) { for (i = 0; i < arr.length; i++) { v = arr[i]; if (v.start && v.start.getTime) { start = new Date(v.start); while (start <= v.end) { ret.push(new Date(start.getFullYear(), start.getMonth(), start.getDate())); start.setDate(start.getDate() + 1); } } else { ret.push(v); } } return ret; } return arr; } // Extended methods // --- inst.getVal = function (temp) { return inst._hasValue || temp ? getDate(inst.getArrayVal(temp)) : null; }; /** * Sets the selected date * * @param {Date} d Date to select. * @param {Boolean} [fill=false] Also set the value of the associated input element. Default is true. * @param {Number} [time=0] Animation time to scroll to the selected date. * @param {Boolean} [temp=false] Set temporary value only. * @param {Boolean} [change=fill] Trigger change on input element. */ inst.setDate = function (d, fill, time, temp, change) { inst.setArrayVal(getArray(d), fill, change, temp, time); }; /** * Returns the selected date. * * @param {Boolean} [temp=false] If true, return the currently shown date on the picker, otherwise the last selected one. * @return {Date} */ inst.getDate = inst.getVal; // --- // Initializations // --- inst.format = hformat; inst.order = o; inst.handlers.now = function () { inst.setDate(new Date(), false, 0.3, true, true); }; inst.buttons.now = { text: s.nowText, handler: 'now' }; invalid = convertRanges(invalid); valid = convertRanges(valid); mins = { y: mind.getFullYear(), m: 0, d: 1, h: minH, i: minM, s: minS, a: 0 }; maxs = { y: maxd.getFullYear(), m: 11, d: 31, h: maxH, i: maxM, s: maxS, a: 1 }; // --- return { wheels: wheels, headerText: s.headerText ? function () { return datetime.formatDate(hformat, getDate(inst.getArrayVal(true)), s); } : false, formatValue: function (d) { return datetime.formatDate(format, getDate(d), s); }, parseValue: function (val) { if (!val) { innerValues = {}; } return getArray(val ? datetime.parseDate(format, val, s) : (s.defaultValue || new Date()), !!val && !!val.getTime); }, validate: function (dw, i, time, dir) { var validated = getClosestValidDate(getDate(inst.getArrayVal(true)), dir), temp = getArray(validated), y = get(temp, 'y'), m = get(temp, 'm'), minprop = true, maxprop = true; $.each(['y', 'm', 'd', 'a', 'h', 'i', 's'], function (x, i) { if (o[i] !== undefined) { var min = mins[i], max = maxs[i], maxdays = 31, val = get(temp, i), t = $('.dw-ul', dw).eq(o[i]); if (i == 'd') { maxdays = s.getMaxDayOfMonth(y, m); max = maxdays; if (regen) { $('.dw-li', t).each(function () { var that = $(this), d = that.data('val'), w = s.getDate(y, m, d).getDay(), str = dord.replace(/[my]/gi, '').replace(/dd/, (d < 10 ? '0' + d : d) + (s.daySuffix || '')).replace(/d/, d + (s.daySuffix || '')); $('.dw-i', that).html(str.match(/DD/) ? str.replace(/DD/, '' + s.dayNames[w] + '') : str.replace(/D/, '' + s.dayNamesShort[w] + '')); }); } } if (minprop && mind) { min = f[i](mind); } if (maxprop && maxd) { max = f[i](maxd); } if (i != 'y') { var i1 = getIndex(t, min), i2 = getIndex(t, max); $('.dw-li', t).removeClass('dw-v').slice(i1, i2 + 1).addClass('dw-v'); if (i == 'd') { // Hide days not in month $('.dw-li', t).removeClass('dw-h').slice(maxdays).addClass('dw-h'); } } if (val < min) { val = min; } if (val > max) { val = max; } if (minprop) { minprop = val == min; } if (maxprop) { maxprop = val == max; } // Disable some days if (i == 'd') { var first = s.getDate(y, m, 1).getDay(), idx = {}; // Set invalid indexes validateDates(invalid, y, m, first, maxdays, idx, 1); // Delete indexes which are valid validateDates(valid, y, m, first, maxdays, idx, 0); $.each(idx, function (i, v) { if (v) { $('.dw-li', t).eq(i).removeClass('dw-v'); } }); } } }); // Invalid times if (hasTime) { $.each(['a', 'h', 'i', 's'], function (i, v) { var val = get(temp, v), d = get(temp, 'd'), t = $('.dw-ul', dw).eq(o[v]); if (o[v] !== undefined) { validateTimes(invalid, i, v, temp, y, m, d, t, 0); validateTimes(valid, i, v, temp, y, m, d, t, 1); // Get valid value validValues[i] = +inst.getValidCell(val, t, dir).val; } }); } inst._tempWheelArray = temp; } }; }; $.each(['date', 'time', 'datetime'], function (i, v) { ms.presets.scroller[v] = preset; }); })(jQuery);