Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rewrite plugin Search to es6. #4892

Merged
merged 19 commits into from Apr 4, 2018
Merged
Changes from 14 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter...
Filter file types
Jump to…
Jump to file or symbol
Failed to load files and symbols.

Always

Just for now

@@ -1,35 +1,149 @@
import Hooks from './../../pluginHooks';
import {addClass, removeClass} from './../../helpers/dom/element';
import {registerRenderer, getRenderer} from './../../renderers';
import BasePlugin from './../_base';
import {registerPlugin} from './../../plugins';
import {isObject} from './../../helpers/object';
import {rangeEach} from './../../helpers/number';
import {isUndefined} from './../../helpers/mixed';

const DEFAULT_SEARCH_RESULT_CLASS = 'htSearchResult';

const DEFAULT_CALLBACK = function(instance, row, col, data, testResult) {
instance.getCellMeta(row, col).isSearchResult = testResult;
};

const DEFAULT_QUERY_METHOD = function(query, value) {
if (isUndefined(query) || query === null || !query.toLowerCase || query.length === 0) {
return false;
}
if (isUndefined(value) || value === null) {
return false;
}

return value.toString().toLowerCase().indexOf(query.toLowerCase()) !== -1;
};

/**
* @private
* @plugin Search
*
* @example
*
* ```js
* ...
* // as boolean
* search: true
*
* // as a object with one or more options
* search: {
* callback: myNewCallbackFunction,
* queryMethod: myNewQueryMethod,
* searchResultClass: 'customClass'
* }
*
* // Access to search plugin instance:
* var searchPlugin = hot.getPlugin('search');
*
* // Set callback programmatically:
* searchPlugin.setCallback(myNewCallbackFunction);
* // Set query method programmatically:
* searchPlugin.setQueryMethod(myNewQueryMethod);
* // Set search result cells class programmatically:
* searchPlugin.setSearchResultClass(customClass);
* ...
* ```
*/
function Search(instance) {
this.query = function(queryStr, callback, queryMethod) {
var rowCount = instance.countRows();
var colCount = instance.countCols();
var queryResult = [];

if (!callback) {
callback = Search.global.getDefaultCallback();
}
class Search extends BasePlugin {
constructor(hotInstance) {
super(hotInstance);
/**
* Callback function is responsible for setting the cell's `isSearchResult` property.
*
* @type {Function}
*/
this.callback = DEFAULT_CALLBACK;
/**
* Query function is responsible for determining whether a query matches the value stored in a cell.
*
* @type {Function}
*/
this.queryMethod = DEFAULT_QUERY_METHOD;
/**
* Class added to every cell which `isSearchResult` property is true.
*
* @type {String}
*/
this.searchResultClass = DEFAULT_SEARCH_RESULT_CLASS;
}

/**
* Check if the plugin is enabled in the Handsontable settings.
*
* @returns {Boolean}
*/
isEnabled() {
return this.hot.getSettings().search;
}

if (!queryMethod) {
queryMethod = Search.global.getDefaultQueryMethod();
/**
* Enable plugin for this Handsontable instance.
*/
enablePlugin() {
if (this.enabled) {
return;
}

for (var rowIndex = 0; rowIndex < rowCount; rowIndex++) {
for (var colIndex = 0; colIndex < colCount; colIndex++) {
var cellData = instance.getDataAtCell(rowIndex, colIndex);
var cellProperties = instance.getCellMeta(rowIndex, colIndex);
var cellCallback = cellProperties.search.callback || callback;
var cellQueryMethod = cellProperties.search.queryMethod || queryMethod;
var testResult = cellQueryMethod(queryStr, cellData);
const searchSettings = this.hot.getSettings().search;
this.checkPluginSettings(searchSettings);

this.addHook('beforeRenderer', (TD, row, col, prop, value, cellProperties) => this.onBeforeRenderer(TD, row, col, prop, value, cellProperties));

This comment has been minimized.

Copy link
@wszymanski

wszymanski Mar 28, 2018

Member

Maybe rest parameters would look more clear? Please apply that also to rest of places where hooks are added.

This comment has been minimized.

Copy link
@pnowak

pnowak Mar 28, 2018

Author Member

Done.


super.enablePlugin();
}

/**
* Disable plugin for this Handsontable instance.
*/
disablePlugin() {
this.hot.addHook('beforeRenderer', (TD, row, col, prop, value, cellProperties) => this.onBeforeRenderer(TD, row, col, prop, value, cellProperties));
this.hot.addHookOnce('afterRender', () => {
this.hot.removeHook('beforeRender', (TD, row, col, prop, value, cellProperties) => this.onBeforeRenderer(TD, row, col, prop, value, cellProperties));

This comment has been minimized.

Copy link
@wszymanski

wszymanski Mar 28, 2018

Member

There should be a reference to the proper function.

});
super.disablePlugin();
}

/**
* Updates the plugin to use the latest options you have specified.
*/
updatePlugin() {
this.disablePlugin();
this.enablePlugin();

super.updatePlugin();
}

/**
* Query method - used inside search input listener.
*
* @param {String} queryStr Searched value.
* @param {Function} [callback=DEFAULT_CALLBACK] Callback function responsible for setting the cell's `isSearchResult` property.

This comment has been minimized.

Copy link
@wszymanski

wszymanski Mar 28, 2018

Member

Please change from [callback=DEFAULT_CALLBACK] to [callback]. Apply the same rule below.

This comment has been minimized.

Copy link
@pnowak

pnowak Mar 28, 2018

Author Member

Done.

* @param {Function} [queryMethod=DEFAULT_QUERY_METHOD] Query function responsible for determining whether a query matches the value stored in a cell.
*
* @returns {Array} Return array of objects with `row`, `col`, `data` properties or empty array.
*/
query(queryStr, callback = this.getCallback(), queryMethod = this.getQueryMethod()) {
const rowCount = this.hot.countRows();
const colCount = this.hot.countCols();
const queryResult = [];
const instance = this.hot;

rangeEach(0, rowCount - 1, (rowIndex) => {
rangeEach(0, colCount - 1, (colIndex) => {
const cellData = this.hot.getDataAtCell(rowIndex, colIndex);
const cellProperties = this.hot.getCellMeta(rowIndex, colIndex);
const cellCallback = cellProperties.search.callback || callback;
const cellQueryMethod = cellProperties.search.queryMethod || queryMethod;
const testResult = cellQueryMethod(queryStr, cellData);

if (testResult) {
var singleResult = {
const singleResult = {
row: rowIndex,
col: colIndex,
data: cellData,
@@ -41,95 +155,122 @@ function Search(instance) {
if (cellCallback) {
cellCallback(instance, rowIndex, colIndex, cellData, testResult);
}
}
}
});
});

return queryResult;
};
};

Search.DEFAULT_CALLBACK = function(instance, row, col, data, testResult) {
instance.getCellMeta(row, col).isSearchResult = testResult;
};

Search.DEFAULT_QUERY_METHOD = function(query, value) {
if (typeof query == 'undefined' || query == null || !query.toLowerCase || query.length === 0) {
return false;
/**
* Get callback function.
*
* @returns {Function} Return the callback function.
*/
getCallback() {
return this.callback;
}
if (typeof value == 'undefined' || value == null) {
return false;
}

return value.toString().toLowerCase().indexOf(query.toLowerCase()) != -1;
};

Search.DEFAULT_SEARCH_RESULT_CLASS = 'htSearchResult';

Search.global = (function() {
/**
* Set callback function.
*
* @param {Function} newCallback
*/
setCallback(newCallback) {
this.callback = newCallback;
}

var defaultCallback = Search.DEFAULT_CALLBACK;
var defaultQueryMethod = Search.DEFAULT_QUERY_METHOD;
var defaultSearchResultClass = Search.DEFAULT_SEARCH_RESULT_CLASS;
/**
* Get queryMethod function.
*
* @returns {Function} Return the query method.
*/
getQueryMethod() {
return this.queryMethod;
}

return {
getDefaultCallback() {
return defaultCallback;
},
/**
* Set queryMethod function.
*
* @param {Function} newQueryMethod
*/
setQueryMethod(newQueryMethod) {
this.queryMethod = newQueryMethod;
}

setDefaultCallback(newDefaultCallback) {
defaultCallback = newDefaultCallback;
},
/**
* Get search result cells class.
*
* @returns {Function} Return the cell class.
*/
getSearchResultClass() {
return this.searchResultClass;
}

getDefaultQueryMethod() {
return defaultQueryMethod;
},
/**
* Set search result cells class.
*
* @param {String} newElementClass
*/
setSearchResultClass(newElementClass) {
this.searchResultClass = newElementClass;
}

setDefaultQueryMethod(newDefaultQueryMethod) {
defaultQueryMethod = newDefaultQueryMethod;
},
/**
* Checks the settings of the plugin.
*
* @param {Object} searchSettings The plugin settings, taken from Handsontable configuration.
* @private
*/
checkPluginSettings(searchSettings) {
if (isObject(searchSettings)) {
if (searchSettings.searchResultClass) {
this.setSearchResultClass(searchSettings.searchResultClass);
}

getDefaultSearchResultClass() {
return defaultSearchResultClass;
},
if (searchSettings.queryMethod) {
this.setQueryMethod(searchSettings.queryMethod);
}

setDefaultSearchResultClass(newSearchResultClass) {
defaultSearchResultClass = newSearchResultClass;
if (searchSettings.callback) {
this.setCallback(searchSettings.callback);
}
}
};

}());

function SearchCellDecorator(instance, TD, row, col, prop, value, cellProperties) {
var searchResultClass = (cellProperties.search !== null && typeof cellProperties.search == 'object' &&
cellProperties.search.searchResultClass) || Search.global.getDefaultSearchResultClass();

if (cellProperties.isSearchResult) {
addClass(TD, searchResultClass);
} else {
removeClass(TD, searchResultClass);
}
};

var originalBaseRenderer = getRenderer('base');

registerRenderer('base', function(instance, TD, row, col, prop, value, cellProperties) {
originalBaseRenderer.apply(this, arguments);
SearchCellDecorator.apply(this, arguments);
});
/** *
* The `beforeRenderer` hook callback.
*
* @private
* @param {HTMLTableCellElement} TD The rendered `TD` element.
* @param {Number} row Visual row index.
* @param {Number} col Visual column index.
* @param {String | Number} prop Column property name or a column index, if datasource is an array of arrays.
* @param {String} value Value of the rendered cell.
* @param {Object} cellProperties Object containing the cell's properties.
*/
onBeforeRenderer(TD, row, col, prop, value, cellProperties) {
if (!cellProperties.className) {
cellProperties.className = '';
}

function init() {
var instance = this;
if (this.isEnabled() && cellProperties.isSearchResult) {
if (!cellProperties.className.includes(this.searchResultClass)) {

This comment has been minimized.

Copy link
@wszymanski

wszymanski Mar 28, 2018

Member

It could be done within util. Property className can be also an array.

cellProperties.className += ` ${this.searchResultClass}`;
}

var pluginEnabled = !!instance.getSettings().search;
} else {
cellProperties.className = cellProperties.className.replace(this.searchResultClass, '');
}
}

if (pluginEnabled) {
instance.search = new Search(instance);
} else {
delete instance.search;
/**
* Destroy plugin instance.
*/
destroy() {
super.destroy();
}
}

Hooks.getSingleton().add('afterInit', init);
Hooks.getSingleton().add('afterUpdateSettings', init);
registerPlugin('search', Search);

export default Search;
ProTip! Use n and p to navigate between commits in a pull request.
You can’t perform that action at this time.