Source: inputhandler.js

// Copyright (C) Plan-A Software Ltd. All Rights Reserved.
//
// Written by Kiran Lakhotia <kiran@plan-a-software.co.uk>, 2014
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

'use strict';

goog.provide('plana.ui.ac.InputHandler');

goog.require('goog.Timer');
goog.require('goog.a11y.aria');
goog.require('goog.array');
goog.require('goog.dom.selection');
goog.require('goog.events.BrowserEvent');
goog.require('goog.events.Event');
goog.require('goog.events.EventHandler');
goog.require('goog.events.EventTarget');
goog.require('goog.events.EventType');
goog.require('goog.events.KeyCodes');
goog.require('goog.events.KeyHandler');
goog.require('goog.string');
goog.require('plana.ui.ac.RemoteObject');

/**
 * This class is heavily based on {@link goog.ui.ac.InputHandler} to provide
 * the SelectionHandler interface required by {@link goog.ui.ac.AutoComplete}.
 * It differs in a few ways, chief amongst them that it only supports a single
 * input element instead of multiple ones. Thus, there's no juggling between
 * active elements :)
 * It also only uses a timer to monitor the input if the user non-left-clicked
 * on the input, e.g. to paste text. The monitoring stops as soon as the user
 * left clicks or types a character.
 * We also removed the tight coupling to the {@link goog.ui.ac.AutoComplete}
 * component, by dispatching events from here, rather than keeping a reference
 * to the autocomplete component.
 * Finally, we maintain a map of input entries to their server objects, in case
 * the server returned an object instead of simple strings
 *
 * @constructor
 * @extends {goog.events.EventTarget}
 * @param {!HTMLInputElement} element The input element to use with the
 *     autocomplete control
 * @param {?boolean=} opt_multi Whether to allow multiple entries
 *     (Default: false)
 */
