313 lines
8.2 KiB
JavaScript
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 results'; break;
|
|
case 1: this.dom.results.innerHTML = '1 result'; break;
|
|
default: this.dom.results.innerHTML = resultCount + ' 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;
|