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

META-INF.resources.primefaces.forms.forms.cascadeselect.js Maven / Gradle / Ivy

There is a newer version: 14.0.7
Show newest version
/**
 * __PrimeFaces CascadeSelect Widget__
 *
 * CascadeSelect CascadeSelect displays a nested structure of options.
 *
 * @prop {JQuery} contents The DOM element for the content in the available selectable options.
 * @prop {PrimeFaces.UnbindCallback} [hideOverlayHandler] Unbind callback for the hide overlay handler.
 * @prop {JQuery} input The DOM element for the hidden input with the current value.
 * @prop {JQuery} items The DOM elements for the the available selectable options.
 * @prop {JQuery} itemsWrapper The DOM element for the wrapper with the container with the available selectable
 * options.
 * @prop {JQuery} label The DOM element for the label indicating the currently selected option.
 * @prop {JQuery} panel The DOM element for the overlay panel with the available selectable options.
 * @prop {PrimeFaces.CssTransitionHandler | null} [transition] Handler for CSS transitions used by this widget.
 * @prop {PrimeFaces.UnbindCallback} [resizeHandler] Unbind callback for the resize handler.
 * @prop {PrimeFaces.UnbindCallback} [scrollHandler] Unbind callback for the scroll handler.
 * @prop {JQuery} triggers The DOM elements for the buttons that can trigger (hide or show) the overlay panel with the
 * available selectable options.
 *
 * @interface {PrimeFaces.widget.CascadeSelectCfg} cfg The configuration for the {@link  CascadeSelect| CascadeSelect 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.DynamicOverlayWidgetCfg} cfg
 *
 * @prop {string} cfg.appendTo Appends the overlay to the element defined by search expression. Defaults to the document
 * body.
 * @prop {boolean} cfg.disabled If true, disables the component.
 */
