Created
November 24, 2011 01:50
-
-
Save pamelafox/1390458 to your computer and use it in GitHub Desktop.
Zepto jQueryTools DateInput
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* @license | |
* jQuery Tools @VERSION Dateinput - <input type="date" /> for humans | |
* | |
* NO COPYRIGHTS OR LICENSES. DO WHAT YOU LIKE. | |
* | |
* http://flowplayer.org/tools/form/dateinput/ | |
* | |
* Since: Mar 2010 | |
* Date: @DATE | |
*/ | |
(function($, undefined) { | |
/* TODO: | |
preserve today highlighted | |
*/ | |
$.tools = $.tools || {version: '@VERSION'}; | |
var instances = [], | |
tool, | |
// h=72, j=74, k=75, l=76, down=40, left=37, up=38, right=39 | |
KEYS = [75, 76, 38, 39, 74, 72, 40, 37], | |
LABELS = {}; | |
tool = $.tools.dateinput = { | |
conf: { | |
format: 'mm/dd/yy', | |
selectors: false, | |
yearRange: [-5, 5], | |
lang: 'en', | |
offset: [0, 0], | |
speed: 0, | |
firstDay: 0, // The first day of the week, Sun = 0, Mon = 1, ... | |
min: undefined, | |
max: undefined, | |
trigger: 0, | |
toggle: 0, | |
editable: 0, | |
css: { | |
prefix: 'cal', | |
input: 'date', | |
// ids | |
root: 0, | |
head: 0, | |
title: 0, | |
prev: 0, | |
next: 0, | |
month: 0, | |
year: 0, | |
days: 0, | |
body: 0, | |
weeks: 0, | |
today: 0, | |
current: 0, | |
// classnames | |
week: 0, | |
off: 0, | |
sunday: 0, | |
focus: 0, | |
disabled: 0, | |
trigger: 0 | |
} | |
}, | |
localize: function(language, labels) { | |
$.each(labels, function(key, val) { | |
labels[key] = val.split(","); | |
}); | |
LABELS[language] = labels; | |
} | |
}; | |
tool.localize("en", { | |
months: 'January,February,March,April,May,June,July,August,September,October,November,December', | |
shortMonths: 'Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec', | |
days: 'Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday', | |
shortDays: 'Sun,Mon,Tue,Wed,Thu,Fri,Sat' | |
}); | |
//{{{ private functions | |
// @return amount of days in certain month | |
function dayAm(year, month) { | |
return new Date(year, month + 1, 0).getDate(); | |
} | |
function zeropad(val, len) { | |
val = '' + val; | |
len = len || 2; | |
while (val.length < len) { val = "0" + val; } | |
return val; | |
} | |
// thanks: http://stevenlevithan.com/assets/misc/date.format.js | |
var Re = /d{1,4}|m{1,4}|yy(?:yy)?|"[^"]*"|'[^']*'/g, tmpTag = $("<a/>"); | |
function format(date, fmt, lang) { | |
var d = date.getDate(), | |
D = date.getDay(), | |
m = date.getMonth(), | |
y = date.getFullYear(), | |
flags = { | |
d: d, | |
dd: zeropad(d), | |
ddd: LABELS[lang].shortDays[D], | |
dddd: LABELS[lang].days[D], | |
m: m + 1, | |
mm: zeropad(m + 1), | |
mmm: LABELS[lang].shortMonths[m], | |
mmmm: LABELS[lang].months[m], | |
yy: String(y).slice(2), | |
yyyy: y | |
}; | |
var ret = fmt.replace(Re, function ($0) { | |
return $0 in flags ? flags[$0] : $0.slice(1, $0.length - 1); | |
}); | |
// a small trick to handle special characters | |
return tmpTag.html(ret).html(); | |
} | |
function integer(val) { | |
return parseInt(val, 10); | |
} | |
function isSameDay(d1, d2) { | |
return d1.getFullYear() === d2.getFullYear() && | |
d1.getMonth() == d2.getMonth() && | |
d1.getDate() == d2.getDate(); | |
} | |
function parseDate(val) { | |
if (val === undefined) { return; } | |
if (val.constructor == Date) { return val; } | |
if (typeof val == 'string') { | |
// rfc3339? | |
var els = val.split("-"); | |
if (els.length == 3) { | |
return new Date(integer(els[0]), integer(els[1]) -1, integer(els[2])); | |
} | |
// invalid offset | |
if ( !(/^-?\d+$/).test(val) ) { return; } | |
// convert to integer | |
val = integer(val); | |
} | |
var date = new Date; | |
date.setDate(date.getDate() + val); | |
return date; | |
} | |
//}}} | |
function Dateinput(input, conf) { | |
// variables | |
var self = this, | |
now = new Date, | |
yearNow = now.getFullYear(), | |
css = conf.css, | |
labels = LABELS[conf.lang], | |
root = $("#" + css.root), | |
title = root.find("#" + css.title), | |
trigger, | |
pm, nm, | |
currYear, currMonth, currDay, | |
value = input.attr("data-value") || conf.value || input.val(), | |
min = input.attr("min") || conf.min, | |
max = input.attr("max") || conf.max, | |
opened, | |
original; | |
// zero min is not undefined | |
if (min === 0) { min = "0"; } | |
// use sane values for value, min & max | |
value = parseDate(value) || now; | |
min = parseDate(min || new Date(yearNow + conf.yearRange[0], 1, 1)); | |
max = parseDate(max || new Date( yearNow + conf.yearRange[1]+ 1, 1, -1)); | |
// check that language exists | |
if (!labels) { throw "Dateinput: invalid language: " + conf.lang; } | |
// Replace built-in date input: NOTE: input.attr("type", "text") throws exception by the browser | |
if (input.attr("type") == 'date') { | |
var original = input.clone(); | |
var def = original.wrap("<div/>").parent().html(); | |
var clone = $(def.replace(/type/i, "type=text data-orig-type")); | |
if (conf.value) clone.val(conf.value); // jquery 1.6.2 val(undefined) will clear val() | |
input.replaceWith(clone); | |
input = clone; | |
} | |
input.addClass(css.input); | |
var fire = input.add(self); | |
// construct layout | |
if (!root.length) { | |
// root | |
root = $('<div><div><a></a><div></div><a></a></div><div><div></div><div></div></div></div>') | |
.hide().css({position: 'absolute'}).attr("id", css.root); | |
// elements | |
root.children() | |
.eq(0).attr("id", css.head).end() | |
.eq(1).attr("id", css.body).children() | |
.eq(0).attr("id", css.days).end() | |
.eq(1).attr("id", css.weeks).end().end().end() | |
.find("a").eq(0).attr("id", css.prev).end().eq(1).attr("id", css.next); | |
// title | |
title = root.find("#" + css.head).find("div").attr("id", css.title); | |
// year & month selectors | |
if (conf.selectors) { | |
var monthSelector = $("<select></select>").attr("id", css.month), | |
yearSelector = $("<select></select>").attr("id", css.year); | |
title.html(monthSelector.add(yearSelector)); | |
} | |
// day titles | |
var days = root.find("#" + css.days); | |
// days of the week | |
for (var d = 0; d < 7; d++) { | |
days.append($("<span></span>").text(labels.shortDays[(d + conf.firstDay) % 7])); | |
} | |
$("body").append(root); | |
} | |
// trigger icon | |
if (conf.trigger) { | |
trigger = $("<a/>").attr("href", "#").addClass(css.trigger).click(function(e) { | |
conf.toggle ? self.toggle() : self.show(); | |
return e.preventDefault(); | |
}).insertAfter(input); | |
} | |
// layout elements | |
var weeks = root.find("#" + css.weeks); | |
yearSelector = root.find("#" + css.year); | |
monthSelector = root.find("#" + css.month); | |
//{{{ pick | |
function select(date, conf, e) { | |
// current value | |
value = date; | |
currYear = date.getFullYear(); | |
currMonth = date.getMonth(); | |
currDay = date.getDate(); | |
// beforChange | |
e = e || $.Event("beforeChange"); | |
// focus the input after selection (doesn't work in IE) | |
if (e.type == "click" && !$.browser.msie) { | |
input.focus(); | |
} | |
fire.trigger(e, [date]); | |
// formatting | |
input.val(format(date, conf.format, conf.lang)); | |
// change | |
e = $.Event('change'); | |
fire.trigger(e); | |
conf.change.call(self, e, date); | |
// store value into input | |
input.data("date", date); | |
self.hide(e); | |
} | |
//}}} | |
//{{{ onShow | |
function onShow(ev) { | |
ev.type = "onShow"; | |
fire.trigger(ev); | |
} | |
//}}} | |
$.extend(self, { | |
/** | |
* @public | |
* Show the calendar | |
*/ | |
show: function(e) { | |
if (input.attr("readonly") || input.attr("disabled") || opened) { | |
return; | |
} | |
// onBeforeShow | |
e = e || $.Event('onBeforeShow'); | |
e.type = "onBeforeShow"; | |
fire.trigger(e); | |
if (e.isDefaultPrevented()) { return; } | |
$.each(instances, function(instance) { | |
this.hide(); | |
}); | |
opened = true; | |
// month selector | |
monthSelector.unbind("change").change(function() { | |
self.setValue(yearSelector.val(), $(this).val()); | |
}); | |
// year selector | |
yearSelector.unbind("change").change(function() { | |
self.setValue($(this).val(), monthSelector.val()); | |
}); | |
// prev / next month | |
pm = root.find("#" + css.prev).unbind("click").click(function(e) { | |
if (!pm.hasClass(css.disabled)) { | |
self.addMonth(-1); | |
} | |
return false; | |
}); | |
nm = root.find("#" + css.next).unbind("click").click(function(e) { | |
if (!nm.hasClass(css.disabled)) { | |
self.addMonth(); | |
} | |
return false; | |
}); | |
// set date | |
self.setValue(value); | |
// show calendar | |
var pos = input.offset(); | |
// iPad position fix | |
if (/iPad/i.test(navigator.userAgent)) { | |
pos.top -= $(window).scrollTop(); | |
} | |
root.css({ | |
top: pos.top + input.outerHeight({margins: true}) + conf.offset[0], | |
left: pos.left + conf.offset[1] | |
}); | |
if (conf.speed) { | |
root.show(conf.speed, function() { | |
onShow(e); | |
}); | |
} else { | |
root.show(); | |
onShow(e); | |
} | |
return self; | |
}, | |
/** | |
* @public | |
* | |
* Set the value of the dateinput | |
*/ | |
setValue: function(year, month, day) { | |
var date = integer(month) >= -1 ? new Date(integer(year), integer(month), integer(day == undefined || isNaN(day) ? 1 : day)) : | |
year || value; | |
if (date < min) { date = min; } | |
else if (date > max) { date = max; } | |
// date given as ISO string | |
if (typeof year == 'string') { date = parseDate(year); } | |
year = date.getFullYear(); | |
month = date.getMonth(); | |
day = date.getDate(); | |
// roll year & month | |
if (month == -1) { | |
month = 11; | |
year--; | |
} else if (month == 12) { | |
month = 0; | |
year++; | |
} | |
if (!opened) { | |
select(date, conf); | |
return self; | |
} | |
currMonth = month; | |
currYear = year; | |
currDay = day; | |
// variables | |
var tmp = new Date(year, month, 1 - conf.firstDay), begin = tmp.getDay(), | |
days = dayAm(year, month), | |
prevDays = dayAm(year, month - 1), | |
week; | |
// selectors | |
if (conf.selectors) { | |
// month selector | |
monthSelector.empty(); | |
$.each(labels.months, function(i, m) { | |
if (min < new Date(year, i + 1, 1) && max > new Date(year, i, 0)) { | |
monthSelector.append($("<option/>").html(m).attr("value", i)); | |
} | |
}); | |
// year selector | |
yearSelector.empty(); | |
var yearNow = now.getFullYear(); | |
for (var i = yearNow + conf.yearRange[0]; i < yearNow + conf.yearRange[1]; i++) { | |
if (min < new Date(i + 1, 0, 1) && max > new Date(i, 0, 0)) { | |
yearSelector.append($("<option/>").text(i)); | |
} | |
} | |
monthSelector.val(month); | |
yearSelector.val(year); | |
// title | |
} else { | |
title.html(labels.months[month] + " " + year); | |
} | |
// populate weeks | |
weeks.empty(); | |
pm.add(nm).removeClass(css.disabled); | |
// !begin === "sunday" | |
for (var j = !begin ? -7 : 0, a, num; j < (!begin ? 35 : 42); j++) { | |
a = $("<a></a>"); | |
if (j % 7 === 0) { | |
week = $("<div></div>").addClass(css.week); | |
weeks.append(week); | |
} | |
if (j < begin) { | |
a.addClass(css.off); | |
num = prevDays - begin + j + 1; | |
date = new Date(year, month-1, num); | |
} else if (j >= begin + days) { | |
a.addClass(css.off); | |
num = j - days - begin + 1; | |
date = new Date(year, month+1, num); | |
} else { | |
num = j - begin + 1; | |
date = new Date(year, month, num); | |
// current date | |
if (isSameDay(value, date)) { | |
a.attr("id", css.current).addClass(css.focus); | |
// today | |
} else if (isSameDay(now, date)) { | |
a.attr("id", css.today); | |
} | |
} | |
// disabled | |
if (min && date < min) { | |
a.add(pm).addClass(css.disabled); | |
} | |
if (max && date > max) { | |
a.add(nm).addClass(css.disabled); | |
} | |
a.attr("href", "#" + num).text(num).data("date", date); | |
week.append(a); | |
} | |
// date picking | |
weeks.find("a").click(function(e) { | |
var el = $(this); | |
if (!el.hasClass(css.disabled)) { | |
$("#" + css.current).removeAttr("id"); | |
el.attr("id", css.current); | |
select(el.data("date"), conf, e); | |
} | |
return false; | |
}); | |
// sunday | |
if (css.sunday) { | |
weeks.find(css.week).each(function() { | |
var beg = conf.firstDay ? 7 - conf.firstDay : 0; | |
$(this).children().slice(beg, beg + 1).addClass(css.sunday); | |
}); | |
} | |
return self; | |
}, | |
//}}} | |
setMin: function(val, fit) { | |
min = parseDate(val); | |
if (fit && value < min) { self.setValue(min); } | |
return self; | |
}, | |
setMax: function(val, fit) { | |
max = parseDate(val); | |
if (fit && value > max) { self.setValue(max); } | |
return self; | |
}, | |
today: function() { | |
return self.setValue(now); | |
}, | |
addDay: function(amount) { | |
return this.setValue(currYear, currMonth, currDay + (amount || 1)); | |
}, | |
addMonth: function(amount) { | |
var targetMonth = currMonth + (amount || 1), | |
daysInTargetMonth = dayAm(currYear, targetMonth), | |
targetDay = currDay <= daysInTargetMonth ? currDay : daysInTargetMonth; | |
return this.setValue(currYear, targetMonth, targetDay); | |
}, | |
addYear: function(amount) { | |
return this.setValue(currYear + (amount || 1), currMonth, currDay); | |
}, | |
destroy: function() { | |
input.add(document).unbind("click.d").unbind("keydown.d"); | |
root.add(trigger).remove(); | |
input.removeData("dateinput").removeClass(css.input); | |
if (original) { input.replaceWith(original); } | |
}, | |
hide: function(e) { | |
if (opened) { | |
// onHide | |
e = $.Event('onHide'); | |
fire.trigger(e); | |
return; | |
// cancelled ? | |
if (e.isDefaultPrevented()) { return; } | |
$(document).unbind("click.d").unbind("keydown.d"); | |
// do the hide | |
root.hide(); | |
opened = false; | |
} | |
return self; | |
}, | |
toggle: function(){ | |
return self.isOpen() ? self.hide() : self.show(); | |
}, | |
getConf: function() { | |
return conf; | |
}, | |
getInput: function() { | |
return input; | |
}, | |
getCalendar: function() { | |
return root; | |
}, | |
getValue: function(dateFormat) { | |
return dateFormat ? format(value, dateFormat, conf.lang) : value; | |
}, | |
isOpen: function() { | |
return opened; | |
} | |
}); | |
// callbacks | |
$.each(['onBeforeShow','onShow','change','onHide'], function(i, name) { | |
// configuration | |
if ($.isFunction(conf[name])) { | |
$(self).bind(name, conf[name]); | |
} | |
// API methods | |
self[name] = function(fn) { | |
if (fn) { $(self).bind(name, fn); } | |
return self; | |
}; | |
}); | |
if (!conf.editable) { | |
// show dateinput & assign keyboard shortcuts | |
input.bind("focus.d click.d", self.show).keydown(function(e) { | |
var key = e.keyCode; | |
// open dateinput with navigation keyw | |
if (!opened && $(KEYS).index(key) >= 0) { | |
self.show(e); | |
return e.preventDefault(); | |
// clear value on backspace or delete | |
} else if (key == 8 || key == 46) { | |
input.val(""); | |
} | |
// allow tab | |
return e.shiftKey || e.ctrlKey || e.altKey || key == 9 ? true : e.preventDefault(); | |
}); | |
} | |
// initial value | |
if (parseDate(input.val())) { | |
select(value, conf); | |
} | |
} | |
$.expr[':'].date = function(el) { | |
var type = el.getAttribute("type"); | |
return type && type == 'date' || !!$(el).data("dateinput"); | |
}; | |
$.fn.dateinput = function(conf) { | |
// already instantiated | |
if (this.data("dateinput")) { return this; } | |
// configuration | |
conf = $.extend(true, {}, tool.conf, conf); | |
// CSS prefix | |
$.each(conf.css, function(key, val) { | |
if (!val && key != 'prefix') { | |
conf.css[key] = (conf.css.prefix || '') + (val || key); | |
} | |
}); | |
var els; | |
this.each(function() { | |
var el = new Dateinput($(this), conf); | |
instances.push(el); | |
var input = el.getInput().data("dateinput", el); | |
els = els ? els.add(input) : input; | |
}); | |
return els ? els : this; | |
}; | |
}) (window.jQuery || window.Zepto); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment