All Downloads are FREE. Search and download functionalities are using the official Maven repository.

META-INF.resources.primefaces.picklist.picklist.js Maven / Gradle / Ivy

There is a newer version: 14.0.0
Show newest version
/**
 * __PrimeFaces PickList Widget__
 *
 * PickList is used for transferring data between two different collections.
 *
 * @typedef {"source" | "target"} PrimeFaces.widget.PickList.ListName The type for the two lists comprising the pick
 * list, i.e. whether a list contain the source or target items.
 *
 * @typedef {"command" | "dblclick" | "dragdrop"} PrimeFaces.widget.PickList.TransferType Indicates how an item was
 * transferred from one list to the other.
 * - `command`: The item was transferred as a result of the user clicking one of the command buttons next to the lists.
 * - `dblclick`: The item was transferred as a result of a double click by the user.
 * - `dragdrop`:  The item was transferred as a result of a drag&drop interaction by the user.
 * - `checkbox`:The item was transferred as a result of a checkbox click by the user.
 *
 * @typedef {"startsWith" |  "contains" |  "endsWith" | "custom"} PrimeFaces.widget.PickList.FilterMatchMode
 * Available modes for filtering the options of a pick list. When `custom` is set, a `filterFunction` must be specified.
 *
 * @typedef PrimeFaces.widget.PickList.FilterFunction A function for filtering the options of a pick list box.
 * @param {string} PrimeFaces.widget.PickList.FilterFunction.itemLabel The label of the currently selected text.
 * @param {string} PrimeFaces.widget.PickList.FilterFunction.filterValue The value to search for.
 * @return {boolean} PrimeFaces.widget.PickList.FilterFunction `true` if the item label matches the filter value, or
 * `false` otherwise.
 *
 * @typedef PrimeFaces.widget.PickList.OnTransferCallback Callback that is invoked when items are transferred from one
 * list to the other. See also {@link PickListCfg.onTransfer}.
 * @param {PrimeFaces.widget.PickList.TransferData} PrimeFaces.widget.PickList.OnTransferCallback.transferData Details
 * about the pick list item that was transferred.
 *
 * @interface {PrimeFaces.widget.PickList.TransferData} TransferData Callback that is invoked when an item was
 * transferred from one list to the other.
 * @prop {JQuery} TransferData.items Items that were transferred from one list to the other.
 * @prop {JQuery} TransferData.from List from which the items were transferred.
 * @prop {JQuery} TransferData.to List to which the items were transferred.
 * @prop {PrimeFaces.widget.PickList.TransferType} TransferData.type Type of the action that caused the items to be
 * transferred.
 *
 * @prop {JQuery} ariaRegion The DOM element for the aria region with the `aria-*` attributes
 * @prop {JQuery} checkboxes The DOM elements for the checkboxes next to each pick list item.
 * @prop {boolean} checkboxClick UI state indicating whether a checkbox was just clicked.
 * @prop {JQuery} cursorItem The currently selected item.
 * @prop {boolean} dragging Whether the user is currently transferring an item via drag&drop.
 * @prop {number} filterTimeout The set-timeout timer ID of the timer for the delay when filtering the source or target
 * list.
 * @prop {PrimeFaces.widget.PickList.FilterFunction} filterMatcher The filter that was selected and is currently used.
 * @prop {Record} filterMatchers
 * Map between the available filter types and the filter implementation.
 * @prop {JQuery} focusedItem The DOM element for the currently focused pick list item, if any.
 * @prop {JQuery} items The DOM elements for the pick list items in the source and target list.
 * @prop {PrimeFaces.widget.PickList.ListName} itemListName When sorting items: to which list the items belong.
 * @prop {JQuery} [sourceFilter] The DOM element for the filter input for the source list.
 * @prop {JQuery} sourceInput The DOM element for the hidden input storing the value of the source list.
 * @prop {JQuery} sourceList The DOM element for the source list.
 * @prop {JQuery} [targetFilter] The DOM element for the filter input for the target list.
 * @prop {JQuery} targetInput The DOM element for the hidden input storing the value of the target list.
 * @prop {JQuery} targetList The DOM element for the target list.
 *
 * @interface {PrimeFaces.widget.PickListCfg} cfg The configuration for the {@link  PickList| PickList widget}.
 * You can access this configuration via {@link PrimeFaces.widget.BaseWidget.cfg|BaseWidget.cfg}. Please note that this
 * configuration is usually meant to be read-only and should not be modified.
 * @extends {PrimeFaces.widget.BaseWidgetCfg} cfg
 *
 * @prop {boolean} cfg.disabled Whether this pick list is initially disabled.
 * @prop {string} cfg.effect Name of the animation to display.
 * @prop {string} cfg.effectSpeed Speed of the animation.
 * @prop {boolean} cfg.escapeValue Whether the item values are escaped for HTML.
 * @prop {number} cfg.filterDelay Delay to wait in milliseconds before sending each filter query. Default is `300`.
 * @prop {string} cfg.filterEvent Client side event to invoke picklist filtering for input fields. Default is `keyup`.
 * @prop {boolean} cfg.filterNormalize Defines if filtering would be done using normalized values.
 * @prop {PrimeFaces.widget.PickList.FilterFunction} cfg.filterFunction A custom filter function that is used when
 * `filterMatchMode` is set to `custom`.
 * @prop {PrimeFaces.widget.PickList.FilterMatchMode} cfg.filterMatchMode Mode of the filter. When set to `custom, a
 * `filterFunction` must be specified.
 * @prop {PrimeFaces.widget.PickList.OnTransferCallback} cfg.onTransfer Callback that is invoked when items are
 * transferred from one list to the other.
 * @prop {boolean} cfg.showCheckbox When true, a checkbox is displayed next to each item.
 * @prop {boolean} cfg.showSourceControls Specifies visibility of reorder buttons of source list.
 * @prop {boolean} cfg.showTargetControls Specifies visibility of reorder buttons of target list.
 * @prop {string} cfg.tabindex Position of the element in the tabbing order.
 */
