/** * Zebra_DatePicker * * Zebra_DatePicker is a small, compact and highly configurable date picker plugin for jQuery * * Visit {@link http://stefangabos.ro/jquery/zebra-datepicker/} for more information. * * For more resources visit {@link http://stefangabos.ro/} * * @author Stefan Gabos * @version 1.9.5 (last revision: May 13, 2016) * @copyright (c) 2011 - 2016 Stefan Gabos * @license http://www.gnu.org/licenses/lgpl-3.0.txt GNU LESSER GENERAL PUBLIC LICENSE * @package Zebra_DatePicker */ ;(function(factory) { 'use strict'; // AMD if (typeof define === 'function' && define.amd) define(['jquery'], factory); // CommonJS else if (typeof exports === 'object') factory(require('jquery')); // browser globals else factory(jQuery); }(function($) { 'use strict'; $.Zebra_DatePicker = function(element, options) { var defaults = { // setting this property to a jQuery element, will result in the date picker being always visible, the indicated // element being the date picker's container; always_visible: false, // by default, the date picker is injected into the ; use this property to tell the library to inject // the date picker into a custom element - useful when you want the date picker to open at a specific position // // must be a jQuery element // // default is $('body') container: $('body'), // dates that should have custom classes applied to them // an object in the form of // { // 'myclass1': [dates_to_apply_the_custom_class_to], // 'myclass2': [dates_to_apply_the_custom_class_to] // } // where "dates_to_apply_the_custom_class_to" is an array of dates in the same format as required for // "disabled_dates" property. // // custom classes will be applied *only* in the day picker view and not on month/year views! // also note that the class name will have the "_disabled" suffix added if the day the class is applied to // is disabled // // in order for the styles in your custom classes to be applied, make sure you are using the following syntax: // // .Zebra_DatePicker .dp_daypicker td.myclass1 { .. } // .Zebra_DatePicker .dp_daypicker td.myclass1_disabled { .. } // // default is FALSE, no custom classes custom_classes: false, // days of the week; Sunday to Saturday days: ['日', '月', '火', '水', '木', '金', '土'], // by default, the abbreviated name of a day consists of the first 2 letters from the day's full name; // while this is common for most languages, there are also exceptions for languages like Thai, Loa, Myanmar, // etc. where this is not correct; for these cases, specify an array with the abbreviations to be used for // the 7 days of the week; leave it FALSE to use the first 2 letters of a day's name as the abbreviation. // // default is FALSE days_abbr: false, // the position of the date picker relative to the element it is attached to. note that, regardless of this // setting, the date picker's position will be automatically adjusted to fit in the viewport, if needed. // // possible values are "above" and "below" // // default is "above" default_position: 'above', // direction of the calendar // // a positive or negative integer: n (a positive integer) creates a future-only calendar beginning at n days // after today; -n (a negative integer); if n is 0, the calendar has no restrictions. use boolean true for // a future-only calendar starting with today and use boolean false for a past-only calendar ending today. // // you may also set this property to an array with two elements in the following combinations: // // - first item is boolean TRUE (calendar starts today), an integer > 0 (calendar starts n days after // today), or a valid date given in the format defined by the "format" attribute, using English for // month names (calendar starts at the specified date), and the second item is boolean FALSE (the calendar // has no ending date), an integer > 0 (calendar ends n days after the starting date), or a valid date // given in the format defined by the "format" attribute, using English for month names, and which occurs // after the starting date (calendar ends at the specified date) // // - first item is boolean FALSE (calendar ends today), an integer < 0 (calendar ends n days before today), // or a valid date given in the format defined by the "format" attribute, using English for month names // (calendar ends at the specified date), and the second item is an integer > 0 (calendar ends n days // before the ending date), or a valid date given in the format defined by the "format" attribute, using // English for month names and which occurs before the starting date (calendar starts at the specified // date) // // [1, 7] - calendar starts tomorrow and ends seven days after that // [true, 7] - calendar starts today and ends seven days after that // ['2013-01-01', false] - calendar starts on January 1st 2013 and has no ending date ("format" is YYYY-MM-DD) // [false, '2012-01-01'] - calendar ends today and starts on January 1st 2012 ("format" is YYYY-MM-DD) // // note that "disabled_dates" property will still apply! // // default is 0 (no restrictions) direction: 0, // an array of disabled dates in the following format: 'day month year weekday' where "weekday" is optional // and can be 0-6 (Saturday to Sunday); the syntax is similar to cron's syntax: the values are separated by // spaces and may contain * (asterisk) - (dash) and , (comma) delimiters: // // ['1 1 2012'] would disable January 1, 2012; // ['* 1 2012'] would disable all days in January 2012; // ['1-10 1 2012'] would disable January 1 through 10 in 2012; // ['1,10 1 2012'] would disable January 1 and 10 in 2012; // ['1-10,20,22,24 1-3 *'] would disable 1 through 10, plus the 22nd and 24th of January through March for every year; // ['* * * 0,6'] would disable all Saturdays and Sundays; // ['01 07 2012', '02 07 2012', '* 08 2012'] would disable 1st and 2nd of July 2012, and all of August of 2012 // // default is FALSE, no disabled dates // // DISABLING ALL DATES AND NOT SPECIFYING AT LEAST ONE ENABLED DATE WILL SEND THE SCRIPT INTO AN INFINITE // LOOP SEARCHING FOR AN ENABLED DATE TO DISPLAY! disabled_dates: false, // an array of enabled dates in the same format as required for "disabled_dates" property. // to be used together with the "disabled_dates" property by first setting the "disabled_dates" property to // something like "[* * * *]" (which will disable everything) and the setting the "enabled_dates" property to, // say, "[* * * 0,6]" to enable just weekends. enabled_dates: false, // week's starting day // // valid values are 0 to 6, Sunday to Saturday // // default is 1, Monday first_day_of_week: 0, // format of the returned date // // accepts the following characters for date formatting: d, D, j, l, N, w, S, F, m, M, n, Y, y borrowing // syntax from PHP's "date" function. // // note that when setting a date format without days ('d', 'j'), the users will be able to select only years // and months, and when setting a format without months and days ('F', 'm', 'M', 'n', 'd', 'j'), the // users will be able to select only years; likewise, when setting a date format with just months ('F', 'm', // 'M', 'n') or just years ('Y', 'y'), users will be able to select only months and years, respectively. // // also note that the value of the "view" property (see below) may be overridden if it is the case: a value of // "days" for the "view" property makes no sense if the date format doesn't allow the selection of days. // // default is Y-m-d Y/m/d(D) format: 'Y/m/d', // captions in the datepicker's header, for the 3 possible views: days, months, years // // for each of the 3 views the following special characters may be used borrowing from PHP's "date" function's // syntax: m, n, F, M, y and Y; any of these will be replaced at runtime with the appropriate date fragment, // depending on the currently viewed date. two more special characters are also available Y1 and Y2 (upper // case representing years with 4 digits, lowercase representing years with 2 digits) which represent // "currently selected year - 7" and "currently selected year + 4" and which only make sense used in the // "years" view. // // even though any of these special characters may be used in any of the 3 views, you should use m, n, F, M // for the "days" view and y, Y, Y1, Y2, y1, y2 for the "months" and "years" view or you may get unexpected // results! // // Text and HTML can also be used, and will be rendered as it is, as in the example below (the library is // smart enough to not replace special characters when used in words or HTML tags): // // header_captions: { // 'days': 'Departure:
F, Y', // 'months': 'Departure:
Y', // 'years': 'Departure:
Y1 - Y2' // } // // Default is // // header_captions: { // 'days': 'F, Y', // 'months': 'Y', // 'years': 'Y1 - Y2' // } header_captions: { 'days': 'Y年 F', 'months': 'Y', 'years': 'Y1 - Y2' }, // HTML to be used for the previous month/next month buttons // // default is ['«','»'] header_navigation: ['«', '»'], // icon's position // accepted values are "left" and "right" // // default is "right" icon_position: 'right', // should the icon for opening the datepicker be inside the element? // if set to FALSE, the icon will be placed to the right of the parent element, while if set to TRUE it will // be placed to the right of the parent element, but *inside* the element itself // // default is TRUE inside: true, // the caption for the "Clear" button lang_clear_date: 'リセット', // months names months: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'], // by default, the abbreviated name of a month consists of the first 3 letters from the month's full name; // while this is common for most languages, there are also exceptions for languages like Thai, Loa, Myanmar, // etc. where this is not correct; for these cases, specify an array with the abbreviations to be used for // the months of the year; leave it FALSE to use the first 3 letters of a month's name as the abbreviation. // // default is FALSE months_abbr: false, // the offset, in pixels (x, y), to shift the date picker's position relative to the top-right of the icon // that toggles the date picker or, if the icon is disabled, relative to the top-right corner of the element // the plugin is attached to. // // note that this only applies if the position of element relative to the browser's viewport doesn't require // the date picker to be placed automatically so that it is visible! // // default is [5, -5] offset: [5, -5], // set whether the date picker should be shown *only* when clicking the icon // note that if you set the "show_icon" property to FALSE, you will not be able to show the date picker anymore! // // default is FALSE open_icon_only: false, // if set as a jQuery element with a Zebra_DatePicker attached, that particular date picker will use the // current date picker's value as starting date // note that the rules set in the "direction" property will still apply, only that the reference date will // not be the current system date but the value selected in the current date picker // default is FALSE (not paired with another date picker) pair: false, // should the element the calendar is attached to, be read-only? // if set to TRUE, a date can be set only through the date picker and cannot be entered manually // // default is TRUE readonly_element: true, // should days from previous and/or next month be selectable when visible? // note that if the value of this property is set to TRUE, the value of "show_other_months" will be considered // TRUE regardless of the actual value! // // default is FALSE select_other_months: false, // should the "Clear date" button be visible? // // accepted values are: // // - 0 (zero) - the button for clearing a previously selected date is shown only if a previously selected date // already exists; this means that if the input the date picker is attached to is empty, and the user selects // a date for the first time, this button will not be visible; once the user picked a date and opens the date // picker again, this time the button will be visible. // // - TRUE will make the button visible all the time // // - FALSE will disable the button // // default is "0" (without quotes) show_clear_date: 0, // should a calendar icon be added to the elements the plugin is attached to? // // default is TRUE show_icon: true, // should days from previous and/or next month be visible? // // default is TRUE show_other_months: true, // should the "Today" button be visible? // setting it to anything but boolean FALSE will enable the button and will use the property's value as // caption for the button; setting it to FALSE will disable the button // // default is "Today" show_select_today: '本日', // should an extra column be shown, showing the number of each week? // anything other than FALSE will enable this feature, and use the given value as column title // i.e. show_week_number: 'Wk' would enable this feature and have "Wk" as the column's title // // default is FALSE show_week_number: false, // a default date to start the date picker with // must be specified in the format defined by the "format" property, or it will be ignored! // note that this value is used only if there is no value in the field the date picker is attached to! start_date: false, // should default values, in the input field the date picker is attached to, be deleted if they are not valid // according to "direction" and/or "disabled_dates"? // // default is FALSE strict: false, // how should the date picker start; valid values are "days", "months" and "years" // note that the date picker is always cycling days-months-years when clicking in the date picker's header, // and years-months-days when selecting dates (unless one or more of the views are missing due to the date's // format) // // also note that the value of the "view" property may be overridden if the date's format requires so! (i.e. // "days" for the "view" property makes no sense if the date format doesn't allow the selection of days) // // default is "days" view: 'days', // days of the week that are considered "weekend days" // valid values are 0 to 6, Sunday to Saturday // // default values are 0 and 6 (Saturday and Sunday) weekend_days: [0, 6], // when set to TRUE, day numbers < 10 will be prefixed with 0; set to FALSE if you don't want that // // default is TRUE zero_pad: false, // callback function to be executed whenever the user changes the view (days/months/years), as well as when // the user navigates by clicking on the "next"/"previous" icons in any of the views; // // the callback function called by this event takes 3 arguments - the first argument represents the current // view (can be "days", "months" or "years"), the second argument represents an array containing the "active" // elements (not disabled) from the view, as jQuery elements, allowing for easy customization and interaction // with particular cells in the date picker's view, while the third argument is a reference to the element // the date picker is attached to, as a jQuery object (deprecated - use the "this" keyword inside the callback // function to refer to the element the date picker is attached to) // // for simplifying searching for particular dates, each element in the second argument will also have a // "date" data attribute whose format depends on the value of the "view" argument: // - YYYY-MM-DD for elements in the "days" view // - YYYY-MM for elements in the "months" view // - YYYY for elements in the "years" view // // the "this" keyword inside the callback function refers to the element the date picker is attached to! onChange: null, // callback function to be executed when the user clicks the "Clear" button // the callback function takes a single argument: // - a reference to the element the date picker is attached to, as a jQuery object (deprecated - use the // "this" keyword inside the callback function to refer to the element the date picker is attached to) // // the "this" keyword inside the callback function refers to the element the date picker is attached to! onClear: null, // callback function to be executed when the date picker is shown // the callback function takes a single argument: // - a reference to the element the date picker is attached to, as a jQuery object (deprecated - use the // "this" keyword inside the callback function to refer to the element the date picker is attached to) // // the "this" keyword inside the callback function refers to the element the date picker is attached to! onOpen: null, // callback function to be executed when the date picker is closed, but only when the "always_visible" // property is set to FALSE // the callback function takes a single argument: // - a reference to the element the date picker is attached to, as a jQuery object (deprecated - use the // "this" keyword inside the callback function to refer to the element the date picker is attached to) // // the "this" keyword inside the callback function refers to the element the date picker is attached to! onClose: null, // callback function to be executed when a date is selected // the callback function takes 5 arguments: // - the date in the format specified by the "format" attribute; // - the date in YYYY-MM-DD format // - the date as a JavaScript Date object // - a reference to the element the date picker is attached to, as a jQuery object (deprecated - use the // "this" keyword inside the callback function to refer to the element the date picker is attached to) // - the ISO 8601 week number of the selected date // // the "this" keyword inside the callback function refers to the element the date picker is attached to! onSelect: null }; // private properties var view, datepicker, icon, header, daypicker, monthpicker, yearpicker, cleardate, current_system_month, current_system_year, current_system_day, first_selectable_month, first_selectable_year, first_selectable_day, selected_month, selected_year, default_day, default_month, default_year, enabled_dates, disabled_dates, shim, start_date, end_date, last_selectable_day, last_selectable_year, last_selectable_month, daypicker_cells, monthpicker_cells, yearpicker_cells, views, clickables, selecttoday, footer, show_select_today, timeout, uniqueid, custom_classes, custom_class_names, original_attributes = {}; var plugin = this; plugin.settings = {}; // the jQuery version of the element // "element" (without the $) will point to the DOM element var $element = $(element); /** * Constructor method. Initializes the date picker. * * @return void */ var init = function(update) { // generate a random ID for each date picker (we'll use this if later a certain date picker is destroyed to // remove related events) // the code is taken from http://stackoverflow.com/a/105074 uniqueid = Math.floor((1 + Math.random()) * 0x10000).toString(16); // unless we're not just updating settings if (!update) { // merge default settings with user-settings ( plugin.settings = $.extend({}, defaults, options); // preserve some of element's original attributes original_attributes['readonly'] = $element.attr('readonly'); original_attributes['style'] = $element.attr('style'); // iterate through the element's data attributes (if any) for (var data in $element.data()) // if data attribute's name starts with "zdp_" if (data.indexOf('zdp_') === 0) { // remove the "zdp_" prefix data = data.replace(/^zdp\_/, ''); // if such a property exists if (undefined !== defaults[data]) // update the property's value // (note that for the "pair" property we need to convert the property to an element) plugin.settings[data] = (data == 'pair' ? $($element.data('zdp_' + data)) : $element.data('zdp_' + data)); } } // if the element should be read-only, set the "readonly" attribute if (plugin.settings.readonly_element) $element.attr('readonly', 'readonly'); // determine the views the user can cycle through, depending on the format // that is, if the format doesn't contain the day, the user will be able to cycle only through years and months, // whereas if the format doesn't contain months nor days, the user will only be able to select years var // the characters that may be present in the date format and that represent days, months and years date_chars = { days: ['d', 'j', 'D'], months: ['F', 'm', 'M', 'n', 't'], years: ['o', 'Y', 'y'] }, // some defaults has_days = false, has_months = false, has_years = false, type = null; // iterate through all the character blocks for (type in date_chars) // iterate through the characters of each block $.each(date_chars[type], function(index, character) { // if current character exists in the "format" property if (plugin.settings.format.indexOf(character) > -1) // set to TRUE the appropriate flag if (type == 'days') has_days = true; else if (type == 'months') has_months = true; else if (type == 'years') has_years = true; }); // if user can cycle through all the views, set the flag accordingly if (has_days && has_months && has_years) views = ['years', 'months', 'days']; // if user can cycle only through year and months, set the flag accordingly else if (!has_days && has_months && has_years) views = ['years', 'months']; // if user can cycle only through months and days, set the flag accordingly else if (has_days && has_months && !has_years) views = ['months', 'days']; // if user can only see the year picker, set the flag accordingly else if (!has_days && !has_months && has_years) views = ['years']; // if user can only see the month picker, set the flag accordingly else if (!has_days && has_months && !has_years) views = ['months']; // if invalid format (no days, no months, no years) use the default where the user is able to cycle through // all the views else views = ['years', 'months', 'days']; // if the starting view is not amongst the views the user can cycle through, set the correct starting view if ($.inArray(plugin.settings.view, views) == -1) plugin.settings.view = views[views.length - 1]; // parse the rules for disabling dates and turn them into arrays of arrays // array that will hold the rules for enabling/disabling dates disabled_dates = []; enabled_dates = []; custom_classes = {}; custom_class_names = []; var dates; for (var k in plugin.settings.custom_classes) if (plugin.settings.custom_classes.hasOwnProperty(k)) custom_class_names.push(k); // it's the same logic for preparing the enabled/disable dates, as well as dates that have custom classes for (var l = 0; l < 2 + custom_class_names.length; l++) { // first time we're doing disabled dates, if (l === 0) dates = plugin.settings.disabled_dates; // second time we're doing enabled_dates else if (l == 1) dates = plugin.settings.enabled_dates; // otherwise, we're doing dates that will have custom classes else dates = plugin.settings.custom_classes[custom_class_names[l - 2]]; // if we have a non-empty array if ($.isArray(dates) && dates.length > 0) // iterate through the rules $.each(dates, function() { // split the values in rule by white space var rules = this.split(' '); // there can be a maximum of 4 rules (days, months, years and, optionally, day of the week) for (var i = 0; i < 4; i++) { // if one of the values is not available // replace it with a * (wildcard) if (!rules[i]) rules[i] = '*'; // if rule contains a comma, create a new array by splitting the rule by commas // if there are no commas create an array containing the rule's string rules[i] = (rules[i].indexOf(',') > -1 ? rules[i].split(',') : new Array(rules[i])); // iterate through the items in the rule for (var j = 0; j < rules[i].length; j++) // if item contains a dash (defining a range) if (rules[i][j].indexOf('-') > -1) { // get the lower and upper limits of the range var limits = rules[i][j].match(/^([0-9]+)\-([0-9]+)/); // if range is valid if (null !== limits) { // iterate through the range for (var k = to_int(limits[1]); k <= to_int(limits[2]); k++) // if value is not already among the values of the rule // add it to the rule if ($.inArray(k, rules[i]) == -1) rules[i].push(k + ''); // remove the range indicator rules[i].splice(j, 1); } } // iterate through the items in the rule // and make sure that numbers are numbers for (j = 0; j < rules[i].length; j++) rules[i][j] = (isNaN(to_int(rules[i][j])) ? rules[i][j] : to_int(rules[i][j])); } // add to the correct list of processed rules // first time we're doing disabled dates, if (l === 0) disabled_dates.push(rules); // second time we're doing enabled_dates else if (l == 1) enabled_dates.push(rules); // otherwise, we're doing the dates to which custom classes need to be applied else { if (undefined === custom_classes[custom_class_names[l - 2]]) custom_classes[custom_class_names[l - 2]] = []; custom_classes[custom_class_names[l - 2]].push(rules); } }); } var // cache the current system date date = new Date(), // when the date picker's starting date depends on the value of another date picker, this value will be // set by the other date picker // this value will be used as base for all calculations (if not set, will be the same as the current // system date) reference_date = (!plugin.settings.reference_date ? ($element.data('zdp_reference_date') && undefined !== $element.data('zdp_reference_date') ? $element.data('zdp_reference_date') : date) : plugin.settings.reference_date), tmp_start_date, tmp_end_date; // reset these values here as this method might be called more than once during a date picker's lifetime // (when the selectable dates depend on the values from another date picker) start_date = undefined; end_date = undefined; // extract the date parts // also, save the current system month/day/year - we'll use them to highlight the current system date first_selectable_month = reference_date.getMonth(); current_system_month = date.getMonth(); first_selectable_year = reference_date.getFullYear(); current_system_year = date.getFullYear(); first_selectable_day = reference_date.getDate(); current_system_day = date.getDate(); // check if the calendar has any restrictions // calendar is future-only, starting today // it means we have a starting date (the current system date), but no ending date if (plugin.settings.direction === true) start_date = reference_date; // calendar is past only, ending today else if (plugin.settings.direction === false) { // it means we have an ending date (the reference date), but no starting date end_date = reference_date; // extract the date parts last_selectable_month = end_date.getMonth(); last_selectable_year = end_date.getFullYear(); last_selectable_day = end_date.getDate(); } else if ( // if direction is not given as an array and the value is an integer > 0 (!$.isArray(plugin.settings.direction) && is_integer(plugin.settings.direction) && to_int(plugin.settings.direction) > 0) || // or direction is given as an array ($.isArray(plugin.settings.direction) && ( // and first entry is a valid date (tmp_start_date = check_date(plugin.settings.direction[0])) || // or a boolean TRUE plugin.settings.direction[0] === true || // or an integer > 0 (is_integer(plugin.settings.direction[0]) && plugin.settings.direction[0] > 0) ) && ( // and second entry is a valid date (tmp_end_date = check_date(plugin.settings.direction[1])) || // or a boolean FALSE plugin.settings.direction[1] === false || // or integer >= 0 (is_integer(plugin.settings.direction[1]) && plugin.settings.direction[1] >= 0) )) ) { // if an exact starting date was given, use that as a starting date if (tmp_start_date) start_date = tmp_start_date; // otherwise else // figure out the starting date // use the Date object to normalize the date // for example, 2011 05 33 will be transformed to 2011 06 02 start_date = new Date( first_selectable_year, first_selectable_month, first_selectable_day + (!$.isArray(plugin.settings.direction) ? to_int(plugin.settings.direction) : to_int(plugin.settings.direction[0] === true ? 0 : plugin.settings.direction[0])) ); // re-extract the date parts first_selectable_month = start_date.getMonth(); first_selectable_year = start_date.getFullYear(); first_selectable_day = start_date.getDate(); // if an exact ending date was given and the date is after the starting date, use that as a ending date if (tmp_end_date && +tmp_end_date >= +start_date) end_date = tmp_end_date; // if have information about the ending date else if (!tmp_end_date && plugin.settings.direction[1] !== false && $.isArray(plugin.settings.direction)) // figure out the ending date // use the Date object to normalize the date // for example, 2011 05 33 will be transformed to 2011 06 02 end_date = new Date( first_selectable_year, first_selectable_month, first_selectable_day + to_int(plugin.settings.direction[1]) ); // if a valid ending date exists if (end_date) { // extract the date parts last_selectable_month = end_date.getMonth(); last_selectable_year = end_date.getFullYear(); last_selectable_day = end_date.getDate(); } } else if ( // if direction is not given as an array and the value is an integer < 0 (!$.isArray(plugin.settings.direction) && is_integer(plugin.settings.direction) && to_int(plugin.settings.direction) < 0) || // or direction is given as an array ($.isArray(plugin.settings.direction) && ( // and first entry is boolean FALSE plugin.settings.direction[0] === false || // or an integer < 0 (is_integer(plugin.settings.direction[0]) && plugin.settings.direction[0] < 0) ) && ( // and second entry is a valid date (tmp_start_date = check_date(plugin.settings.direction[1])) || // or an integer >= 0 (is_integer(plugin.settings.direction[1]) && plugin.settings.direction[1] >= 0) )) ) { // figure out the ending date // use the Date object to normalize the date // for example, 2011 05 33 will be transformed to 2011 06 02 end_date = new Date( first_selectable_year, first_selectable_month, first_selectable_day + (!$.isArray(plugin.settings.direction) ? to_int(plugin.settings.direction) : to_int(plugin.settings.direction[0] === false ? 0 : plugin.settings.direction[0])) ); // re-extract the date parts last_selectable_month = end_date.getMonth(); last_selectable_year = end_date.getFullYear(); last_selectable_day = end_date.getDate(); // if an exact starting date was given, and the date is before the ending date, use that as a starting date if (tmp_start_date && +tmp_start_date < +end_date) start_date = tmp_start_date; // if have information about the starting date else if (!tmp_start_date && $.isArray(plugin.settings.direction)) // figure out the staring date // use the Date object to normalize the date // for example, 2011 05 33 will be transformed to 2011 06 02 start_date = new Date( last_selectable_year, last_selectable_month, last_selectable_day - to_int(plugin.settings.direction[1]) ); // if a valid starting date exists if (start_date) { // extract the date parts first_selectable_month = start_date.getMonth(); first_selectable_year = start_date.getFullYear(); first_selectable_day = start_date.getDate(); } // if there are disabled dates } else if ($.isArray(plugin.settings.disabled_dates) && plugin.settings.disabled_dates.length > 0) // iterate through the rules for disabling dates for (var interval in disabled_dates) // only if there is a rule that disables *everything* if (disabled_dates[interval][0] == '*' && disabled_dates[interval][1] == '*' && disabled_dates[interval][2] == '*' && disabled_dates[interval][3] == '*') { var tmpDates = []; // iterate through the rules for enabling dates // looking for the minimum/maximum selectable date (if it's the case) $.each(enabled_dates, function() { var rule = this; // if the rule doesn't apply to all years if (rule[2][0] != '*') // format date and store it in our stack tmpDates.push(parseInt( rule[2][0] + (rule[1][0] == '*' ? '12' : str_pad(rule[1][0], 2)) + (rule[0][0] == '*' ? (rule[1][0] == '*' ? '31' : new Date(rule[2][0], rule[1][0], 0).getDate()) : str_pad(rule[0][0], 2)), 10)); }); // sort dates ascending tmpDates.sort(); // if we have any rules if (tmpDates.length > 0) { // get date parts var matches = (tmpDates[0] + '').match(/([0-9]{4})([0-9]{2})([0-9]{2})/); // assign the date parts to the appropriate variables first_selectable_year = parseInt(matches[1], 10); first_selectable_month = parseInt(matches[2], 10) - 1; first_selectable_day = parseInt(matches[3], 10); } // don't look further break; } // if first selectable date exists but is disabled, find the actual first selectable date if (is_disabled(first_selectable_year, first_selectable_month, first_selectable_day)) { // loop until we find the first selectable year while (is_disabled(first_selectable_year)) { // if calendar is past-only, if (!start_date) { // decrement the year first_selectable_year--; // because we've changed years, reset the month to December first_selectable_month = 11; // otherwise } else { // increment the year first_selectable_year++; // because we've changed years, reset the month to January first_selectable_month = 0; } } // loop until we find the first selectable month while (is_disabled(first_selectable_year, first_selectable_month)) { // if calendar is past-only if (!start_date) { // decrement the month first_selectable_month--; // because we've changed months, reset the day to the last day of the month first_selectable_day = new Date(first_selectable_year, first_selectable_month + 1, 0).getDate(); // otherwise } else { // increment the month first_selectable_month++; // because we've changed months, reset the day to the first day of the month first_selectable_day = 1; } // if we moved to a following year if (first_selectable_month > 11) { // increment the year first_selectable_year++; // reset the month to January first_selectable_month = 0; // because we've changed months, reset the day to the first day of the month first_selectable_day = 1; // if we moved to a previous year } else if (first_selectable_month < 0) { // decrement the year first_selectable_year--; // reset the month to December first_selectable_month = 11; // because we've changed months, reset the day to the last day of the month first_selectable_day = new Date(first_selectable_year, first_selectable_month + 1, 0).getDate(); } } // loop until we find the first selectable day while (is_disabled(first_selectable_year, first_selectable_month, first_selectable_day)) { // if calendar is past-only, decrement the day if (!start_date) first_selectable_day--; // otherwise, increment the day else first_selectable_day++; // use the Date object to normalize the date // for example, 2011 05 33 will be transformed to 2011 06 02 date = new Date(first_selectable_year, first_selectable_month, first_selectable_day); // re-extract date parts from the normalized date // as we use them in the current loop first_selectable_year = date.getFullYear(); first_selectable_month = date.getMonth(); first_selectable_day = date.getDate(); } // use the Date object to normalize the date // for example, 2011 05 33 will be transformed to 2011 06 02 date = new Date(first_selectable_year, first_selectable_month, first_selectable_day); // re-extract date parts from the normalized date // as we use them in the current loop first_selectable_year = date.getFullYear(); first_selectable_month = date.getMonth(); first_selectable_day = date.getDate(); } // get the default date, from the element, and check if it represents a valid date, according to the required format var default_date = check_date($element.val() || (plugin.settings.start_date ? plugin.settings.start_date : '')); // if there is a default date, date picker is in "strict" mode, and the default date is disabled if (default_date && plugin.settings.strict && is_disabled(default_date.getFullYear(), default_date.getMonth(), default_date.getDate())) // clear the value of the parent element $element.val(''); // updates value for the date picker whose starting date depends on the selected date (if any) if (!update && (undefined !== start_date || undefined !== default_date)) update_dependent(undefined !== default_date ? default_date : start_date); // if date picker is not always visible if (!plugin.settings.always_visible) { // if we're just creating the date picker if (!update) { // if a calendar icon should be added to the element the plugin is attached to, create the icon now if (plugin.settings.show_icon) { // strangely, in Firefox 21+ (or maybe even earlier) input elements have their "display" property // set to "inline" instead of "inline-block" as do all the other browsers. // because this behavior brakes the positioning of the icon, we'll set the "display" property to // "inline-block" before anything else; if (browser.name == 'firefox' && $element.is('input[type="text"]') && $element.css('display') == 'inline') $element.css('display', 'inline-block'); // we create a wrapper for the parent element so that we can later position the icon // also, make sure the wrapper inherits some important css properties of the parent element var icon_wrapper = $('').css({ 'display': $element.css('display'), 'position': $element.css('position') == 'static' ? 'relative' : $element.css('position'), 'float': $element.css('float'), 'top': $element.css('top'), 'right': $element.css('right'), 'bottom': $element.css('bottom'), 'left': $element.css('left') }); // if parent element has its "display" property set to "block" // the wrapper has to have its "width" set if ($element.css('display') == 'block') icon_wrapper.css('width', $element.outerWidth(true)); // put wrapper around the element // also, make sure we set some important css properties for it $element.wrap(icon_wrapper).css({ 'position': 'relative', 'top': 'auto', 'right': 'auto', 'bottom': 'auto', 'left': 'auto' }); // create the actual calendar icon (show a disabled icon if the element is disabled) icon = $(''); // a reference to the icon, as a global property plugin.icon = icon; // the date picker will open when clicking both the icon and the element the plugin is attached to // (or the icon only, if set so) clickables = plugin.settings.open_icon_only ? icon : icon.add($element); // if calendar icon is not visible, the date picker will open when clicking the element } else clickables = $element; // attach the click event to the clickable elements (icon and/or element) clickables.bind('click.Zebra_DatePicker_' + uniqueid, function(e) { e.preventDefault(); // if element is not disabled if (!$element.attr('disabled')) // if the date picker is visible, hide it if (datepicker.hasClass('dp_visible')) plugin.hide(); // if the date picker is not visible, show it else plugin.show(); }); // if users can manually enter dates and a pair date element exists if (!plugin.settings.readonly_element && plugin.settings.pair) // whenever the element looses focus $element.bind('blur.Zebra_DatePicker_' + uniqueid, function() { var date; // if a valid date was entered, update the paired date picker if ((date = check_date($(this).val())) && !is_disabled(date.getFullYear(), date.getMonth(), date.getDate())) update_dependent(date); }); // if icon exists, inject it into the DOM, right after the parent element (and inside the wrapper) if (undefined !== icon) icon.insertAfter($element); } // if calendar icon exists if (undefined !== icon) { // needed when updating: remove any inline style set previously by library, // so we get the right values below icon.attr('style', ''); // if calendar icon is to be placed *inside* the element // add an extra class to the icon if (plugin.settings.inside) icon.addClass('Zebra_DatePicker_Icon_Inside_' + (plugin.settings.icon_position == 'right' ? 'Right' : 'Left')); var // get element's width and height (including margins) element_width = $element.outerWidth(), element_height = $element.outerHeight(), element_margin_left = parseInt($element.css('marginLeft'), 10) || 0, element_margin_top = parseInt($element.css('marginTop'), 10) || 0, // get icon's width, height and margins icon_width = icon.outerWidth(), icon_height = icon.outerHeight(), icon_margin_left = parseInt(icon.css('marginLeft'), 10) || 0, icon_margin_right = parseInt(icon.css('marginRight'), 10) || 0; // if icon is to be placed *inside* the element // position the icon accordingly if (plugin.settings.inside) { // set icon's top icon.css('top', element_margin_top + ((element_height - icon_height) / 2)); // place icon to the right or to the left, according to the settings if (plugin.settings.icon_position == 'right') icon.css('right', 0); else icon.css('left', 0); // if icon is to be placed to the right of the element // position the icon accordingly } else icon.css({ 'top': element_margin_top + ((element_height - icon_height) / 2), 'left': element_margin_left + element_width + icon_margin_left }); // assume the datepicker is not disabled icon.removeClass(' Zebra_DatePicker_Icon_Disabled'); // if element the datepicker is attached to became disabled, disable the calendar icon, too if ($element.attr('disabled') == 'disabled') icon.addClass('Zebra_DatePicker_Icon_Disabled'); } } // if the "Today" button is to be shown and it makes sense to be shown // (the "days" view is available and "today" is not a disabled date) show_select_today = (plugin.settings.show_select_today !== false && $.inArray('days', views) > -1 && !is_disabled(current_system_year, current_system_month, current_system_day) ? plugin.settings.show_select_today : false); // if we just needed to recompute the things above if (update) { // make sure we update these strings, in case they've changed $('.dp_previous', datepicker).html(plugin.settings.header_navigation[0]); $('.dp_next', datepicker).html(plugin.settings.header_navigation[1]); $('.dp_clear', datepicker).html(plugin.settings.lang_clear_date); $('.dp_today', datepicker).html(plugin.settings.show_select_today); // don't go further return; } // update icon/date picker position on resize and/or changing orientation $(window).bind('resize.Zebra_DatePicker_' + uniqueid + ', orientationchange.Zebra_DatePicker_' + uniqueid, function() { // hide the date picker plugin.hide(); // if the icon is visible, update its position as the parent element might have changed position if (icon !== undefined) { // we use timeouts so that we do not call the "update" method on *every* step of the resize event // clear a previously set timeout clearTimeout(timeout); // set timeout again timeout = setTimeout(function() { // update the date picker plugin.update(); }, 100); } }); // generate the container that will hold everything var html = '' + '
' + '' + '' + '' + '' + '' + '' + '
' + plugin.settings.header_navigation[0] + ' ' + plugin.settings.header_navigation[1] + '
' + '
' + '
' + '
' + '' + '' + '' + '' + '
'; // create a jQuery object out of the HTML above and create a reference to it datepicker = $(html); // a reference to the calendar, as a global property plugin.datepicker = datepicker; // create references to the different parts of the date picker header = $('table.dp_header', datepicker); daypicker = $('table.dp_daypicker', datepicker); monthpicker = $('table.dp_monthpicker', datepicker); yearpicker = $('table.dp_yearpicker', datepicker); footer = $('table.dp_footer', datepicker); selecttoday = $('td.dp_today', footer); cleardate = $('td.dp_clear', footer); // if date picker is not always visible if (!plugin.settings.always_visible) // inject the container into the DOM plugin.settings.container.append(datepicker); // otherwise, if element is not disabled else if (!$element.attr('disabled')) { // inject the date picker into the designated container element plugin.settings.always_visible.append(datepicker); // and make it visible right away plugin.show(); } // add the mouseover/mousevents to all to the date picker's cells // except those that are not selectable datepicker. delegate('td:not(.dp_disabled, .dp_weekend_disabled, .dp_not_in_month, .dp_week_number)', 'mouseover', function() { // dp_captionのhoverを無効化 if ($(this).attr('class') != 'dp_caption') { $(this).addClass('dp_hover'); } }). delegate('td:not(.dp_disabled, .dp_weekend_disabled, .dp_not_in_month, .dp_week_number)', 'mouseout', function() { $(this).removeClass('dp_hover'); }); // prevent text highlighting for the text in the header // (for the case when user keeps clicking the "next" and "previous" buttons) disable_text_select($('td', header)); // event for when clicking the "previous" button $('.dp_previous', header).bind('click', function() { // if view is "months" // decrement year by one if (view == 'months') selected_year--; // if view is "years" // decrement years by 12 else if (view == 'years') selected_year -= 12; // if view is "days" // decrement the month and // if month is out of range else if (--selected_month < 0) { // go to the last month of the previous year selected_month = 11; selected_year--; } // generate the appropriate view manage_views(); }); // attach a click event to the caption in header // ---------- event stopped start /---------- //$('.dp_caption', header).bind('click', function() { // // // if current view is "days", take the user to the next view, depending on the format // if (view == 'days') view = ($.inArray('months', views) > -1 ? 'months' : ($.inArray('years', views) > -1 ? 'years' : 'days')); // // // if current view is "months", take the user to the next view, depending on the format // else if (view == 'months') view = ($.inArray('years', views) > -1 ? 'years' : ($.inArray('days', views) > -1 ? 'days' : 'months')); // // // if current view is "years", take the user to the next view, depending on the format // else view = ($.inArray('days', views) > -1 ? 'days' : ($.inArray('months', views) > -1 ? 'months' : 'years')); // // // generate the appropriate view // manage_views(); // //}); // ----------/ event stopped start ---------- // event for when clicking the "next" button $('.dp_next', header).bind('click', function() { // if view is "months" // increment year by 1 if (view == 'months') selected_year++; // if view is "years" // increment years by 12 else if (view == 'years') selected_year += 12; // if view is "days" // increment the month and // if month is out of range else if (++selected_month == 12) { // go to the first month of the next year selected_month = 0; selected_year++; } // generate the appropriate view manage_views(); }); // attach a click event for the cells in the day picker daypicker.delegate('td:not(.dp_disabled, .dp_weekend_disabled, .dp_not_in_month, .dp_week_number)', 'click', function() { // if other months are selectable and currently clicked cell contains a class with the cell's date if (plugin.settings.select_other_months && $(this).attr('class') && null !== (matches = $(this).attr('class').match(/date\_([0-9]{4})(0[1-9]|1[012])(0[1-9]|[12][0-9]|3[01])/))) // use the stored date select_date(matches[1], matches[2] - 1, matches[3], 'days', $(this)); // put selected date in the element the plugin is attached to, and hide the date picker else select_date(selected_year, selected_month, to_int($(this).html()), 'days', $(this)); }); // attach a click event for the cells in the month picker monthpicker.delegate('td:not(.dp_disabled)', 'click', function() { // get the month we've clicked on var matches = $(this).attr('class').match(/dp\_month\_([0-9]+)/); // set the selected month selected_month = to_int(matches[1]); // if user can select only years and months if ($.inArray('days', views) == -1) // put selected date in the element the plugin is attached to, and hide the date picker select_date(selected_year, selected_month, 1, 'months', $(this)); else { // direct the user to the "days" view view = 'days'; // if date picker is always visible // empty the value in the text box the date picker is attached to if (plugin.settings.always_visible) $element.val(''); // generate the appropriate view manage_views(); } }); // attach a click event for the cells in the year picker yearpicker.delegate('td:not(.dp_disabled)', 'click', function() { // set the selected year selected_year = to_int($(this).html()); // if user can select only years if ($.inArray('months', views) == -1) // put selected date in the element the plugin is attached to, and hide the date picker select_date(selected_year, 1, 1, 'years', $(this)); else { // direct the user to the "months" view view = 'months'; // if date picker is always visible // empty the value in the text box the date picker is attached to if (plugin.settings.always_visible) $element.val(''); // generate the appropriate view manage_views(); } }); // function to execute when the "Today" button is clicked $(selecttoday).bind('click', function(e) { e.preventDefault(); // select the current date select_date(current_system_year, current_system_month, current_system_day, 'days', $('.dp_current', daypicker)); // if date picker is always visible if (plugin.settings.always_visible) // repaint the datepicker so it centers on the currently selected date plugin.show(); // hide the date picker plugin.hide(); }); // function to execute when the "Clear" button is clicked $(cleardate).bind('click', function(e) { e.preventDefault(); // clear the element's value $element.val(''); // if date picker is not always visible if (!plugin.settings.always_visible) { // reset these values default_day = null; default_month = null; default_year = null; selected_month = null; selected_year = null; // if date picker is always visible } else { // reset these values default_day = null; default_month = null; default_year = null; // remove the "selected" class from all cells that have it $('td.dp_selected', datepicker).removeClass('dp_selected'); } // hide the date picker plugin.hide(); // if a callback function exists for when clearing a date if (plugin.settings.onClear && typeof plugin.settings.onClear == 'function') // execute the callback function and pass as argument the element the plugin is attached to plugin.settings.onClear.call($element, $element); }); // if date picker is not always visible if (!plugin.settings.always_visible) { //whenever anything is clicked on the page $(document).bind('mousedown.Zebra_DatePicker_' + uniqueid + ', touchstart.Zebra_DatePicker_' + uniqueid, function(e) { // if the date picker is visible if (datepicker.hasClass('dp_visible')) { // if the calendar icon is visible and we clicked it, let the onClick event of the icon to handle the event // (we want it to toggle the date picker) if (plugin.settings.show_icon && $(e.target).get(0) === icon.get(0)) return true; // if what's clicked is not inside the date picker // hide the date picker if ($(e.target).parents().filter('.Zebra_DatePicker').length === 0) plugin.hide(); } }); //whenever a key is pressed on the page $(document).bind('keyup.Zebra_DatePicker_' + uniqueid, function(e) { // if the date picker is visible // and the pressed key is ESC // hide the date picker if (datepicker.hasClass('dp_visible') && e.which == 27) plugin.hide(); }); } // last thing is to pre-render some of the date picker right away manage_views(); }; /** * Clears the selected date. * * @return void */ plugin.clear_date = function() { $(cleardate).trigger('click'); }; /** * Destroys the date picker. * * @return void */ plugin.destroy = function() { // remove the attached icon (if it exists)... if (undefined !== plugin.icon) plugin.icon.remove(); // ...and the calendar plugin.datepicker.remove(); // if calendar icon was shown and the date picker was not always visible, // also remove the wrapper needed for positioning it if (plugin.settings.show_icon && !plugin.settings.always_visible) $element.unwrap(); // remove associated event handlers from the element $element.unbind('click.Zebra_DatePicker_' + uniqueid); $element.unbind('blur.Zebra_DatePicker_' + uniqueid); // remove associated event handlers from the document $(document).unbind('keyup.Zebra_DatePicker_' + uniqueid); $(document).unbind('mousedown.Zebra_DatePicker_' + uniqueid); $(window).unbind('resize.Zebra_DatePicker_' + uniqueid); $(window).unbind('orientationchange.Zebra_DatePicker_' + uniqueid); // remove association with the element $element.removeData('Zebra_DatePicker'); // restore element's modified attributes $element.attr('readonly', original_attributes['readonly'] ? true : false); $element.attr('style', original_attributes['style'] ? original_attributes['style'] : ''); }; /** * Hides the date picker. * * @return void */ plugin.hide = function() { // if date picker is not always visible if (!plugin.settings.always_visible) { // hide the iFrameShim in Internet Explorer 6 iframeShim('hide'); // hide the date picker datepicker.removeClass('dp_visible').addClass('dp_hidden'); // if a callback function exists for when hiding the date picker if (plugin.settings.onClose && typeof plugin.settings.onClose == 'function') // execute the callback function and pass as argument the element the plugin is attached to plugin.settings.onClose.call($element, $element); } }; /** * Set the date picker's value * * Must be in the format set by the "format" property! * * @return void */ plugin.set_date = function(date) { var dateObj; // if a valid date was entered, and date is not disabled if ((dateObj = check_date(date)) && !is_disabled(dateObj.getFullYear(), dateObj.getMonth(), dateObj.getDate())) { // set the element's value $element.val(date); // update the paired date picker (if any) update_dependent(dateObj); } }; /** * Shows the date picker. * * @return void */ plugin.show = function() { // always show the view defined in settings view = plugin.settings.view; // get the default date, from the element, and check if it represents a valid date, according to the required format var default_date = check_date($element.val() || (plugin.settings.start_date ? plugin.settings.start_date : '')); // if the value represents a valid date if (default_date) { // extract the date parts // we'll use these to highlight the default date in the date picker and as starting point to // what year and month to start the date picker with // why separate values? because selected_* will change as user navigates within the date picker default_month = default_date.getMonth(); selected_month = default_date.getMonth(); default_year = default_date.getFullYear(); selected_year = default_date.getFullYear(); default_day = default_date.getDate(); // if the default date represents a disabled date if (is_disabled(default_year, default_month, default_day)) { // if date picker is in "strict" mode, clear the value of the parent element if (plugin.settings.strict) $element.val(''); // the calendar will start with the first selectable year/month selected_month = first_selectable_month; selected_year = first_selectable_year; } // if a default value is not available, or value does not represent a valid date } else { // the calendar will start with the first selectable year/month selected_month = first_selectable_month; selected_year = first_selectable_year; } // generate the appropriate view manage_views(); // if date picker is not always visible and the calendar icon is visible if (!plugin.settings.always_visible) { // if date picker is to be injected into the if (plugin.settings.container.is('body')) { var // get the date picker width and height datepicker_width = datepicker.outerWidth(), datepicker_height = datepicker.outerHeight(), // compute the date picker's default left and top // this will be computed relative to the icon's top-right corner (if the calendar icon exists), or // relative to the element's top-right corner otherwise, to which the offsets given at initialization // are added/subtracted left = (undefined !== icon ? icon.offset().left + icon.outerWidth(true) : $element.offset().left + $element.outerWidth(true)) + plugin.settings.offset[0], top = (undefined !== icon ? icon.offset().top : $element.offset().top) - datepicker_height + plugin.settings.offset[1], // get browser window's width and height window_width = $(window).width(), window_height = $(window).height(), // get browser window's horizontal and vertical scroll offsets window_scroll_top = $(window).scrollTop(), window_scroll_left = $(window).scrollLeft(); if (plugin.settings.default_position == 'below') top = (undefined !== icon ? icon.offset().top : $element.offset().top) + plugin.settings.offset[1]; // if date picker is outside the viewport, adjust its position so that it is visible if (left + datepicker_width > window_scroll_left + window_width) left = window_scroll_left + window_width - datepicker_width; if (left < window_scroll_left) left = window_scroll_left; if (top + datepicker_height > window_scroll_top + window_height) top = window_scroll_top + window_height - datepicker_height; if (top < window_scroll_top) top = window_scroll_top; // make the date picker visible datepicker.css({ 'left': left, 'top': top }); // if date picker is to be injected into a custom container element } else datepicker.css({ 'left': 0, 'top': 0 }); // fade-in the date picker // for Internet Explorer < 9 show the date picker instantly or fading alters the font's weight datepicker.removeClass('dp_hidden').addClass('dp_visible'); // show the iFrameShim in Internet Explorer 6 iframeShim(); // if date picker is always visible, show it } else datepicker.removeClass('dp_hidden').addClass('dp_visible'); // if a callback function exists for when showing the date picker if (plugin.settings.onOpen && typeof plugin.settings.onOpen == 'function') // execute the callback function and pass as argument the element the plugin is attached to plugin.settings.onOpen.call($element, $element); }; /** * Updates the configuration options given as argument * * @param object values An object containing any number of configuration options to be updated * * @return void */ plugin.update = function(values) { // if original direction not saved, save it now if (plugin.original_direction) plugin.original_direction = plugin.direction; // update configuration options plugin.settings = $.extend(plugin.settings, values); // reinitialize the object with the new options init(true); }; /** * Checks if a string represents a valid date according to the format defined by the "format" property. * * @param string str_date A string representing a date, formatted accordingly to the "format" property. * For example, if "format" is "Y-m-d" the string should look like "2011-06-01" * * @return mixed Returns a JavaScript Date object if string represents a valid date according * formatted according to the "format" property, or FALSE otherwise. * * @access private */ var check_date = function(str_date) { // treat argument as a string str_date += ''; // if value is given if ($.trim(str_date) !== '') { var // prepare the format by removing white space from it // and also escape characters that could have special meaning in a regular expression format = escape_regexp(plugin.settings.format), // allowed characters in date's format format_chars = ['d','D','j','l','N','S','w','F','m','M','n','Y','y'], // "matches" will contain the characters defining the date's format matches = [], // "regexp" will contain the regular expression built for each of the characters used in the date's format regexp = [], // "position" will contain the position of the caracter found in the date's format position = null, // "segments" will contain the matches of the regular expression segments = null; // iterate through the allowed characters in date's format for (var i = 0; i < format_chars.length; i++) // if character is found in the date's format if ((position = format.indexOf(format_chars[i])) > -1) // save it, alongside the character's position matches.push({character: format_chars[i], position: position}); // sort characters defining the date's format based on their position, ascending matches.sort(function(a, b){ return a.position - b.position; }); // iterate through the characters defining the date's format $.each(matches, function(index, match) { // add to the array of regular expressions, based on the character switch (match.character) { case 'd': regexp.push('0[1-9]|[12][0-9]|3[01]'); break; case 'D': regexp.push('[a-z]{3}'); break; case 'j': regexp.push('[1-9]|[12][0-9]|3[01]'); break; case 'l': regexp.push('[a-z]+'); break; case 'N': regexp.push('[1-7]'); break; case 'S': regexp.push('st|nd|rd|th'); break; case 'w': regexp.push('[0-6]'); break; case 'F': regexp.push('[a-z]+'); break; case 'm': regexp.push('0[1-9]|1[012]+'); break; case 'M': regexp.push('[a-z]{3}'); break; case 'n': regexp.push('[1-9]|1[012]'); break; case 'Y': regexp.push('[0-9]{4}'); break; case 'y': regexp.push('[0-9]{2}'); break; } }); // if we have an array of regular expressions if (regexp.length) { // we will replace characters in the date's format in reversed order matches.reverse(); // iterate through the characters in date's format $.each(matches, function(index, match) { // replace each character with the appropriate regular expression format = format.replace(match.character, '(' + regexp[regexp.length - index - 1] + ')'); }); // the final regular expression regexp = new RegExp('^' + format + '$', 'ig'); // if regular expression was matched if ((segments = regexp.exec(str_date))) { // check if date is a valid date (i.e. there's no February 31) var tmpdate = new Date(), original_day = 1, original_month = tmpdate.getMonth() + 1, original_year = tmpdate.getFullYear(), english_days = ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'], english_months = ['January','February','March','April','May','June','July','August','September','October','November','December'], iterable, // by default, we assume the date is valid valid = true; // reverse back the characters in the date's format matches.reverse(); // iterate through the characters in the date's format $.each(matches, function(index, match) { // if the date is not valid, don't look further if (!valid) return true; // based on the character switch (match.character) { case 'm': case 'n': // extract the month from the value entered by the user original_month = to_int(segments[index + 1]); break; case 'd': case 'j': // extract the day from the value entered by the user original_day = to_int(segments[index + 1]); break; case 'D': case 'l': case 'F': case 'M': // if day is given as day name, we'll check against the names in the used language if (match.character == 'D' || match.character == 'l') iterable = plugin.settings.days; // if month is given as month name, we'll check against the names in the used language else iterable = plugin.settings.months; // by default, we assume the day or month was not entered correctly valid = false; // iterate through the month/days in the used language $.each(iterable, function(key, value) { // if month/day was entered correctly, don't look further if (valid) return true; // if month/day was entered correctly if (segments[index + 1].toLowerCase() == value.substring(0, (match.character == 'D' || match.character == 'M' ? 3 : value.length)).toLowerCase()) { // extract the day/month from the value entered by the user switch (match.character) { case 'D': segments[index + 1] = english_days[key].substring(0, 3); break; case 'l': segments[index + 1] = english_days[key]; break; case 'F': segments[index + 1] = english_months[key]; original_month = key + 1; break; case 'M': segments[index + 1] = english_months[key].substring(0, 3); original_month = key + 1; break; } // day/month value is valid valid = true; } }); break; case 'Y': // extract the year from the value entered by the user original_year = to_int(segments[index + 1]); break; case 'y': // extract the year from the value entered by the user original_year = '19' + to_int(segments[index + 1]); break; } }); // if everything is ok so far if (valid) { // generate a Date object using the values entered by the user // (handle also the case when original_month and/or original_day are undefined - i.e date format is "Y-m" or "Y") var date = new Date(original_year, (original_month || 1) - 1, original_day || 1); // if, after that, the date is the same as the date entered by the user if (date.getFullYear() == original_year && date.getDate() == (original_day || 1) && date.getMonth() == ((original_month || 1) - 1)) // return the date as JavaScript date object return date; } } } // if script gets this far, return false as something must've went wrong return false; } }; /** * Prevents the possibility of selecting text on a given element. Used on the "previous" and "next" buttons * where text might get accidentally selected when user quickly clicks on the buttons. * * Code by http://chris-barr.com/index.php/entry/disable_text_selection_with_jquery/ * * @param jQuery Element el A jQuery element on which to prevents text selection. * * @return void * * @access private */ var disable_text_select = function(el) { // if browser is Firefox if (browser.name == 'firefox') el.css('MozUserSelect', 'none'); // if browser is Internet Explorer else if (browser.name == 'explorer') el.bind('selectstart', function() { return false; }); // for the other browsers else el.mousedown(function() { return false; }); }; /** * Escapes special characters in a string, preparing it for use in a regular expression. * * @param string str The string in which special characters should be escaped. * * @return string Returns the string with escaped special characters. * * @access private */ var escape_regexp = function(str) { // return string with special characters escaped return str.replace(/([-.,*+?^${}()|[\]\/\\])/g, '\\$1'); }; /** * Formats a JavaScript date object to the format specified by the "format" property. * Code taken from http://electricprism.com/aeron/calendar/ * * @param date date A valid JavaScript date object * * @return string Returns a string containing the formatted date * * @access private */ var format = function(date) { var result = '', // extract parts of the date: // day number, 1 - 31 j = date.getDate(), // day of the week, 0 - 6, Sunday - Saturday w = date.getDay(), // the name of the day of the week Sunday - Saturday l = plugin.settings.days[w], // the month number, 1 - 12 n = date.getMonth() + 1, // the month name, January - December f = plugin.settings.months[n - 1], // the year (as a string) y = date.getFullYear() + ''; // iterate through the characters in the format for (var i = 0; i < plugin.settings.format.length; i++) { // extract the current character var chr = plugin.settings.format.charAt(i); // see what character it is switch(chr) { // year as two digits case 'y': y = y.substr(2); // year as four digits case 'Y': result += y; break; // month number, prefixed with 0 case 'm': n = str_pad(n, 2); // month number, not prefixed with 0 case 'n': result += n; break; // month name, three letters case 'M': f = ($.isArray(plugin.settings.months_abbr) && undefined !== plugin.settings.months_abbr[n - 1] ? plugin.settings.months_abbr[n - 1] : plugin.settings.months[n - 1].substr(0, 3)); // full month name case 'F': result += f; break; // day number, prefixed with 0 case 'd': j = str_pad(j, 2); // day number not prefixed with 0 case 'j': result += j; break; // day name, three letters case 'D': l = ($.isArray(plugin.settings.days_abbr) && undefined !== plugin.settings.days_abbr[w] ? plugin.settings.days_abbr[w] : plugin.settings.days[w].substr(0, 3)); // full day name case 'l': result += l; break; // ISO-8601 numeric representation of the day of the week, 1 - 7 case 'N': w++; // day of the week, 0 - 6 case 'w': result += w; break; // English ordinal suffix for the day of the month, 2 characters // (st, nd, rd or th (works well with j)) case 'S': if (j % 10 == 1 && j != '11') result += 'st'; else if (j % 10 == 2 && j != '12') result += 'nd'; else if (j % 10 == 3 && j != '13') result += 'rd'; else result += 'th'; break; // this is probably the separator default: result += chr; } } // return formated date return result; }; /** * Generates the day picker view, and displays it * * @return void * * @access private */ var generate_daypicker = function() { var // get the number of days in the selected month days_in_month = new Date(selected_year, selected_month + 1, 0).getDate(), // get the selected month's starting day (from 0 to 6) first_day = new Date(selected_year, selected_month, 1).getDay(), // how many days are there in the previous month days_in_previous_month = new Date(selected_year, selected_month, 0).getDate(), // how many days are there to be shown from the previous month days_from_previous_month = first_day - plugin.settings.first_day_of_week; // the final value of how many days are there to be shown from the previous month days_from_previous_month = days_from_previous_month < 0 ? 7 + days_from_previous_month : days_from_previous_month; // manage header caption and enable/disable navigation buttons if necessary manage_header(plugin.settings.header_captions['days']); // start generating the HTML var html = ''; // if a column featuring the number of the week is to be shown if (plugin.settings.show_week_number) // column title html += '' + plugin.settings.show_week_number + ''; // name of week days // show the abbreviated day names (or only the first two letters of the full name if no abbreviations are specified) // and also, take in account the value of the "first_day_of_week" property for (var i = 0; i < 7; i++) html += '' + ($.isArray(plugin.settings.days_abbr) && undefined !== plugin.settings.days_abbr[(plugin.settings.first_day_of_week + i) % 7] ? plugin.settings.days_abbr[(plugin.settings.first_day_of_week + i) % 7] : plugin.settings.days[(plugin.settings.first_day_of_week + i) % 7].substr(0, 2)) + ''; html += ''; // the calendar shows a total of 42 days for (i = 0; i < 42; i++) { // seven days per row if (i > 0 && i % 7 === 0) html += ''; // if week number is to be shown if (i % 7 === 0 && plugin.settings.show_week_number) // show ISO 8601 week number html += '' + getWeekNumber(new Date(selected_year, selected_month, (i - days_from_previous_month + 1))) + ''; // the number of the day in month var day = (i - days_from_previous_month + 1); // if dates in previous/next month can be selected, and this is one of those days if (plugin.settings.select_other_months && (i < days_from_previous_month || day > days_in_month)) { // use the Date object to normalize the date // for example, 2011 05 33 will be transformed to 2011 06 02 var real_date = new Date(selected_year, selected_month, day), real_year = real_date.getFullYear(), real_month = real_date.getMonth(), real_day = real_date.getDate(); // extract normalized date parts and merge them real_date = real_year + str_pad(real_month + 1, 2) + str_pad(real_day, 2); } // if this is a day from the previous month if (i < days_from_previous_month) html += '' + (plugin.settings.select_other_months || plugin.settings.show_other_months ? str_pad(days_in_previous_month - days_from_previous_month + i + 1, plugin.settings.zero_pad ? 2 : 0) : ' ') + ''; // if this is a day from the next month else if (day > days_in_month) html += '' + (plugin.settings.select_other_months || plugin.settings.show_other_months ? str_pad(day - days_in_month, plugin.settings.zero_pad ? 2 : 0) : ' ') + ''; // if this is a day from the current month else { var // get the week day (0 to 6, Sunday to Saturday) weekday = (plugin.settings.first_day_of_week + i) % 7, class_name = '', // custom class, if any custom_class_name = get_custom_class(selected_year, selected_month, day); // if date needs to be disabled if (is_disabled(selected_year, selected_month, day)) { // if day is in weekend if ($.inArray(weekday, plugin.settings.weekend_days) > -1) class_name = 'dp_weekend_disabled'; // if work day else class_name += ' dp_disabled'; // highlight the current system date if (selected_month == current_system_month && selected_year == current_system_year && current_system_day == day) class_name += ' dp_disabled_current'; // apply custom class, with the "_disabled" suffix, if a custom class exists if (custom_class_name != '') class_name += ' ' + custom_class_name + '_disabled'; // if there are no restrictions } else { // if day is in weekend if ($.inArray(weekday, plugin.settings.weekend_days) > -1) class_name = 'dp_weekend'; // highlight the currently selected date if (selected_month == default_month && selected_year == default_year && default_day == day) class_name += ' dp_selected'; // highlight the current system date if (selected_month == current_system_month && selected_year == current_system_year && current_system_day == day) class_name += ' dp_current'; // apply custom class, if a custom class exists if (custom_class_name != '') class_name += ' ' + custom_class_name; } // print the day of the month (if "day" is NaN, use an empty string instead) html += '' + ((plugin.settings.zero_pad ? str_pad(day, 2) : day) || ' ') + ''; } } // wrap up generating the day picker html += ''; // inject the day picker into the DOM daypicker.html($(html)); // if date picker is always visible if (plugin.settings.always_visible) // cache all the cells // (we need them so that we can easily remove the "dp_selected" class from all of them when user selects a date) daypicker_cells = $('td:not(.dp_disabled, .dp_weekend_disabled, .dp_not_in_month, .dp_week_number)', daypicker); // make the day picker visible daypicker.show(); }; /** * Generates the month picker view, and displays it * * @return void * * @access private */ var generate_monthpicker = function() { // manage header caption and enable/disable navigation buttons if necessary manage_header(plugin.settings.header_captions['months']); // start generating the HTML var html = ''; // iterate through all the months for (var i = 0; i < 12; i++) { // three month per row if (i > 0 && i % 3 === 0) html += ''; var class_name = 'dp_month_' + i; // if month needs to be disabled if (is_disabled(selected_year, i)) class_name += ' dp_disabled'; // else, if a date is already selected and this is that particular month, highlight it else if (default_month !== false && default_month == i && selected_year == default_year) class_name += ' dp_selected'; // else, if this the current system month, highlight it else if (current_system_month == i && current_system_year == selected_year) class_name += ' dp_current'; // first three letters of the month's name html += '' + ($.isArray(plugin.settings.months_abbr) && undefined !== plugin.settings.months_abbr[i] ? plugin.settings.months_abbr[i] : plugin.settings.months[i].substr(0, 3)) + ''; } // wrap up html += ''; // inject into the DOM monthpicker.html($(html)); // if date picker is always visible if (plugin.settings.always_visible) // cache all the cells // (we need them so that we can easily remove the "dp_selected" class from all of them when user selects a month) monthpicker_cells = $('td:not(.dp_disabled)', monthpicker); // make the month picker visible monthpicker.show(); }; /** * Generates the year picker view, and displays it * * @return void * * @access private */ var generate_yearpicker = function() { // manage header caption and enable/disable navigation buttons if necessary manage_header(plugin.settings.header_captions['years']); // start generating the HTML var html = ''; // we're showing 9 years at a time, current year in the middle for (var i = 0; i < 12; i++) { // three years per row if (i > 0 && i % 3 === 0) html += ''; var class_name = ''; // if year needs to be disabled if (is_disabled(selected_year - 7 + i)) class_name += ' dp_disabled'; // else, if a date is already selected and this is that particular year, highlight it else if (default_year && default_year == selected_year - 7 + i) class_name += ' dp_selected'; // else, if this is the current system year, highlight it else if (current_system_year == (selected_year - 7 + i)) class_name += ' dp_current'; // first three letters of the month's name html += '' + (selected_year - 7 + i) + ''; } // wrap up html += ''; // inject into the DOM yearpicker.html($(html)); // if date picker is always visible if (plugin.settings.always_visible) // cache all the cells // (we need them so that we can easily remove the "dp_selected" class from all of them when user selects a year) yearpicker_cells = $('td:not(.dp_disabled)', yearpicker); // make the year picker visible yearpicker.show(); }; /** * Return the name of a custom class to be applied to the given date. * * @return string The name of a custom class to be applied to the given date, or an empty string if no custom * class needs to be applied. * * @param integer year The year to check * @param integer month The month to check * @param integer day The day to check * * @access private */ var get_custom_class = function(year, month, day) { var class_name, i, found; // if month is given as argument, increment it (as JavaScript uses 0 for January, 1 for February...) if (typeof month != 'undefined') month = month + 1; // iterate through the custom classes for (i in custom_class_names) { // the class name we're currently checking class_name = custom_class_names[i]; found = false; // if there are any custom classes defined if ($.isArray(custom_classes[class_name])) // iterate through the rules for which the custom class to be applied $.each(custom_classes[class_name], function() { // if a custom class needs to be applied to the date we're checking, don't look further if (found) return; var rule = this; // if the rules apply for the current year if ($.inArray(year, rule[2]) > -1 || $.inArray('*', rule[2]) > -1) // if the rules apply for the current month if ((typeof month != 'undefined' && $.inArray(month, rule[1]) > -1) || $.inArray('*', rule[1]) > -1) // if the rules apply for the current day if ((typeof day != 'undefined' && $.inArray(day, rule[0]) > -1) || $.inArray('*', rule[0]) > -1) { // if custom class is to be applied whatever the day // don't look any further if (rule[3] == '*') return (found = class_name); // get the weekday var weekday = new Date(year, month - 1, day).getDay(); // if custom class is to be applied to weekday // don't look any further if ($.inArray(weekday, rule[3]) > -1) return (found = class_name); } }); // if a custom class needs to be applied to the date we're checking, don't look further if (found) return found; } // return what we've found return found || ''; }; /** * Generates an iFrame shim in Internet Explorer 6 so that the date picker appears above select boxes. * * @return void * * @access private */ var iframeShim = function(action) { // this is necessary only if browser is Internet Explorer 6 if (browser.name == 'explorer' && browser.version == 6) { // if the iFrame was not yet created // "undefined" evaluates as FALSE if (!shim) { // the iFrame has to have the element's zIndex minus 1 var zIndex = to_int(datepicker.css('zIndex')) - 1; // create the iFrame shim = $('