text: /**
   * Provides a time input field with a time dropdown and automatic time validation.
   *
   * This field recognizes and uses JavaScript Date objects as its main {@link #value} type (only the time portion of the
   * date is used; the month/day/year are ignored). In addition, it recognizes string values which are parsed according to
   * the {@link #format} and/or {@link #altFormats} configs. These may be reconfigured to use time formats appropriate for
   * the user's locale.
   *
   * The field may be limited to a certain range of times by using the {@link #minValue} and {@link #maxValue} configs,
   * and the interval between time options in the dropdown can be changed with the {@link #increment} config.
   *
   * Example usage:
   *
   *     @example
   *     Ext.create('Ext.form.Panel', {
   *         title: 'Time Card',
   *         width: 300,
   *         bodyPadding: 10,
   *         renderTo: Ext.getBody(),
   *         items: [{
   *             xtype: 'timefield',
   *             name: 'in',
   *             fieldLabel: 'Time In',
   *             minValue: '6:00 AM',
   *             maxValue: '8:00 PM',
   *             increment: 30,
   *             anchor: '100%'
   *         }, {
   *             xtype: 'timefield',
   *             name: 'out',
   *             fieldLabel: 'Time Out',
   *             minValue: '6:00 AM',
   *             maxValue: '8:00 PM',
   *             increment: 30,
   *             anchor: '100%'
   *        }]
   *     });
   */
  Ext.define('Ext.form.field.Time', {
      extend:'Ext.form.field.ComboBox',
      alias: 'widget.timefield',
      requires: ['Ext.form.field.Date', 'Ext.picker.Time', 'Ext.view.BoundListKeyNav', 'Ext.Date'],
      alternateClassName: ['Ext.form.TimeField', 'Ext.form.Time'],
  
      /**
       * @cfg {String} [triggerCls='x-form-time-trigger']
       * An additional CSS class used to style the trigger button. The trigger will always get the {@link #triggerBaseCls}
       * by default and triggerCls will be **appended** if specified.
       */
      triggerCls: Ext.baseCSSPrefix + 'form-time-trigger',
  
      /**
       * @cfg {Date/String} minValue
       * The minimum allowed time. Can be either a Javascript date object with a valid time value or a string time in a
       * valid format -- see {@link #format} and {@link #altFormats}.
       */
  
      /**
       * @cfg {Date/String} maxValue
       * The maximum allowed time. Can be either a Javascript date object with a valid time value or a string time in a
       * valid format -- see {@link #format} and {@link #altFormats}.
       */
  
      //<locale>
      /**
       * @cfg {String} minText
       * The error text to display when the entered time is before {@link #minValue}.
       */
      minText : "The time in this field must be equal to or after {0}",
      //</locale>
  
      //<locale>
      /**
       * @cfg {String} maxText
       * The error text to display when the entered time is after {@link #maxValue}.
       */
      maxText : "The time in this field must be equal to or before {0}",
      //</locale>
  
      //<locale>
      /**
       * @cfg {String} invalidText
       * The error text to display when the time in the field is invalid.
       */
      invalidText : "{0} is not a valid time",
      //</locale>
  
      //<locale>
      /**
       * @cfg {String} [format=undefined]
       * The default time format string which can be overriden for localization support. The format must be valid
       * according to {@link Ext.Date#parse}.
       *
       * Defaults to `'g:i A'`, e.g., `'3:15 PM'`. For 24-hour time format try `'H:i'` instead.
       */
      format : "g:i A",
      //</locale>
  
      //<locale>
      /**
       * @cfg {String} [submitFormat=undefined]
       * The date format string which will be submitted to the server. The format must be valid according to
       * {@link Ext.Date#parse}.
       *
       * Defaults to {@link #format}.
       */
      //</locale>
  
      //<locale>
      /**
       * @cfg {String} altFormats
       * Multiple date formats separated by "|" to try when parsing a user input value and it doesn't match the defined
       * format.
       */
      altFormats : "g:ia|g:iA|g:i a|g:i A|h:i|g:i|H:i|ga|ha|gA|h a|g a|g A|gi|hi|gia|hia|g|H|gi a|hi a|giA|hiA|gi A|hi A",
      //</locale>
  
      /**
       * @cfg {Number} increment
       * The number of minutes between each time value in the list.
       */
      increment: 15,
  
      /**
       * @cfg {Number} pickerMaxHeight
       * The maximum height of the {@link Ext.picker.Time} dropdown.
       */
      pickerMaxHeight: 300,
  
      /**
       * @cfg {Boolean} selectOnTab
       * Whether the Tab key should select the currently highlighted item.
       */
      selectOnTab: true,
      
      /**
       * @cfg {Boolean} [snapToIncrement=false]
       * Specify as `true` to enforce that only values on the {@link #increment} boundary are accepted.
       */
      snapToIncrement: false,
  
      /**
       * @private
       * This is the date to use when generating time values in the absence of either minValue
       * or maxValue.  Using the current date causes DST issues on DST boundary dates, so this is an
       * arbitrary "safe" date that can be any date aside from DST boundary dates.
       */
      initDate: '1/1/2008',
      initDateFormat: 'j/n/Y',
      
      ignoreSelection: 0,
  
      queryMode: 'local',
  
      displayField: 'disp',
  
      valueField: 'date',
  
      initComponent: function() {
          var me = this,
              min = me.minValue,
              max = me.maxValue;
          if (min) {
              me.setMinValue(min);
          }
          if (max) {
              me.setMaxValue(max);
          }
          me.displayTpl = new Ext.XTemplate(
              '<tpl for=".">' +
                  '{[typeof values === "string" ? values : this.formatDate(values["' + me.displayField + '"])]}' +
                  '<tpl if="xindex < xcount">' + me.delimiter + '</tpl>' +
              '</tpl>', {
              formatDate: Ext.Function.bind(me.formatDate, me)
          });
          this.callParent();
      },
  
      /**
       * @private
       */
      transformOriginalValue: function(value) {
          if (Ext.isString(value)) {
              return this.rawToValue(value);
          }
          return value;
      },
  
      /**
       * @private
       */
      isEqual: function(v1, v2) {
          return Ext.Date.isEqual(v1, v2);
      },
  
      /**
       * Replaces any existing {@link #minValue} with the new time and refreshes the picker's range.
       * @param {Date/String} value The minimum time that can be selected
       */
      setMinValue: function(value) {
          var me = this,
              picker = me.picker;
          me.setLimit(value, true);
          if (picker) {
              picker.setMinValue(me.minValue);
          }
      },
  
      /**
       * Replaces any existing {@link #maxValue} with the new time and refreshes the picker's range.
       * @param {Date/String} value The maximum time that can be selected
       */
      setMaxValue: function(value) {
          var me = this,
              picker = me.picker;
          me.setLimit(value, false);
          if (picker) {
              picker.setMaxValue(me.maxValue);
          }
      },
  
      /**
       * @private
       * Updates either the min or max value. Converts the user's value into a Date object whose
       * year/month/day is set to the {@link #initDate} so that only the time fields are significant.
       */
      setLimit: function(value, isMin) {
          var me = this,
              d, val;
          if (Ext.isString(value)) {
              d = me.parseDate(value);
          }
          else if (Ext.isDate(value)) {
              d = value;
          }
          if (d) {
              val = Ext.Date.clearTime(new Date(me.initDate));
              val.setHours(d.getHours(), d.getMinutes(), d.getSeconds(), d.getMilliseconds());
          }
          // Invalid min/maxValue config should result in a null so that defaulting takes over
          else {
              val = null;
          }
          me[isMin ? 'minValue' : 'maxValue'] = val;
      },
  
      rawToValue: function(rawValue) {
          return this.parseDate(rawValue) || rawValue || null;
      },
  
      valueToRaw: function(value) {
          return this.formatDate(this.parseDate(value));
      },
  
      /**
       * Runs all of Time's validations and returns an array of any errors. Note that this first runs Text's validations,
       * so the returned array is an amalgamation of all field errors. The additional validation checks are testing that
       * the time format is valid, that the chosen time is within the {@link #minValue} and {@link #maxValue} constraints
       * set.
       * @param {Object} [value] The value to get errors for (defaults to the current field value)
       * @return {String[]} All validation errors for this field
       */
      getErrors: function(value) {
          var me = this,
              format = Ext.String.format,
              errors = me.callParent(arguments),
              minValue = me.minValue,
              maxValue = me.maxValue,
              date;
  
          value = me.formatDate(value || me.processRawValue(me.getRawValue()));
  
          if (value === null || value.length < 1) { // if it's blank and textfield didn't flag it then it's valid
               return errors;
          }
  
          date = me.parseDate(value);
          if (!date) {
              errors.push(format(me.invalidText, value, Ext.Date.unescapeFormat(me.format)));
              return errors;
          }
  
          if (minValue && date < minValue) {
              errors.push(format(me.minText, me.formatDate(minValue)));
          }
  
          if (maxValue && date > maxValue) {
              errors.push(format(me.maxText, me.formatDate(maxValue)));
          }
  
          return errors;
      },
  
      formatDate: function() {
          return Ext.form.field.Date.prototype.formatDate.apply(this, arguments);
      },
  
      /**
       * @private
       * Parses an input value into a valid Date object.
       * @param {String/Date} value
       */
      parseDate: function(value) {
          var me = this,
              val = value,
              altFormats = me.altFormats,
              altFormatsArray = me.altFormatsArray,
              i = 0,
              len;
  
          if (value && !Ext.isDate(value)) {
              val = me.safeParse(value, me.format);
  
              if (!val && altFormats) {
                  altFormatsArray = altFormatsArray || altFormats.split('|');
                  len = altFormatsArray.length;
                  for (; i < len && !val; ++i) {
                      val = me.safeParse(value, altFormatsArray[i]);
                  }
              }
          }
  
          // If configured to snap, snap resulting parsed Date to the closest increment.
          if (val && me.snapToIncrement) {
              val = new Date(Ext.Number.snap(val.getTime(), me.increment * 60 * 1000));
          }
          return val;
      },
  
      safeParse: function(value, format){
          var me = this,
              utilDate = Ext.Date,
              parsedDate,
              result = null;
  
          if (utilDate.formatContainsDateInfo(format)) {
              // assume we've been given a full date
              result = utilDate.parse(value, format);
          } else {
              // Use our initial safe date
              parsedDate = utilDate.parse(me.initDate + ' ' + value, me.initDateFormat + ' ' + format);
              if (parsedDate) {
                  result = parsedDate;
              }
          }
          return result;
      },
  
      // @private
      getSubmitValue: function() {
          var me = this,
              format = me.submitFormat || me.format,
              value = me.getValue();
  
          return value ? Ext.Date.format(value, format) : null;
      },
  
      /**
       * @private
       * Creates the {@link Ext.picker.Time}
       */
      createPicker: function() {
          var me = this,
              picker;
  
          me.listConfig = Ext.apply({
              xtype: 'timepicker',
              selModel: {
                  mode: 'SINGLE'
              },
              cls: undefined,
              minValue: me.minValue,
              maxValue: me.maxValue,
              increment: me.increment,
              format: me.format,
              maxHeight: me.pickerMaxHeight
          }, me.listConfig);
          picker = me.callParent();
          me.store = picker.store;
          return picker;
      },
      
      onItemClick: function(picker, record){
          // The selection change events won't fire when clicking on the selected element. Detect it here.
          var me = this,
              selected = picker.getSelectionModel().getSelection();
  
          if (selected.length > 0) {
              selected = selected[0];
              if (selected && Ext.Date.isEqual(record.get('date'), selected.get('date'))) {
                  me.collapse();
              }
          }
      },
  
      /**
       * @private
       * Handles a time being selected from the Time picker.
       */
      onListSelectionChange: function(list, recordArray) {
          if (recordArray.length) {
              var me = this,
                  val = recordArray[0].get('date');
  
              if (!me.ignoreSelection) {
                  me.skipSync = true;
                  me.setValue(val);
                  me.skipSync = false;
                  me.fireEvent('select', me, val);
                  me.picker.clearHighlight();
                  me.collapse();
                  me.inputEl.focus();
              }
          }
      },
  
      /**
       * @private 
       * Synchronizes the selection in the picker to match the current value
       */
      syncSelection: function() {
          var me = this,
              picker = me.picker,
              toSelect,
              selModel,
              value,
              data, d, dLen, rec;
              
          if (picker && !me.skipSync) {
              picker.clearHighlight();
              value = me.getValue();
              selModel = picker.getSelectionModel();
              // Update the selection to match
              me.ignoreSelection++;
              if (value === null) {
                  selModel.deselectAll();
              } else if (Ext.isDate(value)) {
                  // find value, select it
                  data = picker.store.data.items;
                  dLen = data.length;
  
                  for (d = 0; d < dLen; d++) {
                      rec = data[d];
  
                      if (Ext.Date.isEqual(rec.get('date'), value)) {
                         toSelect = rec;
                         break;
                     }
                  }
  
                  selModel.select(toSelect);
              }
              me.ignoreSelection--;
          }
      },
  
      postBlur: function() {
          var me = this,
              val = me.getValue();
  
          me.callParent(arguments);
  
          // Only set the raw value if the current value is valid and is not falsy
          if (me.wasValid && val) {
              me.setRawValue(me.formatDate(val));
          }
      },
  
      setValue: function() {
  
          // Store MUST be created for parent setValue to function
          this.getPicker();
  
          this.callParent(arguments);
      },
  
      getValue: function() {
          return this.parseDate(this.callParent(arguments));
      }
  });
  