Subversion Repositories Applications.papyrus

Rev

Blame | Last modification | View Log | RSS feed

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("dojox.widget", "FisheyeList/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
        }
});

}