Skip to content

Instantly share code, notes, and snippets.

@kreitje
Created October 24, 2011 21:46
Show Gist options
  • Save kreitje/1310424 to your computer and use it in GitHub Desktop.
Save kreitje/1310424 to your computer and use it in GitHub Desktop.
jQueryTools DateInput (Fixes Selectors)
/**
* @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(),
def = original.wrap("<div/>").parent().html(),
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/><div/><a/></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/>").attr("id", css.month),
yearSelector = $("<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/>").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("api");
e.type = "beforeChange";
fire.trigger(e, [date]);
if (e.isDefaultPrevented()) { return; }
// formatting
input.val(format(date, conf.format, conf.lang));
// change
e.type = "change";
fire.trigger(e);
// store value into input
input.data("date", date);
self.hide(e);
}
//}}}
//{{{ onShow
function onShow(ev) {
ev.type = "onShow";
fire.trigger(ev);
$(document).bind("keydown.d", function(e) {
if (e.ctrlKey) { return true; }
var key = e.keyCode;
// backspace clears the value
if (key == 8) {
input.val("");
return self.hide(e);
}
// esc or tab key
if (key == 27 || key == 9) { return self.hide(e); }
if ($(KEYS).index(key) >= 0) {
if (!opened) {
self.show(e);
return e.preventDefault();
}
var days = $("#" + css.weeks + " a"),
el = $("." + css.focus),
index = days.index(el);
el.removeClass(css.focus);
if (key == 74 || key == 40) { index += 7; }
else if (key == 75 || key == 38) { index -= 7; }
else if (key == 76 || key == 39) { index += 1; }
else if (key == 72 || key == 37) { index -= 1; }
if (index > 41) {
self.addMonth();
el = $("#" + css.weeks + " a:eq(" + (index-42) + ")");
} else if (index < 0) {
self.addMonth(-1);
el = $("#" + css.weeks + " a:eq(" + (index+42) + ")");
} else {
el = days.eq(index);
}
el.addClass(css.focus);
return e.preventDefault();
}
// pageUp / pageDown
if (key == 34) { return self.addMonth(); }
if (key == 33) { return self.addMonth(-1); }
// home
if (key == 36) { return self.today(); }
// enter
if (key == 13) {
if (!$(e.target).is("select")) {
$("." + css.focus).click();
}
}
return $([16, 17, 18, 9]).index(key) >= 0;
});
// click outside dateinput
$(document).bind("click.d", function(e) {
var el = e.target;
if (!$(el).parents("#" + css.root).length && el != input[0] && (!trigger || el != trigger[0])) {
self.hide(e);
}
});
}
//}}}
$.extend(self, {
/**
* @public
* Show the calendar
*/
show: function(e) {
if (input.attr("readonly") || input.attr("disabled") || opened) { return; }
// onBeforeShow
e = e || $.Event();
e.type = "onBeforeShow";
fire.trigger(e);
if (e.isDefaultPrevented()) { return; }
$.each(instances, function() {
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/>");
if (j % 7 === 0) {
week = $("<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();
e.type = "onHide";
fire.trigger(e);
$(document).unbind("click.d").unbind("keydown.d");
// cancelled ?
if (e.isDefaultPrevented()) { return; }
// 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();
}
// 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;
};
}) (jQuery);
@kreitje
Copy link
Author

kreitje commented Oct 24, 2011

Comment out line 484

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment