kopia lustrzana https://github.com/c9/core
1830 wiersze
60 KiB
JavaScript
1830 wiersze
60 KiB
JavaScript
define(function(require, module, exports) {
|
|
return function(apf) {
|
|
var $setTimeout = setTimeout;
|
|
var $setInterval = setInterval;
|
|
|
|
apf.popup = {
|
|
cache : {},
|
|
focusFix : {"INPUT":1,"TEXTAREA":1,"SELECT":1},
|
|
|
|
setContent : function(cacheId, content, style, width, height){
|
|
if (!this.popup) this.init();
|
|
|
|
this.cache[cacheId] = {
|
|
content : content,
|
|
style : style,
|
|
width : width,
|
|
height : height
|
|
};
|
|
content.style.position = "absolute";
|
|
//if(content.parentNode) content.parentNode.removeChild(content);
|
|
//if(style) apf.importCssString(style, this.popup.document);
|
|
|
|
content.onmousedown = function(e) {
|
|
if (!e) e = event;
|
|
|
|
|
|
|
|
//@todo can this cancelBubble just go?
|
|
//apf.cancelBubble(e, null, true);
|
|
//e.cancelBubble = true;
|
|
};
|
|
|
|
return content.ownerDocument;
|
|
},
|
|
|
|
removeContent : function(cacheId){
|
|
this.cache[cacheId] = null;
|
|
delete this.cache[cacheId];
|
|
},
|
|
|
|
init : function(){
|
|
//consider using iframe
|
|
this.popup = {};
|
|
|
|
apf.addEventListener("hotkey", function(e){
|
|
if (e.keyCode == "27" || e.altKey)
|
|
apf.popup.forceHide();
|
|
});
|
|
},
|
|
|
|
show : function(cacheId, options){
|
|
if (!this.popup) this.init();
|
|
|
|
options = apf.extend({
|
|
x : 0,
|
|
y : 0,
|
|
animate : false,
|
|
ref : null,
|
|
width : null,
|
|
height : null,
|
|
callback : null,
|
|
draggable : false,
|
|
resizable : false,
|
|
allowTogether: false,
|
|
autoCorrect : true,
|
|
noleft : false,
|
|
setZindex : true
|
|
}, options);
|
|
|
|
if ((!options.allowTogether
|
|
|| options.allowTogether !== true && options.allowTogether != this.last)
|
|
&& this.last != cacheId
|
|
&& this.cache[this.last]
|
|
&& (!this.cache[this.last].options || this.cache[this.last].options.autohide !== false))
|
|
this.hide();
|
|
|
|
var o = this.cache[cacheId];
|
|
o.options = options;
|
|
|
|
var dp,
|
|
popup = o.content,
|
|
moveUp = false,
|
|
moveLeft = false,
|
|
fixed = false;
|
|
|
|
if (options.setZindex)
|
|
apf.window.zManager.set(options.zindextype || "popup", o.content);
|
|
|
|
if ((dp = o.content.style.display) && dp.indexOf("none") > -1)
|
|
o.content.style.display = "";
|
|
|
|
var x = options.x;
|
|
var y = options.y;
|
|
|
|
var refNode = options.ref;
|
|
while (refNode && refNode.nodeType == 1) {
|
|
if (fixed = apf.getStyle(refNode, "position") == "fixed")
|
|
break;
|
|
refNode = refNode.parentNode || refNode.$parentNode;
|
|
}
|
|
|
|
if (!fixed) {
|
|
if (refNode) {
|
|
var pos = apf.getAbsolutePosition(options.ref,
|
|
o.content.offsetParent || o.content.parentNode);
|
|
x = (x || 0) + pos[0];
|
|
y = (y || 0) + pos[1];
|
|
}
|
|
|
|
if (options.width || o.width)
|
|
popup.style.width = ((options.width || o.width) - 3) + "px";
|
|
|
|
popup.style.position = "absolute";
|
|
popup.style.maxHeight = "";
|
|
|
|
var parentMenu = this.cache[options.allowTogether];
|
|
var pOverflow = apf.getOverflowParent(o.content);
|
|
var edgeY = (pOverflow == document.documentElement
|
|
? (apf.isIE
|
|
? pOverflow.offsetHeight
|
|
: (window.innerHeight + window.pageYOffset)) + pOverflow.scrollTop
|
|
: pOverflow.offsetHeight + pOverflow.scrollTop);
|
|
moveUp = options.up || options.autoCorrect && (y
|
|
+ (options.height || o.height || o.content.offsetHeight))
|
|
> edgeY;
|
|
|
|
var maxHeight = 0;
|
|
if (moveUp) {
|
|
var value;
|
|
var height = (options.height || o.height || o.content.offsetHeight);
|
|
if (options.ref)
|
|
value = (pos[1] - height);
|
|
else
|
|
value = Math.max(0, edgeY - height);
|
|
|
|
if (!options.up && value < 0) {
|
|
moveUp = false;
|
|
popup.style.top = y + "px";
|
|
maxHeight = edgeY - y - this.$screenMargin.bottom - 10;
|
|
}
|
|
else {
|
|
var minTop = this.$screenMargin.top + 3;
|
|
maxHeight = (pos ? pos[1] : edgeY) - minTop - 10;
|
|
popup.style.top = Math.max(value, minTop) + "px";
|
|
}
|
|
|
|
}
|
|
else {
|
|
popup.style.top = y + "px";
|
|
maxHeight = edgeY - y - this.$screenMargin.bottom - 10;
|
|
}
|
|
|
|
popup.style.overflowY = "auto";
|
|
popup.style.maxHeight = maxHeight ? maxHeight + "px" : "";
|
|
|
|
if (!options.noleft) {
|
|
var edgeX = (pOverflow == document.documentElement
|
|
? (apf.isIE
|
|
? pOverflow.offsetWidth
|
|
: (window.innerWidth + window.pageXOffset)) + pOverflow.scrollLeft
|
|
: pOverflow.offsetWidth + pOverflow.scrollLeft);
|
|
moveLeft = options.autoCorrect && (x
|
|
+ (options.width || o.width || o.content.offsetWidth))
|
|
> edgeX;
|
|
|
|
if (moveLeft) {
|
|
var value;
|
|
if (options.ref) {
|
|
value = (pos[0] - (options.width || o.width || o.content.offsetWidth))
|
|
+ (options.ref.offsetWidth);
|
|
}
|
|
else {
|
|
value = (edgeX - (options.width || o.width || o.content.offsetWidth)
|
|
- (parentMenu ? (edgeX - parentMenu.content.offsetLeft) : 0))
|
|
+ 5;
|
|
//parentMenu.width || parentMenu.content.offsetWidth) : 0));
|
|
}
|
|
popup.style.left = value < 0 ? x : (value - 1) + "px";
|
|
}
|
|
else {
|
|
popup.style.left = x + "px";
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
pos = apf.getAbsolutePosition(options.ref, refNode);
|
|
y = (y || 0) + pos[1] + refNode.offsetTop;
|
|
pos[0] += refNode.offsetLeft;
|
|
popup.style.position = "fixed";
|
|
popup.style.top = y + "px";
|
|
|
|
if (!options.noleft)
|
|
popup.style.left = x + "px";
|
|
}
|
|
|
|
|
|
// set a className that specifies the direction, to help skins with
|
|
// specific styling options.
|
|
apf.setStyleClass(popup, moveUp ? "upward" : "downward", [moveUp ? "downward" : "upward"]);
|
|
apf.setStyleClass(popup, moveLeft ? "moveleft" : "moveright", [moveLeft ? "moveright" : "moveleft"]);
|
|
|
|
|
|
if (options.animate) {
|
|
if (options.animate == "fade") {
|
|
apf.tween.single(popup, {
|
|
type : 'fade',
|
|
from : 0,
|
|
to : 1,
|
|
anim : apf.tween.NORMAL,
|
|
steps : options.steps || 15 * apf.animSteps
|
|
});
|
|
}
|
|
else {
|
|
var iVal, steps = apf.isIE8 ? 5 : 7, i = 0;
|
|
iVal = setInterval(function(){
|
|
var value = ++i * ((options.height || o.height) / steps);
|
|
|
|
popup.style.height = value + "px";
|
|
if (moveUp)
|
|
popup.style.top = (y - value - (options.y || 0)) + "px";
|
|
else
|
|
(options.container || popup).scrollTop = -1 * (i - steps) * ((options.height || o.height) / steps);
|
|
popup.style.display = "block";
|
|
|
|
if (i >= steps) {
|
|
clearInterval(iVal)
|
|
|
|
if (options.callback)
|
|
options.callback(popup);
|
|
}
|
|
}, 10);
|
|
}
|
|
}
|
|
else {
|
|
if (!refNode) {
|
|
if (options.height || o.height)
|
|
popup.style.height = (options.height || o.height) + "px";
|
|
value = (edgeY - (options.height || o.height || o.content.offsetHeight));
|
|
popup.style.top = y + (options.height || o.height || o.content.offsetHeight) < edgeY
|
|
? y
|
|
: value
|
|
+ "px";
|
|
}
|
|
popup.style.display = "block";
|
|
|
|
if (options.callback)
|
|
options.callback(popup);
|
|
}
|
|
|
|
$setTimeout(function(){
|
|
apf.popup.last = cacheId;
|
|
});
|
|
|
|
if (options.draggable) {
|
|
options.id = cacheId;
|
|
this.makeDraggable(options);
|
|
}
|
|
},
|
|
|
|
hide : function(){
|
|
if (this.isDragging) return;
|
|
|
|
var o = this.cache[this.last];
|
|
if (o) {
|
|
if (o.content)
|
|
o.content.style.display = "none";
|
|
|
|
if (o.options && o.options.onclose) {
|
|
o.options.onclose(apf.extend(o.options, {htmlNode: o.content}));
|
|
o.options.onclose = false;
|
|
}
|
|
}
|
|
},
|
|
|
|
isShowing : function(cacheId){
|
|
return this.last && this.last == cacheId
|
|
&& this.cache[this.last]
|
|
&& this.cache[this.last].content.style.display != "none";
|
|
},
|
|
|
|
isDragging : false,
|
|
|
|
makeDraggable: function(options) {
|
|
if (!apf.Interactive || this.cache[options.id].draggable)
|
|
return;
|
|
|
|
var oHtml = this.cache[options.id].content;
|
|
this.cache[options.id].draggable = true;
|
|
var o = {
|
|
$propHandlers : {},
|
|
minwidth : 10,
|
|
minheight : 10,
|
|
maxwidth : 10000,
|
|
maxheight : 10000,
|
|
dragOutline : false,
|
|
resizeOutline : false,
|
|
draggable : true,
|
|
resizable : options.resizable,
|
|
$ext : oHtml,
|
|
oDrag : oHtml.firstChild
|
|
};
|
|
|
|
oHtml.onmousedown =
|
|
oHtml.firstChild.onmousedown = function(e){
|
|
if (!e) e = event;
|
|
|
|
|
|
|
|
(e || event).cancelBubble = true;
|
|
}
|
|
|
|
apf.implement.call(o, apf.Interactive);
|
|
|
|
o.$propHandlers["draggable"].call(o, true);
|
|
o.$propHandlers["resizable"].call(o, true);
|
|
},
|
|
|
|
getCurrentElement : function(){
|
|
return typeof this.last == "number" && apf.lookup(this.last);
|
|
},
|
|
|
|
$mousedownHandler : function(amlNode, e){
|
|
if (!this.last || (amlNode && this.last == amlNode.$uniqueId) || !this.cache[this.last])
|
|
return;
|
|
|
|
var htmlNode = e.srcElement || e.target;
|
|
|
|
var uId = this.last;
|
|
|
|
while (this.cache[uId]) {
|
|
if (apf.isChildOf(this.cache[uId].content, htmlNode, true))
|
|
return;
|
|
|
|
if (!this.cache[uId].options)
|
|
return;
|
|
|
|
uId = this.cache[uId].options.allowTogether;
|
|
}
|
|
|
|
this.forceHide();
|
|
},
|
|
|
|
forceHide : function(){
|
|
if (document.body.classList.contains("noInput")) return;
|
|
|
|
if (this.last
|
|
|
|
&& !apf.plane.current
|
|
|
|
&& this.isShowing(this.last)
|
|
&& this.cache[this.last]
|
|
&& this.cache[this.last].options
|
|
&& this.cache[this.last].options.autohide !== false) {
|
|
var o = apf.lookup(this.last);
|
|
if (!o)
|
|
this.last = null;
|
|
else if (o.dispatchEvent("popuphide") !== false)
|
|
this.hide();
|
|
}
|
|
},
|
|
|
|
destroy : function(){
|
|
for (var cacheId in this.cache) {
|
|
if (this.cache[cacheId]) {
|
|
this.cache[cacheId].content.onmousedown = null;
|
|
apf.destroyHtmlNode(this.cache[cacheId].content);
|
|
this.cache[cacheId].content = null;
|
|
this.cache[cacheId] = null;
|
|
}
|
|
}
|
|
|
|
if (!this.popup) return;
|
|
//this.popup.document.body.c = null;
|
|
//this.popup.document.body.onmouseover = null;
|
|
},
|
|
|
|
setMargin: function(m) {
|
|
for (var i in this.$screenMargin)
|
|
if (i in m)
|
|
this.$screenMargin[i] = m[i] || 0;
|
|
},
|
|
$screenMargin: {top: 0, left: 0, right: 0, bottom: 0}
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
* This element displays a skinnable menu of items which can be choosen.
|
|
*
|
|
* Based on the context of the menu, items can be shown and hidden.
|
|
*
|
|
*
|
|
* #### Example
|
|
*
|
|
* ```xml, demo
|
|
* <a:application xmlns:a="http://ajax.org/2005/aml">
|
|
* <!-- startcontent -->
|
|
* <a:menu id="menu1">
|
|
* <a:item>Tutorials</a:item>
|
|
* <a:item icon="email.png">Contact</a:item>
|
|
* <a:divider></a:divider>
|
|
* <a:item
|
|
* icon = "application_view_icons.png"
|
|
* hotkey = "Ctrl+T"
|
|
* onclick = "setTimeout(function(){alert('You did it');}, 1000)">
|
|
* Tutorials</a:item>
|
|
* <a:divider />
|
|
* <a:item disabled="true">Visit Ajax.org</a:item>
|
|
* <a:item>Exit</a:item>
|
|
* </a:menu>
|
|
* <a:window
|
|
* width = "400"
|
|
* height = "150"
|
|
* visible = "true"
|
|
* resizable = "true"
|
|
* title = "Mail message"
|
|
* skin = "bk-window2">
|
|
* <a:toolbar>
|
|
* <a:menubar>
|
|
* <a:button submenu="menu1">File</a:button>
|
|
* <a:button submenu="menu1" disabled="true">Edit</a:button>
|
|
* </a:menubar>
|
|
* </a:toolbar>
|
|
* </a:window>
|
|
* <!-- endcontent -->
|
|
* </a:application>
|
|
* ```
|
|
*
|
|
* @class apf.menu
|
|
* @define menu
|
|
* @selection
|
|
* @allowchild item, divider, check, radio
|
|
*
|
|
* @author Ruben Daniels (ruben AT ajax DOT org)
|
|
* @version %I%, %G%
|
|
* @since 0.4
|
|
*
|
|
* @inherits apf.Presentation
|
|
*/
|
|
/**
|
|
* @event display Fires when the contextmenu is shown.
|
|
*/
|
|
/**
|
|
* @event itemclick Fires when a user presses the mouse button while over a child of this element.
|
|
* @param {Object} e The standard event object. The following property is available:
|
|
* - `value` ([[String]]): the value of the clicked element.
|
|
*
|
|
*/
|
|
apf.menu = function(struct, tagName){
|
|
this.$init(tagName || "menu", apf.NODE_VISIBLE, struct);
|
|
|
|
this.animate = apf.enableAnim;
|
|
};
|
|
|
|
(function(){
|
|
this.$focussable = apf.MENU;
|
|
this.$positioning = "basic"
|
|
//var _self = this;
|
|
//var blurring = false;
|
|
|
|
// *** Properties and Attributes *** //
|
|
|
|
//this.zindex = 10000000;
|
|
this.visible = false;
|
|
this.matchhide = false;
|
|
|
|
this.$booleanProperties["animate"] = true;
|
|
this.$booleanProperties["pinned"] = true;
|
|
this.$booleanProperties["sticky"] = true;
|
|
this.$booleanProperties["matchhide"] = true;
|
|
|
|
this.$propHandlers["visible"] = function(value, prop, force, nofocus, hideOpener){
|
|
if (!this.$ext)
|
|
return;
|
|
|
|
if (value) {
|
|
this.$ext.style.display = "block";
|
|
if (this.opener && this.opener.localName.indexOf('item') > -1)
|
|
this.opener.parentNode.$showingSubMenu = this;
|
|
}
|
|
else {
|
|
this.$ext.style.display = "none";
|
|
|
|
var lastFocus = apf.menu.lastFocussed;
|
|
var opener = this.opener;
|
|
//@todo test this with a list being the opener of the menu
|
|
if (lastFocus != this.opener && this.opener && this.opener.$blur)
|
|
this.opener.$blur();
|
|
|
|
if (this.opener && this.opener.parentNode && this.opener.parentNode.localName == "menu") {
|
|
if (!this.$hideTree)
|
|
this.$hideTree = -1
|
|
this.opener.parentNode.focus();
|
|
}
|
|
|
|
|
|
else if (lastFocus) {
|
|
//We're being hidden because some other object gets focus
|
|
if (apf.window.$settingFocus) {
|
|
if (apf.window.$settingFocus != lastFocus && lastFocus.$blur)
|
|
lastFocus.$blur();
|
|
this.$blur();
|
|
|
|
if (apf.window.$settingFocus.localName != "menu") //not menu walking
|
|
apf.menu.lastFocussed = null;
|
|
}
|
|
//We're being hidden because window looses focus
|
|
|
|
//We're just being hidden
|
|
else if (this.$hideTree) {
|
|
if (!this.$hideTree)
|
|
this.$hideTree = -1
|
|
|
|
var visTest = (lastFocus.disabled || lastFocus.$ext
|
|
&& !lastFocus.$ext.offsetHeight) // || !lastFocus.visible
|
|
&& lastFocus != apf.document.documentElement;
|
|
|
|
if (nofocus || visTest) {
|
|
if (lastFocus.$blur)
|
|
lastFocus.$blur();
|
|
this.$blur();
|
|
apf.document.activeElement = null;
|
|
|
|
if (visTest && apf.window.moveNext() === false)
|
|
apf.window.$focusRoot();
|
|
}
|
|
else {
|
|
lastFocus.focus(null, null, true);
|
|
}
|
|
|
|
apf.menu.lastFocussed = null;
|
|
}
|
|
}
|
|
|
|
|
|
clearTimeout(this.$submenuTimer);
|
|
|
|
if (this.$showingSubMenu) {
|
|
this.$showingSubMenu.hide();
|
|
this.$showingSubMenu = null;
|
|
}
|
|
|
|
if (this.opener && this.opener.$submenu) {
|
|
this.opener.$submenu(true, true);
|
|
|
|
//@todo problem with loosing focus when window looses focus
|
|
if (this.$hideTree === true && this.opener
|
|
&& this.opener.parentNode
|
|
&& this.opener.parentNode.localName == "menu"
|
|
&& this.opener.parentNode.$hideTree != -1) {
|
|
this.opener.parentNode.$hideTree = true
|
|
this.opener.parentNode.hide();
|
|
}
|
|
|
|
this.opener = null;
|
|
}
|
|
this.$hideTree = null;
|
|
|
|
if (this.$selected) {
|
|
apf.setStyleClass(this.$selected.$ext, "", ["hover"]);
|
|
this.$selected = null;
|
|
}
|
|
|
|
this.dispatchEvent("hide", {opener: opener});
|
|
}
|
|
};
|
|
|
|
// *** Public Methods *** //
|
|
|
|
var lastFocus;
|
|
|
|
/**
|
|
* Shows the menu, optionally within a certain context.
|
|
* @param {Number} x The left position of the menu.
|
|
* @param {Number} y The top position of the menu.
|
|
* @param {Boolean} noanim Whether to animate the showing of this menu.
|
|
* @param {apf.AmlElement} opener The element that is the context of this menu.
|
|
* @param {XMLElement} xmlNode The {@link term.datanode data node} that provides data context to the menu child nodes.
|
|
* @see apf.GuiElement@contextmenu
|
|
*/
|
|
this.display = function(x, y, noanim, opener, xmlNode, openMenuId, btnWidth){
|
|
this.opener = opener;
|
|
|
|
var lastFocus;
|
|
if (!apf.menu.lastFocussed)
|
|
lastFocus = apf.menu.lastFocussed = apf.menu.lastFocussedItem;
|
|
|
|
//Show / hide Child Nodes Based on XML
|
|
if (xmlNode && !this.disabled) {
|
|
var last, i, node,
|
|
nodes = this.childNodes,
|
|
c = 0,
|
|
l = nodes.length, result;
|
|
for (i = 0; i < l; i++) {
|
|
node = nodes[i];
|
|
if (node.nodeType != 1 || node.localName != "item")
|
|
continue;
|
|
|
|
result = !xmlNode || !node.match || (node.cmatch || (node.cmatch = apf.lm.compile(node.match, {
|
|
xpathmode : 3,
|
|
injectself : true
|
|
})))(xmlNode)
|
|
|
|
if (result) {
|
|
if (this.matchhide)
|
|
node.show();
|
|
else
|
|
node.enable();
|
|
|
|
if (node.localName == "divider" && this.matchhide) {
|
|
last = node;
|
|
if (c == 0)
|
|
node.hide();
|
|
c = 0;
|
|
}
|
|
else c++;
|
|
}
|
|
else {
|
|
if (this.matchhide)
|
|
node.hide();
|
|
else
|
|
node.disable();
|
|
|
|
if (!node.nextSibling && c == 0 && last)
|
|
last.hide();
|
|
}
|
|
}
|
|
}
|
|
|
|
if (this.oOverlay) {
|
|
if (btnWidth) {
|
|
this.oOverlay.style.display = "block";
|
|
this.oOverlay.style.width = btnWidth + "px";
|
|
}
|
|
else
|
|
this.oOverlay.style.display = "none";
|
|
}
|
|
|
|
function afterRender(){
|
|
if (x === null) {
|
|
apf.popup.show(this.$uniqueId, {
|
|
x : 0,
|
|
y : this.ref ? 0 : opener.$ext.offsetHeight,
|
|
animate : noanim || !this.animate ? false : "fade",
|
|
steps : 10,
|
|
ref : (this.ref || opener).$ext,
|
|
allowTogether: openMenuId,
|
|
autohide : !this.pinned,
|
|
noleft : this.left !== undefined,
|
|
setZindex : this.zindex ? false : true,
|
|
up : (this.ref || opener).submenudir == "up"
|
|
});
|
|
}
|
|
else {
|
|
//var bodyPos = apf.getAbsolutePosition(document.body);
|
|
apf.popup.show(this.$uniqueId, {
|
|
x : x,
|
|
y : y - (apf.isIE && apf.isIE < 8 ? 1 : 0),
|
|
animate : noanim || !this.animate ? false : "fade",
|
|
steps : 10,
|
|
//ref : this.$ext.offsetParent,
|
|
allowTogether: openMenuId,
|
|
autohide : !this.pinned,
|
|
setZindex : this.zindex ? false : true
|
|
//autoCorrect : false
|
|
});
|
|
}
|
|
|
|
// var lastFocus =
|
|
// apf.menu.lastFocus = opener && opener.$focussable === true
|
|
// ? opener
|
|
// : apf.menu.lastFocus || apf.document.activeElement;
|
|
|
|
apf.popup.last = null;
|
|
|
|
//if (!apf.isGecko) //This disables keyboard support for gecko - very strange behaviour
|
|
this.focus();
|
|
|
|
//Make the component that provides context appear to have focus
|
|
// second argument is needed for ace tree
|
|
if (lastFocus && lastFocus != this && lastFocus.$focus)
|
|
lastFocus.$focus(null, {fromContextMenu: true});
|
|
|
|
this.xmlReference = xmlNode;
|
|
|
|
//@todo consider renaming this to onshow and onhide
|
|
this.dispatchEvent("display", {opener: opener});
|
|
}
|
|
|
|
this.visible = false;
|
|
|
|
if (!this.parentNode)
|
|
apf.document.documentElement.appendChild(this);
|
|
|
|
if (this.$rendered !== false) {
|
|
this.show();
|
|
afterRender.call(this);
|
|
}
|
|
else {
|
|
this.addEventListener("afterrender", afterRender);
|
|
this.show();
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Returns the current group value of this element.
|
|
* @return {String} The current selected value.
|
|
*/
|
|
this.getValue = function(group){
|
|
return this.getSelected(group).value || "";
|
|
};
|
|
|
|
/**
|
|
* Retrieves the selected element from a group of radio elements.
|
|
* @param {String} group The name of the group.
|
|
* @return {apf.radiobutton} The selected radio element.
|
|
*/
|
|
this.getSelected = function(group){
|
|
var nodes = this.childNodes;
|
|
var i, l = nodes.length;
|
|
for (i = 0; i < l; i++) {
|
|
if (nodes[i].group != group)
|
|
continue;
|
|
|
|
if (nodes[i].selected)
|
|
return nodes[i];
|
|
}
|
|
|
|
return false;
|
|
};
|
|
|
|
/**
|
|
* Selects an element within a radio group.
|
|
* @param {String} group The name of the group.
|
|
* @param {String} value The value of the item to select.
|
|
*/
|
|
this.select = function(group, value){
|
|
var nodes = this.childNodes;
|
|
var i, l = nodes.length;
|
|
for (i = 0; i < l; i++) {
|
|
if (nodes[i].group != group)
|
|
continue;
|
|
|
|
if (value && (nodes[i].value == value || !nodes[i].value && nodes[i].caption == value))
|
|
nodes[i].setProperty("selected", true, false, true);
|
|
//nodes[i].$handlePropSet("selected", true);
|
|
else if (nodes[i].selected)
|
|
nodes[i].setProperty("selected", false, false, true);
|
|
//nodes[i].$handlePropSet("selected", false);
|
|
}
|
|
};
|
|
|
|
// *** Events *** //
|
|
|
|
|
|
this.addEventListener("prop.visible", function() {
|
|
this.$initChildren();
|
|
}, true);
|
|
|
|
this.addEventListener("keydown", function(e){
|
|
var node, key = e.keyCode;
|
|
//var ctrlKey = e.ctrlKey;
|
|
//var shiftKey = e.shiftKey;
|
|
|
|
switch (key) {
|
|
case 13:
|
|
if (!this.$selected)
|
|
return;
|
|
|
|
node = this.$selected;
|
|
node.$down();
|
|
node.$up();
|
|
node.$click();
|
|
break;
|
|
case 27:
|
|
this.hide();
|
|
break;
|
|
case 38:
|
|
//UP
|
|
node = this.$selected && this.$selected.previousSibling
|
|
|| this.lastChild;
|
|
|
|
if (node && node.localName == "divider")
|
|
node = node.previousSibling;
|
|
|
|
if (!node)
|
|
return;
|
|
|
|
if (this.$selected)
|
|
apf.setStyleClass(this.$selected.$ext, "", ["hover"]);
|
|
|
|
apf.setStyleClass(node.$ext, "hover");
|
|
this.$selected = node;
|
|
break;
|
|
case 40:
|
|
//DOWN
|
|
node = this.$selected && this.$selected.nextSibling
|
|
|| this.firstChild;
|
|
|
|
if (node && node.localName == "divider")
|
|
node = node.nextSibling;
|
|
|
|
if (!node)
|
|
return;
|
|
|
|
if (this.$selected)
|
|
apf.setStyleClass(this.$selected.$ext, "", ["hover"]);
|
|
|
|
apf.setStyleClass(node.$ext, "hover");
|
|
this.$selected = node;
|
|
break;
|
|
case 37:
|
|
//LEFT
|
|
//if (this.$selected && this.$selected.submenu)
|
|
//this.$selected.$submenu(true, true);
|
|
|
|
if (!this.opener)
|
|
return;
|
|
|
|
if (this.opener.localName == "button") {
|
|
node = this.opener.previousSibling;
|
|
while(node && !node.submenu)
|
|
node = node.previousSibling;
|
|
|
|
if (node) {
|
|
node.dispatchEvent("mouseover");
|
|
|
|
var btnMenu = node.parentNode.menuIsPressed;
|
|
if (btnMenu) {
|
|
self[btnMenu.submenu].dispatchEvent("keydown", {
|
|
keyCode : 40
|
|
});
|
|
}
|
|
}
|
|
}
|
|
else if (this.opener.parentNode.localName == "menu") {
|
|
//@todo Ahum bad abstraction boundary
|
|
var op = this.opener;
|
|
this.hide();
|
|
apf.setStyleClass(op.$ext, "hover");
|
|
op.parentNode.$showingSubMenu = null;
|
|
}
|
|
|
|
break;
|
|
case 39:
|
|
//RIGHT
|
|
if (this.$selected && this.$selected.submenu) {
|
|
this.$selected.$submenu(null, true);
|
|
this.$showingSubMenu.dispatchEvent("keydown", {
|
|
keyCode : 40
|
|
});
|
|
|
|
return;
|
|
}
|
|
|
|
if (this.opener) {
|
|
var op = this.opener;
|
|
while (op && op.parentNode && op.parentNode.localName == "menu")
|
|
op = op.parentNode.opener;
|
|
|
|
if (op && op.localName == "button") {
|
|
node = op.nextSibling;
|
|
while(node && !node.submenu)
|
|
node = node.nextSibling;
|
|
|
|
if (node) {
|
|
node.dispatchEvent("mouseover");
|
|
|
|
var btnMenu = node.parentNode.menuIsPressed;
|
|
if (btnMenu) {
|
|
(self[btnMenu.submenu] || btnMenu.submenu).dispatchEvent("keydown", {
|
|
keyCode : 40
|
|
});
|
|
}
|
|
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!this.$selected) {
|
|
arguments.callee.call(this, {
|
|
keyCode : 40
|
|
});
|
|
}
|
|
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
|
|
return false;
|
|
}, true);
|
|
|
|
|
|
//Hide menu when it looses focus or when the popup hides itself
|
|
function forceHide(e){
|
|
if (this.$showingSubMenu || this.pinned
|
|
|| apf.isChildOf(e.fromElement, e.toElement)
|
|
|| apf.isChildOf(e.toElement, e.fromElement)
|
|
|| apf.isChildOf(this, e.toElement)
|
|
|| (e.name !== "popuphide" && !e.toElement)
|
|
|| e.toElement && apf.popup.cache[e.toElement.$uniqueId])
|
|
return;
|
|
|
|
if (this.$hideTree != -1) {
|
|
this.$hideTree = true;
|
|
this.hide();
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
this.addEventListener("focus", function(e){
|
|
apf.popup.last = this.$uniqueId;
|
|
|
|
if (!apf.menu.lastFocussed)
|
|
apf.menu.lastFocussed = apf.menu.lastFocussedItem;
|
|
});
|
|
|
|
this.addEventListener("blur", forceHide);
|
|
this.addEventListener("popuphide", forceHide);
|
|
|
|
// *** Init *** //
|
|
|
|
this.$draw = function(){
|
|
this.$pHtmlNode = document.body;
|
|
|
|
//Build Main Skin
|
|
this.$ext = this.$getExternal();
|
|
this.oOverlay = this.$getLayoutNode("main", "overlay", this.$ext);
|
|
|
|
apf.popup.setContent(this.$uniqueId, this.$ext, "", null, null);
|
|
|
|
// workaround for a chrome bug where clicking on shadow clciks on contents of overflown element
|
|
this.$ext.addEventListener("mouseup", function(e) {
|
|
var rect = this.getBoundingClientRect();
|
|
if (e.clientY > rect.bottom && rect.height) {
|
|
e.stopPropagation();
|
|
e.preventDefault();
|
|
}
|
|
}, true);
|
|
};
|
|
|
|
this.$loadAml = function(x){
|
|
this.$int = this.$getLayoutNode("main", "container", this.$ext);
|
|
};
|
|
|
|
this.$destroy = function(){
|
|
apf.popup.removeContent(this.$uniqueId);
|
|
};
|
|
|
|
this.$initChildren = function() {
|
|
this.childNodes.forEach(function(amlNode) {
|
|
if (!amlNode.$amlLoaded)
|
|
amlNode.dispatchEvent("DOMNodeInsertedIntoDocument");
|
|
});
|
|
};
|
|
var insertBefore = this.insertBefore;
|
|
this.insertBefore = function(node, beforeNode) {
|
|
// if menu is visible call apf insertBefore since it rearranges html nodes
|
|
// otherwise use fake and much faster method
|
|
if (this.visible)
|
|
return insertBefore.call(this, node, beforeNode);
|
|
if (beforeNode == node)
|
|
return node;
|
|
if (!this || this == node)
|
|
throw new Error("Invalid insertBefore call");
|
|
|
|
var children = this.childNodes;
|
|
// if (node.parentNode == this)
|
|
// children[index]
|
|
if (node.parentNode)
|
|
node.removeNode();
|
|
|
|
var index = beforeNode ? children.indexOf(beforeNode) : children.length;
|
|
node.parentNode = this;
|
|
|
|
if (beforeNode) {
|
|
children.splice(index, 0, node);
|
|
} else {
|
|
children.push(node);
|
|
}
|
|
|
|
node.previousSibling = children[index - 1];
|
|
node.nextSibling = children[index + 1];
|
|
if (node.previousSibling)
|
|
node.previousSibling.nextSibling = node;
|
|
else
|
|
this.firstChild = children[0];
|
|
|
|
if (node.nextSibling)
|
|
node.nextSibling.previousSibling = node;
|
|
else
|
|
this.lastChild = children[this.childNodes.length - 1];
|
|
|
|
return node;
|
|
};
|
|
|
|
this.appendChild = function(node) {
|
|
return this.insertBefore(node);
|
|
};
|
|
}).call(apf.menu.prototype = new apf.Presentation());
|
|
|
|
apf.addEventListener("movefocus", function(e){
|
|
var next = e.toElement;
|
|
if (next && next.localName != "menu")
|
|
apf.menu.lastFocussedItem = next;
|
|
});
|
|
|
|
apf.aml.setElement("menu", apf.menu);
|
|
|
|
|
|
|
|
/**
|
|
* Element displaying a divider. For use in toolbars, menus, and such.
|
|
* @class apf.divider
|
|
* @define divider
|
|
* @inherits apf.Presentation
|
|
*/
|
|
apf.divider = function(struct, tagName){
|
|
this.$init(tagName || "divider", apf.NODE_VISIBLE, struct);
|
|
};
|
|
|
|
(function() {
|
|
this.$focussable = false;
|
|
|
|
this.minwidth = 0;
|
|
this.minheight = 0;
|
|
|
|
this.implement(apf.ChildValue);
|
|
this.$childProperty = "caption";
|
|
|
|
//@todo apf3.0 fix this
|
|
this.addEventListener("AMLReparent", function(beforeNode, pNode, withinParent){
|
|
if (!this.$amlLoaded)
|
|
return;
|
|
|
|
if (!withinParent && this.skinName != pNode.skinName) {
|
|
//@todo for now, assuming dom garbage collection doesn't leak
|
|
this.loadAml();
|
|
}
|
|
});
|
|
|
|
/**
|
|
* @attribute {String} caption the text displayed in the area defined by this
|
|
* element.
|
|
*/
|
|
this.$supportedProperties.push("caption", "value", "for", "textalign");
|
|
this.$propHandlers["caption"] = function(value){
|
|
if (this.$caption) {
|
|
this.$setStyleClass(this.$ext, this.$baseCSSname + "Caption");
|
|
this.$caption.innerHTML = value;
|
|
}
|
|
else {
|
|
this.$setStyleClass(this.$ext, "", [this.$baseCSSname + "Caption"]);
|
|
}
|
|
};
|
|
|
|
this.$canLeechSkin = true;
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
this.$draw = function() {
|
|
if (this.parentNode.isPaged && this.parentNode.$buttons)
|
|
this.$pHtmlNode = this.parentNode.$buttons;
|
|
|
|
if (this.$isLeechingSkin) {
|
|
this.$ext = apf.insertHtmlNode(
|
|
this.parentNode.$getLayoutNode("divider"), this.$pHtmlNode);
|
|
}
|
|
else {
|
|
this.$ext = this.$getExternal("main");
|
|
this.$caption = this.$getLayoutNode("main", "caption", this.$ext);
|
|
}
|
|
};
|
|
}).call(apf.divider.prototype = new apf.Presentation());
|
|
|
|
apf.aml.setElement("divider", apf.divider);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
* Represents an item in a menu, displaying a clickable area.
|
|
*
|
|
* #### Example
|
|
*
|
|
* ```xml, demo
|
|
* <a:application xmlns:a="http://ajax.org/2005/aml">
|
|
* <!-- startcontent -->
|
|
* <a:menu id="menu1">
|
|
* <a:item>Tutorials</a:item>
|
|
* <a:item>Contact</a:item>
|
|
* </a:menu>
|
|
* <a:toolbar>
|
|
* <a:menubar>
|
|
* <a:button submenu="menu1">File</a:button>
|
|
* </a:menubar>
|
|
* </a:toolbar>
|
|
* <!-- endcontent -->
|
|
* </a:application>
|
|
* ```
|
|
*
|
|
* @class apf.item
|
|
* @selection
|
|
* @define item
|
|
* @inherits apf.Presentation
|
|
*
|
|
*/
|
|
/**
|
|
* @event click Fires when a user presses the mouse button while over this element.
|
|
* @param {Object} e The standard event object. It contains the following properties:
|
|
* - xmlContext ([[XMLElement]]): The XML data node that was selected in the opener at the time of showing the context menu.
|
|
* - opener ([[apf.AmlElement]]): The element that was clicked upon when showing the context menu.
|
|
*/
|
|
apf.item = function(struct, tagName){
|
|
this.$init(tagName || "item", apf.NODE_VISIBLE, struct);
|
|
};
|
|
|
|
(function(){
|
|
this.$focussable = false;
|
|
this.$childProperty = "caption";
|
|
this.$canLeechSkin = "item";
|
|
|
|
this.checked = false;
|
|
this.selected = false;
|
|
|
|
this.implement(apf.ChildValue);
|
|
|
|
// *** Properties and Attributes *** //
|
|
|
|
//1 = force no bind rule, 2 = force bind rule
|
|
this.$attrExcludePropBind = apf.extend({
|
|
"match" : 1
|
|
}, this.$attrExcludePropBind);
|
|
|
|
this.$booleanProperties["checked"] = true;
|
|
this.$booleanProperties["selected"] = true;
|
|
|
|
this.$supportedProperties.push("submenu", "value", "match", "group", "icon",
|
|
"checked", "selected", "disabled", "caption",
|
|
"type", "values");
|
|
|
|
/**
|
|
* @attribute {String} submenu Sets or gets the id of the menu that is shown
|
|
* when the user hovers over this menu item.
|
|
*
|
|
* #### Example
|
|
*
|
|
* ```xml
|
|
* <a:menu id="msub">
|
|
* <a:item icon="tbicons:12">test</a:item>
|
|
* <a:item icon="tbicons:14">test2</a:item>
|
|
* </a:menu>
|
|
*
|
|
* <a:menu id="mmain">
|
|
* <a:item submenu="msub">Sub menu</a:item>
|
|
* </a:menu>
|
|
*
|
|
* <a:toolbar>
|
|
* <a:menubar>
|
|
* <a:button submenu="mmain">File</a:button>
|
|
* </a:menubar>
|
|
* </a:toolbar>
|
|
* ```
|
|
*/
|
|
this.$propHandlers["submenu"] = function(value){
|
|
apf.setStyleClass(this.$ext, "submenu");
|
|
}
|
|
|
|
/**
|
|
* @attribute {String} value Sets or gets the value of this element.
|
|
*/
|
|
|
|
/**
|
|
* @attribute {String} [select] Sets or gets the XPath statement which works on the
|
|
* XML context of the parent menu element to determine whether this
|
|
* item is shown.
|
|
*
|
|
* #### Example
|
|
*
|
|
* This example shows a list:
|
|
*
|
|
* ```xml
|
|
* <a:menu id="mnuTest">
|
|
* <a:item match="[person]" method="send">Send an E-mail</a:item>
|
|
* <a:item match="[phone]" method="call">Call Number</a:item>
|
|
* <a:divider />
|
|
* <a:item match="[phone]" method="remove">Remove</a:item>
|
|
* <a:divider />
|
|
* <a:item match="[person|phone]" method="viewpictures">View Pictures</a:item>
|
|
* </a:menu>
|
|
*
|
|
* <a:menu id="mnuXY">
|
|
* <a:item method="reboot">Reboot</a:item>
|
|
* </a:menu>
|
|
*
|
|
* <a:text contextmenu="mnuXY" width="200" height="200">
|
|
* Please right-click on this plane
|
|
* </a:text>
|
|
*
|
|
* <a:list id="lstTest" allow-deselect="true" width="200" height="200">
|
|
* <a:each match="[person|phone|computer]">
|
|
* <a:caption match="[@caption]" />
|
|
* <a:icon match="[person]" value="user.png" />
|
|
* <a:icon match="[phone]" value="phone.png" />
|
|
* <a:icon match="[computer]" value="computer.png" />
|
|
* </a:each>
|
|
* <a:model>
|
|
* <data>
|
|
* <person caption="Ruben Daniels" />
|
|
* <person caption="Rik Arends" />
|
|
* <phone caption="+31 555 544486" />
|
|
* <phone caption="+1 555 2392" />
|
|
* <computer caption="Mail Server" />
|
|
* <computer caption="File Server" />
|
|
* </data>
|
|
* </a:model>
|
|
* <a:contextmenu menu="mnuXY" match="[computer]" />
|
|
* <a:contextmenu menu="mnuTest" />
|
|
* </a:list>
|
|
* ```
|
|
*/
|
|
this.$propHandlers["select"] = function(value){
|
|
this.select = value
|
|
? "self::" + value.split("|").join("|self::")
|
|
: value;
|
|
}
|
|
|
|
/**
|
|
* @attribute {String} [group] Sets or gets the name of the group this item belongs
|
|
* to.
|
|
*
|
|
* #### Example
|
|
*
|
|
* ```xml
|
|
* <a:menu>
|
|
* <a:item type="radio" group="example">item 1</a:item>
|
|
* <a:item type="radio" group="example">item 2</a:item>
|
|
* <a:item type="radio" group="example">item 3</a:item>
|
|
* <a:item type="radio" group="example">item 4</a:item>
|
|
* </a:menu>
|
|
* ```
|
|
*/
|
|
this.$propHandlers["group"] = function(value){
|
|
if (this.$group && this.$group.$removeRadio)
|
|
this.$group.$removeRadio(this);
|
|
|
|
if (!value) {
|
|
this.$group = null;
|
|
return;
|
|
}
|
|
|
|
var group = typeof value == "string"
|
|
?
|
|
|
|
apf.nameserver.get("group", value)
|
|
|
|
: value;
|
|
if (!group) {
|
|
|
|
group = apf.nameserver.register("group", value,
|
|
new apf.$group());
|
|
group.setAttribute("id", value);
|
|
group.dispatchEvent("DOMNodeInsertedIntoDocument");
|
|
group.parentNode = this;
|
|
|
|
}
|
|
this.$group = group;
|
|
|
|
this.$group.$addRadio(this);
|
|
};
|
|
|
|
|
|
/**
|
|
* @attribute {String} hotkey Sets or gets the key combination a user can press
|
|
* to active the function of this element. Use any combination of
|
|
* [[keys: Ctrl]], [[keys: Shift]], [[keys: Alt]], [[keys: F1]]-[[keys: F12]], and alphanumerical characters. Use a
|
|
* space, a minus or plus sign as a seperator.
|
|
*
|
|
* #### Example
|
|
*
|
|
* ```xml
|
|
* <a:item hotkey="Ctrl+Q">Quit</a:item>
|
|
* ```
|
|
*/
|
|
this.$propHandlers["hotkey"] = function(value){
|
|
if (!this.$amlLoaded) {
|
|
var _self = this;
|
|
this.addEventListener("DOMNodeInsertedIntoDocument", function(e){
|
|
if (_self.$hotkey && _self.hotkey)
|
|
apf.setNodeValue(this.$hotkey, apf.isMac
|
|
? apf.hotkeys.toMacNotation(_self.hotkey) : _self.hotkey);
|
|
});
|
|
}
|
|
else if (this.$hotkey)
|
|
apf.setNodeValue(this.$hotkey, apf.isMac ? apf.hotkeys.toMacNotation(value) : value);
|
|
|
|
if (this.$lastHotkey) {
|
|
apf.hotkeys.remove(this.$lastHotkey[0], this.$lastHotkey[1]);
|
|
delete this.$lastHotkey[0];
|
|
}
|
|
|
|
if (value) {
|
|
this.$lastHotkey = [value];
|
|
var _self = this;
|
|
apf.hotkeys.register(value, this.$lastHotkey[1] = function(){
|
|
if (_self.disabled || !_self.visible)
|
|
return;
|
|
|
|
//hmm not very scalable...
|
|
if (_self.parentNode) {
|
|
var buttons = apf.document.getElementsByTagNameNS(apf.ns.aml, "button");
|
|
for (var i = 0; i < buttons.length; i++) {
|
|
if (buttons[i].submenu == _self.parentNode.name) {
|
|
var btn = buttons[i];
|
|
btn.$setState("Over", {});
|
|
|
|
$setTimeout(function(){
|
|
btn.$setState("Out", {});
|
|
}, 200);
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
_self.$down();
|
|
_self.$up();
|
|
_self.$click();
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @attribute {String} icon Sets or gets the URL of the image used as an icon or
|
|
* a reference to an iconmap.
|
|
*/
|
|
this.$propHandlers["icon"] = function(value){
|
|
if (this.$icon)
|
|
apf.skins.setIcon(this.$icon, value, this.parentNode.iconPath);
|
|
}
|
|
|
|
/**
|
|
* @attribute {String} caption Sets or gets the text displayed on the item.
|
|
*/
|
|
this.$propHandlers["caption"] = function(value){
|
|
if (this.$caption)
|
|
apf.setNodeValue(this.$caption, value);
|
|
}
|
|
|
|
/**
|
|
* @attribute {String} type Sets or gets the function of this item.
|
|
*
|
|
* Possible values include:
|
|
* - `"item"`
|
|
* - `"check"`
|
|
* - `"radio"`
|
|
*/
|
|
this.$propHandlers["type"] = function(value){
|
|
apf.setStyleClass(this.$ext, value, ["item", "check", "radio"]);
|
|
}
|
|
|
|
this.$propHandlers["values"] = function(value){
|
|
this.$values = typeof value == "string"
|
|
? value.split("\|")
|
|
: (value || [1, 0]);
|
|
|
|
this.$propHandlers["value"].call(this, this.value);
|
|
};
|
|
|
|
this.$propHandlers["value"] = function(value){
|
|
if (this.type != "check")
|
|
return;
|
|
|
|
value = (typeof value == "string" ? value.trim() : value);
|
|
|
|
var checked;
|
|
if (this.$values) {
|
|
checked = (typeof value != "undefined" && value !== null
|
|
&& value.toString() == this.$values[0].toString());
|
|
}
|
|
else {
|
|
checked = apf.isTrue(value);
|
|
}
|
|
|
|
if (checked != this.checked) {
|
|
this.checked = checked;
|
|
this.$propHandlers.checked.call(this, checked);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @attribute {Boolean} checked Sets or gets whether the item is checked.
|
|
*/
|
|
this.$propHandlers["checked"] = function(value){
|
|
if (this.type != "check")
|
|
return;
|
|
|
|
if (apf.isTrue(value))
|
|
apf.setStyleClass(this.$ext, "checked");
|
|
else
|
|
apf.setStyleClass(this.$ext, "", ["checked"]);
|
|
|
|
if (!this.$values) {
|
|
if (this.getAttribute("values"))
|
|
this.$propHandlers["values"].call(this, this.getAttribute("values"));
|
|
else
|
|
this.$values = [true, false];
|
|
}
|
|
|
|
if(this.$values && this.$values[value ? 0 : 1] != this.value)
|
|
this.setProperty("value", this.$values ? this.$values[value ? 0 : 1] : true);
|
|
}
|
|
|
|
this.select = function(){
|
|
this.parentNode.select(this.group, this.value || this.caption);
|
|
}
|
|
|
|
this.check = function(){
|
|
this.setProperty("value", this.$values
|
|
? this.$values[0]
|
|
: true);
|
|
}
|
|
this.uncheck = function(){
|
|
this.setProperty("value", this.$values
|
|
? this.$values[1]
|
|
: false);
|
|
}
|
|
|
|
this.$check = function(){
|
|
apf.setStyleClass(this.$ext, "selected");
|
|
}
|
|
|
|
this.$uncheck = function(){
|
|
apf.setStyleClass(this.$ext, "", ["selected"]);
|
|
}
|
|
|
|
/**
|
|
* @attribute {Boolean} selected Sets or gets whether the item is selected.
|
|
*/
|
|
this.$propHandlers["selected"] = function(value){
|
|
if (this.type != "radio")
|
|
return;
|
|
|
|
|
|
if (apf.isTrue(value)) {
|
|
if (this.$group)
|
|
this.$group.setProperty("value", this.value);
|
|
this.$check();
|
|
}
|
|
else
|
|
this.$uncheck();
|
|
}
|
|
|
|
/**
|
|
* @attribute {Boolean} disabled Sets or gets whether the item is active.
|
|
*/
|
|
this.$propHandlers["disabled"] = function(value){
|
|
if (apf.isTrue(value) || value == -1)
|
|
apf.setStyleClass(this.$ext, "disabled");
|
|
else
|
|
apf.setStyleClass(this.$ext, "", ["disabled"]);
|
|
}
|
|
|
|
// *** Dom Hooks *** //
|
|
|
|
//@todo apf3.0
|
|
this.addEventListener("AMLReparent", function(beforeNode, pNode, withinParent){
|
|
if (!this.$amlLoaded)
|
|
return;
|
|
|
|
if (!withinParent && this.skinName != pNode.skinName) {
|
|
//@todo for now, assuming dom garbage collection doesn't leak
|
|
this.loadAml();
|
|
}
|
|
});
|
|
|
|
// *** Events *** //
|
|
|
|
this.$down = function(){
|
|
|
|
};
|
|
|
|
this.$up = function(){
|
|
|
|
|
|
if (this.type == "radio")
|
|
this.parentNode.select(this.group, this.value || this.caption);
|
|
|
|
else if (this.type == "check") {
|
|
this.setProperty("checked", !this.checked);
|
|
//this.$handlePropSet("checked", !this.checked);
|
|
}
|
|
|
|
if (this.submenu) {
|
|
this.$over(null, true);
|
|
return;
|
|
}
|
|
|
|
this.parentNode.$hideTree = true;
|
|
|
|
//@todo This statement makes the menu loose focus.
|
|
if (!this.parentNode.sticky)
|
|
this.parentNode.hide();//true not focus?/
|
|
|
|
this.parentNode.dispatchEvent("itemclick", {
|
|
value : this.value || this.caption,
|
|
relatedNode : this,
|
|
checked : this.checked,
|
|
selected : this.selected
|
|
});
|
|
|
|
//@todo Anim effect here?
|
|
|
|
this.dispatchEvent("click", {
|
|
xmlContext : this.parentNode.xmlReference,
|
|
opener : this.parentNode.opener
|
|
});
|
|
|
|
|
|
};
|
|
|
|
this.$click = function(){
|
|
|
|
};
|
|
|
|
var timer;
|
|
this.$out = function(e){
|
|
if (apf.isChildOf(this.$ext, e.toElement || e.explicitOriginalTarget)
|
|
|| apf.isChildOf(this.$ext, e.srcElement || e.target)) //@todo test FF
|
|
return;
|
|
|
|
clearTimeout(timer);
|
|
if (!this.submenu || this.$submenu(true)) {
|
|
apf.setStyleClass(this.$ext, "", ['hover']);
|
|
|
|
var sel = this.parentNode.$selected;
|
|
if (sel && sel != this)
|
|
apf.setStyleClass(sel.$ext, "", ["hover"]);
|
|
|
|
this.parentNode.$selected = null;
|
|
}
|
|
|
|
|
|
};
|
|
|
|
this.$over = function(e, force){
|
|
function selectItem(el) {
|
|
if (el.parentNode.$selected == el && e)
|
|
return false;
|
|
|
|
if (el.parentNode.$selected)
|
|
apf.setStyleClass(el.parentNode.$selected.$ext, "", ["hover"]);
|
|
|
|
apf.setStyleClass(el.$ext, "hover");
|
|
el.parentNode.$selected = el;
|
|
return true;
|
|
}
|
|
|
|
if (!selectItem(this))
|
|
return;
|
|
|
|
var opener = this.parentNode.opener;
|
|
if (opener && opener.parentNode.$showingSubMenu)
|
|
selectItem(opener);
|
|
|
|
if (!force && (apf.isChildOf(this.$ext, e.toElement || e.explicitOriginalTarget)
|
|
|| apf.isChildOf(this.$ext, e.fromElement || e.target))) //@todo test FF
|
|
return;
|
|
|
|
var _self = this, ps = this.parentNode.$showingSubMenu;
|
|
if (ps && ps.name && ps.name == this.submenu) {
|
|
this.parentNode.$selected = null;
|
|
this.parentNode.$showingSubMenu = null;
|
|
_self.$submenu();
|
|
return;
|
|
}
|
|
|
|
clearTimeout(timer);
|
|
|
|
|
|
function submenu(){
|
|
if (ps && ps.visible) {
|
|
ps.hide();
|
|
|
|
if (_self.parentNode.$showingSubMenu == ps)
|
|
_self.parentNode.$showingSubMenu = null;
|
|
}
|
|
if (_self.submenu && (!_self.parentNode.opener
|
|
|| _self.parentNode.opener.visible))
|
|
_self.$submenu();
|
|
}
|
|
|
|
if (force)
|
|
submenu();
|
|
else {
|
|
timer = $setTimeout(function(){
|
|
submenu();
|
|
timer = null;
|
|
}, 210);
|
|
}
|
|
};
|
|
|
|
this.$submenu = function(hide, force){
|
|
if (!this.submenu)
|
|
return true;
|
|
|
|
var menu = self[this.submenu] || this.submenu;
|
|
if (!menu) {
|
|
|
|
|
|
return;
|
|
}
|
|
|
|
if (!hide) {
|
|
//if (this.parentNode.showingSubMenu == this.submenu)
|
|
//return;
|
|
|
|
this.parentNode.$showingSubMenu = menu;
|
|
|
|
var pos = apf.getAbsolutePosition(this.$ext, this.parentNode.$ext.offsetParent);
|
|
menu.display(pos[0] + this.$ext.offsetWidth - 3,
|
|
pos[1] + 3, true, this,
|
|
this.parentNode.xmlReference, this.parentNode.$uniqueId);
|
|
menu.setAttribute("zindex", (this.parentNode.zindex || this.parentNode.$ext.style.zIndex || 1) + 1);
|
|
}
|
|
else {
|
|
if (menu.visible && !force) {
|
|
return false;
|
|
}
|
|
|
|
if (this.parentNode.$showingSubMenu == menu)
|
|
this.parentNode.$showingSubMenu = null;
|
|
|
|
apf.setStyleClass(this.$ext, '', ['hover']);
|
|
menu.hide();
|
|
return true;
|
|
}
|
|
};
|
|
|
|
// *** Init *** //
|
|
|
|
this.$draw = function(isSkinSwitch){
|
|
var p = this.parentNode;
|
|
while (p.$canLeechSkin == "item")
|
|
p = p.parentNode;
|
|
|
|
if (p.hasFeature(apf.__MULTISELECT__)) {
|
|
var _self = this;
|
|
|
|
//@todo DOMNodeInserted should reset this
|
|
//@todo DOMNodeRemoved should reset this
|
|
if (!this.$hasSetSkinListener) {
|
|
var f;
|
|
this.parentNode.addEventListener("$skinchange", f = function(){
|
|
if (_self.$amlDestroyed) //@todo apf3.x
|
|
return;
|
|
|
|
if (_self.$ext.parentNode)
|
|
this.$deInitNode(_self, _self.$ext);
|
|
|
|
var oInt = p == _self.parentNode ? p.$container : _self.parentNode.$container;
|
|
var node = oInt.lastChild;//@todo this should be more generic
|
|
p.$add(_self, _self.getAttribute(apf.xmldb.xmlIdTag) + "|" + this.$uniqueId,
|
|
_self.parentNode, oInt != p.$container && oInt, null);
|
|
p.$fill();
|
|
|
|
if (p.$isTreeArch) {
|
|
_self.$container = p.$getLayoutNode("item", "container",
|
|
_self.$ext = node && node.nextSibling || oInt.firstChild);//@todo this should be more generic
|
|
}
|
|
else _self.$ext = node && node.nextSibling || oInt.firstChild;
|
|
|
|
var ns = _self;
|
|
while((ns = ns.nextSibling) && ns.nodeType != 1);
|
|
|
|
if (!ns || ns.$canLeechSkin != "item")
|
|
p.dispatchEvent("afterload");
|
|
});
|
|
this.addEventListener("DOMNodeRemoved", function(e){
|
|
if (e.currentTarget == this)
|
|
this.parentNode.removeEventListener("$skinchange", f);
|
|
});
|
|
|
|
this.$hasSetSkinListener = true;
|
|
}
|
|
|
|
if (!p.$itemInited) {
|
|
p.canrename = false; //@todo fix rename
|
|
p.$removeClearMessage(); //@todo this should be more generic
|
|
p.$itemInited = [p.getTraverseNodes, p.getFirstTraverseNode, p.getTraverseParent];
|
|
|
|
p.getTraverseNodes = function(xmlNode){
|
|
return (xmlNode || p).getElementsByTagNameNS(apf.ns.apf, "item");
|
|
}
|
|
p.getFirstTraverseNode = function(xmlNode){
|
|
return (xmlNode || p).getElementsByTagNameNS(apf.ns.apf, "item")[0];
|
|
}
|
|
p.getTraverseParent = function(xmlNode){
|
|
return xmlNode && xmlNode.parentNode;
|
|
}
|
|
p.each = (this.prefix ? this.prefix + ":" : "") + "item";
|
|
|
|
//@todo this is all an ugly hack (copied to baselist.js line 868)
|
|
p.$preventDataLoad = true;//@todo apf3.0 add remove for this
|
|
|
|
p.$initingModel = true;
|
|
p.$setDynamicProperty("icon", "[@icon]");
|
|
p.$setDynamicProperty("image", "[@image]");
|
|
p.$setDynamicProperty("caption", "[label/text()|@caption|text()]");
|
|
p.$setDynamicProperty("eachvalue", "[value/text()|@value|text()]");
|
|
p.$canLoadDataAttr = false;
|
|
|
|
if (!p.xmlRoot)
|
|
p.xmlRoot = p;
|
|
}
|
|
|
|
this.$loadAml = function(){
|
|
//hack
|
|
if (!this.getAttribute("caption"))
|
|
this.setAttribute("caption", this.caption);
|
|
|
|
var oInt = p == this.parentNode ? p.$container : this.parentNode.$container;
|
|
var node = oInt.lastChild;//@todo this should be more generic
|
|
if (!p.documentId)
|
|
p.documentId = apf.xmldb.getXmlDocId(this);
|
|
p.$add(this, apf.xmldb.nodeConnect(p.documentId, this, null, p),
|
|
this.parentNode, oInt != p.$container && oInt, null);
|
|
p.$fill();
|
|
|
|
if (p.$isTreeArch) {
|
|
this.$container = p.$getLayoutNode("item", "container",
|
|
this.$ext = node && node.nextSibling || oInt.firstChild);//@todo this should be more generic
|
|
}
|
|
else this.$ext = node && node.nextSibling || oInt.firstChild;
|
|
|
|
var ns = this;
|
|
while((ns = ns.nextSibling) && ns.nodeType != 1);
|
|
|
|
if (!ns || ns.$canLeechSkin != "item") {
|
|
p.dispatchEvent("afterload");
|
|
if (p.autoselect)
|
|
p.$selectDefault(this.parentNode);
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
this.$ext = this.$getExternal(this.$isLeechingSkin
|
|
? "item" //this.type
|
|
: "main", null, function($ext){
|
|
var o = 'var o = apf.lookup(' + this.$uniqueId + '); if (!o || o.disabled) return; o';
|
|
$ext.setAttribute("onmouseup", o + '.$up(event)');
|
|
$ext.setAttribute("onmousemove", o + '.$over(event)');
|
|
$ext.setAttribute("onmouseout", o + '.$out(event)');
|
|
$ext.setAttribute("onmousedown", o + '.$down()');
|
|
$ext.setAttribute("onclick", o + '.$click()');
|
|
});
|
|
// getExternal always appends to the end which is wrong when drawing is delayed
|
|
var next = this.nextSibling && this.nextSibling.$ext;
|
|
if (next && next.parentNode === this.$ext.parentNode && this.$ext.nextSibling !== next) {
|
|
next.parentNode.insertBefore(this.$ext, next);
|
|
}
|
|
|
|
var _self = this;
|
|
apf.addListener(this.$ext, "mouseover", function(e) {
|
|
if (!_self.disabled)
|
|
_self.dispatchEvent("mouseover", {htmlEvent: e});
|
|
});
|
|
|
|
apf.addListener(this.$ext, "mouseout", function(e) {
|
|
if (!_self.disabled)
|
|
_self.dispatchEvent("mouseout", {htmlEvent: e});
|
|
});
|
|
|
|
/*p.$getNewContext("item");
|
|
var elItem = p.$getLayoutNode("item");*/
|
|
|
|
//@todo if not elItem try using own skin
|
|
|
|
//this.$ext = apf.insertHtmlNode(elItem, this.parentNode.$container);
|
|
this.$caption = this.$getLayoutNode("item", "caption", this.$ext)
|
|
this.$icon = this.$getLayoutNode("item", "icon", this.$ext);
|
|
this.$hotkey = this.$getLayoutNode("item", "hotkey", this.$ext);
|
|
|
|
if (!isSkinSwitch && this.nextSibling && this.nextSibling.$ext
|
|
&& this.nextSibling.$ext.parentNode == this.$ext.parentNode) {
|
|
this.$ext.parentNode.insertBefore(this.$ext, this.nextSibling.$ext);
|
|
}
|
|
};
|
|
|
|
/*
|
|
* @private
|
|
*/
|
|
this.addEventListener("DOMNodeInsertedIntoDocument", function(e){
|
|
//var x = this.$aml;
|
|
|
|
//this.skinName = this.parentNode.skinName;
|
|
var isSkinSwitch = this.$ext ? true : false;
|
|
if (isSkinSwitch) {
|
|
if (typeof this.checked !== "undefined")
|
|
this.$handlePropSet("checked", this.checked);
|
|
else if (typeof this.selected !== "undefined")
|
|
this.$handlePropSet("selected", this.selected);
|
|
|
|
if (this.disabled)
|
|
this.$handlePropSet("disabled", this.disabled);
|
|
|
|
if (this.caption)
|
|
this.$handlePropSet("caption", this.caption);
|
|
}
|
|
});
|
|
}).call(apf.item.prototype = new apf.Presentation());
|
|
|
|
//apf.aml.setElement("radio", apf.radio);
|
|
//apf.aml.setElement("check", apf.check);
|
|
apf.aml.setElement("item", apf.item);
|
|
|
|
};
|
|
|
|
}); |