PrimeFaces.widget.PickList = PrimeFaces.widget.BaseWidget.extend({

    /**
     * @override
     * @inheritdoc
     * @param {PrimeFaces.PartialWidgetCfg} cfg
     */
    init: function(cfg) {
        this._super(cfg);

        this.cfg.transferOnDblclick = this.cfg.transferOnDblclick !== false;
        this.cfg.transferOnCheckboxClick = this.cfg.transferOnCheckboxClick || false;

        this.sourceList = this.jq.find('ul.ui-picklist-source');
        this.targetList = this.jq.find('ul.ui-picklist-target');
        this.sourceInput = $(this.jqId + '_source');
        this.targetInput = $(this.jqId + '_target');
        this.items = this.jq.find('.ui-picklist-item:not(.ui-state-disabled)');
        if(this.cfg.showCheckbox) {
            this.checkboxes = this.items.find('div.ui-chkbox > div.ui-chkbox-box');
        }
        this.focusedItem = null;
        this.ariaRegion = $(this.jqId + '_ariaRegion');

        var sourceCaption = this.sourceList.prev('.ui-picklist-caption'),
            targetCaption = this.targetList.prev('.ui-picklist-caption');

        if(sourceCaption.length) {
            var captionText = sourceCaption.text();

            this.sourceList.attr('aria-label', captionText);
            this.sourceInput.attr('title', captionText);
        }

        if(targetCaption.length) {
            var captionText = targetCaption.text();

            this.targetList.attr('aria-label', captionText);
            this.targetInput.attr('title', captionText);
        }

        this.setTabIndex();

        //generate input options
        this.generateItems(this.sourceList, this.sourceInput);
        this.generateItems(this.targetList, this.targetInput);

        if(this.cfg.disabled) {
            $(this.jqId + ' li.ui-picklist-item').addClass('ui-state-disabled');
            $(this.jqId + ' button').attr('disabled', 'disabled').addClass('ui-state-disabled');
            $(this.jqId + ' .ui-picklist-filter-container').addClass('ui-state-disabled').children('input').attr('disabled', 'disabled');
        }
        else {
            var $this = this,
                reordered = true;

            //Sortable lists
            $(this.jqId + ' ul').sortable({
                cancel: '.ui-state-disabled,.ui-chkbox-box',
                connectWith: this.jqId + ' .ui-picklist-list',
                revert: 1,
                helper: 'clone',
                placeholder: "ui-picklist-item ui-state-highlight",
                forcePlaceholderSize: true,
                update: function(event, ui) {
                    $this.unselectItem(ui.item);

                    $this.saveState();
                    if(reordered) {
                        $this.fireReorderEvent();
                        reordered = false;
                    }
                },
                receive: function(event, ui) {
                    var parentList = ui.item.parents('ul.ui-picklist-list:first');
                    var item = ui.item;

                    if ($this.cfg.transferOnCheckboxClick) {
                        if (parentList.hasClass('ui-picklist-source')) {
                            $this.unselectCheckbox(item.find('div.ui-chkbox-box'));
                        }
                        else {
                            $this.selectCheckbox(item.find('div.ui-chkbox-box'));
                        }
                    }

                    $this.fireTransferEvent(item, ui.sender, parentList, 'dragdrop');
                },

                start: function(event, ui) {
                    $this.itemListName = $this.getListName(ui.item);
                    $this.dragging = true;
                },

                stop: function(event, ui) {
                    $this.dragging = false;
                },

                beforeStop:function(event, ui) {
                    if($this.itemListName !== $this.getListName(ui.item)) {
                        reordered = false;
                    }
                    else {
                        reordered = true;
                    }
                }
            });

            this.bindItemEvents();

            this.bindButtonEvents();

            this.bindFilterEvents();

            this.bindKeyEvents();

            this.updateButtonsState();

            this.updateListRole();
        }
    },

    /**
     * Sets up the event listeners for selecting and transferring pick list items.
     * @private
     */
    bindItemEvents: function() {
        var $this = this;

        this.items.on('mouseover.pickList', function(e) {
            $(this).addClass('ui-state-hover');
        })
        .on('mouseout.pickList', function(e) {
            $(this).removeClass('ui-state-hover');
        })
        .on('click.pickList', function(e) {
            //stop propagation
            if($this.checkboxClick||$this.dragging) {
                $this.checkboxClick = false;
                return;
            }

            var item = $(this),
            parentList = item.parent(),
            metaKey = (e.metaKey||e.ctrlKey);

            if(!e.shiftKey) {
                if(!metaKey) {
                    $this.unselectAll();
                }

                if(metaKey && item.hasClass('ui-state-highlight')) {
                    $this.unselectItem(item, true);
                }
                else {
                    $this.selectItem(item, true);
                    $this.cursorItem = item;
                }
            }
            else {
                $this.unselectAll();

                if($this.cursorItem && ($this.cursorItem.parent().is(item.parent()))) {
                    var currentItemIndex = item.index(),
                    cursorItemIndex = $this.cursorItem.index(),
                    startIndex = (currentItemIndex > cursorItemIndex) ? cursorItemIndex : currentItemIndex,
                    endIndex = (currentItemIndex > cursorItemIndex) ? (currentItemIndex + 1) : (cursorItemIndex + 1);

                    for(var i = startIndex ; i < endIndex; i++) {
                        var it = parentList.children('li.ui-picklist-item').eq(i);

                        if(it.is(':visible')) {
                            if(i === (endIndex - 1))
                                $this.selectItem(it, true);
                            else
                                $this.selectItem(it);
                        }
                    }
                }
                else {
                    $this.selectItem(item, true);
                    $this.cursorItem = item;
                }
            }

            /* For keyboard navigation */
            $this.removeOutline();
            $this.focusedItem = item;
            parentList.trigger('focus.pickList');
        });

        if (this.cfg.transferOnDblclick) {
            this.items.on('dblclick.pickList', function() {
                var item = $(this);

                if ($(this).parent().hasClass('ui-picklist-source')) {
                    $this.transfer(item, $this.sourceList, $this.targetList, 'dblclick');
                }
                else {
                    $this.transfer(item, $this.targetList, $this.sourceList, 'dblclick');
                }

                /* For keyboard navigation */
                $this.removeOutline();
                $this.focusedItem = null;

                PrimeFaces.clearSelection();
            });
        }

        if(this.cfg.showCheckbox) {
            this.checkboxes.on('mouseenter.pickList', function(e) {
                $(this).addClass('ui-state-hover');
            })
            .on('mouseleave.pickList', function(e) {
                $(this).removeClass('ui-state-hover');
            })
            .on('click.pickList', function(e) {
                $this.checkboxClick = true;

                var item = $(this).closest('li.ui-picklist-item');
                if ($this.cfg.transferOnCheckboxClick) {
                    if (item.parent().hasClass('ui-picklist-source')) {
                        $this.transfer(item, $this.sourceList, $this.targetList, 'checkbox', function() {
                            $this.unselectItem(item);
                        });
                    }
                    else {
                        $this.transfer(item, $this.targetList, $this.sourceList, 'checkbox', function() {
                            $this.unselectItem(item);
                        });
                    }
                }
                else {
                    if (item.hasClass('ui-state-highlight')) {
                        $this.unselectItem(item, true);
                    }
                    else {
                        $this.selectItem(item, true);
                    }
                    $this.focusedItem = item;
                }
            });
        }
    },

    /**
     * Sets up the keyboard event listeners for navigating the pick list via keyboard keys.
     * @private
     */
    bindKeyEvents: function() {
        var $this = this,
            listSelector = 'ul.ui-picklist-source, ul.ui-picklist-target';

        this.jq.off('focus.pickList blur.pickList keydown.pickList', listSelector).on('focus.pickList', listSelector, null, function(e) {
            var list = $(this),
                activeItem = $this.focusedItem||list.children('.ui-state-highlight:visible:first');
            if(activeItem.length) {
                $this.focusedItem = activeItem;
            }
            else {
                $this.focusedItem = list.children('.ui-picklist-item:visible:first');
            }

            setTimeout(function() {
                if ($this.focusedItem) {
                    PrimeFaces.scrollInView(list, $this.focusedItem);
                    $this.focusedItem.addClass('ui-picklist-outline');
                    $this.updateAriaRegion();
                }
            }, 100);
        })
        .on('blur.pickList', listSelector, null, function() {
            $this.removeOutline();
            $this.focusedItem = null;
        })
        .on('keydown.pickList', listSelector, null, function(e) {

            if(!$this.focusedItem) {
                return;
            }

            var list = $(this),
                key = e.key;

            switch(key) {
                case 'ArrowUp':
                    $this.removeOutline();

                    if(!$this.focusedItem.hasClass('ui-state-highlight')) {
                        $this.selectItem($this.focusedItem);
                    }
                    else {
                        var prevItem = $this.focusedItem.prevAll('.ui-picklist-item:visible:first');
                        if(prevItem.length) {
                            $this.unselectAll();
                            $this.selectItem(prevItem);
                            $this.focusedItem = prevItem;

                            PrimeFaces.scrollInView(list, $this.focusedItem);
                        }
                    }
                    $this.updateAriaRegion();
                    e.preventDefault();
                break;

                case 'ArrowDown':
                    $this.removeOutline();

                    if(!$this.focusedItem.hasClass('ui-state-highlight')) {
                        $this.selectItem($this.focusedItem);
                    }
                    else {
                        var nextItem = $this.focusedItem.nextAll('.ui-picklist-item:visible:first');
                        if(nextItem.length) {
                            $this.unselectAll();
                            $this.selectItem(nextItem);
                            $this.focusedItem = nextItem;

                            PrimeFaces.scrollInView(list, $this.focusedItem);
                        }
                    }
                    $this.updateAriaRegion();
                    e.preventDefault();
                break;

                case 'Enter':
                case ' ':
                    if($this.focusedItem && $this.focusedItem.hasClass('ui-state-highlight')) {
                        $this.focusedItem.trigger('dblclick.pickList');
                        $this.focusedItem = null;
                    }
                    e.preventDefault();
                break;
                default:
                    // #3304 find first item matching the character typed
                    if (PrimeFaces.utils.isPrintableKey(e)) {
                        var keyChar = key.toLowerCase();
                        list.children('.ui-picklist-item').each(function() {
                            var item = $(this),
                                itemLabel = item.attr('data-item-label');
                            if (itemLabel && itemLabel.toLowerCase().indexOf(keyChar) === 0) {
                                $this.removeOutline();
                                $this.unselectAll();
                                $this.selectItem(item);
                                $this.focusedItem = item;
                                PrimeFaces.scrollInView(list, $this.focusedItem);
                                $this.updateAriaRegion();
                                e.preventDefault();
                                return false;
                            }
                        });
                    }
            };
        });
    },

    /**
     * Removes the outline from the item that is currently focused.
     * @private
     */
    removeOutline: function() {
        if(this.focusedItem && this.focusedItem.hasClass('ui-picklist-outline')) {
            this.focusedItem.removeClass('ui-picklist-outline');
        }
    },

    /**
     * Select the given pick list item in the source or target list.
     * @param {JQuery} item A picklist item to select, with the class `ui-picklist-item`.
     * @param {boolean} [silent] `true` to imit triggering event listeners and behaviors, or `false` otherwise.
     */
    selectItem: function(item, silent) {
        item.addClass('ui-state-highlight');

        if(this.cfg.showCheckbox && !this.cfg.transferOnCheckboxClick) {
            this.selectCheckbox(item.find('div.ui-chkbox-box'));
        }

        if(silent) {
            this.fireItemSelectEvent(item);
        }

        this.updateButtonsState();
    },

    /**
     * Unselect the given pick list item in the source or target list.
     * @param {JQuery} item A picklist item to unselect, with the class `ui-picklist-item`.
     * @param {boolean} [silent] `true` to imit triggering event listeners and behaviors, or `false` otherwise.
     */
    unselectItem: function(item, silent) {
        item.removeClass('ui-state-hover');
        item.removeClass('ui-state-highlight');

        if(this.cfg.showCheckbox) {
            var chkbox = item.find('div.ui-chkbox-box');
            chkbox.removeClass('ui-state-hover');

            if (!this.cfg.transferOnCheckboxClick) {
                this.unselectCheckbox(item.find('div.ui-chkbox-box'));
            }
        }

        if(silent) {
            this.fireItemUnselectEvent(item);
        }

        this.updateButtonsState();
    },

    /**
     * Unselects all items in the source and target list.
     */
    unselectAll: function() {
        var selectedItems = this.items.filter('.ui-state-highlight');
        for(var i = 0; i < selectedItems.length; i++) {
            this.unselectItem(selectedItems.eq(i));
        }
    },

    /**
     * Selects the given checkbox that belongs to a pick list item.
     * @private
     * @param {JQuery} chkbox The hidden checkbox of a pick list item that was selected.
     */
    selectCheckbox: function(chkbox) {
        chkbox.addClass('ui-state-active').children('span.ui-chkbox-icon').removeClass('ui-icon-blank').addClass('ui-icon-check');
    },

    /**
     * Unselects the given checkbox that belongs to a pick list item.
     * @private
     * @param {JQuery} chkbox The hidden checkbox of a pick list item that was unselected.
     */
    unselectCheckbox: function(chkbox) {
        chkbox.removeClass('ui-state-active').children('span.ui-chkbox-icon').addClass('ui-icon-blank').removeClass('ui-icon-check');
    },

    /**
     * Stores the current items in the given list in a hidden form field. Used for submitting the current value of this
     * pick list.
     * @private
     * @param {JQuery} list The source or target list with items to store.
     * @param {JQuery} input The hidden form field where the items are stored.
     */
    generateItems: function(list, input) {
        var $this = this;
        list.children('.ui-picklist-item').each(function() {
            var item = $(this),
            itemValue = item.attr('data-item-value'),
            itemLabel = item.attr('data-item-label') ? PrimeFaces.escapeHTML(item.attr('data-item-label')) : '',
            option = $('');

            if ($this.cfg.escapeValue) {
               itemValue = PrimeFaces.escapeHTML(itemValue);
            }
            option.prop('value', itemValue).text(itemLabel);
            input.append(option);
        });
    },

    /**
     * Sets tup the event listeners for when the command buttons (move up, move down etc.) are pressed.
     * @private
     */
    bindButtonEvents: function() {
        var $this = this;

        //visuals
        PrimeFaces.skinButton(this.jq.find('.ui-button'));

        //events
        $(this.jqId + ' .ui-picklist-button-add').on("click", function() {
            $this.add();
        });
        $(this.jqId + ' .ui-picklist-button-add-all').on("click", function() {
            $this.addAll();
        });
        $(this.jqId + ' .ui-picklist-button-remove').on("click", function() {
            $this.remove();
        });
        $(this.jqId + ' .ui-picklist-button-remove-all').on("click", function() {
            $this.removeAll();
        });

        if(this.cfg.showSourceControls) {
            $(this.jqId + ' .ui-picklist-source-controls .ui-picklist-button-move-up').on("click", function() {
                $this.moveUp($this.sourceList);
            });
            $(this.jqId + ' .ui-picklist-source-controls .ui-picklist-button-move-top').on("click", function() {
                $this.moveTop($this.sourceList);
            });
            $(this.jqId + ' .ui-picklist-source-controls .ui-picklist-button-move-down').on("click", function() {
                $this.moveDown($this.sourceList);
            });
            $(this.jqId + ' .ui-picklist-source-controls .ui-picklist-button-move-bottom').on("click", function() {
                $this.moveBottom($this.sourceList);
            });
        }

        if(this.cfg.showTargetControls) {
            $(this.jqId + ' .ui-picklist-target-controls .ui-picklist-button-move-up').on("click", function() {
                $this.moveUp($this.targetList);
            });
            $(this.jqId + ' .ui-picklist-target-controls .ui-picklist-button-move-top').on("click", function() {
                $this.moveTop($this.targetList);
            });
            $(this.jqId + ' .ui-picklist-target-controls .ui-picklist-button-move-down').on("click", function() {
                $this.moveDown($this.targetList);
            });
            $(this.jqId + ' .ui-picklist-target-controls .ui-picklist-button-move-bottom').on("click", function() {
                $this.moveBottom($this.targetList);
            });
        }
    },

    /**
     * Sets up all event listeners for filtering the source and target lists.
     * @private
     */
    bindFilterEvents: function() {
        this.cfg.filterEvent = this.cfg.filterEvent||'keyup';
        this.cfg.filterDelay = this.cfg.filterDelay||300;
        this.setupFilterMatcher();

        this.sourceFilter = $(this.jqId + '_source_filter');
        this.targetFilter = $(this.jqId + '_target_filter');

        PrimeFaces.skinInput(this.sourceFilter);
        this.bindTextFilter(this.sourceFilter);

        PrimeFaces.skinInput(this.targetFilter);
        this.bindTextFilter(this.targetFilter);
    },

    /**
     * Sets up the event listeners for when text is entered into the filter input of the source or target list.
     * @private
     * @param {JQuery} filter The filter input of the source or target list.
     */
    bindTextFilter: function(filter) {
        if(this.cfg.filterEvent === 'enter') {
            this.bindEnterKeyFilter(filter);
        }
        else {
            this.bindFilterEvent(filter);
        }
    },

    /**
     * Sets up the event listeners for when the enter key is pressed while inside a filter input of the source or target
     * list.
     * @private
     * @param {JQuery} filter The filter input of the source or target list.
     */
    bindEnterKeyFilter: function(filter) {
        var $this = this;

        filter
            .on('keydown', PrimeFaces.utils.blockEnterKey)
            .on('keyup', function(e) {
                if(e.key === 'Enter') {
                    $this.filter(this.value, $this.getFilteredList($(this)));

                    e.preventDefault();
                }
            });
    },

    /**
     * Sets up the event listeners for filtering the source and target lists.
     * @private
     * @param {JQuery} filter The filter input of the source or target list.
     */
    bindFilterEvent: function(filter) {
        var $this = this;

        //prevent form submit on enter key
        filter.on(this.cfg.filterEvent, function(e) {
            if (PrimeFaces.utils.ignoreFilterKey(e)) {
                return;
            }

            var input = $(this);

            if($this.filterTimeout) {
                clearTimeout($this.filterTimeout);
            }

            $this.filterTimeout = setTimeout(function() {
                $this.filter(input.val(), $this.getFilteredList(input));
                $this.filterTimeout = null;
            },
            $this.cfg.filterDelay);
        })
        .on('keydown', PrimeFaces.utils.blockEnterKey);
    },

    /**
     * Finds and stores the filter function which is to be used for filtering the options of this pick list.
     * @private
     */
    setupFilterMatcher: function() {
        this.cfg.filterMatchMode = this.cfg.filterMatchMode||'startsWith';
        this.filterMatchers = {
            'startsWith': this.startsWithFilter
            ,'contains': this.containsFilter
            ,'endsWith': this.endsWithFilter
            ,'custom': this.cfg.filterFunction
        };

        this.filterMatcher = this.filterMatchers[this.cfg.filterMatchMode];
    },

    /**
     * Filters the available options in the source or target list.
     * @param {string} value A value against which the available options are matched.
     * @param {JQuery} list The source or target list that is to be filtered.
     * @param {boolean} [animate] If it should be animated.
     */
    filter: function(value, list, animate) {
        var normalize = this.cfg.filterNormalize,
            filterValue = PrimeFaces.toSearchable(PrimeFaces.trim(value), true, normalize),
            items = list.children('li.ui-picklist-item'),
            animated = animate || this.isAnimated();

        list.removeAttr('role');

        if(filterValue === '') {
            items.filter(':hidden').show();
            list.attr('role', 'menu');
        }
        else {
            for(var i = 0; i < items.length; i++) {
                var item = items.eq(i),
                itemLabel = PrimeFaces.toSearchable(item.attr('data-item-label'), false, normalize),
                matches = this.filterMatcher(itemLabel, filterValue);

                if(matches) {
                    var hasRole = list[0].hasAttribute('role');
                    if(animated) {
                        item.fadeIn('fast', function() {
                            if(!hasRole) {
                                list.attr('role', 'menu');
                            }
                        });
                    }
                    else {
                        item.show();
                        if(!hasRole) {
                            list.attr('role', 'menu');
                        }
                    }
                }
                else {
                    if(animated) {
                        item.fadeOut('fast');
                    }
                    else {
                        item.hide();
                    }
                }
            }
        }

    },

    /**
     * Implementation of a `PrimeFaces.widget.PickList.FilterFunction` that matches the given option when it starts with
     * the given search text.
     * @param {string} value Text of an option.
     * @param {string} filter Value of the filter.
     * @return {boolean} `true` when the text of the options starts with the filter value, or `false` otherwise.
     */
    startsWithFilter: function(value, filter) {
        return value.toLowerCase().indexOf(filter) === 0;
    },

    /**
     * Implementation of a `PrimeFaces.widget.PickList.FilterFunction` that matches the given option when it contains
     * the given search text.
     * @param {string} value Text of an option.
     * @param {string} filter Value of the filter.
     * @return {boolean} `true` when the text of the contains the filter value, or `false` otherwise.
     */
    containsFilter: function(value, filter) {
        return value.toLowerCase().indexOf(filter) !== -1;
    },

    /**
     * Implementation of a `PrimeFaces.widget.PickList.FilterFunction` that matches the given option when it ends with
     * the given search text.
     * @param {string} value Text of an option.
     * @param {string} filter Value of the filter.
     * @return {boolean} `true` when the text of the options ends with the filter value, or `false` otherwise.
     */
    endsWithFilter: function(value, filter) {
        return value.indexOf(filter, value.length - filter.length) !== -1;
    },

    /**
     * Finds the list belonging to the given filter input.
     * @private
     * @param {JQuery} filter The filter input of either the target or source list.
     * @return {JQuery} The list to which the given filter input applies.
     */
    getFilteredList: function(filter) {
        return filter.hasClass('ui-source-filter-input') ? this.sourceList : this.targetList;
    },

    /**
     * Adds all selected items in the source list by transferring them to the target list.
     */
    add: function() {
        var items = this.sourceList.children('li.ui-picklist-item.ui-state-highlight');

        this.transfer(items, this.sourceList, this.targetList, 'command');
    },

    /**
     * Adds all items to the target list by transferring all items from the source list to the target list.
     */
    addAll: function() {
        var items = this.sourceList.children('li.ui-picklist-item:visible:not(.ui-state-disabled)');

        this.transfer(items, this.sourceList, this.targetList, 'command');
    },

    /**
     * Removes all selected items in the target list by transferring them to the source list.
     */
    remove: function() {
        var items = this.targetList.children('li.ui-picklist-item.ui-state-highlight');

        this.transfer(items, this.targetList, this.sourceList, 'command');
    },

    /**
     * Removes all items in the target list by transferring all items from the target list to the source list.
     */
    removeAll: function() {
        var items = this.targetList.children('li.ui-picklist-item:visible:not(.ui-state-disabled)');

        this.transfer(items, this.targetList, this.sourceList, 'command');
    },

    /**
     * Moves the items that are currently selected up by one.
     * @param {JQuery} list The source or target list with items to move up.
     */
    moveUp: function(list) {
        var $this = this,
        animated = $this.isAnimated(),
        items = list.children('.ui-state-highlight'),
        itemsCount = items.length,
        movedCount = 0;

        if(itemsCount) {
            items.each(function() {
                var item = $(this);

                if(!item.is(':first-child')) {

                    if(animated) {
                        item.hide($this.cfg.effect, {}, $this.cfg.effectSpeed, function() {
                            item.insertBefore(item.prev()).show($this.cfg.effect, {}, $this.cfg.effectSpeed, function() {
                                movedCount++;

                                if(movedCount === itemsCount) {
                                    $this.saveState();
                                    $this.fireReorderEvent();
                                }
                            });
                        });
                    }
                    else {
                        item.hide().insertBefore(item.prev()).show();
                    }

                }
            });

            if(!animated) {
                this.saveState();
                this.fireReorderEvent();
            }
        }

    },

    /**
     * Moves the items that are currently selected to the top of the source of target list.
     * @param {JQuery} list The source or target list with items to move to the top.
     */
    moveTop: function(list) {
        var $this = this,
        animated = $this.isAnimated(),
        items = list.children('.ui-state-highlight'),
        itemsCount = items.length,
        movedCount = 0;

        if(itemsCount) {
            items.each(function() {
                var item = $(this);

                if(!item.is(':first-child')) {

                    if(animated) {
                        item.hide($this.cfg.effect, {}, $this.cfg.effectSpeed, function() {
                            item.prependTo(item.parent()).show($this.cfg.effect, {}, $this.cfg.effectSpeed, function(){
                                movedCount++;

                                if(movedCount === itemsCount) {
                                    $this.saveState();
                                    $this.fireReorderEvent();
                                }
                            });
                        });
                    }
                    else {
                        item.hide().prependTo(item.parent()).show();
                    }
                }
            });

            if(!animated) {
                this.saveState();
                this.fireReorderEvent();
            }
        }
    },

    /**
     * Moves the items that are currently selected down by one.
     * @param {JQuery} list The source or target list with items to move down.
     */
    moveDown: function(list) {
        var $this = this,
        animated = $this.isAnimated(),
        items = list.children('.ui-state-highlight'),
        itemsCount = items.length,
        movedCount = 0;

        if(itemsCount) {
            $(items.get().reverse()).each(function() {
                var item = $(this);

                if(!item.is(':last-child')) {
                    if(animated) {
                        item.hide($this.cfg.effect, {}, $this.cfg.effectSpeed, function() {
                            item.insertAfter(item.next()).show($this.cfg.effect, {}, $this.cfg.effectSpeed, function() {
                                movedCount++;

                                if(movedCount === itemsCount) {
                                    $this.saveState();
                                    $this.fireReorderEvent();
                                }
                            });
                        });
                    }
                    else {
                        item.hide().insertAfter(item.next()).show();
                    }
                }

            });

            if(!animated) {
                this.saveState();
                this.fireReorderEvent();
            }
        }
    },

    /**
     * Moves the items that are currently selected to the bottom of the source of target list.
     * @param {JQuery} list The source or target list with items to move to the bottom.
     */
    moveBottom: function(list) {
        var $this = this,
        animated = $this.isAnimated(),
        items = list.children('.ui-state-highlight'),
        itemsCount = items.length,
        movedCount = 0;

        if(itemsCount) {
            items.each(function() {
                var item = $(this);

                if(!item.is(':last-child')) {

                    if(animated) {
                        item.hide($this.cfg.effect, {}, $this.cfg.effectSpeed, function() {
                            item.appendTo(item.parent()).show($this.cfg.effect, {}, $this.cfg.effectSpeed, function() {
                                movedCount++;

                                if(movedCount === itemsCount) {
                                    $this.saveState();
                                    $this.fireReorderEvent();
                                }
                            });
                        });
                    }
                    else {
                        item.hide().appendTo(item.parent()).show();
                    }
                }

            });

            if(!animated) {
                this.saveState();
                this.fireReorderEvent();
            }
        }
    },

    /**
     * Saves the current state of this widget, i.e. to which list the items are currently assigned. Clears inputs and
     * repopulates them from the list states.
     * @private
     */
    saveState: function() {
        this.sourceInput.children().remove();
        this.targetInput.children().remove();

        this.generateItems(this.sourceList, this.sourceInput);
        this.generateItems(this.targetList, this.targetInput);
        this.cursorItem = null;
    },

    /**
     * Transfers the given items from the source or target list to the other list.
     * @param {JQuery} items Items that were transferred from one list to the other.
     * @param {JQuery} from List from which the items were transferred.
     * @param {JQuery} to List to which the items were transferred.
     * @param {PrimeFaces.widget.PickList.TransferType} type Type of the action that caused the items to be transferred.
     * @param {() => JQuery} callback after transfer finished.
     */
    transfer: function(items, from, to, type, callback) {
        $(this.jqId + ' ul').sortable('disable');
        var $this = this;
        var itemsCount = items.length;
        var transferCount = 0;

        if(this.isAnimated()) {
            items.hide(this.cfg.effect, {}, this.cfg.effectSpeed, function() {
                var item = $(this);
                $this.unselectItem(item);

                if ($this.cfg.transferOnCheckboxClick) {
                    if (from.hasClass('ui-picklist-source')) {
                        $this.selectCheckbox(item.find('div.ui-chkbox-box'));
                    }
                    else {
                        $this.unselectCheckbox(item.find('div.ui-chkbox-box'));
                    }
                }

                item.appendTo(to).show($this.cfg.effect, {}, $this.cfg.effectSpeed, function() {
                    transferCount++;

                    //fire transfer when all items are transferred
                    if(transferCount == itemsCount) {
                        $this.saveState();
                        $this.fireTransferEvent(items, from, to, type);
                    }
                });

                $this.updateListRole();

                if (callback) {
                    callback.call($this);
                }
            });
        }
        else {
            items.hide();

            if(this.cfg.showCheckbox) {
                items.each(function() {
                    var item = $(this);
                    $this.unselectItem(item);

                    if ($this.cfg.transferOnCheckboxClick) {
                        if (from.hasClass('ui-picklist-source')) {
                            $this.selectCheckbox(item.find('div.ui-chkbox-box'));
                        }
                        else {
                            $this.unselectCheckbox(item.find('div.ui-chkbox-box'));
                        }
                    }
                });
            }

            items.appendTo(to).show();

            this.saveState();
            this.fireTransferEvent(items, from, to, type);
            this.updateListRole();

            if (callback) {
                callback.call($this);
            }
        }
    },

    /**
     * Triggers the behavior for when pick list items are transferred from the source to the target list or vice-versa.
     * @private
     * @param {JQuery} items Items that were transferred from one list to the other.
     * @param {JQuery} from List from which the items were transferred.
     * @param {JQuery} to List to which the items were transferred.
     * @param {PrimeFaces.widget.PickList.TransferType} type Type of the action that caused the items to be transferred.
     */
    fireTransferEvent: function(items, from, to, type) {
        var $this = this;

        if(this.cfg.onTransfer) {
            var obj = {};
            obj.items = items;
            obj.from = from;
            obj.to = to;
            obj.type = type;

            this.cfg.onTransfer.call(this, obj);
        }

        if(this.hasBehavior('transfer')) {
            var isAdd = from.hasClass('ui-picklist-source');

            var options = {
                params: [
                    {name: $this.id + '_add', value: isAdd}
                ],
                oncomplete: function() {
                    $this.refilterSource();
                    $this.refilterTarget();
                    $($this.jqId + ' ul').sortable('enable');
                    $this.updateButtonsState();
                }
            };

            items.each(function(index, item) {
                options.params.push({name: $this.id + '_transferred', value: $(item).attr('data-item-value')});
            });

            this.callBehavior('transfer', options);
        }
        else {
            $($this.jqId + ' ul').sortable('enable');
            $this.updateButtonsState();
        }
    },

    /**
     * Finds the type of the given list, i.e. whether the list represents the source or target list.
     * @private
     * @param {JQuery} element A list element to check.
     * @return {PrimeFaces.widget.PickList.ListName} Whether the element represents the source or target list.
     */
    getListName: function(element){
        return element.parent().hasClass("ui-picklist-source") ? "source" : "target";
    },

    /**
     * Triggers the behavior for when pick list items are selected.
     * @private
     * @param {JQuery} item A pick list item that was selected.
     */
    fireItemSelectEvent: function(item) {
        if(this.hasBehavior('select')) {
            var listName = this.getListName(item),
            inputContainer = (listName === "source") ? this.sourceInput : this.targetInput,
            ext = {
                params: [
                    {name: this.id + '_itemIndex', value: item.index()},
                    {name: this.id + '_listName', value: listName}
                ],
                onstart: function() {
                    if(!inputContainer.children().length) {
                        return false;
                    }
                }
            };

            this.callBehavior('select', ext);
        }
    },

    /**
     * Triggers the behavior for when pick list items are unselected.
     * @private
     * @param {JQuery} item A pick list item that was unselected.
     */
    fireItemUnselectEvent: function(item) {
        if(this.hasBehavior('unselect')) {
            var ext = {
                params: [
                    {name: this.id + '_itemIndex', value: item.index()},
                    {name: this.id + '_listName', value: this.getListName(item)}
                ]
            };

            this.callBehavior('unselect', ext);
        }
    },

    /**
     * Triggers the behavior for when pick list items are reordered.
     * @private
     */
    fireReorderEvent: function() {
        this.callBehavior('reorder');
    },

    /**
     * Checks whether UI actions of this pick list are animated.
     * @return {boolean} `true` if this pick list is animated, or `false` otherwise.
     */
    isAnimated: function() {
        return (this.cfg.effect && this.cfg.effect != 'none');
    },

    /**
     * Applies the tab index to this pick list widget.
     * @private
     */
    setTabIndex: function() {
        var tabindex = (this.cfg.disabled) ? '-1' : this.getTabIndex();
        this.sourceList.attr('tabindex', tabindex);
        this.targetList.attr('tabindex', tabindex);
        $(this.jqId + ' button').attr('tabindex', tabindex);
        $(this.jqId + ' .ui-picklist-filter-container > input').attr('tabindex', tabindex);
    },

    /**
     * Finds the tab index of this pick list widget.
     * @private
     * @return {string} The tab index of this pick list.
     */
    getTabIndex: function() {
        return this.cfg.tabindex||'0';
    },

    /**
     * Updates the state of all buttons of this pick list, such as whether they are disabled or enabled.
     * @private
     */
    updateButtonsState: function () {
        var addButton = $(this.jqId + ' .ui-picklist-button-add');
        var sourceListButtons = $(this.jqId + ' .ui-picklist-source-controls .ui-button');
        if (this.sourceList.find('li.ui-state-highlight').length) {
            this.enableButton(addButton);
            this.enableButton(sourceListButtons);
        }
        else {
            this.disableButton(addButton);
            this.disableButton(sourceListButtons);
        }

        var removeButton = $(this.jqId + ' .ui-picklist-button-remove');
        var targetListButtons = $(this.jqId + ' .ui-picklist-target-controls .ui-button');
        if (this.targetList.find('li.ui-state-highlight').length) {
            this.enableButton(removeButton);
            this.enableButton(targetListButtons);
        }
        else {
            this.disableButton(removeButton);
            this.disableButton(targetListButtons);
        }

        var addAllButton = $(this.jqId + ' .ui-picklist-button-add-all');
        if (this.sourceList.find('li.ui-picklist-item:not(.ui-state-disabled)').length) {
            this.enableButton(addAllButton);
            this.sourceList.attr('tabindex', this.getTabIndex());
        }
        else {
            this.disableButton(addAllButton);
            this.sourceList.attr('tabindex', '-1');
        }

        var removeAllButton = $(this.jqId + ' .ui-picklist-button-remove-all');
        if (this.targetList.find('li.ui-picklist-item:not(.ui-state-disabled)').length) {
            this.enableButton(removeAllButton);
            this.targetList.attr('tabindex', this.getTabIndex());
        }
        else {
            this.disableButton(removeAllButton);
            this.targetList.attr('tabindex', '-1');
        }
    },

    /**
     * Reapply filtering the current source list.
     * @private
     */
    refilterSource: function() {
        this.filter(this.sourceFilter.val(), this.sourceList, false);
    },

    /**
     * Reapply filtering to the current target list.
     * @private
     */
    refilterTarget: function() {
        this.filter(this.targetFilter.val(), this.targetList, false);
    },

    /**
     * Disables the given button belonging to this pick list.
     * @private
     * @param {JQuery} button A button to disable.
     */
    disableButton: function (button) {
        if (button.hasClass('ui-state-focus')) {
            button.trigger("blur");
        }

        button.attr('disabled', 'disabled').addClass('ui-state-disabled');
        button.attr('tabindex', '-1');
    },

    /**
     * Enables the given button belonging to this pick list.
     * @private
     * @param {JQuery} button A button to enable.
     */
    enableButton: function (button) {
        button.prop('disabled', false).removeClass('ui-state-disabled');
        button.attr('tabindex', this.getTabIndex());
    },

    /**
     * Updates the `role` attribute of the source and target pick list items.
     * @private
     */
    updateListRole: function() {
        this.sourceList.children('li:visible').length > 0 ? this.sourceList.attr('role', 'menu') : this.sourceList.removeAttr('role');
        this.targetList.children('li:visible').length > 0 ? this.targetList.attr('role', 'menu') : this.targetList.removeAttr('role');
    },
    
    /**
     * Updates the `aria-grion` with the focused label text.
     * @private
     */
    updateAriaRegion: function() {
        var labelText = this.focusedItem.data('item-label');
        this.ariaRegion.attr('aria-label', labelText)
        this.ariaRegion.text(labelText);
    }

});




© 2015 - 2024 Weber Informatics LLC | Privacy Policy