Skip to content Skip to sidebar Skip to footer

How To Add Context Menu On Raphael Element?

I am working with ExtJS 4 framework and also using Raphael.js in my main application panel. First I was trying to prevent the regular context menu from showing, and I found out ve

Solution 1:

You can use Ext to display your custom HTML in a floating component:

var menu = Ext.create('Ext.Component', {
    floating: true
    ,html: ['<ulid="rect_menu"class="contextMenu">',
        '<li><ahref="call_to_js_function">Set aaa</a></li>',
        '<li><ahref="call_to_js_function">Set xxx</a></li>',
        '<li><ahref="call_to_js_function">Set ccc</a></li>',
        '</ul>'].join('')
});

Ext.get(raphael_element.node).on({
    contextmenu: function(e) {
        menu.showAt(e.getXY());
        e.preventDefault();
    }
});

However, showing the menu is not the tricky part. The logic to have your menu hidden, either when the user click outside of it, or when the ESC key is pressed is a lot more involved. That's why I would use a regular Menu to let Ext do the heavy lifting for us.

var menu = Ext.create('Ext.menu.Menu', {
    items: [{
        text: 'Set aaa'
        ,handler: function() {
            // logic for aaa
        }
    }, {
        text: 'Set bbb'
        ,handler: function() {
            // logic for bbb
        }
    }]
});

Ext.get(raphael_element.node).on({
    contextmenu: function(e) {
        menu.showAt(e.getXY());
        e.preventDefault();
    }
});

Now, if you're not interested in using all the menu system, with Ext menu items, etc., and you really want to render your own HTML, you can still leverage Ext menu to benefit from its hiding mechanics:

var menu = Ext.create('Ext.menu.Menu', {
    // This will prevent the icon gutter from showing, and your component from
    // being left padded
    plain: true
    ,items: [{
        xtype: 'component'
        ,html: ['<ulid="rect_menu"class="contextMenu">',
            '<li><ahref="call_to_js_function">Set aaa</a></li>',
            '<li><ahref="call_to_js_function">Set xxx</a></li>',
            '<li><ahref="call_to_js_function">Set ccc</a></li>',
            '</ul>'].join('')
    }]
});

Ext.get(raphael_element.node).on({
    contextmenu: function(e) {
        menu.showAt(e.getXY());
        e.preventDefault();
    }
});

Here's a fiddle using that last example. It doesn't use Raphael, but from the moment you get a DOM element that won't bear any importance.

Edit Example with actual Raphael elements

I've updated my fiddle with actual Raphael elements. It does work exactly as intended.

What is cool is that the click event of the element totally respects the vectorial path bounds, meaning you can precisely decide where your menu will pop or not. What is less cool is that with, for example, the famous tiger, that would mean 240 event handlers... Kinda ouch performance-wise.... You can always add the handler to the SVG element instead of individual shape elements, but the menu will be bound to the whole drawing rectangle instead of individual parts of course.

// --- Creating a ball (see http://raphaeljs.com/ball.html)Raphael.fn.ball = function (x, y, r, hue) {
    hue = hue || 0;
    returnthis.set(
        this.ellipse(x, y + r - r / 5, r, r / 2).attr({fill: "rhsb(" + hue + ", 1, .25)-hsb(" + hue + ", 1, .25)", stroke: "none", opacity: 0}),
        this.ellipse(x, y, r, r).attr({fill: "r(.5,.9)hsb(" + hue + ", 1, .75)-hsb(" + hue + ", .5, .25)", stroke: "none"}),
        this.ellipse(x, y, r - r / 5, r - r / 20).attr({stroke: "none", fill: "r(.5,.1)#ccc-#ccc", opacity: 0})
    );
};

var R = Raphael("holder"), x = 310, y = 180, r = 150;
var ball = R.ball(x, y, r, Math.random());

// --- Creatin a custom menuvar menu = Ext.create('Ext.menu.Menu', {
    plain: true
    ,cls: 'myContextMenu'
    ,items: [{
        xtype: 'component'
        ,html: ['<ul id="rect_menu" class="contextMenu">',
            '<li><a href="call_to_js_function">Set aaa</a></li>',
            '<li><a href="call_to_js_function">Set xxx</a></li>',
            '<li><a href="call_to_js_function">Set ccc</a></li>',
            '</ul>'].join('')
    }]
});

// --- Adding menu handler to all ball's elementsvar contextMenuHandler = function(e) {
    menu.showAt(e.getXY());
    e.preventDefault();
};

Ext.each(ball, function(raphaelElement) {
    Ext.fly(raphaelElement.node).on({
        contextmenu: contextMenuHandler
    });
});

Solution 2:

This works good as well, and is under MIT license

https://swisnl.github.io/jQuery-contextMenu//

JsFiddle (example copied from link above)

$(function() {
    $.contextMenu({
        selector: '.context-menu-one', 
        callback: function(key, options) {
            var m = "clicked: " + key;
            window.console && console.log(m) || alert(m); 
        },
        items: {
            "edit": {name: "Edit", icon: "edit"},
            "cut": {name: "Cut", icon: "cut"},
           copy: {name: "Copy", icon: "copy"},
            "paste": {name: "Paste", icon: "paste"},
            "delete": {name: "Delete", icon: "delete"},
            "sep1": "---------",
            "quit": {name: "Quit", icon: function(){
                return'context-menu-icon context-menu-icon-quit';
            }}
        }
    });

    $('.context-menu-one').on('click', function(e){
        console.log('clicked', this);
    })    
});

Post a Comment for "How To Add Context Menu On Raphael Element?"