fleetvu/vexpense/assets/js/jquery.date-dropdowns.js
2017-06-22 16:11:47 +05:30

617 lines
20 KiB
JavaScript

;(function ($, window, document, undefined) {
"use strict";
// Create the defaults once
var pluginName = "dateDropdowns",
pluginDefaults = {
defaultDate: null,
defaultDateFormat: "yyyy-mm-dd",
displayFormat: "dmy",
submitFormat: "yyyy-mm-dd",
minAge: 0,
maxAge: 120,
minYear: null,
maxYear: null,
submitFieldName: "date",
wrapperClass: "date-dropdowns",
dropdownClass: null,
daySuffixes: true,
monthSuffixes: true,
monthFormat: "long",
required: false
};
// The actual plugin constructor
function Plugin (element, options) {
this.element = element; // Element handle
this.$element = $(element); // jQuery element handle
this.config = $.extend({}, pluginDefaults, options); // Plugin options
this.internals = { // Internal variables
objectRefs: {}
};
this._defaults = pluginDefaults; // Reference to the plugin defaults
this._name = pluginName; // Reference to the plugin name
this.init();
return this;
}
Plugin.message = {
day: "Day",
month: "Month",
year: "Year"
};
// Avoid Plugin.prototype conflicts
$.extend(Plugin.prototype, {
/**
* Initialise the plugin
*/
init: function () {
this.checkForDuplicateElement();
this.setInternalVariables();
this.setupMarkup();
this.buildDropdowns();
this.attachDropdowns();
this.bindChangeEvent();
if (this.config.defaultDate) {
this.populateDefaultDate();
}
},
/**
* Check whether an element exists with the same name attribute. If so, throw an error
*/
checkForDuplicateElement: function() {
if ($("input[name=\"" + this.config.submitFieldName + "\"]").length) {
$.error("Duplicate element found");
return false;
}
return true;
},
/**
* Set the plugin"s internal variables
*/
setInternalVariables: function() {
var date = new Date();
this.internals.currentDay = date.getDate();
this.internals.currentMonth = date.getMonth() + 1;
this.internals.currentYear = date.getFullYear();
this.internals.monthShort = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
this.internals.monthLong = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
},
/**
* Set the container which will hold the date dropdowns.
*
* - If the element on which the plugin was called is an input of type text or hidden, set it to
* be hidden and wrap it in a div. This outer div will become the container.
* - If the element was an HTML element (e.g. <div/>), create a hidden text field within it, and use
* the div as the container.
*/
setupMarkup: function() {
var wrapper, hiddenField;
if (this.element.tagName.toLowerCase() === "input") {
if (!this.config.defaultDate) {
this.config.defaultDate = this.element.value;
}
// Configure the input element and wrap
hiddenField = this.$element.attr("type", "hidden")
.attr("name", this.config.submitFieldName)
.wrap("<div class=\"" + this.config.wrapperClass + "\"></div>");
wrapper = this.$element.parent();
} else {
// Build a hidden input and set this.$element as the wrapper
hiddenField = $("<input/>", {
type: "hidden",
name: this.config.submitFieldName
});
this.$element.append(hiddenField).addClass(this.config.wrapperClass);
wrapper = this.$element;
}
// Store a reference to the wrapper and hidden field elements for later use
this.internals.objectRefs.pluginWrapper = wrapper;
this.internals.objectRefs.hiddenField = hiddenField;
return true;
},
/**
* Generate the Day, Month and Year dropdowns
*/
buildDropdowns: function() {
var $dayDropdown, $monthDropdown, $yearDropdown;
// Build the day dropdown element
$dayDropdown = this.buildDayDropdown();
this.internals.objectRefs.dayDropdown = $dayDropdown;
$monthDropdown = this.buildMonthDropdown();
this.internals.objectRefs.monthDropdown = $monthDropdown;
$yearDropdown = this.buildYearDropdown();
this.internals.objectRefs.yearDropdown = $yearDropdown;
return true;
//this.internals.objectRefs.pluginWrapper.append($dayDropdown)
// .append($monthDropdown)
// .append($yearDropdown);
},
/**
* Attach the generated dropdowns to the container
*/
attachDropdowns: function() {
var $element = this.internals.objectRefs.pluginWrapper,
$daySelect = this.internals.objectRefs.dayDropdown,
$monthSelect = this.internals.objectRefs.monthDropdown,
$yearSelect = this.internals.objectRefs.yearDropdown;
switch (this.config.displayFormat) {
case "mdy":
$element.append($monthSelect, $daySelect, $yearSelect);
break;
case "ymd":
$element.append($yearSelect, $monthSelect, $daySelect);
break;
case "dmy":
default:
$element.append($daySelect, $monthSelect, $yearSelect);
break;
}
return true;
},
/**
* Bind the change event to the generated dropdowns
*/
bindChangeEvent: function() {
var $daySelect = this.internals.objectRefs.dayDropdown,
$monthSelect = this.internals.objectRefs.monthDropdown,
$yearSelect = this.internals.objectRefs.yearDropdown,
pluginHandle = this,
objectRefs = this.internals.objectRefs;
objectRefs.pluginWrapper.on("change", "select", function() {
var day = $daySelect.val(),
month = $monthSelect.val(),
year = $yearSelect.val(),
invalidDate = true,
newDate;
// Find out whether the change has made the date invalid (e.g. 31st Feb)
invalidDate = pluginHandle.checkDate(day, month, year);
// If invalid - add an error class to the day dropdown and return
if (invalidDate) {
objectRefs.dayDropdown.addClass("invalid");
return false;
}
if ("00" !== objectRefs.dayDropdown.val()) {
objectRefs.dayDropdown.removeClass("invalid");
}
// Empty the hidden field after each change
objectRefs.hiddenField.val("");
// Only format the submit date if a full date has been selected
if (!invalidDate && (day * month * year !== 0)) {
newDate = pluginHandle.formatSubmitDate(day, month, year);
objectRefs.hiddenField.val(newDate);
}
objectRefs.hiddenField.change();
});
},
/**
* Take a provided default date and populate both the hidden field and the
* dropdown elements with the relevant formatted values
*
* @returns {boolean}
*/
populateDefaultDate: function() {
var date = this.config.defaultDate,
parts = [],
day = "",
month = "",
year = "";
switch (this.config.defaultDateFormat) {
case "yyyy-mm-dd":
default:
parts = date.split("-");
day = parts[2];
month = parts[1];
year = parts[0];
break;
case "dd/mm/yyyy":
parts = date.split("/");
day = parts[0];
month = parts[1];
year = parts[2];
break;
case "mm/dd/yyyy":
parts = date.split("/");
day = parts[1];
month = parts[0];
year = parts[2];
break;
case "unix":
parts = new Date();
parts.setTime(date * 1000);
day = parts.getDate() + "";
month = (parts.getMonth() + 1) + "";
year = parts.getFullYear();
if (day.length < 2) {
day = "0" + day;
}
if (month.length < 2) {
month = "0" + month;
}
break;
}
// Set the values on the dropdowns
this.internals.objectRefs.dayDropdown.val(day);
this.internals.objectRefs.monthDropdown.val(month);
this.internals.objectRefs.yearDropdown.val(year);
this.internals.objectRefs.hiddenField.val(date);
if (true === this.checkDate(day, month, year)) {
this.internals.objectRefs.dayDropdown.addClass("invalid");
}
return true;
},
/**
* Build a generic dropdown element
*
* @param type
* @returns {*|HTMLElement}
*/
buildBaseDropdown: function(type) {
var classString = type;
if (this.config.dropdownClass) {
classString += " " + this.config.dropdownClass;
}
return $("<select></select>", {
class: classString,
name: this.config.submitFieldName + "_[" + type + "]",
required: this.config.required
});
},
/**
* Build the day dropdown element
*
* @returns {*|HTMLElement}
*/
buildDayDropdown: function() {
var day,
dropdown = this.buildBaseDropdown("day"),
option = document.createElement("option");
option.setAttribute("value", "");
option.appendChild(document.createTextNode(Plugin.message.day));
dropdown.append(option);
// Days 1-9
for (var i = 1; i < 10; i++) {
if (this.config.daySuffixes) {
day = i + this.getSuffix(i);
} else {
day = "0" + i;
}
option = document.createElement("option");
option.setAttribute("value", "0" + i);
option.appendChild(document.createTextNode(day));
dropdown.append(option);
}
// Days 10-31
for (var j = 10; j <= 31; j++) {
day = j;
if (this.config.daySuffixes) {
day = j + this.getSuffix(j);
}
option = document.createElement("option");
option.setAttribute("value", j);
option.appendChild(document.createTextNode(day));
dropdown.append(option);
}
return dropdown;
},
/**
* Build the month dropdown element
*
* @returns {*|HTMLElement}
*/
buildMonthDropdown: function() {
var dropdown = this.buildBaseDropdown("month"),
option = document.createElement("option");
option.setAttribute("value", "");
option.appendChild(document.createTextNode(Plugin.message.month));
dropdown.append(option);
// Populate the month values
for (var monthNo = 1; monthNo <= 12; monthNo++) {
var month;
switch (this.config.monthFormat) {
case "short":
month = this.internals.monthShort[monthNo - 1];
break;
case "long":
month = this.internals.monthLong[monthNo - 1];
break;
case "numeric":
month = monthNo;
if (this.config.monthSuffixes) {
month += this.getSuffix(monthNo);
}
break;
}
if (monthNo < 10) {
monthNo = "0" + monthNo;
}
option = document.createElement("option");
option.setAttribute("value", monthNo);
option.appendChild(document.createTextNode(month));
dropdown.append(option);
}
return dropdown;
},
/**
* Build the year dropdown element.
*
* By default minYear and maxYear are null, however if provided they take precedence over
* the minAge and maxAge values.
*
* @returns {*|HTMLElement}
*/
buildYearDropdown: function() {
var minYear = this.config.minYear,
maxYear = this.config.maxYear,
dropdown = this.buildBaseDropdown("year"),
option = document.createElement("option");
option.setAttribute("value", "");
option.appendChild(document.createTextNode(Plugin.message.year));
dropdown.append(option);
if (!minYear) {
minYear = this.internals.currentYear - (this.config.maxAge + 1);
}
if (!maxYear) {
maxYear = this.internals.currentYear - this.config.minAge;
}
for (var i = maxYear; i >= minYear; i--) {
option = document.createElement("option");
option.setAttribute("value", i);
option.appendChild(document.createTextNode(i));
dropdown.append(option);
}
return dropdown;
},
/**
* Get the relevant suffix for a day/month number
*
* @param number
* @returns {string}
*/
getSuffix: function(number) {
var suffix = "";
switch (number % 10){
case 1:
suffix = (number % 100 === 11) ? "th" : "st";
break;
case 2:
suffix = (number % 100 === 12) ? "th" : "nd";
break;
case 3:
suffix = (number % 100 === 13) ? "th" : "rd";
break;
default:
suffix = "th";
break;
}
return suffix;
},
/**
* Check whether the date entered is invalid, e.g. 31st Feb
*
* @param day
* @param month
* @param year
* @returns {boolean}
*/
checkDate: function(day, month, year) {
var invalidDate;
if (month !== "00") {
var numDaysInMonth = (new Date(year, month, 0).getDate()),
selectedDayInt = parseInt(day, 10);
invalidDate = this.updateDayOptions(numDaysInMonth, selectedDayInt);
// If the date is invalid, empty the hidden field to prevent invalid submissions
if (invalidDate) {
this.internals.objectRefs.hiddenField.val("");
}
}
return invalidDate;
},
/**
* Update the day options available based on the month now selected
*
* @param numDaysInMonth
* @param selectedDayInt
* @returns {boolean}
*/
updateDayOptions: function(numDaysInMonth, selectedDayInt) {
var lastDayOption = parseInt(this.internals.objectRefs.dayDropdown.children(":last").val(), 10),
newDayOption = "",
newDayText = "",
invalidDay = false;
// If the selected month has less days than the Day dropdown currently contains - remove the extra days
if (lastDayOption > numDaysInMonth) {
while (lastDayOption > numDaysInMonth) {
this.internals.objectRefs.dayDropdown.children(":last").remove();
lastDayOption--;
}
// If the user-selected day is removed, indicate this so a message can be shown to the user
if (selectedDayInt > numDaysInMonth) {
invalidDay = true;
}
// If the month contains more days than the Day dropdown contains - add the missing options
} else if (lastDayOption < numDaysInMonth) {
while (lastDayOption < numDaysInMonth) {
newDayOption = ++lastDayOption;
newDayText = newDayOption;
// Add the suffix if required
if (this.config.daySuffixes) {
newDayText += this.getSuffix(lastDayOption);
}
// Build the option and append to the dropdown
var option = document.createElement("option");
option.setAttribute("value", newDayOption);
option.appendChild(document.createTextNode(newDayText));
this.internals.objectRefs.dayDropdown.append(option);
}
}
return invalidDay;
},
/**
* Format the selected date based on the submitFormat config value provided
*
* @param day
* @param month
* @param year
* @returns {*}
*/
formatSubmitDate: function(day, month, year) {
var formattedDate,
_date;
switch (this.config.submitFormat) {
case "yyyy-mm-dd":
default:
formattedDate = year + "-" + month + "-" + day;
break;
case "mm/dd/yyyy":
formattedDate = month + "/" + day + "/" + year;
break;
case "dd/mm/yyyy":
formattedDate = day + "/" + month + "/" + year;
break;
case "unix":
_date = new Date();
_date.setDate(day);
_date.setMonth(month - 1);
_date.setYear(year);
formattedDate = Math.round(_date.getTime() / 1000);
}
return formattedDate;
},
/**
* Revert the changes applied by the plugin on the specified element
*/
destroy: function() {
var wrapperClass = this.config.wrapperClass;
if (this.$element.hasClass(wrapperClass)) {
this.$element.empty();
} else {
var $parent = this.$element.parent(),
$select = $parent.find("select");
this.$element.unwrap();
$select.remove();
}
}
});
// A really lightweight plugin wrapper around the constructor,
// preventing against multiple instantiations
$.fn[ pluginName ] = function ( options ) {
this.each(function() {
if(typeof options === "string") {
var args = Array.prototype.slice.call(arguments, 1);
var plugin = $.data(this, "plugin_" + pluginName);
if (typeof plugin === "undefined") {
$.error("Please initialize the plugin before calling this method.");
return false;
}
plugin[options].apply(plugin, args);
} else {
if ( !$.data( this, "plugin_" + pluginName ) ) {
$.data( this, "plugin_" + pluginName, new Plugin( this, options ) );
}
}
});
// chain jQuery functions
return this;
};
})( jQuery, window, document );