Created
December 9, 2008 15:48
-
-
Save leandro/33938 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* @description DropDownAnything, drop down menu based on prototype | |
* @author Leandro N. Camargo; leandro-at-gmail-dot-com | |
* @version 0.1 | |
* @date 08/09/2008 | |
* @requires prototype.js 1.6 | |
*/ | |
var DDropDown = function(menu, targets, opt) { | |
var null_fn = function() {}, _this = this, ie = Prototype.Browser.IE, select_callback = false, menu_caller; | |
_this.menu = menu; | |
_this.rel_element = null; | |
_this.rel_position = 'top'; | |
_this.last_collection = null; | |
_this.menu_caller = null; | |
_this.targets = typeof(targets) == 'string' ? $$(targets) : targets; | |
_this.opt = Object.extend({ | |
context:'unique', | |
items_selector:'a', | |
before_select:null_fn, | |
on_select:null_fn, | |
after_select:null_fn, | |
before_show:null_fn, | |
before_hide:null_fn, | |
after_show:null_fn, | |
after_hide: null_fn, | |
cancel_click_default:true, | |
menu_relative_to:'target', // or 'click' | |
z_index: 100 | |
}, opt || {}); | |
_this.shim = ie ? new Element('iframe', { | |
style: 'position:absolute;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);display:none;opacity:0', | |
src: 'javascript:void(0);', | |
frameborder: 0 | |
}) : false; | |
var fn = function(ev) { | |
Event.stop(ev); | |
menu_caller = _this.menu_caller = this; | |
var before_return = _this.opt.before_show(ev, this, _this); | |
if(typeof before_return != 'undefined' && before_return === false) | |
return false; | |
_this.show(ev, this); | |
_this.opt.after_show(ev, this, _this); | |
DDropDown.current_instance = _this; | |
DDropDown.current_caller = this; | |
}; | |
if(_this.targets instanceof Array) { | |
_this.targets.invoke('observe', 'click', fn).invoke('setStyle', {cursor: Prototype.Browser.IE ? 'hand' : 'pointer'}); | |
} else { | |
_this.targets.observe('click', fn).setStyle({cursor: Prototype.Browser.IE ? 'hand' : 'pointer'}); | |
} | |
// this event has attached a named function because it could be called outside such event (in _this.show) | |
// that's why this function has some quirky statements | |
$(document).observe('click', _this.hide = function(ev, _instance) { | |
if(!DDropDown.showing || _this != DDropDown.current_instance) return false; | |
var src = Event.element(ev), el = DDropDown.showing; | |
if(this != document || DDropDown.should_it_hide(src, el)) { | |
var instance = _instance || _this; | |
if(this != document) instance.menu_caller = DDropDown.current_caller; | |
var before_return = instance.opt.before_hide(ev, el, instance); | |
if(typeof before_return != 'undefined' && before_return === false) | |
return false; | |
instance._hide(ev, el); | |
instance.opt.after_hide(ev, el, instance); | |
DDropDown.current_caller = DDropDown.current_instance = DDropDown.showing = false; | |
instance.menu_caller = menu_caller; // putting back the real element caller to its place | |
} | |
}); | |
_this.show = function(ev, element) { | |
//if showing a menu when another was already opened | |
DDropDown.showing && DDropDown.current_instance.hide(ev, DDropDown.current_instance); | |
if(_this.opt.menu_relative_to == 'click') { | |
var | |
mouseRef = Event.pointer(ev), | |
refX = mouseRef.x, | |
refY = mouseRef.y; | |
} else { | |
var | |
targetDim = element.getDimensions(), | |
targetOff = element.cumulativeOffset(), | |
refX = targetOff[0], | |
refY = targetOff[1], | |
targetW = targetDim.width, | |
targetH = targetDim.height; | |
} | |
var | |
menu = DDropDown.showing = DDropDown.get_menu(element, _this.menu, _this.opt.context), | |
x = refX, | |
y = refY, | |
vpDim = document.viewport.getDimensions(), | |
vpOff = document.viewport.getScrollOffsets(), | |
menuDim = menu.getDimensions(), | |
menuW = menuDim.width, | |
menuH = menuDim.height, | |
menuOff; | |
if(_this.opt.menu_relative_to == 'click') { | |
menuOff = { | |
left: (x + menuW > vpDim.width ? x - menuW : x) + 'px', | |
top: (y + menuH > (vpDim.height + vpOff.top) && (y - vpOff.top) > menuH ? y - menuH : y) + 'px' | |
}; | |
} else { | |
menuOff = { | |
left: (x + menuW + targetW > vpDim.width ? x + targetW - menuW : x) + 'px', | |
top: (y + menuH + targetH > (vpDim.height + vpOff.top) && (y - vpOff.top) > menuH ? y - menuH : y + targetH) + 'px' | |
} | |
} | |
_this.rel_element = menu.next() || menu.previous() || menu.up() || document.body; | |
_this.rel_position = menu.next() ? 'before' : (menu.previous() ? 'after' : 'top'); | |
$(document.body).insert({top: menu}); | |
menu.setStyle(menuOff).setStyle({zIndex: _this.opt.z_index, position: 'absolute'}).show(); | |
if(ie) { | |
$(document.body).insert(_this.shim); | |
_this.shim.setStyle({height: menuH + 'px', width: menuW + 'px'}).setStyle(menuOff).show(); | |
} | |
var items = _this.last_collection = menu.select(_this.opt.items_selector); | |
if(_this.opt.on_select != null_fn || _this.opt.after_select != null_fn || _this.opt.before_select != null_fn) { | |
items.invoke('observe', 'click', select_callback || (select_callback = function(ev) { | |
var before_return = _this.opt.before_select(ev, this); | |
if(typeof before_return != 'undefined' && !before_return) return false; | |
_this.opt.cancel_click_default && Event.stop(ev); | |
_this.opt.on_select(ev, this, _this); | |
_this.opt.after_select(ev, this); | |
})); | |
} | |
}; | |
_this._hide = function(ev, element) { | |
element.hide(); | |
var o = {}; | |
o[_this.rel_position] = element; | |
_this.rel_element.insert(o) | |
if(ie) _this.shim.hide(); | |
_this.last_collection.invoke('stopObserving', 'click', select_callback || null_fn); | |
}; | |
}; | |
DDropDown.showing = false; | |
// these two 'current_*' static methods exist to control whenever happens a click in caller with an already opened menu | |
DDropDown.current_instance = false; | |
DDropDown.current_caller = false; | |
DDropDown.should_it_hide = function(el, menu) { | |
return !el.ancestors().include(menu); | |
}; | |
DDropDown.get_menu = function(element, menu, context) { | |
// possible relations: parent, ancestor, self (when menu is inside the clicked element) and unique (when we talk about #id) | |
if(context.indexOf('parent') === 0) { | |
if(context == 'parent') return element.up().select(menu)[0]; | |
return element.up(context.match(/parent\((\d+)\)/)[1]).select(menu)[0]; | |
} else if(context == 'self') { | |
return element.select(menu)[0]; | |
} else if(context.indexOf('ancestor') === 0) { | |
return element.up(context.match(/ancestor\(([^\)]+)\)/)[1]); | |
} else { // expcts 'unique' here | |
return $$(menu)[0]; | |
} | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment