/js/jQuery.radmenu.js
JavaScript | 350 lines | 203 code | 17 blank | 130 comment | 23 complexity | 01a348ee41a8ffcf6d83f788a2d1dfd3 MD5 | raw file
- /*!
- * jQuery Radmenu (Radial Menu) Plugin
- * version: 0.9.8 (11-SEPT-2010)
- * @requires v1.3.2 or later
- *
- * Documentation:
- * http://www.tikku.com/jquery-radmenu-plugin
- *
- * Copyright 2010, Nirvana Tikku (ntikku@gmail.com)
- *
- * 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;
-
- // private :: defaults
- var defaults = {
- listClass: "list",
- itemClass: "item",
- activeItemClass: "active",
- selectEvent: null, // click, mouseenter etc
- onSelect: function($selected){},
- radius: 10, // in pixels
- initialScale: 1,
- angleOffset: 0, // in radians
- centerX: 0,
- centerY: 0,
- animSpeed: 500,
- scaleAnimSpeed: 400,
- scaleAnimOpts: {},
- afterAnimation: function($m){},
- onShow: function($items){$items.show();},
- onHide: function($items){$items.hide();},
- // example onScaleItem: $item.css("font-size", factor+"em");
- onScaleItem: function($item, factor, coords){}
- };
-
- // DEFAULTS
- $.radmenu = {
- container: {
- clz: "radial_div",
- itemClz: "radial_div_item",
- html: "<div></div>",
- css: { "position": "relative" }
- }
- };
-
- /**
- * 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" ?
- $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);
- 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]);
- });
- };
- /**
- * selectMenuitem
- * @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 MENU events to be bound to the radial menu
- */
- var MENU = {
- 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);
- 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);
- // switch the first and last items and then animate
- switchItems($m, $m.raditems().length-1, 0, 1);
- },
- prev: function(evt){ // anticlockwise
- var $m = getMenu(evt);
- // switch the last and first items and then animate
- switchItems($m, 0, $m.raditems().length-1, 1);
- },
- shuffle: function(evt,rndOffset){
- 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), rnd(rndOffset||15));
- },
- destroy: function(evt){
- var $m = getMenu(evt);
- $m.menu.data(OPTS, null).data(PREVOPTS, null).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+1, $items.length, updatedRadiusOpts);
- var animOpts = {left: coords.x,top: coords.y};
- if(typeof(o.scaleAnimOpts) == "object") {
- animOpts = $.extend({}, o.scaleAnimOpts, {left: coords.x,top: coords.y});
- }
- $this.animate(animOpts, o.scaleAnimSpeed);
- $m.opts.onScaleItem($this, factor, coords);
- });
- }
- return $m.menu;
- }
- };
-
- function updateRadius(opts, radius, factor){
- return $.extend({},opts,{radius:(factor*radius)});
- };
-
- // random int offset
- function rnd(i){return parseInt(Math.random()*i);};
-
- /**
- * getMenu
- * @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);
- }
- };
- };
-
- /**
- * switchItems
- * @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, posOffset){
- 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,posOffset); // 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+1, $menuitems.length, opts); // each item has a position
- ret += "<div class='"+$.radmenu.container.itemClz+"' "; // outer container for the div
- // after getting the coordinates, absolute position element at (x,y)
- ret += "style='position:absolute;left:"+coords.x+"px;top:"+coords.y+"px;display:none;'>";
- ret += $this.html(); // append the HTML _within_ the user's defined 'item'
- ret += "</div>";
- });
- return ret;
- };
-
- /**
- * getCoords - returns coordinates for menuitems
- * @params
- * idx - the instance index (1st, 2nd, 3rd, etc..)
- * num - the number of menuitems to spread
- * opts - the options provided by the user customizations
- * @return
- * Object - (x, y) coords
- */
- function getCoords(idx, num, opts){
- var radius = opts.radius; // user specified radius
- var angleOffset = opts.angleOffset; // provide flexibility of angle
- var angle = 2 * Math.PI * (parseFloat(idx/num)); // radians
- // 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 = opts.centerX + (Math.cos(angle + angleOffset) * radius), // "left"
- t = opts.centerY + (Math.sin(angle + angleOffset) * radius); // "top"
- return {x: l, y: t}; // return the x,y coords
- };
-
- /**
- * animateWheel - performs animation
- * @params
- * $m - object holding menu & options
- * posOffset - the position offset for the initial menuitem
- */
- function animateWheel($m, posOffset){
- // get the menu from the $m menu package
- var $menuitems = $m.menu.find("."+$.radmenu.container.itemClz);
- // 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; len*(Math.PI+(Math.sqrt(5)))
- var coords = getCoords(i+posOffset, len, $m.opts);
- // playing with this is fun - this basically just
- // performs the animation with new coordinates
- $this.animate({
- left: coords.x, top: coords.y
- }, $m.opts.animSpeed, i==(len-1)?function(){
- // allow the user to do something after completing an animation
- $m.opts.afterAnimation($m);
- }:undefined);
- });
- };
-
- })(jQuery);