8398c9048d
code was modified slightly, so the code differs from the original downloadable 1.9.5 version
624 lines
19 KiB
JavaScript
624 lines
19 KiB
JavaScript
dojo.provide("dojox.image.SlideShow");
|
|
//
|
|
// dojox.image.SlideShow courtesy Shane O Sullivan, licensed under a Dojo CLA
|
|
// For a sample usage, see http://www.skynet.ie/~sos/photos.php
|
|
//
|
|
// @author Copyright 2007 Shane O Sullivan (shaneosullivan1@gmail.com)
|
|
//
|
|
// TODO: more cleanups
|
|
//
|
|
dojo.require("dojo.string");
|
|
dojo.require("dojo.fx");
|
|
dojo.require("dijit._Widget");
|
|
dojo.require("dijit._Templated");
|
|
|
|
dojo.declare("dojox.image.SlideShow",
|
|
[dijit._Widget, dijit._Templated],
|
|
{
|
|
// summary: A Slideshow Widget
|
|
|
|
// imageHeight: Number
|
|
// The maximum height of an image
|
|
imageHeight: 375,
|
|
|
|
// imageWidth: Number
|
|
// The maximum width of an image.
|
|
imageWidth: 500,
|
|
|
|
// title: String
|
|
// the initial title of the SlideShow
|
|
title: "",
|
|
|
|
// titleTemplate: String
|
|
// a way to customize the wording in the title. supported parameters to be populated are:
|
|
// ${title} = the passed title of the image
|
|
// ${current} = the current index of the image
|
|
// ${total} = the total number of images in the SlideShow
|
|
//
|
|
// should add more?
|
|
titleTemplate: '${title} <span class="slideShowCounterText">(${current} of ${total})</span>',
|
|
|
|
// noLink: Boolean
|
|
// Prevents the slideshow from putting an anchor link around the displayed image
|
|
// enables if true, though still will not link in absence of a url to link to
|
|
noLink: false,
|
|
|
|
// loop: Boolean
|
|
// true/false - make the slideshow loop
|
|
loop: true,
|
|
|
|
// hasNav: Boolean
|
|
// toggle to enable/disable the visual navigation controls
|
|
hasNav: true,
|
|
|
|
// images: Array
|
|
// Contains the DOM nodes that individual images are stored in when loaded or loading.
|
|
images: [],
|
|
|
|
// pageSize: Number
|
|
// The number of images to request each time.
|
|
pageSize: 20,
|
|
|
|
// autoLoad: Boolean
|
|
// If true, then images are preloaded, before the user navigates to view them.
|
|
// If false, an image is not loaded until the user views it.
|
|
autoLoad: true,
|
|
|
|
// autoStart: Boolean
|
|
// If true, the SlideShow begins playing immediately
|
|
autoStart: false,
|
|
|
|
// fixedHeight: Boolean
|
|
// If true, the widget does not resize itself to fix the displayed image.
|
|
fixedHeight: false,
|
|
|
|
// imageStore: Object
|
|
// Implementation of the dojo.data.api.Read API, which provides data on the images
|
|
// to be displayed.
|
|
imageStore: null,
|
|
|
|
// linkAttr: String
|
|
// Defines the name of the attribute to request from the store to retrieve the
|
|
// URL to link to from an image, if any.
|
|
linkAttr: "link",
|
|
|
|
// imageLargeAttr: String
|
|
// Defines the name of the attribute to request from the store to retrieve the
|
|
// URL to the image.
|
|
imageLargeAttr: "imageUrl",
|
|
|
|
// titleAttr: String
|
|
// Defines the name of the attribute to request from the store to retrieve the
|
|
// title of the picture, if any.
|
|
titleAttr: "title",
|
|
|
|
// slideshowInterval: Number
|
|
// Time, in seconds, between image transitions during a slideshow.
|
|
slideshowInterval: 3,
|
|
|
|
templatePath: dojo.moduleUrl("dojox.image", "resources/SlideShow.html"),
|
|
|
|
// _imageCounter: Number
|
|
// A counter to keep track of which index image is to be loaded next
|
|
_imageCounter: 0,
|
|
|
|
// _tmpImage: DomNode
|
|
// The temporary image to show when a picture is loading.
|
|
_tmpImage: null,
|
|
|
|
// _request: Object
|
|
// Implementation of the dojo.data.api.Request API, which defines the query
|
|
// parameters for accessing the store.
|
|
_request: null,
|
|
|
|
postCreate: function(){
|
|
// summary: Initilizes the widget, sets up listeners and shows the first image
|
|
this.inherited(arguments);
|
|
var img = document.createElement("img");
|
|
|
|
// FIXME: should API be to normalize an image to fit in the specified height/width?
|
|
img.setAttribute("width", this.imageWidth);
|
|
img.setAttribute("height", this.imageHeight);
|
|
|
|
if(this.hasNav){
|
|
dojo.connect(this.outerNode, "onmouseover", this, function(evt){
|
|
try{ this._showNav();}
|
|
catch(e){} //TODO: remove try/catch
|
|
});
|
|
dojo.connect(this.outerNode, "onmouseout", this, function(evt){
|
|
try{ this._hideNav(evt);}
|
|
catch(e){} //TODO: remove try/catch
|
|
});
|
|
}
|
|
|
|
this.outerNode.style.width = this.imageWidth + "px";
|
|
|
|
img.setAttribute("src", this._blankGif);
|
|
var _this = this;
|
|
|
|
this.largeNode.appendChild(img);
|
|
this._tmpImage = this._currentImage = img;
|
|
this._fitSize(true);
|
|
|
|
this._loadImage(0, dojo.hitch(this, "showImage", 0));
|
|
this._calcNavDimensions();
|
|
},
|
|
|
|
setDataStore: function(dataStore, request, /*optional*/paramNames){
|
|
// summary: Sets the data store and request objects to read data from.
|
|
// dataStore:
|
|
// An implementation of the dojo.data.api.Read API. This accesses the image
|
|
// data.
|
|
// request:
|
|
// An implementation of the dojo.data.api.Request API. This specifies the
|
|
// query and paging information to be used by the data store
|
|
// paramNames:
|
|
// An object defining the names of the item attributes to fetch from the
|
|
// data store. The three attributes allowed are 'linkAttr', 'imageLargeAttr' and 'titleAttr'
|
|
this.reset();
|
|
var _this = this;
|
|
|
|
this._request = {
|
|
query: {},
|
|
start: request.start || 0,
|
|
count: request.count || this.pageSize,
|
|
onBegin: function(count, request){
|
|
// FIXME: fires too often?!?
|
|
// console.log('fired', count, "is null");
|
|
_this.maxPhotos = count;
|
|
}
|
|
};
|
|
if(request.query){ dojo.mixin(this._request.query, request.query); }
|
|
if(paramNames){
|
|
dojo.forEach(["imageLargeAttr", "linkAttr", "titleAttr"], function(attrName){
|
|
if(paramNames[attrName]){ this[attrName] = paramNames[attrName]; }
|
|
}, this);
|
|
}
|
|
|
|
var _complete = function(items){
|
|
// FIXME: onBegin above used to work for maxPhotos:
|
|
_this.maxPhotos = items.length;
|
|
_this.showImage(0);
|
|
_this._request.onComplete = null;
|
|
if(_this.autoStart){
|
|
_this.toggleSlideShow();
|
|
}
|
|
};
|
|
|
|
this.imageStore = dataStore;
|
|
this._request.onComplete = _complete;
|
|
this._request.start = 0;
|
|
this.imageStore.fetch(this._request);
|
|
},
|
|
|
|
reset: function(){
|
|
// summary: Resets the widget to its initial state
|
|
// description: Removes all previously loaded images, and clears all caches.
|
|
while(this.largeNode.firstChild){
|
|
this.largeNode.removeChild(this.largeNode.firstChild);
|
|
}
|
|
this.largeNode.appendChild(this._tmpImage);
|
|
while(this.hiddenNode.firstChild){
|
|
this.hiddenNode.removeChild(this.hiddenNode.firstChild);
|
|
}
|
|
dojo.forEach(this.images, function(img){
|
|
if(img && img.parentNode){ img.parentNode.removeChild(img); }
|
|
});
|
|
this.images = [];
|
|
this.isInitialized = false;
|
|
this._imageCounter = 0;
|
|
},
|
|
|
|
isImageLoaded: function(index){
|
|
// summary: Returns true if image at the specified index is loaded, false otherwise.
|
|
// index:
|
|
// The number index in the data store to check if it is loaded.
|
|
return this.images && this.images.length > index && this.images[index];
|
|
},
|
|
|
|
moveImageLoadingPointer: function(index){
|
|
// summary: If 'autoload' is true, this tells the widget to start loading
|
|
// images from the specified pointer.
|
|
// index:
|
|
// The number index in the data store to start loading images from.
|
|
this._imageCounter = index;
|
|
},
|
|
|
|
destroy: function(){
|
|
// summary: Cleans up the widget when it is being destroyed
|
|
if(this._slideId) { this._stop(); }
|
|
this.inherited(arguments);
|
|
},
|
|
|
|
showNextImage: function(inTimer, forceLoop){
|
|
// summary: Changes the image being displayed to the next image in the data store
|
|
// inTimer: Boolean
|
|
// If true, a slideshow is active, otherwise the slideshow is inactive.
|
|
if(inTimer && this._timerCancelled){ return false; }
|
|
|
|
if(this.imageIndex + 1 >= this.maxPhotos){
|
|
if(inTimer && (this.loop || forceLoop)){
|
|
this.imageIndex = -1;
|
|
}else{
|
|
if(this._slideId){ this._stop(); }
|
|
return false;
|
|
}
|
|
}
|
|
|
|
this.showImage(this.imageIndex + 1, dojo.hitch(this,function(){
|
|
if(inTimer){ this._startTimer(); }
|
|
}));
|
|
return true;
|
|
},
|
|
|
|
toggleSlideShow: function(){
|
|
// summary: Switches the slideshow mode on and off.
|
|
if(this._slideId){
|
|
this._stop();
|
|
}else{
|
|
dojo.toggleClass(this.domNode,"slideShowPaused");
|
|
this._timerCancelled = false;
|
|
if(this.images[this.imageIndex] && this.images[this.imageIndex]._img.complete){
|
|
var success = this.showNextImage(true, true);
|
|
if(!success){
|
|
this._stop();
|
|
}
|
|
}else{
|
|
var idx = this.imageIndex;
|
|
var handle = dojo.subscribe(this.getShowTopicName(), dojo.hitch(this,function(info){
|
|
setTimeout(dojo.hitch(this,function(){
|
|
if(info.index == idx){
|
|
var success = this.showNextImage(true, true);
|
|
if(!success){
|
|
this._stop();
|
|
}
|
|
dojo.unsubscribe(handle);
|
|
}}),this.slideshowInterval * 1000);
|
|
}));
|
|
dojo.publish(this.getShowTopicName(),
|
|
[{index: idx, title: "", url: ""}]);
|
|
}
|
|
}
|
|
},
|
|
|
|
getShowTopicName: function(){
|
|
// summary: Returns the topic id published to when an image is shown
|
|
// description:
|
|
// The information published is: index, title and url
|
|
return (this.widgetId || this.id) + "/imageShow";
|
|
},
|
|
|
|
getLoadTopicName: function(){
|
|
// summary: Returns the topic id published to when an image finishes loading.
|
|
// description:
|
|
// The information published is the index position of the image loaded.
|
|
return (this.widgetId ? this.widgetId : this.id) + "/imageLoad";
|
|
},
|
|
|
|
showImage: function(index, /* Function? */callback){
|
|
// summary: Shows the image at index 'index'.
|
|
// index: Number
|
|
// The position of the image in the data store to display
|
|
// callback: Function
|
|
// Optional callback function to call when the image has finished displaying.
|
|
|
|
if(!callback && this._slideId){ this.toggleSlideShow(); }
|
|
var _this = this;
|
|
var current = this.largeNode.getElementsByTagName("div");
|
|
this.imageIndex = index;
|
|
|
|
var showOrLoadIt = function() {
|
|
//If the image is already loaded, then show it.
|
|
if(_this.images[index]){
|
|
while(_this.largeNode.firstChild){
|
|
_this.largeNode.removeChild(_this.largeNode.firstChild);
|
|
}
|
|
dojo.style(_this.images[index],"opacity", 0);
|
|
_this.largeNode.appendChild(_this.images[index]);
|
|
_this._currentImage = _this.images[index]._img;
|
|
_this._fitSize();
|
|
|
|
var onEnd = function(a,b,c){
|
|
|
|
var img = _this.images[index].firstChild;
|
|
if(img.tagName.toLowerCase() != "img"){ img = img.firstChild; }
|
|
var title = img.getAttribute("title") || "";
|
|
if(_this._navShowing){
|
|
_this._showNav(true);
|
|
}
|
|
dojo.publish(_this.getShowTopicName(), [{
|
|
index: index,
|
|
title: title,
|
|
url: img.getAttribute("src")
|
|
}]);
|
|
|
|
if(callback) { callback(a,b,c); }
|
|
_this._setTitle(title);
|
|
|
|
};
|
|
|
|
dojo.fadeIn({
|
|
node: _this.images[index],
|
|
duration: 300,
|
|
onEnd: onEnd
|
|
}).play();
|
|
|
|
}else{
|
|
//If the image is not loaded yet, load it first, then show it.
|
|
_this._loadImage(index, function(){
|
|
dojo.publish(_this.getLoadTopicName(), [index]);
|
|
_this.showImage(index, callback);
|
|
});
|
|
}
|
|
};
|
|
|
|
//If an image is currently showing, fade it out, then show
|
|
//the new image. Otherwise, just show the new image.
|
|
if(current && current.length > 0){
|
|
dojo.fadeOut({
|
|
node: current[0],
|
|
duration: 300,
|
|
onEnd: function(){
|
|
_this.hiddenNode.appendChild(current[0]);
|
|
showOrLoadIt();
|
|
}
|
|
}).play();
|
|
}else{
|
|
showOrLoadIt();
|
|
}
|
|
},
|
|
|
|
_fitSize: function(force){
|
|
// summary: Fits the widget size to the size of the image being shown,
|
|
// or centers the image, depending on the value of 'fixedHeight'
|
|
// force: Boolean
|
|
// If true, the widget is always resized, regardless of the value of 'fixedHeight'
|
|
if(!this.fixedHeight || force){
|
|
var height = (this._currentImage.height + (this.hasNav ? 20:0));
|
|
dojo.style(this.innerWrapper, "height", height + "px");
|
|
return;
|
|
}
|
|
dojo.style(this.largeNode, "paddingTop", this._getTopPadding() + "px");
|
|
},
|
|
|
|
_getTopPadding: function(){
|
|
// summary: Returns the padding to place at the top of the image to center it vertically.
|
|
if(!this.fixedHeight){ return 0; }
|
|
return (this.imageHeight - this._currentImage.height) / 2;
|
|
},
|
|
|
|
_loadNextImage: function(){
|
|
// summary: Load the next unloaded image.
|
|
if(!this.autoLoad){ return; }
|
|
while(this.images.length >= this._imageCounter && this.images[this._imageCounter]){
|
|
this._imageCounter++;
|
|
}
|
|
this._loadImage(this._imageCounter);
|
|
},
|
|
|
|
_loadImage: function(index, callbackFn){
|
|
// summary: Load image at specified index
|
|
// description:
|
|
// This function loads the image at position 'index' into the
|
|
// internal cache of images. This does not cause the image to be displayed.
|
|
// index:
|
|
// The position in the data store to load an image from.
|
|
// callbackFn:
|
|
// An optional function to execute when the image has finished loading.
|
|
if(this.images[index] || !this._request) { return; }
|
|
|
|
var pageStart = index - (index % this.pageSize);
|
|
|
|
this._request.start = pageStart;
|
|
|
|
this._request.onComplete = function(items){
|
|
var diff = index - pageStart;
|
|
if(items && items.length > diff){
|
|
loadIt(items[diff]);
|
|
}else{ /* Squelch - console.log("Got an empty set of items"); */ }
|
|
}
|
|
|
|
var _this = this;
|
|
var loadIt = function(item){
|
|
var url = _this.imageStore.getValue(item, _this.imageLargeAttr);
|
|
var img = new Image(); // when creating img with "createElement" IE doesnt has width and height, so use the Image object
|
|
var div = document.createElement("div");
|
|
div._img = img;
|
|
|
|
var link = _this.imageStore.getValue(item,_this.linkAttr);
|
|
if(!link || _this.noLink){
|
|
div.appendChild(img);
|
|
}else{
|
|
var a = document.createElement("a");
|
|
a.setAttribute("href", link);
|
|
a.setAttribute("target","_blank");
|
|
div.appendChild(a);
|
|
a.appendChild(img);
|
|
}
|
|
|
|
div.setAttribute("id",_this.id + "_imageDiv" + index);
|
|
dojo.connect(img, "onload", function(){
|
|
_this._fitImage(img);
|
|
div.setAttribute("width", _this.imageWidth);
|
|
div.setAttribute("height", _this.imageHeight);
|
|
|
|
dojo.publish(_this.getLoadTopicName(), [index]);
|
|
setTimeout(_this._loadNextImage, 1); // make a short timeout to prevent IE6/7 stack overflow at line 0 ~ still occuring though for first image
|
|
if(callbackFn){ callbackFn(); }
|
|
});
|
|
_this.hiddenNode.appendChild(div);
|
|
|
|
var titleDiv = document.createElement("div");
|
|
dojo.addClass(titleDiv, "slideShowTitle");
|
|
div.appendChild(titleDiv);
|
|
|
|
_this.images[index] = div;
|
|
img.setAttribute("src", url);
|
|
|
|
var title = _this.imageStore.getValue(item, _this.titleAttr);
|
|
if(title){ img.setAttribute("title", title); }
|
|
}
|
|
this.imageStore.fetch(this._request);
|
|
},
|
|
|
|
_stop: function(){
|
|
// summary: Stops a running slide show.
|
|
if(this._slideId){ clearTimeout(this._slideId); }
|
|
this._slideId = null;
|
|
this._timerCancelled = true;
|
|
dojo.removeClass(this.domNode,"slideShowPaused");
|
|
},
|
|
|
|
_prev: function(){
|
|
// summary: Show the previous image.
|
|
// FIXME: either pull code from showNext/prev, or call it here
|
|
if(this.imageIndex < 1){ return; }
|
|
this.showImage(this.imageIndex - 1);
|
|
},
|
|
|
|
_next: function(){
|
|
// summary: Show the next image
|
|
this.showNextImage();
|
|
},
|
|
|
|
_startTimer: function(){
|
|
// summary: Starts a timeout to show the next image when a slide show is active
|
|
var id = this.id;
|
|
this._slideId = setTimeout(function(){
|
|
dijit.byId(id).showNextImage(true);
|
|
}, this.slideshowInterval * 1000);
|
|
},
|
|
|
|
_calcNavDimensions: function() {
|
|
// summary:
|
|
// Calculates the dimensions of the navigation controls
|
|
dojo.style(this.navNode, "position", "absolute");
|
|
|
|
//Place the navigation controls far off screen
|
|
dojo.style(this.navNode, "top", "-10000px");
|
|
|
|
//Make the navigation controls visible
|
|
dojo._setOpacity(this.navNode, 99);
|
|
|
|
this.navPlay._size = dojo.marginBox(this.navPlay);
|
|
this.navPrev._size = dojo.marginBox(this.navPrev);
|
|
this.navNext._size = dojo.marginBox(this.navNext);
|
|
|
|
dojo._setOpacity(this.navNode, 0);
|
|
dojo.style(this.navNode, "position", "");
|
|
dojo.style(this.navNode, "top", "");
|
|
},
|
|
|
|
_setTitle: function(title){
|
|
// summary: Sets the title to the image being displayed
|
|
// title: String
|
|
// The String title of the image
|
|
|
|
this.titleNode.innerHTML = dojo.string.substitute(this.titleTemplate,{
|
|
title: title,
|
|
current: 1 + this.imageIndex,
|
|
total: this.maxPhotos || ""
|
|
});
|
|
},
|
|
|
|
_fitImage: function(img) {
|
|
// summary: Ensures that the image width and height do not exceed the maximum.
|
|
// img: Node
|
|
// The image DOM node to optionally resize
|
|
var width = img.width;
|
|
var height = img.height;
|
|
|
|
if(width > this.imageWidth){
|
|
height = Math.floor(height * (this.imageWidth / width));
|
|
img.height = height;
|
|
img.width = width = this.imageWidth;
|
|
}
|
|
if(height > this.imageHeight){
|
|
width = Math.floor(width * (this.imageHeight / height));
|
|
img.height = this.imageHeight;
|
|
img.width = width;
|
|
}
|
|
},
|
|
|
|
_handleClick: function(/* Event */e){
|
|
// summary: Performs navigation on the images based on users mouse clicks
|
|
// e:
|
|
// An Event object
|
|
switch(e.target){
|
|
case this.navNext: this._next(); break;
|
|
case this.navPrev: this._prev(); break;
|
|
case this.navPlay: this.toggleSlideShow(); break;
|
|
}
|
|
},
|
|
|
|
_showNav: function(force){
|
|
// summary:
|
|
// Shows the navigation controls
|
|
// force: Boolean
|
|
// If true, the navigation controls are repositioned even if they are
|
|
// currently visible.
|
|
if(this._navShowing && !force){return;}
|
|
dojo.style(this.navNode, "marginTop", "0px");
|
|
dojo.style(this.navPlay, "marginLeft", "0px");
|
|
var wrapperSize = dojo.marginBox(this.outerNode);
|
|
|
|
var margin = this._currentImage.height - this.navPlay._size.h - 10 + this._getTopPadding();
|
|
|
|
if(margin > this._currentImage.height){margin += 10;}
|
|
dojo[this.imageIndex < 1 ? "addClass":"removeClass"](this.navPrev, "slideShowCtrlHide");
|
|
dojo[this.imageIndex + 1 >= this.maxPhotos ? "addClass":"removeClass"](this.navNext, "slideShowCtrlHide");
|
|
|
|
var _this = this;
|
|
if(this._navAnim) {
|
|
this._navAnim.stop();
|
|
}
|
|
if(this._navShowing){ return; }
|
|
this._navAnim = dojo.fadeIn({
|
|
node: this.navNode,
|
|
duration: 300,
|
|
onEnd: function(){ _this._navAnim = null; }
|
|
});
|
|
this._navAnim.play();
|
|
this._navShowing = true;
|
|
},
|
|
|
|
_hideNav: function(/* Event */e){
|
|
// summary: Hides the navigation controls
|
|
// e: Event
|
|
// The DOM Event that triggered this function
|
|
if(!e || !this._overElement(this.outerNode, e)){
|
|
var _this = this;
|
|
if(this._navAnim){
|
|
this._navAnim.stop();
|
|
}
|
|
this._navAnim = dojo.fadeOut({
|
|
node: this.navNode,
|
|
duration:300,
|
|
onEnd: function(){ _this._navAnim = null; }
|
|
});
|
|
this._navAnim.play();
|
|
this._navShowing = false;
|
|
}
|
|
},
|
|
|
|
_overElement: function(/*DomNode*/element, /*Event*/e){
|
|
// summary:
|
|
// Returns whether the mouse is over the passed element.
|
|
// Element must be display:block (ie, not a <span>)
|
|
|
|
//When the page is unloading, if this method runs it will throw an
|
|
//exception.
|
|
if(typeof(dojo) == "undefined"){ return false; }
|
|
element = dojo.byId(element);
|
|
var m = { x: e.pageX, y: e.pageY };
|
|
var bb = dojo._getBorderBox(element);
|
|
var absl = dojo.coords(element, true);
|
|
var left = absl.x;
|
|
|
|
return (m.x >= left
|
|
&& m.x <= (left + bb.w)
|
|
&& m.y >= absl.y
|
|
&& m.y <= (top + bb.h)
|
|
); // boolean
|
|
}
|
|
});
|