Team:Brown-Stanford/Outreach/menu.js

From 2011.igem.org

Revision as of 20:24, 8 July 2011 by Yujovi (Talk | contribs)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

/*!

* jQuery Radmenu (Radial Menu) Plugin
* version: 1.0.0 (14-MAY-2011)
* @requires v1.4.2 or later
* 
* Author: Nirvana Tikku - ntikku@gmail.com - @ntikku
* Documentation:
* 		http://www.tikku.com/jquery-radmenu-plugin
* 
* Dual licensed under the MIT and GPL licenses:
*   http://www.opensource.org/licenses/mit-license.php
*   http://www.gnu.org/licenses/gpl.html 
* 
*/
(function($){

// radmenu namespace

var RADMENU = ".radmenu", // events are radmenu.{event} - guarantee no NS collision

OPTS = "options"+RADMENU,

PREVOPTS = "prevoptions"+RADMENU,

RADMENU_CLASS = "ui-radmenu-parent";

// private defaults var defaults = {

// radial menu container properties

listClass: "list",

itemClass: "item",

activeItemClass: "active",

// active item selection properties

selectEvent: null, // click, mouseenter etc

onSelect: function($selected){},

// initial setup properties

radius: 10, // in pixels

angleOffset: 0, // in degrees

centerX: 0,

centerY: 0,

// animation properties

animSpeed: 500,

animEasing: "swing",

// scaling properties and method

initialScale: 1,

scaleAnimSpeed: 0,

scaleAnimEasing: "swing",

scaleAnimOpts: {},

// example onScaleItem: $item.css("font-size", factor+"em"); onScaleItem: function($item, factor, coords){},

// public events

afterAnimation: function($m){},

onShow: function($items){$items.show();},

onHide: function($items){$items.hide();},

onNext: function($items){return true;},

onPrev: function($items){return true;},

// rotation property and fine-tuning event

rotate: false,

getRotation: function(degrees, index, numItems){return degrees;}

};

// radial menu container setup defaults $.radmenu = {

container: {

html: "
",

css: { "position": "relative" },

clz: "radial_div",

itemClz: "radial_div_item"

}

};

/** * jQuery Radmenu Plugin * @params * > input, dealt with by type * if empty - assumes initialization * if object - assumes initialization * if string - assumes trigger method * if number - select a particular menu item */ $.fn.radmenu = function(input, param){

try {

var $this = $(this);

var type = typeof input;

if(arguments.length==0 || type=="object")

return init($this, input);

else if(type=="string")

return (input=="items" || input=="opts") ? $this.triggerHandler(input+RADMENU) : $this.trigger(input+RADMENU, param || null);

else if(type=="number")

return $this.trigger("select"+RADMENU,input);

} catch (e) {

return "error : "+e;

}

};

/** * private :: init fn * @params * $menu - the jQuery obj / array w/ menu target * opts - options object, to be merged with defaults */ function init($menu, opts){

var o = $.extend({}, defaults, opts);

return $menu.each(function(m){

var $this = $(this);

if(!$this.hasClass(RADMENU_CLASS)) {

var $list = $this.find("."+o.listClass);

$list.find("."+o.itemClass).hide(); // ensure its hidden

// set the options within the data for the elem & bind evts $this.data(OPTS, updateRadius(o, o.initialScale, o.radius));

for(e in MENU) $this.bind(e+RADMENU, $this, MENU[e]);

$this.addClass(RADMENU_CLASS);

}

});

};

/** * selects a menu item - this method provides * functionality for nested radial menus * @param * evt - the event object * triggers select event on radmenu container * using the index of the 'target object' */ function selectMenuitem(evt){

var $this = $(this);

var $element = $(evt.target);

var container = $.radmenu.container;

if(!$element.hasClass(container.itemClz)) $element = $element.closest("."+container.itemClz);

var isInNested = $element.parents("."+container.itemClz).length>0;

var index = $element.index();

if(!isInNested) $this.parents("."+container.clz).radmenu(index);

else $this.radmenu(index);

cancelBubble(evt);

};

