New implementation of the "Actions" Javascript context menu. Simpler, easier to customize and more robust.

SVN:trunk[408]
This commit is contained in:
Denis Flaven
2010-05-15 14:51:29 +00:00
parent e22393404f
commit 01539af632
6 changed files with 586 additions and 530 deletions

View File

@@ -954,14 +954,14 @@ class MenuBlock extends DisplayBlock
if ($bIsBulkDeleteAllowed) { $aActions[] = array ('label' => Dict::S('UI:Menu:BulkDelete'), 'url' => "../pages/$sUIPage?operation=select_for_deletion&filter=$sFilter&$sContext"); }
}
}
$sHtml .= "<div class=\"jd_menu_itop\"><ul class=\"jd_menu jd_menu_itop\">\n<li>".Dict::S('UI:Menu:Actions')."\n<ul>\n";
$sHtml .= "<div class=\"itop_popup\"><ul>\n<li>".Dict::S('UI:Menu:Actions')."\n<ul>\n";
foreach ($aActions as $aAction)
{
$sClass = isset($aAction['class']) ? " class=\"{$aAction['class']}\"" : "";
$sHtml .= "<li><a href=\"{$aAction['url']}\"$sClass>{$aAction['label']}</a></li>\n<li>\n";
$sHtml .= "<li><a href=\"{$aAction['url']}\"$sClass>{$aAction['label']}</a></li>\n";
}
$sHtml .= "</ul>\n</li>\n</ul></div>\n";
$oPage->add_ready_script("$(\"ul.jd_menu\").jdMenu();\n");
$oPage->add_ready_script("$(\"div.itop_popup>ul\").popupmenu();\n");
return $sHtml;
}
}

View File

@@ -31,7 +31,8 @@ class iTopWebPage extends NiceWebPage
$this->add_linked_script("../js/jquery.treeview.js");
$this->add_linked_script("../js/jquery.autocomplete.js");
$this->add_linked_script("../js/jquery.bgiframe.js");
$this->add_linked_script("../js/jquery.jdMenu.js");
$this->add_linked_script("../js/jquery.positionBy.js");
$this->add_linked_script("../js/jquery.popupmenu.js");
$this->add_linked_script("../js/date.js");
$this->add_linked_script("../js/jquery.date.picker.js");
$this->add_linked_script("../js/jquery.tablesorter.min.js");

View File

@@ -354,131 +354,82 @@ div.iTopLogo span {
padding:5px 0px 16px 20px;
}
/* jdMenu popup menus */
ul.jd_menu,
ul.jd_menu_vertical {
margin: 0px;
padding: 0px;
list-style-type: none;
}
ul.jd_menu ul,
ul.jd_menu_vertical ul {
display: none;
}
ul.jd_menu li {
float: left;
}
/* -- Sub-Menus -- */
ul.jd_menu ul,
ul.jd_menu_vertical ul {
position: absolute;
display: none;
list-style-type: none;
margin: 0px;
padding: 0px;
z-index: 10000;
}
ul.jd_menu ul li,
ul.jd_menu_vertical ul li {
float: none;
margin: 0px;
}
/* jdMenu popup menus styling */
div.jd_menu_itop {
height:19px;
/* popup menus */
div.itop_popup {
margin: 0;
padding: 0;
float:right;
display:inline;
}
div.itop_popup > ul {
height:19px;
display:block;
width:70px; /* Nasty work-around for IE... en attendant mieux */
padding-left: 5px;
background: url(../images/actions_left.png) no-repeat left;
background: url(../images/actions_left.png) no-repeat top left;
cursor: pointer;
}
ul.jd_menu_itop {
div.itop_popup > ul > li {
float: left;
list-style: none;
font-size: 11px;
font-family: Tahoma,sans-serif;
height: 19px;
padding-right:16px;
background: url(../images/actions_right.png) no-repeat right;
padding-right: 16px;
padding-left: 4px;
background: url(../images/actions_right.png) no-repeat top right transparent;
font-weight: bold;
color: #fff;
vertical-align: middle;
}
.itop_popup li a {
display: block;
padding: 5px 12px;
text-decoration: none;
noborder: 1px solid white;
width: 70px;
color: #000;
font-weight: bold;
white-space: nowrap;
background: #fff;
}
.itop_popup li a:hover {
background: #1A4473;
}
.itop_popup ul > li > ul
{
border: 1px solid black;
background: #fff;
}
.itop_popup li > ul
{ margin: 0;
padding: 0;
position: absolute;
display: none;
border-top: 1px solid white;
}
.itop_popup li ul li {
float: none;
display: inline;
}
.itop_popup li ul li a {
width: auto;
}
.itop_popup li ul li a:hover {
background: #D81515;
color: #fff;
font-weight: bold;
}
ul.jd_menu_vertical {
width: 200px;
height: auto;
clear: both;
background: url(gradient-vertical.png) repeat-x;
background-color: #A5AFB8;
}
ul.jd_menu_itop a,
ul.jd_menu_itop a:active,
ul.jd_menu_itop a:link,
ul.jd_menu_itop a:visited {
text-decoration: none;
color: #FFF;
}
ul.jd_menu_itop ul li a,
ul.jd_menu_itop ul li a:active,
ul.jd_menu_itop ul li a:link,
ul.jd_menu_itop ul li a:visited {
color: #000;
}
ul.jd_menu_itop li {
font-family: Tahoma, sans-serif;
font-size: 11px;
padding: 2px 6px 4px 6px;
cursor: pointer;
white-space: nowrap;
color: #FFF;
}
ul.jd_menu_itop li.jd_menu_active_menubar,
ul.jd_menu_itop li.jd_menu_hover_menubar {
color: #FFF;
}
ul.jd_menu_vertical li.jd_menu_active_menubar,
ul.jd_menu_vertical li.jd_menu_hover_menubar {
padding-left: 6px;
padding-top: 1px;
border-top: 1px solid #70777D;
border-left: 0px;
border-right: 0px;
}
ul.jd_menu_itop ul {
background: #d81515;
border: 1px solid #70777D;
}
ul.jd_menu_itop ul li {
padding: 3px 10px 3px 4px;
background: #FFF;
border: none;
color: #000;
}
ul.jd_menu_itop ul li.jd_menu_active,
ul.jd_menu_itop ul li.jd_menu_hover {
background: #d81515;
padding-top: 2px;
border-top: 1px solid #ABB5BC;
padding-bottom: 2px;
border-bottom: 1px solid #929AA1;
color: #FFF;
}
ul.jd_menu_itop li ul li a {
padding: 0;
background: #fff;
padding-left:0;
}
ul.jd_menu_itop ul li.jd_menu_active a.jd_menu_active,
ul.jd_menu_itop ul li.jd_menu_hover a.jd_menu_hover {
color: #FFF;
background: #d81515;
padding-left:0;
}
/************************************/
.wizHeader {
background: #83b217;
padding: 15px;

View File

@@ -1,5 +1,5 @@
/*
* jdMenu 1.3.beta2 (2007-03-06)
* jdMenu 1.4.1 (2008-03-31)
*
* Copyright (c) 2006,2007 Jonathan Sharp (http://jdsharp.us)
* Dual licensed under the MIT (MIT-LICENSE.txt)
@@ -7,429 +7,165 @@
*
* http://jdsharp.us/
*
* Built upon jQuery 1.1.1 (http://jquery.com)
* This also requires the jQuery dimensions plugin
* Built upon jQuery 1.2.1 (http://jquery.com)
* This also requires the jQuery dimensions >= 1.2 plugin
*/
// This initializes the menu
$(function() {
$('ul.jd_menu').jdMenu();
});
(function($){
// This will store an element list of all our menu objects
var jdMenu = [];
function addEvents(ul) {
var settings = $.data( $(ul).parents().andSelf().filter('ul.jd_menu')[0], 'jdMenuSettings' );
$('> li', ul)
.bind('mouseenter.jdmenu mouseleave.jdmenu', function(evt) {
$(this).toggleClass('jdm_hover');
var ul = $('> ul', this);
if ( ul.length == 1 ) {
clearTimeout( this.$jdTimer );
var enter = ( evt.type == 'mouseenter' );
var fn = ( enter ? showMenu : hideMenu );
this.$jdTimer = setTimeout(function() {
fn( ul[0], settings.onAnimate, settings.isVertical );
}, enter ? settings.showDelay : settings.hideDelay );
}
})
.bind('click.jdmenu', function(evt) {
var ul = $('> ul', this);
if ( ul.length == 1 &&
( settings.disableLinks == true || $(this).hasClass('accessible') ) ) {
showMenu( ul, settings.onAnimate, settings.isVertical );
return false;
}
// The user clicked the li and we need to trigger a click for the a
if ( evt.target == this ) {
var link = $('> a', evt.target).not('.accessible');
if ( link.length > 0 ) {
var a = link[0];
if ( !a.onclick ) {
window.open( a.href, a.target || '_self' );
} else {
$(a).trigger('click');
}
}
}
if ( settings.disableLinks ||
( !settings.disableLinks && !$(this).parent().hasClass('jd_menu') ) ) {
$(this).parent().jdMenuHide();
evt.stopPropagation();
}
})
.find('> a')
.bind('focus.jdmenu blur.jdmenu', function(evt) {
var p = $(this).parents('li:eq(0)');
if ( evt.type == 'focus' ) {
p.addClass('jdm_hover');
} else {
p.removeClass('jdm_hover');
}
})
.filter('.accessible')
.bind('click.jdmenu', function(evt) {
evt.preventDefault();
});
}
function showMenu(ul, animate, vertical) {
var ul = $(ul);
if ( ul.is(':visible') ) {
return;
}
ul.bgiframe();
var li = ul.parent();
ul .trigger('jdMenuShow')
.positionBy({ target: li[0],
targetPos: ( vertical === true || !li.parent().hasClass('jd_menu') ? 1 : 3 ),
elementPos: 0,
hideAfterPosition: true
});
if ( !ul.hasClass('jdm_events') ) {
ul.addClass('jdm_events');
addEvents(ul);
}
li .addClass('jdm_active')
// Hide any adjacent menus
.siblings('li').find('> ul:eq(0):visible')
.each(function(){
hideMenu( this );
});
if ( animate === undefined ) {
ul.show();
} else {
animate.apply( ul[0], [true] );
}
}
function hideMenu(ul, animate) {
var ul = $(ul);
$('.bgiframe', ul).remove();
ul .filter(':not(.jd_menu)')
.find('> li > ul:eq(0):visible')
.each(function() {
hideMenu( this );
})
.end();
if ( animate === undefined ) {
ul.hide()
} else {
animate.apply( ul[0], [false] );
}
ul .trigger('jdMenuHide')
.parents('li:eq(0)')
.removeClass('jdm_active jdm_hover')
.end()
.find('> li')
.removeClass('jdm_active jdm_hover');
}
// Public methods
$.fn.jdMenu = function(inSettings) {
var settings = $.extend({}, arguments.callee.defaults, inSettings);
return this.each(function() {
jdMenu.push(this);
$(this).addClass('jd_menu_flag_root');
this.$settings = $.extend({}, settings, {isVerticalMenu: $(this).is('.jd_menu_vertical')});
$.fn.jdMenu = function(settings) {
// Future settings: activateDelay
var settings = $.extend({ // Time in ms before menu shows
showDelay: 200,
// Time in ms before menu hides
hideDelay: 500,
// Should items that contain submenus not
// respond to clicks
disableLinks: true
// This callback allows for you to animate menus
//onAnimate: null
}, settings);
if ( !$.isFunction( settings.onAnimate ) ) {
settings.onAnimate = undefined;
}
return this.filter('ul.jd_menu').each(function() {
$.data( this,
'jdMenuSettings',
$.extend({ isVertical: $(this).hasClass('jd_menu_vertical') }, settings)
);
addEvents(this);
});
};
$.fn.jdMenuShow = function() {
return this.each(function() {
showMenuLI.apply(this);
});
$.fn.jdMenuUnbind = function() {
$('ul.jdm_events', this)
.unbind('.jdmenu')
.find('> a').unbind('.jdmenu');
};
$.fn.jdMenuHide = function() {
return this.each(function() {
hideMenuUL.apply(this);
return this.filter('ul').each(function(){
hideMenu( this );
});
};
// Private methods and logic
$(window)
// Bind a click event to hide all visible menus when the document is clicked
.bind('click', function(){
$(jdMenu).find('ul:visible').jdMenuHide();
})
// Cleanup after ourself by nulling the $settings object
.bind('unload', function() {
$(jdMenu).each(function() {
this.$settings = null;
});
.bind('click.jdmenu', function(){
$('ul.jd_menu ul:visible').jdMenuHide();
});
// These are our default settings for this plugin
$.fn.jdMenu.defaults = {
activateDelay: 750,
showDelay: 150,
hideDelay: 550,
onShow: null,
onHideCheck: null,
onHide: null,
onAnimate: null,
onClick: null,
offsetX: 4,
offsetY: 2,
iframe: $.browser.msie
};
// Our special parentsUntil method to get all parents up to and including the matched element
$.fn.parentsUntil = function(match) {
var a = [];
$(this[0]).parents().each(function() {
a.push(this);
return !$(this).is(match);
});
return this.pushStack(a, arguments);
};
// Returns our settings object for this menu
function getSettings(el) {
return $(el).parents('ul.jd_menu_flag_root')[0].$settings;
}
// Unbind any events and then rebind them
function addEvents(ul) {
removeEvents(ul);
$('> li', ul)
.hover(hoverOverLI, hoverOutLI)
.bind('click', itemClick)
.find('> a.accessible')
.bind('click', accessibleClick);
};
// Remove all events for this menu
function removeEvents(ul) {
$('> li', ul)
.unbind('mouseover').unbind('mouseout')
.unbind('click')
.find('> a.accessible')
.unbind('click');
};
function hoverOverLI() {
var cls = 'jd_menu_hover' + ($(this).parent().is('.jd_menu_flag_root') ? '_menubar' : '');
$(this).addClass(cls).find('> a').addClass(cls);
if (this.$timer) {
clearTimeout(this.$timer);
}
// Do we have a sub menu?
if ($('> ul', this).size() > 0) {
var settings = getSettings(this);
// Which delay to use, the longer activate one or the shorter show delay if a menu is already visible
var delay = ($(this).parents('ul.jd_menu_flag_root').find('ul:visible').size() == 0)
? settings.activateDelay : settings.showDelay;
var t = this;
this.$timer = setTimeout(function() {
showMenuLI.apply(t);
}, delay);
}
};
function hoverOutLI() {
// Remove both classes so we do not have to test which one we are
$(this) .removeClass('jd_menu_hover').removeClass('jd_menu_hover_menubar')
.find('> a')
.removeClass('jd_menu_hover').removeClass('jd_menu_hover_menubar');
if (this.$timer) {
clearTimeout(this.$timer);
}
// TODO: Possible bug with our test for visibility in that parent menus are hidden child menus are not
// If we have a visible menu, hide it
if ($(this).is(':visible') && $('> ul', this).size() > 0) {
var settings = getSettings(this);
var ul = $('> ul', this)[0];
this.$timer = setTimeout(function() {
hideMenuUL.apply(ul);
}, settings.hideDelay);
}
};
// "this" is a reference to the LI element that contains
// the UL that will be shown
function showMenuLI() {
var ul = $('> ul', this).get(0);
// We are already visible, just return
if ($(ul).is(':visible')) {
return false;
}
// Clear our timer if it exists
if (this.$timer) {
clearTimeout(this.$timer);
}
// Get our settings object
var settings = getSettings(this);
// Call our callback
if (settings.onShow != null && settings.onShow.apply(this) == false) {
return false;
}
// Add hover classes, needed for accessible functionality
var isRoot = $(this).parent().is('.jd_menu_flag_root');
var c = 'jd_menu_active' + (isRoot ? '_menubar' : '');
$(this).addClass(c).find('> a').addClass(c);
if (!isRoot) {
// Add the active class to the parent list item which maybe our menubar
var c = 'jd_menu_active' + ($(this).parent().parent().parent().is('.jd_menu_flag_root') ? '_menubar' : '');
$(this).parent().parent().addClass(c).find('> a').addClass(c);
}
// Hide any existing menues at the same level
$(this).parent().find('> li > ul:visible').not(ul).each(function() {
hideMenuUL.apply(this);
});
addEvents(ul);
// Our range object is used in calculating menu positions
var Range = function(x1, x2, y1, y2) {
this.x1 = x1;
this.x2 = x2;
this.y1 = y1;
this.y2 = y2;
}
Range.prototype.contains = function(range) {
return (this.x1 <= range.x1 && range.x2 <= this.x2)
&&
(this.y1 <= range.y1 && range.y2 <= this.y2);
}
Range.prototype.transform = function(x, y) {
return new Range(this.x1 + x, this.x2 + x, this.y1 + y, this.y2 + y);
}
Range.prototype.nudgeX = function(range) {
if (this.x1 < range.x1) {
return new Range(range.x1, range.x1 + (this.x2 - this.x1), this.y1, this.y2);
} else if (this.x2 > range.x2) {
return new Range(range.x2 - (this.x2 - this.x1), range.x2, this.y1, this.y2);
}
return this;
}
Range.prototype.nudgeY = function(range) {
if (this.y1 < range.y1) {
return new Range(this.x1, this.x2, range.y1, range.y1 + (this.y2 - this.y1));
} else if (this.y2 > range.y2) {
return new Range(this.x1, this.x2, range.y2 - (this.y2 - this.y1), range.y2);
}
return this;
}
// window width & scroll offset
var sx = $(window).scrollLeft()
var sy = $(window).scrollTop();
var ww = $(window).innerWidth();
var wh = $(window).innerHeight();
var viewport = new Range( sx, sx + ww,
sy, sy + wh);
// "Show" our menu so we can calculate its width, set left and top so that it does not accidentally
// go offscreen and trigger browser scroll bars
$(ul).css({visibility: 'hidden', left: 0, top: 0}).show();
var menuWidth = $(ul).outerWidth();
var menuHeight = $(ul).outerHeight();
// Get the LI parent UL outerwidth in case borders are applied to it
var tp = $(this).parent();
var thisWidth = tp.outerWidth();
var thisBorderWidth = parseInt(tp.css('borderLeftWidth')) + parseInt(tp.css('borderRightWidth'));
//var thisBorderTop = parseInt(tp.css('borderTopWidth'));
var thisHeight = $(this).outerHeight();
var thisOffset = $(this).offset({border: false});
$(ul).hide().css({visibility: ''});
// We define a list of valid positions for our menu and then test against them to find one that works best
var position = [];
// Bottom Horizontal
// Menu is directly below and left edges aligned to parent item
position[0] = new Range(thisOffset.left, thisOffset.left + menuWidth,
thisOffset.top + thisHeight, thisOffset.top + thisHeight + menuHeight);
// Menu is directly below and right edges aligned to parent item
position[1] = new Range((thisOffset.left + thisWidth) - menuWidth, thisOffset.left + thisWidth,
position[0].y1, position[0].y2);
// Menu is "nudged" horizontally below parent item
position[2] = position[0].nudgeX(viewport);
// Right vertical
// Menu is directly right and top edge aligned to parent item
position[3] = new Range(thisOffset.left + thisWidth - thisBorderWidth, thisOffset.left + thisWidth - thisBorderWidth + menuWidth,
thisOffset.top, thisOffset.top + menuHeight);
// Menu is directly right and bottom edges aligned with parent item
position[4] = new Range(position[3].x1, position[3].x2,
position[0].y1 - menuHeight, position[0].y1);
// Menu is "nudged" vertically to right of parent item
position[5] = position[3].nudgeY(viewport);
// Top Horizontal
// Menu is directly top and left edges aligned to parent item
position[6] = new Range(thisOffset.left, thisOffset.left + menuWidth,
thisOffset.top - menuHeight, thisOffset.top);
// Menu is directly top and right edges aligned to parent item
position[7] = new Range((thisOffset.left + thisWidth) - menuWidth, thisOffset.left + thisWidth,
position[6].y1, position[6].y2);
// Menu is "nudged" horizontally to the top of parent item
position[8] = position[6].nudgeX(viewport);
// Left vertical
// Menu is directly left and top edges aligned to parent item
position[9] = new Range(thisOffset.left - menuWidth, thisOffset.left,
position[3].y1, position[3].y2);
// Menu is directly left and bottom edges aligned to parent item
position[10]= new Range(position[9].x1, position[9].x2,
position[4].y1 + thisHeight - menuHeight, position[4].y1 + thisHeight);
// Menu is "nudged" vertically to left of parent item
position[11]= position[10].nudgeY(viewport);
// This defines the order in which we test our positions
var order = [];
if ($(this).parent().is('.jd_menu_flag_root') && !settings.isVerticalMenu) {
order = [0, 1, 2, 6, 7, 8, 5, 11];
} else {
order = [3, 4, 5, 9, 10, 11, 0, 1, 2, 6, 7, 8];
}
// Set our default position (first position) if no others can be found
var pos = order[0];
for (var i = 0, j = order.length; i < j; i++) {
// If this position for our menu is within the viewport of the browser, use this position
if (viewport.contains(position[order[i]])) {
pos = order[i];
break;
}
}
var menuPosition = position[pos];
// Find if we are absolutely positioned or have an absolutely positioned parent
$(this).add($(this).parents()).each(function() {
if ($(this).css('position') == 'absolute') {
var abs = $(this).offset();
// Transform our coordinates to be relative to the absolute parent
menuPosition = menuPosition.transform(-abs.left, -abs.top);
return false;
}
});
switch (pos) {
case 3:
menuPosition.y1 += settings.offsetY;
case 4:
menuPosition.x1 -= settings.offsetX;
break;
case 9:
menuPosition.y1 += settings.offsetY;
case 10:
menuPosition.x1 += settings.offsetX;
break;
}
if (settings.iframe) {
$(ul).bgiframe();
}
if (settings.onAnimate) {
$(ul).css({left: menuPosition.x1, top: menuPosition.y1});
// The onAnimate method is expected to "show" the element it is passed
settings.onAnimate.apply(ul, [true]);
} else {
$(ul).css({left: menuPosition.x1, top: menuPosition.y1}).show();
}
return true;
}
// "this" is a reference to a UL menu to be hidden
function hideMenuUL(recurse) {
if (!$(this).is(':visible')) {
return false;
}
var settings = getSettings(this);
// Test if this menu should get hidden
if (settings.onHideCheck != null && settings.onHideCheck.apply(this) == false) {
return false;
}
// Hide all of our child menus first
$('> li ul:visible', this).each(function() {
hideMenuUL.apply(this, [false]);
});
// If we are the root, do not hide ourself
if ($(this).is('.jd_menu_flag_root')) {
alert('We are root');
return false;
}
var elms = $('> li', this).add($(this).parent());
elms.removeClass('jd_menu_hover').removeClass('jd_menu_hover_menubar')
.removeClass('jd_menu_active').removeClass('jd_menu_active_menubar')
.find('> a')
.removeClass('jd_menu_hover').removeClass('jd_menu_hover_menubar')
.removeClass('jd_menu_active').removeClass('jd_menu_active_menubar');
removeEvents(this);
$(this).each(function() {
if (settings.onAnimate != null) {
settings.onAnimate.apply(this, [false]);
} else {
$(this).hide();
}
}).find('> .bgiframe').remove();
// Our callback for after our menu is hidden
if (settings.onHide != null) {
settings.onHide.apply(this);
}
// Recursively hide our parent menus
if (recurse == true) {
$(this).parentsUntil('ul.jd_menu_flag_root')
.removeClass('jd_menu_hover').removeClass('jd_menu_hover_menubar')
.not('.jd_menu_flag_root').filter('ul')
.each(function() {
hideMenuUL.apply(this, [false]);
});
}
return true;
}
// Prevent the default (usually following a link)
function accessibleClick(e) {
if ($(this).is('.accessible')) {
// Stop the browser from the default link action allowing the
// click event to propagate to propagate to our LI (itemClick function)
e.preventDefault();
}
}
// Trigger a menu click
function itemClick(e) {
e.stopPropagation();
var settings = getSettings(this);
if (settings.onClick != null && settings.onClick.apply(this) == false) {
return false;
}
if ($('> ul', this).size() > 0) {
showMenuLI.apply(this);
} else {
if (e.target == this) {
var link = $('> a', e.target).not('.accessible');
if (link.size() > 0) {
var a = link.get(0);
if (!a.onclick) {
window.open(a.href, a.target || '_self');
} else {
$(a).click();
}
}
}
hideMenuUL.apply($(this).parent(), [true]);
}
}
})(jQuery);

62
js/jquery.popupmenu.js Normal file
View File

@@ -0,0 +1,62 @@
/*
* Simple popup menu 1.0 (2010-05-15)
*
* Copyright (c) 2010 Combodo SARL (www.combodo.com)
* Licenced under the GPL licence.
*
* http://www.combodo.com/
*
* Built upon jQuery jQuery 1.2.3a (http://jquery.com)
* Requires the jQuery positionBy plugin by Jonathan Sharp (http://jdsharp.us)
*/
jQuery.fn.popupmenu = function ()
{
var popupmenu = null;
return this.each(function()
{
$(this).bind('mouseenter.popup_menu click.popup_menu', function (evt)
{
console.log(evt.type);
var previous_popup = popupmenu;
var bMenuClosed = false;
popupmenu = $(this).find('ul');
if ( previous_popup != null)
{
if ( ((evt.type == 'click') && ((previous_popup[0] == popupmenu[0])) || // Comparing the jQuery objects
(evt.type == 'mouseenter') && (previous_popup[0] != popupmenu[0])) )
// The user clicked again in the menu or moved over another menu let's close it
previous_popup.css('display', 'none');
bMenuClosed = true;
}
if ( (previous_popup == null) || (previous_popup[0] != popupmenu[0])) // Comparing the jQuery objects
{
// We really clicked in a different menu, let's open it
popupmenu.bgiframe();
popupmenu.positionBy({ target: $(this),
targetPos: 2,
elementPos: 0,
hideAfterPosition: true
});
popupmenu.css('display', 'block');
}
if (bMenuClosed)
{
popupmenu = null;
}
evt.stopPropagation();
});
$(document).bind('click.popup_menu', function(evt)
{
if (popupmenu)
{
// The user clicked in the document's body, let's close the menu
popupmenu.css('display', 'none');
popupmenu = null;
}
});
});
};

306
js/jquery.positionBy.js Normal file
View File

@@ -0,0 +1,306 @@
/*
* positionBy 1.0.7 (2008-01-29)
*
* Copyright (c) 2006,2007 Jonathan Sharp (http://jdsharp.us)
* Dual licensed under the MIT (MIT-LICENSE.txt)
* and GPL (GPL-LICENSE.txt) licenses.
*
* http://jdsharp.us/
*
* Built upon jQuery 1.2.2 (http://jquery.com)
* This also requires the jQuery dimensions plugin
*/
(function($){
/**
* This function centers an absolutely positioned element
*/
/*
$.fn.positionCenter = function(offsetLeft, offsetTop) {
var offsetLeft = offsetLeft || 1;
var offsetTop = offsetTop || 1;
var ww = $(window).width();
var wh = $(window).height();
var sl = $(window).scrollLeft();
var st = $(window).scrollTop();
return this.each(function() {
var $t = $(this);
// If we are not visible we have to display our element (with a negative position offscreen)
var left = Math.round( ( ww - $t.outerWidth() ) / 2 );
if ( left < 0 ) {
left = 0;
} else {
left *= offsetLeft;
}
left += sl;
var top = Math.round( ( wh - $t.outerHeight() ) / 2 );
if ( top < 0 ) {
top = 0;
} else {
top *= offsetTop;
}
top += st;
$(this).parents().each(function() {
var $this = $(this);
if ( $this.css('position') != 'static' ) {
var o = $this.offset();
left += -o.left;
top += -o.top;
return false;
}
});
$t.css({left: left, top: top});
});
};
*/
// Our range object is used in calculating positions
var Range = function(x1, y1, x2, y2) {
this.x1 = x1; this.x2 = x2;
this.y1 = y1; this.y2 = y2;
};
Range.prototype.contains = function(range) {
return (this.x1 <= range.x1 && range.x2 <= this.x2)
&&
(this.y1 <= range.y1 && range.y2 <= this.y2);
};
Range.prototype.transform = function(x, y) {
return new Range(this.x1 + x, this.y1 + y, this.x2 + x, this.y2 + y);
};
$.fn.positionBy = function(args) {
var date1 = new Date();
if ( this.length == 0 ) {
return this;
}
var args = $.extend({ // The target element to position us relative to
target: null,
// The target's corner, possible values 0-3
targetPos: null,
// The element's corner, possible values 0-3
elementPos: null,
// A raw x,y coordinate
x: null,
y: null,
// Pass in an array of positions that are valid 0-15
positions: null,
// Add the final position class to the element (eg. positionBy0 through positionBy3, positionBy15)
addClass: false,
// Force our element to be at the location we specified (don't try to auto position it)
force: false,
// The element that we will make sure our element doesn't go outside of
container: window,
// Should the element be hidden after positioning?
hideAfterPosition: false
}, args);
if ( args.x != null ) {
var tLeft = args.x;
var tTop = args.y;
var tWidth = 0;
var tHeight = 0;
// Position in relation to an element
} else {
var $target = $( $( args.target )[0] );
var tWidth = $target.outerWidth();
var tHeight = $target.outerHeight();
var tOffset = $target.offset();
var tLeft = tOffset.left;
var tTop = tOffset.top;
}
// Our target right, bottom coord
var tRight = tLeft + tWidth;
var tBottom = tTop + tHeight;
return this.each(function() {
var $element = $( this );
// Position our element in the top left so we can grab its width without triggering scrollbars
if ( !$element.is(':visible') ) {
$element.css({ left: -3000,
top: -3000
})
.show();
}
var eWidth = $element.outerWidth();
var eHeight = $element.outerHeight();
// Holds x1,y1,x2,y2 coordinates for a position in relation to our target element
var position = [];
// Holds a list of alternate positions to try if this one is not in the browser viewport
var next = [];
// Our Positions via ASCII ART
/*
8 9 10 11
+------------+
7 | 15 12 | 0
| |
6 | 14 13 | 1
+------------+
5 4 3 2
*/
position[0] = new Range(tRight, tTop, tRight + eWidth, tTop + eHeight);
next[0] = [1,7,4];
position[1] = new Range(tRight, tBottom - eHeight, tRight + eWidth, tBottom);
next[1] = [0,6,4];
position[2] = new Range(tRight, tBottom, tRight + eWidth, tBottom + eHeight);
next[2] = [1,3,10];
position[3] = new Range(tRight - eWidth, tBottom, tRight, tBottom + eHeight);
next[3] = [1,6,10];
position[4] = new Range(tLeft, tBottom, tLeft + eWidth, tBottom + eHeight);
next[4] = [1,6,9];
position[5] = new Range(tLeft - eWidth, tBottom, tLeft, tBottom + eHeight);
next[5] = [6,4,9];
position[6] = new Range(tLeft - eWidth, tBottom - eHeight, tLeft, tBottom);
next[6] = [7,1,4];
position[7] = new Range(tLeft - eWidth, tTop, tLeft, tTop + eHeight);
next[7] = [6,0,4];
position[8] = new Range(tLeft - eWidth, tTop - eHeight, tLeft, tTop);
next[8] = [7,9,4];
position[9] = new Range(tLeft, tTop - eHeight, tLeft + eWidth, tTop);
next[9] = [0,7,4];
position[10]= new Range(tRight - eWidth, tTop - eHeight, tRight, tTop);
next[10] = [0,7,3];
position[11]= new Range(tRight, tTop - eHeight, tRight + eWidth, tTop);
next[11] = [0,10,3];
position[12]= new Range(tRight - eWidth, tTop, tRight, tTop + eHeight);
next[12] = [13,7,10];
position[13]= new Range(tRight - eWidth, tBottom - eHeight, tRight, tBottom);
next[13] = [12,6,3];
position[14]= new Range(tLeft, tBottom - eHeight, tLeft + eWidth, tBottom);
next[14] = [15,1,4];
position[15]= new Range(tLeft, tTop, tLeft + eWidth, tTop + eHeight);
next[15] = [14,0,9];
if ( args.positions !== null ) {
var pos = args.positions[0];
} else if ( args.targetPos != null && args.elementPos != null ) {
var pos = [];
pos[0] = [];
pos[0][0] = 15;
pos[0][1] = 7;
pos[0][2] = 8;
pos[0][3] = 9;
pos[1] = [];
pos[1][0] = 0;
pos[1][1] = 12;
pos[1][2] = 10;
pos[1][3] = 11;
pos[2] = [];
pos[2][0] = 2;
pos[2][1] = 3;
pos[2][2] = 13;
pos[2][3] = 1;
pos[3] = [];
pos[3][0] = 4;
pos[3][1] = 5;
pos[3][2] = 6;
pos[3][3] = 14;
var pos = pos[args.targetPos][args.elementPos];
}
var ePos = position[pos];
var fPos = pos;
if ( !args.force ) {
// TODO: Do the args.container
// window width & scroll offset
$window = $( window );
var sx = $window.scrollLeft();
var sy = $window.scrollTop();
// TODO: Look at innerWidth & innerHeight
var container = new Range( sx, sy, sx + $window.width(), sy + $window.height() );
// If we are outside of our viewport, see if we are outside vertically or horizontally and push onto the stack
var stack;
if ( args.positions ) {
stack = args.positions;
} else {
stack = [pos];
}
var test = []; // Keeps track of our positions we already tried
while ( stack.length > 0 ) {
var p = stack.shift();
if ( test[p] ) {
continue;
}
test[p] = true;
// If our current position is not within the viewport (eg. window)
// add the next suggested position
if ( !container.contains(position[p]) ) {
if ( args.positions === null ) {
stack = jQuery.merge( stack, next[p] );
}
} else {
ePos = position[p];
break;
}
}
}
// + TODO: Determine if we are going to use absolute left, top, bottom, right
// positions relative to our target
// Take into account any absolute or fixed positioning
// to 'normalize' our coordinates
$element.parents().each(function() {
var $this = $(this);
if ( $this.css('position') != 'static' ) {
var abs = $this.offset();
ePos = ePos.transform( -abs.left, -abs.top );
return false;
}
});
// Finally position our element
var css = { left: ePos.x1, top: ePos.y1 };
if ( args.hideAfterPosition ) {
css['display'] = 'none';
}
$element.css( css );
if ( args.addClass ) {
$element.removeClass( 'positionBy0 positionBy1 positionBy2 positionBy3 positionBy4 positionBy5 '
+ 'positionBy6 positionBy7 positionBy8 positionBy9 positionBy10 positionBy11 '
+ 'positionBy12 positionBy13 positionBy14 positionBy15')
.addClass('positionBy' + p);
}
});
};
})(jQuery);