This article contains code for a simple JavaScript primer for Slickgrid that creates a simple searchable grid in only a few lines of code.
Any of you who know Slickgrid may be impressed with how it handles large volumes of data. This is especially useful if you write software for a business which is reluctant to move from Excel based applications into web based applications. If you use it regularly, then I expect you are producing reams of the same boiler plate code to set it up. We certainly have been. So for this reason, I'd like to share with you a small Slickgrid extension that will help with just that. Assuming you have the following basic html with a textbox with an ID of `GridSearch
`, a div
with the ID of `GridContainer
` and a suitable JSON array (called `myFunkyJsonData
` in this case), then you can create a SlickGrid with my extension and the following lines of code.
var gridBuilderOptions = {
data: myFunkyJsonData,
gridSelector: "#GridContainer",
searchTextFieldSelector: "#GridSearch",
searchableColumnName: "SearchField"
};
var gridBuilder = new Slick.SimpleFilteredGridBuilder(gridBuilderOptions );
gridBuilder.withColumn({ name: "Field 1", field: "Field1", minWidth: 100, maxWidth: 200 });
gridBuilder.withColumn({ name: "Field 2", field: "Field2");
gridBuilder.withColumn({ name: "Field 3", field: "Field3", formatter: Slick.Formatters.YesNo);
gridBuilder.withColumn({ name: "SearchField", field: "SearchField", maxWidth: 1);
gridBuilder.buildGrid();
this.grid = gridBuilder.getGrid();
First, you need to set up some grid builder options which contain the grid data, a selector for the search text input, the selector for the grid container div
and the name of the field to search on. Then, initialise the builder with these options. Use the `withColumn
` method to add column information or use `withAutoColumns
` to just use the properties of the data rows to automatically create the columns. Next, build the grid with `buildGrid
`, and get a reference to it with `getGrid
` if you need it.
So what code makes all this possible? Well, this little extension below.
(function ($) {
$.extend(true, window, {
"Slick": {
"SimpleFilteredGridBuilder": SimpleFilteredGridBuilder
}
});
function SimpleFilteredGridBuilder(options) {
var _grid;
var _columns = [];
var _gridOptions;
var _filter;
var _dataView;
var _$searchTextField;
var _searchString;
var _guard = new Guard();
var _options;
var defaults = {
data: [],
gridSelector: "",
searchTextFieldSelector: "",
searchableColumnName: ""
};
guardArguments(options);
setOptions(options);
function guardArguments(options) {
_guard.argumentNotNullOrUndefined(options.data, "data");
_guard.argumentNotNullUndefinedOrEmpty
(options.gridSelector, "gridSelector");
_guard.argumentNotNullUndefinedOrEmpty
(options.searchTextFieldSelector, "searchTextFieldSelector");
_guard.argumentNotNullUndefinedOrEmpty
(options.searchableColumnName, "searchableColumnName");
}
function setOptions(options) {
_options = $.extend(defaults, options);
}
function buildGrid() {
try {
initSearchField();
initFilter();
initDataView();
initGridOptions();
initGrid();
} catch (e) {
throw new Error("Failed to initialise grid. " + e.message);
}
}
function initSearchField() {
_$searchTextField = $(_options.searchTextFieldSelector);
if (!textSearchFieldExists)
throw new Error("No text search field exists
for selector '" + searchTextFieldSelector + "'");
bindSearchFieldHandlers();
setSearchStringFromField();
}
function textSearchFieldExists() {
return _$searchTextField.length > 0;
}
function bindSearchFieldHandlers() {
_$searchTextField.on("keyup change", filterDataView);
}
function setSearchStringFromField() {
_searchString = _$searchTextField.val().toLowerCase();
}
function withAutoColumns() {
var data = _options.data;
var noData = (data.length === 0);
if (noData) throw new Error
("Cannot create auto columns when no data is present! ");
clearColumns();
var firstRow = data[0];
createColumnsFromRow(firstRow);
}
function createColumnsFromRow(row) {
for (var property in row) {
if (row.hasOwnProperty(property)) {
var column = createColumnFromProperty(property);
withColumn(column);
}
}
}
function createColumnFromProperty(property) {
return {
name: property,
field: property,
id: property
}
}
function clearColumns() {
_columns = [];
}
function withColumn(column) {
if (column == null) throw new Error("column must not be null. ");
_columns.push(column);
}
function getGrid() {
return _grid;
}
function filterDataView() {
_searchString = $(this).val().toLowerCase();
_dataView.refresh();
}
function initFilter() {
_filter = filter;
}
function initDataView() {
var dataView = new Slick.Data.DataView();
dataView.beginUpdate();
dataView.setItems(_options.data, _options.searchableColumnName);
dataView.setFilterArgs({ searchString: _searchString });
dataView.setFilter(_filter);
dataView.endUpdate();
dataView.onRowCountChanged.subscribe(onRowCountChanged);
dataView.onRowsChanged.subscribe(onRowsChanged);
_dataView = dataView;
}
function onRowsChanged(e, args) {
_grid.invalidateRows(args.rows);
_grid.render();
}
function onRowCountChanged() {
_grid.updateRowCount();
_grid.render();
}
function initGridOptions() {
_gridOptions = {
forceFitColumns: true,
autoSizeColumns: true,
enableColumnReorder: false
}
}
function initGrid() {
ensureColumnsExist();
_grid = new Slick.Grid(
_options.gridSelector,
_dataView,
_columns,
_gridOptions);
_grid.render();
}
function ensureColumnsExist() {
if (_columns.length === 0) {
throw new Error("Columns must be set before building the grid. " +
"Please use the 'withColumn(column)'
function to add each column. ");
}
}
function filter(item) {
var filterCriteriaMet = true;
var searchableString = item[_options.searchableColumnName].toLowerCase();
var orSearchDelimiter = ",";
var andSearchDelimiter = " ";
var searchString = _searchString;
var performOrSearch = (searchString.indexOf(orSearchDelimiter) !== -1);
var performAndSearch = (searchString.indexOf(andSearchDelimiter) !== -1);
if (performOrSearch) {
filterCriteriaMet = orSearchHasMatch(searchableString);
} else if (performAndSearch) {
filterCriteriaMet = andSearchHasMatch(searchableString);
} else {
var singleWordExactMatchNotFound =
(searchString !== "" && searchableString.indexOf(searchString) === -1);
if (singleWordExactMatchNotFound) filterCriteriaMet = false;
}
return filterCriteriaMet;
}
function orSearchHasMatch(searchableString) {
var matchFound = false;
var searchStringItems = _searchString.split(",");
for (var index = 0; index < searchStringItems.length; index++) {
var trimmedItem = searchStringItems[index].trim();
var notEmpty = trimmedItem !== "";
var searchableStringContainsTrimmedItem = (searchableString.indexOf(trimmedItem) !== -1);
var matchOnTrimmedOrItemNotMet = (notEmpty && searchableStringContainsTrimmedItem);
if (matchOnTrimmedOrItemNotMet) {
matchFound = true;
break;
}
}
return matchFound;
}
function andSearchHasMatch(searchableString) {
var filterCriteriaMet = true;
var searchStringItems = _searchString.split(" ");
for (var index = 0; index < searchStringItems.length; index++) {
var trimmedItem = searchStringItems[index].trim();
var notEmpty = trimmedItem !== "";
var searchableStringDoesnotContainTrimmedItem =
(searchableString.indexOf(trimmedItem) === -1);
var matchOnTrimmedAndItemNotMet =
(notEmpty && searchableStringDoesnotContainTrimmedItem);
if (matchOnTrimmedAndItemNotMet) {
filterCriteriaMet = false;
break;
}
}
return filterCriteriaMet;
}
return {
clearColumns: clearColumns,
buildGrid: buildGrid,
getGrid: getGrid,
withColumn: withColumn,
withAutoColumns: withAutoColumns
}
}
function Guard() {
var argumentNotNullOrUndefined = function (value, argumentName) {
if (value === undefined || value === null)
throw new Error("[" +
argumentName + "] must not be null or undefined! ");
}
var argumentNotNullUndefinedOrEmpty = function (value, argumentName) {
if (value === undefined || value === null ||
(value.length && value.length === 0))
throw new Error("[" + argumentName +
"] must not be null, undefined or empty! ");
}
return {
argumentNotNullOrUndefined: argumentNotNullOrUndefined,
argumentNotNullUndefinedOrEmpty: argumentNotNullUndefinedOrEmpty
};
}
})(jQuery);
Please note the extension requires Slickgrid and JQuery. See my gist for the full code. I hope that helps someone!