/**
* Copyright (c) 2014, Oracle and/or its affiliates.
* All rights reserved.
*/
/**
* @ojcomponent oj.ojInputTime
* @augments oj.inputBase
* @since 0.6
*
* @classdesc
* <h3 id="inputTimeOverview-section">
* JET ojInputTime Component
* <a class="bookmarkable-link" title="Bookmarkable Link" href="#inputTimeOverview-section"></a>
* </h3>
*
* <p>Description: ojInputTime provides a simple time selection drop down. Please note that for V1 timezone is not supported
* by the converter; hence not by the component.
*
* <h3 id="pseudos-section">
* Pseudo-selectors
* <a class="bookmarkable-link" title="Bookmarkable Link" href="#pseudos-section"></a>
* </h3>
*
* <pre class="prettyprint">
* <code>$( ":oj-inputTime" ) // selects all JET input on the page
* </code>
* </pre>
*
* <h3 id="binding-section">
* Declarative Binding
* <a class="bookmarkable-link" title="Bookmarkable Link" href="#binding-section"></a>
* </h3>
*
* <pre class="prettyprint">
* <code>
* <input id="timeId" data-bind="ojComponent: {component: 'ojInputTime'}" />
* </code>
* </pre>
*
* @desc Creates or re-initializes a JET ojInputTime
*
* @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" ).ojInputTime();
*
* * @example <caption>Initialize the input element with some options:</caption>
* $( ".selector" ).ojInputTime( { "disabled": true } );
*
* @example <caption>Initialize the input element via the JET <code class="prettyprint">ojComponent</code> binding:</caption>
* <input id="timeId" data-bind="ojComponent: {component: 'ojInputTime'}" />
*/
oj.__registerWidget("oj.ojInputTime", $['oj']['inputBase'],
{
version : "1.0.0",
widgetEventPrefix : "oj",
//-------------------------------------From base---------------------------------------------------//
_CLASS_NAMES : "oj-inputdatetime-input",
_WIDGET_CLASS_NAMES : "oj-inputdatetime-time-only oj-component oj-inputdatetime",
_INPUT_CONTAINER_CLASS : "oj-inputdatetime-input-container",
_ELEMENT_TRIGGER_WRAPPER_CLASS_NAMES : "",
_INPUT_HELPER_KEY: "inputHelp",
_ATTR_CHECK : [{"attr": "type", "setMandatory": "text"}],
_GET_INIT_OPTIONS_PROPS: [{attribute: "disabled", defaultOptionValue: false, validateOption: true},
{attribute: 'pattern', defaultOptionValue: ""},
{attribute: "title", defaultOptionValue: ""},
{attribute: "placeholder", defaultOptionValue: ""},
{attribute: "value", defaultOptionValue: null, coerceDomValue: coerceIsoString},
{attribute: "required", defaultOptionValue: false,
coerceDomValue: true, validateOption: true},
{attribute: 'readonly', option: 'readOnly', defaultOptionValue: false,
validateOption: true},
{attribute: "min", defaultOptionValue: null, coerceDomValue: coerceIsoString},
{attribute: "max", defaultOptionValue: null, coerceDomValue: coerceIsoString}],
//-------------------------------------End from base-----------------------------------------------//
_TIME_PICKER_ID : "ojInputTime",
_TRIGGER_CLASS : "oj-inputdatetime-input-trigger",
_TRIGGER_TIME_CLASS : "oj-inputdatetime-time-icon",
options :
{
/**
* Default converter for ojInputTime
*
* If one wishes to provide a custom converter for the ojInputTime override the factory returned for
* oj.Validation.converterFactory(oj.ConverterFactory.CONVERTER_TYPE_DATETIME)
*
* @expose
* @memberof! oj.ojInputTime
* @instance
* @default <code class="prettyprint">oj.Validation.converterFactory(oj.ConverterFactory.CONVERTER_TYPE_DATETIME).createConverter({"hour": "2-digit", "hour12": true, "minute": "2-digit"})</code>
*/
converter : oj.Validation.converterFactory(oj.ConverterFactory.CONVERTER_TYPE_DATETIME).createConverter(
{
"hour" : "2-digit", "hour12" : true, "minute" : "2-digit"
}),
/**
* The maximum selectable date. 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: 'ojInputTime', max: 'T13:30:00.000'}" />
*
* @expose
* @instance
* @memberof! oj.ojInputTime
* @default <code class="prettyprint">null</code>
*/
max : undefined,
/**
* 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: 'ojInputTime', min: 'T08:00:00.000'}" />
*
* @expose
* @instance
* @memberof! oj.ojInputTime
* @default <code class="prettyprint">null</code>
*/
min : undefined,
/**
* JSON data passed when the widget is of ojInputDateTime
*
* {
* widget : dateTimePickerInstance,
* inline: true|false
* }
*
* @expose
* @memberof! oj.ojInputTime
* @instance
* @private
*/
datePickerComp : null,
/**
* <p>
* The properties supported on the timePicker option are:
* @property {string=} timeIncrement Time increment to be used for ojInputTime, the format is hh:mm:ss:SS. <br/><br/>
*
* The default value is <code class="prettyprint">{timePicker: {timeIncrement': "00:30:00:00"}}</code>. <br/><br/>
* Example <code class="prettyprint">$(".selector").ojInputTime("option", "timePicker.timeIncrement", "00:10:00:00");</code>
* </p>
*
* @expose
* @instance
* @memberof! oj.ojInputTime
*/
timePicker:
{
/**
* @expose
*/
timeIncrement : "00:30:00:00"
}
// DOCLETS
/**
* The placeholder text to set on the element. Though it is possible to set placeholder
* attribute on the element itself, the component will only read the value when the component
* is created. Subsequent changes to the element's placeholder attribute will not be picked up
* and page authors should update the option directly.
*
* @example <caption>Initialize the component with the <code class="prettyprint">placeholder</code> option:</caption>
* <input id="date" data-bind="ojComponent: {component: 'ojInputTime', placeholder: 'Birth Date'}" />
*
* @example <caption>Initialize <code class="prettyprint">placeholder</code> option from html attribute:</caption>
* <input id="date" data-bind="ojComponent: {component: 'ojInputTime'}" placeholder="User Name" />
*
* @default when the option is not set, the element's placeholder attribute is used if it exists.
* If the attribute is not set then the default can be the converter hint provided by the
* datetime converter. See displayOptions for details.
*
* @access public
* @instance
* @expose
* @name placeholder
* @instance
* @memberof! oj.ojInputTime
*/
/**
* 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").ojInputTime({
* validators: [{
* type: 'dateTimeRange',
* options : {
* max: 'T14:30:00',
* min: 'T02:30:00'
* }
* }],
* });
*
* 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.ojInputTime
* @type {Array|undefined}
*/
/**
* The value of the ojInputTime 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: 'ojInputTime', value: 'T10:30:00.000'}" />
* @example <caption>Initialize the component with the <code class="prettyprint">value</code> option specified programmatically
* using oj.IntlConverterUtils.dateToLocalIso :</caption>
* $(".selector").ojInputTime({'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").ojInputTime("option", "value");
* // Setter: sets it to a different date
* $(".selector").ojInputTime("option", "value", oj.IntlConverterUtils.dateToLocalIso(new Date(2013, 0, 1, 0, 0, 0, 0)));
*
* @expose
* @name value
* @instance
* @memberof! oj.ojInputTime
* @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.ojInputTime
*/
_InitOptions: function(originalDefaults, constructorOptions)
{
this._super(originalDefaults, constructorOptions);
//when it is of ojInputDateTime component, do not initialize values from dom node since it's an empty input node if inline or
//if not inline the values should be taken care of by ojInputDateTime. Note that option values would have been passed by
//ojInputDateTime
if(this.options["datePickerComp"] === null)
{
oj.EditableValueUtils.initializeOptionsFromDom(this._GET_INIT_OPTIONS_PROPS, constructorOptions, this);
}
},
/**
* @ignore
*/
_InitBase : function __InitBase()
{
this._timePickerDefaultValidators = {};
this._datePickerComp = this.options["datePickerComp"];
this._timePickerDisplay = $("<div id='" + this._GetSubId(this._TIME_PICKER_ID) + "' class='oj-listbox-drop' style='display:none'></div>");
//@HTMLUpdateOK
$("body").append(this._timePickerDisplay);
var self = this;
this._popUpTimePickerDisplay = this._timePickerDisplay.ojPopup(
{
"initialFocus": "none",
"rootAttributes": {"class": "datetimepicker-dropdown"},
"chrome": "none",
"open": function ()
{
var selected = $("[aria-selected]", self._timePickerDisplay);
if (selected.length === 1)
{
self._checkScrollTop(selected.parent(), true);
}
$("ul", self._timePickerDisplay).focus();
},
"beforeClose": function ()
{
self._timeListBoxScrollTop = $("ul", self._timePickerDisplay).scrollTop();
}
});
// I want to wrap the inputTime if it is all by itself, or if it is
// part of the inline inputDateTime component which is the inline date stacked on top of an
// inputTime. The inline error messages will go under the inputTime part. TODO: how?
// right now the destroy fails because I am whacking away something.. the dom.
if (this._isIndependentInput())
this._ELEMENT_TRIGGER_WRAPPER_CLASS_NAMES += this._INPUT_CONTAINER_CLASS;
},
_timepickerShowing: function ()
{
return this._popUpTimePickerDisplay.ojPopup("isOpen");
},
/**
* @protected
* @override
* @instance
* @memberof! oj.ojInputTime
*/
_ComponentCreate : function __ComponentCreate()
{
this._InitBase();
var ret = this._superApply(arguments);
if (this._isContainedInDateTimePicker() && !this._isDatePickerInline())
{
//set to nothing since then of not inline and don't want to place two component classes to
//the same input element
this._CLASS_NAMES = "";
}
this._attachTrigger();
return ret;
},
/**
* @protected
* @override
* @instance
* @memberof! oj.ojInputTime
*/
_AfterCreate : function ()
{
var ret = this._superApply(arguments);
disableEnableSpan(this._triggerNode.children(), this.options["disabled"]);
return ret;
},
/**
* @ignore
* @protected
* @override
*/
_setOption : function __setOption(key, value, flags)
{
var retVal = null;
//When a null, undefined, or "" value is passed in set to null for consistency
//note that if they pass in 0 it will also set it null
if (key === "value")
{
if(!value)
{
value = null;
}
retVal = this._super(key, value, flags);
this._generateTime();
return retVal;
}
retVal = this._superApply(arguments);
if(key === "disabled")
{
if(value)
{
this.hide();
}
this._triggerNode.find("." + this._TRIGGER_TIME_CLASS).attr("title", this._getTimeTitle());
disableEnableSpan(this._triggerNode.children(), value);
}
else if ((key === "max" || key === "min") && !this._isContainedInDateTimePicker())
{
//since validators are immutable, they will contain min + max as local values. B/c of this will need to recreate
this._timePickerDefaultValidators[oj.ValidatorFactory.VALIDATOR_TYPE_DATETIMERANGE] = getImplicitDateTimeRangeValidator(this.options, this._GetConverter());
this._AfterSetOptionValidators();
}
else if(key === "readOnly" && value)
{
this.hide();
}
var redrawTimePicker = {"max": true, "min": true, "converter": true, "timePicker": true};
if(key in redrawTimePicker)
{
//changing back to original code of invoking _generateTime per discussion
this._generateTime();
}
return retVal;
},
/**
* @ignore
* @protected
* @override
*/
_destroy : function __destroy()
{
var retVal = this._super();
if (this._triggerNode)
{
this._triggerNode.remove();
}
this._timePickerDisplay.remove();
return retVal;
},
/**
* Invoke super only if it is standlone or if it is part of ojInputDateTime and ojInputDateTime is inline
*
* @ignore
* @protected
* @override
*/
_AppendInputHelper : function __AppendInputHelper()
{
if (this._isIndependentInput())
{
this._superApply(arguments);
}
},
/**
* Need to override due to usage of display: inline-table [as otherwise for webkit the hidden content takes up
* descent amount of space]
*
* @protected
* @instance
* @memberOf !oj.ojInputTime
*/
_AppendInputHelperParent : function __AppendInputHelperParent()
{
return this._triggerNode;
},
/**
* Only time to have ojInputTime handle the display of timepicker by keyDown is when datePickerComp reference is null or
* when it is not null and is inline
*
* @ignore
* @protected
* @override
* @param {Event} event
*/
_onKeyDownHandler : function __onKeyDownHandler(event)
{
if(this._isIndependentInput())
{
this._superApply(arguments);
var kc = $.ui.keyCode,
handled = false;
if (this._timepickerShowing())
{
switch (event.keyCode)
{
case kc.TAB: ;
this.hide();
break;
case kc.ESCAPE:
this.hide();
handled = true;
break;
case kc.UP: ;
case kc.DOWN:
$("ul", this._timePickerDisplay).focus();
handled = true;
break;
}
}
else
{
switch (event.keyCode)
{
case kc.UP: ;
case kc.DOWN:
this._SetValue(this._GetDisplayValue(), event);
this.show();
handled = true;
break;
}
}
if (handled || event.keyCode === kc.ENTER)
{
event.preventDefault();
event.stopPropagation();
return false;
}
}
},
_getTimeTitle: function __getTimeTitle()
{
return this._EscapeXSS(this.getTranslatedString("tooltipTime" + (this.options["disabled"] ? "Disabled" : "")));
},
/**
* This function will create the necessary time trigger container [i.e. image to launch the time drop down]
* and perform any attachment to events
*
* @private
*/
_attachTrigger : function __attachTrigger()
{
//only time to create one's own span element is when datePickerComp reference is null or when it is not null and is inline
var createNewSpan = this._isIndependentInput(),
triggerContainer = createNewSpan ? $($("<span>").addClass(this._TRIGGER_CLASS)) : $("+ span", this.element),
triggerTime = $("<span title='" + this._getTimeTitle() + "'/>").addClass(this._TRIGGER_TIME_CLASS + " oj-clickable-icon-nocontext oj-component-icon");
//@HTMLUpdateOK
triggerContainer.append(triggerTime);
var self = this;
triggerTime.on("click", function ()
{
if (self._timepickerShowing())
{
self.hide();
}
else
{
self.show();
}
}).on("mouseenter", function()
{
$(this).addClass("oj-hover");
}).on("mousedown", function()
{
$(this).addClass("oj-active");
}).on("mouseup", function()
{
$(this).removeClass("oj-active");
}).on("mouseleave", function()
{
$(this).removeClass("oj-hover oj-active");
});
this._triggerIcon = triggerTime;
this._triggerNode = triggerContainer;
if (createNewSpan)
{
//@HTMLUpdateOK
this.element.after(triggerContainer);
}
},
/**
* Returns a boolean of whether the date is in the min + max range
*
* @private
*/
_inMinMaxRange : function __inMinMaxRange(date, minDate, maxDate)
{
return (minDate !== null && date < minDate) || (maxDate !== null && date > maxDate);
},
_getValue : function __getValue()
{
//need to use ojInputDateTime's value when created internally [i.e. for min + max and etc].
return this._isContainedInDateTimePicker() ? this._datePickerComp["widget"].getValueForInputTime() : this.options["value"];
},
/**
* This function will generate the time drop down
*
* @private
*/
_generateTime : function __generateTime()
{
var processDate = this._getValue(),
converter = this._GetConverter(),
timeNode = $("<ul class='oj-listbox-results' tabindex='-1' role='listbox'></ul>"),
selectedDateFormat = (processDate ? converter.format(processDate) : ""),
source = [], i, j;
processDate = processDate ? oj.IntlConverterUtils.isoToLocalDate(processDate) : new Date(); //don't care about year, month, and date since will generate time
processDate.setHours(0);
processDate.setMinutes(0);
processDate.setSeconds(0);
processDate.setMilliseconds(0);
source = this._getTimeSource(processDate);
selectedDateFormat = selectedDateFormat || source[0].value; //either choose the selected date or if it doesn't exist the first value
this._timePickerDisplay.empty();
for (i = 0, j = source.length;i < j;i++)
{
var value = source[i].value,
minMaxRange = source[i]["minMaxRange"],
liNode = $("<li class='oj-listbox-result " + (minMaxRange ? "oj-disabled" : "") + "' role='presentation'>"),
nodeId = this["uuid"] + "_sel" + i,
node = $("<div class='oj-listbox-result-label' " + (minMaxRange ? "aria-disabled " : "") + "data-value='" + value + "' role='option' id='" +
nodeId + "'>" + source[i].label + "</li>");
if (selectedDateFormat === value)
{
node.attr("aria-selected", "true");
liNode.addClass("oj-hover"); //TODO When combo box changes it's CSS to Jet specific [i.e. oj-selected or something else] make the same change
timeNode.attr("aria-activedescendant", nodeId);
}
//@HTMLUpdateOK
liNode.append(node);
timeNode.append(liNode);
}
//@HTMLUpdateOK
this._timePickerDisplay.append(timeNode);
$(".oj-listbox-result", timeNode).on("mousemove", function ()
{
var ref = $(this);
if(ref.hasClass("oj-disabled"))
{
//ignore disabled entries
return;
}
$(".oj-hover", timeNode).removeClass("oj-hover"); //remove previously selected entry TODO modify when combo box changes
ref.addClass("oj-hover"); //TODO modify when combo box changes its CSS selection identifier
timeNode.attr("aria-activedescendant", ref.children()[0].id);
});
var self = this;
timeNode.on("click", function (event)
{
var target = $(event.target);
if(target.hasClass("oj-disabled") || target.attr("aria-disabled") !== undefined)
{
//disabled
return;
}
self.hide();
self._processTimeSelection(event);
}).on("keydown", function (event)
{
self._timeNodeKeyDown(event);
});
},
/**
* This function will return an array of JSON objects of label + value for the
* time drop down
*
* @private
* @param {Object} date to get timeSource of
* @return {Array} source
*/
_getTimeSource : function __getTimeSource(date)
{
var source = [],
converter = this._GetConverter();
if (date)
{
var timeIncrement = this.options["timePicker"]["timeIncrement"],
splitted = timeIncrement.split(":"),
converterUtils = oj.IntlConverterUtils,
containedInDateTimePicker = this._isContainedInDateTimePicker(),
minDate = containedInDateTimePicker ? this._datePickerComp["widget"].options["min"] : this.options["min"],
maxDate = containedInDateTimePicker ? this._datePickerComp["widget"].options["max"] : this.options["max"];
minDate = minDate ? converterUtils.isoToLocalDate(minDate) : null;
maxDate = maxDate ? converterUtils.isoToLocalDate(maxDate) : null;
if (splitted.length === 4)
{
var increments = {
hourIncr : parseInt(splitted[0].substring(0), 10),
minuteIncr : parseInt(splitted[1], 10),
secondIncr : parseInt(splitted[2], 10),
millisecondIncr : parseInt(splitted[3], 10)
};
var processDate = new Date(date), formatted = "";
//continue until day differs
do
{
formatted = converter.format(converterUtils.dateToLocalIso(processDate));
source.push(
{
label : formatted, value : formatted, "minMaxRange" : this._inMinMaxRange(processDate, minDate, maxDate)
});
processDate.setHours(processDate.getHours() + increments.hourIncr);
processDate.setMinutes(processDate.getMinutes() + increments.minuteIncr);
processDate.setSeconds(processDate.getSeconds() + increments.secondIncr);
processDate.setMilliseconds(processDate.getMilliseconds() + increments.millisecondIncr);
}
while (processDate.getDate() === date.getDate());
}
else
{
throw new Error("timeIncrement value should be in the format of hh:mm:ss:SS");
}
}
return source;
},
//This handler is when an user keys down with the drop down has focus
_timeNodeKeyDown : function __timeNodeKeyDown(event)
{
if (this._timepickerShowing())
{
var kc = $.ui.keyCode,
handled = false;
switch (event.keyCode)
{
case kc.TAB: ;
this.hide();
break;
case kc.ESCAPE:
this.hide();
this.element.focus();
handled = true;
break;
case kc.UP:
this._processNextPrevSibling(event, "prev");
handled = true;
break;
case kc.DOWN:
this._processNextPrevSibling(event, "next");
handled = true;
break;
case kc.ENTER:
this._processTimeSelection(event);
handled = true;
break;
}
if (handled)
{
event.preventDefault();
event.stopPropagation();
return false;
}
}
},
/**
* This function will set the oj-hover to the next or previous sibling due to key down or key up stroke
*
* @private
* @param {Event} event
* @param {string} prevOrNext
*/
_processNextPrevSibling : function __processNextPrevSibling(event, prevOrNext)
{
var prevActive = $(".oj-hover", this._timePickerDisplay), //TODO update when combobox updates their selector CSS
ulElement = $("ul", this._timePickerDisplay),
node = null;
if (prevActive.length === 1)
{
node = prevActive[prevOrNext]();
if (node.length === 1)
{
prevActive.removeClass("oj-hover"); //TODO update when combobox updates their selector CSS
}
}
else
{
//if empty node [meaning value of the component can be not in interval, i.e. 12:33PM when interval is 30min
//select the first node
node = $(ulElement.children()[0]);
}
if(node && node.length === 1)
{
node.addClass("oj-hover");
ulElement.attr("aria-activedescendant", node.children()[0].id);
this._checkScrollTop(node);
}
},
/**
* This handler is when an user selects a time entry
*
* @private
* @param {Event} event
*/
_processTimeSelection : function __processTimeSelection(event)
{
var timePickerDisplay = this._timePickerDisplay,
prevSelected = $("[aria-selected]", timePickerDisplay),
ulElement = $("ul", timePickerDisplay),
selected = $(".oj-hover div", timePickerDisplay); //TODO update when combobox updates their selector CSS
if (selected.length !== 1)
{
return;
}
if(prevSelected.length === 1)
{
//previous selection can be 0 so remove only when of size 1
prevSelected.removeAttr("aria-selected");
prevSelected.parent().removeClass("oj-hover"); //TODO update when combobox updates their selector CSS
}
selected.attr("aria-selected", "true");
selected.parent().addClass("oj-hover"); //TODO update when combobox updates their selector CSS
this.hide();
this._SetDisplayValue(selected.attr("data-value")); //requirement to invoke _SetDisplayValue since _SetValue doesn't invoke it
this._SetValue(selected.attr("data-value"), event);
ulElement.attr("aria-activedescendant", selected[0].id);
this.element.focus();
if (this._isContainedInDateTimePicker())
{
//when focus is placed on the input, since datePickerComp w/ showOn of focus can display it
this._datePickerComp["widget"].hide();
}
},
/**
* Invoked when blur is triggered of the this.element
*
* @ignore
* @protected
* @param {Event} event
*/
_onBlurHandler : function __onBlurHandler(event)
{
if(this._isIndependentInput())
{
this._superApply(arguments);
}
},
/**
* Shows the timepicker
*
* @expose
* @instance
* @memberof! oj.ojInputTime
*/
show : function __show()
{
if (this.options["disabled"] || this.options["readOnly"])
{
return;
}
if (this._isContainedInDateTimePicker())
{
//need to hide the datePickerComp prior to showing timepicker
this._datePickerComp["widget"].hide();
}
this._generateTime();
var timePickerDisplay = this._timePickerDisplay,
popUpTimePickerDisplay = this._popUpTimePickerDisplay;
//Need to set the width to align with what combobox does
timePickerDisplay.width(this.element.parent().width());
//TODO REMOVE LATER WHEN THE CSS HAS BEEN MODIFIED for oj-listbox-drop, causes the popup to think it's not visible
//due to offsetwidth + offsetheight being 0
timePickerDisplay.css({"position": "relative"});
var rtl = this._IsRTL();
var position = oj.PositionUtils.normalizeHorizontalAlignment({"my" : "start top", "at" : "start bottom", "of" : this.element, "collision" : "fit"}, rtl);
popUpTimePickerDisplay.ojPopup("open", this._triggerIcon, position);
},
/**
* Hides the timepicker
*
* @expose
* @instance
* @memberof! oj.ojInputTime
*/
hide : function __hide()
{
if (this._timepickerShowing())
{
this._popUpTimePickerDisplay.ojPopup("close");
this.element.focus();
}
},
/**
* @expose
* @instance
* @memberof! oj.ojInputTime
*/
refresh : function __refresh()
{
if(this._triggerNode) {
this._triggerNode.find("." + this._TRIGGER_TIME_CLASS).attr("title", this._getTimeTitle());
}
return this._superApply(arguments) || this;
},
/**
* @ignore
* @protected
* @override
* @instance
* @memberof! oj.ojInputTime
*/
_SetDisplayValue : function __setDisplayValue(displayValue)
{
//When not part of datePickerComp or of inline should update input element
if (this._isIndependentInput())
{
this._superApply(arguments);
}
//so this is a change in behavior from original design. Previously it was decided that app developer
//would have to invoke refresh to render the calendar after setting the new value programatically; however now it is
//required to hook it in when _SetDisplayValue is invoked [can't use _SetValue b/c that function is not invoked
//when developer invokes ("option", "value", "..")
if(this._timepickerShowing())
{
this._generateTime();
}
},
/**
* @ignore
* @protected
* @override
* @instance
* @memberof! oj.ojInputTime
*/
_SetValue : function __SetValue(newValue, event, options)
{
if(this._isContainedInDateTimePicker())
{
//never update the model if part of ojInputDateTime. Have ojInputDateTime update the model's value [otherwise 2 updates]
//this is mainly for check of whether the format is correct [i.e when ojInputDateTime is inline], since the value
//is always picked from the ojInputDateTime component
this._super(newValue, null, options);
try{
//since parsing can cause a conversion error [would have been taken care of in the above call]
var parsedNewValue = this._GetConverter()["parse"](newValue),
converterUtils = oj.IntlConverterUtils,
datePickerCompWidget = this._datePickerComp["widget"],
dateTimeValue = datePickerCompWidget.getValueForInputTime(),
dateTimeDate = dateTimeValue ? converterUtils.isoToLocalDate(dateTimeValue) : new Date(),
newValueDate = copyTimeOver(parsedNewValue ? converterUtils.isoToLocalDate(parsedNewValue) : new Date(), new Date(dateTimeDate)),
isoString = converterUtils.dateToLocalIso(newValueDate);
if(dateTimeDate.getTime() == newValueDate.getTime())
{
//need to kick out if _SetValue happened due to Blur w/o changing of value
return;
}
datePickerCompWidget.timeSelected(isoString, event);
}catch(e)
{
}
}
else
{
this._superApply(arguments);
}
},
/**
* Whether the this.element should be wrapped. Function so that additional conditions can be placed
*
* @ignore
* @protected
* @override
* @return {boolean}
*/
_DoWrapElement : function ()
{
return this._isIndependentInput();
},
/**
* Whether the input element of ojInputTime is shared or not [i.e. not part of ojInputDateTime or if it has
* been created by ojInputDateTime that is inline
*
* @ignore
* @return {boolean}
*/
_isIndependentInput : function __isIndependentInput()
{
return !this._isContainedInDateTimePicker() || this._isDatePickerInline();
},
/**
* @protected
* @override
* @return {string}
* @instance
* @memberof! oj.ojInputTime
*/
_GetDefaultStyleClass : function ()
{
return "oj-inputtime";
},
/**
* @ignore
* @protected
* @override
* @instance
* @memberof! oj.ojInputTime
*/
_GetElementValue : function ()
{
return this.options['value'] || "";
},
/**
* Sets up the default dateTimeRange and dateRestriction validators.
*
* @ignore
* @protected
* @override
* @instance
* @memberof! oj.ojInputDate
*/
_GetImplicitValidators : function ()
{
var ret = this._superApply(arguments);
if((this.options['min'] != null || this.options['max'] != null) && !this._isContainedInDateTimePicker())
{
//need to alter how the default validators work as validators are now immutable and to create the implicit validator only
//if independent input [i.e. otherwise have ojInputDateTime take care of it]
this._timePickerDefaultValidators[oj.ValidatorFactory.VALIDATOR_TYPE_DATETIMERANGE] = getImplicitDateTimeRangeValidator(this.options, this._GetConverter());
}
return $.extend(this._timePickerDefaultValidators, ret);
},
/**
* 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.ojInputTime
* @instance
* @protected
* @override
*/
_GetConverter : function ()
{
return this.options['converter'] ?
this._superApply(arguments) :
$["oj"]["ojInputTime"]["prototype"]["options"]["converter"];
},
/**
* This helper function will check if the currently selected time entry is within the view and if not will scroll to it
*
* @private
* @param {Object} node
* @param {boolean} opening
*/
_checkScrollTop : function (node, opening)
{
var container = node.parent();
// If the popup is closed and the reopened restore the scroll position
if (opening && this._timeListBoxScrollTop)
{
$(container).scrollTop(this._timeListBoxScrollTop);
}
var containerTop = $(container).scrollTop();
var containerBottom = containerTop + $(container).height();
var nodeTop = node[0].offsetTop;
var nodeBottom = nodeTop + $(node).height();
if (nodeTop < containerTop)
{
$(container).scrollTop(nodeTop);
}
else if (nodeBottom > containerBottom)
{
$(container).scrollTop(nodeBottom - $(container).height());
}
},
/**
* Whether ojInputTime has been created by ojInputDateTime
*
* @private
*/
_isContainedInDateTimePicker : function __isContainedInDateTimePicker()
{
return this._datePickerComp !== null;
},
/**
* Helper function to determine whether the provided datePickerComp is inline or not
*
* @private
*/
_isDatePickerInline : function __isDatePickerInline()
{
return this._datePickerComp["inline"];
},
/**
* Notifies the component that its subtree has been removed from the document programmatically after the component has
* been created
* @memberof! oj.ojInputTime
* @instance
* @protected
*/
_NotifyDetached: function()
{
this.hide();
},
/**
* Notifies the component that its subtree has been made hidden programmatically after the component has
* been created
* @memberof! oj.ojInputTime
* @instance
* @protected
*/
_NotifyHidden: function()
{
this.hide();
},
/**
* 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.ojInputTime
* @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" ).ojInputTime( "getNodeBySubId", {'subId': 'oj-some-sub-id'} );
*/
getNodeBySubId: function(locator)
{
var node = null,
subId = locator && locator['subId'];
if(subId) {
switch(subId)
{
case "oj-listbox-drop": node = this._timePickerDisplay[0]; break;
case "oj-inputdatetime-time-icon": node = $(".oj-inputdatetime-time-icon", this._triggerNode)[0]; break;
case "oj-inputdatetime-time-input": node = this.element[0]; break;
default: node = null;
}
}
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.ojInputTime
* @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, ojInputTime, etc.
* var subId = $( ".selector" ).ojFoo( "getSubIdByNode", nodeInsideComponent );
*/
getSubIdByNode: function(node)
{
var timeIcon = $(".oj-inputdatetime-time-icon", this._triggerNode),
subId = null;
if(node === this._timePickerDisplay[0])
{
subId = "oj-listbox-drop";
}
else if(node === timeIcon[0])
{
subId = "oj-inputdatetime-time-icon";
}
else if(node === this.element[0])
{
subId = "oj-inputdatetime-time-input";
}
return subId || this._superApply(arguments);
},
/**
* Returns the root node
*
* @expose
* @instance
* @memberof! oj.ojInputTime
*/
widget : function __widget()
{
return this._isIndependentInput() ? this._super() : this._datePickerComp["widget"].widget();
}
});
////////////////// SUB-IDS //////////////////
/**
* <p>Sub-ID for the ojInputTime component's input element.
*
* <p>See the <a href="#getNodeBySubId">getNodeBySubId</a> and
* <a href="#getSubIdByNode">getSubIdByNode</a> methods for details.
*
* @ojsubid
* @member
* @name oj-inputdatetime-time-input
* @memberof oj.ojInputTime
* @instance
*
* @example <caption>Get the node for the input element:</caption>
* var node = $( ".selector" ).ojInputTime( "getNodeBySubId", {'subId': 'oj-inputdatetime-time-input'} );
*/
/**
* <p>Sub-ID for the time icon that triggers the time drop down display.
*
* <p>See the <a href="#getNodeBySubId">getNodeBySubId</a> and
* <a href="#getSubIdByNode">getSubIdByNode</a> methods for details.
*
* @ojsubid
* @member
* @name oj-inputdatetime-time-icon
* @memberof oj.ojInputTime
* @instance
*
* @example <caption>Get the time icon that triggers the time drop down display:</caption>
* // Foo is ojInputTime or ojInputDateTime.
* var node = $( ".selector" ).ojFoo( "getNodeBySubId", {'subId': 'oj-inputdatetime-time-icon'} );
*/
/**
* <p>Sub-ID for the time drop down div container.
*
* <p>See the <a href="#getNodeBySubId">getNodeBySubId</a> and
* <a href="#getSubIdByNode">getSubIdByNode</a> methods for details.
*
* @ojsubid
* @member
* @name oj-listbox-drop
* @memberof oj.ojInputTime
* @instance
*
* @example <caption>Get the time drop down div container:</caption>
* // Foo is ojInputTime, ojInputDateTime, etc.
* var node = $( ".selector" ).ojFoo( "getNodeBySubId", {'subId': 'oj-listbox-drop'} );
*/
Source: src/main/javascript/oracle/oj/ojdatetimepicker/ojtimepicker.js
Oracle® JavaScript Extension Toolkit (JET)
1.1.2
E65298-01