Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / Javascript

Simple JavaScript Slickgrid Primer Extension that Creates a Simple Searchable Grid

5.00/5 (3 votes)
24 Dec 2015CPOL1 min read 8.2K  
This article contains code for a simple JavaScript primer for Slickgrid that creates a simple searchable grid in only a few lines of code.

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.

JavaScript
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.

JavaScript
/*
/   File:       slickgrid.extensions.js
/    Requires:   JQuery
/               SlickGrid
*/

(function ($) {
    // Register namespace
    $.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() {
            /// <summary>
            /// Builds the grid, once columns have been set. 
            /// </summary>
            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() {
            /// <summary>
            /// Automatically creates columns from the properties of the
            /// first row object. Existing columns will be cleared first!
            /// </summary>
            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() {
            /// <summary>
            /// Clears the columns array. Only need to be called if columns have been
            /// added with `withColumn(column)`
            /// </summary>
            _columns = [];
        }

        function withColumn(column) {
            /// <summary>
            /// Adds the specified column to the columns array. 
            /// </summary>
            if (column == null) throw new Error("column must not be null. ");

            _columns.push(column);
        }

        function getGrid() {
            /// <summary>
            /// Returns a reference to the grid. 
            /// </summary>
            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) {
                //Option A - When the search string includes a comma, 
                //treat it as an OR search, e.g. expand the results
                filterCriteriaMet = orSearchHasMatch(searchableString);
                
            } else if (performAndSearch) {
                // Option B - When the search string includes a space, 
                // treat it as an AND search, e.g. restrict the results
                filterCriteriaMet = andSearchHasMatch(searchableString);

            } else {
                // Option C - Single word exact match
                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 {    // public interface
            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 {    // public interface
            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!

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)