From 09c7907deda53a14ed7872c14d39382f839ec9d4 Mon Sep 17 00:00:00 2001 From: Nick Spacek Date: Fri, 9 Oct 2009 15:35:14 -0300 Subject: [PATCH] README and fixes. --- .gitignore | 1 + LICENSE.txt | 20 ++ README | 0 README.txt | 51 ++++++ example/example.css | 104 +++++++++++ example/example.html | 35 ++++ example/jquery.date_input.js | 344 +++++++++++++++++++++++++++++++++++ jquery.datespan-picker.js | 43 +++-- 8 files changed, 581 insertions(+), 17 deletions(-) create mode 100644 .gitignore create mode 100644 LICENSE.txt delete mode 100644 README create mode 100644 README.txt create mode 100644 example/example.css create mode 100644 example/example.html create mode 100644 example/jquery.date_input.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fd98d34 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*~$ diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..63179e7 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,20 @@ +Copyright (c) 2009 Nick Spacek + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README b/README deleted file mode 100644 index e69de29..0000000 diff --git a/README.txt b/README.txt new file mode 100644 index 0000000..7a7f610 --- /dev/null +++ b/README.txt @@ -0,0 +1,51 @@ +jQuery DateSpanPicker +===================== + +This plugin is meant to offer a date span widget similar to the widget used in +Google's Calendar application. + +Requirements +--------------- +jQuery 1.3.2 (maybe not, but haven't tested with other versions) +jQuery.date_input.js: http://jonathanleighton.com/projects/date-input + +Features +--------------- +* Calendar widget using jQuery.date_input.js for selecting a date. +* Select time in 30 minute intervals +* End date/time automatically adjusted when the start date/time adjusted to + maintain a consistent interval. +** End date/time not adjusted if less than start date/time +* If end date and start date are the same (day), end time selector requires the + end time to be greater than the start time, and displays the difference. +* "All day" checkbox hides the start/end time. + +Usage +--------------- +Note: The included stylesheet pretties it up, but shouldn't be strictly +required. That said, it will look like junk if you don't use any. ;D + +Given this HTML (style and script tags not shown): + +
+ + + + + + + + + + +
+ +You can apply the plugin like this: $( '#picker' ).dateSpanPicker() + +Issues +--------------- +* Need to autocorrect bad entries +* Create elements if they don't exist (optional?) +* Better style for time picker +* jquery.date_input.js should allow custom classes + diff --git a/example/example.css b/example/example.css new file mode 100644 index 0000000..f64c9b9 --- /dev/null +++ b/example/example.css @@ -0,0 +1,104 @@ + +.date_selector { + background: #C3D9FF; + border: 1px solid #C3D9FF; + position: absolute; + z-index: 100000; + display: none; + line-height: 1em; + font-family: Arial, sans-serif; + font-size: 70%; + width: 12.45em; +} +.date_selector_ieframe { + position: absolute; + z-index: 99999; + display: none; +} +.date_selector .nav { +} +.date_selector .month_nav, .date_selector .year_nav { + margin: 0 0 3px 0; + padding: 0; + display: block; + position: relative; + text-align: center; + font-weight: bold; +} +.date_selector .month_nav { +} +.date_selector .year_nav { + display: none; +} +.date_selector .month_name, .date_selector .year_name { + display: block; + margin-top: 2px; +} +.date_selector .button { + position: absolute; + top: 0; + margin: 0 12px; +} + .date_selector .button:hover, .date_selector .button.hover { + background: none; + color: #003C78; + cursor: pointer; + border-color: #ccc; + } +.date_selector .prev { + left: 0; +} +.date_selector .next { + right: 0; +} +.date_selector table { + border-spacing: 0; + border-collapse: collapse; + width: 100%; +} + +.date_selector th, .date_selector td { + font-family: Verdana, sans-serif; + padding: 2px; + + text-align: center; + font-size: 65%; +} +.date_selector th { + font-weight: normal; +} +.date_selector td { + background: white; +} +.date_selector td.today { + background: #FFFEB3; +} +.date_selector td.unselected_month { + color: #ccc; +} +.date_selector td.selectable_day { + cursor: pointer; +} +.date_selector td.selected { + background: #D8DFE5; + font-weight: bold; +} +.date_selector td.selectable_day:hover, .date_selector td.selectable_day.hover { + background: #003C78; + color: white; +} + +.picker-time-chooser { + height: 10em; + list-style: none; + overflow-x: hidden; + overflow-y: auto; + padding: 0; + width: 6em; +} + +.picker-sameday { + width: 11em; +} + + diff --git a/example/example.html b/example/example.html new file mode 100644 index 0000000..d4ba387 --- /dev/null +++ b/example/example.html @@ -0,0 +1,35 @@ + + +jQuery DateSpanPicker + + + + + + + + + + + + + +
+ + + + + + + + + + +
+ + + diff --git a/example/jquery.date_input.js b/example/jquery.date_input.js new file mode 100644 index 0000000..7323441 --- /dev/null +++ b/example/jquery.date_input.js @@ -0,0 +1,344 @@ +/* +Date Input 1.2.1 +Requires jQuery version: >= 1.2.6 + +Copyright (c) 2007-2008 Jonathan Leighton & Torchbox Ltd + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. +*/ + +DateInput = (function($) { + +function DateInput(el, opts) { + if (typeof(opts) != "object") opts = {}; + $.extend(this, DateInput.DEFAULT_OPTS, opts); + + this.input = $(el); + this.bindMethodsToObj("show", "hide", "hideIfClickOutside", "keydownHandler", "selectDate"); + + this.build(); + this.selectDate(); + this.hide(); +}; +DateInput.DEFAULT_OPTS = { + month_names: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"], + short_month_names: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"], + short_day_names: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"], + start_of_week: 1 +}; +DateInput.prototype = { + build: function() { + var monthNav = $('

' + + '«' + + ' ' + + '»' + + '

'); + this.monthNameSpan = $(".month_name", monthNav); + $(".prev", monthNav).click(this.bindToObj(function() { this.moveMonthBy(-1); })); + $(".next", monthNav).click(this.bindToObj(function() { this.moveMonthBy(1); })); + + var yearNav = $('

' + + '«' + + ' ' + + '»' + + '

'); + this.yearNameSpan = $(".year_name", yearNav); + $(".prev", yearNav).click(this.bindToObj(function() { this.moveMonthBy(-12); })); + $(".next", yearNav).click(this.bindToObj(function() { this.moveMonthBy(12); })); + + var nav = $('').append(monthNav, yearNav); + + var tableShell = ""; + $(this.adjustDays(this.short_day_names)).each(function() { + tableShell += ""; + }); + tableShell += "
" + this + "
"; + + this.dateSelector = this.rootLayers = $('
').append(nav, tableShell).insertAfter(this.input); + + if ($.browser.msie && $.browser.version < 7) { + + this.ieframe = $('').insertBefore(this.dateSelector); + this.rootLayers = this.rootLayers.add(this.ieframe); + + $(".button", nav).mouseover(function() { $(this).addClass("hover") }); + $(".button", nav).mouseout(function() { $(this).removeClass("hover") }); + }; + + this.tbody = $("tbody", this.dateSelector); + + this.input.change(this.bindToObj(function() { this.selectDate(); })); + this.selectDate(); + }, + + selectMonth: function(date) { + var newMonth = new Date(date.getFullYear(), date.getMonth(), 1); + + if (!this.currentMonth || !(this.currentMonth.getFullYear() == newMonth.getFullYear() && + this.currentMonth.getMonth() == newMonth.getMonth())) { + + this.currentMonth = newMonth; + + var rangeStart = this.rangeStart(date), rangeEnd = this.rangeEnd(date); + var numDays = this.daysBetween(rangeStart, rangeEnd); + var dayCells = ""; + + for (var i = 0; i <= numDays; i++) { + var currentDay = new Date(rangeStart.getFullYear(), rangeStart.getMonth(), rangeStart.getDate() + i, 12, 00); + + if (this.isFirstDayOfWeek(currentDay)) dayCells += ""; + + if (currentDay.getMonth() == date.getMonth()) { + dayCells += '' + currentDay.getDate() + ''; + } else { + dayCells += '' + currentDay.getDate() + ''; + }; + + if (this.isLastDayOfWeek(currentDay)) dayCells += ""; + }; + this.tbody.empty().append(dayCells); + + this.monthNameSpan.empty().append(this.monthName(date)); + this.yearNameSpan.empty().append(this.currentMonth.getFullYear()); + + $(".selectable_day", this.tbody).click(this.bindToObj(function(event) { + this.changeInput($(event.target).attr("date")); + })); + + $("td[date=" + this.dateToString(new Date()) + "]", this.tbody).addClass("today"); + + $("td.selectable_day", this.tbody).mouseover(function() { $(this).addClass("hover") }); + $("td.selectable_day", this.tbody).mouseout(function() { $(this).removeClass("hover") }); + }; + + $('.selected', this.tbody).removeClass("selected"); + $('td[date=' + this.selectedDateString + ']', this.tbody).addClass("selected"); + }, + + selectDate: function(date) { + if (typeof(date) == "undefined") { + date = this.stringToDate(this.input.val()); + }; + if (!date) date = new Date(); + + this.selectedDate = date; + this.selectedDateString = this.dateToString(this.selectedDate); + this.selectMonth(this.selectedDate); + }, + + changeInput: function(dateString) { + this.input.val(dateString).change(); + this.hide(); + }, + + show: function() { + this.rootLayers.css("display", "block"); + $([window, document.body]).click(this.hideIfClickOutside); + this.input.unbind("focus", this.show); + $(document.body).keydown(this.keydownHandler); + this.setPosition(); + }, + + hide: function() { + this.rootLayers.css("display", "none"); + $([window, document.body]).unbind("click", this.hideIfClickOutside); + this.input.focus(this.show); + $(document.body).unbind("keydown", this.keydownHandler); + }, + + hideIfClickOutside: function(event) { + if (event.target != this.input[0] && !this.insideSelector(event)) { + this.hide(); + }; + }, + + insideSelector: function(event) { + var offset = this.dateSelector.position(); + offset.right = offset.left + this.dateSelector.outerWidth(); + offset.bottom = offset.top + this.dateSelector.outerHeight(); + + return event.pageY < offset.bottom && + event.pageY > offset.top && + event.pageX < offset.right && + event.pageX > offset.left; + }, + + keydownHandler: function(event) { + switch (event.keyCode) + { + case 9: + case 27: + this.hide(); + return; + break; + case 13: + this.changeInput(this.selectedDateString); + break; + case 33: + this.moveDateMonthBy(event.ctrlKey ? -12 : -1); + break; + case 34: + this.moveDateMonthBy(event.ctrlKey ? 12 : 1); + break; + case 38: + this.moveDateBy(-7); + break; + case 40: + this.moveDateBy(7); + break; + case 37: + this.moveDateBy(-1); + break; + case 39: + this.moveDateBy(1); + break; + default: + return; + } + event.preventDefault(); + }, + + stringToDate: function(string) { + var matches; + if (matches = string.match(/^(\d{1,2}) ([^\s]+) (\d{4,4})$/)) { + return new Date(matches[3], this.shortMonthNum(matches[2]), matches[1], 12, 00); + } else { + return null; + }; + }, + + dateToString: function(date) { + return date.getDate() + " " + this.short_month_names[date.getMonth()] + " " + date.getFullYear(); + }, + + setPosition: function() { + var offset = this.input.offset(); + this.rootLayers.css({ + top: offset.top + this.input.outerHeight(), + left: offset.left + }); + + if (this.ieframe) { + this.ieframe.css({ + width: this.dateSelector.outerWidth(), + height: this.dateSelector.outerHeight() + }); + }; + }, + + moveDateBy: function(amount) { + var newDate = new Date(this.selectedDate.getFullYear(), this.selectedDate.getMonth(), this.selectedDate.getDate() + amount); + this.selectDate(newDate); + }, + + moveDateMonthBy: function(amount) { + var newDate = new Date(this.selectedDate.getFullYear(), this.selectedDate.getMonth() + amount, this.selectedDate.getDate()); + if (newDate.getMonth() == this.selectedDate.getMonth() + amount + 1) { + + newDate.setDate(0); + }; + this.selectDate(newDate); + }, + + moveMonthBy: function(amount) { + var newMonth = new Date(this.currentMonth.getFullYear(), this.currentMonth.getMonth() + amount, this.currentMonth.getDate()); + this.selectMonth(newMonth); + }, + + monthName: function(date) { + return this.month_names[date.getMonth()]; + }, + + bindToObj: function(fn) { + var self = this; + return function() { return fn.apply(self, arguments) }; + }, + + bindMethodsToObj: function() { + for (var i = 0; i < arguments.length; i++) { + this[arguments[i]] = this.bindToObj(this[arguments[i]]); + }; + }, + + indexFor: function(array, value) { + for (var i = 0; i < array.length; i++) { + if (value == array[i]) return i; + }; + }, + + monthNum: function(month_name) { + return this.indexFor(this.month_names, month_name); + }, + + shortMonthNum: function(month_name) { + return this.indexFor(this.short_month_names, month_name); + }, + + shortDayNum: function(day_name) { + return this.indexFor(this.short_day_names, day_name); + }, + + daysBetween: function(start, end) { + start = Date.UTC(start.getFullYear(), start.getMonth(), start.getDate()); + end = Date.UTC(end.getFullYear(), end.getMonth(), end.getDate()); + return (end - start) / 86400000; + }, + + changeDayTo: function(dayOfWeek, date, direction) { + var difference = direction * (Math.abs(date.getDay() - dayOfWeek - (direction * 7)) % 7); + return new Date(date.getFullYear(), date.getMonth(), date.getDate() + difference); + }, + + rangeStart: function(date) { + return this.changeDayTo(this.start_of_week, new Date(date.getFullYear(), date.getMonth()), -1); + }, + + rangeEnd: function(date) { + return this.changeDayTo((this.start_of_week - 1) % 7, new Date(date.getFullYear(), date.getMonth() + 1, 0), 1); + }, + + isFirstDayOfWeek: function(date) { + return date.getDay() == this.start_of_week; + }, + + isLastDayOfWeek: function(date) { + return date.getDay() == (this.start_of_week - 1) % 7; + }, + + adjustDays: function(days) { + var newDays = []; + for (var i = 0; i < days.length; i++) { + newDays[i] = days[(i + this.start_of_week) % 7]; + }; + return newDays; + } +}; + +$.fn.date_input = function(opts) { + return this.each(function() { new DateInput(this, opts); }); +}; +$.date_input = { initialize: function(opts) { + $("input.date_input").date_input(opts); +} }; + +return DateInput; +})(jQuery); diff --git a/jquery.datespan-picker.js b/jquery.datespan-picker.js index e0a0c4d..db76af0 100644 --- a/jquery.datespan-picker.js +++ b/jquery.datespan-picker.js @@ -1,19 +1,19 @@ ( function ( $ ) { -function TimePicker ( options ) { +function DateSpanPicker ( options ) { var _options = {}; - $.extend( _options, options, TimePicker.prototype.defaults ); + $.extend( _options, options, DateSpanPicker.prototype.defaults ); - var allTimes = TimePicker.prototype._makeTimes(); + var allTimes = DateSpanPicker.prototype._makeTimes(); - function _createElement( $parent ) { + function _createElements( $parent ) { /*$parent .append( '= 12 ? 'pm' : 'am'; hour = hour == 0 ? 12 : ( hour > 12 ? hour - 12 : hour ); @@ -247,14 +256,14 @@ TimePicker.prototype._dateToTimeString = function ( date ) { return str; } -TimePicker.prototype.defaults = { +DateSpanPicker.prototype.defaults = { date_input_defaults: { - stringToDate: TimePicker.prototype._stringToDate, - dateToString: TimePicker.prototype._dateToString, + stringToDate: DateSpanPicker.prototype._stringToDate, + dateToString: DateSpanPicker.prototype._dateToString, short_day_names: ["S", "M", "T", "W", "T", "F", "S"], start_of_week: 0 } }; -$.fn.timePicker = TimePicker; +$.fn.dateSpanPicker = DateSpanPicker; })( jQuery );