Updated
This commit is contained in:
@@ -0,0 +1,50 @@
|
||||
// Copyright 2012 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 Utility methods supporting the autocomplete package.
|
||||
*
|
||||
* @see ../../demos/autocomplete-basic.html
|
||||
*/
|
||||
|
||||
goog.provide('goog.ui.ac');
|
||||
|
||||
goog.require('goog.ui.ac.ArrayMatcher');
|
||||
goog.require('goog.ui.ac.AutoComplete');
|
||||
goog.require('goog.ui.ac.InputHandler');
|
||||
goog.require('goog.ui.ac.Renderer');
|
||||
|
||||
|
||||
/**
|
||||
* Factory function for building a basic autocomplete widget that autocompletes
|
||||
* an inputbox or text area from a data array.
|
||||
* @param {Array} data Data array.
|
||||
* @param {Element} input Input element or text area.
|
||||
* @param {boolean=} opt_multi Whether to allow multiple entries separated with
|
||||
* semi-colons or commas.
|
||||
* @param {boolean=} opt_useSimilar use similar matches. e.g. "gost" => "ghost".
|
||||
* @return {!goog.ui.ac.AutoComplete} A new autocomplete object.
|
||||
*/
|
||||
goog.ui.ac.createSimpleAutoComplete =
|
||||
function(data, input, opt_multi, opt_useSimilar) {
|
||||
var matcher = new goog.ui.ac.ArrayMatcher(data, !opt_useSimilar);
|
||||
var renderer = new goog.ui.ac.Renderer();
|
||||
var inputHandler = new goog.ui.ac.InputHandler(null, null, !!opt_multi);
|
||||
|
||||
var autoComplete = new goog.ui.ac.AutoComplete(
|
||||
matcher, renderer, inputHandler);
|
||||
inputHandler.attachAutoComplete(autoComplete);
|
||||
inputHandler.attachInputs(input);
|
||||
return autoComplete;
|
||||
};
|
||||
@@ -0,0 +1,164 @@
|
||||
// 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 Basic class for matching words in an array.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
goog.provide('goog.ui.ac.ArrayMatcher');
|
||||
|
||||
goog.require('goog.iter');
|
||||
goog.require('goog.string');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Basic class for matching words in an array
|
||||
* @constructor
|
||||
* @param {Array} rows Dictionary of items to match. Can be objects if they
|
||||
* have a toString method that returns the value to match against.
|
||||
* @param {boolean=} opt_noSimilar if true, do not do similarity matches for the
|
||||
* input token against the dictionary.
|
||||
*/
|
||||
goog.ui.ac.ArrayMatcher = function(rows, opt_noSimilar) {
|
||||
this.rows_ = rows;
|
||||
this.useSimilar_ = !opt_noSimilar;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Replaces the rows that this object searches over.
|
||||
* @param {Array} rows Dictionary of items to match.
|
||||
*/
|
||||
goog.ui.ac.ArrayMatcher.prototype.setRows = function(rows) {
|
||||
this.rows_ = rows;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Function used to pass matches to the autocomplete
|
||||
* @param {string} token Token to match.
|
||||
* @param {number} maxMatches Max number of matches to return.
|
||||
* @param {Function} matchHandler callback to execute after matching.
|
||||
* @param {string=} opt_fullString The full string from the input box.
|
||||
*/
|
||||
goog.ui.ac.ArrayMatcher.prototype.requestMatchingRows =
|
||||
function(token, maxMatches, matchHandler, opt_fullString) {
|
||||
|
||||
var matches = this.getPrefixMatches(token, maxMatches);
|
||||
|
||||
if (matches.length == 0 && this.useSimilar_) {
|
||||
matches = this.getSimilarRows(token, maxMatches);
|
||||
}
|
||||
matchHandler(token, matches);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Matches the token against the start of words in the row.
|
||||
* @param {string} token Token to match.
|
||||
* @param {number} maxMatches Max number of matches to return.
|
||||
* @return {Array} Rows that match.
|
||||
*/
|
||||
goog.ui.ac.ArrayMatcher.prototype.getPrefixMatches =
|
||||
function(token, maxMatches) {
|
||||
var matches = [];
|
||||
|
||||
if (token != '') {
|
||||
var escapedToken = goog.string.regExpEscape(token);
|
||||
var matcher = new RegExp('(^|\\W+)' + escapedToken, 'i');
|
||||
|
||||
goog.iter.some(this.rows_, function(row) {
|
||||
if (String(row).match(matcher)) {
|
||||
matches.push(row);
|
||||
}
|
||||
return matches.length >= maxMatches;
|
||||
});
|
||||
}
|
||||
return matches;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Matches the token against similar rows, by calculating "distance" between the
|
||||
* terms.
|
||||
* @param {string} token Token to match.
|
||||
* @param {number} maxMatches Max number of matches to return.
|
||||
* @return {Array} The best maxMatches rows.
|
||||
*/
|
||||
goog.ui.ac.ArrayMatcher.prototype.getSimilarRows =
|
||||
function(token, maxMatches) {
|
||||
|
||||
var results = [];
|
||||
|
||||
goog.iter.forEach(this.rows_, function(row, index) {
|
||||
var str = token.toLowerCase();
|
||||
var txt = String(row).toLowerCase();
|
||||
var score = 0;
|
||||
|
||||
if (txt.indexOf(str) != -1) {
|
||||
score = parseInt((txt.indexOf(str) / 4).toString(), 10);
|
||||
|
||||
} else {
|
||||
var arr = str.split('');
|
||||
|
||||
var lastPos = -1;
|
||||
var penalty = 10;
|
||||
|
||||
for (var i = 0, c; c = arr[i]; i++) {
|
||||
var pos = txt.indexOf(c);
|
||||
|
||||
if (pos > lastPos) {
|
||||
var diff = pos - lastPos - 1;
|
||||
|
||||
if (diff > penalty - 5) {
|
||||
diff = penalty - 5;
|
||||
}
|
||||
|
||||
score += diff;
|
||||
|
||||
lastPos = pos;
|
||||
} else {
|
||||
score += penalty;
|
||||
penalty += 5;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (score < str.length * 6) {
|
||||
results.push({
|
||||
str: row,
|
||||
score: score,
|
||||
index: index
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
results.sort(function(a, b) {
|
||||
var diff = a.score - b.score;
|
||||
if (diff != 0) {
|
||||
return diff;
|
||||
}
|
||||
return a.index - b.index;
|
||||
});
|
||||
|
||||
var matches = [];
|
||||
for (var i = 0; i < maxMatches && i < results.length; i++) {
|
||||
matches.push(results[i].str);
|
||||
}
|
||||
|
||||
return matches;
|
||||
};
|
||||
@@ -0,0 +1,736 @@
|
||||
// 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.events');
|
||||
goog.require('goog.events.EventTarget');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* This is the central manager class for an AutoComplete instance.
|
||||
*
|
||||
* @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.
|
||||
* @type {Object}
|
||||
* @protected
|
||||
* @suppress {underscore}
|
||||
*/
|
||||
this.matcher_ = matcher;
|
||||
|
||||
/**
|
||||
* A handler which interacts with the input DOM element (textfield, textarea,
|
||||
* or richedit).
|
||||
* @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);
|
||||
|
||||
/**
|
||||
* Currently typed token which will be used for completion.
|
||||
* @type {?string}
|
||||
* @protected
|
||||
* @suppress {underscore}
|
||||
*/
|
||||
this.token_ = null;
|
||||
|
||||
/**
|
||||
* Autcomplete 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.
|
||||
* @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'
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns the renderer that renders/shows/highlights/hides the autocomplete
|
||||
* menu.
|
||||
* @return {goog.events.EventTarget} Renderer used by the this widget.
|
||||
*/
|
||||
goog.ui.ac.AutoComplete.prototype.getRenderer = function() {
|
||||
return this.renderer_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* 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:
|
||||
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.
|
||||
*/
|
||||
goog.ui.ac.AutoComplete.prototype.getRowCount = function() {
|
||||
return this.rows_.length;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Moves the hilite to the next row, or does nothing if we're already at the
|
||||
* end of the current set of matches. 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;
|
||||
if (this.hiliteId_ >= this.firstRowId_ && this.hiliteId_ < lastId) {
|
||||
this.hiliteId(this.hiliteId_ + 1);
|
||||
return true;
|
||||
} else if (this.hiliteId_ == -1) {
|
||||
this.hiliteId(this.firstRowId_);
|
||||
return true;
|
||||
} else if (this.hiliteId_ == lastId) {
|
||||
if (this.allowFreeSelect_) {
|
||||
this.hiliteId(-1);
|
||||
return false;
|
||||
} else if (this.wrap_) {
|
||||
this.hiliteId(this.firstRowId_);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Moves the hilite to the previous row, or does nothing if we're already at
|
||||
* the beginning of the current set of matches. Calls renderer.hiliteId()
|
||||
* when there's something to do.
|
||||
* @return {boolean} Returns true on a successful hilite.
|
||||
*/
|
||||
goog.ui.ac.AutoComplete.prototype.hilitePrev = function() {
|
||||
if (this.hiliteId_ > this.firstRowId_) {
|
||||
this.hiliteId(this.hiliteId_ - 1);
|
||||
return true;
|
||||
} else if (this.allowFreeSelect_ && this.hiliteId_ == this.firstRowId_) {
|
||||
this.hiliteId(-1);
|
||||
return false;
|
||||
} else if (this.wrap_ &&
|
||||
(this.hiliteId_ == -1 || this.hiliteId_ == this.firstRowId_)) {
|
||||
var lastId = this.firstRowId_ + this.rows_.length - 1;
|
||||
this.hiliteId(lastId);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Hilites the id if it's valid, otherwise does nothing.
|
||||
* @param {number} id A row id (not index).
|
||||
* @return {boolean} Whether the id was hilited.
|
||||
*/
|
||||
goog.ui.ac.AutoComplete.prototype.hiliteId = function(id) {
|
||||
this.hiliteId_ = id;
|
||||
this.renderer_.hiliteId(id);
|
||||
return this.getIndexOfId(id) != -1;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Hilites the index, if it's valid, 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);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
if ((autoHilite || indexToHilite >= 0) &&
|
||||
rendRows.length != 0 &&
|
||||
this.token_) {
|
||||
var idToHilite = indexToHilite >= 0 ?
|
||||
this.getIdOfIndex_(indexToHilite) : this.firstRowId_;
|
||||
this.hiliteId(idToHilite);
|
||||
} else {
|
||||
this.hiliteId_ = -1;
|
||||
}
|
||||
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);
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,114 @@
|
||||
// Copyright 2007 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 Factory class to create a simple autocomplete that will match
|
||||
* from an array of data provided via ajax.
|
||||
*
|
||||
* @see ../../demos/autocompleteremote.html
|
||||
*/
|
||||
|
||||
goog.provide('goog.ui.ac.Remote');
|
||||
|
||||
goog.require('goog.ui.ac.AutoComplete');
|
||||
goog.require('goog.ui.ac.InputHandler');
|
||||
goog.require('goog.ui.ac.RemoteArrayMatcher');
|
||||
goog.require('goog.ui.ac.Renderer');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Factory class for building a remote autocomplete widget that autocompletes
|
||||
* an inputbox or text area from a data array provided via ajax.
|
||||
* @param {string} url The Uri which generates the auto complete matches.
|
||||
* @param {Element} input Input element or text area.
|
||||
* @param {boolean=} opt_multi Whether to allow multiple entries; defaults
|
||||
* to false.
|
||||
* @param {boolean=} opt_useSimilar Whether to use similar matches; e.g.
|
||||
* "gost" => "ghost".
|
||||
* @constructor
|
||||
* @extends {goog.ui.ac.AutoComplete}
|
||||
*/
|
||||
goog.ui.ac.Remote = function(url, input, opt_multi, opt_useSimilar) {
|
||||
var matcher = new goog.ui.ac.RemoteArrayMatcher(url, !opt_useSimilar);
|
||||
this.matcher_ = matcher;
|
||||
|
||||
var renderer = new goog.ui.ac.Renderer();
|
||||
|
||||
var inputhandler = new goog.ui.ac.InputHandler(null, null, !!opt_multi, 300);
|
||||
|
||||
goog.ui.ac.AutoComplete.call(this, matcher, renderer, inputhandler);
|
||||
|
||||
inputhandler.attachAutoComplete(this);
|
||||
inputhandler.attachInputs(input);
|
||||
};
|
||||
goog.inherits(goog.ui.ac.Remote, goog.ui.ac.AutoComplete);
|
||||
|
||||
|
||||
/**
|
||||
* Set whether or not standard highlighting should be used when rendering rows.
|
||||
* @param {boolean} useStandardHighlighting true if standard highlighting used.
|
||||
*/
|
||||
goog.ui.ac.Remote.prototype.setUseStandardHighlighting =
|
||||
function(useStandardHighlighting) {
|
||||
this.renderer_.setUseStandardHighlighting(useStandardHighlighting);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the attached InputHandler object.
|
||||
* @return {goog.ui.ac.InputHandler} The input handler.
|
||||
*/
|
||||
goog.ui.ac.Remote.prototype.getInputHandler = function() {
|
||||
return /** @type {goog.ui.ac.InputHandler} */ (
|
||||
this.selectionHandler_);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Set the send method ("GET", "POST") for the matcher.
|
||||
* @param {string} method The send method; default: GET.
|
||||
*/
|
||||
goog.ui.ac.Remote.prototype.setMethod = function(method) {
|
||||
this.matcher_.setMethod(method);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Set the post data for the matcher.
|
||||
* @param {string} content Post data.
|
||||
*/
|
||||
goog.ui.ac.Remote.prototype.setContent = function(content) {
|
||||
this.matcher_.setContent(content);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Set the HTTP headers for the matcher.
|
||||
* @param {Object|goog.structs.Map} headers Map of headers to add to the
|
||||
* request.
|
||||
*/
|
||||
goog.ui.ac.Remote.prototype.setHeaders = function(headers) {
|
||||
this.matcher_.setHeaders(headers);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Set the timeout interval for the matcher.
|
||||
* @param {number} interval Number of milliseconds after which an
|
||||
* incomplete request will be aborted; 0 means no timeout is set.
|
||||
*/
|
||||
goog.ui.ac.Remote.prototype.setTimeoutInterval = function(interval) {
|
||||
this.matcher_.setTimeoutInterval(interval);
|
||||
};
|
||||
@@ -0,0 +1,270 @@
|
||||
// Copyright 2007 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 Class that retrieves autocomplete matches via an ajax call.
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.ui.ac.RemoteArrayMatcher');
|
||||
|
||||
goog.require('goog.Disposable');
|
||||
goog.require('goog.Uri');
|
||||
goog.require('goog.events');
|
||||
goog.require('goog.json');
|
||||
goog.require('goog.net.XhrIo');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* An array matcher that requests matches via ajax.
|
||||
* @param {string} url The Uri which generates the auto complete matches. The
|
||||
* search term is passed to the server as the 'token' query param.
|
||||
* @param {boolean=} opt_noSimilar If true, request that the server does not do
|
||||
* similarity matches for the input token against the dictionary.
|
||||
* The value is sent to the server as the 'use_similar' query param which is
|
||||
* either "1" (opt_noSimilar==false) or "0" (opt_noSimilar==true).
|
||||
* @constructor
|
||||
* @extends {goog.Disposable}
|
||||
*/
|
||||
goog.ui.ac.RemoteArrayMatcher = function(url, opt_noSimilar) {
|
||||
goog.Disposable.call(this);
|
||||
|
||||
/**
|
||||
* The base URL for the ajax call. The token and max_matches are added as
|
||||
* query params.
|
||||
* @type {string}
|
||||
* @private
|
||||
*/
|
||||
this.url_ = url;
|
||||
|
||||
/**
|
||||
* Whether similar matches should be found as well. This is sent as a hint
|
||||
* to the server only.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
this.useSimilar_ = !opt_noSimilar;
|
||||
|
||||
/**
|
||||
* The XhrIo object used for making remote requests. When a new request
|
||||
* is made, the current one is aborted and the new one sent.
|
||||
* @type {goog.net.XhrIo}
|
||||
* @private
|
||||
*/
|
||||
this.xhr_ = new goog.net.XhrIo();
|
||||
};
|
||||
goog.inherits(goog.ui.ac.RemoteArrayMatcher, goog.Disposable);
|
||||
|
||||
|
||||
/**
|
||||
* The HTTP send method (GET, POST) to use when making the ajax call.
|
||||
* @type {string}
|
||||
* @private
|
||||
*/
|
||||
goog.ui.ac.RemoteArrayMatcher.prototype.method_ = 'GET';
|
||||
|
||||
|
||||
/**
|
||||
* Data to submit during a POST.
|
||||
* @type {string|undefined}
|
||||
* @private
|
||||
*/
|
||||
goog.ui.ac.RemoteArrayMatcher.prototype.content_ = undefined;
|
||||
|
||||
|
||||
/**
|
||||
* Headers to send with every HTTP request.
|
||||
* @type {Object|goog.structs.Map}
|
||||
* @private
|
||||
*/
|
||||
goog.ui.ac.RemoteArrayMatcher.prototype.headers_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* Key to the listener on XHR. Used to clear previous listeners.
|
||||
* @type {?number}
|
||||
* @private
|
||||
*/
|
||||
goog.ui.ac.RemoteArrayMatcher.prototype.lastListenerKey_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* Set the send method ("GET", "POST").
|
||||
* @param {string} method The send method; default: GET.
|
||||
*/
|
||||
goog.ui.ac.RemoteArrayMatcher.prototype.setMethod = function(method) {
|
||||
this.method_ = method;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Set the post data.
|
||||
* @param {string} content Post data.
|
||||
*/
|
||||
goog.ui.ac.RemoteArrayMatcher.prototype.setContent = function(content) {
|
||||
this.content_ = content;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Set the HTTP headers.
|
||||
* @param {Object|goog.structs.Map} headers Map of headers to add to the
|
||||
* request.
|
||||
*/
|
||||
goog.ui.ac.RemoteArrayMatcher.prototype.setHeaders = function(headers) {
|
||||
this.headers_ = headers;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Set the timeout interval.
|
||||
* @param {number} interval Number of milliseconds after which an
|
||||
* incomplete request will be aborted; 0 means no timeout is set.
|
||||
*/
|
||||
goog.ui.ac.RemoteArrayMatcher.prototype.setTimeoutInterval =
|
||||
function(interval) {
|
||||
this.xhr_.setTimeoutInterval(interval);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Builds a complete GET-style URL, given the base URI and autocomplete related
|
||||
* parameter values.
|
||||
* <b>Override this to build any customized lookup URLs.</b>
|
||||
* <b>Can be used to change request method and any post content as well.</b>
|
||||
* @param {string} uri The base URI of the request target.
|
||||
* @param {string} token Current token in autocomplete.
|
||||
* @param {number} maxMatches Maximum number of matches required.
|
||||
* @param {boolean} useSimilar A hint to the server.
|
||||
* @param {string=} opt_fullString Complete text in the input element.
|
||||
* @return {?string} The complete url. Return null if no request should be sent.
|
||||
* @protected
|
||||
*/
|
||||
goog.ui.ac.RemoteArrayMatcher.prototype.buildUrl = function(uri,
|
||||
token, maxMatches, useSimilar, opt_fullString) {
|
||||
var url = new goog.Uri(uri);
|
||||
url.setParameterValue('token', token);
|
||||
url.setParameterValue('max_matches', String(maxMatches));
|
||||
url.setParameterValue('use_similar', String(Number(useSimilar)));
|
||||
return url.toString();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether the suggestions should be updated?
|
||||
* <b>Override this to prevent updates eg - when token is empty.</b>
|
||||
* @param {string} uri The base URI of the request target.
|
||||
* @param {string} token Current token in autocomplete.
|
||||
* @param {number} maxMatches Maximum number of matches required.
|
||||
* @param {boolean} useSimilar A hint to the server.
|
||||
* @param {string=} opt_fullString Complete text in the input element.
|
||||
* @return {boolean} Whether new matches be requested.
|
||||
* @protected
|
||||
*/
|
||||
goog.ui.ac.RemoteArrayMatcher.prototype.shouldRequestMatches =
|
||||
function(uri, token, maxMatches, useSimilar, opt_fullString) {
|
||||
return true;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Parses and retrieves the array of suggestions from XHR response.
|
||||
* <b>Override this if the response is not a simple JSON array.</b>
|
||||
* @param {string} responseText The XHR response text.
|
||||
* @return {Array.<string>} The array of suggestions.
|
||||
* @protected
|
||||
*/
|
||||
goog.ui.ac.RemoteArrayMatcher.prototype.parseResponseText = function(
|
||||
responseText) {
|
||||
|
||||
var matches = [];
|
||||
// If there is no response text, unsafeParse will throw a syntax error.
|
||||
if (responseText) {
|
||||
/** @preserveTry */
|
||||
try {
|
||||
matches = goog.json.unsafeParse(responseText);
|
||||
} catch (exception) {
|
||||
}
|
||||
}
|
||||
return /** @type {Array.<string>} */ (matches);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handles the XHR response.
|
||||
* @param {string} token The XHR autocomplete token.
|
||||
* @param {Function} matchHandler The AutoComplete match handler.
|
||||
* @param {goog.events.Event} event The XHR success event.
|
||||
*/
|
||||
goog.ui.ac.RemoteArrayMatcher.prototype.xhrCallback = function(token,
|
||||
matchHandler, event) {
|
||||
var text = event.target.getResponseText();
|
||||
matchHandler(token, this.parseResponseText(text));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve a set of matching rows from the server via ajax.
|
||||
* @param {string} token The text that should be matched; passed to the server
|
||||
* as the 'token' query param.
|
||||
* @param {number} maxMatches The maximum number of matches requested from the
|
||||
* server; passed as the 'max_matches' query param. The server is
|
||||
* responsible for limiting the number of matches that are returned.
|
||||
* @param {Function} matchHandler Callback to execute on the result after
|
||||
* matching.
|
||||
* @param {string=} opt_fullString The full string from the input box.
|
||||
*/
|
||||
goog.ui.ac.RemoteArrayMatcher.prototype.requestMatchingRows =
|
||||
function(token, maxMatches, matchHandler, opt_fullString) {
|
||||
|
||||
if (!this.shouldRequestMatches(this.url_, token, maxMatches, this.useSimilar_,
|
||||
opt_fullString)) {
|
||||
return;
|
||||
}
|
||||
// Set the query params on the URL.
|
||||
var url = this.buildUrl(this.url_, token, maxMatches, this.useSimilar_,
|
||||
opt_fullString);
|
||||
if (!url) {
|
||||
// Do nothing if there is no URL.
|
||||
return;
|
||||
}
|
||||
|
||||
// The callback evals the server response and calls the match handler on
|
||||
// the array of matches.
|
||||
var callback = goog.bind(this.xhrCallback, this, token, matchHandler);
|
||||
|
||||
// Abort the current request and issue the new one; prevent requests from
|
||||
// being queued up by the browser with a slow server
|
||||
if (this.xhr_.isActive()) {
|
||||
this.xhr_.abort();
|
||||
}
|
||||
// This ensures if previous XHR is aborted or ends with error, the
|
||||
// corresponding success-callbacks are cleared.
|
||||
if (this.lastListenerKey_) {
|
||||
goog.events.unlistenByKey(this.lastListenerKey_);
|
||||
}
|
||||
// Listen once ensures successful callback gets cleared by itself.
|
||||
this.lastListenerKey_ = goog.events.listenOnce(this.xhr_,
|
||||
goog.net.EventType.SUCCESS, callback);
|
||||
this.xhr_.send(url, this.method_, this.content_, this.headers_);
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
goog.ui.ac.RemoteArrayMatcher.prototype.disposeInternal = function() {
|
||||
this.xhr_.dispose();
|
||||
goog.ui.ac.RemoteArrayMatcher.superClass_.disposeInternal.call(
|
||||
this);
|
||||
};
|
||||
@@ -0,0 +1,936 @@
|
||||
// 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 Class for rendering the results of an auto complete and
|
||||
* allow the user to select an row.
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.ui.ac.Renderer');
|
||||
goog.provide('goog.ui.ac.Renderer.CustomRenderer');
|
||||
|
||||
goog.require('goog.dispose');
|
||||
goog.require('goog.dom');
|
||||
goog.require('goog.dom.a11y');
|
||||
goog.require('goog.dom.classes');
|
||||
goog.require('goog.events.Event');
|
||||
goog.require('goog.events.EventTarget');
|
||||
goog.require('goog.events.EventType');
|
||||
goog.require('goog.fx.dom.FadeInAndShow');
|
||||
goog.require('goog.fx.dom.FadeOutAndHide');
|
||||
goog.require('goog.iter');
|
||||
goog.require('goog.positioning');
|
||||
goog.require('goog.positioning.Corner');
|
||||
goog.require('goog.positioning.Overflow');
|
||||
goog.require('goog.string');
|
||||
goog.require('goog.style');
|
||||
goog.require('goog.ui.IdGenerator');
|
||||
goog.require('goog.ui.ac.AutoComplete.EventType');
|
||||
goog.require('goog.userAgent');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Class for rendering the results of an auto-complete in a drop down list.
|
||||
*
|
||||
* @constructor
|
||||
* @param {Element=} opt_parentNode optional reference to the parent element
|
||||
* that will hold the autocomplete elements. goog.dom.getDocument().body
|
||||
* will be used if this is null.
|
||||
* @param {?({renderRow}|{render})=} opt_customRenderer Custom full renderer to
|
||||
* render each row. Should be something with a renderRow or render method.
|
||||
* @param {boolean=} opt_rightAlign Determines if the autocomplete will always
|
||||
* be right aligned. False by default.
|
||||
* @param {boolean=} opt_useStandardHighlighting Determines if standard
|
||||
* highlighting should be applied to each row of data. Standard highlighting
|
||||
* bolds every matching substring for a given token in each row.
|
||||
* @extends {goog.events.EventTarget}
|
||||
*/
|
||||
goog.ui.ac.Renderer = function(opt_parentNode, opt_customRenderer,
|
||||
opt_rightAlign, opt_useStandardHighlighting) {
|
||||
goog.base(this);
|
||||
|
||||
/**
|
||||
* Reference to the parent element that will hold the autocomplete elements
|
||||
* @type {Element}
|
||||
* @private
|
||||
*/
|
||||
this.parent_ = opt_parentNode || goog.dom.getDocument().body;
|
||||
|
||||
/**
|
||||
* Dom helper for the parent element's document.
|
||||
* @type {goog.dom.DomHelper}
|
||||
* @private
|
||||
*/
|
||||
this.dom_ = goog.dom.getDomHelper(this.parent_);
|
||||
|
||||
/**
|
||||
* Whether to reposition the autocomplete UI below the target node
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
this.reposition_ = !opt_parentNode;
|
||||
|
||||
/**
|
||||
* Reference to the main element that controls the rendered autocomplete
|
||||
* @type {Element}
|
||||
* @private
|
||||
*/
|
||||
this.element_ = null;
|
||||
|
||||
/**
|
||||
* The current token that has been entered
|
||||
* @type {string}
|
||||
* @private
|
||||
*/
|
||||
this.token_ = '';
|
||||
|
||||
/**
|
||||
* Array used to store the current set of rows being displayed
|
||||
* @type {Array}
|
||||
* @private
|
||||
*/
|
||||
this.rows_ = [];
|
||||
|
||||
/**
|
||||
* Array of the node divs that hold each result that is being displayed.
|
||||
* @type {Array.<Element>}
|
||||
* @protected
|
||||
* @suppress {underscore}
|
||||
*/
|
||||
this.rowDivs_ = [];
|
||||
|
||||
/**
|
||||
* The index of the currently highlighted row
|
||||
* @type {number}
|
||||
* @protected
|
||||
* @suppress {underscore}
|
||||
*/
|
||||
this.hilitedRow_ = -1;
|
||||
|
||||
/**
|
||||
* The time that the rendering of the menu rows started
|
||||
* @type {number}
|
||||
* @protected
|
||||
* @suppress {underscore}
|
||||
*/
|
||||
this.startRenderingRows_ = -1;
|
||||
|
||||
/**
|
||||
* Store the current state for the renderer
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
this.visible_ = false;
|
||||
|
||||
/**
|
||||
* Classname for the main element
|
||||
* @type {string}
|
||||
*/
|
||||
this.className = goog.getCssName('ac-renderer');
|
||||
|
||||
/**
|
||||
* Classname for row divs
|
||||
* @type {string}
|
||||
*/
|
||||
this.rowClassName = goog.getCssName('ac-row');
|
||||
|
||||
// TODO(gboyer): Remove this as soon as we remove references and ensure that
|
||||
// no groups are pushing javascript using this.
|
||||
/**
|
||||
* The old class name for active row. This name is deprecated because its
|
||||
* name is generic enough that a typical implementation would require a
|
||||
* descendant selector.
|
||||
* Active row will have rowClassName & activeClassName &
|
||||
* legacyActiveClassName.
|
||||
* @type {string}
|
||||
* @private
|
||||
*/
|
||||
this.legacyActiveClassName_ = goog.getCssName('active');
|
||||
|
||||
/**
|
||||
* Class name for active row div.
|
||||
* Active row will have rowClassName & activeClassName &
|
||||
* legacyActiveClassName.
|
||||
* @type {string}
|
||||
*/
|
||||
this.activeClassName = goog.getCssName('ac-active');
|
||||
|
||||
/**
|
||||
* Class name for the bold tag highlighting the matched part of the text.
|
||||
* @type {string}
|
||||
*/
|
||||
this.highlightedClassName = goog.getCssName('ac-highlighted');
|
||||
|
||||
/**
|
||||
* Custom full renderer
|
||||
* @type {?({renderRow}|{render})}
|
||||
* @private
|
||||
*/
|
||||
this.customRenderer_ = opt_customRenderer || null;
|
||||
|
||||
/**
|
||||
* Flag to indicate whether standard highlighting should be applied.
|
||||
* this is set to true if left unspecified to retain existing
|
||||
* behaviour for autocomplete clients
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
this.useStandardHighlighting_ = opt_useStandardHighlighting != null ?
|
||||
opt_useStandardHighlighting : true;
|
||||
|
||||
/**
|
||||
* Flag to set all tokens as highlighted in the autocomplete row.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
this.highlightAllTokens_ = false;
|
||||
|
||||
/**
|
||||
* Determines if the autocomplete will always be right aligned
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
this.rightAlign_ = !!opt_rightAlign;
|
||||
|
||||
/**
|
||||
* Whether to align with top of target field
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
this.topAlign_ = false;
|
||||
|
||||
/**
|
||||
* Duration (in msec) of fade animation when menu is shown/hidden.
|
||||
* Setting to 0 (default) disables animation entirely.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
this.menuFadeDuration_ = 0;
|
||||
|
||||
/**
|
||||
* Animation in progress, if any.
|
||||
* @type {goog.fx.Animation|undefined}
|
||||
*/
|
||||
this.animation_;
|
||||
};
|
||||
goog.inherits(goog.ui.ac.Renderer, goog.events.EventTarget);
|
||||
|
||||
|
||||
/**
|
||||
* The anchor element to position the rendered autocompleter against.
|
||||
* @type {Element}
|
||||
* @private
|
||||
*/
|
||||
goog.ui.ac.Renderer.prototype.anchorElement_;
|
||||
|
||||
|
||||
/**
|
||||
* The element on which to base the width of the autocomplete.
|
||||
* @type {Node}
|
||||
* @private
|
||||
*/
|
||||
goog.ui.ac.Renderer.prototype.widthProvider_;
|
||||
|
||||
|
||||
/**
|
||||
* The delay before mouseover events are registered, in milliseconds
|
||||
* @type {number}
|
||||
* @const
|
||||
*/
|
||||
goog.ui.ac.Renderer.DELAY_BEFORE_MOUSEOVER = 300;
|
||||
|
||||
|
||||
/**
|
||||
* Gets the renderer's element.
|
||||
* @return {Element} The main element that controls the rendered autocomplete.
|
||||
*/
|
||||
goog.ui.ac.Renderer.prototype.getElement = function() {
|
||||
return this.element_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sets the width provider element. The provider is only used on redraw and as
|
||||
* such will not automatically update on resize.
|
||||
* @param {Node} widthProvider The element whose width should be mirrored.
|
||||
*/
|
||||
goog.ui.ac.Renderer.prototype.setWidthProvider = function(widthProvider) {
|
||||
this.widthProvider_ = widthProvider;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Set whether to align autocomplete to top of target element
|
||||
* @param {boolean} align If true, align to top.
|
||||
*/
|
||||
goog.ui.ac.Renderer.prototype.setTopAlign = function(align) {
|
||||
this.topAlign_ = align;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Set whether to align autocomplete to the right of the target element.
|
||||
* @param {boolean} align If true, align to right.
|
||||
*/
|
||||
goog.ui.ac.Renderer.prototype.setRightAlign = function(align) {
|
||||
this.rightAlign_ = align;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Set whether or not standard highlighting should be used when rendering rows.
|
||||
* @param {boolean} useStandardHighlighting true if standard highlighting used.
|
||||
*/
|
||||
goog.ui.ac.Renderer.prototype.setUseStandardHighlighting =
|
||||
function(useStandardHighlighting) {
|
||||
this.useStandardHighlighting_ = useStandardHighlighting;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Set whether or not to highlight all matching tokens rather than just the
|
||||
* first.
|
||||
* @param {boolean} highlightAllTokens Whether to highlight all matching tokens
|
||||
* rather than just the first.
|
||||
*/
|
||||
goog.ui.ac.Renderer.prototype.setHighlightAllTokens =
|
||||
function(highlightAllTokens) {
|
||||
this.highlightAllTokens_ = highlightAllTokens;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sets the duration (in msec) of the fade animation when menu is shown/hidden.
|
||||
* Setting to 0 (default) disables animation entirely.
|
||||
* @param {number} duration Duration (in msec) of the fade animation (or 0 for
|
||||
* no animation).
|
||||
*/
|
||||
goog.ui.ac.Renderer.prototype.setMenuFadeDuration = function(duration) {
|
||||
this.menuFadeDuration_ = duration;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sets the anchor element for the subsequent call to renderRows.
|
||||
* @param {Element} anchor The anchor element.
|
||||
*/
|
||||
goog.ui.ac.Renderer.prototype.setAnchorElement = function(anchor) {
|
||||
this.anchorElement_ = anchor;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Render the autocomplete UI
|
||||
*
|
||||
* @param {Array} rows Matching UI rows.
|
||||
* @param {string} token Token we are currently matching against.
|
||||
* @param {Element=} opt_target Current HTML node, will position popup beneath
|
||||
* this node.
|
||||
*/
|
||||
goog.ui.ac.Renderer.prototype.renderRows = function(rows, token, opt_target) {
|
||||
this.token_ = token;
|
||||
this.rows_ = rows;
|
||||
this.hilitedRow_ = -1;
|
||||
this.startRenderingRows_ = goog.now();
|
||||
this.target_ = opt_target;
|
||||
this.rowDivs_ = [];
|
||||
this.redraw();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Hide the object.
|
||||
*/
|
||||
goog.ui.ac.Renderer.prototype.dismiss = function() {
|
||||
if (this.target_) {
|
||||
goog.dom.a11y.setActiveDescendant(this.target_, null);
|
||||
}
|
||||
if (this.visible_) {
|
||||
this.visible_ = false;
|
||||
|
||||
// Clear ARIA popup role for the target input box.
|
||||
if (this.target_) {
|
||||
goog.dom.a11y.setState(this.target_, goog.dom.a11y.State.HASPOPUP, false);
|
||||
}
|
||||
|
||||
if (this.menuFadeDuration_ > 0) {
|
||||
goog.dispose(this.animation_);
|
||||
this.animation_ = new goog.fx.dom.FadeOutAndHide(this.element_,
|
||||
this.menuFadeDuration_);
|
||||
this.animation_.play();
|
||||
} else {
|
||||
goog.style.showElement(this.element_, false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Show the object.
|
||||
*/
|
||||
goog.ui.ac.Renderer.prototype.show = function() {
|
||||
if (!this.visible_) {
|
||||
this.visible_ = true;
|
||||
|
||||
// Set ARIA roles and states for the target input box.
|
||||
if (this.target_) {
|
||||
goog.dom.a11y.setRole(this.target_, goog.dom.a11y.Role.COMBOBOX);
|
||||
goog.dom.a11y.setState(
|
||||
this.target_, goog.dom.a11y.State.AUTOCOMPLETE, 'list');
|
||||
goog.dom.a11y.setState(this.target_, goog.dom.a11y.State.HASPOPUP, true);
|
||||
}
|
||||
|
||||
if (this.menuFadeDuration_ > 0) {
|
||||
goog.dispose(this.animation_);
|
||||
this.animation_ = new goog.fx.dom.FadeInAndShow(this.element_,
|
||||
this.menuFadeDuration_);
|
||||
this.animation_.play();
|
||||
} else {
|
||||
goog.style.showElement(this.element_, true);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {boolean} True if the object is visible.
|
||||
*/
|
||||
goog.ui.ac.Renderer.prototype.isVisible = function() {
|
||||
return this.visible_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sets the 'active' class of the nth item.
|
||||
* @param {number} index Index of the item to highlight.
|
||||
*/
|
||||
goog.ui.ac.Renderer.prototype.hiliteRow = function(index) {
|
||||
var rowDiv = index >= 0 && index < this.rowDivs_.length ?
|
||||
this.rowDivs_[index] : undefined;
|
||||
|
||||
var evtObj = {type: goog.ui.ac.AutoComplete.EventType.ROW_HILITE,
|
||||
rowNode: rowDiv};
|
||||
if (this.dispatchEvent(evtObj)) {
|
||||
this.hiliteNone();
|
||||
this.hilitedRow_ = index;
|
||||
if (rowDiv) {
|
||||
goog.dom.classes.add(rowDiv, this.activeClassName,
|
||||
this.legacyActiveClassName_);
|
||||
if (this.target_) {
|
||||
goog.dom.a11y.setActiveDescendant(this.target_, rowDiv);
|
||||
}
|
||||
goog.style.scrollIntoContainerView(rowDiv, this.element_);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Removes the 'active' class from the currently selected row.
|
||||
*/
|
||||
goog.ui.ac.Renderer.prototype.hiliteNone = function() {
|
||||
if (this.hilitedRow_ >= 0) {
|
||||
goog.dom.classes.remove(this.rowDivs_[this.hilitedRow_],
|
||||
this.activeClassName, this.legacyActiveClassName_);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sets the 'active' class of the item with a given id.
|
||||
* @param {number} id Id of the row to hilight. If id is -1 then no rows get
|
||||
* hilited.
|
||||
*/
|
||||
goog.ui.ac.Renderer.prototype.hiliteId = function(id) {
|
||||
if (id == -1) {
|
||||
this.hiliteRow(-1);
|
||||
} else {
|
||||
for (var i = 0; i < this.rows_.length; i++) {
|
||||
if (this.rows_[i].id == id) {
|
||||
this.hiliteRow(i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sets CSS classes on autocomplete conatainer element.
|
||||
*
|
||||
* @param {Element} elt The container element.
|
||||
* @private
|
||||
*/
|
||||
goog.ui.ac.Renderer.prototype.setMenuClasses_ = function(elt) {
|
||||
goog.dom.classes.add(elt, this.className);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* If the main HTML element hasn't been made yet, creates it and appends it
|
||||
* to the parent.
|
||||
* @private
|
||||
*/
|
||||
goog.ui.ac.Renderer.prototype.maybeCreateElement_ = function() {
|
||||
if (!this.element_) {
|
||||
// Make element and add it to the parent
|
||||
var el = this.dom_.createDom('div', {style: 'display:none'});
|
||||
this.element_ = el;
|
||||
this.setMenuClasses_(el);
|
||||
goog.dom.a11y.setRole(el, goog.dom.a11y.Role.LISTBOX);
|
||||
|
||||
el.id = goog.ui.IdGenerator.getInstance().getNextUniqueId();
|
||||
|
||||
this.dom_.appendChild(this.parent_, el);
|
||||
|
||||
// Add this object as an event handler
|
||||
goog.events.listen(el, goog.events.EventType.CLICK,
|
||||
this.handleClick_, false, this);
|
||||
goog.events.listen(el, goog.events.EventType.MOUSEDOWN,
|
||||
this.handleMouseDown_, false, this);
|
||||
goog.events.listen(el, goog.events.EventType.MOUSEOVER,
|
||||
this.handleMouseOver_, false, this);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Redraw (or draw if this is the first call) the rendered auto-complete drop
|
||||
* down.
|
||||
*/
|
||||
goog.ui.ac.Renderer.prototype.redraw = function() {
|
||||
// Create the element if it doesn't yet exist
|
||||
this.maybeCreateElement_();
|
||||
|
||||
// For top aligned with target (= bottom aligned element),
|
||||
// we need to hide and then add elements while hidden to prevent
|
||||
// visible repositioning
|
||||
if (this.topAlign_) {
|
||||
this.element_.style.visibility = 'hidden';
|
||||
}
|
||||
|
||||
if (this.widthProvider_) {
|
||||
var width = this.widthProvider_.clientWidth + 'px';
|
||||
this.element_.style.minWidth = width;
|
||||
}
|
||||
|
||||
// Remove the current child nodes
|
||||
this.rowDivs_.length = 0;
|
||||
this.dom_.removeChildren(this.element_);
|
||||
|
||||
// Generate the new rows (use forEach so we can change rows_ from an
|
||||
// array to a different datastructure if required)
|
||||
if (this.customRenderer_ && this.customRenderer_.render) {
|
||||
this.customRenderer_.render(this, this.element_, this.rows_, this.token_);
|
||||
} else {
|
||||
var curRow = null;
|
||||
goog.iter.forEach(this.rows_, function(row) {
|
||||
row = this.renderRowHtml(row, this.token_);
|
||||
if (this.topAlign_) {
|
||||
// Aligned with top of target = best match at bottom
|
||||
this.element_.insertBefore(row, curRow);
|
||||
} else {
|
||||
this.dom_.appendChild(this.element_, row);
|
||||
}
|
||||
curRow = row;
|
||||
}, this);
|
||||
}
|
||||
|
||||
// Don't show empty result sets
|
||||
if (this.rows_.length == 0) {
|
||||
this.dismiss();
|
||||
return;
|
||||
} else {
|
||||
this.show();
|
||||
}
|
||||
|
||||
this.reposition();
|
||||
|
||||
// Make the autocompleter unselectable, so that it
|
||||
// doesn't steal focus from the input field when clicked.
|
||||
goog.style.setUnselectable(this.element_, true);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Repositions the auto complete popup relative to the location node, if it
|
||||
* exists and the auto position has been set.
|
||||
*/
|
||||
goog.ui.ac.Renderer.prototype.reposition = function() {
|
||||
if (this.target_ && this.reposition_) {
|
||||
var anchorElement = this.anchorElement_ || this.target_;
|
||||
var anchorCorner = this.rightAlign_ ?
|
||||
goog.positioning.Corner.BOTTOM_RIGHT :
|
||||
goog.positioning.Corner.BOTTOM_LEFT;
|
||||
if (this.topAlign_) {
|
||||
anchorCorner = goog.positioning.flipCornerVertical(anchorCorner);
|
||||
}
|
||||
|
||||
goog.positioning.positionAtAnchor(
|
||||
anchorElement, anchorCorner,
|
||||
this.element_, goog.positioning.flipCornerVertical(anchorCorner),
|
||||
null, null, goog.positioning.Overflow.ADJUST_X_EXCEPT_OFFSCREEN);
|
||||
|
||||
if (this.topAlign_) {
|
||||
// This flickers, but is better than the alternative of positioning
|
||||
// in the wrong place and then moving.
|
||||
this.element_.style.visibility = 'visible';
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sets whether the renderer should try to determine where to position the
|
||||
* drop down.
|
||||
* @param {boolean} auto Whether to autoposition the drop down.
|
||||
*/
|
||||
goog.ui.ac.Renderer.prototype.setAutoPosition = function(auto) {
|
||||
this.reposition_ = auto;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Disposes of the renderer and its associated HTML.
|
||||
* @override
|
||||
* @protected
|
||||
*/
|
||||
goog.ui.ac.Renderer.prototype.disposeInternal = function() {
|
||||
if (this.element_) {
|
||||
goog.events.unlisten(this.element_, goog.events.EventType.CLICK,
|
||||
this.handleClick_, false, this);
|
||||
goog.events.unlisten(this.element_, goog.events.EventType.MOUSEDOWN,
|
||||
this.handleMouseDown_, false, this);
|
||||
goog.events.unlisten(this.element_, goog.events.EventType.MOUSEOVER,
|
||||
this.handleMouseOver_, false, this);
|
||||
this.dom_.removeNode(this.element_);
|
||||
this.element_ = null;
|
||||
this.visible_ = false;
|
||||
}
|
||||
|
||||
goog.dispose(this.animation_);
|
||||
this.parent_ = null;
|
||||
|
||||
goog.base(this, 'disposeInternal');
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Generic function that takes a row and renders a DOM structure for that row.
|
||||
*
|
||||
* Normally this will only be matching a maximum of 20 or so items. Even with
|
||||
* 40 rows, DOM this building is fine.
|
||||
*
|
||||
* @param {Object} row Object representing row.
|
||||
* @param {string} token Token to highlight.
|
||||
* @param {Node} node The node to render into.
|
||||
* @private
|
||||
*/
|
||||
goog.ui.ac.Renderer.prototype.renderRowContents_ =
|
||||
function(row, token, node) {
|
||||
node.innerHTML = goog.string.htmlEscape(row.data.toString());
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Goes through a node and all of its child nodes, replacing HTML text that
|
||||
* matches a token with <b>token</b>.
|
||||
*
|
||||
* @param {Node} node Node to match.
|
||||
* @param {string|Array.<string>} tokenOrArray Token to match or array of tokens
|
||||
* to match. By default, only the first match will be highlighted. If
|
||||
* highlightAllTokens is set, then all tokens appearing at the start of a
|
||||
* word, in whatever order and however many times, will be highlighted.
|
||||
* @private
|
||||
*/
|
||||
goog.ui.ac.Renderer.prototype.hiliteMatchingText_ =
|
||||
function(node, tokenOrArray) {
|
||||
if (node.nodeType == goog.dom.NodeType.TEXT) {
|
||||
|
||||
var rest = null;
|
||||
if (goog.isArray(tokenOrArray) &&
|
||||
tokenOrArray.length > 1 &&
|
||||
!this.highlightAllTokens_) {
|
||||
rest = goog.array.slice(tokenOrArray, 1);
|
||||
}
|
||||
|
||||
var token = this.getTokenRegExp_(tokenOrArray);
|
||||
if (token.length == 0) return;
|
||||
|
||||
var text = node.nodeValue;
|
||||
|
||||
// Create a regular expression to match a token at the beginning of a line
|
||||
// or preceeded by non-alpha-numeric characters
|
||||
// NOTE(user): this used to have a (^|\\W+) clause where it now has \\b
|
||||
// but it caused various browsers to hang on really long strings. It is
|
||||
// also excessive, because .*?\W+ is the same as .*?\b since \b already
|
||||
// checks that the character before the token is a non-word character
|
||||
// (the only time the regexp is different is if token begins with a
|
||||
// non-word character), and ^ matches the start of the line or following
|
||||
// a line terminator character, which is also \W. The initial group cannot
|
||||
// just be .*? as it will miss line terminators (which is what the \W+
|
||||
// clause used to match). Instead we use [\s\S] to match every character,
|
||||
// including line terminators.
|
||||
var re = new RegExp('([\\s\\S]*?)\\b(' + token + ')', 'gi');
|
||||
var textNodes = [];
|
||||
var lastIndex = 0;
|
||||
|
||||
// Find all matches
|
||||
// Note: text.split(re) has inconsistencies between IE and FF, so
|
||||
// manually recreated the logic
|
||||
var match = re.exec(text);
|
||||
var numMatches = 0;
|
||||
while (match) {
|
||||
numMatches++;
|
||||
textNodes.push(match[1]);
|
||||
textNodes.push(match[2]);
|
||||
lastIndex = re.lastIndex;
|
||||
match = re.exec(text);
|
||||
}
|
||||
textNodes.push(text.substring(lastIndex));
|
||||
|
||||
// Replace the tokens with bolded text. Each pair of textNodes
|
||||
// (starting at index idx) includes a node of text before the bolded
|
||||
// token, and a node (at idx + 1) consisting of what should be
|
||||
// enclosed in bold tags.
|
||||
if (textNodes.length > 1) {
|
||||
var maxNumToBold = !this.highlightAllTokens_ ? 1 : numMatches;
|
||||
for (var i = 0; i < maxNumToBold; i++) {
|
||||
var idx = 2 * i;
|
||||
|
||||
node.nodeValue = textNodes[idx];
|
||||
var boldTag = this.dom_.createElement('b');
|
||||
boldTag.className = this.highlightedClassName;
|
||||
this.dom_.appendChild(boldTag,
|
||||
this.dom_.createTextNode(textNodes[idx + 1]));
|
||||
boldTag = node.parentNode.insertBefore(boldTag, node.nextSibling);
|
||||
node.parentNode.insertBefore(this.dom_.createTextNode(''),
|
||||
boldTag.nextSibling);
|
||||
node = boldTag.nextSibling;
|
||||
}
|
||||
|
||||
// Append the remaining text nodes to the end.
|
||||
var remainingTextNodes = goog.array.slice(textNodes, maxNumToBold * 2);
|
||||
node.nodeValue = remainingTextNodes.join('');
|
||||
} else if (rest) {
|
||||
this.hiliteMatchingText_(node, rest);
|
||||
}
|
||||
} else {
|
||||
var child = node.firstChild;
|
||||
while (child) {
|
||||
var nextChild = child.nextSibling;
|
||||
this.hiliteMatchingText_(child, tokenOrArray);
|
||||
child = nextChild;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Transforms a token into a string ready to be put into the regular expression
|
||||
* in hiliteMatchingText_.
|
||||
* @param {string|Array.<string>} tokenOrArray The token or array to get the
|
||||
* regex string from.
|
||||
* @return {string} The regex-ready token.
|
||||
* @private
|
||||
*/
|
||||
goog.ui.ac.Renderer.prototype.getTokenRegExp_ = function(tokenOrArray) {
|
||||
var token = '';
|
||||
|
||||
if (!tokenOrArray) {
|
||||
return token;
|
||||
}
|
||||
|
||||
if (goog.isArray(tokenOrArray)) {
|
||||
// Remove invalid tokens from the array, which may leave us with nothing.
|
||||
tokenOrArray = goog.array.filter(tokenOrArray, function(str) {
|
||||
return !goog.string.isEmptySafe(str);
|
||||
});
|
||||
}
|
||||
|
||||
// If highlighting all tokens, join them with '|' so the regular expression
|
||||
// will match on any of them.
|
||||
if (this.highlightAllTokens_) {
|
||||
if (goog.isArray(tokenOrArray)) {
|
||||
var tokenArray = goog.array.map(tokenOrArray, goog.string.regExpEscape);
|
||||
token = tokenArray.join('|');
|
||||
} else {
|
||||
// Remove excess whitespace from the string so bars will separate valid
|
||||
// tokens in the regular expression.
|
||||
token = goog.string.collapseWhitespace(tokenOrArray);
|
||||
|
||||
token = goog.string.regExpEscape(token);
|
||||
token = token.replace(/ /g, '|');
|
||||
}
|
||||
} else {
|
||||
// Not highlighting all matching tokens. If tokenOrArray is a string, use
|
||||
// that as the token. If it is an array, use the first element in the
|
||||
// array.
|
||||
// TODO(user): why is this this way?. We should match against all
|
||||
// tokens in the array, but only accept the first match.
|
||||
if (goog.isArray(tokenOrArray)) {
|
||||
token = tokenOrArray.length > 0 ?
|
||||
goog.string.regExpEscape(tokenOrArray[0]) : '';
|
||||
} else {
|
||||
// For the single-match string token, we refuse to match anything if
|
||||
// the string begins with a non-word character, as matches by definition
|
||||
// can only occur at the start of a word. (This also handles the
|
||||
// goog.string.isEmptySafe(tokenOrArray) case.)
|
||||
if (!/^\W/.test(tokenOrArray)) {
|
||||
token = goog.string.regExpEscape(tokenOrArray);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return token;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Render a row by creating a div and then calling row rendering callback or
|
||||
* default row handler
|
||||
*
|
||||
* @param {Object} row Object representing row.
|
||||
* @param {string} token Token to highlight.
|
||||
* @return {Element} An element with the rendered HTML.
|
||||
*/
|
||||
goog.ui.ac.Renderer.prototype.renderRowHtml = function(row, token) {
|
||||
// Create and return the node
|
||||
var node = this.dom_.createDom('div', {
|
||||
className: this.rowClassName,
|
||||
id: goog.ui.IdGenerator.getInstance().getNextUniqueId()
|
||||
});
|
||||
goog.dom.a11y.setRole(node, goog.dom.a11y.Role.OPTION);
|
||||
if (this.customRenderer_ && this.customRenderer_.renderRow) {
|
||||
this.customRenderer_.renderRow(row, token, node);
|
||||
} else {
|
||||
this.renderRowContents_(row, token, node);
|
||||
}
|
||||
|
||||
if (token && this.useStandardHighlighting_) {
|
||||
this.hiliteMatchingText_(node, token);
|
||||
}
|
||||
|
||||
goog.dom.classes.add(node, this.rowClassName);
|
||||
this.rowDivs_.push(node);
|
||||
return node;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Given an event target looks up through the parents till it finds a div. Once
|
||||
* found it will then look to see if that is one of the childnodes, if it is
|
||||
* then the index is returned, otherwise -1 is returned.
|
||||
* @param {Element} et HtmlElement.
|
||||
* @return {number} Index corresponding to event target.
|
||||
* @private
|
||||
*/
|
||||
goog.ui.ac.Renderer.prototype.getRowFromEventTarget_ = function(et) {
|
||||
while (et && et != this.element_ &&
|
||||
!goog.dom.classes.has(et, this.rowClassName)) {
|
||||
et = /** @type {Element} */ (et.parentNode);
|
||||
}
|
||||
return et ? goog.array.indexOf(this.rowDivs_, et) : -1;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handle the click events. These are redirected to the AutoComplete object
|
||||
* which then makes a callback to select the correct row.
|
||||
* @param {goog.events.Event} e Browser event object.
|
||||
* @private
|
||||
*/
|
||||
goog.ui.ac.Renderer.prototype.handleClick_ = function(e) {
|
||||
var index = this.getRowFromEventTarget_(/** @type {Element} */ (e.target));
|
||||
if (index >= 0) {
|
||||
this.dispatchEvent({
|
||||
type: goog.ui.ac.AutoComplete.EventType.SELECT,
|
||||
row: this.rows_[index].id
|
||||
});
|
||||
}
|
||||
e.stopPropagation();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handle the mousedown event and prevent the AC from losing focus.
|
||||
* @param {goog.events.Event} e Browser event object.
|
||||
* @private
|
||||
*/
|
||||
goog.ui.ac.Renderer.prototype.handleMouseDown_ = function(e) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handle the mousing events. These are redirected to the AutoComplete object
|
||||
* which then makes a callback to set the correctly highlighted row. This is
|
||||
* because the AutoComplete can move the focus as well, and there is no sense
|
||||
* duplicating the code
|
||||
* @param {goog.events.Event} e Browser event object.
|
||||
* @private
|
||||
*/
|
||||
goog.ui.ac.Renderer.prototype.handleMouseOver_ = function(e) {
|
||||
var index = this.getRowFromEventTarget_(/** @type {Element} */ (e.target));
|
||||
if (index >= 0) {
|
||||
if ((goog.now() - this.startRenderingRows_) <
|
||||
goog.ui.ac.Renderer.DELAY_BEFORE_MOUSEOVER) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.dispatchEvent({
|
||||
type: goog.ui.ac.AutoComplete.EventType.HILITE,
|
||||
row: this.rows_[index].id
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Class allowing different implementations to custom render the autocomplete.
|
||||
* Extending classes should override the render function.
|
||||
* @constructor
|
||||
*/
|
||||
goog.ui.ac.Renderer.CustomRenderer = function() {
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Renders the autocomplete box. May be set to null.
|
||||
* @type {function(goog.ui.ac.Renderer, Element, Array, string)|
|
||||
* null|undefined}
|
||||
* param {goog.ui.ac.Renderer} renderer The autocomplete renderer.
|
||||
* param {Element} element The main element that controls the rendered
|
||||
* autocomplete.
|
||||
* param {Array} rows The current set of rows being displayed.
|
||||
* param {string} token The current token that has been entered.
|
||||
*/
|
||||
goog.ui.ac.Renderer.CustomRenderer.prototype.render = function(
|
||||
renderer, element, rows, token) {
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Generic function that takes a row and renders a DOM structure for that row.
|
||||
* @param {Object} row Object representing row.
|
||||
* @param {string} token Token to highlight.
|
||||
* @param {Node} node The node to render into.
|
||||
*/
|
||||
goog.ui.ac.Renderer.CustomRenderer.prototype.renderRow =
|
||||
function(row, token, node) {
|
||||
};
|
||||
@@ -0,0 +1,80 @@
|
||||
// Copyright 2012 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 Options for rendering matches.
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.ui.ac.RenderOptions');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A simple class that contains options for rendering a set of autocomplete
|
||||
* matches. Used as an optional argument in the callback from the matcher.
|
||||
* @constructor
|
||||
*/
|
||||
goog.ui.ac.RenderOptions = function() {
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Whether the current highlighting is to be preserved when displaying the new
|
||||
* set of matches.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
goog.ui.ac.RenderOptions.prototype.preserveHilited_ = false;
|
||||
|
||||
|
||||
/**
|
||||
* Whether the first match is to be highlighted. When undefined the autoHilite
|
||||
* flag of the autocomplete is used.
|
||||
* @type {boolean|undefined}
|
||||
* @private
|
||||
*/
|
||||
goog.ui.ac.RenderOptions.prototype.autoHilite_;
|
||||
|
||||
|
||||
/**
|
||||
* @param {boolean} flag The new value for the preserveHilited_ flag.
|
||||
*/
|
||||
goog.ui.ac.RenderOptions.prototype.setPreserveHilited = function(flag) {
|
||||
this.preserveHilited_ = flag;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {boolean} The value of the preserveHilited_ flag.
|
||||
*/
|
||||
goog.ui.ac.RenderOptions.prototype.getPreserveHilited = function() {
|
||||
return this.preserveHilited_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {boolean} flag The new value for the autoHilite_ flag.
|
||||
*/
|
||||
goog.ui.ac.RenderOptions.prototype.setAutoHilite = function(flag) {
|
||||
this.autoHilite_ = flag;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {boolean|undefined} The value of the autoHilite_ flag.
|
||||
*/
|
||||
goog.ui.ac.RenderOptions.prototype.getAutoHilite = function() {
|
||||
return this.autoHilite_;
|
||||
};
|
||||
@@ -0,0 +1,58 @@
|
||||
// Copyright 2007 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 Class for managing the interactions between a rich autocomplete
|
||||
* object and a text-input or textarea.
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.ui.ac.RichInputHandler');
|
||||
|
||||
goog.require('goog.ui.ac.InputHandler');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Class for managing the interaction between an autocomplete object and a
|
||||
* text-input or textarea.
|
||||
* @param {?string=} opt_separators Seperators to split multiple entries.
|
||||
* @param {?string=} opt_literals Characters used to delimit text literals.
|
||||
* @param {?boolean=} opt_multi Whether to allow multiple entries
|
||||
* (Default: true).
|
||||
* @param {?number=} opt_throttleTime Number of milliseconds to throttle
|
||||
* keyevents with (Default: 150).
|
||||
* @constructor
|
||||
* @extends {goog.ui.ac.InputHandler}
|
||||
*/
|
||||
goog.ui.ac.RichInputHandler = function(opt_separators, opt_literals,
|
||||
opt_multi, opt_throttleTime) {
|
||||
goog.ui.ac.InputHandler.call(this, opt_separators, opt_literals,
|
||||
opt_multi, opt_throttleTime);
|
||||
};
|
||||
goog.inherits(goog.ui.ac.RichInputHandler, goog.ui.ac.InputHandler);
|
||||
|
||||
|
||||
/**
|
||||
* Selects the given rich row. The row's select(target) method is called.
|
||||
* @param {Object} row The row to select.
|
||||
* @return {boolean} Whether to suppress the update event.
|
||||
* @override
|
||||
*/
|
||||
goog.ui.ac.RichInputHandler.prototype.selectRow = function(row) {
|
||||
var suppressUpdate = goog.ui.ac.RichInputHandler.superClass_
|
||||
.selectRow.call(this, row);
|
||||
row.select(this.ac_.getTarget());
|
||||
return suppressUpdate;
|
||||
};
|
||||
@@ -0,0 +1,107 @@
|
||||
// Copyright 2007 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 Factory class to create a rich autocomplete that will match
|
||||
* from an array of data provided via ajax. The server returns a complex data
|
||||
* structure that is used with client-side javascript functions to render the
|
||||
* results.
|
||||
*
|
||||
* The server sends a list of the form:
|
||||
* [["type1", {...}, {...}, ...], ["type2", {...}, {...}, ...], ...]
|
||||
* The first element of each sublist is a string designating the type of the
|
||||
* hashes in the sublist, each of which represents one match. The type string
|
||||
* must be the name of a function(item) which converts the hash into a rich
|
||||
* row that contains both a render(node, token) and a select(target) method.
|
||||
* The render method is called by the renderer when rendering the rich row,
|
||||
* and the select method is called by the RichInputHandler when the rich row is
|
||||
* selected.
|
||||
*
|
||||
* @see ../../demos/autocompleterichremote.html
|
||||
*/
|
||||
|
||||
goog.provide('goog.ui.ac.RichRemote');
|
||||
|
||||
goog.require('goog.ui.ac.AutoComplete');
|
||||
goog.require('goog.ui.ac.Remote');
|
||||
goog.require('goog.ui.ac.Renderer');
|
||||
goog.require('goog.ui.ac.RichInputHandler');
|
||||
goog.require('goog.ui.ac.RichRemoteArrayMatcher');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Factory class to create a rich autocomplete widget that autocompletes an
|
||||
* inputbox or textarea from data provided via ajax. The server returns a
|
||||
* complex data structure that is used with client-side javascript functions to
|
||||
* render the results.
|
||||
* @param {string} url The Uri which generates the auto complete matches.
|
||||
* @param {Element} input Input element or text area.
|
||||
* @param {boolean=} opt_multi Whether to allow multiple entries; defaults
|
||||
* to false.
|
||||
* @param {boolean=} opt_useSimilar Whether to use similar matches; e.g.
|
||||
* "gost" => "ghost".
|
||||
* @constructor
|
||||
* @extends {goog.ui.ac.Remote}
|
||||
*/
|
||||
goog.ui.ac.RichRemote = function(url, input, opt_multi, opt_useSimilar) {
|
||||
// Create a custom renderer that renders rich rows. The renderer calls
|
||||
// row.render(node, token) for each row.
|
||||
var customRenderer = {};
|
||||
customRenderer.renderRow = function(row, token, node) {
|
||||
return row.data.render(node, token);
|
||||
};
|
||||
|
||||
/**
|
||||
* A standard renderer that uses a custom row renderer to display the
|
||||
* rich rows generated by this autocomplete widget.
|
||||
* @type {goog.ui.ac.Renderer}
|
||||
* @private
|
||||
*/
|
||||
var renderer = new goog.ui.ac.Renderer(null, customRenderer);
|
||||
this.renderer_ = renderer;
|
||||
|
||||
/**
|
||||
* A remote matcher that parses rich results returned by the server.
|
||||
* @type {goog.ui.ac.RichRemoteArrayMatcher}
|
||||
* @private
|
||||
*/
|
||||
var matcher = new goog.ui.ac.RichRemoteArrayMatcher(url,
|
||||
!opt_useSimilar);
|
||||
this.matcher_ = matcher;
|
||||
|
||||
/**
|
||||
* An input handler that calls select on a row when it is selected.
|
||||
* @type {goog.ui.ac.RichInputHandler}
|
||||
* @private
|
||||
*/
|
||||
var inputhandler = new goog.ui.ac.RichInputHandler(null, null,
|
||||
!!opt_multi, 300);
|
||||
|
||||
// Create the widget and connect it to the input handler.
|
||||
goog.ui.ac.AutoComplete.call(this, matcher, renderer, inputhandler);
|
||||
inputhandler.attachAutoComplete(this);
|
||||
inputhandler.attachInputs(input);
|
||||
};
|
||||
goog.inherits(goog.ui.ac.RichRemote, goog.ui.ac.Remote);
|
||||
|
||||
|
||||
/**
|
||||
* Set the filter that is called before the array matches are returned.
|
||||
* @param {Function} rowFilter A function(rows) that returns an array of rows as
|
||||
* a subset of the rows input array.
|
||||
*/
|
||||
goog.ui.ac.RichRemote.prototype.setRowFilter = function(rowFilter) {
|
||||
this.matcher_.setRowFilter(rowFilter);
|
||||
};
|
||||
@@ -0,0 +1,124 @@
|
||||
// Copyright 2007 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 Class that retrieves rich autocomplete matches, represented as
|
||||
* a structured list of lists, via an ajax call. The first element of each
|
||||
* sublist is the name of a client-side javascript function that converts the
|
||||
* remaining sublist elements into rich rows.
|
||||
*
|
||||
*/
|
||||
|
||||
goog.provide('goog.ui.ac.RichRemoteArrayMatcher');
|
||||
|
||||
goog.require('goog.ui.ac.RemoteArrayMatcher');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* An array matcher that requests rich matches via ajax and converts them into
|
||||
* rich rows.
|
||||
* @param {string} url The Uri which generates the auto complete matches. The
|
||||
* search term is passed to the server as the 'token' query param.
|
||||
* @param {boolean=} opt_noSimilar If true, request that the server does not do
|
||||
* similarity matches for the input token against the dictionary.
|
||||
* The value is sent to the server as the 'use_similar' query param which is
|
||||
* either "1" (opt_noSimilar==false) or "0" (opt_noSimilar==true).
|
||||
* @constructor
|
||||
* @extends {goog.ui.ac.RemoteArrayMatcher}
|
||||
*/
|
||||
goog.ui.ac.RichRemoteArrayMatcher = function(url, opt_noSimilar) {
|
||||
goog.ui.ac.RemoteArrayMatcher.call(this, url, opt_noSimilar);
|
||||
|
||||
/**
|
||||
* A function(rows) that is called before the array matches are returned.
|
||||
* It runs client-side and filters the results given by the server before
|
||||
* being rendered by the client.
|
||||
* @type {Function}
|
||||
* @private
|
||||
*/
|
||||
this.rowFilter_ = null;
|
||||
|
||||
};
|
||||
goog.inherits(goog.ui.ac.RichRemoteArrayMatcher, goog.ui.ac.RemoteArrayMatcher);
|
||||
|
||||
|
||||
/**
|
||||
* Set the filter that is called before the array matches are returned.
|
||||
* @param {Function} rowFilter A function(rows) that returns an array of rows as
|
||||
* a subset of the rows input array.
|
||||
*/
|
||||
goog.ui.ac.RichRemoteArrayMatcher.prototype.setRowFilter = function(rowFilter) {
|
||||
this.rowFilter_ = rowFilter;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve a set of matching rows from the server via ajax and convert them
|
||||
* into rich rows.
|
||||
* @param {string} token The text that should be matched; passed to the server
|
||||
* as the 'token' query param.
|
||||
* @param {number} maxMatches The maximum number of matches requested from the
|
||||
* server; passed as the 'max_matches' query param. The server is
|
||||
* responsible for limiting the number of matches that are returned.
|
||||
* @param {Function} matchHandler Callback to execute on the result after
|
||||
* matching.
|
||||
* @override
|
||||
*/
|
||||
goog.ui.ac.RichRemoteArrayMatcher.prototype.requestMatchingRows =
|
||||
function(token, maxMatches, matchHandler) {
|
||||
// The RichRemoteArrayMatcher must map over the results and filter them
|
||||
// before calling the request matchHandler. This is done by passing
|
||||
// myMatchHandler to RemoteArrayMatcher.requestMatchingRows which maps,
|
||||
// filters, and then calls matchHandler.
|
||||
var myMatchHandler = goog.bind(function(token, matches) {
|
||||
/** @preserveTry */
|
||||
try {
|
||||
var rows = [];
|
||||
for (var i = 0; i < matches.length; i++) {
|
||||
var func = /** @type {!Function} */
|
||||
(goog.json.unsafeParse(matches[i][0]));
|
||||
for (var j = 1; j < matches[i].length; j++) {
|
||||
var richRow = func(matches[i][j]);
|
||||
rows.push(richRow);
|
||||
|
||||
// If no render function was provided, set the node's innerHTML.
|
||||
if (typeof richRow.render == 'undefined') {
|
||||
richRow.render = function(node, token) {
|
||||
node.innerHTML = richRow.toString();
|
||||
};
|
||||
}
|
||||
|
||||
// If no select function was provided, set the text of the input.
|
||||
if (typeof richRow.select == 'undefined') {
|
||||
richRow.select = function(target) {
|
||||
target.value = richRow.toString();
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
if (this.rowFilter_) {
|
||||
rows = this.rowFilter_(rows);
|
||||
}
|
||||
matchHandler(token, rows);
|
||||
} catch (exception) {
|
||||
// TODO(user): Is this what we want?
|
||||
matchHandler(token, []);
|
||||
}
|
||||
}, this);
|
||||
|
||||
// Call the super's requestMatchingRows with myMatchHandler
|
||||
goog.ui.ac.RichRemoteArrayMatcher.superClass_
|
||||
.requestMatchingRows.call(this, token, maxMatches, myMatchHandler);
|
||||
};
|
||||
Reference in New Issue
Block a user