DateInput

function
 DateInput() 

Option name Type Description
el Element
params Object

DateInput constructor.

var DateInput = function(el, params) {

  if (!el) {
    return;
  }

  this._setParams(this.defaults, true);
  this._cacheElements(el);
  this._bindEventListenerCallbacks();
  this._setParams(params || {});
  this._parseParams();
  this._convertLabel();
  this._initializeInputs();
  this._updateClass();
  this._addEventListeners();
};

DateInput.prototype = {

_setParams

property
 _setParams 

Include common functionality.

_setParams: Base.setParams,
_toggleClass: Base.toggleClass,
_removeClass: Base.removeClass,
_addClass: Base.addClass,
_hasClass: Base.hasClass,
_getChildIndex: Base.getChildIndex,
_appendChildren: Base.appendChildren,
_triggerEvent: Base.triggerEvent,
_copyAttributes: Base.copyAttributes,
_getElementMatchingParent: Base.getElementMatchingParent,

setError

property
 setError 

Inherit functionality from TextInput.

setError: TextInput.prototype.setError,
clearError: TextInput.prototype.clearError,
setWarning: TextInput.prototype.setWarning,
clearWarning: TextInput.prototype.clearWarning,
setSuccess: TextInput.prototype.setSuccess,
clearSuccess: TextInput.prototype.clearSuccess,
clearMessages: TextInput.prototype.clearMessages,
setMessage: TextInput.prototype.setMessage,
_showMessage: TextInput.prototype._showMessage,
_hideMessage: TextInput.prototype._hideMessage,
_isMessageVisible: TextInput.prototype._isMessageVisible,

_whitelistedParams

property
 _whitelistedParams 

Whitelisted parameters which can be set on construction.

_whitelistedParams: ['onChange', 'onFocus', 'onBlur', 'isTypeahead', 'isSelect', 'format', 'textFormat', 'showDateAsText'],

defaults

property
 defaults 

Default values for internal properties we will be setting.
These are set on each construction so we don't leak properties
into the prototype chain.

defaults: {
  el: null,
  inputEl: null,
  messageEl: null,
  toggleEl: null,
  inFocus: null,
  isActive: null,
  isSelect: null,
  isTypeahead: null,
  typeaheads: null,
  typeaheadEls: null,
  selects: null,
  selectEls: null,
  format: null,
  parsedFormat: null,
  showDateAsText: null,
  runningTypeaheads: false,
  textFormat: null,
  onChange: null,
  onFocus: null,
  onBlur: null,
  _hasFocus: false,
  _pauseInputChange: false,
  _onClickBound: null,
  _onPieceChangeBound: null,
  _onTypeaheadFocusBound: null,
  _onTypeaheadBlurBound: null,
  _onTypeaheadBackspaceBound: null,
  _onTypeaheadEndBound: null,
  _onInputChangeBound: null
},

show

method
 show() 

Show the input by adding the active state and setting character counts (if necessary).

show: function() {

  if (!this.isActive) {
    this._runTypeaheads();
    this.isActive = true;
    this._updateClass();
  }
},

hide

method
 hide() 

Hide the input by removing the active state.

hide: function() {
  this.isActive = false;
  this._updateClass();
},

remove

method
 remove() 

Augment default remove call w/ helper cleanup.

remove: function() {

  Base.remove.apply(this, arguments);

  // Remove text input
  this.textInput.remove();
  delete this.textInput;

  // Remove typeaheads
  if (this.typeaheads) {
    for (var i in this.typeaheads) {
      this.typeaheads[i].remove();
    }
  }

  // Remove select inputs
  if (this.selectEls) {
    this.selectEls.forEach(this._removeSelectPiece.bind(this));
  }
},

setValue

method
 setValue() 

Option name Type Description
values Object

Given an object with day, month and year, set the value of the input.

setValue: function(values) {

  values = values || {
    day: '',
    month: '',
    year: ''
  };
  var i;
  var hadValue;

  for (i in this.typeaheads) {
    if (values[i] !== undefined) {
      this.typeaheads[i].setValue(values[i]);
      hadValue = hadValue || (values[i] ? true : false);
    }
  }

  for (i in this.selects) {
    if (values[i] !== undefined) {
      this.selects[i].setValue(values[i]);
      hadValue = hadValue || (values[i] ? true : false);
    }
  }

  if (!this.isActive && hadValue) {
    this.isActive = true;
  }

  this._padTypeaheads();
  this._updateClass();
  this.updateInput();
},

validate

method
 validate() 

Validate the date values.

validate: function() {

  if (this.isTypeahead) {
    this._validateTypeaheads();
  }
  else if (this.isSelect) {
    this._validateSelects();
  }
},

updateInput

method
 updateInput() 

Update the input values to match the typeaheads.

updateInput: function() {

  var inputs;

  if (this.isTypeahead && this.typeaheads) {
    inputs = this.typeaheads;
  }
  else if (this.isSelect) {
    inputs = this.selects;
  }

  if (inputs) {

    var day = (inputs.day && inputs.day.getValue(true)) || 0;
    var month = (inputs.month && inputs.month.getValue(true)) || 0;
    var year = (inputs.year && inputs.year.getValue(true)) || 0;

    var val = this.inputEl.value;

    this.inputEl.value = [day, month, year].indexOf(0) === -1 ? padNumber(year, 4) + '-' + padNumber(month, 2) + '-' + padNumber(day, 2) : '';

    if (val !== this.inputEl.value) {
      this._pauseInputChange = true;
      this._triggerEvent(this.inputEl, 'change');
      (this.onChange || noop)(this.inputEl.value, this.inputEl);
      this._pauseInputChange = false;
    }

  }
},

focus

method
 focus() 

Option name Type Description
i Number
character String

Optional A character to add

Move the focus to a typeahead element.

focus: function(i, character) {

  if (!this.isActive || !this.inFocus) {
    return;
  }

  var index = this.typeaheadEls.indexOf(this.inFocus.typeahead.el);
  var sib = this.typeaheadEls[index + i];
  var typeahead;

  // If we were passed a character to prepend, find the typeahead for this element
  if (character) {
    typeahead = this._getTypeaheadByElement(sib);
    if (typeahead) {
      typeahead.typeahead.addCharacterAtIndex(character, 0);
    }
  }

  if (!sib) {
    return false;
  }

  var sibInput = sib.querySelector('input');

  if (sibInput) {
    sibInput.focus();

    // If we have a typeahead (because we needed to prepend a character), move the caret.
    if (typeahead) {
      typeahead.typeahead.moveCaret(1);
    }
  }

  return true;
},

focusNext

method
 focusNext() 

Option name Type Description
character String

Optional A character to add

Move the focus to the next element.

focusNext: function(character) {
  if (this.focus(1, character)) {
    if (this.inFocus && !character)
      this.inFocus.typeahead.moveCaretToStart();
  }
},

focusPrevious

method
 focusPrevious() 

Option name Type Description
character String

Optional A character to add

Move the focus to the next element.

focusPrevious: function(character) {
  if (this.focus(-1, character)) {
    if (this.inFocus)
      this.inFocus.typeahead.moveCaretToEnd();
  }
},

hasPartialValue

method
 hasPartialValue() 

Do we have any values?

hasPartialValue: function() {

  var i;

  for (i in this.typeaheads) {
    if (this.typeaheads[i].getValue()) {
      return true;
    }
  }

  for (i in this.selects) {
    if (this.selects[i].getValue()) {
      return true;
    }
  }

  return false;
},

_cacheElements

method
 _cacheElements() 

Option name Type Description
el Element

Store a reference to the needed elements.

_cacheElements: function(el) {

  this.el = el;

  this.inputEl = this.el.querySelector('[type="date"]');

  if (!this.inputEl) {
    throw new Error('No <input type="date"> element present in date input container!', this.el);
  }

  this.toggleEl = this.el.querySelector('.spark-date__toggle');

  this.messageEl = this.el.querySelector('.spark-input__message') || document.createElement('span');
},

_parseParams

method
 _parseParams() 

Parse parameters from the elements.

_parseParams: function() {

  this.isActive = this.isActive === null ? (this.inputEl.value ? true : false) : this.isActive;
  this.isSelect = this.isSelect === null ? (this._hasClass(this.el, 'spark-date--select') ? true : false) : this.isSelect;
  this.isTypeahead = this.isTypeahead === null ? (!this.isSelect ? true : false) : this.isTypeahead;
  this.format = this.format === null ? (this.inputEl.getAttribute('data-format') ? this.inputEl.getAttribute('data-format') : 'MM-DD-YYYY') : this.format;
  this.textFormat = this.textFormat === null ? (this.inputEl.getAttribute('data-text-format') ? this.inputEl.getAttribute('data-text-format') : 'MM DD YYYY') : this.textFormat;
  this.showDateAsText = this.showDateAsText === null ? this.inputEl.getAttribute('data-show-date-as-text') !== null : this.showDateAsText;

  this.parsedFormat = parseDateFormat(this.format);
  this.parsedTextFormat = parseDateFormat(this.textFormat);
  this.min = this.inputEl.getAttribute('min') && parsedDomFormat.getValues(this.inputEl.getAttribute('min'));
  this.max = this.inputEl.getAttribute('max') && parsedDomFormat.getValues(this.inputEl.getAttribute('max'));
},

_initializeInputs

method
 _initializeInputs() 

Setup the proper inputs. This could mean creating a typeahead, or creating selects.

_initializeInputs: function() {

  // @todo: remove this when Android fixes its keyup/keypress/keydown bug
  // http://stackoverflow.com/questions/17139039/keycode-is-always-zero-in-chrome-for-android
  if (this.isTypeahead) {
    this._initializeInputPieces();
    this._runTypeaheads();
  }
  else if (this.isSelect) {
    this._removeClass(this.el, 'spark-input');
    this._initializeInputPieces();
  }
},

_initializeInputPieces

method
 _initializeInputPieces() 

Replace the date input with a group of typeaheads or select inputs.
Keep the date input around and store the typeahead data in there in an ISO date format.

_initializeInputPieces: function() {

  // Hide the original element. This will be updated as the typeahead values change
  this.inputEl.style.display = 'none';

  var els = [];
  var label;

  // Create a new typeahead for each part of the parsed format. Also add placeholder elements.
  this.parsedFormat.parts.forEach(function(part) {
    // Something weird with Node that makes us have to specify what `this` is here.
    (this.isTypeahead ? this._initializeTypeaheadPiece : this._initializeSelectPiece).call(this, els, part);
  }.bind(this));

  // Create a holder for all the pieces
  var piecesEl = document.createElement('span');
  piecesEl.className = this.isTypeahead ? 'spark-input__fields' : 'spark-select-group';

  // Add all the necessary elements
  this._appendChildren(piecesEl, els);

  // If this is a select group, move the label element.
  if (this.isSelect && (label = this.el.querySelector('.spark-label'))) {
    piecesEl.appendChild(label);
  }

  // Add the pieces holder
  this.el.insertBefore(piecesEl, this.inputEl);

  // Set the value
  if (this.inputEl.value) {
    this.setValue(parsedDomFormat.getValues(this.inputEl.value));
    this.isActive = true;
  }
},

_initializeTypeaheadPiece

method
 _initializeTypeaheadPiece() 

Option name Type Description
els Array
part Object

Create a typeahead or placeholder piece.

_initializeTypeaheadPiece: function(els, part) {

  this.typeaheads = this.typeaheads || {};
  this.typeaheadEls = this.typeaheadEls || [];

  var el;

  switch (part.name) {
    case 'day':
    case 'month':
    case 'year':
      this.typeaheads[part.name] = new DateTypeahead({
        type: part.name,
        length: part.length,
        placeholder: part.value,
        onFocus: this._onTypeaheadFocusBound,
        onBlur: this._onTypeaheadBlurBound,
        onChange: this._onPieceChangeBound,
        onBackspace: this._onTypeaheadBackspaceBound,
        onEnd: this._onTypeaheadEndBound
      });
      el = this.typeaheads[part.name].typeahead.el;
      this.typeaheadEls.push(el);
      break;
    default:
      el = document.createElement('span');
      el.innerHTML = part.value;
      el.className = 'spark-input__divider';
      break;
  }

  els.push(el);
},

_initializeSelectPiece

method
 _initializeSelectPiece() 

Replace the date input with three date dropdowns. Keep the date input around and store the
select data in there.

_initializeSelectPiece: function(els, part) {

  this.selects = this.selects || {};
  this.selectEls = this.selectEls || [];

  if (['day', 'month', 'year'].indexOf(part.name) === -1) {
    return;
  }

  var el;

  this.selects[part.name] = new DateSelect({
    type: part.name,
    onChange: this._onPieceChangeBound
  });
  el = this.selects[part.name].select.el;

  els.push(el);
  this.selectEls.push(el);
},

_convertLabel

method
 _convertLabel() 

If our element is a label, convert it to a div so that
we are semantically correct. Can't have more than one
input inside of a label!

_convertLabel: function() {

  if (this.isTypeahead || this.el.nodeName.toLowerCase() !== 'label') {
    return;
  }

  var newEl = document.createElement('fieldset');

  this._copyAttributes(this.el, newEl);
  this._appendChildren(newEl, this.el.children);

  if (this.el.parentNode) {
    this.el.parentNode.replaceChild(newEl, this.el);
  }

  this.el = newEl;
},

_validateTypeaheads

method
 _validateTypeaheads() 

Validate the typeahead values.

_validateTypeaheads: function() {

  if (!this.typeaheads) {
    return;
  }

  var month = this.typeaheads.month ? this.typeaheads.month.getValue(true) : null;
  var year = this.typeaheads.year ? this.typeaheads.year.getValue(true) : null;

  var maxDay = (month && new Date(year !== null ? year : new Date().getFullYear(), month, 0).getDate()) || this._getMaxDaysInMonth(month);
  var day = this.typeaheads.day ? this.typeaheads.day.getValue(true) : null;

  if (maxDay < day) {
    this.typeaheads.day.setValue(maxDay);
    this.updateInput();
  }
},

_validateTypeaheadBounds

method
 _validateTypeaheadBounds() 

Validate the boundaries of the typeahead values relative to the min and max values.

_validateTypeaheadBounds: function() {

  var year = this.typeaheads.year ? this.typeaheads.year.getValue(true) : null;
  var month = this.typeaheads.month ? this.typeaheads.month.getValue(true) : null;
  var day = this.typeaheads.day ? this.typeaheads.day.getValue(true) : null;

  if (!year || !month || !day) {
    return;
  }

  var date = new Date(year, month - 1, day);
  var set = '';

  if (this.min && date < new Date(this.min.year, this.min.month - 1, this.min.day)) {
    set = 'min';
  }
  else if (this.max && date > new Date(this.max.year, this.max.month - 1, this.max.day)) {
    set = 'max';
  }

  if (set) {
    this.typeaheads.year.setValue(padNumber(this[set].year, this.typeaheads.year.typeahead.format.length));
    this.typeaheads.month.setValue(padNumber(this[set].month, this.typeaheads.month.typeahead.format.length));
    this.typeaheads.day.setValue(padNumber(this[set].day, this.typeaheads.day.typeahead.format.length));
    this.updateInput();
  }
},

_padTypeaheads

method
 _padTypeaheads() 

Pad the typeahead input values.

_padTypeaheads: function() {

  if (this._pauseInputChange) return;

  this._pauseInputChange = true;

  for (var i in this.typeaheads) {
    this._padTypeahead(this.typeaheads[i]);
  }

  this._pauseInputChange = false;
},

_padTypeahead

method
 _padTypeahead() 

Option name Type Description
typeahead Typeahead

Pad the typeahead input values.

_padTypeahead: function(typeahead) {

  var value = typeahead.getValue();

  if (value) {
    var padded = padNumber(value, typeahead.typeahead.format.length);
    if (value !== padded) typeahead.setValue(padNumber(value, typeahead.typeahead.format.length));
  }
},

_hasTypeaheadValue

method
 _hasTypeaheadValue() 

Do any of the typeaheads have a value?

_hasTypeaheadValue: function() {

  for (var i in this.typeaheads) {
    if (this.typeaheads[i].getValue(true)) {
      return true;
    }
  }

  return false;
},

_validateSelects

method
 _validateSelects() 

Validate select input values.

_validateSelects: function() {

  if (!this.selects) {
    return;
  }

  var month = this.selects.month ? this.selects.month.getValue(true) : null;
  var year = this.selects.year ? this.selects.year.getValue(true) : null;

  var maxDay = (month && new Date(year !== null ? year : new Date().getFullYear(), month, 0).getDate()) || this._getMaxDaysInMonth(month);
  var day = this.selects.day ? this.selects.day.getValue(true) : null;

  this.selects.day.setOptions({
    max: maxDay
  });
  if (maxDay < day) {
    this.selects.day.setValue(maxDay);
  }

  this.updateInput();
},

_getMaxDaysInMonth

method
 _getMaxDaysInMonth() 

Option name Type Description
month Number

The month's number. 1-12.

return Number

The maximum number of days. 28-31.

Get the maximum number of days for a given month.

_getMaxDaysInMonth: function(month) {
  if (month === 2) return 29;
  else if ([4, 6, 9, 11].indexOf(month) !== -1) return 30;
  return 31;
},

_bindEventListenerCallbacks

method
 _bindEventListenerCallbacks() 

Create bound versions of event listener callbacks and store them.
Otherwise we can't unbind from these events later because the
function signatures won't match.

_bindEventListenerCallbacks: function() {
  this._onClickBound = this._onClick.bind(this);
  this._onTypeaheadFocusBound = this._onTypeaheadFocus.bind(this);
  this._onTypeaheadBlurBound = this._onTypeaheadBlur.bind(this);
  this._onPieceChangeBound = this._onPieceChange.bind(this);
  this._onTypeaheadBackspaceBound = this._onTypeaheadBackspace.bind(this);
  this._onTypeaheadEndBound = this._onTypeaheadEnd.bind(this);
  this._onInputChangeBound = this._onInputChange.bind(this);
  this._onVisibleChildrenBound = this._onVisibleChildren.bind(this);
},

_addEventListeners

method
 _addEventListeners() 

Add event listeners.

_addEventListeners: function() {
  this.el.addEventListener('click', this._onClickBound);
  this.inputEl.addEventListener('change', this._onInputChangeBound);
  document.addEventListener('spark.visible-children', this._onVisibleChildrenBound, true);
},

_removeEventListeners

method
 _removeEventListeners() 

Remove event listeners.

_removeEventListeners: function() {
  this.el.removeEventListener('click', this._onClickBound);
  this.inputEl.removeEventListener('change', this._onInputChangeBound);
  document.removeEventListener('spark.visible-children', this._onVisibleChildrenBound, true);
},

_onVisibleChildren

method
 _onVisibleChildren() 

Option name Type Description
e Object

Handle the spark.visible-children event

_onVisibleChildren: function(e) {
  if (e.target.contains(this.el)) {
    window.setTimeout(function() {
      this.update();
    }.bind(this), 0);
  }
},

update

method
 update() 

Resize the elements, to account for any changed display property

update: function() {
  this._runTypeaheads();
},

_runTypeaheads

method
 _runTypeaheads() 

Run all typeaheads so they have placeholder values.

_runTypeaheads: function() {

  // Make sure we don't get into an infinite loop. Even though the logic
  // in the typeaheads should be stopping this from happening, there is
  // something in Safari where the focus and blur events fire in different
  // order than other browsers so those failsafes do not work.
  if (this.runningTypeaheads) {
    return;
  }

  this.runningTypeaheads = true;

  if (this.inFocus) {
    this.inFocus.pause();
  }

  for (var i in this.typeaheads) {
    if (this.typeaheads[i] !== this.inFocus) {
      this.typeaheads[i].run();
    }
  }

  if (this.inFocus) {
    this.inFocus.resume();
    this.inFocus.run();
  }

  this.runningTypeaheads = false;
},

_updateClass

method
 _updateClass() 

Update the active and focus classes.

_updateClass: function() {
  this._toggleClass(this.el, 'active', this.isActive);
  this._toggleClass(this.el, 'has-partial-value', this.hasPartialValue());
  this._toggleClass(this.el, 'focus', this.inFocus ? true : false);
},

_getTypeaheadByElement

method
 _getTypeaheadByElement() 

Option name Type Description
el Element
return Object

Get the typeahead that corresponds to the given element.

_getTypeaheadByElement: function(el) {
  for (var i in this.typeaheads) {
    if (this.typeaheads[i].typeahead.el === el) {
      return this.typeaheads[i];
    }
  }
},

_showDateText

method
 _showDateText() 

Show the date as text.

_showDateText: function() {

  var text = this._getDateText();

  if (!text || !this.showDateAsText) {
    return;
  }

  if (!this.dateTextEl) {
    this._createDateTextEl();
  }

  this.dateTextEl.innerHTML = text;
  this.dateTextEl.style.display = '';
},

_hideDateText

method
 _hideDateText() 

Hide the date as text.

_hideDateText: function() {

  if (!this.showDateAsText || !this.dateTextEl) {
    return;
  }

  this.dateTextEl.style.display = 'none';
},

_createDateTextEl

method
 _createDateTextEl() 

Create the date text element.

_createDateTextEl: function() {

  var el = document.createElement('div');
  el.className = 'spark-input__overlay';
  el.style.display = 'none';

  this.el.appendChild(el);
  this.dateTextEl = el;
},

_getDateText

method
 _getDateText() 

Get the date as text.

_getDateText: function() {

  var parts = this.parsedTextFormat.parts;
  var i = 0;
  var len = parts.length;
  var str = '';
  var isValid = true;
  var val;

  for(; i < len; i++) {

    val = this.typeaheads[parts[i].name] && this.typeaheads[parts[i].name].getValue();

    switch(parts[i].name) {
      case 'month':
        str += date.getMonthNameShort(val);
        if (!val) {
          isValid = false;
          break;
        }
        break;
      case 'day':
      case 'year':
        str += val;
        if (!val) {
          isValid = false;
          break;
        }
        break;
      default:
        str += parts[i].value;
        break;
    }
  }

  return (isValid ? str : false);
},

_onPieceChange

method
 _onPieceChange() 

Option name Type Description
val Number
typeahead Object

When the value of a typeahead or select changes, validate.

_onPieceChange: function() {

  this.validate();

  if (this.isTypeahead && this.showDateAsText && !this._hasFocus) {

    if (this._showTextTimer) {
      clearTimeout(this._showTextTimer);
    }

    this._showTextTimer = setTimeout(function() {
      this._showDateText();
    }.bind(this), 0);
  }
},

_onTypeaheadFocus

method
 _onTypeaheadFocus() 

Option name Type Description
val Number
typeahead Object

When the typeahead gains focus.

_onTypeaheadFocus: function(val, typeahead) {

  if (this.runningTypeaheads) return;

  this._hideDateText();

  if (!this._hasFocus) {
    this._hasFocus = true;
    (this.onFocus || noop)(this, this.inputEl.value);
  }

  this._triggerEvent(this.inputEl, 'focus');
  this.inFocus = typeahead;
  this.show();
  this._updateClass();

  if (this._blurTimer) {
    clearTimeout(this._blurTimer);
    this._blurTimer = null;
  }
},

_onTypeaheadBlur

method
 _onTypeaheadBlur() 

Option name Type Description
val Number
typeahead Object

When the typeahead loses focus, make sure numbers are padded properly.

_onTypeaheadBlur: function(val, typeahead) {

  if (this.runningTypeaheads) return;

  this.inFocus = null;

  this._padTypeahead(typeahead);
  this.updateInput();
  this._updateClass();

  if (!this.inputEl.value && !this._hasTypeaheadValue()) {
    this.hide();
  }
  else {
    this._validateTypeaheadBounds();
  }

  this._blurTimer = setTimeout(function() {
    this._hasFocus = false;
    (this.onBlur || noop)(this, this.inputEl.value);
    this._showDateText();
  }.bind(this), 1);
},

_onTypeaheadBackspace

method
 _onTypeaheadBackspace() 

Option name Type Description
val Number
typeahead Object

When the typeahead fires a backspace event, move back to the previous input.

_onTypeaheadBackspace: function() {
  this.focusPrevious();
},

_onTypeaheadEnd

method
 _onTypeaheadEnd() 

Option name Type Description
typeahead Object
character String

Optional

When the typeahead is at its maximum length and the caret is at the end,
focus on the next input field.

_onTypeaheadEnd: function(typeahead, character) {
  this.focusNext(character);
},

_onInputChange

method
 _onInputChange() 

Option name Type Description
e Object

When the input that corresponds to this instance changes. Allows us to listen
and respond to changes made by other components (Calendar Popover, for example).

_onInputChange: function(e) {

  if (this.isTypeahead) {
    this.isActive = e.target.value ? true : false;
    this._updateClass();
  }

  if (this._pauseInputChange) return;
  this.setValue(parsedDomFormat.getValues(e.target.value));
  (this.onChange || noop)(this.inputEl.value, this.inputEl);
},

_onClick

method
 _onClick() 

Option name Type Description
e Object

When the input group is clicked, focus on the first typeahead
if we don't already have focus.

_onClick: function(