/** * cancel event bubbling - x-browser friendly * @param * evt - the event object */ function cancelBubble(evt){

if(!$.support.opacity) window.event.cancelBubble = true;

else evt.stopPropagation();

};

/** * All the events bound to the radial menu instance */ var MENU = { opts: function(evt) {

return getMenu(evt).opts;

}, show: function(evt, fn){ // fn = user input onshow

var $m = getMenu(evt);

var container = $.radmenu.container;

// clear any existing radial menus within the menu $m.menu.find("."+container.clz).remove();

// grab the desired menu items to be used in building the radmenu var $menuitems = $m.menu.find("."+$m.opts.itemClass);

// create a div that will be the radmenu & create the HTML for the items var $radialMenu = $(container.html) .addClass(container.clz) .css(container.css) .html(buildMenuHTML($menuitems, $m.opts));

// assign a selection event if the user has specified something var $menuitems = $radialMenu.find("."+container.itemClz);

if($m.opts.selectEvent!=null) $menuitems.bind($m.opts.selectEvent,selectMenuitem);

// append the radmenu items inside the menu $radialMenu.appendTo($m.menu);

if(typeof(fn) == "function") fn($menuitems); // allow passing in a method

else $m.opts.onShow($menuitems); // user can do what they want

cancelBubble(evt);

}, hide: function(evt){

var $m = getMenu(evt);

// remove the radmenu that was built and appended inside the menu var $menu = $m.menu.find("."+$.radmenu.container.clz);

$m.opts.onHide($menu.find("."+$.radmenu.container.itemClz));

$menu.remove();

cancelBubble(evt);

}, select: function(evt, selectIndex){

var $m = getMenu(evt);

// with a specific index specified, grab the item var $selected = $($m.raditems().get(selectIndex));

// remove the active class on the elements siblings $selected.siblings().removeClass($m.opts.activeItemClass);

// add the active class on the selected item $selected.addClass($m.opts.activeItemClass);

// pass the selected item to a customizable function $m.opts.onSelect($selected);

cancelBubble(evt);

}, next: function(evt){ // clockwise

var $m = getMenu(evt);

if( !$m.opts.onNext($m) ) return;

// switch the first and last items and then animate switchItems($m, $m.raditems().length-1, 0);

}, prev: function(evt){ // anticlockwise

var $m = getMenu(evt);

if( !$m.opts.onPrev($m) ) return;

// switch the last and first items and then animate switchItems($m, 0, $m.raditems().length-1);

}, shuffle: function(evt){

var $m = getMenu(evt);

var len = $m.raditems().length;

// swap some random item with another random item, and add some shuffling effects switchItems($m, rnd(len), rnd(len));

}, destroy: function(evt){

var $m = getMenu(evt);

$m.menu.data(OPTS, null) .data(PREVOPTS, null) .removeClass(RADMENU_CLASS) .unbind(RADMENU);

return $m.menu;

}, items: function(evt){

return getMenu(evt).raditems();

}, scale: function(evt, factor){

var $m = getMenu(evt);

if(factor){

var o = $m.opts;

var container = $.radmenu.container;

var prevOpts = $m.menu.data(PREVOPTS);

if(!prevOpts) $m.menu.data(PREVOPTS, prevOpts=o);

// get the radial menu items var $items = $m.menu.find("."+container.itemClz);

var updatedRadiusOpts = updateRadius(o, factor, prevOpts.radius);

$m.menu.data(OPTS, updatedRadiusOpts); // save the radius for anim purposes

$items.each(function(i){ // for each item update the x,y + css

var $this = $(this);

var coords = getCoords(i, $items.length, updatedRadiusOpts);

var animOpts = {

top: coords.top,

left: coords.left

};

if(typeof(o.scaleAnimOpts) == "object") {

animOpts = $.extend({}, o.scaleAnimOpts, animOpts);

}

$this.animate(animOpts, o.scaleAnimSpeed, o.scaleAnimEasing);

$m.opts.onScaleItem($this, factor, coords);

});

}

return $m.menu; } };

