if (!dojo._hasResource["dojox.widget.FisheyeList"]) { // _hasResource checks
// added by build. Do
// not use _hasResource
// directly in your
// code.
dojo._hasResource["dojox.widget.FisheyeList"] = true;
dojo.provide("dojox.widget.FisheyeList");
dojo.require("dijit._Widget");
dojo.require("dijit._Templated");
dojo.require("dijit._Container");
dojo.declare("dojox.widget.FisheyeList", [dijit._Widget, dijit._Templated,
dijit._Container], {
// summary:
// Menu similar to the fish eye menu on the Mac OS
// example:
// |
// |
// |
// |
// | ...
// |
//
constructor : function() {
//
// TODO
// fix really long labels in vertical mode
//
this.pos = {
'x' : -1,
'y' : -1
}; // current cursor position, relative to the grid
// for conservative trigger mode, when triggered, timerScale
// is gradually increased from 0 to 1
this.timerScale = 1.0;
},
EDGE : {
CENTER : 0,
LEFT : 1,
RIGHT : 2,
TOP : 3,
BOTTOM : 4
},
templateString : '',
snarfChildDomOutput : true,
// itemWidth: Integer
// width of menu item (in pixels) in it's dormant state (when
// the mouse is far away)
itemWidth : 40,
// itemHeight: Integer
// height of menu item (in pixels) in it's dormant state (when
// the mouse is far away)
itemHeight : 40,
// itemMaxWidth: Integer
// width of menu item (in pixels) in it's fully enlarged state
// (when the mouse is directly over it)
itemMaxWidth : 150,
// itemMaxHeight: Integer
// height of menu item (in pixels) in it's fully enlarged state
// (when the mouse is directly over it)
itemMaxHeight : 150,
imgNode : null,
// orientation: String
// orientation of the menu, either "horizontal" or "vertical"
orientation : 'horizontal',
// isFixed: Boolean
// toggle to enable additional listener (window scroll) if
// FisheyeList is in a fixed postion
isFixed : false,
// conservativeTrigger: Boolean
// if true, don't start enlarging menu items until mouse is over
// an image;
// if false, start enlarging menu items as the mouse moves near
// them.
conservativeTrigger : false,
// effectUnits: Number
// controls how much reaction the menu makes, relative to the
// distance of the mouse from the menu
effectUnits : 2,
// itemPadding: Integer
// padding (in pixels) betweeen each menu item
itemPadding : 10,
// attachEdge: String
// controls the border that the menu items don't expand past;
// for example, if set to "top", then the menu items will drop
// downwards as they expand.
// values
// "center", "left", "right", "top", "bottom".
attachEdge : 'center',
// labelEdge: String
// controls were the labels show up in relation to the menu item
// icons
// values
// "center", "left", "right", "top", "bottom".
labelEdge : 'bottom',
postCreate : function() {
var e = this.EDGE;
dojo.setSelectable(this.domNode, false);
var isHorizontal = this.isHorizontal = (this.orientation == 'horizontal');
this.selectedNode = -1;
this.isOver = false;
this.hitX1 = -1;
this.hitY1 = -1;
this.hitX2 = -1;
this.hitY2 = -1;
//
// only some edges make sense...
//
this.anchorEdge = this._toEdge(this.attachEdge, e.CENTER);
this.labelEdge = this._toEdge(this.labelEdge, e.TOP);
if (this.labelEdge == e.CENTER) {
this.labelEdge = e.TOP;
}
if (isHorizontal) {
if (this.anchorEdge == e.LEFT) {
this.anchorEdge = e.CENTER;
}
if (this.anchorEdge == e.RIGHT) {
this.anchorEdge = e.CENTER;
}
if (this.labelEdge == e.LEFT) {
this.labelEdge = e.TOP;
}
if (this.labelEdge == e.RIGHT) {
this.labelEdge = e.TOP;
}
} else {
if (this.anchorEdge == e.TOP) {
this.anchorEdge = e.CENTER;
}
if (this.anchorEdge == e.BOTTOM) {
this.anchorEdge = e.CENTER;
}
if (this.labelEdge == e.TOP) {
this.labelEdge = e.LEFT;
}
if (this.labelEdge == e.BOTTOM) {
this.labelEdge = e.LEFT;
}
}
//
// figure out the proximity size
//
var effectUnits = this.effectUnits;
this.proximityLeft = this.itemWidth * (effectUnits - 0.5);
this.proximityRight = this.itemWidth * (effectUnits - 0.5);
this.proximityTop = this.itemHeight * (effectUnits - 0.5);
this.proximityBottom = this.itemHeight
* (effectUnits - 0.5);
if (this.anchorEdge == e.LEFT) {
this.proximityLeft = 0;
}
if (this.anchorEdge == e.RIGHT) {
this.proximityRight = 0;
}
if (this.anchorEdge == e.TOP) {
this.proximityTop = 0;
}
if (this.anchorEdge == e.BOTTOM) {
this.proximityBottom = 0;
}
if (this.anchorEdge == e.CENTER) {
this.proximityLeft /= 2;
this.proximityRight /= 2;
this.proximityTop /= 2;
this.proximityBottom /= 2;
}
},
startup : function() {
// summary: create our connections and setup our FisheyeList
this.children = this.getChildren();
// original postCreate() --tk
this._initializePositioning();
//
// in liberal trigger mode, activate menu whenever mouse is
// close
//
if (!this.conservativeTrigger) {
this._onMouseMoveHandle = dojo.connect(
document.documentElement, "onmousemove", this,
"_onMouseMove");
}
if (this.isFixed) {
this._onScrollHandle = dojo.connect(document,
"onscroll", this, "_onScroll");
}
// Deactivate the menu if mouse is moved off screen (doesn't
// work for FF?)
this._onMouseOutHandle = dojo.connect(
document.documentElement, "onmouseout", this,
"_onBodyOut");
this._addChildHandle = dojo.connect(this, "addChild", this,
"_initializePositioning");
this._onResizeHandle = dojo.connect(window, "onresize",
this, "_initializePositioning");
},
_initializePositioning : function() {
this.itemCount = this.children.length;
this.barWidth = (this.isHorizontal ? this.itemCount : 1)
* this.itemWidth;
this.barHeight = (this.isHorizontal ? 1 : this.itemCount)
* this.itemHeight;
this.totalWidth = this.proximityLeft + this.proximityRight
+ this.barWidth;
this.totalHeight = this.proximityTop + this.proximityBottom
+ this.barHeight;
//
// calculate effect ranges for each item
//
for (var i = 0; i < this.children.length; i++) {
this.children[i].posX = this.itemWidth
* (this.isHorizontal ? i : 0);
this.children[i].posY = this.itemHeight
* (this.isHorizontal ? 0 : i);
this.children[i].cenX = this.children[i].posX
+ (this.itemWidth / 2);
this.children[i].cenY = this.children[i].posY
+ (this.itemHeight / 2);
var isz = this.isHorizontal
? this.itemWidth
: this.itemHeight;
var r = this.effectUnits * isz;
var c = this.isHorizontal
? this.children[i].cenX
: this.children[i].cenY;
var lhs = this.isHorizontal
? this.proximityLeft
: this.proximityTop;
var rhs = this.isHorizontal
? this.proximityRight
: this.proximityBottom;
var siz = this.isHorizontal
? this.barWidth
: this.barHeight;
var range_lhs = r;
var range_rhs = r;
if (range_lhs > c + lhs) {
range_lhs = c + lhs;
}
if (range_rhs > (siz - c + rhs)) {
range_rhs = siz - c + rhs;
}
this.children[i].effectRangeLeft = range_lhs / isz;
this.children[i].effectRangeRght = range_rhs / isz;
// dojo.debug('effect range for '+i+' is
// '+range_lhs+'/'+range_rhs);
}
//
// create the bar
//
this.domNode.style.width = this.barWidth + 'px';
this.domNode.style.height = this.barHeight + 'px';
//
// position the items
//
for (var i = 0; i < this.children.length; i++) {
var itm = this.children[i];
var elm = itm.domNode;
elm.style.left = itm.posX + 'px';
elm.style.top = itm.posY + 'px';
elm.style.width = this.itemWidth + 'px';
elm.style.height = this.itemHeight + 'px';
itm.imgNode.style.left = this.itemPadding + '%';
itm.imgNode.style.top = this.itemPadding + '%';
itm.imgNode.style.width = (100 - 2 * this.itemPadding)
+ '%';
itm.imgNode.style.height = (100 - 2 * this.itemPadding)
+ '%';
}
//
// calc the grid
//
this._calcHitGrid();
},
_overElement : function(/* DomNode|String */node, /* Event */
e) {
// summary:
// Returns whether the mouse is over the passed element.
// Node: Must must be display:block (ie, not a )
node = dojo.byId(node);
var mouse = {
x : e.pageX,
y : e.pageY
};
var bb = dojo._getBorderBox(node);
var absolute = dojo.coords(node, true);
var top = absolute.y;
var bottom = top + bb.h;
var left = absolute.x;
var right = left + bb.w;
return (mouse.x >= left && mouse.x <= right
&& mouse.y >= top && mouse.y <= bottom); // boolean
},
_onBodyOut : function(/* Event */e) {
// clicking over an object inside of body causes this event
// to fire; ignore that case
if (this._overElement(dojo.body(), e)) {
return;
}
this._setDormant(e);
},
_setDormant : function(/* Event */e) {
// summary: called when mouse moves out of menu's range
if (!this.isOver) {
return;
} // already dormant?
this.isOver = false;
if (this.conservativeTrigger) {
// user can't re-trigger the menu expansion
// until he mouses over a icon again
dojo.disconnect(this._onMouseMoveHandle);
}
this._onGridMouseMove(-1, -1);
},
_setActive : function(/* Event */e) {
// summary: called when mouse is moved into menu's range
if (this.isOver) {
return;
} // already activated?
this.isOver = true;
if (this.conservativeTrigger) {
// switch event handlers so that we handle mouse events
// from anywhere near
// the menu
this._onMouseMoveHandle = dojo.connect(
document.documentElement, "onmousemove", this,
"_onMouseMove");
this.timerScale = 0.0;
// call mouse handler to do some initial necessary
// calculations/positioning
this._onMouseMove(e);
// slowly expand the icon size so it isn't jumpy
this._expandSlowly();
}
},
_onMouseMove : function(/* Event */e) {
// summary: called when mouse is moved
if ((e.pageX >= this.hitX1) && (e.pageX <= this.hitX2)
&& (e.pageY >= this.hitY1)
&& (e.pageY <= this.hitY2)) {
if (!this.isOver) {
this._setActive(e);
}
this._onGridMouseMove(e.pageX - this.hitX1, e.pageY
- this.hitY1);
} else {
if (this.isOver) {
this._setDormant(e);
}
}
},
_onScroll : function() {
this._calcHitGrid();
},
onResized : function() {
this._calcHitGrid();
},
_onGridMouseMove : function(x, y) {
// summary: called when mouse is moved in the vicinity of
// the menu
this.pos = {
x : x,
y : y
};
this._paint();
},
_paint : function() {
var x = this.pos.x;
var y = this.pos.y;
if (this.itemCount <= 0) {
return;
}
//
// figure out our main index
//
var pos = this.isHorizontal ? x : y;
var prx = this.isHorizontal
? this.proximityLeft
: this.proximityTop;
var siz = this.isHorizontal
? this.itemWidth
: this.itemHeight;
var sim = this.isHorizontal ? (1.0 - this.timerScale)
* this.itemWidth + this.timerScale
* this.itemMaxWidth : (1.0 - this.timerScale)
* this.itemHeight + this.timerScale
* this.itemMaxHeight;
var cen = ((pos - prx) / siz) - 0.5;
var max_off_cen = (sim / siz) - 0.5;
if (max_off_cen > this.effectUnits) {
max_off_cen = this.effectUnits;
}
//
// figure out our off-axis weighting
//
var off_weight = 0;
if (this.anchorEdge == this.EDGE.BOTTOM) {
var cen2 = (y - this.proximityTop) / this.itemHeight;
off_weight = (cen2 > 0.5) ? 1 : y
/ (this.proximityTop + (this.itemHeight / 2));
}
if (this.anchorEdge == this.EDGE.TOP) {
var cen2 = (y - this.proximityTop) / this.itemHeight;
off_weight = (cen2 < 0.5)
? 1
: (this.totalHeight - y)
/ (this.proximityBottom + (this.itemHeight / 2));
}
if (this.anchorEdge == this.EDGE.RIGHT) {
var cen2 = (x - this.proximityLeft) / this.itemWidth;
off_weight = (cen2 > 0.5) ? 1 : x
/ (this.proximityLeft + (this.itemWidth / 2));
}
if (this.anchorEdge == this.EDGE.LEFT) {
var cen2 = (x - this.proximityLeft) / this.itemWidth;
off_weight = (cen2 < 0.5) ? 1 : (this.totalWidth - x)
/ (this.proximityRight + (this.itemWidth / 2));
}
if (this.anchorEdge == this.EDGE.CENTER) {
if (this.isHorizontal) {
off_weight = y / (this.totalHeight);
} else {
off_weight = x / (this.totalWidth);
}
if (off_weight > 0.5) {
off_weight = 1 - off_weight;
}
off_weight *= 2;
}
//
// set the sizes
//
for (var i = 0; i < this.itemCount; i++) {
var weight = this._weighAt(cen, i);
if (weight < 0) {
weight = 0;
}
this._setItemSize(i, weight * off_weight);
}
//
// set the positions
//
var main_p = Math.round(cen);
var offset = 0;
if (cen < 0) {
main_p = 0;
} else if (cen > this.itemCount - 1) {
main_p = this.itemCount - 1;
} else {
offset = (cen - main_p)
* ((this.isHorizontal
? this.itemWidth
: this.itemHeight) - this.children[main_p].sizeMain);
}
this._positionElementsFrom(main_p, offset);
},
_weighAt : function(/* Integer */cen, /* Integer */i) {
var dist = Math.abs(cen - i);
var limit = ((cen - i) > 0)
? this.children[i].effectRangeRght
: this.children[i].effectRangeLeft;
return (dist > limit) ? 0 : (1 - dist / limit); // Integer
},
_setItemSize : function(p, scale) {
scale *= this.timerScale;
var w = Math.round(this.itemWidth
+ ((this.itemMaxWidth - this.itemWidth) * scale));
var h = Math.round(this.itemHeight
+ ((this.itemMaxHeight - this.itemHeight) * scale));
if (this.isHorizontal) {
this.children[p].sizeW = w;
this.children[p].sizeH = h;
this.children[p].sizeMain = w;
this.children[p].sizeOff = h;
var y = 0;
if (this.anchorEdge == this.EDGE.TOP) {
y = (this.children[p].cenY - (this.itemHeight / 2));
} else if (this.anchorEdge == this.EDGE.BOTTOM) {
y = (this.children[p].cenY - (h - (this.itemHeight / 2)));
} else {
y = (this.children[p].cenY - (h / 2));
}
this.children[p].usualX = Math
.round(this.children[p].cenX - (w / 2));
this.children[p].domNode.style.top = y + 'px';
this.children[p].domNode.style.left = this.children[p].usualX
+ 'px';
} else {
this.children[p].sizeW = w;
this.children[p].sizeH = h;
this.children[p].sizeOff = w;
this.children[p].sizeMain = h;
var x = 0;
if (this.anchorEdge == this.EDGE.LEFT) {
x = this.children[p].cenX - (this.itemWidth / 2);
} else if (this.anchorEdge == this.EDGE.RIGHT) {
x = this.children[p].cenX
- (w - (this.itemWidth / 2));
} else {
x = this.children[p].cenX - (w / 2);
}
this.children[p].domNode.style.left = x + 'px';
this.children[p].usualY = Math
.round(this.children[p].cenY - (h / 2));
this.children[p].domNode.style.top = this.children[p].usualY
+ 'px';
}
this.children[p].domNode.style.width = w + 'px';
this.children[p].domNode.style.height = h + 'px';
if (this.children[p].svgNode) {
this.children[p].svgNode.setSize(w, h);
}
},
_positionElementsFrom : function(p, offset) {
var pos = 0;
if (this.isHorizontal) {
pos = Math.round(this.children[p].usualX + offset);
this.children[p].domNode.style.left = pos + 'px';
} else {
pos = Math.round(this.children[p].usualY + offset);
this.children[p].domNode.style.top = pos + 'px';
}
this._positionLabel(this.children[p]);
// position before
var bpos = pos;
for (var i = p - 1; i >= 0; i--) {
bpos -= this.children[i].sizeMain;
if (this.isHorizontal) {
this.children[i].domNode.style.left = bpos + 'px';
} else {
this.children[i].domNode.style.top = bpos + 'px';
}
this._positionLabel(this.children[i]);
}
// position after
var apos = pos;
for (var i = p + 1; i < this.itemCount; i++) {
apos += this.children[i - 1].sizeMain;
if (this.isHorizontal) {
this.children[i].domNode.style.left = apos + 'px';
} else {
this.children[i].domNode.style.top = apos + 'px';
}
this._positionLabel(this.children[i]);
}
},
_positionLabel : function(itm) {
var x = 0;
var y = 0;
var mb = dojo.marginBox(itm.lblNode);
if (this.labelEdge == this.EDGE.TOP) {
x = Math.round((itm.sizeW / 2) - (mb.w / 2));
y = -mb.h;
}
if (this.labelEdge == this.EDGE.BOTTOM) {
x = Math.round((itm.sizeW / 2) - (mb.w / 2));
y = itm.sizeH;
}
if (this.labelEdge == this.EDGE.LEFT) {
x = -mb.w;
y = Math.round((itm.sizeH / 2) - (mb.h / 2));
}
if (this.labelEdge == this.EDGE.RIGHT) {
x = itm.sizeW;
y = Math.round((itm.sizeH / 2) - (mb.h / 2));
}
itm.lblNode.style.left = x + 'px';
itm.lblNode.style.top = y + 'px';
},
_calcHitGrid : function() {
var pos = dojo.coords(this.domNode, true);
this.hitX1 = pos.x - this.proximityLeft;
this.hitY1 = pos.y - this.proximityTop;
this.hitX2 = this.hitX1 + this.totalWidth;
this.hitY2 = this.hitY1 + this.totalHeight;
},
_toEdge : function(inp, def) {
return this.EDGE[inp.toUpperCase()] || def;
},
_expandSlowly : function() {
// summary: slowly expand the image to user specified max
// size
if (!this.isOver) {
return;
}
this.timerScale += 0.2;
this._paint();
if (this.timerScale < 1.0) {
setTimeout(dojo.hitch(this, "_expandSlowly"), 10);
}
},
destroyRecursive : function() {
// need to disconnect when we destroy
dojo.disconnect(this._onMouseOutHandle);
dojo.disconnect(this._onMouseMoveHandle);
dojo.disconnect(this._addChildHandle);
if (this.isFixed) {
dojo.disconnect(this._onScrollHandle);
}
dojo.disconnect(this._onResizeHandle);
this.inherited("destroyRecursive", arguments);
}
});
dojo.declare("dojox.widget.FisheyeListItem", [dijit._Widget,
dijit._Templated, dijit._Contained], {
/*
* summary Menu item inside of a FisheyeList. See FisheyeList
* documentation for details on usage.
*/
// iconSrc: String
// pathname to image file (jpg, gif, png, etc.) of icon for this
// menu item
iconSrc : "",
// label: String
// label to print next to the icon, when it is moused-over
label : "",
// id: String
// will be set to the id of the orginal div element
id : "",
_blankImgPath : dojo.moduleUrl("dojox.widget",
"FisheyeList/blank.gif"),
templateString : ''
+ '
'
+ '
'
+ '
',
_isNode : function(/* object */wh) {
// summary:
// checks to see if wh is actually a node.
if (typeof Element == "function") {
try {
return wh instanceof Element; // boolean
} catch (e) {
}
} else {
// best-guess
return wh && !isNaN(wh.nodeType); // boolean
}
},
_hasParent : function(/* Node */node) {
// summary:
// returns whether or not node is a child of another node.
return Boolean(node && node.parentNode
&& this._isNode(node.parentNode)); // boolean
},
postCreate : function() {
// set image
if ((this.iconSrc.toLowerCase()
.substring(this.iconSrc.length - 4) == ".png")
&& (dojo.isIE) && (dojo.isIE < 7)) {
/*
* we set the id of the new fisheyeListItem to the id of
* the div defined in the HTML
*/
if (this._hasParent(this.imgNode) && this.id != "") {
var parent = this.imgNode.parentNode;
parent.setAttribute("id", this.id);
}
this.imgNode.style.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='"
+ this.iconSrc + "', sizingMethod='scale')";
this.imgNode.src = this._blankImgPath.toString();
} else {
if (this._hasParent(this.imgNode) && this.id != "") {
var parent = this.imgNode.parentNode;
parent.setAttribute("id", this.id);
}
this.imgNode.src = this.iconSrc;
}
// Label
if (this.lblNode) {
this.lblNode.appendChild(document
.createTextNode(this.label));
}
dojo.setSelectable(this.domNode, false);
this.startup();
},
startup : function() {
this.parent = this.getParent();
},
onMouseOver : function(/* Event */e) {
// summary: callback when user moves mouse over this menu
// item
// in conservative mode, don't activate the menu until user
// mouses over an icon
if (!this.parent.isOver) {
this.parent._setActive(e);
}
if (this.label != "") {
dojo.addClass(this.lblNode, "dojoxFishSelected");
this.parent._positionLabel(this);
}
},
onMouseOut : function(/* Event */e) {
// summary: callback when user moves mouse off of this menu
// item
dojo.removeClass(this.lblNode, "dojoxFishSelected");
},
onClick : function(/* Event */e) {
// summary: user overridable callback when user clicks this
// menu item
}
});
}