plana.ui.ac.InputHandler = function(element, opt_multi) {
  goog.events.EventTarget.call(this);

  /**
   * Reference to the input element we'er listening to
   * @type {!HTMLInputElement}
   * @private
   */
  this.input_ = element;

  /**
   * The key handler for this input
   * @type {goog.events.KeyHandler}
   * @private
   */
  this.keyHandler_ = new goog.events.KeyHandler(element, false);
  /* set the parent event target so that parents of this class can
   * get notified of key events too
   */
  this.keyHandler_.setParentEventTarget(this);

  /**
   * The event handler to use to listen to key and mouse
   * events
   * @type {goog.events.EventHandler}
   * @private
   */
  this.eventHandler_ = new goog.events.EventHandler(this);

  /**
   * An update timer that is used to watch for changes in
   * the input element that might have been missed by the
   * key event handler. For example, after pasting content
   * via mouse operations
   * @type {?goog.Timer}
   * @private
   */
  this.updateTimer_ = null;

  /**
   * The interval to use for the update timer
   * @type {number}
   * @private
   */
  this.updateInterval_ = 150;

  /**
   * Flag whether a user can enter multiple items
   * @type {boolean}
   * @private
   */
  this.supportsMulti_ = opt_multi || false;

  /**
   * @see http://docs.closure-library.googlecode.com/git-history/7bb23f83ca959ae16e10ebc6734b0ba882629904/class_goog_ui_ac_InputHandler.html
   * The separators used if the input supports multiple entries.
   * Added here because the base class doesn't expose this
   * property
   * @type {string}
   * @private
   */
  this.separators_ = plana.ui.ac.InputHandler.STANDARD_LIST_SEPARATORS;

  /**
   * The regular expression to split the value of the text input
   * @type {RegExp}
   * @private
   */
  this.sepSplitRegEx_ = new RegExp('[' + this.separators_ + ']');

  /**
   * The default character to use for separating tokens in multi mode.
   * This is used in 'autocomplete' mode, i.e. when automatically appending
   * the separator when the user selected a match
   * @type {string}
   * @private
   */
  this.defaultSeparator_ = this.separators_.substring(0, 1);

  /**
   * The regular expression to make sure we have a separator + space
   * for multi token inputs
   * @type {RegExp}
   * @private
   */
  this.formatRegEx_ = new RegExp(this.defaultSeparator_ + '([^\\s])', 'g');

  /**
   * @see http://docs.closure-library.googlecode.com/git-history/7bb23f83ca959ae16e10ebc6734b0ba882629904/class_goog_ui_ac_InputHandler.html
   * If we're in 'multi' mode, does typing a separator force the updating of
   * suggestions?
   * For example, if somebody finishes typing "obama, hillary,", should the
   * last comma trigger updating suggestions in a guaranteed manner? Especially
   * useful when the suggestions depend on complete keywords. Note that
   * "obama, hill" (a leading sub-string of "obama, hillary" will lead to
   * different and possibly irrelevant suggestions.
   * @type {boolean}
   * @private
   */
  this.separatorUpdates_ = true;

  /**
   * @see http://docs.closure-library.googlecode.com/git-history/7bb23f83ca959ae16e10ebc6734b0ba882629904/class_goog_ui_ac_InputHandler.html
   * If we're in 'multi' mode, does typing a separator force the current term
   * to autocomplete?
   * For example, if 'tomato' is a suggested completion and the user has typed
   * 'to,', do we autocomplete to turn that into 'tomato,'?
   * @type {boolean}
   * @private
   */
  this.separatorSelects_ = true;

  /**
   * The previous value of the text input before a key event
   * @type {string}
   * @private
   */
  this.previousValue_ = '';

  /**
   * @see http://docs.closure-library.googlecode.com/git-history/7bb23f83ca959ae16e10ebc6734b0ba882629904/class_goog_ui_ac_InputHandler.html
   * Flag used to indicate that the IME key has been seen and we need to wait
   * for the up event.
   * @type {boolean}
   * @private
   */
  this.isHandlingIME_ = false;

  /**
   * The most recent keycode
   * @type {number}
   * @private
   */
  this.lastKeyCode_ = -1;

  /**
   * This is an ordered list of objects matched by
   * the text in the input
   * @type {Array.<Object|string>}
   * @private
   */
  this.matchedObjects_ = [];

  /**
   * Flag whether we should be case-insenstive when
   * checking for tokens against mapped objects
   * @type {boolean}
   * @private
   */
  this.caseInsensitve_ = false;

  //setup listeners
  this.eventHandler_.listen(this.keyHandler_,
    goog.events.KeyHandler.EventType.KEY,
    this.onKey_, false);
  this.eventHandler_.listen(element,
    goog.events.EventType.KEYUP,
    this.onKeyUp_, false);
  this.eventHandler_.listen(element,
    goog.events.EventType.MOUSEDOWN,
    this.onMouseDown_, false);
  this.eventHandler_.listen(element,
    goog.events.EventType.BLUR,
    this.onBlur_, false);

  //set aria role
  goog.a11y.aria.setState(element, 'haspopup', true);

};
goog.inherits(plana.ui.ac.InputHandler, goog.events.EventTarget);

/**
 * Standard list separators.
 * @type {string}
 * @const
 */
plana.ui.ac.InputHandler.STANDARD_LIST_SEPARATORS = ',;';

/**
 * Cleanup.
 * @override
 * @suppress {checkTypes}
 */
plana.ui.ac.InputHandler.prototype.disposeInternal = function() {
  plana.ui.ac.InputHandler.superClass_.disposeInternal.call(this);

  this.eventHandler_.unlisten(this.keyHandler_,
    goog.events.KeyHandler.EventType.KEY,
    this.onKey_, false);
  this.eventHandler_.unlisten(this.input_,
    goog.events.EventType.KEYUP,
    this.onKeyUp_, false);
  this.eventHandler_.unlisten(this.input_,
    goog.events.EventType.KEYPRESS,
    this.onKeyPress_, false);
  this.eventHandler_.unlisten(this.input_,
    goog.events.EventType.MOUSEDOWN,
    this.onMouseDown_, false);
  this.eventHandler_.unlisten(this.input_,
    goog.events.EventType.BLUR,
    this.onBlur_, false);

  this.stopUpdateCheckTimer_();

  this.input_ = null;
  if (this.updateTimer_ != null) {
    this.updateTimer_.dispose();
    this.updateTimer_ = null;
  }
  this.updateInterval_ = null;

  this.keyHandler_.dispose();
  this.keyHandler_ = null;

  this.eventHandler_.dispose();
  this.eventHandler_ = null;

  this.supportsMulti_ = null;
  this.separators_ = null;
  this.defaultSeparator_ = null;
  this.sepSplitRegEx_ = null;
  this.formatRegEx_ = null;
  this.separatorUpdates_ = null;
  this.separatorSelects_ = null;
  this.previousValue_ = null;
  this.isHandlingIME_ = null;
  this.lastKeyCode_ = null;

  this.matchedObjects_.length = 0;
  this.matchedObjects_ = null;
  this.caseInsensitve_ = null;
};