// simply multiples the radius by a factor function updateRadius(opts, radius, factor){

return $.extend({},opts,{radius:(factor*radius)});

};

// random int offset function rnd(i){

return parseInt( Math.random() * i );

};

/** * getMenu - this is a method that returns all * the required objects within a given method * that is subscribed to the radmenu object. * * @params * evt - the event object * @return * Object * > menu - jQueryfied menu * > opts - the options * > raditems - the radial menu items */ function getMenu(evt){

var $menu = evt.data;

return {

menu: $menu,

opts: $menu.data(OPTS),

raditems: function(){

// you will want to trigger raditems() if the contents get modified return $menu.find("."+$.radmenu.container.itemClz);

}

}; };

/** * Switch Items -- this method is used in re-evaluating the (x,y) coords * as a result of swapping the position of items. The intent with this * is that the plugin will reoganize the items (in the container, * as opposed to the original dom elements) such that the elements * positions, when selected, remains fixed around the circle. * Based on given indexes the items are swapped and then animated. The * most typical case involves removing the first and swapping the last * and vice versa. * * @params * $m - the menu package * remove - the index of the menuitem to replace in the swap * add - the index of the menuitem to use in the swap (a placeholder) */ function switchItems($m, remove, add){

if(remove==add) add = remove - 1; // ensure that we don't lose any items

var $remove = $($m.raditems()[remove]); // grab the replacement item

var toAddto = $m.raditems()[add]; // grab the placeholder

// insertion is dependent on index of items if(remove>add) $remove.insertBefore(toAddto);

else $remove.insertAfter(toAddto);

animateWheel($m, (remove<add)); // posOffset = 5:neat, 10:fireworksesque, 15:subtleish

};

