1 /*jshint node:true, indent:2, curly:true, eqeqeq:true, immed:true, latedef:true, newcap:true, noarg:true,
2 regexp:true, undef:true, trailing:true, white:true */
3 /*global XT:true, XV:true, enyo:true, _:true, Globalize:true */
9 @class An input control used to specify a date.<br />
10 Reformats and sets a date entered either as a date type or a string.
11 If a string is not a recognizable date, sets the input to null.<br />
12 The superkind of {@link XV.DateWidget}.
16 /** @lends XV.Date# */{
24 Returns the value in the input field of the widget.
26 getValueToString: function (value) {
27 return this.$.input.value;
30 Sets the value of date programatically.
32 @param {Date|String} value Can be Date or Date String
33 @param {Object} options
35 setValue: function (value, options) {
36 var nullValue = this.getNullValue();
38 if (_.isString(value)) {
39 value = this.validate(value);
41 value = new Date(value.valueOf()); // clone
43 if (isNaN(value.getTime())) {
49 XV.Input.prototype.setValue.call(this, value, options);
52 This function takes the value entered into a DateWidget and returns
53 the correct date object for this value. If the value does not correspond
54 to a valid date, the function returns false.
56 textToDate: function (value) {
58 daysInMilliseconds = 1000 * 60 * 60 * 24;
60 // Try to parse out a date given the various allowable shortcuts
62 value.indexOf('+') === 0 ||
63 value.indexOf('-') === 0) {
64 // 0 means today, +1 means tomorrow, -2 means 2 days ago, etc.
65 date = new Date(new Date().getTime() + value * daysInMilliseconds);
66 } else if (value.indexOf('#') === 0) {
67 // #40 means the fortieth day of this year, so set the month to 0
68 // and set the date accordingly. JS appropriately pushes the date
69 // into subsequent months as necessary
70 // Only allow number strings and not "" after the "#"
71 if (/^\d+$/.test(value.substring(1))) {
74 date.setDate(value.substring(1));
76 } else if (value.length && !isNaN(value)) {
77 // A positive integer by itself means that day of this month
81 // This number is limited to the number of days in the current month.
82 // If the user enters 60, the date will be the last day of the month.
83 var lastDayOfMonth = new Date();
84 lastDayOfMonth.setMonth(lastDayOfMonth.getMonth() + 1);
85 lastDayOfMonth.setDate(0);
86 if (lastDayOfMonth.getTime() < date.getTime()) {
87 date = lastDayOfMonth;
90 // If we get to this point, we're assuming that either the user is trying to enter a date
91 // and not one of the functions. We allow the JS Date to parse the date so we can allow
94 // Here we are trying to leniently limit what we pass to the JS Date function since it evaluates
95 // invalid strings such as "%1678" as a date. It may still allow some crazy strings, but these will
96 // return an Invalid Date object and we will catch it later. If we need more stingent checking, we
97 // would have to lock the users into entering dates in a specific format.
98 if (/^\d+[\/\-\.]\d+[\/\-\.]\d+/.test(value)) {
99 date = new Date(value);
103 // Check here to see if date is actually a Date. If it is "Invalid Date", then it may
104 // pass _.isDate and still not truly be a valid Date object.
105 if (!_.isDate(date) || isNaN(date.getTime())) {
109 // TODO: This little hacky block of code was making all years < 2000 convert to 2000s
110 // Firefox does not assume that these dates are in the 2000s so there's a little trickery here
111 // if (date.getFullYear() < 2000) {
112 // date.setYear("20" + value.substring(value.length - 2));
117 date = this.applyTimezoneOffset(date);
122 This function strips the time from a valid date and mimics the model's
123 action of offseting the time by the timezone for the sake of what the user
124 sees populated in the input box. This action is undone just before the value
125 is set into the model.
127 applyTimezoneOffset: function (value) {
128 value.setHours(0, 0, 0, 0);
129 return XT.date.applyTimezoneOffset(value, false);
132 This function calls textToDate, which converts the text to a valid date,
135 validate: function (value) {
136 value = this.textToDate(value);
137 // if textToDate is not able to convert the entered value, it returns false
138 return ((_.isDate(value) || _.isEmpty(value)) && value !== false) ? value : false;
141 This function puts the date in the correct format based on the locale set in Globalize.
142 It also puts back the timezoneoffset that was done in the validation function before it
143 sends the value to the model.
145 valueChanged: function (value) {
146 var nullValue = this.getNullValue();
147 if (_.isDate(value) && _.isDate(nullValue) &&
148 XT.date.compareDate(value, nullValue) === 0) {
149 value = this.getNullText();
151 value = XT.date.applyTimezoneOffset(value, true);
152 value = Globalize.format(value, "d");
156 return XV.Input.prototype.valueChanged.call(this, value);
162 @class An input control consisting of fittable columns.<br />
163 Use to implement a styled input field for entering a date
164 including associated popup menu for selecting a date.<br />
165 Creates an HTML input element.
168 enyo.kind(/** @lends XV.DateWidget# */{
169 name: "XV.DateWidget",
171 classes: "xv-input xv-datewidget",
177 {kind: "FittableColumns", components: [
178 {name: "label", content: "", fit: true, classes: "xv-flexible-label"},
179 // setting the tag to "div" on the decorator
180 // so that clicks of button don't get redirected back
181 // to the input field (default behavior)
182 {kind: "onyx.InputDecorator", name: "decorator", tag: "div",
183 classes: "xv-input-decorator", components: [
184 {name: "input", kind: "onyx.Input", onchange: "inputChanged",
185 classes: "xv-subinput", onkeydown: "keyDown"},
186 {name: "icon", kind: "onyx.Icon", ontap: "iconTapped",
187 classes: "icon-calendar"}
189 {name: "datePickPopup", kind: "onyx.Popup", maxHeight: 400, floating: true,
190 centered: true, modal: true, components: [
191 // TODO: get rid of this inline style
192 {kind: "GTS.DatePicker", name: "datePick", style: "min-width:400px;",
193 onChange: "datePicked"}
198 @todo Document create method.
200 create: function () {
201 this.inherited(arguments);
203 this.showLabelChanged();
207 This function handles a date chosen via the
208 datepicker versus text entered into the input field.
210 datePicked: function (inSender, inEvent) {
211 var date = inEvent, options = {};
212 // mimic the human-typed behavior
213 this.applyTimezoneOffset(date);
215 options.silent = true;
216 this.setValue(date, options);
217 this.$.datePickPopup.hide();
218 this.$.input.focus();
220 disabledChanged: function () {
221 this.inherited(arguments);
222 this.$.label.addRemoveClass("disabled", this.getDisabled());
225 This function handles the click of the calendar icon
226 that opens the datepicker.
228 iconTapped: function (inSender, inEvent) {
229 this.$.datePickPopup.show();
230 this.$.datePick.render();
233 Sets the label of the Date Widget with text specified in the kind.
235 labelChanged: function () {
236 var label = (this.getLabel() || ("_" + this.attr || "").loc()) + ":";
237 this.$.label.setContent(label);
240 Sets the visibility of the label of the date widget based on
241 the value specified in the kind.
243 showLabelChanged: function () {
244 if (this.getShowLabel()) {
251 Set the date value into the input field and the date picker and
252 sets the value to the model.
254 valueChanged: function (value) {
255 var dateValue = value;
256 value = XV.Date.prototype.valueChanged.call(this, value);
257 if (!this.$.input.value && this.$.input.attributes.value) {
258 // XXX workaround for incident 19171. Something deep into enyo's
259 // setters are causing the attributes value to be updated when
260 // a value is entered but not updated when the empty string is
261 // entered. Seems to only affect date widgets due to the complicated
262 // two-step of turning a inputted value into an actual date.
263 this.$.input.attributes.value = "";
265 this.$.datePick.setValue(value ? dateValue : new Date());
266 this.$.datePick.render();