/*
* Copyright (C) 2006 Google Inc.
*
* 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.
*/
/*
* Search-as-you-type
*/
var searchAsYouTypeConfiguration = {
// The path (beginning of the URL) to the place containing /images and
// /styles. Should end with a slash.
// e.g. http://intranet.company.com/search-as-you-type/
resourcesPath:
"http://en.visitparisregion.com/FRONT/CRT_PIDF/scripts/sayt/",
// The fully qualified URL to the Ajax responder.
// e.g. http://intranet.company.com/search-as-you-type/responder.php
ajaxResponderUrl:
"http://en.visitparisregion.com/FRONT/CRT_PIDF/scripts/sayt/search-responder.php",
// The fully qualified URL to the help page. Leave as empty string if
// not available
// e.g. http://intranet.company.com/search-as-you-type/help.html
helpPageUrl:
"",
// How many results will be shown in full. If there are more than these,
// all but "direct hits" will be summarized. Default value: 3
maxFullResults: 0,
// The delay (in ms) between pressing a key (while typing in a search
// query) and firing the query search. Shouldn't be too big, because the
// users will have to wait a long time for results. Shouldn't be too small,
// because it will increase the load on a server. Default value: 20
keystrokeDelay: 5,
// The delay (in ms) between pressing a key and results being shown.
// Shouldn't be too big, because it will be less usable, and the users
// will grow impatient. Shouldn't be too small, because the results will
// flicker below as the user is typing. Please note that the actual
// time might be bigger if the Ajax responder is slow. Default value: 200
showResultsDelay: 10,
// The distance (in pixels) that should be left from the bottom edge of
// the screen if there are many results. Default value: 10
bottomPageMargin: 10,
maxHeight: '300px',
rubrique: ''
};
/**
* SearchAsYouType class.
* @constructor
*/
function SearchAsYouType() {
}
/**
* Initialize Search-as-you-type. This needs to be run on the page
* using Search-as-you-type.
*
* @param {element} inputFieldEl An input field element Search-as-you-type
* should attach itself to
* @param {bool} focus Whether to set focus on this element
*/
SearchAsYouType.prototype.initialize = function(inputFieldEl, focus, varname) {
this.varname = varname;
this.initializeVariables_(inputFieldEl);
this.detectBrowser_();
this.attachStylesheets_();
this.createDomElements_();
this.restoreInputField_();
this.addEventHandlers_();
//this.prepareRandomTip_();
this.updateDimensionsAndShadow_(null);
if (this.debugMode) {
this.activateDebugConsole_();
}
if (focus) {
this.focusInputField_();
}
this.initialized = true;
}
/**
* Initialize all the variables needed for later.
* @param {element} inputFieldEl An input field element Search-as-you-type
* should attach itself to
*/
SearchAsYouType.prototype.initializeVariables_ = function(inputFieldEl) {
this.token = Math.floor(Math.random()*100000);
// Location (URL) of the parent page
this.location = "" + window.location;
// Protocol used by the parent page ("http" or "https").
this.protocol = this.location.substr(0, this.location.indexOf("://") + 3);
// Path (URL beginning) to resources such as images or CSS files
this.resourcesPath = searchAsYouTypeConfiguration.resourcesPath;
// (...) make it understand https
// script object for Ajax calls
this.ajaxObject = null;
// Results from the last search
this.results = {};
// Search cache (containing previous responses)
this.searchCache = [];
// Whether the whole as-you-type search engine has been initialized
this.initialized = false;
// Whether we are waiting for Ajax response (shows a rotating progress
// icon if so)
this.waitingForSearchResults = false;
// Whether search results window is hidden or visible
this.resultsWindowHidden = true;
// A handler to the input field
this.inputFieldEl = inputFieldEl;
// The query last typed by the user
this.typedQuery = this.getInputFieldValue_();
// A handler to the search results window element
this.searchResultsEl = 0;
// A handler to the alternate search results window (we have two and switch
// between them for better visuals)
this.alternateSearchResultsEl = 0;
// Whether the input field currently has focus (can be 0, 0.5 or 1)
this.inputFieldHasFocus = 0;
// Whether any of the results is activated by navigating through it via
// keyboard. -1 if no, 0 or more if yes (indicates the number of the
// active search result)
this.activeResult = -1;
// Whether the search result window has been dismissed manually by clicking
// somewhere else
this.resultsWindowHiddenByClicking = false;
// Whether the arrow key has been processed on keydown event, and can be
// ignored on keypress (see handleBodyKeyPress for more information on why
// this is necessary)
this.arrowKeyProcessed = false;
// The code of the last pressed key
this.lastKeyPressed = 0;
// Timer id of the JavaScript timer to show results
this.showResultsTimeoutId = -1;
// The id of the JavaScript timer to fire a query after
// searchAsYouTypeConfiguration.keystrokeDelay ms have passed
// since the last keystroke
this.keystrokeTimeoutId = -1;
// Current autocomplete value
this.autocomplete = '';
// Whether autocomplete has just been collapsed (i.e. turned into regular
// regular input text by pressing Tab or right arrow)
this.autocompleteJustCollapsed = false;
// Contents of the tip appearing as the last search result for 5% of the
// queries ('' if not available)
this.tipText = '';
// Whether we're in the debug mode (activated by adding
// ?debugSearchAsYouType to the URL)
this.debugMode = this.location.indexOf("debugSearchAsYouType") > -1;
this.rubrique = '';
}
/**
* Figure out which browser is being used.
*/
SearchAsYouType.prototype.detectBrowser_ = function() {
this.browserIE = false;
this.browserFirefox = false;
this.browserSafari = false;
if (navigator.userAgent.indexOf("MSIE") > -1) {
this.browserIE = true;
} else if ((navigator.userAgent.indexOf("Firefox/") > -1)) {
this.browserFirefox = true;
if ((navigator.userAgent.indexOf("Firefox/1.0.") > -1)) {
this.browserFirefox10 = true;
} else {
this.browserFirefox10 = false;
}
} else if (navigator.userAgent.indexOf("Safari") > -1) {
this.browserSafari = true;
if (navigator.userAgent.indexOf("Version/") > -1) {
this.browserSafari3OrHigher = true;
}
}
}
/**
* Attach the necessary CSS stylesheets to the document body. This adds
* a generic CSS plus extra stylesheets containing exceptions for IE and
* Safari.
*/
SearchAsYouType.prototype.attachStylesheets_ = function() {
this.attachStylesheet_('generic.css');
this.attachStylesheet_('customized.css');
if (this.browserIE) {
this.attachStylesheet_('ie.css');
} else if (this.browserSafari) {
this.attachStylesheet_('safari.css');
}
}
/**
* Attach a CSS stylesheet to the document body.
* @param {String} filename Absolute URL of the stylesheet
*/
SearchAsYouType.prototype.attachStylesheet_ = function(filename) {
var el = document.createElement('link');
el.href = this.resourcesPath + "styles/" + filename;
el.type = 'text/css';
el.rel = 'stylesheet';
document.getElementsByTagName('head').item(0).appendChild(el);
}
/**
* Create all the necessary page elements: search results window(s),
* shadow elements, loading, backup input element, and autocomplete.
*/
SearchAsYouType.prototype.createDomElements_ = function() {
// A backup input field necessary to preserve the last entry when
// coming back to the page -- since we're disabling browser's native
// autocomplete on the regular input field, it will always be clean when
// entering the page
var el = document.createElement("input");
el.id = 'searchAsYouTypeBackupSearchField'+this.token;
el.style.display = 'none'; // in case CSS is not yet loaded
document.body.appendChild(el);
// Two search results canvas windows
this.searchResultsEl = document.createElement("div");
this.searchResultsEl.id = 'searchAsYouTypeResults1_'+this.token;
this.searchResultsEl.className = 'searchResults';
this.searchResultsEl.style.display = 'none';
this.searchResultsEl.style.position = 'absolute';
this.searchResultsEl.onclick = 'event.cancelBubble = true;';
this.searchResultsEl.tabIndex = -1;
this.alternateSearchResultsEl = document.createElement("div");
this.alternateSearchResultsEl.id = 'searchAsYouTypeResults2_'+this.token;
this.alternateSearchResultsEl.className = 'searchResults';
this.alternateSearchResultsEl.style.display = 'none';
this.alternateSearchResultsEl.style.position = 'absolute';
this.alternateSearchResultsEl.onclick = 'event.cancelBubble = true;';
this.alternateSearchResultsEl.tabIndex = -1;
// Shadows for the current search results canvas
this.searchResultsShadowEl = document.createElement("div");
this.searchResultsShadowEl.id = 'searchAsYouTypeResultsShadow'+this.token;
this.searchResultsShadowEl.className = 'searchAsYouTypeResultsShadow';
this.searchResultsShadowEl.style.visibility = 'hidden';
this.searchResultsShadowEl.style.display = 'none';
this.searchResultsShadowEl.style.left = 0;
this.searchResultsShadowEl.style.top = 0;
this.searchResultsShadowEl.style.width = 0;
this.searchResultsShadowEl.style.height = 0;
var el = document.createElement("div");
el.className = 'searchAsYouTypeResultsShadowL';
this.searchResultsShadowEl.appendChild(el);
var el = document.createElement("div");
el.className = 'searchAsYouTypeResultsShadowR';
this.searchResultsShadowEl.appendChild(el);
var el = document.createElement("div");
el.className = 'searchAsYouTypeResultsShadowB';
this.searchResultsShadowEl.appendChild(el);
var el = document.createElement("div");
el.className = 'searchAsYouTypeResultsShadowBL';
this.searchResultsShadowEl.appendChild(el);
var el = document.createElement("div");
el.className = 'searchAsYouTypeResultsShadowBR';
this.searchResultsShadowEl.appendChild(el);
var el = document.createElement("div");
el.className = 'searchAsYouTypeResultsShadowTL';
this.searchResultsShadowEl.appendChild(el);
var el = document.createElement("div");
el.className = 'searchAsYouTypeResultsShadowTR';
this.searchResultsShadowEl.appendChild(el);
var el = document.createElement("div");
el.id = 'searchAsYouType'+this.token;
el.className = 'searchAsYouType';
el.appendChild(this.searchResultsEl);
el.appendChild(this.alternateSearchResultsEl);
el.appendChild(this.searchResultsShadowEl);
document.body.appendChild(el);
// Loading animation (to be position in the input field)
this.waitingForSearchResultsEl = document.createElement("img");
this.waitingForSearchResultsEl.style.visibility = 'hidden';
this.waitingForSearchResultsEl.style.position = 'absolute';
this.waitingForSearchResultsEl.src =
this.resourcesPath + "images/loading.gif";
document.body.appendChild(this.waitingForSearchResultsEl);
// Autocomplete element
this.autocompleteEl = document.createElement("div");
this.autocompleteEl.id = 'searchAsYouTypeAutocomplete'+this.token;
this.autocompleteEl.className = 'searchAsYouTypeAutocompleteInputMatch';
document.body.appendChild(this.autocompleteEl);
this.autocompleteEl.onmousedown =
searchAsYouTypeBind(this.handleAutocompleteMouseDown, this);
this.autocompleteEl.style.zIndex = 5000;
this.autocompleteEl.style.display = 'none';
// Autocomplete helper, used to calculate dimensions
this.autocompleteHelperEl = document.createElement("div");
this.autocompleteHelperEl.id = 'searchAsYouTypeAutocompleteHelper'+this.token;
this.autocompleteHelperEl.visibility = 'hidden';
this.autocompleteHelperEl.className = 'searchAsYouTypeAutocompleteInputMatch';
document.body.appendChild(this.autocompleteHelperEl);
}
/**
* Get a query from the input field and clean it up a little bit
* @return {String} A cleaned up query
*/
SearchAsYouType.prototype.getInputFieldValue_ = function() {
return this.inputFieldEl.value.toLowerCase().
replace(/^\s+/g, '').replace(/\s+$/g, '');
}
/**
* Set focus on the input field. We do some extra gymnastics here for IE
* so that the caret ends up at the end of the input field.
*/
SearchAsYouType.prototype.focusInputField_ = function() {
this.inputFieldEl.focus();
if (this.inputFieldEl.createTextRange && window.document.selection) {
var sel = this.inputFieldEl.createTextRange();
sel.collapse(true);
sel.move("character", this.inputFieldEl.value.length);
sel.select();
}
}
/**
* Clear the input field and autocomplete. Prepares a random tip (we only do
* it here so tips don't change or come and go as the user is typing).
*/
SearchAsYouType.prototype.clearInputField_ = function() {
this.inputFieldEl.value = '';
this.clearAutocomplete_(true);
//this.prepareRandomTip_();
}
/**
* Save the contents of the input field in case the user goes back
* to the page.
*/
SearchAsYouType.prototype.saveInputField = function(e) {
// The main input field has browser autocomplete turned off, because
// the auto-complete window would cover SearchAsYouType window.
// Unfortunately, this has another side effect -- the contents of the
// input field won't be retained after the user pressed back button to
// go back to the homepage.
//
// We need to copy the value to a hidden input field (but with
// autocomplete) and copy it back when the page loads.
document.getElementById('searchAsYouTypeBackupSearchField'+this.token).value =
this.inputFieldEl.value;
document.getElementById('searchAsYouTypeBackupSearchField'+this.token).
setAttribute("active", 1);
}
/**
* Retain the previous text entry and put focus on the input field.
*/
SearchAsYouType.prototype.restoreInputField_ = function() {
if (document.getElementById('searchAsYouTypeBackupSearchField'+this.token).
getAttribute("active")) {
this.inputFieldEl.value =
document.getElementById('searchAsYouTypeBackupSearchField'+this.token).value;
this.typedQuery = this.getInputFieldValue_();
}
}
/**
* Add necessary event handlers for the input field and the body of the page.
*/
SearchAsYouType.prototype.addEventHandlers_ = function() {
// (...) event listener
this.inputFieldEl.onkeyup = searchAsYouTypeBind(this.handleInputKeyUp, this);
this.inputFieldEl.onkeypress =
searchAsYouTypeBind(this.handleInputKeyPress, this);
this.inputFieldEl.onkeydown =
searchAsYouTypeBind(this.handleInputKeyDown, this);
this.inputFieldEl.onfocus = searchAsYouTypeBind(this.handleInputFocus, this);
this.inputFieldEl.onblur = searchAsYouTypeBind(this.handleInputBlur, this);
this.inputFieldEl.onclick = searchAsYouTypeBind(this.handleInputClick, this);
this.inputFieldEl.onmousedown =
searchAsYouTypeBind(this.handleInputMouseDown, this);
this.inputFieldEl.setAttribute('autocomplete', 'off');
if (window.addEventListener) { // Mozilla, Netscape, Firefox
document.body.addEventListener('click',
searchAsYouTypeBind(this.handleBodyClick, this), false);
document.addEventListener('keyup',
searchAsYouTypeBind(this.handleBodyKeyUp, this), false);
document.addEventListener('keydown',
searchAsYouTypeBind(this.handleBodyKeyDown, this), false);
document.addEventListener('keypress',
searchAsYouTypeBind(this.handleBodyKeyPress, this), false);
window.addEventListener('resize',
searchAsYouTypeBind(this.handleBodyResize, this), false);
} else { // IE
document.body.attachEvent('onclick',
searchAsYouTypeBind(this.handleBodyClick, this));
document.body.attachEvent('onkeyup',
searchAsYouTypeBind(this.handleBodyKeyUp, this));
document.body.attachEvent('onkeydown',
searchAsYouTypeBind(this.handleBodyKeyDown, this));
document.onkeypress = searchAsYouTypeBind(this.handleBodyKeyPress, this);
window.attachEvent('onresize',
searchAsYouTypeBind(this.handleBodyResize, this));
}
// The below is for Firefox 1.5's fastback feature.
// (...) CHANGE TO event listener
try {
window.onpageshow = function(event) {
if (event.persisted) {
searchAsYouType.restoreInputField_();
}
};
} catch(e) {
}
if ((this.browserFirefox) && (!this.browserFirefox10)) {
window.onpagehide = searchAsYouTypeBind(this.saveInputField, this);
} else {
window.onunload = searchAsYouTypeBind(this.saveInputField, this);
}
}
/**
* Prepare a random tip for 5% of the queries. This tip will be shown as
* the last search result.
*/
/*SearchAsYouType.prototype.prepareRandomTip_ = function() {
var tips = [
'You can use arrow keys to navigate these results.',
'Press Tab, space or right arrow to auto-complete.',
'Press Esc or up arrow to hide this pop-up. ' +
'Press Esc again to quickly clear the search field.',
'Click outside this pop-up to hide it. ' +
'Click on the search bar twice to show it again.'];
if (Math.random() < 0.05) {
this.tipText = tips[Math.floor(Math.random() * tips.length)];
} else {
this.tipText = '';
}
}*/
/**
* Calculate and update the dimensions of Search-as-you-type elements,
* including autocomplete, loading animation and shadows
* @param {element} searchResultsEl A search results element to be updated
*/
SearchAsYouType.prototype.updateDimensionsAndShadow_ =
function(searchResultsEl) {
// Figure out the absolute position of the input field element
var el = this.inputFieldEl;
var x = 0;
var y = 0;
var obj = el;
do {
x += obj.offsetLeft;
y += obj.offsetTop;
obj = obj.offsetParent;
} while (obj);
// Position the waiting animation, so it's inside the input field, flushed
// right
// (...) height too
this.waitingForSearchResultsEl.style.left =
(x + this.inputFieldEl.offsetWidth - 19) + 'px';
this.waitingForSearchResultsEl.style.top =
(y + 3) + 'px';
// Position the autocomplete element
this.autocompleteEl.setAttribute("originalLeft", x);
this.autocompleteEl.style.top = y + 'px';
this.autocompleteEl.style.height =
(this.inputFieldEl.clientHeight - 1) + 'px';
// Position the search results canvas element
if (searchResultsEl) {
y += el.offsetHeight - 2;
var w = el.offsetWidth - 2;
searchResultsEl.style.left = (x + 1) + "px";
searchResultsEl.style.top = y + "px";
searchResultsEl.style.width = w + "px";
x = searchResultsEl.offsetLeft;
y = searchResultsEl.offsetTop;
w = searchResultsEl.offsetWidth;
var ch = searchResultsEl.scrollHeight;
if (self.innerHeight) {
var screenHeight = self.innerHeight;
} else if (document.documentElement &&
document.documentElement.clientHeight) {
var screenHeight = document.documentElement.clientHeight;
} else if (document.body) {
var screenHeight = document.body.clientHeight;
}
if (document.documentElement.scrollTop) {
var scrollTop = document.documentElement.scrollTop;
} else {
var scrollTop = document.body.scrollTop;
}
var documentContentHeight = screenHeight - scrollTop;
var maxSearchResultsHeight =
documentContentHeight - y - searchAsYouTypeConfiguration.bottomPageMargin;
if (ch > maxSearchResultsHeight) {
searchResultsEl.style.height = maxSearchResultsHeight + "px";
} else {
searchResultsEl.style.height = "auto";
}
// if (searchResultsEl.style.height > searchAsYouTypeConfiguration.maxHeight)
// searchResultsEl.style.height = searchAsYouTypeConfiguration.maxHeight;
var h = searchResultsEl.offsetHeight;
// Resize shadows
this.resizeShadowEl_("", x, y, w + 4, h + 6);
this.resizeShadowEl_("L", -2, 5, 2, h - 5);
this.resizeShadowEl_("TL", -2, 0, 2, 5);
this.resizeShadowEl_("TR", w, 0, 2, 5);
this.resizeShadowEl_("R", w, 5, 2, h - 5);
this.resizeShadowEl_("B", 4, h, w - 8, 6);
this.resizeShadowEl_("BL", -2, h, 6, 6);
this.resizeShadowEl_("BR", w - 4, h, 6, 6);
}
}
/**
* Resize one of the shadow elements.
* @param {string} id Id of the shadow element (cf. "BR")
* @param {int} x Horizontal position (in pixels)
* @param {int} y Vertical position (in pixels)
* @param {int} w Width (in pixels)
* @param {int} h Height (in pixels)
*/
SearchAsYouType.prototype.resizeShadowEl_ = function(id, x, y, w, h) {
var el = document.getElementById('searchAsYouTypeResultsShadow' + this.token + id);
/* Wrapped around in try/catch because of an IE7 bug */
try {
el.style.left = x + "px";
el.style.top = y + "px";
el.style.width = w + "px";
el.style.height = h + "px";
} catch(e) {
}
}
/**
* Perform query search (an Ajax request) on whatever the user typed.
* Skip if already in cache.
*/
SearchAsYouType.prototype.search_ = function(dontDelayShowResults) {
if (dontDelayShowResults === true) {
this.delayShowResults = false;
} else {
this.delayShowResults = true;
}
// If a query is empty we don't do anything
if (this.typedQuery.length == 0) {
this.changeWaitingForSearchResults_(false);
return;
}
URL = searchAsYouTypeConfiguration.ajaxResponderUrl;
URL += "?query=" + encodeURIComponent(this.typedQuery);
URL += "&jsonp=" + this.varname +".handleAjaxResponse";
URL += "&lan="+lang;
URL += "&varname="+this.varname;
if (this.rubrique == 'evt')
{
URL += "&evt=1";
}
if (this.debugMode) {
URL += "&debug=1";
}
if (this.waitingForSearchResults) {
this.cancelCurrentSearch_();
}
if (this.debugMode) {
this.addToDebugConsoleTimesNewLine_("
" + this.typedQuery + "
");
var date = new Date();
this.debugQueryStartTime = date.getTime();
}
this.changeWaitingForSearchResults_(true);
// If already in cache, use cache
if (this.searchCache["_" + this.typedQuery]) {
this.ajaxRequestStartTime = -1;
this.processResults_(this.searchCache["_" + this.typedQuery].results, true);
} else {
var date = new Date();
this.ajaxRequestStartTime = date.getTime();
this.ajaxObject = document.createElement('script');
this.ajaxObject.src = URL;
this.ajaxObject.type = "text/javascript";
this.ajaxObject.charset = "utf-8";
document.getElementsByTagName('head').item(0).appendChild(this.ajaxObject);
}
}
/**
* Cancel the Ajax request we're currently waiting for.
*/
SearchAsYouType.prototype.cancelCurrentSearch_ = function() {
if (this.ajaxObject) {
try {
document.getElementsByTagName('head').item(0).
removeChild(this.ajaxObject);
} catch(e) {
}
}
}
/**
* Show or hide the "results coming up" pie animation depending on
* whether it's needed. Abort the current Ajax request if necessary.
* @param {bool} value Whether we're waiting or not for search results
*/
SearchAsYouType.prototype.changeWaitingForSearchResults_ = function(value) {
if (this.waitingForSearchResults != value) {
if (value) {
this.waitingForSearchResultsEl.style.visibility = 'visible';
} else {
this.waitingForSearchResultsEl.style.visibility = 'hidden';
this.cancelCurrentSearch_();
}
}
this.waitingForSearchResults = value;
}
/**
* Handle Ajax response when it's back. Add a tip if necessary, then forward
* for processing.
* @param {object} results Results object
*/
SearchAsYouType.prototype.handleAjaxResponse = function(results) {
if (results.results.length && this.tipText) {
var moreDetailsUrl = searchAsYouTypeConfiguration.helpPageUrl;
var content = '
';
results.results.push({"type": "Tip",
"name": "",
"content": content,
"style": "compact",
"moreDetailsUrl": moreDetailsUrl});
// results.results.push({"type": "Tip",
// "name": "",
// "content": content,
// "style": "compact",
// "moreDetailsUrl": moreDetailsUrl});
}
this.processResults_(results, false);
}
/**
* Cache and process search results (Ajax response) if there are any.
* @param {object} results Results object
* @param {bool} cached Whether the results come from the cache
*/
SearchAsYouType.prototype.processResults_ = function(results, cached) {
if (this.lastKeyPressed == 8) {
var dontDoAutocomplete = true;
} else {
var dontDoAutocomplete = false;
}
if (!results.autocompletedQuery) {
results.autocompletedQuery = results.query;
}
results.countNotCompact = 0;
results.countExpanded = 0;
for (var i in results.results) {
if (results.results[i].style == 'expanded') {
results.countExpanded++;
results.countNotCompact++;
} else if (results.results[i].style == 'normal') {
results.countNotCompact++;
}
}
// Copy the object for future reference
this.results = searchAsYouTypeCloneObject(results);
// Cache the results
this.searchCache["_" + this.results.query] = {};
this.searchCache["_" + this.results.query].results =
searchAsYouTypeCloneObject(results);
this.resultsWindowHiddenByClicking = false;
// See if the results respond to the last typed query (Ajax requests might
// come out of order)
if (results.query == this.typedQuery) {
// Add to debug info if we're in debug mode
if (this.debugMode) {
var date = new Date();
var debugQueryEndTime = date.getTime();
this.addToDebugConsoleTimesCurrentLine_(
"
" + results.autocompletedQuery + "
");
this.addToDebugConsoleTimesCurrentLine_(
"
" + results.results.length + "
");
this.addToDebugConsoleTimesCurrentLine_(
"
" + searchAsYouTypeConfiguration.showResultsDelay + " ms
");
if (cached) {
this.addToDebugConsoleTimesCurrentLine_(
"
" + (debugQueryEndTime - this.debugQueryStartTime) +
" ms
");
this.addToDebugConsoleTimesCurrentLine_(
"
" + this.results.debugInfo.globalTime + " ms
");
}
}
if ((cached) && (dontDoAutocomplete)) {
if (this.searchCache["_" + this.results.query].autocompleted) {
this.hideResultsWindow_();
this.changeWaitingForSearchResults_(false);
return;
}
}
// If nothing has been returned, hide the results window
if (!this.results.results.length) {
this.hideResultsWindow_();
this.changeWaitingForSearchResults_(false);
} else {
this.prepareResultsWindow_();
if (!dontDoAutocomplete) {
this.addAutocompleteTextIfPossible_();
}
}
}
}
/**
* Get an HTML snippet showing the current result type. This is used if
* we show summarized results.
* @param {string} type Search result type (e.g. "Conference rooms")
* @return {string} Corresponding HTML snippet
*/
SearchAsYouType.prototype.getResultTypeDescriptionHtml_ = function(type) {
return '';
// return '
' + type + ": " + "
";
}
/**
* Get a CSS class name corresponding to a result type. What this does is
* removes all of the spaces.
* @param {string} type Search result type (e.g. "Conference rooms")
* @return {string} Corresponding class name (e.g. "Conferencerooms")
*/
SearchAsYouType.prototype.getResultTypeClassName_ = function(type) {
return type.replace(/\ /g, "");
}
/**
* Get HTML markup for the results.
* @param {int} resultId Specific Search result to return (-1 if all)
* @return {string} HTML markup for the result(s)
*/
SearchAsYouType.prototype.getResultsHtml_ = function(resultId) {
var currentResultId = 0;
var html = '';
var lastType = null;
var openDiv = false;
var styles = ['expandedPriority', 'expanded', 'normal', 'compact'];
for (var styleNo in styles) {
for (var i = 0; i < this.results.results.length; i++) {
if (this.results.results[i].style != styles[styleNo]) {
continue;
}
if ((resultId == -1) || (resultId == currentResultId)) {
if (resultId > -1) {
var style = 'expandedPriority';
} else {
var style = styles[styleNo];
}
if ((style != 'normal') || (lastType != this.results.results[i].type)) {
if (openDiv) {
html += '';
}
var className = "searchResult " +
this.getResultTypeClassName_(this.results.results[i].type);
/*if (currentResultId == 0) {
className += " first";
}*/
if (style == 'normal') {
html += '
';
lastType = this.results.results[i].type;
html +=
this.getResultTypeDescriptionHtml_(this.results.results[i].type);
openDiv = true;
}
} else if (style == 'normal') {
html += " · ";
}
if (style != 'normal') {
//html += '
';
} else {
html += '' +
this.results.results[i].name + '';
}
}
currentResultId++;
}
}
return html;
}
/**
* Prepare HTML markup for the search results window.
*/
SearchAsYouType.prototype.prepareResultsWindow_ = function() {
var showExpanded;
this.activeResult = -1;
if (this.results.countNotCompact <=
searchAsYouTypeConfiguration.maxFullResults) {
for (var i = 0; i < this.results.results.length; i++) {
if (this.results.results[i].style == 'expanded') {
this.results.results[i].style = 'expandedPriority';
} else if (this.results.results[i].style == 'normal') {
this.results.results[i].style = 'expanded';
}
}
}
this.resultsHtml = this.getResultsHtml_(-1);
if (this.showResultsTimeoutId > -1) {
clearTimeout(this.showResultsTimeoutId);
}
var time;
if (this.delayShowResults) {
if (this.ajaxRequestStartTime == -1) {
time = 0;
} else {
var date = new Date();
time = date.getTime() - this.ajaxRequestStartTime;
}
var time = searchAsYouTypeConfiguration.showResultsDelay - time;
if (time <= 1) {
time = 1;
}
} else {
time = 1;
}
this.showResultsTimeoutId =
setTimeout(searchAsYouTypeBind(this.showResultsWindow_, this), time);
}
/**
* Show the search result window, incl. the shadow.
*/
SearchAsYouType.prototype.showResultsWindow_ = function() {
this.showResultsTimeoutId = -1;
this.changeWaitingForSearchResults_(false);
clearInterval(this.hideTimeout);
this.resultsWindowHiddenByClicking = false;
this.resultsWindowHidden = false;
// cleaning ids for safari
var i = 0;
var el;
while (el = document.getElementById('searchResult_'+this.token + '_'+ i)) {
el.id = '';
i++;
}
this.alternateSearchResultsEl.style.height = '1px';
this.alternateSearchResultsEl.style.visibility = 'hidden';
this.alternateSearchResultsEl.style.display = 'block';
this.alternateSearchResultsEl.innerHTML = this.resultsHtml;
this.alternateSearchResultsEl.style.opacity = 0.99;
// We go through all of the links in the results, and remove tabindex
// and make them override an iframe, if we're in one
var els = this.alternateSearchResultsEl.getElementsByTagName('a');
for (var i = 0, j = els.length; i < j; i++) {
els.item(i).tabIndex = -1;
els.item(i).target = "_top";
}
// We go through all of the images, hide them, and assign the function
// to show them when they're fully loaded. Since an image can resize
// a search result window, we need to make sure that we recalculate the
// dimensions (and shadows) on image load
var els = this.alternateSearchResultsEl.getElementsByTagName('img');
for (var i = 0, j = els.length; i < j; i++) {
els.item(i).style.display = 'none';
els.item(i).onload =
searchAsYouTypeBind(this.handleImageOnLoad, this, els.item(i));
}
this.updateDimensionsAndShadow_(this.alternateSearchResultsEl);
this.searchResultsEl.style.visibility = 'hidden';
this.searchResultsEl.style.display = 'none';
this.searchResultsShadowEl.style.display = 'block';
this.searchResultsShadowEl.style.visibility = 'visible';
this.searchResultsShadowEl.style.opacity = 1;
this.alternateSearchResultsEl.style.visibility = 'visible';
// Swap search result elements handlers
var el = this.searchResultsEl;
this.searchResultsEl = this.alternateSearchResultsEl;
this.alternateSearchResultsEl = el;
}
/**
* Show the image after it's loaded. Prevents images loading and layout
* reflowing bit by bit -- it only shows the image if it is fully loaded.
*
* @param {element} el The image to be shown
*/
SearchAsYouType.prototype.handleImageOnLoad = function(el) {
if (el) {
el.style.display = 'inline';
this.updateDimensionsAndShadow_(this.searchResultsEl);
}
return false;
}
/**
* Hide the search results window. This initializes the fadeout.
*/
SearchAsYouType.prototype.hideResultsWindow_ = function() {
if (this.resultsWindowHidden) {
return;
}
this.clearAutocomplete_(true);
this.hideOpacity = this.searchResultsEl.style.opacity;
clearInterval(this.hideTimeout);
this.fadeLastTime = new Date().getTime();
this.hideTimeout =
setInterval(searchAsYouTypeBind(this.fadeResultsWindow_, this), 20);
this.resultsWindowHidden = true;
this.activeResult = -1;
}
/**
* Fade the search results window a little bit more. We're counting the
* time so it should always take the same amount of time, only perhaps be a
* little less smooth on less powerful machines.
*/
SearchAsYouType.prototype.fadeResultsWindow_ = function() {
var newTime = new Date().getTime();
this.hideOpacity -= (newTime - this.fadeLastTime) * 0.005;
this.fadeLastTime = newTime;
if (this.hideOpacity <= 0) {
clearInterval(this.hideTimeout);
this.searchResultsEl.style.display = 'none';
this.searchResultsShadowEl.style.visibility = 'hidden';
} else {
this.searchResultsEl.style.opacity = this.hideOpacity;
this.searchResultsShadowEl.style.opacity = this.hideOpacity;
}
}
/**
* Activate (highlight) a result. Used for keyboard navigation
* between search results.
* @param {int} no The number of the result to activate
*/
SearchAsYouType.prototype.highlightSearchResult_ = function(no) {
document.getElementById('searchResult_'+this.token + '_' + no).className += " highlighted";
}
/**
* Deactivate (de-highlight) a result. Used for keyboard navigation
* between search results.
* @param {int} no The number of the result to deactivate
*/
SearchAsYouType.prototype.unhighlightSearchResult_ = function(no) {
document.getElementById('searchResult_'+this.token +'_'+ no).className =
document.getElementById('searchResult_'+this.token + '_'+ no).className.
replace(/ highlighted/, "");
}
/**
* Activate (highlight) a next result, if possible.
*/
SearchAsYouType.prototype.highlightNextSearchResult_ = function() {
if (this.results.results.length) {
if (this.activeResult == -1) {
this.activeResult = 0;
if (this.inputFieldHasFocus) {
this.inputFieldEl.blur();
}
this.highlightSearchResult_(this.activeResult);
} else if (this.activeResult < this.results.results.length - 1) {
this.unhighlightSearchResult_(this.activeResult);
this.activeResult++;
this.highlightSearchResult_(this.activeResult);
}
}
}
/**
* Deactivate (de-highlight) a next result, if possible.
*/
SearchAsYouType.prototype.highlightPrevSearchResult_ = function() {
if (this.results.results.length) {
if (this.activeResult == 0) {
// Going up from the first result will get us back in the input field
this.unhighlightSearchResult_(this.activeResult);
this.activeResult = -1;
this.inputFieldEl.focus();
} else if (this.activeResult > 0) {
this.unhighlightSearchResult_(this.activeResult);
this.activeResult--;
this.highlightSearchResult_(this.activeResult);
}
}
}
/**
* Expand a summarized result.
* @param {event} e Browser event (or null if invoked from here)
* @param {int} id Id of the result to be expanded
*/
SearchAsYouType.prototype.expandSummaryResult_ = function(e, id) {
e = e || window.event;
if (e) {
e.cancelBubble = true;
}
var dividerEl = document.createElement("divider");
var el = document.getElementById('searchResult_'+this.token+ '_' + id);
var result = this.results.results[el.getAttribute('originalId')];
var elParent = el.parentNode;
elParent.insertBefore(dividerEl, el);
elParent.removeChild(el);
elParent.parentNode.innerHTML =
elParent.parentNode.innerHTML.replace(//,
"
" + this.getResultsHtml_(id) +
"
");
var el = document.getElementById('searchResult_'+this.token + '_' + id);
var newEl = document.createElement("span");
newEl.innerHTML = ' · ';
var elPrev = el.previousSibling;
if (elPrev) {
if (!elPrev.getElementsByTagName('a').length) {
elPrev.parentNode.removeChild(elPrev);
} else {
elPrev.innerHTML =
elPrev.innerHTML.replace(new RegExp(newEl.innerHTML + "$"), "");
}
}
var elNext = el.nextSibling;
if (elNext) {
if (!elNext.getElementsByTagName('a').length) {
elNext.parentNode.removeChild(elNext);
} else {
elNext.innerHTML =
elNext.innerHTML.replace(new RegExp("^" + newEl.innerHTML),
this.getResultTypeDescriptionHtml_(result.type));
}
}
this.updateDimensionsAndShadow_(this.searchResultsEl);
return false;
}
/**
* Add autocomplete if it's available.
* @return {boolean} true if added, false if not
*/
SearchAsYouType.prototype.addAutocompleteTextIfPossible_ = function() {
var results = this.results;
if (!results.query) {
return; // not there yet
}
var inputFieldValue = this.getInputFieldValue_().toLowerCase();
if ((results.query.toLowerCase() ==
inputFieldValue.substr(0, results.query.length)) &&
(inputFieldValue ==
results.autocompletedQuery.substr(0, inputFieldValue.length).
toLowerCase())) {
this.autocomplete =
results.autocompletedQuery.substring(inputFieldValue.length);
if (this.autocomplete) {
var noAutocomplete = this.inputFieldEl.value.replace(/\ /, " ");
this.autocompleteHelperEl.style.display = 'block';
this.autocompleteHelperEl.innerHTML = noAutocomplete;
var noAutocompleteWidth = this.autocompleteHelperEl.offsetWidth;
this.autocompleteHelperEl.innerHTML = this.autocomplete;
var autocompleteWidth = this.autocompleteHelperEl.offsetWidth;
this.autocompleteHelperEl.style.display = 'none';
this.autocompleteEl.innerHTML =
this.autocomplete.replace(/\ /, " ");
this.autocompleteEl.style.left =
(parseInt(this.autocompleteEl.getAttribute("originalLeft")) +
noAutocompleteWidth) + "px";
this.autocompleteEl.style.display = 'block';
} else {
this.autocompleteEl.style.display = 'none';
}
return true;
}
this.clearAutocomplete_(true);
return false;
}
/**
* Collapse autocomplete, i.e. make it part of the actual input field.
*/
SearchAsYouType.prototype.collapseAutocomplete_ = function() {
if (this.autocomplete) {
this.inputFieldEl.value += this.autocomplete + " ";
this.inputFieldEl.selectionStart = this.inputFieldEl.value.length;
this.inputFieldEl.selectionEnd = this.inputFieldEl.value.length;
this.clearAutocomplete_(false);
}
}
/**
* Clear and hide autocomplete if present.
* @param {boolean} hideResultsWindow Whether to hide the results window after
* clearing autocomplete
*/
SearchAsYouType.prototype.clearAutocomplete_ = function(hideResultsWindow) {
if (this.autocomplete != '') {
this.autocomplete = '';
this.autocompleteEl.innerHTML = '';
this.autocompleteEl.style.display = 'none';
if (hideResultsWindow) {
this.hideResultsWindow_();
}
}
}
/**
* Handle a key press event in the input field.
* @param {event} e Browser event
*/
SearchAsYouType.prototype.handleInputKeyPress = function(e) {
if (!this.initialized) {
return;
}
var valueToReturn = true;
e = e || window.event;
var whichKey = (e.which) ? e.which : e.keyCode;
switch (whichKey) {
case 9: // Tab
if (this.autocompleteJustCollapsed) {
valueToReturn = false;
}
break;
}
return valueToReturn;
}
/**
* Handle a key down event in the input field.
* @param {event} e Browser event
*/
SearchAsYouType.prototype.handleInputKeyDown = function(e) {
if (!this.initialized) {
return;
}
e = e || window.event;
var whichKey = (e.which) ? e.which : e.keyCode;
if ((whichKey == 8) || (whichKey == 46)) {
this.clearAutocomplete_(false);
}
}
/**
* Handle a key up event in the input field. Fire a search query if
* applicable.
* @param {event} e Browser event
*/
SearchAsYouType.prototype.handleInputKeyUp = function(e) {
if (!this.initialized) return;
e = e || window.event;
var whichKey = (e.which) ? e.which : e.keyCode;
this.lastKeyPressed = whichKey;
if (this.autocompleteJustCollapsed) {
this.typedQuery = this.lastTypedQuery = this.getInputFieldValue_();
this.autocompleteJustCollapsed = false;
return;
}
// Changing the query to lowercase and stripping out the white
// space surrounding it
var query = this.getInputFieldValue_();
if (query != this.typedQuery) {
if (this.showResultsTimeoutId > -1) {
clearTimeout(this.showResultsTimeoutId);
}
this.lastTypedQuery = this.typedQuery;
// We don't auto-complete on Backspace
if (whichKey != 8) {
if (this.addAutocompleteTextIfPossible_()) {
this.typedQuery = this.lastTypedQuery = this.getInputFieldValue_();
this.search_();
}
}
this.typedQuery = this.getInputFieldValue_();
if (this.lastTypedQuery != this.typedQuery) {
if (this.keystrokeTimeoutId != -1) {
clearTimeout(this.keystrokeTimeoutId);
this.keystrokeTimeoutId = -1;
}
if (!this.typedQuery) {
this.hideResultsWindow_();
this.clearInputField_();
}
if (whichKey == 8) {
this.clearAutocomplete_(true);
}
this.keystrokeTimeoutId = setTimeout(
searchAsYouTypeBind(this.search_, this),
searchAsYouTypeConfiguration.keystrokeDelay);
}
}
return true;
}
/**
* Handle a key down event in the document body.
* @param {event} e Browser event
*/
SearchAsYouType.prototype.handleBodyKeyDown = function(e) {
var valueToReturn = true;
if (!this.initialized) {
return;
}
e = e || window.event;
var whichKey = (e.which) ? e.which : e.keyCode;
var targetElement = (e.target) ? e.target : e.srcElement;
switch (whichKey) {
case 13: // Enter
case 32: // space
if ((!this.resultsWindowHidden) && (this.activeResult >= 0)) {
if (document.getElementById('searchResult_'+this.token + '_'+ this.activeResult).
className.indexOf('summarized') == -1) {
// Pressing Enter or space while a search result is active (navigated
// to from the keyboard) will follow the "More info" link
var el = document.getElementById('searchResult_'+this.token + '_' + this.activeResult);
if (el.href) {
var url = el.href;
} else if (el.getAttribute("moreDetailsUrl")) {
var url = el.getAttribute("moreDetailsUrl");
}
if (url) {
this.hideResultsWindow_();
this.goToUrl_(url);
}
} else {
// Otherwise zoom in on a given summary record.
this.expandSummaryResult_(null, this.activeResult);
this.highlightSearchResult_(this.activeResult);
}
valueToReturn = false;
}
break;
case 27: // Escape
// Escape can do three things, in order of precedence:
// 1. If the page with results is loading, Escape should
// be handled by the browser to cancel loading the page.
// 2. If the pop-down with results is shown, Escape should
// remove it.
// 3. Otherwise it should clear the field.
if (this.inputFieldHasFocus) {
// Safari sends Esc code twice, so we ignore the second time
// it happens
if (this.browserSafari && !this.browserSafari3OrHigher) {
if (this.escapeKeyJustPressed) {
this.escapeKeyJustPressed = false;
break;
} else {
this.escapeKeyJustPressed = true;
}
}
if (!this.resultsWindowHidden) {
this.hideResultsWindow_();
valueToReturn = false;
this.inputFieldEl.focus();
this.inputFieldHasFocus = 1;
} else {
this.clearInputField_();
valueToReturn = false;
}
}
break;
case 35: // End
if ((this.inputFieldHasFocus) && (this.autocomplete != '')) {
this.collapseAutocomplete_();
this.autocompleteJustCollapsed = true;
}
break;
case 40: // down arrow
case 63233: // down arrow
case 39: // right arrow
if (whichKey == 39) {
if ((this.inputFieldHasFocus) && (this.autocomplete != '')) {
this.collapseAutocomplete_();
this.autocompleteJustCollapsed = true;
}
}
// If we press down arrow in the input field, we can force the
// re-query
if ((this.resultsWindowHidden) && (this.inputFieldHasFocus) &&
(whichKey != 39)) {
this.search_(true);
valueToReturn = false;
} else if ((!this.resultsWindowHidden) &&
((this.activeResult >= 0) ||
((whichKey != 39) && (this.inputFieldHasFocus)))) {
// If not, right or down arrow activate the next result
this.highlightNextSearchResult_();
valueToReturn = false;
this.arrowKeyProcessed = true;
}
break;
case 38: // up arrow
case 63235: // up arrow
case 37: // left arrow
if (whichKey == 37) {
this.clearAutocomplete_(true);
}
// If we press up arrow in the input field, we hide the pop-down
if ((!this.resultsWindowHidden) && (this.inputFieldHasFocus) &&
(whichKey != 37)) {
this.hideResultsWindow_();
valueToReturn = false;
this.arrowKeyProcessed = true;
} else if ((!this.resultsWindowHidden) && (this.activeResult >= 0)) {
// If not, left or up arrow activate the previous result
this.highlightPrevSearchResult_();
valueToReturn = false;
this.arrowKeyProcessed = true;
}
break;
case 9: // Tab
if (this.inputFieldHasFocus && (this.autocomplete != '')) {
this.collapseAutocomplete_();
this.autocompleteJustCollapsed = true;
valueToReturn = false;
}
break;
}
if (!this.resultsWindowHidden && valueToReturn) {
if (((!this.inputFieldHasFocus) && ((whichKey < 37) || (whichKey > 40))) ||
((whichKey == 9) && (!this.autocompleteJustCollapsed))) {
this.hideResultsWindow_();
}
}
if (!valueToReturn) {
e.returnValue = false;
if (e.preventDefault) {
e.preventDefault();
}
}
}
/**
* Handle a key press event in the document body.
* @param {event} e Browser event
*/
SearchAsYouType.prototype.handleBodyKeyPress = function(e) {
var valueToReturn = true;
if (this.initialized) {
e = e || window.event;
var whichKey = (e.which) ? e.which : e.keyCode;
// Arrow keys have the same key codes here as some other characters
// (for example, down arrow is the same as left parenthesis)
// We have to detect whether the arrow key was pressed during key down,
// and then ignore it here if that's the case (otherwise it'd scroll
// the screen)
if ((this.arrowKeyProcessed) && (whichKey >= 37) && (whichKey <= 40)) {
this.arrowKeyProcessed = false;
valueToReturn = false;
}
if (!valueToReturn) {
e.returnValue = false;
if (e.preventDefault) {
e.preventDefault();
}
}
}
}
/**
* Handle a key up event in the document body.
* @param {event} e Browser event
*/
SearchAsYouType.prototype.handleBodyKeyUp = function(e) {
var valueToReturn = true;
e = e || window.event;
var whichKey = (e.which) ? e.which : e.keyCode;
var targetElement = (e.target) ? e.target : e.srcElement;
this.arrowKeyProcessed = false;
switch (whichKey) {
case 32: // space
if (this.inputFieldHasFocus && (this.autocomplete != '')) {
this.clearAutocomplete_(true);
valueToReturn = false;
}
break;
}
if (!valueToReturn) {
e.returnValue = false;
if (e.preventDefault) {
e.preventDefault();
}
}
}
/**
* Handle a resize event in the document body (to recalculate the search
* results window and its shadow).
* @param {event} e Browser event
*/
SearchAsYouType.prototype.handleBodyResize = function(e) {
this.updateDimensionsAndShadow_(this.searchResultsEl);
}
/**
* Handle input field losing focus. Remember this in a variable.
* @param {event} e Browser event
*/
SearchAsYouType.prototype.handleInputBlur = function(e) {
this.inputFieldHasFocus = 0;
}
/**
* Handle input field receiving focus. Remember this in a variable.
* @param {event} e Browser event
*/
SearchAsYouType.prototype.handleInputFocus = function(e) {
this.inputFieldHasFocus = 0.5;
}
/**
* Handle mouse down on the input field. Collapses autocomplete if
* present.
* @param {event} e Browser event
*/
SearchAsYouType.prototype.handleInputMouseDown = function(e) {
if (this.autocomplete) {
this.collapseAutocomplete_();
}
}
/**
* Handle mouse down on an autocomplete object. Collapses autocomplete if
* present.
* @param {event} e Browser event
*/
SearchAsYouType.prototype.handleAutocompleteMouseDown = function(e) {
if (this.autocomplete) {
this.collapseAutocomplete_();
}
}
/**
* Handle input field receiving a click.
* @param {event} e Browser event
*/
SearchAsYouType.prototype.handleInputClick = function(e) {
e = e || window.event;
e.cancelBubble = true;
// Clicking on the input field again when it's already active
// shows the pop-down again
if (this.inputFieldHasFocus == 1) {
if (this.resultsWindowHidden) {
this.search_(true);
}
} else {
this.inputFieldHasFocus = 1;
}
}
/**
* Handle a click on a search result. Goes to a "more details" URL if the
* given search result has any.
* @param {event} e Browser event
*/
SearchAsYouType.prototype.handleSearchResultClick = function(e) {
e = e || window.event;
var el = (e.target) ? e.target : e.srcElement;
while ((el.tagName != 'DIV') ||
(el.className.indexOf('searchResult') == -1)) {
el = el.parentNode;
}
var eltSelected;
if (e.target.text == undefined)
eltSelected = e.target.innerHTML;
else
eltSelected = e.target.text;
this.inputFieldEl.value = eltSelected;
}
/**
* Handle a click in document body.
* @param {event} e Browser event
*/
SearchAsYouType.prototype.handleBodyClick = function(e) {
e = e || window.event;
var targetElement = (e.target) ? e.target : e.srcElement;
this.clearAutocomplete_();
this.hideResultsWindow_();
this.resultsWindowHiddenByClicking = true;
}
/**
* Go to a specific URL. If the current page is inside an iframe, it breaks
* out of that iframe.
* @param {string} url URL to go to
*/
SearchAsYouType.prototype.goToUrl_ = function (url) {
try {
if (top.location != location) {
top.location.href = url;
} else {
location.href = url;
}
} catch(e) {
location.href = url;
}
}
/**
* Activate the debug mode, create the debug console.
*/
SearchAsYouType.prototype.activateDebugConsole_ = function() {
document.write("
" +
"
" +
"" +
"" +
"" +
"
Search-as-you-type debug console
" +
" " +
"
" +
"
" +
"
");
this.debugConsoleTimesHeader =
'
Query
' +
'
Auto-completed
' +
'
No. of results
' +
'
Delay before displaying: (fixed)
' +
'
' +
'Total turn-around client+server
' +
'
' +
'Server: Total time
' +
'
';
this.clearDebugConsoleTimes();
}
/**
* Show or hide the debug console.
* @param {event} e Browser event
*/
SearchAsYouType.prototype.toggleDebugConsole = function(e) {
var debugConsoleEl = document.getElementById('searchAsYouTypeDebugConsole');
if (debugConsoleEl.className.indexOf('expanded') != -1) {
debugConsoleEl.className =
debugConsoleEl.className.replace(/expanded/, 'contracted');
} else {
debugConsoleEl.className =
debugConsoleEl.className.replace(/contracted/, 'expanded');
}
e = e || window.event;
e.cancelBubble = true;
this.inputFieldEl.focus();
}
/**
* Add a new line to a debug console times table.
* @param {text} line A new line to be added
*/
SearchAsYouType.prototype.addToDebugConsoleTimesNewLine_ = function(line) {
this.debugConsoleTimesContents =
this.debugConsoleTimesCurrentLine + this.debugConsoleTimesContents;
this.debugConsoleTimesCurrentLine = "
" + line;
document.getElementById("searchAsYouTypeDebugTimes").innerHTML =
this.debugConsoleTimesHeader + this.debugConsoleTimesCurrentLine +
this.debugConsoleTimesContents;
}
/**
* Append to the most recent line to a debug console times table.
* @param {text} line A text to be appended
*/
SearchAsYouType.prototype.addToDebugConsoleTimesCurrentLine_ = function(line) {
this.debugConsoleTimesCurrentLine += line;
document.getElementById("searchAsYouTypeDebugTimes").innerHTML =
this.debugConsoleTimesHeader + this.debugConsoleTimesCurrentLine +
this.debugConsoleTimesContents;
}
/**
* Clear the debug console.
*/
SearchAsYouType.prototype.clearDebugConsoleTimes = function() {
this.debugConsoleTimesContents = '';
this.debugConsoleTimesCurrentLine = '';
document.getElementById("searchAsYouTypeDebugTimes").innerHTML =
this.debugConsoleTimesHeader;
this.inputFieldEl.focus();
}
/**
* Clear the search cache. Used only for debugging.
*/
SearchAsYouType.prototype.clearCache = function() {
this.searchCache = [];
this.inputFieldEl.focus();
}
/**
* A helper function which partially applies a function to a particular
* "this" object and zero or more arguments. The result is a new function
* with some arguments of the first function pre-filled and the value
* of |this| "pre-specified".
*
* Remaining arguments specified at call-time are appended to the pre-
* specified ones.
*/
function searchAsYouTypeBind(fn, self, var_args) {
var boundargs = fn.boundArgs_ || [];
boundargs = boundargs.concat(Array.prototype.slice.call(arguments, 2));
if (typeof fn.boundSelf_ != "undefined") {
self = fn.boundSelf_;
}
if (typeof fn.foundFn_ != "undefined") {
fn = fn.boundFn_;
}
var newfn = function() {
// Combine the static args and the new args into one big array
var args = boundargs.concat(Array.prototype.slice.call(arguments));
return fn.apply(self, args);
}
newfn.boundArgs_ = boundargs;
newfn.boundSelf_ = self;
newfn.boundFn_ = fn;
return newfn;
}
/**
* A helper function cloning an object. It should support well arrays and
* objects inside the object being cloned.
* @param {object} obj An object to be cloned
* @return {object} A cloned object
*/
function searchAsYouTypeCloneObject(obj) {
if (obj instanceof Array) {
var newObj = [];
} else {
var newObj = {};
}
for (var i in obj) {
if (obj[i].constructor.toString().indexOf("Array") != -1) {
newObj[i] = searchAsYouTypeCloneObject(obj[i]);
} else if (typeof obj[i] == 'object') {
newObj[i] = searchAsYouTypeCloneObject(obj[i]);
} else {
newObj[i] = obj[i];
}
}
return newObj;
}
// Instantiating the object...
var searchAsYouType = new SearchAsYouType();
// If a callback function is defined, call it now. This compensates
// for