/** * buildMenuHTML - returns string instead of objects * for performance * * @params * $menuitems - the jQueryified menu items * opts - the radial menu's options * @return * String * > each item is wrapped with an * absolute positioned div at an * offset determined by it's location * on a circle */ function buildMenuHTML($menuitems, opts){

var ret = [];

$menuitems.each(function(i){ // for each item we will want to build the HTML

var $this = $(this);

var coords = getCoords(i, $menuitems.length, opts); // each item has a position

var rotationHTML = "transform:rotate("+coords.angle+"deg); ";

ret.push("
");

ret.push($this.html()); // append the HTML _within_ the user's defined 'item'

ret.push("
");

});

return ret.join("");

};

/** * Get the radians value of an angle given a * particular slice of the selection * * @params * iIdx - the instance index * iNum - the number of menu items */ function getAngleAtIndex(iIdx, iNum){

return 2 * Math.PI * parseFloat(iIdx/iNum); // radians

};

/** * getCoords - returns coordinates for menuitems, as * well as an animation object with the appropriate rotation * as per config * * @params * iIdx - the instance index (1st, 2nd, 3rd, etc..) * iNum - the number of menuitems to distribute * oOpts - the options provided by the user customizations * bClockwise - a flag for the animation object * @return * Object - (x, y) coords & the angle in degrees */ function getCoords(iIdx, iNum, oOpts, bClockwise){

var radius = oOpts.radius; // user specified radius

var angle = getAngleAtIndex(iIdx, iNum);

angle += toRadians(oOpts.angleOffset); // provide flexibility of angle

// assuming: hypotenuse (hyp) = radius // // opposite |\ hypotenuse // | \ // 90deg |__\ (*theta* - angle) // adjacent // // x-axis offset: cos(theta) = adjacent / hypotenuse // ==> adjacent = left = cos(theta) * radius // y-axis offset: sin(theta) = opposite / hypotenuse // ==> opposite = top = sin(theta) * radius

var l = oOpts.centerX + ( Math.cos( angle ) * radius ), // "left" // angle is rounded to 2dp to fix a bug t = oOpts.centerY + ( Math.sin( parseInt(angle*100)/100 ) * radius ); // "top"

var degrees = oOpts.rotate ? oOpts.getRotation( angle * 180 / Math.PI, iIdx, iNum ) : 0;

// NOTE: why not just simply rotate to the angle? buggy // the element that gets shifted cycles through an unnecessary revolution // i.e. >360deg -> >0 deg var slice = oOpts.rotate ? ( getAngleAtIndex(1, iNum) * 180 / Math.PI ) : 0;

var rotation = ( bClockwise==true ? "-=" : "+=" ) + slice;

return {

left: l,

top: t,

angle: degrees,

animObj:{

left: l,

top: t,

radrotate: rotation

}

}; // return the x,y coords

};

/** * simple method to convert degrees to radians */ function toRadians(degrees){

return degrees * Math.PI / 180;

};

/** * animateWheel - performs animation on menu items within * the container elements * * @params * $m - object holding menu & options * iPosOffset - the position offset for the initial menuitem * bClockwise - for the animation, clockwise or counter */ function animateWheel($m, bClockwise){

// get the menu from the $m menu package var $menuitems = $m.raditems();

// get a handle on the number of items var len = $menuitems.length;

// for each item, we're going to animate left/top attributes $menuitems.each(function(i){

var $this = $(this);

// establish the new coordinates with a customizable offset var coords = getCoords(i, len, $m.opts, bClockwise);

// playing with this is fun - this basically just // performs the animation with new coordinates

$this.animate( coords.animObj, $m.opts.animSpeed, $m.opts.animEasing, function(){ if(i==(len-1) ){ // allow the user to do something after completing an animation $m.opts.afterAnimation($m); } } ); }); };

/** * Transform Utils */ var XForm = {};

// local cache of the appropriate transform to use XForm.attr = undefined;

// Safari, Chrome, FF 3.5+, IE 9+, and Opera 11+ XForm.opts = ["","-webkit-","-moz-","-ms-","-o-"]; XForm.cssattrs = ["","Webkit","Moz","ms","O"];

/** * Get the relevant CSS attr and cache it */ XForm.getCSSAttr = function($elm){

if( this.attr ) return this.attr;

return this.attr = (function(){

for(var ii=0; ii<XForm.cssattrs.length; ii++){

var opt = XForm.cssattrs[ii]+"Transform";

if( $elm[0].style[opt] ) return opt;

}

return "transform";

})();

};

/** * Deduce which transform is applicable * and extract the attr's value * @params * $elm - jQuerified element which the * css attribute is evaluated against */ XForm.getTransformValue = function($elm){

return jQuery.style( $elm[0], XForm.getCSSAttr($elm) );

};

/** * jQuery Proxy object */ var _ = {};

_.cur = $.fx.prototype.cur;

/** * jQuery Proxy Method: * We need to override this method in order for the "rotate([x]deg)" * css value to be parsed and passed through to the step fn numerically * * Credit due: Zachary Johnson www.zachstronaut.com * from https://github.com/zachstronaut/jquery-animate-css-rotate-scale */

   $.fx.prototype.cur = function() {
       if ( this.prop == "radrotate" ) {

var $elm = $(this.elem);

var style = XForm.getTransformValue($elm) || 'none';

if (style) {

               var m = style.match(/rotate\(([^)]+)\)/);
               if (m && m[1])	{

return parseFloat( m[1] );

}

           }
           return 0;
       }
       return _.cur.apply(this, arguments);
   };	

// // use a custom animation property - radrotate // $.fx.step.radrotate = function(fx) {

var $elm = $(fx.elem);

       $elm.css(XForm.getCSSAttr($elm), "rotate("+ fx.now +"deg)");
   };

})(jQuery);