Skip to content

Instantly share code, notes, and snippets.

@michelts
Forked from pamelafox/bootstrap.datepicker.js
Last active December 23, 2015 11:19
Show Gist options
  • Save michelts/6627626 to your computer and use it in GitHub Desktop.
Save michelts/6627626 to your computer and use it in GitHub Desktop.
jQuery/Zepto Bootstrap Datepicker now with tables and able to start with blank values.
/* ===========================================================
* bootstrap-datepicker.js v1.3.0
* http://twitter.github.com/bootstrap/javascript.html#datepicker
* ===========================================================
* Copyright 2011 Twitter, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Contributed by Scott Torborg - github.com/storborg
* Loosely based on jquery.date_input.js by Jon Leighton, heavily updated and
* rewritten to match bootstrap javascript approach and add UI features.
* =========================================================== */
!function ( $ ) {
var STANDALONE = true;
var selector = '[data-datepicker]',
all = [];
function clearDatePickers(except) {
var ii;
for(ii = 0; ii < all.length; ii++) {
if(all[ii] != except) {
all[ii].hide();
}
}
}
function DatePicker( element, options ) {
this.$el = $(element);
this.proxy('show').proxy('ahead').proxy('hide').proxy('keyHandler').proxy('selectDate');
var options = $.extend({}, $.fn.datepicker.defaults, options );
if((!!options.parse) || (!!options.format) || !this.detectNative()) {
$.extend(this, options);
this.$el.data('datepicker', this);
all.push(this);
this.init();
}
}
DatePicker.prototype = {
detectNative: function(el) {
if (STANDALONE) return false;
// Attempt to activate the native datepicker, if there is a known good
// one. If successful, return true. Note that input type="date"
// requires that the string be RFC3339, so if the format/parse methods
// have been overridden, this won't be used.
if(navigator.userAgent.match(/(iPad|iPhone); CPU(\ iPhone)? OS 5_\d/i)) {
// jQuery will only change the input type of a detached element.
var $marker = $('<span>').insertBefore(this.$el);
this.$el.detach().attr('type', 'date').insertAfter($marker);
$marker.remove();
return true;
}
return false;
}
, init: function() {
var $months = this.nav('months', 1);
var $years = this.nav('years', 12);
var $nav = $('<div>').addClass('nav').append($months).append($years);
this.$month = $months.find('.name');
this.$year = $years.find('.name');
$calendar = $("<table>");
$dow = $('<tr>');
$calendar.append($('<thead>'));
$calendar.find('thead').append($dow);
// Populate day of week headers, realigned by startOfWeek.
for (var i = 0; i < this.shortDayNames.length; i++) {
$dow.append('<th>' + this.shortDayNames[(i + this.startOfWeek) % 7] + '</th>');
};
this.$days = $('<tbody>');
$calendar.append(this.$days);
this.$picker = $('<div></div>')
.click(function(e) { e.stopPropagation() })
// Use this to prevent accidental text selection.
.mousedown(function(e) { e.preventDefault() })
.addClass('datepicker')
.append($nav)
.append($calendar)
.insertAfter(this.$el);
this.$el.change($.proxy(function() { this.selectDate(); }, this));
this.selectDate();
if (STANDALONE) {
this.$el.hide();
this.show();
} else {
this.$el
.focus(this.show)
.click(this.show)
this.hide();
}
}
, nav: function( c, months ) {
var $subnav = $('<div>' +
'<span class="prev button">&larr;</span>' +
'<span class="name"></span>' +
'<span class="next button">&rarr;</span>' +
'</div>').addClass(c)
$('.prev', $subnav).click($.proxy(function() { this.ahead(-months, 0) }, this));
$('.next', $subnav).click($.proxy(function() { this.ahead(months, 0) }, this));
return $subnav;
}
, updateName: function($area, s) {
// Update either the month or year field
$area.html(s);
}
, selectMonth: function(date) {
var newMonth = new Date(date.getFullYear(), date.getMonth(), 1);
if (!this.curMonth || !(this.curMonth.getFullYear() == newMonth.getFullYear() &&
this.curMonth.getMonth() == newMonth.getMonth())) {
this.curMonth = newMonth;
var rangeStart = this.rangeStart(date), rangeEnd = this.rangeEnd(date);
var num_days = this.daysBetween(rangeStart, rangeEnd);
this.$days.empty();
for (var ii = 0; ii <= num_days; ii++) {
if(ii % 7 == 0) {
$tr = $('<tr>');
this.$days.append($tr);
}
var thisDay = new Date(rangeStart.getFullYear(), rangeStart.getMonth(), rangeStart.getDate() + ii, 12, 00);
var $day = $('<td></td>').attr('date', this.format(thisDay));
$day.text(thisDay.getDate());
if (thisDay.getMonth() != date.getMonth()) {
$day.addClass('overlap');
};
$tr.append($day);
};
this.updateName(this.$month, this.monthNames[date.getMonth()]);
this.updateName(this.$year, this.curMonth.getFullYear());
this.$days.find('td').click($.proxy(function(e) {
var $targ = $(e.target);
// The date= attribute is used here to provide relatively fast
// selectors for setting certain date cells.
this.update($targ.attr("date"));
this.$days.find('td').removeClass('selected');
$targ.addClass('selected');
// Don't consider this selection final if we're just going to an
// adjacent month.
if(!$targ.hasClass('overlap')) {
this.hide();
}
}, this));
$("[date='" + this.format(new Date()) + "']", this.$days).addClass('today');
};
// Select the input value if available
$('.selected', this.$days).removeClass('selected');
if(this.$el.val()) {
date = this.parse(this.$el.val());
selectedDateStr = this.format(date);
$('[date="' + selectedDateStr + '"]', this.$days).addClass('selected');
}
}
, selectDate: function(date) {
if (typeof(date) == "undefined") {
date = this.parse(this.$el.val());
};
if (!date) {
date = new Date();
}
this.referenceDate = date;
this.referenceDateStr = this.format(this.referenceDate);
this.selectMonth(this.referenceDate);
}
, update: function(s) {
this.$el.val(s);
this.$el.trigger('change');
}
, show: function(e) {
e && e.stopPropagation();
// Hide all other datepickers.
if (!STANDALONE) clearDatePickers(this);
var offset = this.$el.offset();
if (!STANDALONE) {
this.$picker.css({
top: offset.top + this.$el.outerHeight() + 2,
left: offset.left,
position: 'absolute',
zIndex: '900',
margin: '0 0 18px 0'
});
}
this.$picker.show();
if (!STANDALONE) $('html').on('keydown', this.keyHandler);
}
, hide: function() {
if (!STANDALONE) this.$picker.hide();
if (!STANDALONE) $('html').off('keydown', this.keyHandler);
}
, keyHandler: function(e) {
// Keyboard navigation shortcuts.
switch (e.keyCode)
{
case 9:
case 27:
// Tab or escape hides the datepicker. In this case, just return
// instead of breaking, so that the e doesn't get stopped.
this.hide(); return;
case 13:
// Enter selects the currently highlighted date.
this.update(this.referenceDateStr); this.hide(); break;
case 38:
// Arrow up goes to prev week.
this.ahead(0, -7); break;
case 40:
// Arrow down goes to next week.
this.ahead(0, 7); break;
case 37:
// Arrow left goes to prev day.
this.ahead(0, -1); break;
case 39:
// Arrow right goes to next day.
this.ahead(0, 1); break;
default:
return;
}
e.preventDefault();
}
, parse: function(s) {
// Parse a partial RFC 3339 string into a Date.
var m;
if ((m = s.match(/^(\d{4,4})-(\d{2,2})-(\d{2,2})$/))) {
return new Date(m[1], m[2] - 1, m[3]);
} else {
return null;
}
}
, format: function(date) {
// Format a Date into a string as specified by RFC 3339.
var month = (date.getMonth() + 1).toString(),
dom = date.getDate().toString();
if (month.length === 1) {
month = '0' + month;
}
if (dom.length === 1) {
dom = '0' + dom;
}
return date.getFullYear() + '-' + month + "-" + dom;
}
, ahead: function(months, days) {
// Move ahead ``months`` months and ``days`` days, both integers, can be
// negative.
this.selectDate(new Date(this.referenceDate.getFullYear(),
this.referenceDate.getMonth() + months,
this.referenceDate.getDate() + days));
}
, proxy: function(meth) {
// Bind a method so that it always gets the datepicker instance for
// ``this``. Return ``this`` so chaining calls works.
this[meth] = $.proxy(this[meth], this);
return this;
}
, daysBetween: function(start, end) {
// Return number of days between ``start`` Date object and ``end``.
var start = Date.UTC(start.getFullYear(), start.getMonth(), start.getDate());
var end = Date.UTC(end.getFullYear(), end.getMonth(), end.getDate());
return (end - start) / 86400000;
}
, findClosest: function(dow, date, direction) {
// From a starting date, find the first day ahead of behind it that is
// a given day of the week.
var difference = direction * (Math.abs(date.getDay() - dow - (direction * 7)) % 7);
return new Date(date.getFullYear(), date.getMonth(), date.getDate() + difference);
}
, rangeStart: function(date) {
// Get the first day to show in the current calendar view.
return this.findClosest(this.startOfWeek,
new Date(date.getFullYear(), date.getMonth()),
-1);
}
, rangeEnd: function(date) {
// Get the last day to show in the current calendar view.
return this.findClosest((this.startOfWeek - 1) % 7,
new Date(date.getFullYear(), date.getMonth() + 1, 0),
1);
}
};
/* DATEPICKER PLUGIN DEFINITION
* ============================ */
$.fn.datepicker = function( options ) {
return this.each(function() { new DatePicker(this, options); });
};
$(function() {
$(selector).datepicker();
if (!STANDALONE) {
$('html').click(clearDatePickers);
}
});
$.fn.datepicker.DatePicker = DatePicker;
$.fn.datepicker.defaults = {
monthNames: ["Jan", "Fev", "Mar", "Abr", "Mai", "Jun",
"Jul", "Ago", "Set", "Out", "Nov", "Dez"]
, shortDayNames: ["Dom", "Seg", "Ter", "Qua", "Qui", "Sex", "Sáb"]
, startOfWeek: 1
};
}( window.jQuery || window.ender || window.Zepto);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment