diff options
Diffstat (limited to 'includes/js/dojox/image/ThumbnailPicker.js')
-rw-r--r-- | includes/js/dojox/image/ThumbnailPicker.js | 535 |
1 files changed, 535 insertions, 0 deletions
diff --git a/includes/js/dojox/image/ThumbnailPicker.js b/includes/js/dojox/image/ThumbnailPicker.js new file mode 100644 index 0000000..42f9862 --- /dev/null +++ b/includes/js/dojox/image/ThumbnailPicker.js @@ -0,0 +1,535 @@ +if(!dojo._hasResource["dojox.image.ThumbnailPicker"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dojox.image.ThumbnailPicker"] = true; +dojo.provide("dojox.image.ThumbnailPicker"); +dojo.experimental("dojox.image.ThumbnailPicker"); +// +// dojox.image.ThumbnailPicker courtesy Shane O Sullivan, licensed under a Dojo CLA +// @author Copyright 2007 Shane O Sullivan (shaneosullivan1@gmail.com) +// +// For a sample usage, see http://www.skynet.ie/~sos/photos.php +// +// document topics. + +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:"<div dojoAttachPoint=\"outerNode\" class=\"thumbOuter\">\n\t<div dojoAttachPoint=\"navPrev\" class=\"thumbNav thumbClickable\">\n\t <img src=\"\" dojoAttachPoint=\"navPrevImg\"/> \n\t</div>\n\t<div dojoAttachPoint=\"thumbScroller\" class=\"thumbScroller\">\n\t <div dojoAttachPoint=\"thumbsNode\" class=\"thumbWrapper\"></div>\n\t</div>\n\t<div dojoAttachPoint=\"navNext\" class=\"thumbNav thumbClickable\">\n\t <img src=\"\" dojoAttachPoint=\"navNextImg\"/> \n\t</div>\n</div>\n", + tempImgPath: dojo.moduleUrl("dojo", "resources/blank.gif"), + + // 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.isScrollable) { + // FIXME: does this break builds or anything? + dojo.require("dojox.fx.scroll"); + dojo.require("dojox.fx.easing"); + } + 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); + + this.navNextImg.setAttribute("src", this.tempImgPath); + this.navPrevImg.setAttribute("src", this.tempImgPath); + + 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; + + 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){ + // dojo.event.browser.clean(img); + if(img.parentNode){ + img.parentNode.removeChild(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); + }, + + _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){ + 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:dojox.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); + } + }, + + markImageLoaded: function(index){ + // summary: Changes a visual cue to show the image is loaded + // description: If 'useLoadNotifier' is set to true, then a visual cue is + // given to state whether the image is loaded or not. Calling this function + // marks an image as loaded. + var thumbNotifier = dojo.byId("loadingDiv_"+this.widgetid+"_"+index); + if(thumbNotifier){this._setThumbClass(thumbNotifier, "thumbLoaded");} + this._loadedImages[index] = true; + }, + + _setThumbClass: function(thumb, className){ + // summary: Adds a CSS class to a thumbnail, only if 'autoLoad' is true + // thumb: DomNode + // The thumbnail DOM node to set the class on + // className: String + // The CSS class to add to the DOM node. + if(!this.autoLoad){ return; } + dojo.addClass(thumb, className); + }, + + _loadNextPage: function(){ + // summary: Loads the next page of thumbnail images + if(this._loadInProgress){return;} + this._loadInProgress = true; + var start = this.request.start + (this._noImages ? 0 : this.pageSize); + + var pos = start; + while(pos < this._thumbs.length && this._thumbs[pos]){pos ++;} + + //Define the function to call when the items have been + //returned from the data store. + var complete = function(items, request){ + if(items && items.length){ + var itemCounter = 0; + var loadNext = dojo.hitch(this, function(){ + if(itemCounter >= items.length){ + this._loadInProgress = false; + return; + } + var counter = itemCounter++; + + this._loadImage(items[counter], pos + counter, loadNext); + }); + loadNext(); + + //Show or hide the navigation arrows on the thumbnails, + //depending on whether or not the widget is at the start, + //end, or middle of the list of images. + this._updateNavControls(); + }else{ + this._loadInProgress = false; + } + }; + + //Define the function to call if the store reports an error. + var error = function(){ + this._loadInProgress = false; + console.debug("Error getting items"); + }; + + this.request.onComplete = dojo.hitch(this, complete); + this.request.onError = dojo.hitch(this, error); + + //Increment the start parameter. This is the dojo.data API's + //version of paging. + this.request.start = start; + this._noImages = false; + + //Execute the request for data. + this.imageStore.fetch(this.request); + + }, + + _loadImage: function(data, index, callback){ + var url = this.imageStore.getValue(data,this.imageThumbAttr); + var img = document.createElement("img"); + var imgContainer = document.createElement("div"); + imgContainer.setAttribute("id","img_" + this.widgetid+"_"+index); + imgContainer.appendChild(img); + img._index = index; + img._data = data; + + this._thumbs[index] = imgContainer; + var loadingDiv; + if(this.useLoadNotifier){ + loadingDiv = document.createElement("div"); + loadingDiv.setAttribute("id","loadingDiv_" + this.widgetid+"_"+index); + + //If this widget was previously told that the main image for this + //thumb has been loaded, make the loading indicator transparent. + this._setThumbClass(loadingDiv, + this._loadedImages[index] ? "thumbLoaded":"thumbNotifier"); + + imgContainer.appendChild(loadingDiv); + } + var size = dojo.marginBox(this.thumbsNode); + var defaultSize; + var sizeParam; + if(this.isHorizontal){ + defaultSize = this.thumbWidth; + sizeParam = 'w'; + } else{ + defaultSize = this.thumbHeight; + sizeParam = 'h'; + } + size = size[sizeParam]; + var sl = this.thumbScroller.scrollLeft, st = this.thumbScroller.scrollTop; + dojo.style(this.thumbsNode, this._sizeProperty, (size + defaultSize + 20) + "px"); + //Remember the scroll values, as changing the size can alter them + this.thumbScroller.scrollLeft = sl; + this.thumbScroller.scrollTop = st; + this.thumbsNode.appendChild(imgContainer); + + dojo.connect(img, "onload", this, function(){ + var realSize = dojo.marginBox(img)[sizeParam]; + this._totalSize += (Number(realSize) + 4); + dojo.style(this.thumbsNode, this._sizeProperty, this._totalSize + "px"); + + if(this.useLoadNotifier){dojo.style(loadingDiv, "width", (img.width - 4) + "px"); } + callback(); + return false; + }); + + dojo.connect(img, "onclick", this, function(evt){ + dojo.publish(this.getClickTopicName(), [{ + index: evt.target._index, + data: evt.target._data, + url: img.getAttribute("src"), + largeUrl: this.imageStore.getValue(data,this.imageLargeAttr), + title: this.imageStore.getValue(data,this.titleAttr), + link: this.imageStore.getValue(data,this.linkAttr) + }]); + return false; + }); + dojo.addClass(img, "imageGalleryThumb"); + img.setAttribute("src", url); + var title = this.imageStore.getValue(data, this.titleAttr); + if(title){ img.setAttribute("title",title); } + this._updateNavControls(); + + }, + + _updateNavControls: function(){ + // summary: Updates the navigation controls to hide/show them when at + // the first or last images. + var cells = []; + var change = function(node, add){ + var fn = add ? "addClass" : "removeClass"; + dojo[fn](node,"enabled"); + dojo[fn](node,"thumbClickable"); + }; + + var pos = this.isHorizontal ? "scrollLeft" : "scrollTop"; + var size = this.isHorizontal ? "offsetWidth" : "offsetHeight"; + change(this.navPrev, (this.thumbScroller[pos] > 0)); + + var last = this._thumbs[this._thumbs.length - 1]; + var addClass = (this.thumbScroller[pos] + this._scrollerSize < this.thumbsNode[size]); + change(this.navNext, addClass); + } +}); + +} |