Context Menus in RaphaelJS

Read all about it in http://terryyoung.blogspot.com/2011/08/context-menus-for-raphaeljs.html

HTML

<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js"></script>
<p>Drag, click or right-click on the elements, and fool around with them.</p>
<br>
<div id="SVG_DND_HOLDER_1" class="SVG_GETABOX_HOLDER"></div>


<!-- Yes, I know, custom attributes below. I've made the decision for now, so no debates -->

<ul id="menuRect" class="contextMenu">
    <li><a type="radio" name="size" w="64" href="#size_64">Set size to 64</a></li>
    <li><a type="radio" name="size" w="100" href="#size_100">Set size to 100</a></li>
    <li><a type="radio" name="size" w="130" href="#size_130">Set size to 130</a></li>
    <li class="separator"><a type="radio" name="style" val="default" href="#style_default">Use Default Style</a></li>
    <li><a type="radio" name="style" val="custom" href="#style_custom">Use Custom Style</a></li>
</ul>

<ul id="menuCircle" class="contextMenu">
    <li><a type="radio" name="radius" r="30" href="#radius_30">Set radius to 30</a></li>
    <li><a type="radio" name="radius" r="50" href="#radius_50">Set radius to 50</a></li>
    <li><a type="radio" name="radius" r="80" href="#radius_80">Set radius to 80</a></li>
    <li class="separator"><a type="radio" name="style" val="default" href="#style_default">Use Default Style</a></li>
    <li><a type="radio" name="style" val="custom" href="#style_custom">Use Custom Style</a></li>
</ul>

CSS

/***********************************************************************
Common styles
***********************************************************************/

html, body
{
font-family:            Tahoma;
font-size:              9pt;
}

br.clear
{
clear:                  both;
}


/***********************************************************************
Common styles
***********************************************************************/

.SVG_GETABOX_HOLDER {

-moz-border-radius:       10px;
-webkit-border-radius:    10px;
border:                   solid 1px #2b333c;
background-color:         #dcdfe4;
width:                    640px;
height:                   200px;
}


/***********************************************************************
Context Menu
***********************************************************************/

.contextMenu {
    position: absolute;
    width: 150px;
    z-index: 99999;
    border: solid 1px #CCC;
    background: #EEE;
    padding: 0px;
    margin: 0px;
    display: none;
}

.contextMenu LI {
    list-style: none;
    padding: 0px;
    margin: 0px;
}

.contextMenu A {
    color: #333;
    text-decoration: none;
    display: block;
    line-height: 20px;
    height: 20px;
    background-position: 6px center;
    background-repeat: no-repeat;
    outline: none;
    padding: 1px 5px;
    padding-left: 28px;
}

.contextMenu LI.hover A {
    color: #FFF;
    background-color: #3399FF;
}

.contextMenu LI.disabled A {
    color: #AAA;
    cursor: default;
}

.contextMenu LI.hover.disabled A {
    background-color: transparent;
}

.contextMenu LI.separator {
    border-top: solid 1px #CCC;
}

/***********************************************************************
Context Menu Icons
***********************************************************************/

.contextMenu LI.checked A { background-image: url(http://dl.dropbox.com/u/20165443/jsFiddle/images/ICON_OK.png); }

JavaScript

/**
 * @blog       http://terryyoung.blogspot.com/2011/08/context-menus-for-raphaeljs.html
 */


///////////////////////////////////////////////////////////////////////////////
// Please scroll to the bottom to look at the usage code of this demo.
// These below are some core extensions to RaphaelJS that makes it all happen.


/**
 * Simplifies something like this:
 *
 * this.ox = this.type == 'rect' ? this.attr('x') : this.attr('cx');
 * this.oy = this.type == 'rect' ? this.attr('y') : this.attr('cy');
 *
 * to this:
 *
 * el.o();    // and better, it supports chaining
 *
 * @copyright   Terry Young <terryyounghk [at] gmail.com>
 * @license     WTFPL Version 2 ( http://en.wikipedia.org/wiki/WTFPL )
 */
Raphael.el.is = function (type) { return this.type == (''+type).toLowerCase(); };
Raphael.el.x = function () { return this.is('circle') ? this.attr('cx') : this.attr('x'); };
Raphael.el.y = function () { return this.is('circle') ? this.attr('cy') : this.attr('y'); };
Raphael.el.o = function () { this.ox = this.x(); this.oy = this.y(); return this; };


/**
 *
 * getABox() for RaphaelJS - getBBox() On Steroids
 * More routine, pre-calculated values that getBBox() doesn't provide
 *
 * @author      Terry Young <terryyounghk [at] gmail.com>
 * @license     WTFPL Version 2 ( http://en.wikipedia.org/wiki/WTFPL )
 *
 */
Raphael.el.getABox = function ()
{
    var b = this.getBBox(); // thanks, I'll take it from here...

    var o =
    {
        // we'd still return what the original getBBox() provides us with
        x:              b.x,
        y:              b.y,
        width:          b.width,
        height:         b.height,

        // now we can actually pre-calculate the following into properties that are more readible for humans
        // x coordinates have three points: left edge, centered, and right edge
        xLeft:          b.x,
        xCenter:        b.x + b.width / 2,
        xRight:         b.x + b.width,


        // y coordinates have three...