XF 2.0 Keep menu open while input is focused

Arty

Well-known member
I'm building JavaScript menu, similar to color picker menu (using color_picker.js as reference). There are two items that trigger it: span element (similar to color picker's color sample box) and input.

When clicking span, everything works correctly. Menu opens and stays open.

However when clicking input, menu doesn't show up. I do get onMenuOpen event, but looks like menu immediately closes. Probably its caused by input outside of menu getting focus.

Is it possible to prevent menu from closing when trigger is input and it is focused?
 
Solved.

1. Used parent item to initialise and to toggle menu instead of trigger box (that would be $target in color picker's example init)

2. Replaced click event for input with focus. In event listener added conditional check to toggle menu only if its not hidden. Also it adds 'menu--iconify-search--focused' class to this.menu.$menu (and event handler for clicking box removes that class), so menu would have different class list, making it easy to check what triggered menu.

3. Hijacked XF.autoFocusWithin. If $container has class 'menu--iconify-search--focused', do nothing, otherwise call old instance of that function. That prevents input from losing focus when menu is opened.

Full code of basic menu draft if it helps someone else with similar issue:
Code:
!function($, window, document, _undefined)
{
    "use strict";

    var oldAutoFocus = XF.autoFocusWithin;

    XF.autoFocusWithin = function($container) {
        if ($container.hasClass('menu--iconify-search--focused')) {
            return;
        }
        return oldAutoFocus.apply(this, arguments);
    };

    XF.IconifyInput = XF.Element.newHandler({
        options: {
            input: '| input[type="text"]',
            box: '| .js-iconifyTrigger',
            canClear: true,
            defaultValue: ''
        },

        $container: null,
        $input: null,
        $box: null,

        menuInited: false,
        menu: null,

        init: function() {
            var $target = this.$target;

            this.$container = $target;

            this.$input = XF.findRelativeIf(this.options.input, $target);
            this.$input.on('keyup paste', XF.proxy(this, 'updateFromInput'));
            this.$input.on('focus', XF.proxy(this, 'inputFocus'));

            this.$box = XF.findRelativeIf(this.options.box, $target);
            this.$box.click(XF.proxy(this, 'click'));

            this.updateFromInput();
        },

        updateFromInput: function()
        {
            var val = this.$input.val();

            // this.inputColor = XF.Color.fromString(val);
            this.updateBox();
        },

        updateBox: function()
        {
            console.log(Date.now(), 'updateBox()');
        },

        setupMenu: function()
        {
            console.log(Date.now(), 'setupMenu()');
            if (this.menuInited)
            {
                return;
            }
            this.menuInited = true;

            var $menu = this.getMenuEl();
            this.$target.after($menu);
            XF.activate($menu);

            this.menu = new XF.MenuClick(this.$container, {});
            this.menu.isPotentiallyFixed = true;
        },

        destroyMenu: function()
        {
            console.log(Date.now(), 'destroyMenu()');
            if (!this.menuInited)
            {
                return;
            }

            this.closeMenu();
            this.menuInited = false;
            this.menu = null;
        },

        openMenu: function()
        {
            console.log(Date.now(), 'openMenu()');
            if (this.$input.prop('disabled'))
            {
                return;
            }

            this.setupMenu();

            this.menu.open();
        },

        closeMenu: function()
        {
            console.log(Date.now(), 'closeMenu()');
            if (this.menu)
            {
                this.menu.close();
            }
        },

        getMenuEl: function()
        {
            var html = ''
                + '<div class="menu menu--iconify-search" data-menu="menu" aria-hidden="true"><div class="menu-content">'
                + 'Icon picker!<br />...'
                + '</div></div>';

            return $($.parseHTML(html));
        },

        onMenuOpen: function()
        {
            console.log(Date.now(), 'onMenuOpen()');
        },

        toggleMenu: function(e, toggle)
        {
            if (this.$input.prop('disabled'))
            {
                if (this.menu)
                {
                    this.closeMenu();
                }
                return;
            }

            this.setupMenu();

            if (!this.menu.isOpen())
            {
                this.onMenuOpen();
            }

            if (toggle || !this.menu.isOpen())
            {
                this.menu.$menu[toggle ? 'removeClass' : 'addClass']('menu--iconify-search--focused');
                this.menu.toggle(XF.isEventTouchTriggered(e));
            }
        },

        click: function(e)
        {
            this.toggleMenu(e, true);
        },

        inputFocus: function(e)
        {
            this.toggleMenu(e, false);
        }
    });

    XF.Element.register('iconify-option', 'XF.IconifyInput');

}(jQuery, window, document);
 
Top Bottom