jsoneditor/src_old/js/SearchBox.js

313 lines
8.2 KiB
JavaScript

'use strict';
/**
* @constructor SearchBox
* Create a search box in given HTML container
* @param {JSONEditor} editor The JSON Editor to attach to
* @param {Element} container HTML container element of where to
* create the search box
*/
function SearchBox (editor, container) {
var searchBox = this;
this.editor = editor;
this.timeout = undefined;
this.delay = 200; // ms
this.lastText = undefined;
this.dom = {};
this.dom.container = container;
var table = document.createElement('table');
this.dom.table = table;
table.className = 'jsoneditor-search';
container.appendChild(table);
var tbody = document.createElement('tbody');
this.dom.tbody = tbody;
table.appendChild(tbody);
var tr = document.createElement('tr');
tbody.appendChild(tr);
var td = document.createElement('td');
tr.appendChild(td);
var results = document.createElement('div');
this.dom.results = results;
results.className = 'jsoneditor-results';
td.appendChild(results);
td = document.createElement('td');
tr.appendChild(td);
var divInput = document.createElement('div');
this.dom.input = divInput;
divInput.className = 'jsoneditor-frame';
divInput.title = 'Search fields and values';
td.appendChild(divInput);
// table to contain the text input and search button
var tableInput = document.createElement('table');
divInput.appendChild(tableInput);
var tbodySearch = document.createElement('tbody');
tableInput.appendChild(tbodySearch);
tr = document.createElement('tr');
tbodySearch.appendChild(tr);
var refreshSearch = document.createElement('button');
refreshSearch.className = 'jsoneditor-refresh';
td = document.createElement('td');
td.appendChild(refreshSearch);
tr.appendChild(td);
var search = document.createElement('input');
this.dom.search = search;
search.oninput = function (event) {
searchBox._onDelayedSearch(event);
};
search.onchange = function (event) { // For IE 9
searchBox._onSearch();
};
search.onkeydown = function (event) {
searchBox._onKeyDown(event);
};
search.onkeyup = function (event) {
searchBox._onKeyUp(event);
};
refreshSearch.onclick = function (event) {
search.select();
};
// TODO: ESC in FF restores the last input, is a FF bug, https://bugzilla.mozilla.org/show_bug.cgi?id=598819
td = document.createElement('td');
td.appendChild(search);
tr.appendChild(td);
var searchNext = document.createElement('button');
searchNext.title = 'Next result (Enter)';
searchNext.className = 'jsoneditor-next';
searchNext.onclick = function () {
searchBox.next();
};
td = document.createElement('td');
td.appendChild(searchNext);
tr.appendChild(td);
var searchPrevious = document.createElement('button');
searchPrevious.title = 'Previous result (Shift+Enter)';
searchPrevious.className = 'jsoneditor-previous';
searchPrevious.onclick = function () {
searchBox.previous();
};
td = document.createElement('td');
td.appendChild(searchPrevious);
tr.appendChild(td);
}
/**
* Go to the next search result
* @param {boolean} [focus] If true, focus will be set to the next result
* focus is false by default.
*/
SearchBox.prototype.next = function(focus) {
if (this.results != undefined) {
var index = (this.resultIndex != undefined) ? this.resultIndex + 1 : 0;
if (index > this.results.length - 1) {
index = 0;
}
this._setActiveResult(index, focus);
}
};
/**
* Go to the prevous search result
* @param {boolean} [focus] If true, focus will be set to the next result
* focus is false by default.
*/
SearchBox.prototype.previous = function(focus) {
if (this.results != undefined) {
var max = this.results.length - 1;
var index = (this.resultIndex != undefined) ? this.resultIndex - 1 : max;
if (index < 0) {
index = max;
}
this._setActiveResult(index, focus);
}
};
/**
* Set new value for the current active result
* @param {Number} index
* @param {boolean} [focus] If true, focus will be set to the next result.
* focus is false by default.
* @private
*/
SearchBox.prototype._setActiveResult = function(index, focus) {
// de-activate current active result
if (this.activeResult) {
var prevNode = this.activeResult.node;
var prevElem = this.activeResult.elem;
if (prevElem == 'field') {
delete prevNode.searchFieldActive;
}
else {
delete prevNode.searchValueActive;
}
prevNode.updateDom();
}
if (!this.results || !this.results[index]) {
// out of range, set to undefined
this.resultIndex = undefined;
this.activeResult = undefined;
return;
}
this.resultIndex = index;
// set new node active
var node = this.results[this.resultIndex].node;
var elem = this.results[this.resultIndex].elem;
if (elem == 'field') {
node.searchFieldActive = true;
}
else {
node.searchValueActive = true;
}
this.activeResult = this.results[this.resultIndex];
node.updateDom();
// TODO: not so nice that the focus is only set after the animation is finished
node.scrollTo(function () {
if (focus) {
node.focus(elem);
}
});
};
/**
* Cancel any running onDelayedSearch.
* @private
*/
SearchBox.prototype._clearDelay = function() {
if (this.timeout != undefined) {
clearTimeout(this.timeout);
delete this.timeout;
}
};
/**
* Start a timer to execute a search after a short delay.
* Used for reducing the number of searches while typing.
* @param {Event} event
* @private
*/
SearchBox.prototype._onDelayedSearch = function (event) {
// execute the search after a short delay (reduces the number of
// search actions while typing in the search text box)
this._clearDelay();
var searchBox = this;
this.timeout = setTimeout(function (event) {
searchBox._onSearch();
},
this.delay);
};
/**
* Handle onSearch event
* @param {boolean} [forceSearch] If true, search will be executed again even
* when the search text is not changed.
* Default is false.
* @private
*/
SearchBox.prototype._onSearch = function (forceSearch) {
this._clearDelay();
var value = this.dom.search.value;
var text = (value.length > 0) ? value : undefined;
if (text != this.lastText || forceSearch) {
// only search again when changed
this.lastText = text;
this.results = this.editor.search(text);
this._setActiveResult(undefined);
// display search results
if (text != undefined) {
var resultCount = this.results.length;
switch (resultCount) {
case 0: this.dom.results.innerHTML = 'no&nbsp;results'; break;
case 1: this.dom.results.innerHTML = '1&nbsp;result'; break;
default: this.dom.results.innerHTML = resultCount + '&nbsp;results'; break;
}
}
else {
this.dom.results.innerHTML = '';
}
}
};
/**
* Handle onKeyDown event in the input box
* @param {Event} event
* @private
*/
SearchBox.prototype._onKeyDown = function (event) {
var keynum = event.which;
if (keynum == 27) { // ESC
this.dom.search.value = ''; // clear search
this._onSearch();
event.preventDefault();
event.stopPropagation();
}
else if (keynum == 13) { // Enter
if (event.ctrlKey) {
// force to search again
this._onSearch(true);
}
else if (event.shiftKey) {
// move to the previous search result
this.previous();
}
else {
// move to the next search result
this.next();
}
event.preventDefault();
event.stopPropagation();
}
};
/**
* Handle onKeyUp event in the input box
* @param {Event} event
* @private
*/
SearchBox.prototype._onKeyUp = function (event) {
var keynum = event.keyCode;
if (keynum != 27 && keynum != 13) { // !show and !Enter
this._onDelayedSearch(event); // For IE 9
}
};
/**
* Clear the search results
*/
SearchBox.prototype.clear = function () {
this.dom.search.value = '';
this._onSearch();
};
/**
* Destroy the search box
*/
SearchBox.prototype.destroy = function () {
this.editor = null;
this.dom.container.removeChild(this.dom.table);
this.dom = null;
this.results = null;
this.activeResult = null;
this._clearDelay();
};
module.exports = SearchBox;