Zelter Ady
Zelter Ady

Reputation: 6338

jquery-ui-picklist: set maximum selected items

I use the jquery-ui-picklist jquery plugin in my project and I want to set a limit for the items the user can select.

  1. How do I set a limit for the maximum items that can be selected?
  2. How to disable a button on the control? - in case the max number of items are selected, I want to disable the "add" button

Thank you.

Upvotes: 0

Views: 1577

Answers (2)

Zelter Ady
Zelter Ady

Reputation: 6338

The plugin has no such property. No limit for maximum selected options exists. To add this I changed a bit the code, by adding the limit by myself.

The code is long and I change it in many points, so it's difficult now (I don't even remember all the changes I did) to point all here. A short explanation about how I did is this:

  • a added a new internal variable called selectLimit, by default set to a big number. To enabl limitation you should set that value to a value smaller than the total items in select list.
  • before to select a new item I check if I can select more (if the counter of selected items, plus items already added in the right panel was rich), case when I select the item, else I cancel the selection.
  • I limit the selection only on left panel - if in the right panel you have more items than the limit, then the limit won't affect you when selecting from that panel. This is good if you fill the picker dynamically and the selected items are above the limit.

the full code - plugin with limit is:

/**
 * jQuery PickList Widget
 *
 * Copyright (c) 2012 Jonathon Freeman <[email protected]>
 * Distributed under the terms of the MIT License.
 *
 * http://code.google.com/p/jquery-ui-picklist/
 */
