source: Dev/branches/rest-dojo-ui/client/dojox/image/Lightbox.js @ 256

Last change on this file since 256 was 256, checked in by hendrikvanantwerpen, 13 years ago

Reworked project structure based on REST interaction and Dojo library. As
soon as this is stable, the old jQueryUI branch can be removed (it's
kept for reference).

File size: 17.5 KB
Line 
1define(["dojo", "dijit", "dojox", "dojo/text!./resources/Lightbox.html", "dijit/Dialog", "dojox/fx/_base"], function(dojo, dijit, dojox, template){
2
3        dojo.experimental("dojox.image.Lightbox");
4        dojo.getObject("image", true, dojox);
5
6        dojo.declare("dojox.image.Lightbox", dijit._Widget, {
7                // summary:
8                //              A dojo-based Lightbox implementation.
9                //
10                // description:
11                //      An Elegant, keyboard accessible, markup and store capable Lightbox widget to show images
12                //      in a modal dialog-esque format. Can show individual images as Modal dialog, or can group
13                //      images with multiple entry points, all using a single "master" Dialog for visualization
14                //
15                //      key controls:
16                //              ESC - close
17                //              Down Arrow / Rt Arrow / N - Next Image
18                //              Up Arrow / Lf Arrow / P - Previous Image
19                //
20                // example:
21                // |    <a href="image1.jpg" dojoType="dojox.image.Lightbox">show lightbox</a>
22                //
23                // example:
24                // |    <a href="image2.jpg" dojoType="dojox.image.Lightbox" group="one">show group lightbox</a>
25                // |    <a href="image3.jpg" dojoType="dojox.image.Lightbox" group="one">show group lightbox</a>
26                //
27                // example:
28                // |    not implemented fully yet, though works with basic datastore access. need to manually call
29                // |    widget._attachedDialog.addImage(item,"fromStore") for each item in a store result set.
30                // |    <div dojoType="dojox.image.Lightbox" group="fromStore" store="storeName"></div>
31                //
32                // group: String
33                //              Grouping images in a page with similar tags will provide a 'slideshow' like grouping of images
34                group: "",
35
36                // title: String
37                //              A string of text to be shown in the Lightbox beneath the image (empty if using a store)
38                title: "",
39
40                // href; String
41                //              Link to image to use for this Lightbox node (empty if using a store).
42                href: "",
43
44                // duration: Integer
45                //              Generic time in MS to adjust the feel of widget. could possibly add various
46                //              durations for the various actions (dialog fadein, sizeing, img fadein ...)
47                duration: 500,
48
49                // modal: Boolean
50                //              If true, this Dialog instance will be truly modal and prevent closing until
51                //              explicitly told to by calling hide() or clicking the (x) - Defaults to false
52                //              to preserve previous behaviors. (aka: enable click-to-click on the underlay)
53                modal: false,
54
55                // _allowPassthru: Boolean
56                //              Privately set this to disable/enable natural link of anchor tags
57                _allowPassthru: false,
58
59                // _attachedDialg: dojox.image._LightboxDialog
60                //              The pointer to the global lightbox dialog for this widget
61                _attachedDialog: null, // try to share a single underlay per page?
62
63                startup: function(){
64                        this.inherited(arguments);
65                        // setup an attachment to the masterDialog (or create the masterDialog)
66                        var tmp = dijit.byId('dojoxLightboxDialog');
67                        if(tmp){
68                                this._attachedDialog = tmp;
69                        }else{
70                                // this is the first instance to start, so we make the masterDialog
71                                this._attachedDialog = new dojox.image.LightboxDialog({ id: "dojoxLightboxDialog" });
72                                this._attachedDialog.startup();
73                        }
74                        if(!this.store){
75                                // FIXME: full store support lacking, have to manually call this._attachedDialog.addImage(imgage,group) as it stands
76                                this._addSelf();
77                                this.connect(this.domNode, "onclick", "_handleClick");
78                        }
79
80                },
81
82                _addSelf: function(){
83                        // summary: Add this instance to the master LightBoxDialog
84                        this._attachedDialog.addImage({
85                                href: this.href,
86                                title: this.title
87                        }, this.group || null);
88                },
89
90                _handleClick: function(/* Event */e){
91                        // summary: Handle the click on the link
92                        if(!this._allowPassthru){ e.preventDefault(); }
93                        else{ return; }
94                        this.show();
95                },
96
97                show: function(){
98                        // summary: Show the Lightbox with this instance as the starting point
99                        this._attachedDialog.show(this);
100                },
101
102                hide: function(){
103                        // summary: Hide the Lightbox currently showing
104                        this._attachedDialog.hide();
105                },
106
107                // FIXME: switch to .attr, deprecate eventually.
108                disable: function(){
109                        // summary: Disables event clobbering and dialog, and follows natural link
110                        this._allowPassthru = true;
111                },
112
113                enable: function(){
114                        // summary: Enables the dialog (prevents default link)
115                        this._allowPassthru = false;
116                },
117
118                onClick: function(){
119                        // summary:
120                        //              Stub fired when the image in the lightbox is clicked.
121                },
122
123                destroy: function(){
124                        this._attachedDialog.removeImage(this);
125                        this.inherited(arguments);
126                }
127
128        });
129
130        dojo.declare("dojox.image.LightboxDialog",
131                dijit.Dialog, {
132                // summary:
133                //              The "dialog" shared      between any Lightbox instances on the page, publically available
134                //              for programatic manipulation.
135                //
136                // description:
137                //
138                //              A widget that intercepts anchor links (typically around images)
139                //              and displays a modal Dialog. this is the actual Dialog, which you can
140                //              create and populate manually, though should use simple Lightbox's
141                //              unless you need the direct access.
142                //
143                //              There should only be one of these on a page, so all dojox.image.Lightbox's will us it
144                //              (the first instance of a Lightbox to be show()'n will create me If i do not exist)
145                //
146                //      example:
147                //      |       // show a single image from a url
148                //      |       var url = "http://dojotoolkit.org/logo.png";
149                //      |       var dialog = new dojox.image.LightboxDialog().startup();
150                //      |       dialog.show({ href: url, title:"My Remote Image"});
151                //
152                // title: String
153                //              The current title, read from object passed to show()
154                title: "",
155
156                // FIXME: implement titleTemplate
157
158                // inGroup: Array
159                //              Array of objects. this is populated by from the JSON object _groups, and
160                //              should not be populate manually. it is a placeholder for the currently
161                //              showing group of images in this master dialog
162                inGroup: null,
163
164                // imgUrl: String
165                //              The src="" attribute of our imageNode (can be null at statup)
166                imgUrl: dijit._Widget.prototype._blankGif,
167
168                // errorMessage: String
169                //              The text to display when an unreachable image is linked
170                errorMessage: "Image not found.",
171
172                // adjust: Boolean
173                //              If true, ensure the image always stays within the viewport
174                //              more difficult than necessary to disable, but enabled by default
175                //              seems sane in most use cases.
176                adjust: true,
177
178                // modal: Boolean
179                //              If true, this Dialog instance will be truly modal and prevent closing until
180                //              explicitly told to by calling hide() or clicking the (x) - Defaults to false
181                //              to preserve previous behaviors. (aka: enable click-to-click on the underlay)
182                modal: false,
183
184        /*=====
185                // _groups: Object
186                //              an object of arrays, each array (of objects) being a unique 'group'
187                _groups: { XnoGroupX: [] },
188
189        =====*/
190
191                // errorImg: Url
192                //              Path to the image used when a 404 is encountered
193                errorImg: dojo.moduleUrl("dojox.image","resources/images/warning.png"),
194
195                templateString: template,
196               
197                constructor: function(args){
198                        this._groups = this._groups || (args && args._groups) || { XnoGroupX:[] };
199                },
200
201                startup: function(){
202                        // summary: Add some extra event handlers, and startup our superclass.
203                        //
204                        // returns: dijit._Widget
205                        //              Perhaps the only `dijit._Widget` that returns itself to allow
206                        //              'chaining' or var referencing with .startup()
207
208                        this.inherited(arguments);
209
210                        this._animConnects = [];
211                        this.connect(this.nextButtonNode, "onclick", "_nextImage");
212                        this.connect(this.prevButtonNode, "onclick", "_prevImage");
213                        this.connect(this.closeButtonNode, "onclick", "hide");
214                        this._makeAnims();
215                        this._vp = dojo.window.getBox();
216                        return this;
217                },
218
219                show: function(/* Object */groupData){
220                        // summary: Show the Master Dialog. Starts the chain of events to show
221                        //              an image in the dialog, including showing the dialog if it is
222                        //              not already visible
223                        //
224                        // groupData: Object
225                        //              needs href and title attributes. the values for this image.
226                        //
227                        //
228                        var _t = this; // size
229                        this._lastGroup = groupData;
230
231                        // we only need to call dijit.Dialog.show() if we're not already open.
232                        if(!_t.open){
233                                _t.inherited(arguments);
234                                _t._modalconnects.push(
235                                        dojo.connect(dojo.global, "onscroll", this, "_position"),
236                                        dojo.connect(dojo.global, "onresize", this, "_position"),
237                                        dojo.connect(dojo.body(), "onkeypress", this, "_handleKey")
238                                );
239                                if(!groupData.modal){
240                                        _t._modalconnects.push(
241                                                dojo.connect(dijit._underlay.domNode, "onclick", this, "onCancel")
242                                        );
243                                }
244                        }
245
246                        if(this._wasStyled){
247                                // ugly fix for IE being stupid. place the new image relative to the old
248                                // image to allow for overriden templates to adjust the location of the
249                                // titlebar. DOM will remain "unchanged" between views.
250                                var tmpImg = dojo.create("img", null, _t.imgNode, "after");
251                                dojo.destroy(_t.imgNode);
252                                _t.imgNode = tmpImg;
253                                _t._makeAnims();
254                                _t._wasStyled = false;
255                        }
256
257                        dojo.style(_t.imgNode,"opacity","0");
258                        dojo.style(_t.titleNode,"opacity","0");
259
260                        var src = groupData.href;
261
262                        if((groupData.group && groupData !== "XnoGroupX") || _t.inGroup){
263                                if(!_t.inGroup){
264                                        _t.inGroup = _t._groups[(groupData.group)];
265                                        // determine where we were or are in the show
266                                        dojo.forEach(_t.inGroup, function(g, i){
267                                                if(g.href == groupData.href){
268                                                        _t._index = i;
269                                                        //return false;
270                                                }
271                                                //return true;
272                                        });
273                                }
274                                if(!_t._index){
275                                        _t._index = 0;
276                                        var sr = _t.inGroup[_t._index];
277                                        src = (sr && sr.href) || _t.errorImg;
278                                }
279                                // FIXME: implement titleTemplate
280                                _t.groupCount.innerHTML = " (" + (_t._index + 1) + " of " + Math.max(1, _t.inGroup.length) + ")";
281                                _t.prevButtonNode.style.visibility = "visible";
282                                _t.nextButtonNode.style.visibility = "visible";
283                        }else{
284                                // single images don't have buttons, or counters:
285                                _t.groupCount.innerHTML = "";
286                                _t.prevButtonNode.style.visibility = "hidden";
287                                _t.nextButtonNode.style.visibility = "hidden";
288                        }
289                        if(!groupData.leaveTitle){
290                                _t.textNode.innerHTML = groupData.title;
291                        }
292                        _t._ready(src);
293                },
294
295                _ready: function(src){
296                        // summary: A function to trigger all 'real' showing of some src
297
298                        var _t = this;
299
300                        // listen for 404's:
301                        _t._imgError = dojo.connect(_t.imgNode, "error", _t, function(){
302                                dojo.disconnect(_t._imgError);
303                                // trigger the above onload with a new src:
304                                _t.imgNode.src = _t.errorImg;
305                                _t.textNode.innerHTML = _t.errorMessage;
306                        });
307
308                        // connect to the onload of the image
309                        _t._imgConnect = dojo.connect(_t.imgNode, "load", _t, function(e){
310                                _t.resizeTo({
311                                        w: _t.imgNode.width,
312                                        h: _t.imgNode.height,
313                                        duration:_t.duration
314                                });
315                                // cleanup
316                                dojo.disconnect(_t._imgConnect);
317                                if(_t._imgError){
318                                        dojo.disconnect(_t._imgError);
319                                }
320                        });
321
322                        _t.imgNode.src = src;
323                },
324
325                _nextImage: function(){
326                        // summary: Load next image in group
327                        if(!this.inGroup){ return; }
328                        if(this._index + 1 < this.inGroup.length){
329                                this._index++;
330                        }else{
331                                this._index = 0;
332                        }
333                        this._loadImage();
334                },
335
336                _prevImage: function(){
337                        // summary: Load previous image in group
338                        if(this.inGroup){
339                                if(this._index == 0){
340                                        this._index = this.inGroup.length - 1;
341                                }else{
342                                        this._index--;
343                                }
344                                this._loadImage();
345                        }
346                },
347
348                _loadImage: function(){
349                        // summary: Do the prep work before we can show another image
350                        this._loadingAnim.play(1);
351                },
352
353                _prepNodes: function(){
354                        // summary: A localized hook to accompany _loadImage
355                        this._imageReady = false;
356                        if(this.inGroup && this.inGroup[this._index]){
357                                this.show({
358                                        href: this.inGroup[this._index].href,
359                                        title: this.inGroup[this._index].title
360                                });
361                        }else{
362                                this.show({
363                                        title: this.errorMessage,
364                                        href: this.errorImg
365                                });
366                        }
367
368                },
369
370                _calcTitleSize: function(){
371                        var sizes = dojo.map(dojo.query("> *", this.titleNode).position(), function(s){ return s.h; });
372                        return { h: Math.max.apply(Math, sizes) };
373                },
374
375                resizeTo: function(/* Object */size, forceTitle){
376                        // summary: Resize our dialog container, and fire _showImage
377
378                        var adjustSize = dojo.boxModel == "border-box" ?
379                                dojo._getBorderExtents(this.domNode).w : 0,
380                                titleSize = forceTitle || this._calcTitleSize()
381                        ;
382
383                        this._lastTitleSize = titleSize;
384
385                        if(this.adjust &&
386                                (size.h + titleSize.h + adjustSize + 80 > this._vp.h ||
387                                 size.w + adjustSize + 60 > this._vp.w
388                                )
389                        ){
390                                this._lastSize = size;
391                                size = this._scaleToFit(size);
392                        }
393                        this._currentSize = size;
394
395                        var _sizeAnim = dojox.fx.sizeTo({
396                                node: this.containerNode,
397                                duration: size.duration||this.duration,
398                                width: size.w + adjustSize,
399                                height: size.h + titleSize.h + adjustSize
400                        });
401                        this.connect(_sizeAnim, "onEnd", "_showImage");
402                        _sizeAnim.play(15);
403                },
404
405                _scaleToFit: function(/* Object */size){
406                        // summary: resize an image to fit within the bounds of the viewport
407                        // size: Object
408                        //              The 'size' object passed around for this image
409
410                        var ns = {},   // New size
411                                nvp = {
412                                        w: this._vp.w - 80,
413                                        h: this._vp.h - 60 - this._lastTitleSize.h
414                                };      // New viewport
415
416                        // Calculate aspect ratio
417                        var viewportAspect = nvp.w / nvp.h,
418                                imageAspect = size.w / size.h;
419
420                        // Calculate new image size
421                        if(imageAspect >= viewportAspect){
422                                ns.h = nvp.w / imageAspect;
423                                ns.w = nvp.w;
424                        }else{
425                                ns.w = imageAspect * nvp.h;
426                                ns.h = nvp.h;
427                        }
428
429                        // we actually have to style this image, it's too big
430                        this._wasStyled = true;
431                        this._setImageSize(ns);
432
433                        ns.duration = size.duration;
434                        return ns; // Object
435                },
436
437                _setImageSize: function(size){
438                        // summary: Reset the image size to some actual size.
439                        var s = this.imgNode;
440                        s.height = size.h;
441                        s.width = size.w;
442                },
443
444                // clobber inherited function, it is useless.
445                _size: function(){},
446
447                _position: function(/* Event */e){
448                        // summary: we want to know the viewport size any time it changes
449                        this._vp = dojo.window.getBox();
450                        this.inherited(arguments);
451
452                        // determine if we need to scale up or down, if at all.
453                        if(e && e.type == "resize"){
454                                if(this._wasStyled){
455                                        this._setImageSize(this._lastSize);
456                                        this.resizeTo(this._lastSize);
457                                }else{
458                                        if(this.imgNode.height + 80 > this._vp.h || this.imgNode.width + 60 > this._vp.h){
459                                                this.resizeTo({
460                                                        w: this.imgNode.width, h: this.imgNode.height
461                                                });
462                                        }
463                                }
464                        }
465                },
466
467                _showImage: function(){
468                        // summary: Fade in the image, and fire showNav
469                        this._showImageAnim.play(1);
470                },
471
472                _showNav: function(){
473                        // summary: Fade in the footer, and setup our connections.
474                        var titleSizeNow = dojo.marginBox(this.titleNode);
475                        if(titleSizeNow.h > this._lastTitleSize.h){
476                                this.resizeTo(this._wasStyled ? this._lastSize : this._currentSize, titleSizeNow);
477                        }else{
478                                this._showNavAnim.play(1);
479                        }
480                },
481
482                hide: function(){
483                        // summary: Hide the Master Lightbox
484                        dojo.fadeOut({
485                                node: this.titleNode,
486                                duration: 200,
487                                // #5112 - if you _don't_ change the .src, safari will
488                                // _never_ fire onload for this image
489                                onEnd: dojo.hitch(this, function(){
490                                        this.imgNode.src = this._blankGif;
491                                })
492                        }).play(5);
493
494                        this.inherited(arguments);
495
496                        this.inGroup = null;
497                        this._index = null;
498                },
499
500                addImage: function(child, group){
501                        // summary: Add an image to this Master Lightbox
502                        //
503                        // child: Object
504                        //              The image information to add.
505                        //              href: String - link to image (required)
506                        //              title: String - title to display
507                        //
508                        // group: String?
509                        //              attach to group of similar tag or null for individual image instance
510                        var g = group;
511                        if(!child.href){ return; }
512                        if(g){
513                                if(!this._groups[g]){
514                                        this._groups[g] = [];
515                                }
516                                this._groups[g].push(child);
517                        }else{ this._groups["XnoGroupX"].push(child); }
518                },
519
520                removeImage: function(/* Widget */child){
521                        // summary: Remove an image instance from this LightboxDialog.
522                        // child: Object
523                        //              A reference to the Lightbox child that was added (or an object literal)
524                        //              only the .href member is compared for uniqueness. The object may contain
525                        //              a .group member as well.
526
527                        var g = child.group || "XnoGroupX";
528                        dojo.every(this._groups[g], function(item, i, ar){
529                                if(item.href == child.href){
530                                        ar.splice(i, 1);
531                                        return false;
532                                }
533                                return true;
534                        });
535                },
536
537                removeGroup: function(group){
538                        // summary: Remove all images in a passed group
539                        if(this._groups[group]){ this._groups[group] = []; }
540                },
541
542                _handleKey: function(/* Event */e){
543                        // summary: Handle keyboard navigation internally
544                        if(!this.open){ return; }
545
546                        var dk = dojo.keys;
547                        switch(e.charOrCode){
548
549                                case dk.ESCAPE:
550                                        this.hide();
551                                        break;
552
553                                case dk.DOWN_ARROW:
554                                case dk.RIGHT_ARROW:
555                                case 78: // key "n"
556                                        this._nextImage();
557                                        break;
558
559                                case dk.UP_ARROW:
560                                case dk.LEFT_ARROW:
561                                case 80: // key "p"
562                                        this._prevImage();
563                                        break;
564                        }
565                },
566
567                _makeAnims: function(){
568                        // summary: make and cleanup animation and animation connections
569
570                        dojo.forEach(this._animConnects, dojo.disconnect);
571                        this._animConnects = [];
572                        this._showImageAnim = dojo.fadeIn({
573                                        node: this.imgNode,
574                                        duration: this.duration
575                                });
576                        this._animConnects.push(dojo.connect(this._showImageAnim, "onEnd", this, "_showNav"));
577                        this._loadingAnim = dojo.fx.combine([
578                                        dojo.fadeOut({ node:this.imgNode, duration:175 }),
579                                        dojo.fadeOut({ node:this.titleNode, duration:175 })
580                                ]);
581                        this._animConnects.push(dojo.connect(this._loadingAnim, "onEnd", this, "_prepNodes"));
582                        this._showNavAnim = dojo.fadeIn({ node: this.titleNode, duration:225 });
583                },
584
585                onClick: function(groupData){
586                        // summary: a stub function, called with the currently displayed image as the only argument
587                },
588
589                _onImageClick: function(e){
590                        if(e && e.target == this.imgNode){
591                                this.onClick(this._lastGroup);
592                                // also fire the onclick for the Lightbox widget which triggered, if you
593                                // aren't working directly with the LBDialog
594                                if(this._lastGroup.declaredClass){
595                                        this._lastGroup.onClick(this._lastGroup);
596                                }
597                        }
598                }
599        });
600       
601
602        return dojox.image.Lightbox;
603
604});
605
Note: See TracBrowser for help on using the repository browser.