/**
 * This function starts the timer to periodiacally check for updates
 * to the input that have been missed by the key handler
 * @private
 */
plana.ui.ac.InputHandler.prototype.startUpdateCheckTimer_ = function() {
  this.updateTimer_ =
    new goog.Timer(this.updateInterval_);
  this.eventHandler_.listen(this.updateTimer_,
    goog.Timer.TICK, this.onTick_, false);
  this.updateTimer_.start();
};

/**
 * This function stops the timer that checks for input updates
 * @private
 */
plana.ui.ac.InputHandler.prototype.stopUpdateCheckTimer_ = function() {
  if (this.updateTimer_ != null) {
    this.eventHandler_.unlisten(this.updateTimer_,
      goog.Timer.TICK, this.onTick_, false);
    this.updateTimer_.stop();
    this.updateTimer_.dispose();
    this.updateTimer_ = null;
  }
};

/**
 * Callback for mousedown events. If the mousedown is not a left button click
 * we start a timer to monitor changes on the input element, because a user
 * could be pasting text. We stop the timer when there's a subsequent
 * left click (thus dismissing the context menu), or, the user types something
 * @param {goog.events.BrowserEvent} e
 * @private
 */
plana.ui.ac.InputHandler.prototype.onMouseDown_ = function(e) {
  if (e.button != 0) {
    //possibility of pasting text, so start timer
    this.previousValue_ = this.input_.value;
    this.startUpdateCheckTimer_();
  } else {
    this.stopUpdateCheckTimer_();
  }
};

/**
 * Callback for KEY events. Simply save the keycode
 * @param {goog.events.KeyEvent} e The key event
 * @private
 */
plana.ui.ac.InputHandler.prototype.onKey_ = function(e) {
  this.lastKeyCode_ = e.keyCode;

  switch (e.keyCode) {
    case goog.events.KeyCodes.WIN_IME:
      if (!this.isHandlingIME_) {
        this.isHandlingIME_ = true;
        this.eventHandler_.listen(this.input_,
          goog.events.EventType.KEYPRESS,
          this.onKeyPress_, false);
        return;
      }
    default:
  }
  this.handleSeparator_(e);
};

/**
 * Handles a KEYPRESS event generated by typing in the active input element.
 * Checks if IME input is ended.
 * @param {goog.events.BrowserEvent} e Browser event object.
 * @private
 */
plana.ui.ac.InputHandler.prototype.onKeyPress_ = function(e) {
  if (this.isHandlingIME_ &&
    this.lastKeyCode_ != goog.events.KeyCodes.WIN_IME) {
    this.isHandlingIME_ = false;
    this.eventHandler_.unlisten(this.input_,
      goog.events.EventType.KEYPRESS,
      this.onKeyPress_, false);
  }
};

/**
 * This function stops the update check timer if it's running, and fires a
 * 'SELECT_HIGHLIGHTED' event if the user pressed enter. it also fires
 * TEXT_CHANGED event if the input value was modified
 * @param {goog.events.BrowserEvent} e Browser event object
 * @private
 */
plana.ui.ac.InputHandler.prototype.onKeyUp_ = function(e) {

  this.stopUpdateCheckTimer_();

  if (this.isHandlingIME_ &&
    (e.keyCode == goog.events.KeyCodes.ENTER ||
      (e.keyCode == goog.events.KeyCodes.M && e.ctrlKey))) {
    this.isHandlingIME_ = false;
    this.eventHandler_.unlisten(this.input_,
      goog.events.EventType.KEYPRESS,
      this.onKeyPress_, false);
  }

  if (goog.events.KeyCodes.isTextModifyingKeyEvent(e)) {
    switch (e.keyCode) {
      case goog.events.KeyCodes.TAB:
      case goog.events.KeyCodes.ENTER:
      case goog.events.KeyCodes.MAC_ENTER:
        break;
      default:
        var entries = this.getEntries();
        var numEntries = entries.length;
        var numMatches = this.matchedObjects_.length;
        var index = 0;
        for (; index < numEntries && index < numMatches; ++index) {
          var token = goog.string.trim(entries[index]);
          var match = this.matchedObjects_[index];
          if (match != null) {
            if (goog.isString(match))
              match = goog.string.trim(match);
            else {
              var obj = new plana.ui.ac.RemoteObject(match);
              match = goog.string.trim(obj.toString());
              obj.dispose();
            }
            if (!this.areStringsEqual(token, match)) {
              this.matchedObjects_[index] = null;
            }
          }
        }
        for (; index < numMatches; ++index) {
          this.matchedObjects_[index] = null;
        }
        this.sendChangeNotification_();
    }
  }
  this.previousValue_ = this.input_.value;
};

