3

I'm using some customized code gleaned from Telerik's forums to modify Kendo's odd built in multi-select widget to use checkboxes instead.

//MultiSelect - A user extension of KendoUI DropDownList widget.
(function ($) {
    // shorten references to variables
    var kendo = window.kendo,
        ui = kendo.ui,
        DropDownList = ui.DropDownList,
        keys = kendo.keys,
        SELECT = "select",
        SELECTIONCHANGED = "selectionChanged",
        SELECTED = "k-state-selected",
        HIGHLIGHTED = "k-state-active",
        CHECKBOX = "custom-multiselect-check-item",
        SELECTALLITEM = "custom-multiselect-selectAll-item",
        MULTISELECTPOPUP = "custom-multiselect-popup",
        EMPTYSELECTION = "custom-multiselect-summary-empty";

    var lineTemplate = '<input type="checkbox" name="#= {1} #" value="#= {0} #" class="' + CHECKBOX + '" />' +
                    '<span data-value="#= {0} #">#= {1} #</span>';

    var MultiSelectBox = DropDownList.extend({
        init: function (element, options) {
            options.template = kendo.template(kendo.format(lineTemplate, options.dataValueField, options.dataTextField));
            // base call to widget initialization
            DropDownList.fn.init.call(this, element, options);
        },
        options: {
            name: "MultiSelectBox",
            index: -1,
            showSelectAll: null,
            preSummaryCount: 1,  // number of items to show before summarising
            emptySelectionLabel: '',  // what to show when no items are selected
            selectionChanged: null, // provide callback to invoke when selection has changed
        },
        events: [
            SELECTIONCHANGED
        ],
        refresh: function () {
            // base call
            DropDownList.fn.refresh.call(this);
            this._updateSummary();
            $(this.popup.element).addClass(MULTISELECTPOPUP);
        },
        current: function (candidate) {
            return this._current;
        },
        open: function () {
            this._removeSelectAllItem();
            this._addSelectAllItem();
            DropDownList.fn.open.call(this);
            //hook on to popup event because dropdown close does not
            //fire consistently when user clicks on some other elements
            //like a dataviz chart graphic
            this.popup.one('close', $.proxy(this._onPopupClosed, this));
        },
        _onPopupClosed: function () {
            this._removeSelectAllItem();
            this._current = null;
            //this._highlightCurrent();
            this._raiseSelectionChanged();
        },
        _raiseSelectionChanged: function () {
            var currentValue = this.value();
            var currentValues = $.map((currentValue.length > 0 ? currentValue.split(",") : []).sort(), function (item) { return item.toString(); });
            var oldValues = $.map((this._oldValue || []).sort(), function (item) { return item.toString(); });
            // store for next pass
            this._oldValue = $.map(currentValues, function (item) { return item.toString(); });
            var changedArgs = { newValue: currentValues, oldValue: oldValues };
            if (oldValues) {
                var hasChanged = ($(oldValues).not(currentValues).length == 0 && $(currentValues).not(oldValues).length == 0) !== true;
                if (hasChanged) {
                    //if (this.options.selectionChanged)
                    //    this.options.selectionChanged(changedArgs);
                    this.trigger(SELECTIONCHANGED, changedArgs);
                }
            }
            else if (currentValue.length > 0) {
                //if (this.options.selectionChanged)
                //    this.options.selectionChanged(changedArgs);
                this.trigger(SELECTIONCHANGED, changedArgs);
            }
        },
        _addSelectAllItem: function () {
            if (!this.options.showSelectAll) return;
            var firstListItem = this.ul.children('li:first');
            if (firstListItem.length > 0) {
                this.selectAllListItem = $('<li tabindex="-1" role="option" unselectable="on" class="k-item ' + SELECTALLITEM + '"></li>').insertBefore(firstListItem);
                // fake a data object to use for the template binding below
                var selectAllData = {};
                selectAllData[this.options.dataValueField] = '*';
                selectAllData[this.options.dataTextField] = 'All';
                this.selectAllListItem.html(this.options.template(selectAllData));
                this._updateSelectAllItem();
                this._makeUnselectable(); // required for IE8
            }
        },
        _removeSelectAllItem: function () {
            if (this.selectAllListItem) {
                this.selectAllListItem.remove();
            }
            this.selectAllListItem = null;
        },
        _focus: function (li) {
            if (this.popup.visible() && li && this.trigger(SELECT, { item: li })) {
                this.close();
                return;
            }
            this.select(li);
        },
        //_highlightCurrent: function () {

        //    $('li', this.ul).removeClass(HIGHLIGHTED);
        //    $(this._current).addClass(HIGHLIGHTED);
        //},
        _keydown: function (e) {
            // currently ignore Home and End keys
            // can be added later
            if (e.keyCode == kendo.keys.HOME ||
                e.keyCode == kendo.keys.END) {
                e.preventDefault();
                return;
            }
            DropDownList.fn._keydown.call(this, e);
        },
        _keypress: function(e) {
            // disable existing function
        },
        _move: function (e) {
            var that = this,
                key = e.keyCode,
                ul = that.ul[0],
                down = key === keys.DOWN,
                pressed;
            if (key === keys.UP || down) {
                if (down) {
                    if (!that.popup.visible()) {
                        that.toggle(down);
                    }
                    if (!that._current) {
                        that._current = ul.firstChild;
                    } else {
                        that._current = ($(that._current)[0].nextSibling || that._current);
                    }
                } else {
                    //up
                    // only if anything is highlighted
                    if (that._current) {
                        that._current = ($(that._current)[0].previousSibling || ul.firstChild);
                    }
                }
                if (that._current) {
                    that._scroll(that._current);
                }
                that._highlightCurrent();
                e.preventDefault();
                pressed = true;
            } else {
                pressed = DropDownList.fn._move.call(this, e);
            }
            return pressed;
        },
        selectAll: function () {
            var unselectedItems = this._getUnselectedListItems();
            this._selectItems(unselectedItems);
            // todo: raise custom event
        },
        unselectAll: function () {
            var selectedItems = this._getSelectedListItems();
            this._selectItems(selectedItems);  // will invert the selection
            // todo: raise custom event
        },
        _selectItems: function (listItems) {
            var that = this;
            $.each(listItems, function (i, item) {
                var idx = ui.List.inArray(item, that.ul[0]);
                that.select(idx);  // select OR unselect
            });
        },
        _selectItem: function () {
            // method override to prevent default selection of first item, done by normal dropdown
            var that = this,
                options = that.options,
                useOptionIndex,
                value;
            useOptionIndex = that._isSelect && !that._initial && !options.value && options.index && !that._bound;
            if (!useOptionIndex) {
                value = that._selectedValue || options.value || that._accessor();
            }
            if (value) {
                that.value(value);
            } else if (that._bound === undefined) {
                that.select(options.index);
            }
        },
        _select: function (li) {
            var that = this,
                value,
                text,
                idx;
            li = that._get(li);
            if (li && li[0]) {
                idx = ui.List.inArray(li[0], that.ul[0]);
                if (idx > -1) {
                    if (li.hasClass(SELECTED)) {
                        li.removeClass(SELECTED);
                        that._uncheckItem(li);
                        if (this.selectAllListItem && li[0] === this.selectAllListItem[0]) {
                            this.unselectAll();
                        }
                    } else {
                        li.addClass(SELECTED);
                        that._checkItem(li);
                        if (this.selectAllListItem && li[0] === this.selectAllListItem[0]) {
                            this.selectAll();
                        }
                    }
                    if (this._open) {
                        that._current(li);
                        that._highlightCurrent();
                    }
                    var selecteditems = this._getSelectedListItems();
                    value = [];
                    text = [];
                    $.each(selecteditems, function (indx, item) {
                        var obj = $(item).children("span").first();
                        value.push(obj.attr("data-value"));
                        text.push(obj.text());
                    });
                    that._updateSummary(text);
                    that._updateSelectAllItem();
                    that._accessor(value, idx);
                    // todo: raise change event (add support for selectedIndex) if required
                }
            }

        },
        _getAllValueListItems: function () {
            if (this.selectAllListItem) {
                return this.ul.children("li").not(this.selectAllListItem[0]);
            } else {
                return this.ul.children("li");
            }
        },
        _getSelectedListItems: function () {
            return this._getAllValueListItems().filter("." + SELECTED);
        },
        _getUnselectedListItems: function () {
            return this._getAllValueListItems().filter(":not(." + SELECTED + ")");
        },
        _getSelectedItemsText: function () {
            var text = [];
            var selecteditems = this._getSelectedListItems();
            $.each(selecteditems, function (indx, item) {
                var obj = $(item).children("span").first();
                text.push(obj.text());
            });
            return text;
        },
        _updateSelectAllItem: function () {
            if (!this.selectAllListItem) return;
            // are all items selected?
            if (this._getAllValueListItems().length == this._getSelectedListItems().length) {
                this._checkItem(this.selectAllListItem);
                this.selectAllListItem.addClass(SELECTED);
            }
            else {
                this._uncheckItem(this.selectAllListItem);
                this.selectAllListItem.removeClass(SELECTED);
            }
        },
        _updateSummary: function (itemsText) {
            if (!itemsText) {
                itemsText = this._getSelectedItemsText();
            }
            if (itemsText.length == 0) {
                this._inputWrapper.addClass(EMPTYSELECTION);
                this.text(this.options.emptySelectionLabel);
                return;
            } else {
                this._inputWrapper.removeClass(EMPTYSELECTION);
            }

            if (itemsText.length <= this.options.preSummaryCount) {
                this._textAccessor(itemsText.join(", "));
            }
            else {
                this._textAccessor(itemsText.length + ' selected');
            }
        },
        _checkItem: function (itemContainer) {
            if (!itemContainer) return;
            itemContainer.children("input").prop("checked", true);
        },
        _uncheckItem: function (itemContainer) {
            if (!itemContainer) return;
            itemContainer.children("input").removeAttr("checked");
        },
        _isItemChecked: function (itemContainer) {
            return itemContainer.children("input:checked").length > 0;
        },
        value: function (value) {
            var that = this,
                idx,
                valuesList = [];
            if (value !== undefined) {
                if (!$.isArray(value)) {
                    valuesList.push(value);
                    this._oldValue = valuesList; // to allow for selectionChanged event
                }
                else {
                    valuesList = value;
                    this._oldValue = value; // to allow for selectionChanged event
                }
                // clear all selections first
                $(that.ul[0]).children("li").removeClass(SELECTED);
                $("input", that.ul[0]).removeAttr("checked");
                $.each(valuesList, function (indx, item) {
                    var hasValue;
                    if (item !== null) {
                        item = item.toString();
                    }
                    that._selectedValue = item;
                    hasValue = value || (that.options.optionLabel && !that.element[0].disabled && value === "");
                    if (hasValue && that._fetchItems(value)) {
                        return;
                    }
                    idx = that._index(item);
                    if (idx > -1) {
                        that.select(idx);
                    }
                });
                that._updateSummary();
            }
            else {
                var selecteditems = this._getSelectedListItems();
                return $.map(selecteditems, function(item) {
                    var obj = $(item).children("span").first();
                    return obj.attr("data-value");
                }).join();
            }
        },

    });
    ui.plugin(MultiSelectBox);
})(jQuery);

