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