36

I'd like to know if there is a best practice/correct way to setup a right-click menu for a React component.

I currently have this...

// nw is nw.gui from Node-Webkit
componentWillMount: function() {
    var menu = new nw.Menu();
    menu .append(new nw.MenuItem({
        label: 'doSomething',
        click: function() {
            // doSomething
        }
    }));

    // I'd like to know if this bit can be done in a cleaner/more succinct way...
    // BEGIN
    var that = this;
    addEventListener('contextmenu', function(e){
        e.preventDefault();
        // Use the attributes property to uniquely identify this react component 
        // (so different elements can have different right click menus)
        if (e.target.attributes[0].nodeValue == that.getDOMNode().attributes[0].nodeValue) {
            menu.popup(e.x, e.y);
        }
    })
    // END
},

This works, but it feels a little messy and I was wondering if there was another approach I might be able to use, any information would be greatly appreciated,

Thanks!

Tom
  • 1,956
  • 2
  • 18
  • 23
  • 1
    Take a look on this [article](http://yahooeng.tumblr.com/post/110069372255/menus-dialogs-and-tooltips-oh-my-in-any-web), I think it will help you. – pablolmiranda Feb 21 '15 at 19:53
  • @pablolmiranda Ah cool thanks, hadn't seen this article before. I had found this video (https://www.youtube.com/watch?v=ecc0JopiZe4) which has info about node-webkit, but has nothing React related. I just didn't know if there was a better approach. I guess I could use a unique id and a div to refer to this item which might be slightly cleaner, I'm not sure. Thanks though! – Tom Feb 21 '15 at 22:42

3 Answers3

54

UPDATE:

Figured it out - here is what you can do

var addMenu;

componentWillMount: function() {
    addMenu = new nw.Menu();
    addMenu.append(new nw.MenuItem({
        label: 'doSomething',
        click: function() {
            // doSomething
        }
    }));
},

contextMenu: function(e) {
    e.preventDefault();
    addMenu.popup(e.clientX, e.clientY);
},

render: function(){
    return <button onClick={this.handleClick} onContextMenu={this.contextMenu}>SomethingUseful</button>
}

In render you can pass a function to onContextMenu for when a right click occurs for this react component.

Tom
  • 1,956
  • 2
  • 18
  • 23
  • 2
    Could you please add JSFiddle example ? – Peter Jul 10 '15 at 13:23
  • 2
    Hey there - Here is something that might be useful - (https://jsfiddle.net/q09xkja9/3/) The right click menu I'm using (addMenu = new nw.Menu()) is from Node-Webkit which provides access to native menus. Hopefully the example will give you an idea though! – Tom Jul 13 '15 at 12:46
  • 1
    @Tom Is this JSFiddle supposed to work? All I see is a white area where the output/click events should be occurring. – kojow7 Feb 19 '18 at 05:08
  • @kojow7 I'm afraid it's been forever since I looked at this and I have no idea if what I tried back in 2015 still works Sorry not to be more help! – Tom Feb 25 '18 at 20:15
  • 1
    @kojow7 Here's an updated fiddle: https://jsfiddle.net/ejtkn8x1/ (had to switch the "language" setting from "with jQuery" to "Babel with JSX") – 1j01 Mar 21 '18 at 04:00
16

There are few things to take care about with popup menus:

  1. it should be rendered away from its parent and siblings - preferably in an overlay which is the last child of document.body
  2. special logic should take care that it's always displayed on screen and never cropped by screen edges
  3. if there is a hierarchy involved, child popups should be aligned to items from previous popup (opener).

I created a library which you can use to accomplish all of these:

http://dkozar.github.io/react-data-menu/

Danko Kozar
  • 221
  • 2
  • 8
6

I was alsolooking for a solution while working with Material UI, So what you want to do is first disable the default behaviour of a browser on right click and then add the menu which you want, here's the working code:

import React from 'react';
import { withStyles } from '@material-ui/core/styles';
import Button from '@material-ui/core/Button';
import Menu from '@material-ui/core/Menu';
import MenuItem from '@material-ui/core/MenuItem';
import ListItemIcon from '@material-ui/core/ListItemIcon';
import ListItemText from '@material-ui/core/ListItemText';
import InboxIcon from '@material-ui/icons/MoveToInbox';
import DraftsIcon from '@material-ui/icons/Drafts';
import SendIcon from '@material-ui/icons/Send';

const StyledMenu = withStyles({
  paper: {
    border: '1px solid #d3d4d5',
  },
})((props) => (
  <Menu
    elevation={0}
    getContentAnchorEl={null}
    anchorOrigin={{
      vertical: 'bottom',
      horizontal: 'center',
    }}
    transformOrigin={{
      vertical: 'top',
      horizontal: 'center',
    }}
    {...props}
  />
));

const StyledMenuItem = withStyles((theme) => ({
  root: {
    '&:focus': {
      backgroundColor: theme.palette.primary.main,
      '& .MuiListItemIcon-root, & .MuiListItemText-primary': {
        color: theme.palette.common.white,
      },
    },
  },
}))(MenuItem);

export default function CustomizedMenus() {
  const [anchorEl, setAnchorEl] = React.useState(null);

  const handleClick = (event) => {
    event.preventDefault();
    setAnchorEl(event.currentTarget);
  };

  const handleClose = () => {
    setAnchorEl(null);
  };

  return (
    <div>
      <Button
        aria-controls="customized-menu"
        aria-haspopup="true"
        variant="contained"
        color="primary"
        onContextMenu={handleClick}
      >
        Open Menu
      </Button>
      <StyledMenu
        id="customized-menu"
        anchorEl={anchorEl}
        keepMounted
        open={Boolean(anchorEl)}
        onClose={handleClose}
      >
        <StyledMenuItem>
          <ListItemIcon>
            <SendIcon fontSize="small" />
          </ListItemIcon>
          <ListItemText primary="Sent mail" />
        </StyledMenuItem>
        <StyledMenuItem>
          <ListItemIcon>
            <DraftsIcon fontSize="small" />
          </ListItemIcon>
          <ListItemText primary="Drafts" />
        </StyledMenuItem>
        <StyledMenuItem>
          <ListItemIcon>
            <InboxIcon fontSize="small" />
          </ListItemIcon>
          <ListItemText primary="Inbox" />
        </StyledMenuItem>
      </StyledMenu>
    </div>
  );
}

and the sandbox project

Mob_Abominator
  • 196
  • 2
  • 5
  • 19