(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);