Source: src/main/javascript/oracle/oj/ojdatetimepicker/ojtimepicker.js

Oracle® JavaScript Extension Toolkit (JET)
1.1.2

E65298-01

/**
 * 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'} );
 */