/* Copyright (c) 2011, Geert Bergman (geert@scrivo.nl)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. Neither the name of "Scrivo" nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* $Id: Date.js 616 2013-04-22 23:48:38Z geert $
*/
"use strict";
SUI.control.Date = SUI.defineClass(
/** @lends SUI.control.Date.prototype */{
/** @ignore */ baseClass: SUI.Box,
/**
* @class
* SUI.control.Date is a control for date entry. It displays date and or
* time entry fields and a button for a date/time selection box. The
* control works in a JavaScript setting, but also as a form field. The
* order in which the date fields are presented depends on the
* internationalization settings.
*
* @augments SUI.Box
*
* @description
* Create a date(time) control: a set of input fields in the order of
* date(time) format, together with a button for a date(time) dialog
* box to facilitate date entry.
*
* @constructs
* @param see base class
* @param {Date} arg.value Initial date(time) to show in the control.
* @param {String} arg.type "date" (default), "time" or "datetime"
*/
initializer: function(arg) {
SUI.control.Date.initializeBase(this, arg);
// set the control's type, default to date
this._type = arg.type || "date";
// initialize the fields array
this._fields = [];
// for the button set overflow to visible
this.el().style.overflow = "visible";
// get the internationalized date format ...
var df = SUI.i18n.dateFormat;
if (this._type == "datetime") {
df = SUI.i18n.dateFormat + " " + SUI.i18n.timeFormat;
} else if (this._type == "time") {
df = SUI.i18n.timeFormat;
}
// create a hidden control that will contains a single string
// representation of the date in the input fields of the control.
// Note: can't add to doc tree and set type afterwards in IE
this._hidden = new SUI.form.Input({/*parent: this*/});
this._hidden.el().type = "hidden";
this._hidden.el().name = arg.name || this.el().id;
this._hidden.parent(this);
// ... and use that set the control's fields in that order
var l = this._addInputFields(df);
// create a button that opens the date dialog box
var that = this;
this._button = new SUI.ToolbarButton({
parent: this,
left: l + this.BUTTON_MARGIN,
width: this.BUTTON_SIZE,
height: this.BUTTON_SIZE,
top: (this.HEIGHT-this.BUTTON_SIZE)/2,
title: "", // TODO
icon: this._type == "time"
? SUI.resource.calTime : SUI.resource.calDate,
handler: function() {
new SUI.dialog.Calendar({
type: that._type,
date: that._toDate(),
onOK: function(date) {
that._setDate(date);
}
}).show();
}
});
// also add the button and the hidden field so that they will be
// handled by layOut and display
this._fields.push(this._hidden);
this._fields.push(this._button);
// set the width and height of the control
this.width(this._button.left() + this._button.width());
this.height(this.HEIGHT);
// if a value as given in the arguments ...
if (arg.value) {
// ... put that value into the control
this._setValue(arg.value);
}
},
/**
* Margin between the button and the input fields.
*/
BUTTON_MARGIN: 7,
/**
* Size (width/height) of the button
*/
BUTTON_SIZE: 22,
/**
* Height of the control (not that the button is larger)
*/
HEIGHT: 20,
/**
* Width of the 2 digit input fields (day, month, minutes, hours)
*/
WIDTH_2: 20,
/**
* Width of the 4 digit input field (year)
*/
WIDTH_4: 36,
/**
* Width between the input fields
*/
WIDTH_SEP: 10,
/**
* Display the date control. Set the CSS size and position of the input
* fields and button.
*/
display: function() {
this.setDim();
for (var i=0; i<this._fields.length; i++) {
this._fields[i].display();
}
},
/**
* Return the first input field of the control. This is needed to set the
* forBox (for) field of a label field.
* @return {SUI.Box} the first input box of the date control.
*/
firstBox: function() {
return this._fields[0];
},
/**
* Lay out the date control. Calculate the sizes and positions of the
* input fields and button.
*/
layOut: function() {
for (var i=0; i<this._fields.length; i++) {
this._fields[i].layOut();
}
},
/**
* Get or set the value of the date control.
* @param {string|Date} v Either a JavaScript Date object or string in SQL
* Date format to which the control's date will be set. Or the string
* "date" or "string" to select the return type of the function. If no
* parameter is given, the method returns an object. containing the
* fields: date, dateStr and error.
* @returns {Object|string|Date} Depending on the given arguments the
* return value can be an object containing the fields: date, dateStr
* and error (no arguments), a Date object (argument: "date") or
* a date string in SQL format (argument: "string"). If the method is
* used as a setter the method returns null.
*/
value: function(v) {
// act as a getter
if (v === undefined) {
return this._getValue();
} else if (v === "string") {
return this._getValue().strDate;
} else if (v === "date") {
return this._getValue().date;
}
// got here? than act as a setter
this._setValue(v);
return null;
},
// the button that opens the date selection dialog
_button: null,
// the day input field
_day: null,
// list of all the child boxes of this control
_fields: null,
// hidden input field that holds a SQL date string of value set in control
_hidden: null,
// the hours input field
_hour: null,
// the minutes input field
_min: null,
// the month input field
_month: null,
// the control's type: date time or datetime
_type: "",
// the year input field
_year: null,
// add the date input fields including the characters in between them to
// the _fields array, and add event listeners to the input fields
_addInputFields: function(df) {
// current left position of the field/text
var l = 0;
// loop through the characters of the format string
for (var i=0; i<df.length; i++) {
// add a input field or text for each character in format string
switch(df.charAt(i)) {
case "d":
// add a 2 character field for the day
this.day = new SUI.form.Input(
{parent: this, left: l, width: this.WIDTH_2 });
this.day.el().maxLength = 2;
this._fields.push(this.day);
l += this.WIDTH_2;
break;
case "m":
// add a 2 character field for the month
this.month = new SUI.form.Input(
{parent: this, left: l, width: this.WIDTH_2 });
this.month.el().maxLength = 2;
this._fields.push(this.month);
l += this.WIDTH_2;
break;
case "y":
// add a 4 character field for the year
this.year = new SUI.form.Input(
{parent: this, left: l, width: this.WIDTH_4 });
this.year.el().maxLength = 4;
this._fields.push(this.year);
l += this.WIDTH_4;
break;
case "h":
// add a 2 character field for the hours
this.hour = new SUI.form.Input(
{parent: this, left: l, width: this.WIDTH_2 });
this.hour.el().maxLength = 2;
this._fields.push(this.hour);
l += this.WIDTH_2;
break;
case "i":
// add a 2 character field for the minutes
this.min = new SUI.form.Input(
{parent: this, left: l, width: this.WIDTH_2 });
this.min.el().maxLength = 2;
this._fields.push(this.min);
l += this.WIDTH_2;
break;
default:
// add a 1 character field for the (separator) character
var tmp = new SUI.TextBox({parent: this, text: df.charAt(i),
left: l, width: this.WIDTH_SEP, height: this.HEIGHT });
tmp.el().style.textAlign = "center";
this._fields.push(tmp);
l += this.WIDTH_SEP;
break;
}
}
// set the hidden field on each "oninput" event on any of the input
// fields
var that = this;
for (var i=0; i<this._fields.length; i++) {
if (this._fields[i] instanceof SUI.form.Input) {
SUI.browser.addEventListener(this._fields[i].el(), "input",
function(e) {
if (!that._setHidden()) {
SUI.browser.noPropagation(e);
}
}
);
}
}
// return the total length of the controls so far
return l;
},
// return the value of the control in different formats: as Date object,
// SQL string and type "error/empty/date/time/datetime"
_getValue: function(e) {
// start with the default return value
var res = {date: null, strDate: "error", type: "error"};
// is the control empty ...
if (this._isEmpty()) {
// ... then set return values to empty ...
res.type = "empty";
res.strDate = "";
} else {
// ... else try to construct a date value from the input fields ...
var dt = this._toDate();
if (dt) {
// (ascertain that the hidden field is set)
this._setHidden();
// ... if successful return these values
res = {date: dt, strDate: this._hidden.el().value,
type: this._type};
}
}
// return the result
return res;
},
// check if there are values entered in any of the date fields
_isEmpty: function() {
// value to start with
var res = "";
// if we have date input fields ...
if (this.year) {
// ... add these values to res
res += this.year.el().value + this.month.el().value +
this.day.el().value;
}
// if we have time input fields ...
if (this.hour) {
// ... add these values to res
res += this.hour.el().value + this.min.el().value;
}
// does a SUI.trim results in an empty string?
return SUI.trim(res) === "";
},
// set the input field to the values given by the JavaScript Date object
_setDate: function(date) {
// if we have date input fields ...
if (this.year) {
if (date) {
// ... set date fields with zero padded values ...
this.year.el().value = SUI.date.padZero(date.getFullYear(), 4);
this.day.el().value = SUI.date.padZero(date.getDate());
this.month.el().value = SUI.date.padZero(date.getMonth()+1);
} else {
// ... else clear the values
this.year.el().value = this.day.el().value =
this.month.el().value = "";
}
}
// if we have time input fields ...
if (this.hour) {
if (date) {
// ... set time fields with zero padded values ...
this.hour.el().value = SUI.date.padZero(date.getHours());
this.min.el().value = SUI.date.padZero(date.getMinutes());
} else {
// ... else clear the values
if (this.hour) {
this.hour.el().value = this.min.el().value = "";
}
}
}
// set the hidden control field
this._setHidden();
},
// set the hidden field to the current date selection in SQL date format
// ("YY-MM-DD HH:II:SS"). "error" and "" are also possible values.
_setHidden: function() {
// expect the worst
var val = "error";
// do none of the fields contain any values ...
if (this._isEmpty()) {
// ... return an empty string ...
val = "";
} else {
// ... get try the control's date selection ...
var d = this._toDate();
if (d) {
// ... if successful set the SQL date string
val = SUI.date.toSqlDate(d, this.hour);
}
}
// set the hidden input field of the control
this._hidden.el().value = val;
},
// set the value of the date control using an SQL date string or a
// JavaScript Date object
_setValue: function(val) {
// if the argument was no Date object ...
if (!(val instanceof Date)) {
// ... was it null or empty? ...
if (val === null || val === "") {
// ... yes, then the value null will clear the fields ...
val = null;
} else {
// ... no, try to convert it to a date
val = SUI.date.parseSqlDate(val);
if (!val) {
throw "SUI.control.Date: "
+ "_setValue called with invalid date";
}
}
}
// set the date into the control fields
this._setDate(val);
},
// get the values of the input fields and construct a JavaScript Date
// object.
_toDate: function() {
// date object to return
var dt = new Date();
// is the date value is valid?
var valid = true;
// if we have date input fields ...
if (this.year) {
// ... get the values ...
var y = parseInt(this.year.el().value, 10);
var m = parseInt(this.month.el().value, 10);
var d = parseInt(this.day.el().value, 10);
// ... set the values in the date object
dt.setFullYear(y, m-1, d);
// the JavaScript Date object corrects erroneous entry: 31th of
// June becomes the 1th of July, so compare entered values with
// the ones set in the date object.
valid = valid && dt.getFullYear() === y &&
(dt.getMonth() + 1) === m && dt.getDate() === d;
}
// if we have time input fields ...
if (this.hour) {
// ... get the values ...
var h = parseInt(this.hour.el().value, 10);
if (isNaN(h)) {
h = 0;
}
var i = parseInt(this.min.el().value, 10);
if (isNaN(i)) {
i = 0;
}
// ... set the values in the date object
dt.setHours(h);
dt.setMinutes(i);
// the JavaScript Date object corrects erroneous entry: 14:65
// becomes 15:05, so compare entered values with the
// ones set in the date object.
valid = valid && dt.getHours() === h && dt.getMinutes() === i;
}
// return the result
return valid ? dt : null;
}
});