自定义下拉焦点:调用堆栈大小超出

Custom dropdown focus: call stack size exceeded

本文关键字:堆栈 调用 焦点 自定义      更新时间:2023-09-26

我正在用jquery (slim)构建我自己的下拉插件。下拉元素本身是一个带有tabindex="0"的div。

我希望下拉菜单与浏览器的焦点状态一起工作:当元素聚焦时打开下拉菜单,当元素失去焦点时关闭下拉菜单。目前我得到以下错误:

jquery.slim.min.js:2 Uncaught RangeError:最大调用栈大小超过了

代码看起来像这样(为了可读性,删除了部分,标记了问题):

var plugin   = 'dropdown',
    defaults = {
        onOpened : function() {},
        onClosed : function() {}
    };
// Constructor
function Dropdown(element, options) {
    this.element  = element;
    this.settings = $.extend({}, defaults, options);
    this.init();
}
// Instance
$.extend(Dropdown.prototype, {
    init: function() {
        var instance = this,
            $element = $(instance.element);
        // Bind listeners
        $element.focus(function(e) {
            instance.open();
            e.preventDefault();
        }).focusout(function() {
            instance.close();
        }).mousedown(function() {
            instance.toggle();
        });
    },
    /**
     * Check the state of the dropdown.
     *
     * @param state
     * @returns {*}
     */
    is: function(state) {
        var $element = $(this.element);
        return {
            open: function() {
                return $element.hasClass('dropdown--open');
            },
            focused: function() {
                return document.activeElement === $element[0];
            }
        }[state].apply();
    },
    /**
     * Open the dropdown.
     */
    open: function() {
        var instance = this,
            $element = $(instance.element);
        if (instance.is('open')) {
            return;
        }
        $element.addClass('dropdown--open');
        this.callback(this.settings.onOpened, $element);
    },
    /**
     * Close the dropdown.
     */
    close: function() {
        var instance = this,
            $element = $(this.element);
        if ( ! instance.is('open')) {
            return;
        }
        $element.removeClass('dropdown--open');
        this.callback(this.settings.onClosed, $element);
    },
    /**
     * Make a callback.
     *
     * @param callback
     * @param $element
     */
    callback: function(callback, $element) {
        if (callback && typeof callback === 'function') {
            callback($element);
        }
    }
});

我知道我正在触发一个(无穷无尽的)递归函数,但我不确定如何解决这个问题。

感谢所有的帮助!

编辑:

固定的

;(function($, window, document) {
    'use strict';
    var plugin   = 'dropdown',
        defaults = {
            onOpened : function() {},
            onClosed : function() {}
        };
    // Constructor
    function Dropdown(element, options) {
        this.element  = element;
        this.settings = $.extend({}, defaults, options);
        this.init();
    }
    // Instance
    $.extend(Dropdown.prototype, {
        init: function() {
            var instance = this,
                $element = $(instance.element);
            // Bind listeners
            $element.focus(function(e) {
                console.log('opening');
                instance.open();
                e.preventDefault();
            }).focusout(function() {
                console.log('closing');
                instance.close();
            }).mousedown(function() {
                console.log('toggling');
                instance.toggle();
            });
        },
        /**
         * Check the state of the dropdown.
         *
         * @param state
         * @returns {*}
         */
        is: function(state) {
            var $element = $(this.element);
            return {
                open: function() {
                    return $element.hasClass('dropdown--open');
                },
                empty: function() {
                    return $element.hasClass('dropdown--empty');
                },
                focused: function() {
                    return document.activeElement === $element[0];
                }
            }[state].apply();
        },
        /**
         * Toggles the dropdown.
         */
        toggle: function() {
            if  (this.is('open')) this.close();
            else this.open();
        },
        /**
         * Open the dropdown.
         */
        open: function() {
            var instance = this,
                $element = $(instance.element);
            if (instance.is('open')) {
                return;
            }
            $element.addClass('dropdown--open');
            this.callback(this.settings.onOpened, $element);
        },
        /**
         * Close the dropdown.
         */
        close: function() {
            var instance = this,
                $element = $(this.element);
            if ( ! instance.is('open')) {
                return;
            }
            $element.removeClass('dropdown--open');
            this.callback(this.settings.onClosed, $element);
        },
        /**
         * Make a callback.
         *
         * @param callback
         * @param $element
         */
        callback: function(callback, $element) {
            if (callback && typeof callback === 'function') {
                callback($element);
            }
        }
    });
    // Plugin definition
    $.fn.dropdown = function(options, args) {
        return this.each(function() {
            if ( ! $ .data(this, plugin)) {
                $.data(this, plugin, new Dropdown(this, options));
            }
        });
    };
})(jQuery, window, document);
$('.dropdown').dropdown();
.dropdown {
    position: relative;
    display: block;
    padding: .625rem .8125rem;
    padding-right: 2rem;
    font-size: 16px;
    color: #333;
    line-height: 1.125;
    outline: 0;
    cursor: pointer;
    border: 1px solid #d9d9d9;
    background-color: #fff;
}
.dropdown.dropdown--open .dropdown__menu {
    display: block;
}
.dropdown__menu {
    display: none;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="dropdown" tabindex="0">
  <span>Favorite animal</span>
  <ul class="dropdown__menu" tabindex="-1">
    <li class="dropdown__item">Cats</li>
    <li class="dropdown__item">Dogs</li>
    <li class="dropdown__item">Monkeys</li>
    <li class="dropdown__item">Elephants</li>
  </ul>
</div>

所以。问题:

1)你触发一次又一次focus()focusout(),如果下拉菜单打开/关闭。(你已经这样做了)

2)使用toggle()函数关闭/打开下拉菜单

你的问题是你有点击事件检查是下拉打开,然后关闭。但是你必须在focusOut()中这样做。

我编辑了你的小提琴

        // Bind listeners
        $element.on('click', function(e) {
            instance.toggle();
        });

3) 更新你的评论

修改值