/**
 * @see http://docs.closure-library.googlecode.com/git-history/7bb23f83ca959ae16e10ebc6734b0ba882629904/class_goog_ui_ac_InputHandler.html
 * Handles a key event for a separator key.
 * @param {goog.events.BrowserEvent} e Browser event object.
 * @private
 */
plana.ui.ac.InputHandler.prototype.handleSeparator_ = function(e) {
  var isSeparatorKey = this.supportsMulti_ && e.charCode &&
    this.separators_.indexOf(String.fromCharCode(e.charCode)) != -1;
  if (this.separatorUpdates_ && isSeparatorKey) {
    this.sendChangeNotification_();
  }
  if (this.separatorSelects_ && isSeparatorKey) {
    var continueDefaultAction = this.dispatchEvent(
      new goog.events.Event(
        plana.ui.ac.InputHandler.EventType.SELECT_HIGHLIGHTED, this
      )
    );
    if (!continueDefaultAction) {
      e.preventDefault();
    }
  }
};

/**
 * Send a DISMISS notification
 * @param {goog.events.BrowserEvent} e
 * @private
 */
plana.ui.ac.InputHandler.prototype.onBlur_ = function(e) {
  /**
   * @type {plana.ui.ac.InputHandler}
   */
  var self = this;
  /**
   * send blur event slightly delayed in case it fires before
   * the click event in the renderer has been fired and handled
   * @suppress {checkTypes}
   */
  window.setTimeout(function() {
    if (!self.isDisposed()) {
      self.dispatchEvent(
        new goog.events.Event(
          plana.ui.ac.InputHandler.EventType.DISMISS, self
        )
      );
    }
    self = null;
  }, 0);
};

/**
 * Callback for the timer to check if the input changed. If it has,
 * send a change notification
 * @param {goog.events.Event} e
 * @private
 */
plana.ui.ac.InputHandler.prototype.onTick_ = function(e) {
  var prevValue = this.previousValue_;
  if (!this.areStringsEqual(prevValue, this.input_.value)) {
    this.sendChangeNotification_();
    this.previousValue_ = this.input_.value;
  }
};

/**
 * This function dispatched a change notification with the current token and
 * the complete value of the input
 * @private
 */
plana.ui.ac.InputHandler.prototype.sendChangeNotification_ = function() {
  this.dispatchEvent({
    type: plana.ui.ac.InputHandler.EventType.TEXT_CHANGED,
    target: this,
    token: this.getCurrentToken(),
    fullstring: this.getValue()
  });
};

/**
 * This function updates the list of matched server objects
 * @param {plana.ui.ac.RemoteObject} match
 * @param {number} index The index of the edited item
 * @private
 */
plana.ui.ac.InputHandler.prototype.updateMatchedObject_ = function(
  match, index) {
  if (match == null) {
    this.matchedObjects_[index] = null;
  } else
    this.matchedObjects_[index] = match.getData();
};

/**
 * This function checks if two strings are equal with the current
 * setting for case sensitive behaviour
 * @param {!string} str1 The string to compare
 * @param {!string} str2 The string to compare str1 to
 * @return {boolean}
 */
plana.ui.ac.InputHandler.prototype.areStringsEqual = function(str1, str2) {
  if (this.caseInsensitve_ == false) {
    return str1 == str2;
  } else {
    return str1.toLowerCase() == str2.toLowerCase();
  }
};

/**
 * This function returns the value of the text input
 * @return {!string}
 */
plana.ui.ac.InputHandler.prototype.getValue = function() {
  return this.input_.value;
};

/**
 * This function returns the index of the token the current cursor is at
 * in the input. For multi-token inputs, this is the index of the array that
 * corresponds to the token in which the current cursor is at. For non-multi
 * token inputs this returns -1. If the cursor is after the last word in the
 * input, it returns the index of the last item
 * @param {Array.<string>} entries The list of tokens entered, split by the
 *     default delimiters
 * @return {!number}
 */
