source: [view]
dojo.provide("dojox.image.ThumbnailPicker");
dojo.experimental("dojox.image.ThumbnailPicker");
//
// dojox.image.ThumbnailPicker courtesy Shane O Sullivan, licensed under a Dojo CLA
//
// For a sample usage, see http://www.skynet.ie/~sos/photos.php
//
// document topics.
dojo.require("dojox.fx.scroll"); // is optional, but don't want to dojo[require] it
dojo.require("dojo.fx.easing");
dojo.require("dojo.fx");
dojo.require("dijit._Widget");
dojo.require("dijit._Templated");
dojo.declare("dojox.image.ThumbnailPicker",
[dijit._Widget, dijit._Templated],
{
// summary: A scrolling Thumbnail Picker widget
//
// imageStore: Object
// A data store that implements the dojo.data Read API.
imageStore: null,
// request: Object
// A dojo.data Read API Request object.
request: null,
// size: Number
// Width or height in pixels, depending if horizontal or vertical.
size: 500, //FIXME: use CSS?
// thumbHeight: Number
// Default height of a thumbnail image
thumbHeight: 75, // FIXME: use CSS?
// thumbWidth: Number
// Default width of an image
thumbWidth: 100, // FIXME: use CSS?
// useLoadNotifier: Boolean
// Setting useLoadNotifier to true makes a colored DIV appear under each
// thumbnail image, which is used to display the loading status of each
// image in the data store.
useLoadNotifier: false,
// useHyperlink: boolean
// Setting useHyperlink to true causes a click on a thumbnail to open a link.
useHyperlink: false,
// hyperlinkTarget: String
// If hyperlinkTarget is set to "new", clicking on a thumb will open a new window
// If it is set to anything else, clicking a thumbnail will open the url in the
// current window.
hyperlinkTarget: "new",
// isClickable: Boolean
// When set to true, the cursor over a thumbnail changes.
isClickable: true,
// isScrollable: Boolean
// When true, uses smoothScroll to move between pages
isScrollable: true,
// isHorizontal: Boolean
// If true, the thumbnails are displayed horizontally. Otherwise they are displayed
// vertically
isHorizontal: true,
//autoLoad: Boolean
autoLoad: true,
// linkAttr: String
// The attribute name for accessing the url from the data store
linkAttr: "link",
// imageThumbAttr: String
// The attribute name for accessing the thumbnail image url from the data store
imageThumbAttr: "imageUrlThumb",
// imageLargeAttr: String
// The attribute name for accessing the large image url from the data store
imageLargeAttr: "imageUrl",
// pageSize: Number
// The number of images to request each time.
pageSize: 20,
// titleAttr: String
// The attribute name for accessing the title from the data store
titleAttr: "title",
templateString: dojo.cache("dojox.image", "resources/ThumbnailPicker.html"),
// thumbs: Array
// Stores the image nodes for the thumbnails.
_thumbs: [],
// _thumbIndex: Number
// The index of the first thumbnail shown
_thumbIndex: 0,
// _maxPhotos: Number
// The total number of photos in the image store
_maxPhotos: 0,
// _loadedImages: Object
// Stores the indices of images that have been marked as loaded using the
// markImageLoaded function.
_loadedImages: {},
postCreate: function(){
// summary:
// Initializes styles and listeners
this.widgetid = this.id;
this.inherited(arguments);
this.pageSize = Number(this.pageSize);
this._scrollerSize = this.size - (51 * 2);
var sizeProp = this._sizeProperty = this.isHorizontal ? "width" : "height";
// FIXME: do this via css? calculate the correct width for the widget
dojo.style(this.outerNode, "textAlign","center");
dojo.style(this.outerNode, sizeProp, this.size+"px");
dojo.style(this.thumbScroller, sizeProp, this._scrollerSize + "px");
//If useHyperlink is true, then listen for a click on a thumbnail, and
//open the link
if(this.useHyperlink){
dojo.subscribe(this.getClickTopicName(), this, function(packet){
var index = packet.index;
var url = this.imageStore.getValue(packet.data,this.linkAttr);
//If the data item doesn't contain a URL, do nothing
if(!url){return;}
if(this.hyperlinkTarget == "new"){
window.open(url);
}else{
window.location = url;
}
});
}
if(this.isClickable){
dojo.addClass(this.thumbsNode, "thumbClickable");
}
this._totalSize = 0;
this.init();
},
init: function(){
// summary:
// Creates DOM nodes for thumbnail images and initializes their listeners
if(this.isInitialized) {return false;}
var classExt = this.isHorizontal ? "Horiz" : "Vert";
// FIXME: can we setup a listener around the whole element and determine based on e.target?
dojo.addClass(this.navPrev, "prev" + classExt);
dojo.addClass(this.navNext, "next" + classExt);
dojo.addClass(this.thumbsNode, "thumb"+classExt);
dojo.addClass(this.outerNode, "thumb"+classExt);
dojo.attr(this.navNextImg, "src", this._blankGif);
dojo.attr(this.navPrevImg, "src", this._blankGif);
this.connect(this.navPrev, "onclick", "_prev");
this.connect(this.navNext, "onclick", "_next");
this.isInitialized = true;
if(this.isHorizontal){
this._offsetAttr = "offsetLeft";
this._sizeAttr = "offsetWidth";
this._scrollAttr = "scrollLeft";
}else{
this._offsetAttr = "offsetTop";
this._sizeAttr = "offsetHeight";
this._scrollAttr = "scrollTop";
}
this._updateNavControls();
if(this.imageStore && this.request){this._loadNextPage();}
return true;
},
getClickTopicName: function(){
// summary:
// Returns the name of the dojo topic that can be
// subscribed to in order to receive notifications on
// which thumbnail was selected.
return (this.widgetId || this.id) + "/select"; // String
},
getShowTopicName: function(){
// summary:
// Returns the name of the dojo topic that can be
// subscribed to in order to receive notifications on
// which thumbnail is now visible
return (this.widgetId || this.id) + "/show"; // String
},
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 four attributes allowed are 'linkAttr', 'imageLargeAttr',
// 'imageThumbAttr' and 'titleAttr'
this.reset();
this.request = {
query: {},
start: request.start || 0,
count: request.count || 10,
onBegin: dojo.hitch(this, function(total){
this._maxPhotos = total;
})
};
if(request.query){ dojo.mixin(this.request.query, request.query);}
if(paramNames){
dojo.forEach(["imageThumbAttr", "imageLargeAttr", "linkAttr", "titleAttr"], function(attrName){
if(paramNames[attrName]){ this[attrName] = paramNames[attrName]; }
}, this);
}
this.request.start = 0;
this.request.count = this.pageSize;
this.imageStore = dataStore;
this._loadInProgress = false;
if(!this.init()){this._loadNextPage();}
},
reset: function(){
// summary:
// Resets the widget back to its original state.
this._loadedImages = {};
dojo.forEach(this._thumbs, function(img){
if(img && img.parentNode){
dojo.destroy(img);
}
});
this._thumbs = [];
this.isInitialized = false;
this._noImages = true;
},
isVisible: function(index) {
// summary:
// Returns true if the image at the specified index is currently visible. False otherwise.
var img = this._thumbs[index];
if(!img){return false;}
var pos = this.isHorizontal ? "offsetLeft" : "offsetTop";
var size = this.isHorizontal ? "offsetWidth" : "offsetHeight";
var scrollAttr = this.isHorizontal ? "scrollLeft" : "scrollTop";
var offset = img[pos] - this.thumbsNode[pos];
return (offset >= this.thumbScroller[scrollAttr]
&& offset + img[size] <= this.thumbScroller[scrollAttr] + this._scrollerSize);
},
resize: function(dim){
var sizeParam = this.isHorizontal ? "w": "h";
var total = 0;
if(this._thumbs.length > 0 && dojo.marginBox(this._thumbs[0]).w == 0){
// Skip the resize if the widget is not visible
return;
}
// Calculate the complete size of the thumbnails
dojo.forEach(this._thumbs, dojo.hitch(this, function(imgContainer){
var mb = dojo.marginBox(imgContainer.firstChild);
var size = mb[sizeParam];
total += (Number(size) + 10);
if(this.useLoadNotifier && mb.w > 0){
dojo.style(imgContainer.lastChild, "width", (mb.w - 4) + "px");
}
dojo.style(imgContainer, "width", mb.w + "px");
}));
dojo.style(this.thumbsNode, this._sizeProperty, total + "px");
this._updateNavControls();
},
_next: function() {
// summary:
// Displays the next page of images
var pos = this.isHorizontal ? "offsetLeft" : "offsetTop";
var size = this.isHorizontal ? "offsetWidth" : "offsetHeight";
var baseOffset = this.thumbsNode[pos];
var firstThumb = this._thumbs[this._thumbIndex];
var origOffset = firstThumb[pos] - baseOffset;
var index = -1, img;
for(var i = this._thumbIndex + 1; i < this._thumbs.length; i++){
img = this._thumbs[i];
if(img[pos] - baseOffset + img[size] - origOffset > this._scrollerSize){
this._showThumbs(i);
return;
}
}
},
_prev: function(){
// summary:
// Displays the next page of images
if(this.thumbScroller[this.isHorizontal ? "scrollLeft" : "scrollTop"] == 0){return;}
var pos = this.isHorizontal ? "offsetLeft" : "offsetTop";
var size = this.isHorizontal ? "offsetWidth" : "offsetHeight";
var firstThumb = this._thumbs[this._thumbIndex];
var origOffset = firstThumb[pos] - this.thumbsNode[pos];
var index = -1, img;
for(var i = this._thumbIndex - 1; i > -1; i--) {
img = this._thumbs[i];
if(origOffset - img[pos] > this._scrollerSize){
this._showThumbs(i + 1);
return;
}
}
this._showThumbs(0);
},
_checkLoad: function(img, index){
// summary:
// Checks if an image is loaded.
dojo.publish(this.getShowTopicName(), [{index:index}]);
this._updateNavControls();
this._loadingImages = {};
this._thumbIndex = index;
//If we have not already requested the data from the store, do so.
if(this.thumbsNode.offsetWidth - img.offsetLeft < (this._scrollerSize * 2)){
this._loadNextPage();
}
},
_showThumbs: function(index){
// summary:
// Displays thumbnail images, starting at position 'index'
// index: Number
// The index of the first thumbnail
//FIXME: When is this be called with an invalid index? Do we need this check at all?
// if(typeof index != "number"){ index = this._thumbIndex; }
index = Math.min(Math.max(index, 0), this._maxPhotos);
if(index >= this._maxPhotos){ return; }
var img = this._thumbs[index];
if(!img){ return; }
var left = img.offsetLeft - this.thumbsNode.offsetLeft;
var top = img.offsetTop - this.thumbsNode.offsetTop;
var offset = this.isHorizontal ? left : top;
if( (offset >= this.thumbScroller[this._scrollAttr]) &&
(offset + img[this._sizeAttr] <= this.thumbScroller[this._scrollAttr] + this._scrollerSize)
){
// FIXME: WTF is this checking for?
return;
}
if(this.isScrollable){
var target = this.isHorizontal ? {x: left, y: 0} : { x:0, y:top};
dojox.fx.smoothScroll({
target: target,
win: this.thumbScroller,
duration:300,
easing:dojo.fx.easing.easeOut,
onEnd: dojo.hitch(this, "_checkLoad", img, index)
}).play(10);
}else{
if(this.isHorizontal){
this.thumbScroller.scrollLeft = left;
}else{
this.thumbScroller.scrollTop = top;
}
this._checkLoad(img, index);
}