/**
* The search functionality
* @namespace
*/
/* global debug */
/* global findAndReplaceDOMText */
/* global highlightElements */
/* global scrollToElement */
/* global getId */
/* global MenubarActions */
/* global errorPacketHandler */
/* global LoadingView */
/* global handleErrorPage */
/* global dataAsJson */
/* global dataHasJSONError */
/* global addPromptToAjax */
/* global addParametersToAJAX */
/* global SACK */
/* global buildBASEURLForPageFile */
/* global getTranslation */
/* global setInnerText */
/* global menubar */
/* global generator */
var documentSearch = {
searchMenu: null,
resultCount: null,
currentResult: 0,
searchResult: [],
searchResultsTraversed: [],
lastPage: -1,
prevButton: null,
nextButton: null,
lastHighlight: null,
lastPageModeWasSingle: null,
/**
* Create the search panel
* @param {object} parentButton where to add the search panel
* @param {boolean} insertBefore Element to insert the resulting button before
*/
buildSearch: function (parentButton, insertBefore) {
var menu = new generator.submenuContainer(parentButton);
var group = document.createElement("div");
group.className = "__menuGroup __searchBar";
var search = document.createElement("div");
search.className = "__comboBox __menuDropDown";
group.appendChild(search);
menu.input = document.createElement("input");
menu.input.className = "__comboInput";
search.appendChild(menu.input);
menu.input.addEvent("keydown", documentSearch.inputKeyEvent);
documentSearch.resultCount = document.createElement("span");
search.appendChild(documentSearch.resultCount);
var bar = new menubar();
var button = bar.createButton("", "searchfield", null, documentSearch.runSearch, null, getTranslation("menuBar.search"), search, '__comboButton __icon');
button.removeClassName("__menuButton");
documentSearch.prevButton = bar.createButton("<", "search.previous", null, documentSearch.searchPrevious, null, getTranslation("menuBar.search.previous"), group, '__icon');
documentSearch.nextButton = bar.createButton(">", "search.next", null, documentSearch.searchNext, null, getTranslation("menuBar.search.next"), group, '__icon');
documentSearch.prevButton.setEnabled(false);
documentSearch.nextButton.setEnabled(false);
menu.addEntry(group);
if (typeof insertBefore != 'undefined') {
getId('__menuBar').insertBefore(menu.submenu, insertBefore);
} else {
getId('__menuBar').appendChild(menu.submenu);
}
documentSearch.searchMenu = menu;
},
clearSearch: function( full ) {
documentSearch.searchResultsTraversed = [];
documentSearch.currentResult = 0;
documentSearch.lastHighlight = null;
// Clear DOM of HighlightElements
documentSearch.searchResult.forEach(function (element) {
if (element.highlight) {
element.highlight.forEach(function (self) {
var text = document.createTextNode(self.textContent || self.innerText);
self.parentNode.insertBefore(text, self);
self.parentNode.removeChild(self);
});
}
});
documentSearch.searchResult = [];
documentSearch.updateButtons();
documentSearch.setCurrentCount();
documentSearch.setResultCount();
if ( full === true ) {
documentSearch.searchMenu.input.value = "";
documentSearch.showSearch( null, false );
}
},
inputKeyEvent: function (event) {
let keycode;
if (event.which) {
keycode = event.which;
} else {
keycode = event.keyCode;
}
switch (keycode) {
case 16:
break; // Shift / Ignore single modifier
case 17:
break; // CTRL
case 18:
break; // Alt
case 91:
break; // OSX CMD
case 13:
documentSearch.runSearch(event);
break;
case 27:
if (documentSearch.searchResult.length === 0 && documentSearch.searchMenu.menuContainer.style.display == 'block') {
documentSearch.showSearch(null, false); // Close
break;
}
documentSearch.clearSearch( true );
/* fall through */
default: // Keypressed, so reset counts
if (!event.altKey && !event.metaKey && !event.altGraphKey) {
documentSearch.clearSearch();
}
}
},
updateCount: function (attribute, value) {
if (documentSearch.resultCount) {
if (parseInt(value) > 0) {
documentSearch.resultCount.setAttribute(attribute, value);
} else {
documentSearch.resultCount.removeAttribute(attribute);
}
}
},
hasMoreResult: function() {
return documentSearch.searchResult.length > 0 && documentSearch.lastPage < (new MenubarActions()).maxPages;
},
setResultCount: function () {
var resultCount = documentSearch.searchResult.length;
documentSearch.updateCount("resultcount", resultCount);
},
setCurrentCount: function () {
documentSearch.updateCount("currentcount", documentSearch.currentResult);
},
jumpedOverTimeout: null,
setJumpedOver: function (element) {
if (!documentSearch.resultCount) {
return;
}
documentSearch.resultCount.setAttribute("animate", "true");
setInnerText(documentSearch.resultCount, getTranslation("menuBar.search.jumpedElement") + element);
if (documentSearch.jumpedOverTimeout != null) {
window.clearTimeout(documentSearch.jumpedOverTimeout);
documentSearch.jumpedOverTimeout = null;
}
documentSearch.jumpedOverTimeout = window.setTimeout(function () {
documentSearch.resultCount.removeAttribute("animate");
documentSearch.jumpedOverTimeout = null;
}, 2000);
},
/** Open the search popup */
showSearch: function ( event, show ) {
if ( typeof show == "undefined" ) {
show = documentSearch.searchMenu.menuContainer.style.display != 'block';
}
documentSearch.searchMenu.menuContainer.style.display = show ? 'block' : 'none';
documentSearch.searchMenu.parentElement.setIsCurrent(show);
if ( show ) {
documentSearch.searchMenu.input.focus();
}
},
/** Start the search using the server */
runSearch: function (event, lastPage) {
// Called on Enter and Click. If there is a result already, cycle.
if (documentSearch.searchResult.length != 0 && !lastPage) {
if (event.shiftKey) {
documentSearch.searchPrevious();
} else {
documentSearch.searchNext();
}
return;
}
var url = buildBASEURLForPageFile(null, true);
var ajax = new SACK(url);
// Set all parameters
addParametersToAJAX(ajax, {
phrase: documentSearch.searchMenu.input.value,
cmd: "search",
page: lastPage || 0
});
addPromptToAjax && addPromptToAjax(ajax);
ajax.onCompletion = function () {
/* check for error */
var data = ajax.response;
var status = ajax.responseStatus[0];
(new LoadingView()).hide();
if (dataHasJSONError(data)) {
return;
}
if (status >= 200 && status < 400) {
// should be a search response!
var json = dataAsJson(data) || {};
documentSearch.searchResult = documentSearch.searchResult.concat( json.results || [] );
documentSearch.lastPage = json.lastPage || -1;
// Traverse per page
documentSearch.searchResult.forEach(function (page) {
if (typeof documentSearch.searchResultsTraversed[page.page] != 'object') {
documentSearch.searchResultsTraversed[page.page] = [];
}
page.index = documentSearch.searchResultsTraversed[page.page].length;
documentSearch.searchResultsTraversed[page.page].push(page);
});
documentSearch.setResultCount();
documentSearch.updateButtons();
!lastPage && documentSearch.searchNext();
} else if (!ajax.canceled && status > 0) {
handleErrorPage(data, status);
}
};
try {
(new LoadingView()).show();
debug("Searching");
ajax.runAJAX();
} catch (e) {
(new LoadingView()).hide();
new errorPacketHandler(this.pageNotfoundError);
}
},
/** Skip to next result */
searchNext: function () {
if (!documentSearch.nextButton.isEnabled()) {
return false;
}
if (documentSearch.searchResult.length === 0) {
documentSearch.runSearch();
return false;
}
var nextPage = Math.min(++documentSearch.currentResult, documentSearch.searchResult.length);
documentSearch.showResult(nextPage, documentSearch.searchNext);
if ( documentSearch.hasMoreResult() && nextPage == documentSearch.searchResult.length ) {
documentSearch.runSearch( null, documentSearch.lastPage );
}
},
/** Skip to previous result */
searchPrevious: function () {
if (!documentSearch.prevButton.isEnabled()) {
return false;
}
documentSearch.showResult(Math.max(1, --documentSearch.currentResult), documentSearch.searchPrevious);
},
resetResultPage: function (oldPage) {
// Reset
oldPage.highlight = null;
oldPage.skip = null;
},
showResult: function (number, directionFunction) {
// get result
var result = documentSearch.searchResult[number - 1];
var menu = new MenubarActions();
if (documentSearch.lastPageModeWasSingle === null) {
documentSearch.lastPageModeWasSingle = getId('single-page').isCurrent();
} else if (documentSearch.lastPageModeWasSingle != getId('single-page').isCurrent()) {
debug("Page mode changed! Reset saved highlight Groups.");
documentSearch.searchResult.forEach(documentSearch.resetResultPage);
documentSearch.lastPageModeWasSingle = getId('single-page').isCurrent();
}
documentSearch.setCurrentCount();
documentSearch.updateButtons();
documentSearch.searchMenu.input.focus();
if (result.skip) {
documentSearch.setJumpedOver(number);
return directionFunction();
}
menu.assurePageIsPresent(result.page, null, function () {
var numberOfResultsOnPage = documentSearch.searchResultsTraversed[result.page];
if (!numberOfResultsOnPage) {
// WTF
return;
}
var page = menu.getCurrentPageName(result.page);
debug("current page on search: " + page);
if (!result.highlight) {
// Now search the page ...
// alert("page:" + result.page + " index:" + index + " result:" + numberOfResultsOnPage.length + " " + page);
documentSearch.highlight(getId(page), result);
}
if (documentSearch.lastHighlight) {
// If previous highlight, remove it
if (documentSearch.lastHighlight.highlight) {
documentSearch.lastHighlight.highlight.forEach(function (self) {
self.removeClassName("currenthighlight");
});
}
// Page Change
if (documentSearch.lastHighlight.page != result.page && getId('single-page').isCurrent()) {
documentSearch.searchResultsTraversed[documentSearch.lastHighlight.page].forEach(documentSearch.resetResultPage);
}
}
if (result.highlight) {
result.highlight.forEach(function (self) {
self.addClassName("currenthighlight");
});
documentSearch.lastHighlight = result;
}
// Might just have changed
if (result.skip) {
documentSearch.setJumpedOver(number);
directionFunction();
} else if (result.highlight) {
scrollToElement(result.highlight[0]);
highlightElements(result.highlight, true);
}
});
},
highlightRegExp: function () {
var regex = [];
var escapeRegExp = function (str) {
return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
};
documentSearch.searchResult.forEach(function (result) {
regex.push(escapeRegExp(result.result));
});
return new RegExp("(" + regex.getUnique().join("|").replace(new RegExp("\\s+", "g"), "\\s*?") + ")", "ig");
},
highlight: function (root, result) {
var adjust = [];
root.addClassName('isSearching');
var addFunction = function (highlight, elementNumber) {
var currentElem = documentSearch.searchResultsTraversed[result.page][elementNumber];
if (currentElem) {
if (typeof currentElem.highlight !== 'object' || currentElem.highlight == null) {
currentElem.highlight = [];
}
currentElem.highlight.push(highlight); // Next Element
highlight.result = currentElem;
adjust.push(highlight);
}
return highlight;
};
findAndReplaceDOMText(documentSearch.highlightRegExp(), root, function (el, matchIndex, currentNode) {
// No no empty.
if (el.trim().length === 0) {
return document.createTextNode(el);
}
var highlight = document.createElement("span");
highlight.className = "highlightprepare";
highlight.appendChild(document.createTextNode(el));
highlight.setAttribute("text", el);
// Go upward the currentNode - max to root
while (currentNode.parentNode && currentNode.parentNode != root) {
currentNode = currentNode.parentNode;
if (currentNode.elementStyle().overflow == 'hidden' && !currentNode.hasClassName('searchOverflowOverride')) {
currentNode.addClassName('searchOverflowOverride');
}
}
return addFunction(highlight, matchIndex);
}, null, function (shouldConsider, matchIndex) {
if (shouldConsider.className.indexOf("highlightprepare") > -1) {
addFunction(shouldConsider, matchIndex);
return false;
}
return true;
});
adjust.forEach(function (element) {
var offsetTop = element.absoluteOffsetTop(root);
if (offsetTop < 0 || offsetTop > root.offsetHeight) {
// UhOh. Not visible on Page!
debug("element: " + element.result.page + "/" + element.result.index + " not visible on Page: " + offsetTop + " (> " + root.offsetHeight + ") ");
element.result.skip = true;
}
});
},
updateButtons: function () {
documentSearch.prevButton.setEnabled(documentSearch.currentResult > 1);
documentSearch.nextButton.setEnabled(documentSearch.currentResult < documentSearch.searchResult.length);
}
};