diff options
Diffstat (limited to 'includes/js/dojox/widget/FisheyeList.js')
-rw-r--r-- | includes/js/dojox/widget/FisheyeList.js | 708 |
1 files changed, 708 insertions, 0 deletions
diff --git a/includes/js/dojox/widget/FisheyeList.js b/includes/js/dojox/widget/FisheyeList.js new file mode 100644 index 0000000..f06d913 --- /dev/null +++ b/includes/js/dojox/widget/FisheyeList.js @@ -0,0 +1,708 @@ +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: + // | <div dojoType="FisheyeList" + // | itemWidth="40" itemHeight="40" + // | itemMaxWidth="150" itemMaxHeight="150" + // | orientation="horizontal" + // | effectUnits="2" + // | itemPadding="10" + // | attachEdge="center" + // | labelEdge="bottom"> + // | + // | <div dojoType="FisheyeListItem" + // | id="item1" + // | onclick="alert('click on' + this.label + '(from widget id ' + this.widgetId + ')!');" + // | label="Item 1" + // | iconSrc="images/fisheye_1.png"> + // | </div> + // | ... + // | </div> + // + 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: '<div class="dojoxFisheyeListBar" dojoAttachPoint="containerNode"></div>', + + 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 <span>) + 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("dojo", "resources/blank.gif"), + + templateString: + '<div class="dojoxFisheyeListItem">' + + ' <img class="dojoxFisheyeListItemImage" dojoAttachPoint="imgNode" dojoAttachEvent="onmouseover:onMouseOver,onmouseout:onMouseOut,onclick:onClick">' + + ' <div class="dojoxFisheyeListItemLabel" dojoAttachPoint="lblNode"></div>' + + '</div>', + + _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 + } +}); + +} |