PrimeFaces.widget.CascadeSelect = PrimeFaces.widget.DynamicOverlayWidget.extend({

    /**
     * @override
     * @inheritdoc
     * @param {PrimeFaces.PartialWidgetCfg} cfg
     */
    init: function(cfg) {
        this.panel = $(PrimeFaces.escapeClientId(cfg.id) + '_panel');
        this._super(cfg, this.panel, cfg.id + '_panel');

        this.input = $(this.jqId + '_input');
        this.label = this.jq.children('.ui-cascadeselect-label');
        this.triggers = this.jq.children('.ui-cascadeselect-trigger').add(this.label);
        this.itemsWrapper = this.panel.children('.ui-cascadeselect-items-wrapper');
        this.items = this.itemsWrapper.find('li.ui-cascadeselect-item');
        this.contents = this.items.children('.ui-cascadeselect-item-content');
        this.cfg.disabled = this.jq.hasClass('ui-state-disabled');

        if (!this.cfg.disabled) {
            this.bindEvents();
            this.transition = PrimeFaces.utils.registerCSSTransition(this.panel, 'ui-connected-overlay');
        }
    },

    /**
     * Sets up all event listeners that are required by this widget.
     * @private
     */
    bindEvents: function() {
        var $this = this;

        this.triggers.off('click.cascadeselect').on('click.cascadeselect', function(e) {
            if ($this.panel.is(':hidden')) {
                $this.show();
            }
            else {
                $this.hide();
            }

            $this.input.trigger('focus.cascadeselect');
            e.preventDefault();
        });

        this.input.off('focus.cascadeselect blur.cascadeselect keydown.cascadeselect')
            .on('focus.cascadeselect', function() {
                $this.jq.addClass('ui-state-focus');
            })
            .on('blur.cascadeselect', function(){
                $this.jq.removeClass('ui-state-focus');
            })
            .on('keydown.cascadeselect', function(e) {
                switch(e.key) {
                    case 'ArrowDown':
                        if ($this.panel.is(':visible')) {
                            $this.panel.find('.ui-cascadeselect-item:first > .ui-cascadeselect-item-content').focus();
                        }
                        else if (e.altKey) {
                            $this.show();
                        }
                        e.preventDefault();
                        break;

                    case 'Escape':
                        if ($this.panel.is(':visible')) {
                            $this.hide();
                            e.preventDefault();
                        }
                        break;

                    case 'Tab':
                        $this.hide();
                        break;

                    default:
                        break;
                }
            });

        this.contents.off('click.cascadeselect keydown.cascadeselect')
            .on('click.cascadeselect', function(e) {
                var item = $(this).parent();
                var subpanel = item.children('.ui-cascadeselect-panel');

                $this.deactivateItems(item);
                item.addClass('ui-cascadeselect-item-active ui-state-highlight');

                if (subpanel.length > 0) {
                    var parentPanel = item.closest('.ui-cascadeselect-panel');
                    $this.alignSubPanel(subpanel, parentPanel);
                    subpanel.show();
                }
                else {
                    $this.input.val(item.attr('data-value'));
                    $this.label.text(item.attr('data-label'));
                    $this.callBehavior('itemSelect');
                    $this.hide();
                    e.stopPropagation();
                }
            })
            .on('keydown.cascadeselect', function(e) {
                var item = $(this).parent();

                switch(e.key) {
                    case 'ArrowDown':
                        var nextItem = item.next();
                        if (nextItem) {
                            nextItem.children('.ui-cascadeselect-item-content').focus();
                        }
                        break;

                    case 'ArrowUp':
                        var prevItem = item.prev();
                        if (prevItem) {
                            prevItem.children('.ui-cascadeselect-item-content').focus();
                        }
                        break;

                    case 'ArrowRight':
                        if (item.hasClass('ui-cascadeselect-item-group')) {
                            if (item.hasClass('ui-cascadeselect-item-active')) {
                                item.find('> .ui-cascadeselect-panel > .ui-cascadeselect-item:first > .ui-cascadeselect-item-content').focus();
                            }
                            else {
                                item.children('.ui-cascadeselect-item-content').trigger('click.cascadeselect');
                            }
                        }
                        break;

                    case 'ArrowLeft':
                        $this.hideGroup(item);
                        $this.hideGroup(item.siblings('.ui-cascadeselect-item-active'));

                        var parentItem = item.parent().closest('.ui-cascadeselect-item');
                        if (parentItem) {
                            parentItem.children('.ui-cascadeselect-item-content').focus();
                        }
                        break;

                    case 'Enter':
                    case 'NumpadEnter':
                        item.children('.ui-cascadeselect-item-content').trigger('click.cascadeselect');
                        if (!item.hasClass('ui-cascadeselect-item-group')) {
                            $this.input.trigger('focus.cascadeselect');
                        }
                        break;

                    default:
                        break;
                }

                e.preventDefault();
            });
    },

    /**
     * Removes some event listeners when this widget was disabled.
     * @private
     */
    unbindEvents: function() {
        this.contents.off();
        this.triggers.off();
        this.input.off();
    },

    /**
     * Disables this widget so that the user cannot select any option.
     */
    disable: function() {
        if (!this.cfg.disabled) {
            this.cfg.disabled = true;
            this.jq.addClass('ui-state-disabled');
            this.input.attr('disabled', 'disabled');
            this.unbindEvents();
        }
    },

    /**
     * Enables this widget so that the user can select an option.
     */
    enable: function() {
        if (this.cfg.disabled) {
            this.cfg.disabled = false;
            this.jq.removeClass('ui-state-disabled');
            this.input.removeAttr('disabled');
            this.bindEvents();
        }
    },

    /**
     * Deactivate siblings and active children of an item.
     * @private
     * @param {JQuery} item Cascade select panel element.
     */
    deactivateItems: function(item) {
        var parentItem = item.parent().parent();
        var siblings = item.siblings('.ui-cascadeselect-item-active');

        this.hideGroup(siblings);
        this.hideGroup(siblings.find('.ui-cascadeselect-item-active'));

        if (!parentItem.is(this.itemsWrapper)) {
            this.deactivateItems(parentItem);
        }
    },

    /**
     * Sets up all panel event listeners
     * @private
     */
    bindPanelEvents: function() {
        var $this = this;

        this.hideOverlayHandler = PrimeFaces.utils.registerHideOverlayHandler(this, 'mousedown.' + this.id + '_hide', this.panel,
            function() { return $this.triggers; },
            function(e, eventTarget) {
                if(!($this.panel.is(eventTarget) || $this.panel.has(eventTarget).length > 0)) {
                    $this.hide();
                }
            });

        this.resizeHandler = PrimeFaces.utils.registerResizeHandler(this, 'resize.' + this.id + '_hide', this.panel, function() {
            $this.handleViewportChange();
        });

        // #12008 - Only register scroll handler if there is a small number of items
        if (this.items.length < 10) {
            this.scrollHandler = PrimeFaces.utils.registerConnectedOverlayScrollHandler(this, 'scroll.' + this.id + '_hide', this.jq, function() {
                $this.handleViewportChange();
            });
        }
    },

    /**
     * Fired when the browser viewport is resized or scrolled.  In Mobile environment we don't want to hider the overlay
     * we want to re-align it.  This is because on some mobile browser the popup may force the browser to trigger a
     * resize immediately and close the overlay. See GitHub #7075.
     * @private
     */
    handleViewportChange: function() {
        if (PrimeFaces.env.mobile || PrimeFaces.hideOverlaysOnViewportChange === false) {
            this.alignPanel();
        } else {
            this.hide();
        }
    },

    /**
     * Unbind all panel event listeners
     * @private
     */
    unbindPanelEvents: function() {
        if (this.hideOverlayHandler) {
            this.hideOverlayHandler.unbind();
        }

        if (this.resizeHandler) {
            this.resizeHandler.unbind();
        }

        if (this.scrollHandler) {
            this.scrollHandler.unbind();
        }
    },

    /**
     * Brings up the overlay panel with the available options.
     */
    show: function() {
        var $this = this;

        if (this.transition) {
            this.transition.show({
                onEnter: function() {
                    $this.panel.css('z-index', PrimeFaces.nextZindex());
                    $this.alignPanel();
                },
                onEntered: function() {
                    $this.input.attr('aria-expanded', true);
                    $this.bindPanelEvents();
                }
            });
        }
    },

    /**
     * Hides the panel of a group item.
     * @param {JQuery} item Dom element of the cascadeselect.
     */
    hideGroup: function(item) {
        item.removeClass('ui-cascadeselect-item-active ui-state-highlight').children('.ui-cascadeselect-panel').hide();
    },

    /**
     * Hides the overlay panel with the available options.
     */
    hide: function() {
        if (this.panel.is(':visible') && this.transition) {
            var $this = this;

            this.transition.hide({
                onExit: function() {
                    $this.unbindPanelEvents();
                },
                onExited: function() {
                    $this.panel.css('z-index', '');
                    $this.input.attr('aria-expanded', false);
                }
            });
        }
    },

    /**
     * Adjust the width of the overlay panel.
     * @private
     */
    alignPanelWidth: function() {
        //align panel and container
        if (this.cfg.appendTo) {
            this.panel.css('min-width', this.jq.outerWidth());
        }
    },

    /**
     * Align the overlay panel with the available options.
     * @private
     */
    alignPanel: function() {
        this.alignPanelWidth();

        if (this.panel.parent().is(this.jq)) {
            this.panel.css({
                left: '0px',
                top: this.jq.innerHeight() + 'px',
                'transform-origin': 'center top'
            });
        }
        else {
            this.panel.css({left:'0px', top:'0px', 'transform-origin': 'center top'}).position({
                my: 'left top'
                ,at: 'left bottom'
                ,of: this.jq
                ,collision: 'flipfit'
                ,using: function(pos, directions) {
                    $(this).css('transform-origin', 'center ' + directions.vertical).css(pos);
                }
            });
        }
    },

    /**
     * Align the sub overlay panel with the available options.
     * @private
     * @param {JQuery} subpanel Sub panel element of the cascade select panel.
     * @param {JQuery} parentPanel Parent panel element of the sub panel element.
     */
    alignSubPanel: function(subpanel, parentPanel) {
        var subitemWrapper = subpanel.children('.ui-cascadeselect-items-wrapper');
        subpanel.css({'display':'block', 'opacity':'0', 'pointer-events': 'none'});
        subitemWrapper.css({'overflow': 'scroll'});

        subpanel.css({left:'0px', top:'0px'}).position({
                my: 'left top'
                ,at: 'right top'
                ,of: parentPanel.children('.ui-cascadeselect-item-active:first') 
                ,collision: 'flipfit'
            });

        subpanel.css({'display':'none', 'opacity':'', 'pointer-events': '', 'z-index': PrimeFaces.nextZindex()});
        subitemWrapper.css({'overflow': ''});
    }
});




© 2015 - 2024 Weber Informatics LLC | Privacy Policy