source: Dev/trunk/src/client/dojox/image/Lightbox.js @ 529

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

Added Dojo 1.9.3 release.

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