920 lines
26 KiB
JavaScript
920 lines
26 KiB
JavaScript
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
|
|
//
|
|
// 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.
|
|
|
|
/**
|
|
* @fileoverview Gmail-like AutoComplete logic.
|
|
*
|
|
* @see ../../demos/autocomplete-basic.html
|
|
*/
|
|
|
|
goog.provide('goog.ui.ac.AutoComplete');
|
|
goog.provide('goog.ui.ac.AutoComplete.EventType');
|
|
|
|
goog.require('goog.array');
|
|
goog.require('goog.asserts');
|
|
goog.require('goog.events');
|
|
goog.require('goog.events.EventTarget');
|
|
goog.require('goog.object');
|
|
|
|
|
|
|
|
/**
|
|
* This is the central manager class for an AutoComplete instance. The matcher
|
|
* can specify disabled rows that should not be hilited or selected by
|
|
* implementing <code>isRowDisabled(row):boolean</code> for each autocomplete
|
|
* row. No row will not be considered disabled if this method is not
|
|
* implemented.
|
|
*
|
|
* @param {Object} matcher A data source and row matcher, implements
|
|
* <code>requestMatchingRows(token, maxMatches, matchCallback)</code>.
|
|
* @param {goog.events.EventTarget} renderer An object that implements
|
|
* <code>
|
|
* isVisible():boolean<br>
|
|
* renderRows(rows:Array, token:string, target:Element);<br>
|
|
* hiliteId(row-id:number);<br>
|
|
* dismiss();<br>
|
|
* dispose():
|
|
* </code>.
|
|
* @param {Object} selectionHandler An object that implements
|
|
* <code>
|
|
* selectRow(row);<br>
|
|
* update(opt_force);
|
|
* </code>.
|
|
*
|
|
* @constructor
|
|
* @extends {goog.events.EventTarget}
|
|
*/
|
|
goog.ui.ac.AutoComplete = function(matcher, renderer, selectionHandler) {
|
|
goog.events.EventTarget.call(this);
|
|
|
|
/**
|
|
* A data-source which provides autocomplete suggestions.
|
|
*
|
|
* TODO(user): Tighten the type to !Object.
|
|
*
|
|
* @type {Object}
|
|
* @protected
|
|
* @suppress {underscore}
|
|
*/
|
|
this.matcher_ = matcher;
|
|
|
|
/**
|
|
* A handler which interacts with the input DOM element (textfield, textarea,
|
|
* or richedit).
|
|
*
|
|
* TODO(user): Tighten the type to !Object.
|
|
*
|
|
* @type {Object}
|
|
* @protected
|
|
* @suppress {underscore}
|
|
*/
|
|
this.selectionHandler_ = selectionHandler;
|
|
|
|
/**
|
|
* A renderer to render/show/highlight/hide the autocomplete menu.
|
|
* @type {goog.events.EventTarget}
|
|
* @protected
|
|
* @suppress {underscore}
|
|
*/
|
|
this.renderer_ = renderer;
|
|
goog.events.listen(
|
|
renderer,
|
|
[
|
|
goog.ui.ac.AutoComplete.EventType.HILITE,
|
|
goog.ui.ac.AutoComplete.EventType.SELECT,
|
|
goog.ui.ac.AutoComplete.EventType.CANCEL_DISMISS,
|
|
goog.ui.ac.AutoComplete.EventType.DISMISS
|
|
],
|
|
this.handleEvent, false, this);
|
|
|
|
/**
|
|
* Currently typed token which will be used for completion.
|
|
* @type {?string}
|
|
* @protected
|
|
* @suppress {underscore}
|
|
*/
|
|
this.token_ = null;
|
|
|
|
/**
|
|
* Autocomplete suggestion items.
|
|
* @type {Array}
|
|
* @protected
|
|
* @suppress {underscore}
|
|
*/
|
|
this.rows_ = [];
|
|
|
|
/**
|
|
* Id of the currently highlighted row.
|
|
* @type {number}
|
|
* @protected
|
|
* @suppress {underscore}
|
|
*/
|
|
this.hiliteId_ = -1;
|
|
|
|
/**
|
|
* Id of the first row in autocomplete menu. Note that new ids are assigned
|
|
* everytime new suggestions are fetched.
|
|
*
|
|
* TODO(user): Figure out what subclass does with this value
|
|
* and whether we should expose a more proper API.
|
|
*
|
|
* @type {number}
|
|
* @protected
|
|
* @suppress {underscore}
|
|
*/
|
|
this.firstRowId_ = 0;
|
|
|
|
/**
|
|
* The target HTML node for displaying.
|
|
* @type {Element}
|
|
* @protected
|
|
* @suppress {underscore}
|
|
*/
|
|
this.target_ = null;
|
|
|
|
/**
|
|
* The timer id for dismissing autocomplete menu with a delay.
|
|
* @type {?number}
|
|
* @private
|
|
*/
|
|
this.dismissTimer_ = null;
|
|
|
|
/**
|
|
* Mapping from text input element to the anchor element. If the
|
|
* mapping does not exist, the input element will act as the anchor
|
|
* element.
|
|
* @type {Object.<Element>}
|
|
* @private
|
|
*/
|
|
this.inputToAnchorMap_ = {};
|
|
};
|
|
goog.inherits(goog.ui.ac.AutoComplete, goog.events.EventTarget);
|
|
|
|
|
|
/**
|
|
* The maximum number of matches that should be returned
|
|
* @type {number}
|
|
* @private
|
|
*/
|
|
goog.ui.ac.AutoComplete.prototype.maxMatches_ = 10;
|
|
|
|
|
|
/**
|
|
* True iff the first row should automatically be highlighted
|
|
* @type {boolean}
|
|
* @private
|
|
*/
|
|
goog.ui.ac.AutoComplete.prototype.autoHilite_ = true;
|
|
|
|
|
|
/**
|
|
* True iff the user can unhilight all rows by pressing the up arrow.
|
|
* @type {boolean}
|
|
* @private
|
|
*/
|
|
goog.ui.ac.AutoComplete.prototype.allowFreeSelect_ = false;
|
|
|
|
|
|
/**
|
|
* True iff item selection should wrap around from last to first. If
|
|
* allowFreeSelect_ is on in conjunction, there is a step of free selection
|
|
* before wrapping.
|
|
* @type {boolean}
|
|
* @private
|
|
*/
|
|
goog.ui.ac.AutoComplete.prototype.wrap_ = false;
|
|
|
|
|
|
/**
|
|
* Whether completion from suggestion triggers fetching new suggestion.
|
|
* @type {boolean}
|
|
* @private
|
|
*/
|
|
goog.ui.ac.AutoComplete.prototype.triggerSuggestionsOnUpdate_ = false;
|
|
|
|
|
|
/**
|
|
* Events associated with the autocomplete
|
|
* @enum {string}
|
|
*/
|
|
goog.ui.ac.AutoComplete.EventType = {
|
|
|
|
/** A row has been highlighted by the renderer */
|
|
ROW_HILITE: 'rowhilite',
|
|
|
|
// Note: The events below are used for internal autocomplete events only and
|
|
// should not be used in non-autocomplete code.
|
|
|
|
/** A row has been mouseovered and should be highlighted by the renderer. */
|
|
HILITE: 'hilite',
|
|
|
|
/** A row has been selected by the renderer */
|
|
SELECT: 'select',
|
|
|
|
/** A dismiss event has occurred */
|
|
DISMISS: 'dismiss',
|
|
|
|
/** Event that cancels a dismiss event */
|
|
CANCEL_DISMISS: 'canceldismiss',
|
|
|
|
/**
|
|
* Field value was updated. A row field is included and is non-null when a
|
|
* row has been selected. The value of the row typically includes fields:
|
|
* contactData and formattedValue as well as a toString function (though none
|
|
* of these fields are guaranteed to exist). The row field may be used to
|
|
* return custom-type row data.
|
|
*/
|
|
UPDATE: 'update',
|
|
|
|
/**
|
|
* The list of suggestions has been updated, usually because either the list
|
|
* has opened, or because the user has typed another character and the
|
|
* suggestions have been updated, or the user has dismissed the autocomplete.
|
|
*/
|
|
SUGGESTIONS_UPDATE: 'suggestionsupdate'
|
|
};
|
|
|
|
|
|
/**
|
|
* @return {!Object} The data source providing the `autocomplete
|
|
* suggestions.
|
|
*/
|
|
goog.ui.ac.AutoComplete.prototype.getMatcher = function() {
|
|
return goog.asserts.assert(this.matcher_);
|
|
};
|
|
|
|
|
|
/**
|
|
* Sets the data source providing the autocomplete suggestions.
|
|
*
|
|
* See constructor documentation for the interface.
|
|
*
|
|
* @param {!Object} matcher The matcher.
|
|
* @protected
|
|
*/
|
|
goog.ui.ac.AutoComplete.prototype.setMatcher = function(matcher) {
|
|
this.matcher_ = matcher;
|
|
};
|
|
|
|
|
|
/**
|
|
* @return {!Object} The handler used to interact with the input DOM
|
|
* element (textfield, textarea, or richedit), e.g. to update the
|
|
* input DOM element with selected value.
|
|
* @protected
|
|
*/
|
|
goog.ui.ac.AutoComplete.prototype.getSelectionHandler = function() {
|
|
return goog.asserts.assert(this.selectionHandler_);
|
|
};
|
|
|
|
|
|
/**
|
|
* @return {goog.events.EventTarget} The renderer that
|
|
* renders/shows/highlights/hides the autocomplete menu.
|
|
* See constructor documentation for the expected renderer API.
|
|
*/
|
|
goog.ui.ac.AutoComplete.prototype.getRenderer = function() {
|
|
return this.renderer_;
|
|
};
|
|
|
|
|
|
/**
|
|
* Sets the renderer that renders/shows/highlights/hides the autocomplete
|
|
* menu.
|
|
*
|
|
* See constructor documentation for the expected renderer API.
|
|
*
|
|
* @param {goog.events.EventTarget} renderer The renderer.
|
|
* @protected
|
|
*/
|
|
goog.ui.ac.AutoComplete.prototype.setRenderer = function(renderer) {
|
|
this.renderer_ = renderer;
|
|
};
|
|
|
|
|
|
/**
|
|
* @return {?string} The currently typed token used for completion.
|
|
* @protected
|
|
*/
|
|
goog.ui.ac.AutoComplete.prototype.getToken = function() {
|
|
return this.token_;
|
|
};
|
|
|
|
|
|
/**
|
|
* Sets the current token (without changing the rendered autocompletion).
|
|
*
|
|
* NOTE(user): This method will likely go away when we figure
|
|
* out a better API.
|
|
*
|
|
* @param {?string} token The new token.
|
|
* @protected
|
|
*/
|
|
goog.ui.ac.AutoComplete.prototype.setTokenInternal = function(token) {
|
|
this.token_ = token;
|
|
};
|
|
|
|
|
|
/**
|
|
* @param {number} index The suggestion index, must be within the
|
|
* interval [0, this.getSuggestionCount()).
|
|
* @return {Object} The currently suggested item at the given index
|
|
* (or null if there is none).
|
|
*/
|
|
goog.ui.ac.AutoComplete.prototype.getSuggestion = function(index) {
|
|
return this.rows_[index];
|
|
};
|
|
|
|
|
|
/**
|
|
* @return {!Array} The current autocomplete suggestion items.
|
|
*/
|
|
goog.ui.ac.AutoComplete.prototype.getAllSuggestions = function() {
|
|
return goog.asserts.assert(this.rows_);
|
|
};
|
|
|
|
|
|
/**
|
|
* @return {number} The number of currently suggested items.
|
|
*/
|
|
goog.ui.ac.AutoComplete.prototype.getSuggestionCount = function() {
|
|
return this.rows_.length;
|
|
};
|
|
|
|
|
|
/**
|
|
* @return {number} The id (not index!) of the currently highlighted row.
|
|
*/
|
|
goog.ui.ac.AutoComplete.prototype.getHighlightedId = function() {
|
|
return this.hiliteId_;
|
|
};
|
|
|
|
|
|
/**
|
|
* Sets the current highlighted row to the given id (not index). Note
|
|
* that this does not change any rendering.
|
|
*
|
|
* NOTE(user): This method will likely go away when we figure
|
|
* out a better API.
|
|
*
|
|
* @param {number} id The new highlighted row id.
|
|
*/
|
|
goog.ui.ac.AutoComplete.prototype.setHighlightedIdInternal = function(id) {
|
|
this.hiliteId_ = id;
|
|
};
|
|
|
|
|
|
/**
|
|
* Generic event handler that handles any events this object is listening to.
|
|
* @param {goog.events.Event} e Event Object.
|
|
*/
|
|
goog.ui.ac.AutoComplete.prototype.handleEvent = function(e) {
|
|
if (e.target == this.renderer_) {
|
|
switch (e.type) {
|
|
case goog.ui.ac.AutoComplete.EventType.HILITE:
|
|
this.hiliteId(/** @type {number} */ (e.row));
|
|
break;
|
|
|
|
case goog.ui.ac.AutoComplete.EventType.SELECT:
|
|
// e.row can be either a valid row number or empty.
|
|
var rowId = /** @type {number} */ (e.row);
|
|
var index = this.getIndexOfId(rowId);
|
|
var row = this.rows_[index];
|
|
|
|
// Make sure the row selected is not a disabled row.
|
|
var rowDisabled = !!row && this.matcher_.isRowDisabled &&
|
|
this.matcher_.isRowDisabled(row);
|
|
if (rowId && row && !rowDisabled && this.hiliteId_ != rowId) {
|
|
// Event target row not currently highlighted - fix the mismatch.
|
|
this.hiliteId(rowId);
|
|
}
|
|
if (!rowDisabled) {
|
|
// Note that rowDisabled can be false even if e.row does not
|
|
// contain a valid row ID; at least one client depends on us
|
|
// proceeding anyway.
|
|
this.selectHilited();
|
|
}
|
|
break;
|
|
|
|
case goog.ui.ac.AutoComplete.EventType.CANCEL_DISMISS:
|
|
this.cancelDelayedDismiss();
|
|
break;
|
|
|
|
case goog.ui.ac.AutoComplete.EventType.DISMISS:
|
|
this.dismissOnDelay();
|
|
break;
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* Sets the max number of matches to fetch from the Matcher.
|
|
*
|
|
* @param {number} max Max number of matches.
|
|
*/
|
|
goog.ui.ac.AutoComplete.prototype.setMaxMatches = function(max) {
|
|
this.maxMatches_ = max;
|
|
};
|
|
|
|
|
|
/**
|
|
* Sets whether or not the first row should be highlighted by default.
|
|
*
|
|
* @param {boolean} autoHilite true iff the first row should be
|
|
* highlighted by default.
|
|
*/
|
|
goog.ui.ac.AutoComplete.prototype.setAutoHilite = function(autoHilite) {
|
|
this.autoHilite_ = autoHilite;
|
|
};
|
|
|
|
|
|
/**
|
|
* Sets whether or not the up/down arrow can unhilite all rows.
|
|
*
|
|
* @param {boolean} allowFreeSelect true iff the up arrow can unhilite all rows.
|
|
*/
|
|
goog.ui.ac.AutoComplete.prototype.setAllowFreeSelect =
|
|
function(allowFreeSelect) {
|
|
this.allowFreeSelect_ = allowFreeSelect;
|
|
};
|
|
|
|
|
|
/**
|
|
* Sets whether or not selections can wrap around the edges.
|
|
*
|
|
* @param {boolean} wrap true iff sections should wrap around the edges.
|
|
*/
|
|
goog.ui.ac.AutoComplete.prototype.setWrap = function(wrap) {
|
|
this.wrap_ = wrap;
|
|
};
|
|
|
|
|
|
/**
|
|
* Sets whether or not to request new suggestions immediately after completion
|
|
* of a suggestion.
|
|
*
|
|
* @param {boolean} triggerSuggestionsOnUpdate true iff completion should fetch
|
|
* new suggestions.
|
|
*/
|
|
goog.ui.ac.AutoComplete.prototype.setTriggerSuggestionsOnUpdate = function(
|
|
triggerSuggestionsOnUpdate) {
|
|
this.triggerSuggestionsOnUpdate_ = triggerSuggestionsOnUpdate;
|
|
};
|
|
|
|
|
|
/**
|
|
* Sets the token to match against. This triggers calls to the Matcher to
|
|
* fetch the matches (up to maxMatches), and then it triggers a call to
|
|
* <code>renderer.renderRows()</code>.
|
|
*
|
|
* @param {string} token The string for which to search in the Matcher.
|
|
* @param {string=} opt_fullString Optionally, the full string in the input
|
|
* field.
|
|
*/
|
|
goog.ui.ac.AutoComplete.prototype.setToken = function(token, opt_fullString) {
|
|
if (this.token_ == token) {
|
|
return;
|
|
}
|
|
this.token_ = token;
|
|
this.matcher_.requestMatchingRows(this.token_,
|
|
this.maxMatches_, goog.bind(this.matchListener_, this), opt_fullString);
|
|
this.cancelDelayedDismiss();
|
|
};
|
|
|
|
|
|
/**
|
|
* Gets the current target HTML node for displaying autocomplete UI.
|
|
* @return {Element} The current target HTML node for displaying autocomplete
|
|
* UI.
|
|
*/
|
|
goog.ui.ac.AutoComplete.prototype.getTarget = function() {
|
|
return this.target_;
|
|
};
|
|
|
|
|
|
/**
|
|
* Sets the current target HTML node for displaying autocomplete UI.
|
|
* Can be an implementation specific definition of how to display UI in relation
|
|
* to the target node.
|
|
* This target will be passed into <code>renderer.renderRows()</code>
|
|
*
|
|
* @param {Element} target The current target HTML node for displaying
|
|
* autocomplete UI.
|
|
*/
|
|
goog.ui.ac.AutoComplete.prototype.setTarget = function(target) {
|
|
this.target_ = target;
|
|
};
|
|
|
|
|
|
/**
|
|
* @return {boolean} Whether the autocomplete's renderer is open.
|
|
*/
|
|
goog.ui.ac.AutoComplete.prototype.isOpen = function() {
|
|
return this.renderer_.isVisible();
|
|
};
|
|
|
|
|
|
/**
|
|
* @return {number} Number of rows in the autocomplete.
|
|
* @deprecated Use this.getSuggestionCount().
|
|
*/
|
|
goog.ui.ac.AutoComplete.prototype.getRowCount = function() {
|
|
return this.getSuggestionCount();
|
|
};
|
|
|
|
|
|
/**
|
|
* Moves the hilite to the next non-disabled row.
|
|
* Calls renderer.hiliteId() when there's something to do.
|
|
* @return {boolean} Returns true on a successful hilite.
|
|
*/
|
|
goog.ui.ac.AutoComplete.prototype.hiliteNext = function() {
|
|
var lastId = this.firstRowId_ + this.rows_.length - 1;
|
|
var toHilite = this.hiliteId_;
|
|
// Hilite the next row, skipping any disabled rows.
|
|
for (var i = 0; i < this.rows_.length; i++) {
|
|
// Increment to the next row.
|
|
if (toHilite >= this.firstRowId_ && toHilite < lastId) {
|
|
toHilite++;
|
|
} else if (toHilite == -1) {
|
|
toHilite = this.firstRowId_;
|
|
} else if (this.allowFreeSelect_ && toHilite == lastId) {
|
|
this.hiliteId(-1);
|
|
return false;
|
|
} else if (this.wrap_ && toHilite == lastId) {
|
|
toHilite = this.firstRowId_;
|
|
} else {
|
|
return false;
|
|
}
|
|
|
|
if (this.hiliteId(toHilite)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
};
|
|
|
|
|
|
/**
|
|
* Moves the hilite to the previous non-disabled row. Calls
|
|
* renderer.hiliteId() when there's something to do.
|
|
* @return {boolean} Returns true on a successful hilite.
|
|
*/
|
|
goog.ui.ac.AutoComplete.prototype.hilitePrev = function() {
|
|
var lastId = this.firstRowId_ + this.rows_.length - 1;
|
|
var toHilite = this.hiliteId_;
|
|
// Hilite the previous row, skipping any disabled rows.
|
|
for (var i = 0; i < this.rows_.length; i++) {
|
|
// Decrement to the previous row.
|
|
if (toHilite > this.firstRowId_) {
|
|
toHilite--;
|
|
} else if (this.allowFreeSelect_ && toHilite == this.firstRowId_) {
|
|
this.hiliteId(-1);
|
|
return false;
|
|
} else if (this.wrap_ && (toHilite == -1 || toHilite == this.firstRowId_)) {
|
|
toHilite = lastId;
|
|
} else {
|
|
return false;
|
|
}
|
|
|
|
if (this.hiliteId(toHilite)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
};
|
|
|
|
|
|
/**
|
|
* Hilites the id if it's valid and the row is not disabled, otherwise does
|
|
* nothing.
|
|
* @param {number} id A row id (not index).
|
|
* @return {boolean} Whether the id was hilited. Returns false if the row is
|
|
* disabled.
|
|
*/
|
|
goog.ui.ac.AutoComplete.prototype.hiliteId = function(id) {
|
|
var index = this.getIndexOfId(id);
|
|
var row = this.rows_[index];
|
|
var rowDisabled = !!row && this.matcher_.isRowDisabled &&
|
|
this.matcher_.isRowDisabled(row);
|
|
if (!rowDisabled) {
|
|
this.hiliteId_ = id;
|
|
this.renderer_.hiliteId(id);
|
|
return index != -1;
|
|
}
|
|
return false;
|
|
};
|
|
|
|
|
|
/**
|
|
* Hilites the index, if it's valid and the row is not disabled, otherwise does
|
|
* nothing.
|
|
* @param {number} index The row's index.
|
|
* @return {boolean} Whether the index was hilited.
|
|
*/
|
|
goog.ui.ac.AutoComplete.prototype.hiliteIndex = function(index) {
|
|
return this.hiliteId(this.getIdOfIndex_(index));
|
|
};
|
|
|
|
|
|
/**
|
|
* If there are any current matches, this passes the hilited row data to
|
|
* <code>selectionHandler.selectRow()</code>
|
|
* @return {boolean} Whether there are any current matches.
|
|
*/
|
|
goog.ui.ac.AutoComplete.prototype.selectHilited = function() {
|
|
var index = this.getIndexOfId(this.hiliteId_);
|
|
if (index != -1) {
|
|
var selectedRow = this.rows_[index];
|
|
var suppressUpdate = this.selectionHandler_.selectRow(selectedRow);
|
|
if (this.triggerSuggestionsOnUpdate_) {
|
|
this.token_ = null;
|
|
this.dismissOnDelay();
|
|
} else {
|
|
this.dismiss();
|
|
}
|
|
if (!suppressUpdate) {
|
|
this.dispatchEvent({
|
|
type: goog.ui.ac.AutoComplete.EventType.UPDATE,
|
|
row: selectedRow
|
|
});
|
|
if (this.triggerSuggestionsOnUpdate_) {
|
|
this.selectionHandler_.update(true);
|
|
}
|
|
}
|
|
return true;
|
|
} else {
|
|
this.dismiss();
|
|
this.dispatchEvent(
|
|
{
|
|
type: goog.ui.ac.AutoComplete.EventType.UPDATE,
|
|
row: null
|
|
});
|
|
return false;
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* Returns whether or not the autocomplete is open and has a highlighted row.
|
|
* @return {boolean} Whether an autocomplete row is highlighted.
|
|
*/
|
|
goog.ui.ac.AutoComplete.prototype.hasHighlight = function() {
|
|
return this.isOpen() && this.getIndexOfId(this.hiliteId_) != -1;
|
|
};
|
|
|
|
|
|
/**
|
|
* Clears out the token, rows, and hilite, and calls
|
|
* <code>renderer.dismiss()</code>
|
|
*/
|
|
goog.ui.ac.AutoComplete.prototype.dismiss = function() {
|
|
this.hiliteId_ = -1;
|
|
this.token_ = null;
|
|
this.firstRowId_ += this.rows_.length;
|
|
this.rows_ = [];
|
|
window.clearTimeout(this.dismissTimer_);
|
|
this.dismissTimer_ = null;
|
|
this.renderer_.dismiss();
|
|
this.dispatchEvent(goog.ui.ac.AutoComplete.EventType.SUGGESTIONS_UPDATE);
|
|
this.dispatchEvent(goog.ui.ac.AutoComplete.EventType.DISMISS);
|
|
};
|
|
|
|
|
|
/**
|
|
* Call a dismiss after a delay, if there's already a dismiss active, ignore.
|
|
*/
|
|
goog.ui.ac.AutoComplete.prototype.dismissOnDelay = function() {
|
|
if (!this.dismissTimer_) {
|
|
this.dismissTimer_ = window.setTimeout(goog.bind(this.dismiss, this), 100);
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* Cancels any delayed dismiss events immediately.
|
|
* @return {boolean} Whether a delayed dismiss was cancelled.
|
|
* @private
|
|
*/
|
|
goog.ui.ac.AutoComplete.prototype.immediatelyCancelDelayedDismiss_ =
|
|
function() {
|
|
if (this.dismissTimer_) {
|
|
window.clearTimeout(this.dismissTimer_);
|
|
this.dismissTimer_ = null;
|
|
return true;
|
|
}
|
|
return false;
|
|
};
|
|
|
|
|
|
/**
|
|
* Cancel the active delayed dismiss if there is one.
|
|
*/
|
|
goog.ui.ac.AutoComplete.prototype.cancelDelayedDismiss = function() {
|
|
// Under certain circumstances a cancel event occurs immediately prior to a
|
|
// delayedDismiss event that it should be cancelling. To handle this situation
|
|
// properly, a timer is used to stop that event.
|
|
// Using only the timer creates undesirable behavior when the cancel occurs
|
|
// less than 10ms before the delayed dismiss timout ends. If that happens the
|
|
// clearTimeout() will occur too late and have no effect.
|
|
if (!this.immediatelyCancelDelayedDismiss_()) {
|
|
window.setTimeout(goog.bind(this.immediatelyCancelDelayedDismiss_, this),
|
|
10);
|
|
}
|
|
};
|
|
|
|
|
|
/** @override */
|
|
goog.ui.ac.AutoComplete.prototype.disposeInternal = function() {
|
|
goog.ui.ac.AutoComplete.superClass_.disposeInternal.call(this);
|
|
delete this.inputToAnchorMap_;
|
|
this.renderer_.dispose();
|
|
this.selectionHandler_.dispose();
|
|
this.matcher_ = null;
|
|
};
|
|
|
|
|
|
/**
|
|
* Callback passed to Matcher when requesting matches for a token.
|
|
* This might be called synchronously, or asynchronously, or both, for
|
|
* any implementation of a Matcher.
|
|
* If the Matcher calls this back, with the same token this AutoComplete
|
|
* has set currently, then this will package the matching rows in object
|
|
* of the form
|
|
* <pre>
|
|
* {
|
|
* id: an integer ID unique to this result set and AutoComplete instance,
|
|
* data: the raw row data from Matcher
|
|
* }
|
|
* </pre>
|
|
*
|
|
* @param {string} matchedToken Token that corresponds with the rows.
|
|
* @param {!Array} rows Set of data that match the given token.
|
|
* @param {(boolean|goog.ui.ac.RenderOptions)=} opt_options If true,
|
|
* keeps the currently hilited (by index) element hilited. If false not.
|
|
* Otherwise a RenderOptions object.
|
|
* @private
|
|
*/
|
|
goog.ui.ac.AutoComplete.prototype.matchListener_ =
|
|
function(matchedToken, rows, opt_options) {
|
|
if (this.token_ != matchedToken) {
|
|
// Matcher's response token doesn't match current token.
|
|
// This is probably an async response that came in after
|
|
// the token was changed, so don't do anything.
|
|
return;
|
|
}
|
|
|
|
this.renderRows(rows, opt_options);
|
|
};
|
|
|
|
|
|
/**
|
|
* Renders the rows and adds highlighting.
|
|
* @param {!Array} rows Set of data that match the given token.
|
|
* @param {(boolean|goog.ui.ac.RenderOptions)=} opt_options If true,
|
|
* keeps the currently hilited (by index) element hilited. If false not.
|
|
* Otherwise a RenderOptions object.
|
|
*/
|
|
goog.ui.ac.AutoComplete.prototype.renderRows = function(rows, opt_options) {
|
|
// The optional argument should be a RenderOptions object. It can be a
|
|
// boolean for backwards compatibility, defaulting to false.
|
|
var optionsObj = goog.typeOf(opt_options) == 'object' && opt_options;
|
|
|
|
var preserveHilited =
|
|
optionsObj ? optionsObj.getPreserveHilited() : opt_options;
|
|
var indexToHilite = preserveHilited ? this.getIndexOfId(this.hiliteId_) : -1;
|
|
|
|
// Current token matches the matcher's response token.
|
|
this.firstRowId_ += this.rows_.length;
|
|
this.rows_ = rows;
|
|
var rendRows = [];
|
|
for (var i = 0; i < rows.length; ++i) {
|
|
rendRows.push({
|
|
id: this.getIdOfIndex_(i),
|
|
data: rows[i]
|
|
});
|
|
}
|
|
|
|
var anchor = null;
|
|
if (this.target_) {
|
|
anchor = this.inputToAnchorMap_[goog.getUid(this.target_)] || this.target_;
|
|
}
|
|
this.renderer_.setAnchorElement(anchor);
|
|
this.renderer_.renderRows(rendRows, this.token_, this.target_);
|
|
|
|
var autoHilite = this.autoHilite_;
|
|
if (optionsObj && optionsObj.getAutoHilite() !== undefined) {
|
|
autoHilite = optionsObj.getAutoHilite();
|
|
}
|
|
this.hiliteId_ = -1;
|
|
if ((autoHilite || indexToHilite >= 0) &&
|
|
rendRows.length != 0 &&
|
|
this.token_) {
|
|
if (indexToHilite >= 0) {
|
|
this.hiliteId(this.getIdOfIndex_(indexToHilite));
|
|
} else {
|
|
// Hilite the first non-disabled row.
|
|
this.hiliteNext();
|
|
}
|
|
}
|
|
this.dispatchEvent(goog.ui.ac.AutoComplete.EventType.SUGGESTIONS_UPDATE);
|
|
};
|
|
|
|
|
|
/**
|
|
* Gets the index corresponding to a particular id.
|
|
* @param {number} id A unique id for the row.
|
|
* @return {number} A valid index into rows_, or -1 if the id is invalid.
|
|
* @protected
|
|
*/
|
|
goog.ui.ac.AutoComplete.prototype.getIndexOfId = function(id) {
|
|
var index = id - this.firstRowId_;
|
|
if (index < 0 || index >= this.rows_.length) {
|
|
return -1;
|
|
}
|
|
return index;
|
|
};
|
|
|
|
|
|
/**
|
|
* Gets the id corresponding to a particular index. (Does no checking.)
|
|
* @param {number} index The index of a row in the result set.
|
|
* @return {number} The id that currently corresponds to that index.
|
|
* @private
|
|
*/
|
|
goog.ui.ac.AutoComplete.prototype.getIdOfIndex_ = function(index) {
|
|
return this.firstRowId_ + index;
|
|
};
|
|
|
|
|
|
/**
|
|
* Attach text areas or input boxes to the autocomplete by DOM reference. After
|
|
* elements are attached to the autocomplete, when a user types they will see
|
|
* the autocomplete drop down.
|
|
* @param {...Element} var_args Variable args: Input or text area elements to
|
|
* attach the autocomplete too.
|
|
*/
|
|
goog.ui.ac.AutoComplete.prototype.attachInputs = function(var_args) {
|
|
// Delegate to the input handler
|
|
var inputHandler = /** @type {goog.ui.ac.InputHandler} */
|
|
(this.selectionHandler_);
|
|
inputHandler.attachInputs.apply(inputHandler, arguments);
|
|
};
|
|
|
|
|
|
/**
|
|
* Detach text areas or input boxes to the autocomplete by DOM reference.
|
|
* @param {...Element} var_args Variable args: Input or text area elements to
|
|
* detach from the autocomplete.
|
|
*/
|
|
goog.ui.ac.AutoComplete.prototype.detachInputs = function(var_args) {
|
|
// Delegate to the input handler
|
|
var inputHandler = /** @type {goog.ui.ac.InputHandler} */
|
|
(this.selectionHandler_);
|
|
inputHandler.detachInputs.apply(inputHandler, arguments);
|
|
|
|
// Remove mapping from input to anchor if one exists.
|
|
goog.array.forEach(arguments, function(input) {
|
|
goog.object.remove(this.inputToAnchorMap_, goog.getUid(input));
|
|
}, this);
|
|
};
|
|
|
|
|
|
/**
|
|
* Attaches the autocompleter to a text area or text input element
|
|
* with an anchor element. The anchor element is the element the
|
|
* autocomplete box will be positioned against.
|
|
* @param {Element} inputElement The input element. May be 'textarea',
|
|
* text 'input' element, or any other element that exposes similar
|
|
* interface.
|
|
* @param {Element} anchorElement The anchor element.
|
|
*/
|
|
goog.ui.ac.AutoComplete.prototype.attachInputWithAnchor = function(
|
|
inputElement, anchorElement) {
|
|
this.inputToAnchorMap_[goog.getUid(inputElement)] = anchorElement;
|
|
this.attachInputs(inputElement);
|
|
};
|
|
|
|
|
|
/**
|
|
* Forces an update of the display.
|
|
* @param {boolean=} opt_force Whether to force an update.
|
|
*/
|
|
goog.ui.ac.AutoComplete.prototype.update = function(opt_force) {
|
|
var inputHandler = /** @type {goog.ui.ac.InputHandler} */
|
|
(this.selectionHandler_);
|
|
inputHandler.update(opt_force);
|
|
};
|