/**
* Copyright (c) 2014, Oracle and/or its affiliates.
* All rights reserved.
*/
/**
* @ojcomponent oj.ojInputDateTime
* @augments oj.ojInputDate
* @since 0.6
*
* @classdesc
* <h3 id="inputDateTimeOverview-section">
* JET ojInputDateTime Component
* <a class="bookmarkable-link" title="Bookmarkable Link" href="#inputDateTimeOverview-section"></a>
* </h3>
*
* <p>Description: ojInputDateTime extends from ojInputDate providing additionally time selection drop down. Please note that for V1 timezone is not supported
* by the converter; hence not by the component.</p>
* <h3 id="keyboard-section">
* Keyboard End User Information
* <a class="bookmarkable-link" title="Bookmarkable Link" href="#keyboard-section"></a>
* </h3>
*
* <p>
*
* <table class="keyboard-table">
* <thead>
* <tr>
* <th>Key</th>
* <th>Use</th>
* </tr>
* </thead>
* <tbody>
* <tr>
* <td><kbd>Enter</kbd></td>
* <td>Select the currently focused day</td>
* </tr>
* <tr>
* <td><kbd>UpArrow</kbd></td>
* <td>Move up in the grid.</tr>
* <tr>
* <td><kbd>DownArrow</kbd></td>
* <td>Move down in the grid.</tr>
* <tr>
* <td><kbd>RightArrow</kbd></td>
* <td>Move right in the grid.</tr>
* <tr>
* <td><kbd>LeftArrow</kbd></td>
* <td>Move left in the grid.</tr>
* <tr>
* <td><kbd>Esc</kbd></td>
* <td>Close the grid.</tr>
* <tr>
* <td><kbd>Home</kbd></td>
* <td>Move focus to first day of the month.</tr>
* <tr>
* <td><kbd>End</kbd></td>
* <td>Move focus to last day of the month.</tr>
* <tr>
* <td><kbd>PageUp</kbd></td>
* <td>Switch to previous month.</tr>
* <tr>
* <td><kbd>PageDown</kbd></td>
* <td>Switch to next month.</tr>
* <tr>
* <td><kbd>Alt + PageUp</kbd></td>
* <td>Switch to previous year.</tr>
* <tr>
* <td><kbd>Alt + PageDown</kbd></td>
* <td>Switch to next year.</tr>
* <tr>
* <td><kbd>Ctrl + Alt + PageUp</kbd></td>
* <td>Switch to previous by stepBigMonths.</tr>
* <tr>
* <td><kbd>Ctrl + Alt + PageDown</kbd></td>
* <td>Switch to next by stepBigMonths.</tr>
* <tr>
* <td><kbd>Ctrl + Alt + T</kbd></td>
* <td>Places focus on Today button if it exists.</tr>
* <tr>
* <td><kbd>Shift + DownArrow or UpArrow</kbd></td>
* <td>Shows the timepicker and moves the focus into the expanded timepicker list</td>
* </tr>
* </tbody></table>
*
* <h3 id="pseudos-section">
* Pseudo-selectors
* <a class="bookmarkable-link" title="Bookmarkable Link" href="#pseudos-section"></a>
* </h3>
*
* <pre class="prettyprint">
* <code>$( ":oj-inputDateTime" ) // selects all JET input on the page
* </code>
* </pre>
*
* <h3 id="a11y-section">
* Accessibility
* <a class="bookmarkable-link" title="Bookmarkable Link" href="#a11y-section"></a>
* </h3>
* <p>
* It is up to the application developer to associate the label to the input component.
* For inputDateTime, you should put an <code>id</code> on the input, and then set
* the <code>for</code> attribute on the label to be the input's id.
* </p>
* <h3 id="label-section">
* Label and InputDateTime
* <a class="bookmarkable-link" title="Bookmarkable Link" href="#label-section"></a>
* </h3>
* <p>
* For accessibility, you should associate a label element with the input
* by putting an <code>id</code> on the input, and then setting the
* <code>for</code> attribute on the label to be the input's id.
* </p>
* <p>
* The component will decorate its associated label with required and help
* information, if the <code>required</code> and <code>help</code> options are set.
* </p>
* <h3 id="binding-section">
* Declarative Binding
* <a class="bookmarkable-link" title="Bookmarkable Link" href="#binding-section"></a>
* </h3>
*
* <pre class="prettyprint">
* <code>
* <input id="dateTimeId" data-bind="ojComponent: {component: 'ojInputDateTime'}" />
* </code>
* </pre>
*
* @desc Creates or re-initializes a JET ojInputDateTime
*
* @param {Object=} options a map of option-value pairs to set on the component
*
* @example <caption>Initialize the input element with no options specified:</caption>
* $( ".selector" ).ojInputDateTime();
*
* * @example <caption>Initialize the input element with some options:</caption>
* $( ".selector" ).ojInputDateTime( { "disabled": true } );
*
* @example <caption>Initialize the input element via the JET <code class="prettyprint">ojComponent</code> binding:</caption>
* <input id="dateTimeId" data-bind="ojComponent: {component: 'ojInputDateTime'}" />
*/
oj.__registerWidget("oj.ojInputDateTime", $['oj']['ojInputDate'],
{
version : "1.0.0",
widgetEventPrefix : "oj",
//-------------------------------------From base---------------------------------------------------//
_WIDGET_CLASS_NAMES : "oj-inputdatetime-date-time oj-component oj-inputdatetime",
_INPUT_HELPER_KEY: "inputHelpBoth",
//-------------------------------------End from base-----------------------------------------------//
options :
{
/**
* Default converter for ojInputDateTime
*
* If one wishes to provide a custom converter for the ojInputDateTime override the factory returned for
* oj.Validation.converterFactory(oj.ConverterFactory.CONVERTER_TYPE_DATETIME)
*
* @expose
* @instance
* @memberof! oj.ojInputDateTime
* @default <code class="prettyprint">oj.Validation.converterFactory(oj.ConverterFactory.CONVERTER_TYPE_DATETIME).createConverter({"day": "2-digit", "month": "2-digit", "year": "2-digit", "hour": "2-digit", "hour12": true, "minute": "2-digit"})</code>
*/
converter : oj.Validation.converterFactory(oj.ConverterFactory.CONVERTER_TYPE_DATETIME).createConverter(
{
"day" : "2-digit", "month" : "2-digit", "year" : "2-digit", "hour" : "2-digit", "hour12" : true, "minute" : "2-digit"
}),
/**
* Time Picker option is a JSON object containing sub-options for the ojInputDateTime component
*
* <p>
* The properties supported on the timePicker option are:
* @property {string=} timeIncrement Time increment to be used for ojInputDateTime, the format is hh:mm:ss:SS. <br/>
* The default value is <code class="prettyprint">{timePicker: {timeIncrement': "00:30:00:00"}}</code>. <br/>
* Example <code class="prettyprint">$(".selector").ojInputDateTime("option", "timePicker.timeIncrement", "00:10:00:00");</code>
* </p>
*
* @expose
* @instance
* @memberof! oj.ojInputDateTime
* @type {Object.<string>}
*/
timePicker:
{
/**
* @expose
*/
timeIncrement : "00:30:00:00"
}
/**
* The maximum selectable datetime. When set to null, there is no maximum.
*
* <ul>
* <li> type string - local ISOString meaning lacking Z, timezone, and timezone offset
* <li> null - no limit
* </ul>
*
* @example <caption>Initialize the component with the <code class="prettyprint">max</code> option:</caption>
* <input id="date" data-bind="ojComponent: {component: 'ojInputDateTime', max: '2014-09-25T13:30:00.000'}" />
*
* @expose
* @name max
* @instance
* @memberof! oj.ojInputDateTime
* @default <code class="prettyprint">null</code>
*/
/**
* The minimum selectable date. When set to null, there is no minimum.
*
* <ul>
* <li> type string - local ISOString meaning lacking Z, timezone, and timezone offset
* <li> null - no limit
* </ul>
*
* @example <caption>Initialize the component with the <code class="prettyprint">min</code> option:</caption>
* <input id="date" data-bind="ojComponent: {component: 'ojInputDateTime', min: '2014-08-25T08:00:00.000'}" />
*
* @expose
* @name min
* @instance
* @memberof! oj.ojInputDateTime
* @default <code class="prettyprint">null</code>
*/
/**
* List of validators used by component when performing validation. Each item is either an
* instance that duck types {@link oj.Validator}, or is an Object literal containing the
* properties listed below. Implicit validators created by a component when certain options
* are present (e.g. <code class="prettyprint">required</code> option), are separate from
* validators specified through this option. At runtime when the component runs validation, it
* combines the implicit validators with the list specified through this option.
* <p>
* Hints exposed by validators are shown in the notewindow by default, or as determined by the
* 'validatorHint' property set on the <code class="prettyprint">displayOptions</code>
* option.
* </p>
*
* <p>
* When <code class="prettyprint">validators</code> option changes due to programmatic
* intervention, the component may decide to clear messages and run validation, based on the
* current state it is in. </br>
*
* <h4>Steps Performed Always</h4>
* <ul>
* <li>The cached list of validator instances are cleared and new validator hints is pushed to
* messaging. E.g., notewindow displays the new hint(s).
* </li>
* </ul>
*
* <h4>Running Validation</h4>
* <ul>
* <li>if component is valid when validators changes, component does nothing other than the
* steps it always performs.</li>
* <li>if component is invalid and is showing messages -
* <code class="prettyprint">messagesShown</code> option is non-empty, when
* <code class="prettyprint">validators</code> changes then all component messages are cleared
* and full validation run using the display value on the component.
* <ul>
* <li>if there are validation errors, then <code class="prettyprint">value</code>
* option is not updated and the error pushed to <code class="prettyprint">messagesShown</code>
* option.
* </li>
* <li>if no errors result from the validation, the <code class="prettyprint">value</code>
* option is updated; page author can listen to the <code class="prettyprint">optionChange</code>
* event on the <code class="prettyprint">value</code> option to clear custom errors.</li>
* </ul>
* </li>
* <li>if component is invalid and has deferred messages when validators changes, it does
* nothing other than the steps it performs always.</li>
* </ul>
* </p>
*
* <h4>Clearing Messages</h4>
* <ul>
* <li>Only messages created by the component are cleared. These include ones in
* <code class="prettyprint">messagesHidden</code> and <code class="prettyprint">messagesShown</code>
* options.</li>
* <li><code class="prettyprint">messagesCustom</code> option is not cleared.</li>
* </ul>
* </p>
*
* @property {string} type - the validator type that has a {@link oj.ValidatorFactory} that can
* be retrieved using the {@link oj.Validation} module. For a list of supported validators refer
* to {@link oj.ValidatorFactory}. <br/>
* @property {Object=} options - optional Object literal of options that the validator expects.
*
* @example <caption>Initialize the component with validator object literal:</caption>
* $(".selector").ojInputDateTime({
* validators: [{
* type: 'dateTimeRange',
* options : {
* max: '2014-09-10T13:30:00.000',
* min: '2014-09-01T00:00:00.000'
* }
* }],
* });
*
* NOTE: oj.Validation.validatorFactory('dateTimeRange') returns the validator factory that is used
* to instantiate a range validator for dateTime.
*
* @example <caption>Initialize the component with multiple validator instances:</caption>
* var validator1 = new MyCustomValidator({'foo': 'A'});
* var validator2 = new MyCustomValidator({'foo': 'B'});
* // Foo is InputText, InputNumber, Select, etc.
* $(".selector").ojFoo({
* value: 10,
* validators: [validator1, validator2]
* });
*
* @expose
* @name validators
* @instance
* @memberof oj.ojInputDateTime
* @type {Array|undefined}
*/
/**
* The value of the ojInputDateTime component which should be a local ISOString meaning lacking Z, timezone, and timezone offset
*
* @example <caption>Initialize the component with the <code class="prettyprint">value</code> option:</caption>
* <input id="date" data-bind="ojComponent: {component: 'ojInputDateTime', value: '2014-09-10T13:30:00.000'}" />
* @example <caption>Initialize the component with the <code class="prettyprint">value</code> option specified programmatically
* using oj.IntlConverterUtils.dateToLocalIso :</caption>
* $(".selector").ojInputDateTime({'value': oj.IntlConverterUtils.dateToLocalIso(new Date())});<br/>
* @example <caption>Get or set the <code class="prettyprint">value</code> option, after initialization:</caption>
* // Getter: returns Today's date in ISOString
* $(".selector").ojInputDateTime("option", "value");
* // Setter: sets it to a different date
* $(".selector").ojInputDateTime("option", "value", oj.IntlConverterUtils.dateToLocalIso(new Date(2013, 0, 1, 0, 0, 0, 0)));
*
* @expose
* @name value
* @instance
* @memberof! oj.ojInputDateTime
* @default When the option is not set, the element's value property is used as its initial value
* if it exists. This value must be a local ISOString meaning lacking Z, timezone, and timezone offset.
*/
},
/**
* @protected
* @override
* @instance
* @memberof! oj.ojInputDateTime
*/
_InitBase : function __InitBase()
{
this._super();
this._timePickerElement = this.element; //if the ojInputDateTime is inline, then this ref will change to a NEW input element
this._timePicker = null;
this._timeConverter = null;
//need to remember the last _SetValue for the case of timepicker [i.e. select a date that is not in range due to
//time; however since we don't push invalid values to this.options["value"] the timepicker would pick up the wrong
//selected date
this._previousValue = null;
},
/**
* @protected
* @override
* @instance
* @memberof! oj.ojInputDateTime
*/
_ComponentCreate : function __ComponentCreate()
{
var ret = this._super(),
timeConverter = this._getTimePickerConverter(this._GetConverter());
if (timeConverter === null)
{
throw new Error("Please use ojInputDate if you do not have time portion");
}
if (this._isInLine)
{
//Since DatePicker never intended to have timepicker associated to it
//need to have an input element that is tied to the time selector
//@HTMLUpdateOK
var input = $("<input type='text'>");
input.insertAfter(this.element);
//Now need to reset this._timePickerElement to the newly created input element
this._timePickerElement = input;
}
var passOptions = ["title", "placeholder", "disabled", "required", "readOnly"],
passObject = {};
for(var i=0, j=passOptions.length; i < j; i++)
{
passObject[passOptions[i]] = this.options[passOptions[i]];
}
var messagesDisplayOption = this.options['displayOptions']['messages'];
//create time instance for the time portion
// jmw Seems to be a bug where messages are always in notewindow. So I think I should
// carry the displayOptions over to the timePicker.
this._timePicker = this._timePickerElement.ojInputTime(
$.extend(passObject, {
"converter" : timeConverter,
"displayOptions" : {"converterHint": "none", "title": "none", "messages": messagesDisplayOption},
"value": oj.IntlConverterUtils.dateToLocalIso(this._getDate()),
"timePicker" : this.options["timePicker"],
"datePickerComp" : {"widget": this, "inline": this._isInLine}
}));
return ret;
},
_setOption : function __setOption(key, value, flags)
{
var retVal = this._superApply(arguments);
if (key === "value")
{
//if goes through model does it needs to update or should be only used by selection + keydown
this._previousValue = value;
}
if(this._timePicker)
{
var timeInvoker = {"disabled": true, "readOnly": true};
if (key in timeInvoker)
{
this._timePicker.ojInputTime("option", key, value);
}
else if(key === "timePicker")
{
this._timePicker.ojInputTime("option", "timePicker.timeIncrement", value["timeIncrement"]);
}
else if (key === "converter")
{
this._timeConverter = null;
this._timePicker.ojInputTime("option", key, this._getTimePickerConverter(this._GetConverter())); //need to invoke _GetConverter for the case when null and etc sent in
}
}
return retVal;
},
/**
* @ignore
* @protected
* @override
*/
_destroy : function __destroy()
{
var ret = this._super();
this._timePicker.ojInputTime("destroy");
if (this._isInLine)
{
//note that this.element below would be of the TimePicker's input element
this._timePickerElement.remove();
}
return ret;
},
/*
* Will provide the timePicker converter based on the actual converter
*/
_getTimePickerConverter : function __getTimePickerConverter(converter)
{
if(this._timeConverter !== null)
{
return this._timeConverter;
}
var resolvedOptions = converter.resolvedOptions(), options = { },
params = ["hour", "hour12", "minute", "second", "millisecond", "timeFormat"], i, j;
for (i = 0, j = params.length;i < j;i++)
{
if (params[i] in resolvedOptions)
{
if(params[i] === "timeFormat") {
//special case for timeFormat, formatType of time must be added
options["formatType"] = "time";
}
options[params[i]] = resolvedOptions[params[i]];
}
}
if ($.isEmptyObject(options))
{
throw new Error("Plase use ojInputDateTime's converter is lacking the time portion");
}
var timeConverter = oj.Validation.converterFactory(oj.ConverterFactory.CONVERTER_TYPE_DATETIME).createConverter(options);
this._timeConverter = timeConverter;
return timeConverter;
},
/**
* Handler for when the time is selected. Should be invoked ONLY by the ojInputTime component
*
* @ignore
* @param {string} newValue
* @param {Event} event
*/
timeSelected : function __timeSelected(newValue, event)
{
this._SetDisplayValue( this._GetConverter()["format"](newValue) );
this._SetValue(newValue, event);
},
/**
* Provides the current displayed selected value for ojInputTime component [i.e. when is invalid return this._previousValue]
* The complication occurs b/c we do not push invalid values to the model and b/c of that reason this.options["value"]
* might contain outdated isoString for ojInputTime. For instance let's say the min date is 02/01/14 2PM then
* when an user selects 02/01/14 the component would be invalid and the value would not be pushed. However one needs
* to give opportunity for ojInputTime to allow user in selecting the valid datetime in full so the _previousValue
* must be passed through.
*
* @ignore
*/
getValueForInputTime : function __getValueForInputTime()
{
return this.isValid() ? this.options["value"] : (this._previousValue ? this._GetConverter()["parse"](this._previousValue) : null);
},
/**
* @ignore
* @protected
* @override
* @instance
* @memberof! oj.ojInputDateTime
*/
_SetValue : function __SetValue(newValue, event, options)
{
this._superApply(arguments);
this._previousValue = newValue;
},
/**
* Just for the case of launching timepicker with Shift + Up or Shift + Down
*
* @ignore
* @protected
* @override
* @param {Event} event
* @instance
* @memberof! oj.ojInputDateTime
*/
_onKeyDownHandler : function __onKeyDownHandler(event)
{
var kc = $.ui.keyCode,
handled = false;
switch (event.keyCode)
{
case kc.UP: ;
case kc.DOWN:
if(event.shiftKey)
{
this._SetValue(this._GetDisplayValue(), event);
this._timePicker.ojInputTime("show");
handled = true;
}
break;
default: ;
}
if (handled)
{
event.preventDefault();
event.stopPropagation();
return false;
}
return this._superApply(arguments);
},
/**
* @ignore
* @expose
* @instance
* @memberof! oj.ojInputDateTime
*/
show : function __show()
{
this._timePicker.ojInputTime("hide");
return this._super();
},
/**
* Method to show the internally created ojInputTime
*
* @expose
* @memberof! oj.ojInputDateTime
* @instance
*/
showTimePicker : function __showTimePicker()
{
this.hide();
return this._timePicker.ojInputTime("show");
},
/**
* Method to hide the internally created ojInputTime
*
* @expose
* @memberof! oj.ojInputDateTime
* @instance
*/
hideTimePicker : function __hideTimePicker()
{
return this._timePicker.ojInputTime("hide");
},
/**
* @ignore
* @override
* @instance
* @memberof! oj.ojInputDateTime
*/
refresh : function __refresh()
{
var retVal = this._superApply(arguments) || this;
this._timePicker.ojInputTime("refresh");
return retVal;
},
/**
* Return the subcomponent node represented by the documented locator attribute values. <br/>
* If the locator is null or no subId string is provided then this method returns the element that
* this component was initalized with. <br/>
* If a subId was provided but a subcomponent node cannot be located this method returns null.
*
* <p>If the <code class="prettyprint">locator</code> or its <code class="prettyprint">subId</code> is
* <code class="prettyprint">null</code>, then this method returns the element on which this component was initalized.
*
* <p>If a <code class="prettyprint">subId</code> was provided but no corresponding node
* can be located, then this method returns <code class="prettyprint">null</code>.
*
* @expose
* @override
* @memberof oj.ojInputDateTime
* @instance
*
* @param {Object} locator An Object containing, at minimum, a <code class="prettyprint">subId</code>
* property. See the table for details on its fields.
*
* @property {string=} locator.subId - A string that identifies a particular DOM node in this component.
*
* <p>The supported sub-ID's are documented in the <a href="#subids-section">Sub-ID's</a> section of this document.
*
* @property {number=} locator.index - A zero-based index, used to locate a message content node
* or a hint node within the popup.
* @returns {Element|null} The DOM node located by the <code class="prettyprint">subId</code> string passed in
* <code class="prettyprint">locator</code>, or <code class="prettyprint">null</code> if none is found.
*
* @example <caption>Get the node for a certain subId:</caption>
* var node = $( ".selector" ).ojInputDateTime( "getNodeBySubId", {'subId': 'oj-some-sub-id'} );
*/
getNodeBySubId: function(locator)
{
var subId = locator && locator['subId'],
node = null;
if(subId)
{
if(subId === "oj-listbox-drop" || subId === "oj-inputdatetime-time-icon")
{
node = this._timePicker.ojInputTime("getNodeBySubId", locator);
}
else if(subId === "oj-inputdatetime-date-input")
{
node = this._isInLine ? this._timePickerElement[0] : this.element[0];
}
}
return node || this._superApply(arguments);
},
/**
* Returns the subId string for the given child DOM node. For more details, see
* <a href="#getNodeBySubId">getNodeBySubId</a>.
*
* @expose
* @override
* @memberof oj.ojInputDateTime
* @instance
*
* @param {!Element} node - child DOM node
* @return {string|null} The subId for the DOM node, or <code class="prettyprint">null</code> when none is found.
*
* @example <caption>Get the subId for a certain DOM node:</caption>
* // Foo is ojInputNumber, ojInputDate, etc.
* var subId = $( ".selector" ).ojFoo( "getSubIdByNode", nodeInsideComponent );
*/
getSubIdByNode: function(node)
{
var dateTimeSpecific = null;
if(this._isInLine)
{
if(node === this._timePickerElement[0])
{
dateTimeSpecific = "oj-inputdatetime-date-input";
}
}
else
{
if(node === this.element[0])
{
dateTimeSpecific = "oj-inputdatetime-date-input";
}
}
return dateTimeSpecific || this._timePicker.ojInputTime("getSubIdByNode", node) || this._superApply(arguments);
},
/**
* Need to override since apparently we allow users to set the converter to null, undefined, and etc and when
* they do we use the default converter
*
* @return {Object} a converter instance or null
*
* @memberof! oj.ojInputDateTime
* @instance
* @protected
* @override
*/
_GetConverter : function ()
{
return this.options['converter'] ?
this._superApply(arguments) :
$["oj"]["ojInputDateTime"]["prototype"]["options"]["converter"];
},
/**
* Notifies the component that its subtree has been removed from the document programmatically after the component has
* been created
* @memberof! oj.ojInputDateTime
* @instance
* @protected
*/
_NotifyDetached: function()
{
this._superApply(arguments);
if(this._timePicker)
{
this.hideTimePicker();
}
},
/**
* Notifies the component that its subtree has been made hidden programmatically after the component has
* been created
* @memberof! oj.ojInputDateTime
* @instance
* @protected
*/
_NotifyHidden: function()
{
this._superApply(arguments);
if(this._timePicker)
{
this.hideTimePicker();
}
},
/**
*
* @return {Object} jquery object
*
*
* @expose
* @protected
* @override
* @instance
* @memberof! oj.ojInputDateTime
*/
_GetMessagingLauncherElement : function ()
{
return !this._isInLine ? this._super() : this._timePickerElement;
},
/**
* @protected
* @override
* @instance
* @memberof! oj.ojInputDateTime
* @return {string}
*/
_GetDefaultStyleClass : function ()
{
return "oj-inputdatetime";
},
/**
* @protected
* @override
* @instance
* @memberof! oj.ojInputDateTime
*/
_GetTranslationsSectionName: function()
{
return "oj-ojInputDate";
}
});
Source: src/main/javascript/oracle/oj/ojdatetimepicker/ojdatetimepicker.js
Oracle® JavaScript Extension Toolkit (JET)
1.1.2
E65298-01