source: Dev/trunk/src/client/dojox/widget/FisheyeList.js

Last change on this file was 483, checked in by hendrikvanantwerpen, 11 years ago

Added Dojo 1.9.3 release.

File size: 18.1 KB
Line 
1define(["dojo/_base/declare", "dojo/_base/sniff", "dojo/_base/lang", "dojo/aspect", "dojo/dom", "dojo/dom-attr", "dojo/dom-class", "dojo/dom-geometry", "dojo/dom-style", "dojo/dom-construct", "dojo/on", "dojo/_base/window", "dojo/mouse", "dijit/_WidgetBase", "dijit/_TemplatedMixin", "dijit/_Container", "./FisheyeListItem"],
2        function(declare, has, lang, aspect, dom, attr, domClass, geometry, style, construct, on, winUtil, mouse, _WidgetBase, _TemplatedMixin, _Container, FisheyeListItem){
3
4                return declare("dojox.widget.FisheyeList",
5                        [_WidgetBase, _TemplatedMixin, _Container], {
6                                // summary:
7                                //              Menu similar to the fish eye menu on the Mac OS
8                                // example:
9                                // |    <div dojoType="dojo.widget.FisheyeList"
10                                // |            itemWidth="40" itemHeight="40"
11                                // |            itemMaxWidth="150" itemMaxHeight="150"
12                                // |            orientation="horizontal"
13                                // |            effectUnits="2"
14                                // |            itemPadding="10"
15                                // |            attachEdge="center"
16                                // |            labelEdge="bottom">
17                                // |
18                                // |            <div dojoType="dojox.widget.FisheyeListItem"
19                                // |                    id="item1"
20                                // |                    onclick="alert('click on' + this.label + '(from widget id ' + this.widgetId + ')!');"
21                                // |                    label="Item 1"
22                                // |                    iconSrc="images/fisheye_1.png">
23                                // |            </div>
24                                // |            ...
25                                // |    </div>
26
27                                constructor: function(){
28                                        //
29                                        // TODO
30                                        // fix really long labels in vertical mode
31                                        //
32
33                                        this.pos = {'x': -1, 'y': -1};  // current cursor position, relative to the grid
34
35                                        // for conservative trigger mode, when triggered, timerScale is gradually increased from 0 to 1
36                                        this.timerScale = 1.0;
37                                },
38
39                                EDGE: {
40                                        CENTER: 0,
41                                        LEFT: 1,
42                                        RIGHT: 2,
43                                        TOP: 3,
44                                        BOTTOM: 4
45                                },
46
47                                templateString: '<div class="dojoxFisheyeListBar" data-dojo-attach-point="containerNode"></div>',
48
49                                snarfChildDomOutput: true,
50
51                                // itemWidth: Integer
52                                //              width of menu item (in pixels) in it's dormant state (when the mouse is far away)
53                                itemWidth: 40,
54
55                                // itemHeight: Integer
56                                //              height of menu item (in pixels) in it's dormant state (when the mouse is far away)
57                                itemHeight: 40,
58
59                                // itemMaxWidth: Integer
60                                //              width of menu item (in pixels) in it's fully enlarged state (when the mouse is directly over it)
61                                itemMaxWidth: 150,
62
63                                // itemMaxHeight: Integer
64                                //              height of menu item (in pixels) in it's fully enlarged state (when the mouse is directly over it)
65                                itemMaxHeight: 150,
66
67                                imgNode: null,
68
69                                // orientation: String
70                                //              orientation of the menu, either "horizontal" or "vertical"
71                                orientation: 'horizontal',
72
73                                // isFixed: Boolean
74                                //              toggle to enable additional listener (window scroll) if FisheyeList is in a fixed postion
75                                isFixed: false,
76
77                                // conservativeTrigger: Boolean
78                                //              if true, don't start enlarging menu items until mouse is over an image;
79                                //              if false, start enlarging menu items as the mouse moves near them.
80                                conservativeTrigger: false,
81
82                                // effectUnits: Number
83                                //              controls how much reaction the menu makes, relative to the distance of the mouse from the menu
84                                effectUnits: 2,
85
86                                // itemPadding: Integer
87                                //              padding (in pixels) between each menu item
88                                itemPadding: 10,
89
90                                // attachEdge: String
91                                //              Controls the border that the menu items don't expand past;
92                                //              for example, if set to "top", then the menu items will drop downwards as they expand.
93                                //              Values: "center", "left", "right", "top", "bottom".
94                                attachEdge: 'center',
95
96                                // labelEdge: String
97                                //              Controls were the labels show up in relation to the menu item icons.
98                                //              Values: "center", "left", "right", "top", "bottom".
99                                labelEdge: 'bottom',
100
101                                postCreate: function(){
102                                        var e = this.EDGE,
103                                                isHorizontal = this.isHorizontal = (this.orientation == 'horizontal');
104
105                                        dom.setSelectable(this.domNode, false);
106
107                                        this.selectedNode = -1;
108
109                                        this.isOver = false;
110                                        this.hitX1 = -1;
111                                        this.hitY1 = -1;
112                                        this.hitX2 = -1;
113                                        this.hitY2 = -1;
114
115                                        //
116                                        // only some edges make sense...
117                                        //
118                                        this.anchorEdge = this._toEdge(this.attachEdge, e.CENTER);
119                                        this.labelEdge  = this._toEdge(this.labelEdge,  e.TOP);
120
121                                        if(this.labelEdge == e.CENTER){ this.labelEdge = e.TOP; }
122
123                                        if(isHorizontal){
124                                                if(this.anchorEdge == e.LEFT){ this.anchorEdge = e.CENTER; }
125                                                if(this.anchorEdge == e.RIGHT){ this.anchorEdge = e.CENTER; }
126                                                if(this.labelEdge == e.LEFT){ this.labelEdge = e.TOP; }
127                                                if(this.labelEdge == e.RIGHT){ this.labelEdge = e.TOP; }
128                                        }else{
129                                                if(this.anchorEdge == e.TOP){ this.anchorEdge = e.CENTER; }
130                                                if(this.anchorEdge == e.BOTTOM){ this.anchorEdge = e.CENTER; }
131                                                if(this.labelEdge == e.TOP){ this.labelEdge = e.LEFT; }
132                                                if(this.labelEdge == e.BOTTOM){ this.labelEdge = e.LEFT; }
133                                        }
134
135                                        //
136                                        // figure out the proximity size
137                                        //
138                                        var effectUnits = this.effectUnits;
139                                        this.proximityLeft   = this.itemWidth  * (effectUnits - 0.5);
140                                        this.proximityRight  = this.itemWidth  * (effectUnits - 0.5);
141                                        this.proximityTop       = this.itemHeight * (effectUnits - 0.5);
142                                        this.proximityBottom = this.itemHeight * (effectUnits - 0.5);
143
144                                        if(this.anchorEdge == e.LEFT){
145                                                this.proximityLeft = 0;
146                                        }
147                                        if(this.anchorEdge == e.RIGHT){
148                                                this.proximityRight = 0;
149                                        }
150                                        if(this.anchorEdge == e.TOP){
151                                                this.proximityTop = 0;
152                                        }
153                                        if(this.anchorEdge == e.BOTTOM){
154                                                this.proximityBottom = 0;
155                                        }
156                                        if(this.anchorEdge == e.CENTER){
157                                                this.proximityLeft   /= 2;
158                                                this.proximityRight  /= 2;
159                                                this.proximityTop       /= 2;
160                                                this.proximityBottom /= 2;
161                                        }
162                                },
163
164                                startup: function(){
165                                        // summary:
166                                        //              create our connections and setup our FisheyeList
167                                        this.children = this.getChildren();
168                                        //original postCreate() --tk
169                                        this._initializePositioning();
170
171                                        //
172                                        // in liberal trigger mode, activate menu whenever mouse is close, in conservative mode, pause until needed
173                                        //
174                                        this._onMouseMoveHandle = on.pausable(winUtil.doc.documentElement, "mousemove", lang.hitch(this, "_onMouseMove"));
175                                        if(this.conservativeTrigger){
176                                                this._onMouseMoveHandle.pause();
177                                        }
178                                        if(this.isFixed){
179                                                this.own(on(winUtil.doc,"scroll", lang.hitch(this, this._onScroll)));
180                                        }
181
182                                        // Deactivate the menu if mouse is moved off screen (doesn't work for FF?)
183                                        this.own(
184                                                on(winUtil.doc.documentElement, mouse.leave, lang.hitch(this, "_onBodyOut")),
185                                                aspect.after(this, "addChild", lang.hitch(this, "_initializePositioning"), true),
186                                                aspect.after(winUtil.global, "onresize", lang.hitch(this, "_initializePositioning"), true)
187                                        );
188                                },
189
190                                _initializePositioning: function(){
191                                        this.itemCount = this.children.length;
192
193                                        this.barWidth  = (this.isHorizontal ? this.itemCount : 1) * this.itemWidth;
194                                        this.barHeight = (this.isHorizontal ? 1 : this.itemCount) * this.itemHeight;
195
196                                        this.totalWidth  = this.proximityLeft + this.proximityRight  + this.barWidth;
197                                        this.totalHeight = this.proximityTop  + this.proximityBottom + this.barHeight;
198
199                                        //
200                                        // calculate effect ranges for each item
201                                        //
202
203                                        for(var i=0; i<this.children.length; i++){
204
205                                                this.children[i].posX = this.itemWidth  * (this.isHorizontal ? i : 0);
206                                                this.children[i].posY = this.itemHeight * (this.isHorizontal ? 0 : i);
207
208                                                this.children[i].cenX = this.children[i].posX + (this.itemWidth  / 2);
209                                                this.children[i].cenY = this.children[i].posY + (this.itemHeight / 2);
210
211                                                var isz = this.isHorizontal ? this.itemWidth : this.itemHeight,
212                                                        r = this.effectUnits * isz,
213                                                        c = this.isHorizontal ? this.children[i].cenX : this.children[i].cenY,
214                                                        lhs = this.isHorizontal ? this.proximityLeft : this.proximityTop,
215                                                        rhs = this.isHorizontal ? this.proximityRight : this.proximityBottom,
216                                                        siz = this.isHorizontal ? this.barWidth : this.barHeight,
217
218                                                        range_lhs = r,
219                                                        range_rhs = r;
220
221                                                if(range_lhs > c+lhs){ range_lhs = c+lhs; }
222                                                if(range_rhs > (siz-c+rhs)){ range_rhs = siz-c+rhs; }
223
224                                                this.children[i].effectRangeLeft = range_lhs / isz;
225                                                this.children[i].effectRangeRght = range_rhs / isz;
226
227                                                //dojo.debug('effect range for '+i+' is '+range_lhs+'/'+range_rhs);
228                                        }
229
230                                        //
231                                        // create the bar
232                                        //
233                                        style.set(this.domNode, {width: this.barWidth + 'px', height: this.barHeight + 'px'});
234
235                                        //
236                                        // position the items
237                                        //
238                                        for(i=0; i<this.children.length; i++){
239                                                var itm = this.children[i];
240                                                var elm = itm.domNode;
241                                                style.set(elm, {left: itm.posX + 'px', top: itm.posY + 'px', width: this.itemWidth + 'px', height: this.itemHeight + 'px'});
242
243                                                style.set(itm.imgNode, {left: this.itemPadding+'%', top: this.itemPadding+'%', width: (100 - 2 * this.itemPadding) + '%', height: (100 - 2 * this.itemPadding) + '%'});
244                                        }
245                                        //
246                                        // calc the grid
247                                        //
248                                        this._calcHitGrid();
249                                },
250
251                                _overElement: function(/* DomNode|String */ node, /* Event */ e){
252                                        // summary:
253                                        //              Returns whether the mouse is over the passed element.
254                                        // node:
255                                        //              Must must be display:block (ie, not a `<span>`)
256                                        node = dom.byId(node);
257                                        var mouse = {x: e.pageX, y: e.pageY},
258                                                absolute = geometry.position(node, true),
259                                                top = absolute.y,
260                                                bottom = top + absolute.h,
261                                                left = absolute.x,
262                                                right = left + absolute.w;
263
264                                        return (mouse.x >= left
265                                                && mouse.x <= right
266                                                && mouse.y >= top
267                                                && mouse.y <= bottom
268                                                );      // Boolean
269                                },
270
271                                _onBodyOut: function(/*Event*/ e){
272                                        // clicking over an object inside of body causes this event to fire; ignore that case
273                                        if( this._overElement(winUtil.body(), e) ){
274                                                return;
275                                        }
276                                        this._setDormant(e);
277                                },
278
279                                _setDormant: function(/*Event*/ e){
280                                        // summary:
281                                        //              called when mouse moves out of menu's range
282
283                                        if(!this.isOver){ return; }     // already dormant?
284                                        this.isOver = false;
285
286                                        if(this.conservativeTrigger){
287                                                // user can't re-trigger the menu expansion
288                                                // until he mouses over a icon again
289                                                this._onMouseMoveHandle.pause();
290                                        }
291                                        this._onGridMouseMove(-1, -1);
292                                },
293
294                                _setActive: function(/*Event*/ e){
295                                        // summary:
296                                        //              called when mouse is moved into menu's range
297
298                                        if(this.isOver){ return; }      // already activated?
299                                        this.isOver = true;
300
301                                        if(this.conservativeTrigger){
302                                                // switch event handlers so that we handle mouse events from anywhere near
303                                                // the menu
304                                                this._onMouseMoveHandle.resume();
305
306                                                this.timerScale=0.0;
307
308                                                // call mouse handler to do some initial necessary calculations/positioning
309                                                this._onMouseMove(e);
310
311                                                // slowly expand the icon size so it isn't jumpy
312                                                this._expandSlowly();
313                                        }
314                                },
315
316                                _onMouseMove: function(/*Event*/ e){
317                                        // summary:
318                                        //              called when mouse is moved
319                                        if(     (e.pageX >= this.hitX1) && (e.pageX <= this.hitX2) &&
320                                                (e.pageY >= this.hitY1) && (e.pageY <= this.hitY2)      ){
321                                                if(!this.isOver){
322                                                        this._setActive(e);
323                                                }
324                                                this._onGridMouseMove(e.pageX-this.hitX1, e.pageY-this.hitY1);
325                                        }else{
326                                                if(this.isOver){
327                                                        this._setDormant(e);
328                                                }
329                                        }
330                                },
331
332                                _onScroll: function(){
333                                        this._calcHitGrid();
334                                },
335
336                                onResized: function(){
337                                        this._calcHitGrid();
338                                },
339
340                                _onGridMouseMove: function(x, y){
341                                        // summary:
342                                        //              called when mouse is moved in the vicinity of the menu
343                                        this.pos = {x:x, y:y};
344                                        this._paint();
345                                },
346
347                                _paint: function(){
348                                        var x=this.pos.x;
349                                        var y=this.pos.y;
350
351                                        if(this.itemCount <= 0){ return; }
352
353                                        //
354                                        // figure out our main index
355                                        //
356                                        var pos = this.isHorizontal ? x : y,
357                                                prx = this.isHorizontal ? this.proximityLeft : this.proximityTop,
358                                                siz = this.isHorizontal ? this.itemWidth : this.itemHeight,
359                                                sim = this.isHorizontal ?
360                                                        (1.0-this.timerScale)*this.itemWidth + this.timerScale*this.itemMaxWidth :
361                                                        (1.0-this.timerScale)*this.itemHeight + this.timerScale*this.itemMaxHeight,
362
363                                                cen = ((pos - prx) / siz) - 0.5,
364                                                max_off_cen = (sim / siz) - 0.5;
365
366                                        if(max_off_cen > this.effectUnits){ max_off_cen = this.effectUnits; }
367
368                                        //
369                                        // figure out our off-axis weighting
370                                        //
371                                        var off_weight = 0, cen2;
372
373                                        if(this.anchorEdge == this.EDGE.BOTTOM){
374                                                cen2 = (y - this.proximityTop) / this.itemHeight;
375                                                off_weight = (cen2 > 0.5) ? 1 : y / (this.proximityTop + (this.itemHeight / 2));
376                                        }
377                                        if(this.anchorEdge == this.EDGE.TOP){
378                                                cen2 = (y - this.proximityTop) / this.itemHeight;
379                                                off_weight = (cen2 < 0.5) ? 1 : (this.totalHeight - y) / (this.proximityBottom + (this.itemHeight / 2));
380                                        }
381                                        if(this.anchorEdge == this.EDGE.RIGHT){
382                                                cen2 = (x - this.proximityLeft) / this.itemWidth;
383                                                off_weight = (cen2 > 0.5) ? 1 : x / (this.proximityLeft + (this.itemWidth / 2));
384                                        }
385                                        if(this.anchorEdge == this.EDGE.LEFT){
386                                                cen2 = (x - this.proximityLeft) / this.itemWidth;
387                                                off_weight = (cen2 < 0.5) ? 1 : (this.totalWidth - x) / (this.proximityRight + (this.itemWidth / 2));
388                                        }
389                                        if(this.anchorEdge == this.EDGE.CENTER){
390                                                if(this.isHorizontal){
391                                                        off_weight = y / (this.totalHeight);
392                                                }else{
393                                                        off_weight = x / (this.totalWidth);
394                                                }
395
396                                                if(off_weight > 0.5){
397                                                        off_weight = 1 - off_weight;
398                                                }
399
400                                                off_weight *= 2;
401                                        }
402
403                                        //
404                                        // set the sizes
405                                        //
406                                        for(var i=0; i<this.itemCount; i++){
407                                                var weight = this._weighAt(cen, i);
408                                                if(weight < 0){weight = 0;}
409                                                this._setItemSize(i, weight * off_weight);
410                                        }
411
412                                        //
413                                        // set the positions
414                                        //
415
416                                        var main_p = Math.round(cen),
417                                                offset = 0;
418
419                                        if(cen < 0){
420
421                                                main_p = 0;
422
423                                        }else if(cen > this.itemCount - 1){
424
425                                                main_p = this.itemCount -1;
426
427                                        }else{
428
429                                                offset = (cen - main_p) * ((this.isHorizontal ? this.itemWidth : this.itemHeight) - this.children[main_p].sizeMain);
430                                        }
431
432                                        this._positionElementsFrom(main_p, offset);
433                                },
434
435                                _weighAt: function(/*Integer*/ cen, /*Integer*/ i){
436                                        var dist = Math.abs(cen - i),
437                                                limit = ((cen - i) > 0) ? this.children[i].effectRangeRght : this.children[i].effectRangeLeft;
438
439                                        return (dist > limit) ? 0 : (1 - dist / limit); // Integer
440                                },
441
442                                _setItemSize: function(p, scale){
443                                        if(this.children[p].scale == scale){ return; }
444                                        this.children[p].scale = scale;
445
446                                        scale *= this.timerScale;
447                                        var w = Math.round(this.itemWidth  + ((this.itemMaxWidth  - this.itemWidth ) * scale)),
448                                                h = Math.round(this.itemHeight + ((this.itemMaxHeight - this.itemHeight) * scale));
449
450                                        if(this.isHorizontal){
451
452                                                this.children[p].sizeW = w;
453                                                this.children[p].sizeH = h;
454
455                                                this.children[p].sizeMain = w;
456                                                this.children[p].sizeOff  = h;
457
458                                                var y = 0;
459                                                if(this.anchorEdge == this.EDGE.TOP){
460                                                        y = (this.children[p].cenY - (this.itemHeight / 2));
461                                                }else if(this.anchorEdge == this.EDGE.BOTTOM){
462                                                        y = (this.children[p].cenY - (h - (this.itemHeight / 2)));
463                                                }else{
464                                                        y = (this.children[p].cenY - (h / 2));
465                                                }
466
467                                                this.children[p].usualX = Math.round(this.children[p].cenX - (w / 2));
468                                                style.set(this.children[p].domNode, {top: y + 'px', left: this.children[p].usualX + 'px'});
469
470                                        }else{
471
472                                                this.children[p].sizeW = w;
473                                                this.children[p].sizeH = h;
474
475                                                this.children[p].sizeOff  = w;
476                                                this.children[p].sizeMain = h;
477
478                                                var x = 0;
479                                                if(this.anchorEdge == this.EDGE.LEFT){
480                                                        x = this.children[p].cenX - (this.itemWidth / 2);
481                                                }else if(this.anchorEdge == this.EDGE.RIGHT){
482                                                        x = this.children[p].cenX - (w - (this.itemWidth / 2));
483                                                }else{
484                                                        x = this.children[p].cenX - (w / 2);
485                                                }
486
487                                                this.children[p].usualY = Math.round(this.children[p].cenY - (h / 2));
488                                                style.set(this.children[p].domNode, {left: x + 'px', top: this.children[p].usualY + 'px'});
489                                        }
490
491                                        style.set(this.children[p].domNode, {width: w + 'px', height: h + 'px'});
492
493                                        if(this.children[p].svgNode){
494                                                this.children[p].svgNode.setSize(w, h);
495                                        }
496                                },
497
498                                _positionElementsFrom: function(p, offset){
499                                        var pos = 0;
500
501                                        var usual, start;
502                                        if(this.isHorizontal){
503                                                usual = "usualX";
504                                                start = "left";
505                                        }else{
506                                                usual = "usualY";
507                                                start = "top";
508                                        }
509                                        pos = Math.round(this.children[p][usual] + offset);
510                                        if(style.get(this.children[p].domNode, start) != (pos + 'px')){
511                                                style.set(this.children[p].domNode, start, pos + 'px');
512                                                this._positionLabel(this.children[p]);
513                                        }
514
515                                        // position before
516                                        var bpos = pos;
517                                        for(var i=p-1; i>=0; i--){
518                                                bpos -= this.children[i].sizeMain;
519
520                                                if(style.get(this.children[p].domNode, start) != (bpos + 'px')){
521                                                        style.set(this.children[i].domNode, start, bpos + 'px');
522                                                        this._positionLabel(this.children[i]);
523                                                }
524                                        }
525
526                                        // position after
527                                        var apos = pos;
528                                        for(i=p+1; i<this.itemCount; i++){
529                                                apos += this.children[i-1].sizeMain;
530                                                if(style.get(this.children[p].domNode, start) != (apos + 'px')){
531                                                        style.set(this.children[i].domNode, start, apos + 'px');
532                                                        this._positionLabel(this.children[i]);
533                                                }
534                                        }
535
536                                },
537
538                                _positionLabel: function(itm){
539                                        var x = 0;
540                                        var y = 0;
541
542                                        var mb = geometry.getMarginBox(itm.lblNode);
543
544                                        if(this.labelEdge == this.EDGE.TOP){
545                                                x = Math.round((itm.sizeW / 2) - (mb.w / 2));
546                                                y = -mb.h;
547                                        }
548
549                                        if(this.labelEdge == this.EDGE.BOTTOM){
550                                                x = Math.round((itm.sizeW / 2) - (mb.w / 2));
551                                                y = itm.sizeH;
552                                        }
553
554                                        if(this.labelEdge == this.EDGE.LEFT){
555                                                x = -mb.w;
556                                                y = Math.round((itm.sizeH / 2) - (mb.h / 2));
557                                        }
558
559                                        if(this.labelEdge == this.EDGE.RIGHT){
560                                                x = itm.sizeW;
561                                                y = Math.round((itm.sizeH / 2) - (mb.h / 2));
562                                        }
563
564                                        style.set(itm.lblNode, {left: x + 'px', top: y + 'px'});
565                                },
566
567                                _calcHitGrid: function(){
568
569                                        var pos = geometry.position(this.domNode, true);
570
571                                        this.hitX1 = pos.x - this.proximityLeft;
572                                        this.hitY1 = pos.y - this.proximityTop;
573                                        this.hitX2 = this.hitX1 + this.totalWidth;
574                                        this.hitY2 = this.hitY1 + this.totalHeight;
575
576                                },
577
578                                _toEdge: function(inp, def){
579                                        return this.EDGE[inp.toUpperCase()] || def;
580                                },
581
582                                _expandSlowly: function(){
583                                        // summary:
584                                        //              slowly expand the image to user specified max size
585                                        if(!this.isOver){ return; }
586                                        this.timerScale += 0.2;
587                                        this._paint();
588                                        if(this.timerScale<1.0){
589                                                setTimeout(lang.hitch(this, "_expandSlowly"), 10);
590                                        }
591                                }
592                        });
593
594        });
Note: See TracBrowser for help on using the repository browser.