(function($)
{
    $.widget("awnry.pickList",
    {
        widgetEventPrefix: "pickList_",

        options:
        {
            // Container classes
            mainClass:                  "pickList",
            listContainerClass:         "pickList_listContainer",
            sourceListContainerClass:   "pickList_sourceListContainer",
            controlsContainerClass:     "pickList_controlsContainer",
            targetListContainerClass:   "pickList_targetListContainer",
            listClass:                  "pickList_list",
            sourceListClass:            "pickList_sourceList",
            targetListClass:            "pickList_targetList",
            clearClass:                 "pickList_clear",

            // List item classes
            listItemClass:              "pickList_listItem",
            richListItemClass:          "pickList_richListItem",
            selectedListItemClass:      "pickList_selectedListItem",

            // Control classes
            addAllClass:                "pickList_addAll",
            addClass:                   "pickList_add",
            removeAllClass:             "pickList_removeAll",
            removeClass:                "pickList_remove",

            // Control labels
            addAllLabel:                "&gt;&gt;",
            addLabel:                   "&gt;",
            removeAllLabel:             "&lt;&lt;",
            removeLabel:                "&lt;",

            // List labels
            listLabelClass:             "pickList_listLabel",
            sourceListLabel:            "Available",
            sourceListLabelClass:       "pickList_sourceListLabel",
            targetListLabel:            "Selected",
            targetListLabelClass:       "pickList_targetListLabel",

            // Sorting
            sortItems:                  true,
            sortAttribute:              "label",

            // Name of custom value attribute for list items
            listItemValueAttribute:     "data-value",

            // Additional list items
            items:                      [],
            selectLimit:                10000
        },

        _create: function()
        {
            var self = this;

            self._buildPickList();
            self._refresh();
        },

        _buildPickList: function()
        {
            var self = this;

            self._trigger("beforeBuild");

            self.pickList = $("<div/>")
                    .hide()
                    .addClass(self.options.mainClass)
                    .insertAfter(self.element)
                    .append(self._buildSourceList())
                    .append(self._buildControls())
                    .append(self._buildTargetList())
                    .append( $("<div/>").addClass(self.options.clearClass) );

            self._populateLists();

            self.element.hide();
            self.pickList.show();

            self._trigger("afterBuild");
        },

        _buildSourceList: function()
        {
            var self = this;

            var container = $("<div/>")
                    .addClass(self.options.listContainerClass)
                    .addClass(self.options.sourceListContainerClass)
                    .css({
                        "-moz-user-select": "none",
                        "-webkit-user-select": "none",
                        "user-select": "none",
                        "-ms-user-select": "none"
                    })
                    .each(function()
                    {
                        this.onselectstart = function() { return false; };
                    });

            var label = $("<div/>")
                    .text(self.options.sourceListLabel)
                    .addClass(self.options.listLabelClass)
                    .addClass(self.options.sourceListLabelClass);

            self.sourceList = $("<ul/>")
                    .addClass(self.options.listClass)
                    .addClass(self.options.sourceListClass)
                    .delegate("li", "click", { pickList: self }, self._changeHandler);

            container
                    .append(label)
                    .append(self.sourceList);

            return container;
        },

        _buildTargetList: function()
        {
            var self = this;

            var container = $("<div/>")
                    .addClass(self.options.listContainerClass)
                    .addClass(self.options.targetListContainerClass)
                    .css({
                        "-moz-user-select": "none",
                        "-webkit-user-select": "none",
                        "user-select": "none",
                        "-ms-user-select": "none"
                    })
                    .each(function()
                    {
                        this.onselectstart = function() { return false; };
                    });

            var label = $("<div/>")
                    .text(self.options.targetListLabel)
                    .addClass(self.options.listLabelClass)
                    .addClass(self.options.targetListLabelClass);

            self.targetList = $("<ul/>")
                    .addClass(self.options.listClass)
                    .addClass(self.options.targetListClass)
                    .delegate("li", "click", { pickList: self }, self._changeHandler);

            container
                    .append(label)
                    .append(self.targetList);

            return container;
        },

        _buildControls: function()
        {
            var self = this;

            self.controls = $("<div/>").addClass(self.options.controlsContainerClass);

            self.addAllButton = $("<button type='button'/>").click({pickList: self}, self._addAllHandler).html(self.options.addAllLabel).addClass(self.options.addAllClass);
            self.addButton = $("<button type='button'/>").click({pickList: self}, self._addHandler).html(self.options.addLabel).addClass(self.options.addClass);
            self.removeButton = $("<button type='button'/>").click({pickList: self}, self._removeHandler).html(self.options.removeLabel).addClass(self.options.removeClass);
            self.removeAllButton = $("<button type='button'/>").click({pickList: self}, self._removeAllHandler).html(self.options.removeAllLabel).addClass(self.options.removeAllClass);

            self.controls
                    .append(self.addAllButton)
                    .append(self.addButton)
                    .append(self.removeButton)
                    .append(self.removeAllButton);

            return self.controls;
        },

        _populateLists: function()
        {
            var self = this;

            self._trigger("beforePopulate");

            var sourceListItems = [];
            var targetListItems = [];
            var selectItems = self.element.children();

            selectItems.not(":selected").each(function()
            {
                sourceListItems.push( self._createDoppelganger(this) );
            });

            selectItems.filter(":selected").each(function()
            {
                targetListItems.push( self._createDoppelganger(this) );
            });

            self.sourceList.append(sourceListItems.join("\n"));
            self.targetList.append(targetListItems.join("\n"));
            self.insertItems(self.options.items);

            self._trigger("afterPopulate");
        },

        _addAllHandler: function(e)
        {
            var self = e.data.pickList;

            self._trigger("beforeAddAll");

            var items = self.sourceList.children();
            self.targetList.append( self._removeSelections(items) );

            self.element.children().not(":selected").attr("selected", "selected");

            self._refresh();

            self._trigger("afterAddAll", null, { items: items });
            self._trigger("onChange", null, { type: "addAll", items: items });
        },

        _addHandler: function(e)
        {
            var self = e.data.pickList;

            self._trigger("beforeAdd");

            var items = self.sourceList.children(".ui-selected");
            self.targetList.append( self._removeSelections(items) );

            var itemIds = [];
            items.each(function()
            {
                itemIds.push( self._getItemValue(this) );
            });

            self.element.children().filter(function()
            {
                return $.inArray(this.value, itemIds) != -1;
            }).attr("selected", "selected");

            self._refresh();

            self._trigger("afterAdd", null, { items: items });
            self._trigger("onChange", null, { type: "add", items: items });
        },

        _removeHandler: function(e)
        {
            var self = e.data.pickList;

            self._trigger("beforeRemove");

            var items = self.targetList.children(".ui-selected");
            self.sourceList.append( self._removeSelections(items) );

            var itemIds = [];
            items.each(function()
            {
                itemIds.push( self._getItemValue(this) );
            });

            self.element.children().filter(function()
            {
                return $.inArray(this.value, itemIds) != -1;
            }).removeAttr("selected");

            self._refresh();

            self._trigger("afterRemove", null, { items: items });
            self._trigger("onChange", null, { type: "remove", items: items });
        },

        _removeAllHandler: function(e)
        {
            var self = e.data.pickList;

            self._trigger("beforeRemoveAll");

            var items = self.targetList.children();
            self.sourceList.append( self._removeSelections(items) );

            self.element.children().filter(":selected").removeAttr("selected");

            self._refresh();

            self._trigger("afterRemoveAll", null, { items: items });
            self._trigger("onChange", null, { type: "removeAll", items: items });
        },

        _refresh: function()
        {
            var self = this;

            self._trigger("beforeRefresh");

            self._refreshControls();

            // Sort the selection lists.
            if(self.options.sortItems)
            {
                self._sortItems(self.sourceList, self.options);
                self._sortItems(self.targetList, self.options);
            }

            self._trigger("afterRefresh");
        },

        _refreshControls: function()
        {
            var self = this;

            var addBtnEnabled = (self.targetList.children().length < self.options.selectLimit);
            self._trigger("beforeRefreshControls");

            // Enable/disable the Add All button state.
            if(self.sourceList.children().length)
            {
                self.addAllButton.removeAttr("disabled");
            }
            else
            {
                self.addAllButton.attr("disabled", "disabled");
            }

            // Enable/disable the Remove All button state.
            if(self.targetList.children().length)
            {
                self.removeAllButton.removeAttr("disabled");
            }
            else
            {
                self.removeAllButton.attr("disabled", "disabled");
            }

            // Enable/disable the Add button state.
            if(self.sourceList.children(".ui-selected").length && addBtnEnabled)
            {
                self.addButton.removeAttr("disabled");
            }
            else
            {
                self.addButton.attr("disabled", "disabled");
            }

            // Enable/disable the Remove button state.
            if(self.targetList.children(".ui-selected").length)
            {
                self.removeButton.removeAttr("disabled");
            }
            else
            {
                self.removeButton.attr("disabled", "disabled");
            }

            self._trigger("afterRefreshControls");
        },

        _sortItems: function(list, options)
        {
            var items = new Array();

            list.children().each(function()
            {
                items.push( $(this) );
            });

            items.sort(function(a, b)
            {
                if(a.attr(options.sortAttribute) > b.attr(options.sortAttribute))
                {
                    return 1;
                }
                else if(a.attr(options.sortAttribute) == b.attr(options.sortAttribute))
                {
                    return 0;
                }
                else
                {
                    return -1;
                }
            });

            list.empty();

            for(var i = 0; i < items.length; i++)
            {
                list.append(items[i]);
            }
        },

        _changeHandler: function(e)
        {
            var self = e.data.pickList;
            var isClickOnSourcePanel = this.parentNode.attributes["class"].nodeValue.indexOf("pickList_sourceList") >= 0;
            var selectedItems = self.sourceList.children(".ui-selected").length;
            var leftToSelect = self.options.selectLimit - self.targetList.children().length - selectedItems - 1;
            var canSelectMore = isClickOnSourcePanel && leftToSelect >= 0;

            if(e.ctrlKey)
            {
                if(self._isSelected( $(this) ))
                {
                    self._removeSelection( $(this) );
                }
                else
                {
                    if(canSelectMore){
                        self.lastSelectedItem = $(this);
                        self._addSelection( $(this) );
                    }
                    else if(!isClickOnSourcePanel){  //allow to select/deselect without restrictions on right panel.
                        self.lastSelectedItem = $(this);
                        self._addSelection( $(this) );
                    }
                }
            }
            //don't need to select with shift. For a small amount of selectale items
//          else if(e.shiftKey)
//          {
//              var current = self._getItemValue(this);
//              var last = self._getItemValue(self.lastSelectedItem);
//
//              if($(this).index() < $(self.lastSelectedItem).index())
//              {
//                  var temp = current;
//                  current = last;
//                  last = temp;
//              }
//
//              var pastStart = false;
//              var beforeEnd = true;
//
//              self._clearSelections( $(this).parent() );
//
//              $(this).parent().children().each(function()
//              {
//                  if(self._getItemValue(this) == last)
//                  {
//                      pastStart = true;
//                  }
//
//                  if(pastStart && beforeEnd)
//                  {
//                      self._addSelection( $(this) );
//                  }
//
//                  if(self._getItemValue(this) == current)
//                  {
//                      beforeEnd = false;
//                  }
//
//              });
//          }
            else
            {
                if(canSelectMore){
                    self.lastSelectedItem = $(this);
                    self._clearSelections( $(this).parent() );
                    self._addSelection( $(this) );
                }
                else if(!isClickOnSourcePanel){
                    self.lastSelectedItem = $(this);
                    self._clearSelections( $(this).parent() );
                    self._addSelection( $(this) );
                }
            }

            self._refreshControls();
        },

        _isSelected: function(listItem)
        {
            return listItem.hasClass("ui-selected");
        },

        _addSelection: function(listItem)
        {
            var self = this;

            return listItem
                    .addClass("ui-selected")
                    .addClass("ui-state-highlight")
                    .addClass(self.options.selectedListItemClass);
        },

        _removeSelection: function(listItem)
        {
            var self = this;

            return listItem
                    .removeClass("ui-selected")
                    .removeClass("ui-state-highlight")
                    .removeClass(self.options.selectedListItemClass);
        },

        _removeSelections: function(listItems)
        {
            var self = this;

            listItems.each(function()
            {
                $(this)
                        .removeClass("ui-selected")
                        .removeClass("ui-state-highlight")
                        .removeClass(self.options.selectedListItemClass);
            });

            return listItems;
        },

        _clearSelections: function(list)
        {
            var self = this;

            list.children().each(function()
            {
                self._removeSelection( $(this) );
            });
        },

        _setOption: function(key, value)
        {
            switch(key)
            {
                case "clear":
                {
                    break;
                }
            }

            $.Widget.prototype._setOption.apply(this, arguments);
        },

        destroy: function()
        {
            var self = this;

            self._trigger("onDestroy");

            self.pickList.remove();
            self.element.show();

            $.Widget.prototype.destroy.call(self);
        },

        insert: function(item)
        {
            var self = this;

            var list = item.selected ? self.targetList : self.sourceList;
            var selectItem = self._createSelectItem(item);
            var listItem = self._createListItem(item);

            self.element.append(selectItem);
            list.append(listItem);

            self._trigger("onChange");

            self._refresh();
        },

        insertItems: function(items)
        {
            var self = this;

            var selectItems = [];
            var sourceItems = [];
            var targetItems = [];

            $(items).each(function()
            {
                var selectItem = self._createSelectItem(this);
                var listItem = self._createListItem(this);

                selectItems.push(selectItem);

                if(this.selected)
                {
                    targetItems.push(listItem);
                }
                else
                {
                    sourceItems.push(listItem);
                }
            });

            self.element.append(selectItems.join("\n"));
            self.sourceList.append(sourceItems.join("\n"));
            self.targetList.append(targetItems.join("\n"));

            self._trigger("onChange");

            self._refresh();
        },

        _createSelectItem: function(item)
        {
            var selected = item.selected ? " selected='selected'" : "";
            return "<option value='" + item.value + "'" + selected + ">" + item.label + "</option>";
        },

        _createListItem: function(item)
        {
            var self = this;

            if(item.element != undefined)
            {
                var richItemHtml = item.element.clone().wrap("<div>").parent().html();
                item.element.hide();
                return "<li " + self.options.listItemValueAttribute + "='" + item.value + "' label='" + item.label + "' class='" + self.options.listItemClass + " " + self.options.richListItemClass + "'>" + richItemHtml + "</li>";
            }

            return "<li " + self.options.listItemValueAttribute + "='" + item.value + "' label='" + item.label + "' class='" + self.options.listItemClass + "'>" + item.label + "</li>";
        },

        _createDoppelganger: function(item)
        {
            var self = this;
            return "<li " + self.options.listItemValueAttribute + "='" + $(item).val() + "' label='" + $(item).text() + "' class='" + self.options.listItemClass + "'>" + $(item).text() + "</li>";
        },

        _getItemValue: function(item)
        {
            var self = this;
            return $(item).attr(self.options.listItemValueAttribute);
        },

        selectedAssets:function(){
            var self = this;
            var selectedItems = [];
            self.targetList.children().each(function(){
                var a = {};
                a.id = $(this).attr(self.options.listItemValueAttribute);
                a.name = $(this).attr("label");
                selectedItems.push(a);
            });
            return selectedItems;
        }
    });
}(jQuery));

and now, to use it you just write this:

$("#pckAssets").pickList({
    selectLimit : 5
});

I hope will help someone.

Upvotes: 1

Four_lo
Four_lo

Reputation: 1150

jquery-ui-picklist has multiple event hooks that you could tie a function into, I assume that best choice is depending on the situation. afterAdd is most likely the best choice so you can disable the add button once the limit is reached. onChange counts the events in both directions, which if you are limiting a quantity you should count removals with afterRemove as well.

Here is the list.

https://code.google.com/p/jquery-ui-picklist/wiki/CallbackEvents#onChange

The button control is a regular HTML button control with a class of "addClass". You could switch or modify this class or preventDefault. I believe the Jquery plug in by default includes an "add all" button as well as "add". If your amount of possible additions is greater than your limit then this needs to be disabled in the beforePopulate eventhook. Just call the button with .hide(). and if the add button becomes eligble again show()

Upvotes: 1

Related Questions