plana.ui.ac.InputHandler.prototype.getCurrentTokenIndex = function(entries) {
  if (this.supportsMulti_) {
    var cursorPos = goog.dom.selection.getStart(this.input_);
    var numEntries = entries.length;
    if (numEntries == 0 || cursorPos == 0) return 0;
    else {
      if (cursorPos == this.input_.value.length) return entries.length - 1;
      var indx = 0;
      for (var pos = 0; indx < numEntries; ++indx) {
        var tk = entries[indx];
        pos += tk.length;
        if (pos >= cursorPos) {
          break;
        }
        // + 1 because we need to include separator
        pos += 1;
      }
      if (indx >= numEntries) {
        //get last token
        return numEntries - 1;
      }
      return indx;
    }
  }
  return -1;
};

/**
 * This function returns the current token (trimmed) at the position of the
 * cursor
 * @return {!string}
 */
plana.ui.ac.InputHandler.prototype.getCurrentToken = function() {
  if (this.supportsMulti_) {
    var entries = this.getEntries();
    var indx = this.getCurrentTokenIndex(entries);
    if (indx == -1)
      return '';
    else
      return goog.string.trim(entries[indx]);
  }
  return goog.string.trim(this.getValue());
};

/**
 * This function returns an array of tokens in the text input
 * @return {Array.<string>}
 */
plana.ui.ac.InputHandler.prototype.getEntries = function() {
  var val = this.getValue();
  if (this.supportsMulti_) {
    return val.split(this.sepSplitRegEx_);
  } else
    return [val];
};

/**
 * Getter for the HTML input element
 * @return {HTMLInputElement}
 */
plana.ui.ac.InputHandler.prototype.getInput = function() {
  return this.input_;
};

/**
 * This function returns a copy of the list of matched server objects
 * @return {Array.<Object|string>}
 */
plana.ui.ac.InputHandler.prototype.getMatchedObjects = function() {
  var filtered = goog.array.filter(this.matchedObjects_, function(match, indx) {
    if (match != null)
      return true;
    return false;
  });
  if (this.supportsMulti_)
    return filtered.slice(0);
  else
    return filtered.slice(0, 1);
};

/**
 * This function initializes the input with a set of matched objects
 * @param {Array.<Object|string>} matches
 */
plana.ui.ac.InputHandler.prototype.setMatchedObjects = function(matches) {
  this.matchedObjects_.length = 0;
  this.input_.value = '';
  //update text input
  var numMatches = matches.length;
  for (var i = 0; i < numMatches; ++i) {
    var match = /**@type {Object|string}*/ (matches[i]);
    this.selectRow(new plana.ui.ac.RemoteObject(match));
  }
};

/**
 * This function implements the seletionhandler interface. It updates
 * the input value based on the value of the selected row.
 * @param {plana.ui.ac.RemoteObject} row
 */
plana.ui.ac.InputHandler.prototype.selectRow = function(row) {
  var index;
  if (this.supportsMulti_) {
    var entries = this.getEntries();
    index = this.getCurrentTokenIndex(entries);
    goog.asserts.assert(index >= 0 && index < entries.length);
    entries[index] = row.toString();

    //compute caret position
    /**
     * @type {number}
     */
    var pos = 0;
    for (var i = 0; i <= index; ++i) {
      //+1 for separator
      pos += entries[i].length + 1;
    }

    var str = entries.join(this.defaultSeparator_);
    //if it's the last item, add ', '
    if (index == entries.length - 1) {
      str += this.defaultSeparator_ + ' ';
      pos += 1;
    }

    /**
     * @type {?string}
     */
    var sep = this.defaultSeparator_ + ' ';

    /**
     * @type {?function(string):string}
     */
    var adjustSeparators =
    /**
     * @param {string} regexMatch
     * @return {string}
     */

      function(regexMatch) {
        pos += 1;
        return sep + regexMatch.substring(1);
      };
    this.input_.value = str.replace( /** @type {RegExp} */ (this.formatRegEx_),
      adjustSeparators);
    sep = null;
    adjustSeparators = null;
    goog.dom.selection.setStart(this.input_, pos);
    goog.dom.selection.setEnd(this.input_, pos);
  } else {
    index = 0;
    this.input_.value = row.toString();
  }
  //update match objects
  this.updateMatchedObject_(row, index);
  this.previousValue_ = this.input_.value;
};

