1

I want to create a dynamic JQuery menu with React (ES6). I'm following the JQuery menu examples (https://jqueryui.com/menu/), but all examples in there are creating static menus. It doesn't explain how to change menu items such as updating labels or enablement. Also, I do not want to end up with a new implementation of a menu in React.

I have a json array for my menu items, which presumes nested levels.

let simple= [{
            disabled: true,
            label: "Item1"
        },
        {
            label: "Item2"
        },
        {
            label: "-"
        },
        {
            label: "Item3"
        },
        {
            label: "Item4",
            children: [
                {
                    label: "Sub Item1"
                },
                {
                    label: "Sub Item2"
                },
                {
                    label: "Sub Item3"
                }
            ]
        }];

I want updating the label or display of any of a json item to be done in the menu directly. I need to avoid manual DOM manipulation as it is an error-prone approach.

1 Answer 1

1

Dynamic manipulation in React is straightforward with the mutation capabilities. First, I think you already did this, you need to import the JQuery libraries required

import $ from 'jquery';
import 'jquery-ui/ui/core';
import 'jquery-ui/ui/widgets/menu';

And you need to reference your menu in a react component as below. The dynamic lifecycle will be handled as a react state (https://facebook.github.io/react/docs/state-and-lifecycle.html). We use a state, for instance, menuItems, to manage the JSON object/arrays. You can see that the position is set to absolute. Without this, your menu will cause your entire document to expand, unless this is the behaviour you want.

<ul ref="menu" style={{display: "none", position: "absolute"}}>
    {this.state.menuItems.map(this.processMenuEntry.bind(this, 0))}
</ul>

The implementation of the method processMenuEntry is below. Certainly you can update this method to match any different JSON structure. I would recommend a small change to use className: "ui-state-disabled" instead of disabled: true. Hence, you will have more options to customise your styles rather than enablement/disablement only. The method below assumes nested levels, which start with zero, as in the initial call in the snippet above. And it is important to make sure to assign a key to a child component.

processMenuEntry(level, entry, idx){
        let id= "menu"+ level+ "_"+ idx;

        if(!entry.className){
            entry.className= "";
        }

        let children= entry.children;
        let child;
        if(children && children.length){
            child= <li className= {entry.className} key= {id}><div>{entry.label}</div><ul>{children.map(this.processMenuEntry.bind(this, level+1))}</ul></li>;
        }else{
            child= <li className= {entry.className} key= {id}><div>{entry.label}</div></li>;
        }

        return child;
}

Make sure in your constructor to make this call, so your method will have the same context of your react component in HTML

this.processMenuEntry= this.processMenuEntry.bind(this);

Add this code snippet in your componentDidMount, in order to start the menu API working, in addition to your click handler.

componentDidMount(){
        $(this.refs.menu).menu({
            select: function( event, ui ) {
                //do 
            }
        });
}

Now, for instance, if you want to disable the second item in your menu, you can simply do that.

simple[1].className= "ui-state-disabled";
this.setState({"menuItems": simple});

You can as well set an entirely different JSON array, and React will take care of it

this.setState({"menuItems": [{
    label: "Some new Item1"
}]}); 

The use of "-" as a label will add a separator to your menu as the JQuery Menu API is designed.

Finally, in terms of handling the toggle of your menu, or hiding the menu when clicking outside it, you can look at the snippet below. Certainly, you can modify it based on your work. I am assuming below that we toggle the menu when clicking on an anchor. The check if the user clicked outside the menu is only added if the menu is visible.

<a href="https://stackoverflow.com" onClick={this.toggleMenu}>Test</a>

componentWillUnmount(){
    this.stopMenuListeners();
}

stopMenuListeners(){
    $(document).off("click.menuOutsideClicks");
}

toggleMenu(event){
        let menu= $(this.refs.menu);
        let visible= !menu.is(':visible');
        menu.slideToggle("fast");

        this.stopMenuListeners();
        if(visible){
            setTimeout(()=>{
                this._handler= $(document).on("click.menuOutsideClicks", (event)=>{
                    if(!$(event.target).closest(menu).length){
                        menu.slideUp('slow').hide();
                        this.stopMenuListeners();
                    }
                });
            });
        }

        event.stopPropagation();
        event.preventDefault();
}
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.