Skip to content

Instantly share code, notes, and snippets.

@pamelafox
Created February 8, 2012 05:12
Show Gist options
  • Save pamelafox/1765620 to your computer and use it in GitHub Desktop.
Save pamelafox/1765620 to your computer and use it in GitHub Desktop.
jQuery/Zepto Bootstrap Datepicker
/* ===========================================================
* 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 = $("<div>").addClass('calendar');
// Populate day of week headers, realigned by startOfWeek.
for (var i = 0; i < this.shortDayNames.length; i++) {
$calendar.append('<div class="dow">' + this.shortDayNames[(i + this.startOfWeek) % 7] + '</div>');
};
this.$days = $('<div></div>').addClass('days');
$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++) {
var thisDay = new Date(rangeStart.getFullYear(), rangeStart.getMonth(), rangeStart.getDate() + ii, 12, 00);
var $day = $('<div></div>').attr('date', this.format(thisDay));
$day.text(thisDay.getDate());
if (thisDay.getMonth() != date.getMonth()) {
$day.addClass('overlap');
};
this.$days.append($day);
};
this.updateName(this.$month, this.monthNames[date.getMonth()]);
this.updateName(this.$year, this.curMonth.getFullYear());
this.$days.find('div').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('div').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');
};
$('.selected', this.$days).removeClass('selected');
$('[date="' + this.selectedDateStr + '"]', this.$days).addClass('selected');
}
, selectDate: function(date) {
if (typeof(date) == "undefined") {
date = this.parse(this.$el.val());
};
if (!date) date = new Date();
this.selectedDate = date;
this.selectedDateStr = this.format(this.selectedDate);
this.selectMonth(this.selectedDate);
}
, 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.selectedDateStr); 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.selectedDate.getFullYear(),
this.selectedDate.getMonth() + months,
this.selectedDate.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: ["January", "February", "March", "April", "May", "June",
"July", "August", "September", "October", "November", "December"]
, shortDayNames: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]
, startOfWeek: 1
};
}( window.jQuery || window.ender || window.Zepto);
@siamkreative
Copy link

Hey Pamela,

I'm knew to Zepto. Using this JS file along with https://github.com/eternicode/bootstrap-datepicker/blob/master/css/datepicker.css, and the following code:

<input type="text" class="input-block-level date" value="02/16/12">
$('.date').datepicker();

I can get it to work. What am I doing wrong?

Cheers

@aicibei
Copy link

aicibei commented Jun 16, 2014

it does not work well with zepto, the datapicker will not hide when I choosed the data or clicked other element.
so I modify the code
line 27 var STANDALONE = true TO var STANDALONE = false
line 209 if (!STANDALONE) TO if (window.jQuery)
now it work

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