source: Dev/trunk/src/client/dojox/image/SlideShow.js @ 532

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

Added Dojo 1.9.3 release.

File size: 19.0 KB
Line 
1dojo.provide("dojox.image.SlideShow");
2//
3// dojox.image.SlideShow courtesy Shane O Sullivan, licensed under a Dojo CLA
4// For a sample usage, see http://www.skynet.ie/~sos/photos.php
5//
6//
7//      TODO: more cleanups
8//
9dojo.require("dojo.string");
10dojo.require("dojo.fx");
11dojo.require("dijit._Widget");
12dojo.require("dijit._Templated");
13
14dojo.declare("dojox.image.SlideShow",
15        [dijit._Widget, dijit._Templated],
16        {
17        // summary:
18        //              A Slideshow Widget
19
20        // imageHeight: Number
21        //              The maximum height of an image
22        imageHeight: 375,
23       
24        // imageWidth: Number
25        //              The maximum width of an image.
26        imageWidth: 500,
27
28        // title: String
29        //              The initial title of the SlideShow
30        title: "",
31
32        // titleTemplate: String
33        //      a way to customize the wording in the title. supported parameters to be populated are:
34        //              ${title} = the passed title of the image
35        //              ${current} = the current index of the image
36        //              ${total} = the total number of images in the SlideShow
37        //
38        //      should add more?
39        titleTemplate: '${title} <span class="slideShowCounterText">(${current} of ${total})</span>',
40
41        // noLink: Boolean
42        //      Prevents the slideshow from putting an anchor link around the displayed image
43        //      enables if true, though still will not link in absence of a url to link to
44        noLink: false,
45
46        // loop: Boolean
47        //      true/false - make the slideshow loop
48        loop: true,
49
50        // hasNav: Boolean
51        //      toggle to enable/disable the visual navigation controls
52        hasNav: true,
53
54        // images: Array
55        //      Contains the DOM nodes that individual images are stored in when loaded or loading.
56        images: [],
57       
58        // pageSize: Number
59        //      The number of images to request each time.
60        pageSize: 20,
61               
62        // autoLoad: Boolean
63        //      If true, then images are preloaded, before the user navigates to view them.
64        //      If false, an image is not loaded until the user views it.
65        autoLoad: true,
66
67        // autoStart: Boolean
68        //      If true, the SlideShow begins playing immediately
69        autoStart: false,
70       
71        // fixedHeight: Boolean
72        //      If true, the widget does not resize itself to fix the displayed image.
73        fixedHeight: false,
74
75        // imageStore: Object
76        //      Implementation of the dojo/data/api/Read API, which provides data on the images
77        //      to be displayed.
78        imageStore: null,
79               
80        // linkAttr: String
81        //      Defines the name of the attribute to request from the store to retrieve the
82        //      URL to link to from an image, if any.
83        linkAttr: "link",
84       
85        // imageLargeAttr: String
86        //      Defines the name of the attribute to request from the store to retrieve the
87        //      URL to the image.
88        imageLargeAttr: "imageUrl",
89       
90        // titleAttr: String
91        //      Defines the name of the attribute to request from the store to retrieve the
92        //      title of the picture, if any.
93        titleAttr: "title",
94
95        // slideshowInterval: Number
96        //      Time, in seconds, between image transitions during a slideshow.
97        slideshowInterval: 3,
98       
99        templateString: dojo.cache("dojox.image", "resources/SlideShow.html"),
100       
101        // _imageCounter: Number
102        //      A counter to keep track of which index image is to be loaded next
103        _imageCounter: 0,
104       
105        // _tmpImage: DomNode
106        //      The temporary image to show when a picture is loading.
107        _tmpImage: null,
108       
109        // _request: Object
110        //      Implementation of the dojo/data/api/Request API, which defines the query
111        //      parameters for accessing the store.
112        _request: null,
113
114        postCreate: function(){
115                // summary:
116                //              Initializes the widget, sets up listeners and shows the first image
117                this.inherited(arguments);
118                var img = document.createElement("img");
119
120                // FIXME: should API be to normalize an image to fit in the specified height/width?
121                img.setAttribute("width", this.imageWidth);
122                img.setAttribute("height", this.imageHeight);
123
124                if(this.hasNav){
125                        dojo.connect(this.outerNode, "onmouseover", this, function(evt){
126                                try{ this._showNav();}
127                                catch(e){} //TODO: remove try/catch
128                        });
129                        dojo.connect(this.outerNode, "onmouseout", this, function(evt){
130                                try{ this._hideNav(evt);}
131                                catch(e){} //TODO: remove try/catch
132                        });
133                }
134               
135                this.outerNode.style.width = this.imageWidth + "px";
136
137                img.setAttribute("src", this._blankGif);
138                var _this = this;
139               
140                this.largeNode.appendChild(img);
141                this._tmpImage = this._currentImage = img;
142                this._fitSize(true);
143               
144                this._loadImage(0, dojo.hitch(this, "showImage", 0));
145                this._calcNavDimensions();
146                dojo.style(this.navNode, "opacity", 0);
147        },
148
149        setDataStore: function(dataStore, request, /*optional*/paramNames){
150                // summary:
151                //              Sets the data store and request objects to read data from.
152                // dataStore:
153                //              An implementation of the dojo/data/api/Read API. This accesses the image
154                //              data.
155                // request:
156                //              An implementation of the dojo/data/api/Request API. This specifies the
157                //              query and paging information to be used by the data store
158                // paramNames:
159                //              An object defining the names of the item attributes to fetch from the
160                //              data store.  The three attributes allowed are 'linkAttr', 'imageLargeAttr' and 'titleAttr'
161                this.reset();
162                var _this = this;
163
164                this._request = {
165                        query: {},
166                        start: request.start || 0,
167                        count: request.count || this.pageSize,
168                        onBegin: function(count, request){
169                                // FIXME: fires too often?!?
170                                _this.maxPhotos = count;
171                        }
172                };
173                if(request.query){
174                        dojo.mixin(this._request.query, request.query);
175                }
176                if(paramNames){
177                        dojo.forEach(["imageLargeAttr", "linkAttr", "titleAttr"], function(attrName){
178                                if(paramNames[attrName]){
179                                        this[attrName] = paramNames[attrName];
180                                }
181                        }, this);
182                }
183       
184                var _complete = function(items){
185                        // FIXME: onBegin above used to work for maxPhotos:
186                        _this.maxPhotos = items.length;
187                        _this._request.onComplete = null;
188                        if(_this.autoStart){
189                                _this.imageIndex = -1;
190                                _this.toggleSlideShow();
191                        } else {
192                                _this.showImage(0);
193                        }
194                       
195                };
196
197                this.imageStore = dataStore;
198                this._request.onComplete = _complete;
199                this._request.start = 0;
200                this.imageStore.fetch(this._request);
201        },
202
203        reset: function(){
204                // summary:
205                //              Resets the widget to its initial state
206                // description:
207                //              Removes all previously loaded images, and clears all caches.
208                dojo.query("> *", this.largeNode).orphan();
209                this.largeNode.appendChild(this._tmpImage);
210               
211                dojo.query("> *", this.hiddenNode).orphan();
212                dojo.forEach(this.images, function(img){
213                        if(img && img.parentNode){ img.parentNode.removeChild(img); }
214                });
215                this.images = [];
216                this.isInitialized = false;
217                this._imageCounter = 0;
218        },
219
220        isImageLoaded: function(index){
221                // summary:
222                //              Returns true if image at the specified index is loaded, false otherwise.
223                // index:
224                //              The number index in the data store to check if it is loaded.
225                return this.images && this.images.length > index && this.images[index];
226        },
227
228        moveImageLoadingPointer: function(index){
229                // summary:
230                //              If 'autoload' is true, this tells the widget to start loading
231                //              images from the specified pointer.
232                // index:
233                //              The number index in the data store to start loading images from.
234                this._imageCounter = index;
235        },
236       
237        destroy: function(){
238                // summary:
239                //              Cleans up the widget when it is being destroyed
240                if(this._slideId) { this._stop(); }
241                this.inherited(arguments);
242        },
243
244        showNextImage: function(inTimer, forceLoop){
245                // summary:
246                //              Changes the image being displayed to the next image in the data store
247                // inTimer: Boolean
248                //              If true, a slideshow is active, otherwise the slideshow is inactive.
249                if(inTimer && this._timerCancelled){ return false; }
250               
251                if(this.imageIndex + 1 >= this.maxPhotos){
252                        if(inTimer && (this.loop || forceLoop)){
253                                this.imageIndex = -1;
254                        }else{
255                                if(this._slideId){ this._stop(); }
256                                return false;
257                        }
258                }
259
260                this.showImage(this.imageIndex + 1, dojo.hitch(this,function(){
261                        if(inTimer){ this._startTimer(); }
262                }));
263                return true;
264        },
265
266        toggleSlideShow: function(){
267                // summary:
268                //              Switches the slideshow mode on and off.
269               
270                // If the slideshow is already running, stop it.
271                if(this._slideId){
272                        this._stop();
273                }else{
274                        dojo.toggleClass(this.domNode,"slideShowPaused");
275                        this._timerCancelled = false;
276                        var idx = this.imageIndex;
277
278                        if(idx < 0 || (this.images[idx] && this.images[idx]._img.complete)){
279                                var success = this.showNextImage(true, true);
280
281                                if(!success){
282                                        this._stop();
283                                }
284                        }else{
285                                var handle = dojo.subscribe(this.getShowTopicName(), dojo.hitch(this, function(info){
286                                        setTimeout(dojo.hitch(this, function(){
287                                                if(info.index == idx){
288                                                        var success = this.showNextImage(true, true);
289                                                        if(!success){
290                                                                this._stop();
291                                                        }
292                                                        dojo.unsubscribe(handle);
293                                                }}),
294                                                this.slideshowInterval * 1000);
295                                }));
296                                dojo.publish(this.getShowTopicName(),
297                                  [{index: idx, title: "", url: ""}]);
298                        }
299                }
300        },
301
302        getShowTopicName: function(){
303                // summary:
304                //              Returns the topic id published to when an image is shown
305                // description:
306                //              The information published is: index, title and url
307                return (this.widgetId || this.id) + "/imageShow";
308        },
309
310        getLoadTopicName: function(){
311                // summary:
312                //              Returns the topic id published to when an image finishes loading.
313                // description:
314                //              The information published is the index position of the image loaded.
315                return (this.widgetId ? this.widgetId : this.id) + "/imageLoad";
316        },
317
318        showImage: function(index, /* Function? */callback){
319                // summary:
320                //              Shows the image at index 'index'.
321                // index: Number
322                //              The position of the image in the data store to display
323                // callback: Function
324                //              Optional callback function to call when the image has finished displaying.
325
326                if(!callback && this._slideId){
327                        this.toggleSlideShow();
328                }
329                var _this = this;
330                var current = this.largeNode.getElementsByTagName("div");
331                this.imageIndex = index;
332
333                var showOrLoadIt = function() {
334                        //If the image is already loaded, then show it.
335                        if(_this.images[index]){
336                                while(_this.largeNode.firstChild){
337                                        _this.largeNode.removeChild(_this.largeNode.firstChild);
338                                }
339                                dojo.style(_this.images[index],"opacity", 0);
340                                _this.largeNode.appendChild(_this.images[index]);
341                                _this._currentImage = _this.images[index]._img;
342                                _this._fitSize();
343                                                               
344                                var onEnd = function(a,b,c){
345
346                                        var img = _this.images[index].firstChild;
347                                        if(img.tagName.toLowerCase() != "img"){ img = img.firstChild; }
348                                        var title = img.getAttribute("title") || "";
349                                        if(_this._navShowing){
350                                                _this._showNav(true);
351                                        }
352                                        dojo.publish(_this.getShowTopicName(), [{
353                                                index: index,
354                                                title: title,
355                                                url: img.getAttribute("src")
356                                        }]);
357
358                                        if(callback) {
359                                                callback(a,b,c);
360                                        }
361                                        _this._setTitle(title);
362                                };
363                               
364                                dojo.fadeIn({
365                                        node: _this.images[index],
366                                        duration: 300,
367                                        onEnd: onEnd
368                                }).play();
369                               
370                        }else{
371                                //If the image is not loaded yet, load it first, then show it.
372                                _this._loadImage(index, function(){
373                                        _this.showImage(index, callback);
374                                });
375                        }
376                };
377
378                //If an image is currently showing, fade it out, then show
379                //the new image. Otherwise, just show the new image.
380                if(current && current.length > 0){
381                        dojo.fadeOut({
382                                node: current[0],
383                                duration: 300,
384                                onEnd: function(){
385                                        _this.hiddenNode.appendChild(current[0]);
386                                        showOrLoadIt();
387                                }
388                        }).play();
389                }else{
390                        showOrLoadIt();
391                }
392        },
393       
394        _fitSize: function(force){
395                // summary:
396                //              Fits the widget size to the size of the image being shown,
397                //              or centers the image, depending on the value of 'fixedHeight'
398                // force: Boolean
399                //              If true, the widget is always resized, regardless of the value of 'fixedHeight'
400                if(!this.fixedHeight || force){
401                        var height = (this._currentImage.height + (this.hasNav ? 20:0));
402                        dojo.style(this.innerWrapper, "height", height + "px");
403                        return;
404                }
405                dojo.style(this.largeNode, "paddingTop", this._getTopPadding() + "px");
406        },
407       
408        _getTopPadding: function(){
409                // summary:
410                //              Returns the padding to place at the top of the image to center it vertically.
411                if(!this.fixedHeight){ return 0; }
412                return (this.imageHeight - this._currentImage.height) / 2;
413        },
414       
415        _loadNextImage: function(){
416                // summary:
417                //              Load the next unloaded image.
418
419                if(!this.autoLoad){
420                        return;
421                }
422                while(this.images.length >= this._imageCounter && this.images[this._imageCounter]){
423                        this._imageCounter++;
424                }
425                this._loadImage(this._imageCounter);
426        },
427       
428        _loadImage: function(index, callbackFn){
429                // summary:
430                //              Load image at specified index
431                // description:
432                //              This function loads the image at position 'index' into the
433                //              internal cache of images.  This does not cause the image to be displayed.
434                // index:
435                //              The position in the data store to load an image from.
436                // callbackFn:
437                //              An optional function to execute when the image has finished loading.
438
439                if(this.images[index] || !this._request) {
440                        return;
441                }
442               
443                var pageStart = index - (index % (this._request.count || this.pageSize));
444
445                this._request.start = pageStart;
446
447                this._request.onComplete = function(items){
448                        var diff = index - pageStart;
449                       
450                        if(items && items.length > diff){
451                                loadIt(items[diff]);
452                        }else{ /* Squelch - console.log("Got an empty set of items"); */ }
453                }
454
455                var _this = this;
456                var store = this.imageStore;
457                var loadIt = function(item){
458                        var url = _this.imageStore.getValue(item, _this.imageLargeAttr);
459                       
460                        var img = new Image();  // when creating img with "createElement" IE doesnt has width and height, so use the Image object
461                        var div = dojo.create("div", {
462                                id: _this.id + "_imageDiv" + index
463                        });
464                        div._img = img;
465
466                        var link = _this.imageStore.getValue(item,_this.linkAttr);
467                        if(!link || _this.noLink){
468                                div.appendChild(img);
469                        }else{
470                                var a = dojo.create("a", {
471                                        "href": link,
472                                        "target": "_blank"
473                                }, div);
474                                a.appendChild(img);
475                        }
476
477                        dojo.connect(img, "onload", function(){
478                                if(store != _this.imageStore){
479                                        // If the store has changed, ignore this load event.
480                                        return;
481                                }
482                                _this._fitImage(img);
483                                dojo.attr(div, {"width": _this.imageWidth, "height": _this.imageHeight});
484                               
485                                // make a short timeout to prevent IE6/7 stack overflow at line 0 ~ still occuring though for first image
486                                dojo.publish(_this.getLoadTopicName(), [index]);
487
488                                setTimeout(function(){_this._loadNextImage();}, 1);
489                                if(callbackFn){ callbackFn(); }
490                        });
491                        _this.hiddenNode.appendChild(div);
492
493                        var titleDiv = dojo.create("div", {
494                                className: "slideShowTitle"
495                        }, div);
496
497                        _this.images[index] = div;
498                        dojo.attr(img, "src", url);
499                       
500                        var title = _this.imageStore.getValue(item, _this.titleAttr);
501                        if(title){ dojo.attr(img, "title", title); }
502                }
503                this.imageStore.fetch(this._request);
504        },
505
506        _stop: function(){
507                // summary:
508                //              Stops a running slide show.
509                if(this._slideId){ clearTimeout(this._slideId); }
510                this._slideId = null;
511                this._timerCancelled = true;
512                dojo.removeClass(this.domNode,"slideShowPaused");
513        },
514
515        _prev: function(){
516                // summary:
517                //              Show the previous image.
518
519                // FIXME: either pull code from showNext/prev, or call it here
520                if(this.imageIndex < 1){ return; }
521                this.showImage(this.imageIndex - 1);
522        },
523
524        _next: function(){
525                // summary:
526                //              Show the next image
527                this.showNextImage();
528        },
529
530        _startTimer: function(){
531                // summary:
532                //              Starts a timeout to show the next image when a slide show is active
533                var id = this.id;
534                this._slideId = setTimeout(function(){
535                        dijit.byId(id).showNextImage(true);
536                }, this.slideshowInterval * 1000);
537        },
538       
539        _calcNavDimensions: function() {
540                // summary:
541                //              Calculates the dimensions of the navigation controls
542                dojo.style(this.navNode, "position", "absolute");
543               
544                //Place the navigation controls far off screen
545                dojo.style(this.navNode, "top", "-10000px");
546               
547                dojo.style(this.navPlay, 'marginLeft', 0);
548               
549                this.navPlay._size = dojo.marginBox(this.navPlay);
550                this.navPrev._size = dojo.marginBox(this.navPrev);
551                this.navNext._size = dojo.marginBox(this.navNext);
552               
553                dojo.style(this.navNode, {"position": "", top: ""});
554        },
555
556        _setTitle: function(title){
557                // summary:
558                //              Sets the title to the image being displayed
559                // title: String
560                //              The String title of the image
561
562                this.titleNode.innerHTML = dojo.string.substitute(this.titleTemplate,{
563                        title: title,
564                        current: 1 + this.imageIndex,
565                        total: this.maxPhotos || ""
566                });
567        },
568       
569        _fitImage: function(img) {
570                // summary:
571                //              Ensures that the image width and height do not exceed the maximum.
572                // img: Node
573                //              The image DOM node to optionally resize
574                var width = img.width;
575                var height = img.height;
576               
577                if(width > this.imageWidth){
578                        height = Math.floor(height * (this.imageWidth / width));
579                        img.height = height;
580                        img.width = width = this.imageWidth;
581                }
582                if(height > this.imageHeight){
583                        width = Math.floor(width * (this.imageHeight / height));
584                        img.height = this.imageHeight;
585                        img.width = width;
586                }
587        },
588       
589        _handleClick: function(/* Event */e){
590                // summary:
591                //              Performs navigation on the images based on users mouse clicks
592                // e:
593                //              An Event object
594                switch(e.target){
595                        case this.navNext: this._next(); break;
596                        case this.navPrev: this._prev(); break;
597                        case this.navPlay: this.toggleSlideShow(); break;
598                }
599        },
600       
601        _showNav: function(force){
602                // summary:
603                //              Shows the navigation controls
604                // force: Boolean
605                //              If true, the navigation controls are repositioned even if they are
606                //              currently visible.
607                if(this._navShowing && !force){return;}
608                this._calcNavDimensions();
609                dojo.style(this.navNode, "marginTop", "0px");
610               
611               
612                var navPlayPos = dojo.style(this.navNode, "width")/2 - this.navPlay._size.w/2 - this.navPrev._size.w;
613                console.log('navPlayPos = ' + dojo.style(this.navNode, "width")/2 + ' - ' + this.navPlay._size.w + '/2 - '
614                                + this.navPrev._size.w);
615               
616                dojo.style(this.navPlay, "marginLeft", navPlayPos + "px");
617                var wrapperSize = dojo.marginBox(this.outerNode);
618               
619                var margin = this._currentImage.height - this.navPlay._size.h - 10 + this._getTopPadding();
620               
621                if(margin > this._currentImage.height){margin += 10;}
622                dojo[this.imageIndex < 1 ? "addClass":"removeClass"](this.navPrev, "slideShowCtrlHide");
623                dojo[this.imageIndex + 1 >= this.maxPhotos ? "addClass":"removeClass"](this.navNext, "slideShowCtrlHide");
624       
625                var _this = this;
626                if(this._navAnim) {
627                        this._navAnim.stop();
628                }
629                if(this._navShowing){ return; }
630                this._navAnim = dojo.fadeIn({
631                        node: this.navNode,
632                        duration: 300,
633                        onEnd: function(){ _this._navAnim = null; }
634                });
635                this._navAnim.play();
636                this._navShowing = true;
637        },
638       
639        _hideNav: function(/* Event */e){
640                // summary:
641                //              Hides the navigation controls
642                // e: Event
643                //              The DOM Event that triggered this function
644                if(!e || !this._overElement(this.outerNode, e)){
645                        var _this = this;
646                        if(this._navAnim){
647                                this._navAnim.stop();
648                        }
649                        this._navAnim = dojo.fadeOut({
650                                node: this.navNode,
651                                duration:300,
652                                onEnd: function(){ _this._navAnim = null; }
653                        });
654                        this._navAnim.play();
655                        this._navShowing = false;
656                }
657        },
658       
659        _overElement: function(/*DomNode*/element, /*Event*/e){
660                // summary:
661                //              Returns whether the mouse is over the passed element.
662                //              Element must be display:block (ie, not a `<span>`)
663               
664                //When the page is unloading, if this method runs it will throw an
665                //exception.
666                if(typeof(dojo) == "undefined"){ return false; }
667                element = dojo.byId(element);
668                var m = { x: e.pageX, y: e.pageY };
669                var bb = dojo.position(element, true);
670
671                return (m.x >= bb.x
672                        && m.x <= (bb.x + bb.w)
673                        && m.y >= bb.y
674                        && m.y <= (top + bb.h)
675                );      //      boolean
676        }
677});
Note: See TracBrowser for help on using the repository browser.