/**
 * This function implements the seletionhandler interface. It dispatches
 * a change notification to the autocomplete, which in turns updates the
 * token
 * @param {boolean=} opt_force Whether we should force sending an update
 * to set the token
 */
plana.ui.ac.InputHandler.prototype.update = function(opt_force) {
  var prevValue = this.previousValue_;
  if (!this.areStringsEqual(prevValue, this.input_.value) ||
    opt_force) {
    this.sendChangeNotification_();
    this.previousValue_ = this.input_.value;
  }
};

/**
 * Setter for chaning the interval when checking for changes to the input
 * that were not handled by the keyhandler
 * @param {!number} interval The value to set
 */
plana.ui.ac.InputHandler.prototype.setUpdateInterval = function(interval) {
  goog.asserts.assert(goog.isNumber(interval),
    'timer interval must be numeric');
  this.updateInterval_ = interval;
  if (this.updateTimer_ != null) {
    this.stopUpdateCheckTimer_();
    this.startUpdateCheckTimer_();
  }
};

/**
 * Setter for the separator characters to use for multi-token inputs
 * @param {string} separators The list of separator characters used to split
 *     tokens in the input
 * @param {string=} opt_defaultSeparator The default character to use
 *     during autocomplete events, i.e. the character that is appended by
 *     default when a user selects a match
 */
plana.ui.ac.InputHandler.prototype.setSeparator = function(
  separators, opt_defaultSeparator) {
  if (separators == null || goog.string.isEmpty(separators)) {
    this.supportsMulti_ = false;
    this.defaultSeparator_ = '';
  } else {
    var sep;
    if (goog.isDefAndNotNull(opt_defaultSeparator)) {
      goog.asserts.assert(opt_defaultSeparator.length == 1,
        'default separator string must be a single char');
      sep = opt_defaultSeparator;
    } else {
      sep = separators.substring(0, 1);
    }
    //replace any old separator chars in the input
    if (this.defaultSeparator_ != sep) {
      var val = this.getValue();
      var currentPos = goog.dom.selection.getStart(this.input_);
      var regex = new RegExp(this.sepSplitRegEx_.source, 'g');
      this.input_.value = val.replace(regex, sep);
      goog.dom.selection.setStart(this.input_, currentPos);
      goog.dom.selection.setEnd(this.input_, currentPos);
      this.defaultSeparator_ = sep;
      this.formatRegEx_ = new RegExp(this.defaultSeparator_ + '([^\\s])', 'g');
    }
    this.sepSplitRegEx_ = new RegExp('[' + separators + ']');
    this.supportsMulti_ = true;
  }
};

/**
 * Sets whether separators perform autocomplete.
 * @param {boolean} newValue Whether to autocomplete on separators.
 */
plana.ui.ac.InputHandler.prototype.setSeparatorCompletes = function(newValue) {
  this.separatorUpdates_ = newValue;
  this.separatorSelects_ = newValue;
};


/**
 * Sets whether separators perform autocomplete.
 * @param {boolean} newValue Whether to autocomplete on separators.
 */
plana.ui.ac.InputHandler.prototype.setSeparatorSelects = function(newValue) {
  this.separatorSelects_ = newValue;
};

/**
 * This function sets whether string comparisons in this class are
 * case-insensitive
 * @param {boolean} newValue Whether we should ignore case for strings
 */
plana.ui.ac.InputHandler.prototype.setCaseInsensitive = function(newValue) {
  this.caseInsensitve_ = newValue;
};

/**
 * Getter for the flag whether this component handles strings case-insensitive
 * @return {boolean}
 */
plana.ui.ac.InputHandler.prototype.getCaseInsensitive = function() {
  return this.caseInsensitve_;
};

/**
 * List of events dispatched by this class
 * @enum {!string}
 */
plana.ui.ac.InputHandler.EventType = {
  /**
   * @desc the event dispatched if the input text
   * changed. Could be as a result of keypresses,
   * or by pasting text
   */
  TEXT_CHANGED: goog.events.getUniqueId('change'),
  /**
   * @desc the event dispatched if the input element
   * looses focus and we should dimiss the autocomplete
   */
  DISMISS: goog.events.getUniqueId('dismiss'),
  /**
   * @desc the event dispatched if a match was selected
   * and the autocomplete model needs updating
   */
  SELECT_HIGHLIGHTED: goog.events.getUniqueId('select')
};