Skip to content

Instantly share code, notes, and snippets.

@pamelafox
Created November 24, 2011 01:50
Show Gist options
  • Save pamelafox/1390458 to your computer and use it in GitHub Desktop.
Save pamelafox/1390458 to your computer and use it in GitHub Desktop.
Zepto jQueryTools DateInput
/**
* @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