mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-09-09 03:01:18 +02:00
git-svn-id: svn://svn.silverstripe.com/silverstripe/open/modules/sapphire/trunk@92488 467b73ca-7a2a-4603-9d3b-597d59a354a9
397 lines
10 KiB
JavaScript
397 lines
10 KiB
JavaScript
/**
|
|
* $Id: DropMenu.js 1070 2009-04-01 18:03:06Z spocke $
|
|
*
|
|
* @author Moxiecode
|
|
* @copyright Copyright © 2004-2008, Moxiecode Systems AB, All rights reserved.
|
|
*/
|
|
|
|
(function(tinymce) {
|
|
var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, Event = tinymce.dom.Event, Element = tinymce.dom.Element;
|
|
|
|
/**#@+
|
|
* @class This class is used to create drop menus, a drop menu can be a
|
|
* context menu, or a menu for a list box or a menu bar.
|
|
* @member tinymce.ui.DropMenu
|
|
* @base tinymce.ui.Menu
|
|
*/
|
|
tinymce.create('tinymce.ui.DropMenu:tinymce.ui.Menu', {
|
|
/**
|
|
* Constructs a new drop menu control instance.
|
|
*
|
|
* @param {String} id Button control id for the button.
|
|
* @param {Object} s Optional name/value settings object.
|
|
*/
|
|
DropMenu : function(id, s) {
|
|
s = s || {};
|
|
s.container = s.container || DOM.doc.body;
|
|
s.offset_x = s.offset_x || 0;
|
|
s.offset_y = s.offset_y || 0;
|
|
s.vp_offset_x = s.vp_offset_x || 0;
|
|
s.vp_offset_y = s.vp_offset_y || 0;
|
|
|
|
if (is(s.icons) && !s.icons)
|
|
s['class'] += ' mceNoIcons';
|
|
|
|
this.parent(id, s);
|
|
this.onShowMenu = new tinymce.util.Dispatcher(this);
|
|
this.onHideMenu = new tinymce.util.Dispatcher(this);
|
|
this.classPrefix = 'mceMenu';
|
|
},
|
|
|
|
/**#@+
|
|
* @method
|
|
*/
|
|
|
|
/**
|
|
* Created a new sub menu for the drop menu control.
|
|
*
|
|
* @param {Object} s Optional name/value settings object.
|
|
* @return {tinymce.ui.DropMenu} New drop menu instance.
|
|
*/
|
|
createMenu : function(s) {
|
|
var t = this, cs = t.settings, m;
|
|
|
|
s.container = s.container || cs.container;
|
|
s.parent = t;
|
|
s.constrain = s.constrain || cs.constrain;
|
|
s['class'] = s['class'] || cs['class'];
|
|
s.vp_offset_x = s.vp_offset_x || cs.vp_offset_x;
|
|
s.vp_offset_y = s.vp_offset_y || cs.vp_offset_y;
|
|
m = new tinymce.ui.DropMenu(s.id || DOM.uniqueId(), s);
|
|
|
|
m.onAddItem.add(t.onAddItem.dispatch, t.onAddItem);
|
|
|
|
return m;
|
|
},
|
|
|
|
/**
|
|
* Repaints the menu after new items have been added dynamically.
|
|
*/
|
|
update : function() {
|
|
var t = this, s = t.settings, tb = DOM.get('menu_' + t.id + '_tbl'), co = DOM.get('menu_' + t.id + '_co'), tw, th;
|
|
|
|
tw = s.max_width ? Math.min(tb.clientWidth, s.max_width) : tb.clientWidth;
|
|
th = s.max_height ? Math.min(tb.clientHeight, s.max_height) : tb.clientHeight;
|
|
|
|
if (!DOM.boxModel)
|
|
t.element.setStyles({width : tw + 2, height : th + 2});
|
|
else
|
|
t.element.setStyles({width : tw, height : th});
|
|
|
|
if (s.max_width)
|
|
DOM.setStyle(co, 'width', tw);
|
|
|
|
if (s.max_height) {
|
|
DOM.setStyle(co, 'height', th);
|
|
|
|
if (tb.clientHeight < s.max_height)
|
|
DOM.setStyle(co, 'overflow', 'hidden');
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Displays the menu at the specified cordinate.
|
|
*
|
|
* @param {Number} x Horizontal position of the menu.
|
|
* @param {Number} y Vertical position of the menu.
|
|
* @param {Numner} px Optional parent X position used when menus are cascading.
|
|
*/
|
|
showMenu : function(x, y, px) {
|
|
var t = this, s = t.settings, co, vp = DOM.getViewPort(), w, h, mx, my, ot = 2, dm, tb, cp = t.classPrefix;
|
|
|
|
t.collapse(1);
|
|
|
|
if (t.isMenuVisible)
|
|
return;
|
|
|
|
if (!t.rendered) {
|
|
co = DOM.add(t.settings.container, t.renderNode());
|
|
|
|
each(t.items, function(o) {
|
|
o.postRender();
|
|
});
|
|
|
|
t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container});
|
|
} else
|
|
co = DOM.get('menu_' + t.id);
|
|
|
|
// Move layer out of sight unless it's Opera since it scrolls to top of page due to an bug
|
|
if (!tinymce.isOpera)
|
|
DOM.setStyles(co, {left : -0xFFFF , top : -0xFFFF});
|
|
|
|
DOM.show(co);
|
|
t.update();
|
|
|
|
x += s.offset_x || 0;
|
|
y += s.offset_y || 0;
|
|
vp.w -= 4;
|
|
vp.h -= 4;
|
|
|
|
// Move inside viewport if not submenu
|
|
if (s.constrain) {
|
|
w = co.clientWidth - ot;
|
|
h = co.clientHeight - ot;
|
|
mx = vp.x + vp.w;
|
|
my = vp.y + vp.h;
|
|
|
|
if ((x + s.vp_offset_x + w) > mx)
|
|
x = px ? px - w : Math.max(0, (mx - s.vp_offset_x) - w);
|
|
|
|
if ((y + s.vp_offset_y + h) > my)
|
|
y = Math.max(0, (my - s.vp_offset_y) - h);
|
|
}
|
|
|
|
DOM.setStyles(co, {left : x , top : y});
|
|
t.element.update();
|
|
|
|
t.isMenuVisible = 1;
|
|
t.mouseClickFunc = Event.add(co, 'click', function(e) {
|
|
var m;
|
|
|
|
e = e.target;
|
|
|
|
if (e && (e = DOM.getParent(e, 'tr')) && !DOM.hasClass(e, cp + 'ItemSub')) {
|
|
m = t.items[e.id];
|
|
|
|
if (m.isDisabled())
|
|
return;
|
|
|
|
dm = t;
|
|
|
|
while (dm) {
|
|
if (dm.hideMenu)
|
|
dm.hideMenu();
|
|
|
|
dm = dm.settings.parent;
|
|
}
|
|
|
|
if (m.settings.onclick)
|
|
m.settings.onclick(e);
|
|
|
|
return Event.cancel(e); // Cancel to fix onbeforeunload problem
|
|
}
|
|
});
|
|
|
|
if (t.hasMenus()) {
|
|
t.mouseOverFunc = Event.add(co, 'mouseover', function(e) {
|
|
var m, r, mi;
|
|
|
|
e = e.target;
|
|
if (e && (e = DOM.getParent(e, 'tr'))) {
|
|
m = t.items[e.id];
|
|
|
|
if (t.lastMenu)
|
|
t.lastMenu.collapse(1);
|
|
|
|
if (m.isDisabled())
|
|
return;
|
|
|
|
if (e && DOM.hasClass(e, cp + 'ItemSub')) {
|
|
//p = DOM.getPos(s.container);
|
|
r = DOM.getRect(e);
|
|
m.showMenu((r.x + r.w - ot), r.y - ot, r.x);
|
|
t.lastMenu = m;
|
|
DOM.addClass(DOM.get(m.id).firstChild, cp + 'ItemActive');
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
t.onShowMenu.dispatch(t);
|
|
|
|
if (s.keyboard_focus) {
|
|
Event.add(co, 'keydown', t._keyHandler, t);
|
|
DOM.select('a', 'menu_' + t.id)[0].focus(); // Select first link
|
|
t._focusIdx = 0;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Hides the displayed menu.
|
|
*/
|
|
hideMenu : function(c) {
|
|
var t = this, co = DOM.get('menu_' + t.id), e;
|
|
|
|
if (!t.isMenuVisible)
|
|
return;
|
|
|
|
Event.remove(co, 'mouseover', t.mouseOverFunc);
|
|
Event.remove(co, 'click', t.mouseClickFunc);
|
|
Event.remove(co, 'keydown', t._keyHandler);
|
|
DOM.hide(co);
|
|
t.isMenuVisible = 0;
|
|
|
|
if (!c)
|
|
t.collapse(1);
|
|
|
|
if (t.element)
|
|
t.element.hide();
|
|
|
|
if (e = DOM.get(t.id))
|
|
DOM.removeClass(e.firstChild, t.classPrefix + 'ItemActive');
|
|
|
|
t.onHideMenu.dispatch(t);
|
|
},
|
|
|
|
/**
|
|
* Adds a new menu, menu item or sub classes of them to the drop menu.
|
|
*
|
|
* @param {tinymce.ui.Control} o Menu or menu item to add to the drop menu.
|
|
* @return {tinymce.ui.Control} Same as the input control, the menu or menu item.
|
|
*/
|
|
add : function(o) {
|
|
var t = this, co;
|
|
|
|
o = t.parent(o);
|
|
|
|
if (t.isRendered && (co = DOM.get('menu_' + t.id)))
|
|
t._add(DOM.select('tbody', co)[0], o);
|
|
|
|
return o;
|
|
},
|
|
|
|
/**
|
|
* Collapses the menu, this will hide the menu and all menu items.
|
|
*
|
|
* @param {bool} d Optional deep state. If this is set to true all children will be collapsed as well.
|
|
*/
|
|
collapse : function(d) {
|
|
this.parent(d);
|
|
this.hideMenu(1);
|
|
},
|
|
|
|
/**
|
|
* Removes a specific sub menu or menu item from the drop menu.
|
|
*
|
|
* @param {tinymce.ui.Control} o Menu item or menu to remove from drop menu.
|
|
* @return {tinymce.ui.Control} Control instance or null if it wasn't found.
|
|
*/
|
|
remove : function(o) {
|
|
DOM.remove(o.id);
|
|
this.destroy();
|
|
|
|
return this.parent(o);
|
|
},
|
|
|
|
/**
|
|
* Destroys the menu. This will remove the menu from the DOM and any events added to it etc.
|
|
*/
|
|
destroy : function() {
|
|
var t = this, co = DOM.get('menu_' + t.id);
|
|
|
|
Event.remove(co, 'mouseover', t.mouseOverFunc);
|
|
Event.remove(co, 'click', t.mouseClickFunc);
|
|
|
|
if (t.element)
|
|
t.element.remove();
|
|
|
|
DOM.remove(co);
|
|
},
|
|
|
|
/**
|
|
* Renders the specified menu node to the dom.
|
|
*
|
|
* @return {Element} Container element for the drop menu.
|
|
*/
|
|
renderNode : function() {
|
|
var t = this, s = t.settings, n, tb, co, w;
|
|
|
|
w = DOM.create('div', {id : 'menu_' + t.id, 'class' : s['class'], 'style' : 'position:absolute;left:0;top:0;z-index:200000'});
|
|
co = DOM.add(w, 'div', {id : 'menu_' + t.id + '_co', 'class' : t.classPrefix + (s['class'] ? ' ' + s['class'] : '')});
|
|
t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container});
|
|
|
|
if (s.menu_line)
|
|
DOM.add(co, 'span', {'class' : t.classPrefix + 'Line'});
|
|
|
|
// n = DOM.add(co, 'div', {id : 'menu_' + t.id + '_co', 'class' : 'mceMenuContainer'});
|
|
n = DOM.add(co, 'table', {id : 'menu_' + t.id + '_tbl', border : 0, cellPadding : 0, cellSpacing : 0});
|
|
tb = DOM.add(n, 'tbody');
|
|
|
|
each(t.items, function(o) {
|
|
t._add(tb, o);
|
|
});
|
|
|
|
t.rendered = true;
|
|
|
|
return w;
|
|
},
|
|
|
|
// Internal functions
|
|
|
|
_keyHandler : function(e) {
|
|
var t = this, kc = e.keyCode;
|
|
|
|
function focus(d) {
|
|
var i = t._focusIdx + d, e = DOM.select('a', 'menu_' + t.id)[i];
|
|
|
|
if (e) {
|
|
t._focusIdx = i;
|
|
e.focus();
|
|
}
|
|
};
|
|
|
|
switch (kc) {
|
|
case 38:
|
|
focus(-1); // Select first link
|
|
return;
|
|
|
|
case 40:
|
|
focus(1);
|
|
return;
|
|
|
|
case 13:
|
|
return;
|
|
|
|
case 27:
|
|
return this.hideMenu();
|
|
}
|
|
},
|
|
|
|
_add : function(tb, o) {
|
|
var n, s = o.settings, a, ro, it, cp = this.classPrefix, ic;
|
|
|
|
if (s.separator) {
|
|
ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'ItemSeparator'});
|
|
DOM.add(ro, 'td', {'class' : cp + 'ItemSeparator'});
|
|
|
|
if (n = ro.previousSibling)
|
|
DOM.addClass(n, 'mceLast');
|
|
|
|
return;
|
|
}
|
|
|
|
n = ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'Item ' + cp + 'ItemEnabled'});
|
|
n = it = DOM.add(n, 'td');
|
|
n = a = DOM.add(n, 'a', {href : 'javascript:;', onclick : "return false;", onmousedown : 'return false;'});
|
|
|
|
DOM.addClass(it, s['class']);
|
|
// n = DOM.add(n, 'span', {'class' : 'item'});
|
|
|
|
ic = DOM.add(n, 'span', {'class' : 'mceIcon' + (s.icon ? ' mce_' + s.icon : '')});
|
|
|
|
if (s.icon_src)
|
|
DOM.add(ic, 'img', {src : s.icon_src});
|
|
|
|
n = DOM.add(n, s.element || 'span', {'class' : 'mceText', title : o.settings.title}, o.settings.title);
|
|
|
|
if (o.settings.style)
|
|
DOM.setAttrib(n, 'style', o.settings.style);
|
|
|
|
if (tb.childNodes.length == 1)
|
|
DOM.addClass(ro, 'mceFirst');
|
|
|
|
if ((n = ro.previousSibling) && DOM.hasClass(n, cp + 'ItemSeparator'))
|
|
DOM.addClass(ro, 'mceFirst');
|
|
|
|
if (o.collapse)
|
|
DOM.addClass(ro, cp + 'ItemSub');
|
|
|
|
if (n = ro.previousSibling)
|
|
DOM.removeClass(n, 'mceLast');
|
|
|
|
DOM.addClass(ro, 'mceLast');
|
|
}
|
|
|
|
/**#@-*/
|
|
});
|
|
})(tinymce); |