And here's the code adding the widget to my Kendo columns array:

/****************************************************************
     * Set up the columns.
     ***************************************************************/
    var columns =  [
        {
            field: 'status',
            title: 'Status',
            width: 80,
            filterable: {
                extra: false, // Don't allow secondary filters.
                operators: {
                    // Sets up filter operators for "string" type columns.
                    string: {
                        // Allow 'equals'.
                        eq: "Equals"
                    }
                },
                ui: function(element) {
                    element.removeAttr("data-bind");
                    element.kendoMultiSelectBox({
                        dataSource: reasonCodes,
                        dataTextField: 'text',
                        dataValueField: 'value',
                        showSelectAll: false,
                        autoBind: false,
                        emptySelectionLabel: "--Select Value(s)--",
                        selectionChanged: function() {
                            applyStatusFilter(this.value());
                        }
                    });
                }
            },
            template: function(dataItem) {
                return dataItem.statusDesc;
            }
        }
    ];

Here's my issue: The drop down filter still displays the Kendo "logic" box for selecting "is equal to", which I don't want. I'm not sure how to hide this box without affecting all the other filters in column headers since Kendo doesn't put an ID on them.

Also, the Filter and Cancel buttons get blocked by the drop down when the user clicks in the "---Select Value(s)---" block, which means they have to click their selections, then click back above the drop down to close it, thereby making the buttons visible again.

Best case scenario is I would like to hide the logic box, for only this column, and move the submit buttons above the drop down box, so they are visible while it's open. I'm still a pretty green javascripter, not to mention using a library like Kendo where so much is done "auto-magically", so I've been floundering a bit.

Any help would be greatly appreciated!

deaddancer
  • 544
  • 8
  • 16

2 Answers2

0

I actually ended up turning off filtering for this column, added an ID to the input tag using the headerTemplate option, and added a multiSelectBox with JQuery:

var statusDropdown = $('#statusMultiSelect');
statusDropdown.kendoMultiSelectBox({
    dataTextField: 'text',
    dataValueField: 'value',
    dataSource: [{"value":"AC","text":"Active"},{"value":"OH","text":"On Hold"},{"value":"CL","text":"Closed"}],
    showSelectAll: false,
    emptySelectionLabel: "Status",
    selectionChanged: function() {
        applyStatusFilter(this.value());
    }
}); 

When selection(s) is made in my dropdown, I just have to manually manipulate the kendo grid filters based on the dropdown value(s).

deaddancer
  • 544
  • 8
  • 16
0

Your code doesn't work because of undefined functions _fetchItems, _index and select. These three are called in value: function (value).