source: Dev/trunk/src/client/dojox/image/ThumbnailPicker.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: 17.1 KB
Line 
1dojo.provide("dojox.image.ThumbnailPicker");
2dojo.experimental("dojox.image.ThumbnailPicker");
3//
4// dojox.image.ThumbnailPicker courtesy Shane O Sullivan, licensed under a Dojo CLA
5//
6// For a sample usage, see http://www.skynet.ie/~sos/photos.php
7//
8//      document topics.
9
10dojo.require("dojox.fx.scroll"); // is optional, but don't want to dojo[require] it
11dojo.require("dojo.fx.easing");
12
13dojo.require("dojo.fx");
14dojo.require("dijit._Widget");
15dojo.require("dijit._Templated");
16
17// FIXME: use CSS for size, thumbHeight, and thumbWidth
18
19dojo.declare("dojox.image.ThumbnailPicker",
20        [dijit._Widget, dijit._Templated],
21        {
22        // summary:
23        //              A scrolling Thumbnail Picker widget
24
25        // imageStore: Object
26        //              A data store that implements the dojo.data Read API.
27        imageStore: null,
28
29        // request: Object
30        //              A dojo.data Read API Request object.
31        request: null,
32
33        // size: Number
34        //              Width or height in pixels, depending if horizontal or vertical.
35        size: 500,
36
37        // thumbHeight: Number
38        //              Default height of a thumbnail image
39        thumbHeight: 75,
40
41        // thumbWidth: Number
42        //              Default width of an image
43        thumbWidth: 100,
44
45        // useLoadNotifier: Boolean
46        //              Setting useLoadNotifier to true makes a colored DIV appear under each
47        //              thumbnail image, which is used to display the loading status of each
48        //              image in the data store.
49        useLoadNotifier: false,
50
51        // useHyperlink: boolean
52        //              Setting useHyperlink to true causes a click on a thumbnail to open a link.
53        useHyperlink: false,
54
55        // hyperlinkTarget: String
56        //              If hyperlinkTarget is set to "new", clicking on a thumb will open a new window
57        //              If it is set to anything else, clicking a thumbnail will open the url in the
58        //              current window.
59        hyperlinkTarget: "new",
60
61        // isClickable: Boolean
62        //              When set to true, the cursor over a thumbnail changes.
63        isClickable: true,
64
65        // isScrollable: Boolean
66        //              When true, uses smoothScroll to move between pages
67        isScrollable: true,
68
69        // isHorizontal: Boolean
70        //              If true, the thumbnails are displayed horizontally. Otherwise they are displayed
71        //              vertically
72        isHorizontal: true,
73
74        // autoLoad: Boolean
75        autoLoad: true,
76
77        // linkAttr: String
78        //              The attribute name for accessing the url from the data store
79        linkAttr: "link",
80       
81        // imageThumbAttr: String
82        //              The attribute name for accessing the thumbnail image url from the data store
83        imageThumbAttr: "imageUrlThumb",
84       
85        // imageLargeAttr: String
86        //              The attribute name for accessing the large image url from the data store
87        imageLargeAttr: "imageUrl",
88       
89        // pageSize: Number
90        //              The number of images to request each time.
91        pageSize: 20,
92       
93        // titleAttr: String
94        //              The attribute name for accessing the title from the data store
95        titleAttr: "title",
96       
97        templateString: dojo.cache("dojox.image", "resources/ThumbnailPicker.html"),
98       
99        // thumbs: Array
100        //              Stores the image nodes for the thumbnails.
101        _thumbs: [],
102       
103        // _thumbIndex: Number
104        //              The index of the first thumbnail shown
105        _thumbIndex: 0,
106       
107        // _maxPhotos: Number
108        //              The total number of photos in the image store
109        _maxPhotos: 0,
110       
111        // _loadedImages: Object
112        //              Stores the indices of images that have been marked as loaded using the
113        //              markImageLoaded function.
114        _loadedImages: {},
115
116        baseClass: "ThumbnailPicker",
117
118        cellClass: "Thumbnail",
119
120        postCreate: function(){
121                // summary:
122                //              Initializes styles and listeners
123
124                this.inherited(arguments);
125               
126                this.pageSize = Number(this.pageSize);
127
128                this._scrollerSize = this.size - (51 * 2);
129               
130                var sizeProp = this._sizeProperty = this.isHorizontal ? "width" : "height";
131       
132                // FIXME: do this via css? calculate the correct width for the widget
133                dojo.style(this.outerNode, "textAlign","center");
134                dojo.style(this.outerNode, sizeProp, this.size+"px");
135       
136                dojo.style(this.thumbScroller, sizeProp, this._scrollerSize + "px");
137       
138                //If useHyperlink is true, then listen for a click on a thumbnail, and
139                //open the link
140                if(this.useHyperlink){
141                        dojo.subscribe(this.getClickTopicName(), this, function(packet){
142                                var index = packet.index;
143                                var url = this.imageStore.getValue(packet.data,this.linkAttr);
144                               
145                                //If the data item doesn't contain a URL, do nothing
146                                if(!url){return;}
147                               
148                                if(this.hyperlinkTarget == "new"){
149                                        window.open(url);
150                                }else{
151                                        window.location = url;
152                                }
153                        });
154                }
155       
156                if(this.isClickable){
157                        dojo.addClass(this.thumbsNode, "thumbClickable");
158                }
159                this._totalSize = 0;
160
161                var classExt = this.isHorizontal ? "Horiz" : "Vert";
162       
163                // FIXME: can we setup a listener around the whole element and determine based on e.target?
164                dojo.addClass(this.navPrev, "prev" + classExt);
165                dojo.addClass(this.navNext, "next" + classExt);
166                dojo.addClass(this.thumbsNode, "thumb"+classExt);
167                dojo.addClass(this.outerNode, "thumb"+classExt);
168
169                dojo.attr(this.navNextImg, "src", this._blankGif);
170                dojo.attr(this.navPrevImg, "src", this._blankGif);
171
172                this.connect(this.navPrev, "onclick", "_prev");
173                this.connect(this.navNext, "onclick", "_next");
174
175                if(this.isHorizontal){
176                        this._sizeAttr = "offsetWidth";
177                        this._scrollAttr = "scrollLeft";
178                }else{
179                        this._sizeAttr = "offsetHeight";
180                        this._scrollAttr = "scrollTop";
181                }
182       
183                this._updateNavControls();
184               
185                this.init();
186        },
187       
188        init: function(){
189                // summary
190                //              Loads first image
191                if(this.isInitialized){
192                        return false;
193                }
194                this.isInitialized = true;
195
196                if(this.imageStore && this.request){
197                        this._loadNextPage();
198                }
199                return true;
200        },
201
202        getClickTopicName: function(){
203                // summary:
204                //              Returns the name of the dojo topic that can be
205                //              subscribed to in order to receive notifications on
206                //              which thumbnail was selected.
207                return this.id + "/select"; // String
208        },
209
210        getShowTopicName: function(){
211                // summary:
212                //              Returns the name of the dojo topic that can be
213                //              subscribed to in order to receive notifications on
214                //              which thumbnail is now visible
215                return this.id + "/show"; // String
216        },
217
218        setDataStore: function(dataStore, request, /*optional*/paramNames){
219                // summary:
220                //              Sets the data store and request objects to read data from.
221                // dataStore:
222                //              An implementation of the dojo/data/api/Read API. This accesses the image
223                //              data.
224                // request:
225                //              An implementation of the dojo/data/api/Request API. This specifies the
226                //              query and paging information to be used by the data store
227                // paramNames:
228                //              An object defining the names of the item attributes to fetch from the
229                //              data store.  The four attributes allowed are 'linkAttr', 'imageLargeAttr',
230                //              'imageThumbAttr' and 'titleAttr'
231                this.reset();
232       
233                this.request = {
234                        query: {},
235                        start: request.start || 0,
236                        count: request.count || 10,
237                        onBegin: dojo.hitch(this, function(total){
238                                this._maxPhotos = total;
239                        })
240                };
241       
242                if(request.query){ dojo.mixin(this.request.query, request.query);}
243       
244                if(paramNames){
245                        dojo.forEach(["imageThumbAttr", "imageLargeAttr", "linkAttr", "titleAttr"], function(attrName){
246                                if(paramNames[attrName]){ this[attrName] = paramNames[attrName]; }
247                        }, this);
248                }
249               
250                this.request.start = 0;
251                this.request.count = this.pageSize;
252                this.imageStore = dataStore;
253                this._loadInProgress = false;
254                if(!this.init()){this._loadNextPage();}
255        },
256
257        reset: function(){
258                // summary:
259                //              Resets the widget back to its original state.
260                this._loadedImages = {};
261                dojo.forEach(this._thumbs, function(img){
262                        if(img && img.parentNode){
263                                dojo.destroy(img);
264                        }
265                });
266       
267                this._thumbs = [];
268                this.isInitialized = false;
269                this._noImages = true;
270        },
271       
272        isVisible: function(index) {
273                // summary:
274                //              Returns true if the image at the specified index is currently visible. False otherwise.
275                var img = this._thumbs[index];
276                if(!img){return false;}
277                var pos = this.isHorizontal ? "offsetLeft" : "offsetTop";
278                var size = this.isHorizontal ? "offsetWidth" : "offsetHeight";
279                var scrollAttr = this.isHorizontal ? "scrollLeft" : "scrollTop";
280                var offset = img[pos] - this.thumbsNode[pos];
281                return (offset >= this.thumbScroller[scrollAttr]
282                        && offset + img[size] <= this.thumbScroller[scrollAttr] + this._scrollerSize);
283        },
284       
285        resize: function(dim){
286                var sizeParam = this.isHorizontal ? "w": "h";
287
288                var total = 0;
289
290                if(this._thumbs.length > 0 && dojo.marginBox(this._thumbs[0]).w == 0){
291                        // Skip the resize if the widget is not visible
292                        return;
293                }
294
295                // Calculate the complete size of the thumbnails
296                dojo.forEach(this._thumbs, dojo.hitch(this, function(imgContainer){
297                        var mb = dojo.marginBox(imgContainer.firstChild);
298                        var size = mb[sizeParam];
299                        total += (Number(size) + 10);
300                       
301                        if(this.useLoadNotifier && mb.w > 0){
302                                dojo.style(imgContainer.lastChild, "width", (mb.w - 4) + "px");
303                        }
304                        dojo.style(imgContainer, "width", mb.w + "px");
305                }));
306
307                dojo.style(this.thumbsNode, this._sizeProperty, total + "px");
308                this._updateNavControls();
309        },
310       
311        _next: function() {
312                // summary:
313                //              Displays the next page of images
314                var pos = this.isHorizontal ? "offsetLeft" : "offsetTop";
315                var size = this.isHorizontal ? "offsetWidth" : "offsetHeight";
316                var baseOffset = this.thumbsNode[pos];
317                var firstThumb = this._thumbs[this._thumbIndex];
318                var origOffset = firstThumb[pos] - baseOffset;
319       
320                var index = -1, img;
321       
322                for(var i = this._thumbIndex + 1; i < this._thumbs.length; i++){
323                        img = this._thumbs[i];
324                        if(img[pos] - baseOffset + img[size] - origOffset > this._scrollerSize){
325                                this._showThumbs(i);
326                                return;
327                        }
328                }
329        },
330
331        _prev: function(){
332                // summary:
333                //              Displays the next page of images
334                if(this.thumbScroller[this.isHorizontal ? "scrollLeft" : "scrollTop"] == 0){return;}
335                var pos = this.isHorizontal ? "offsetLeft" : "offsetTop";
336                var size = this.isHorizontal ? "offsetWidth" : "offsetHeight";
337       
338                var firstThumb = this._thumbs[this._thumbIndex];
339                var origOffset = firstThumb[pos] - this.thumbsNode[pos];
340       
341                var index = -1, img;
342       
343                for(var i = this._thumbIndex - 1; i > -1; i--) {
344                        img = this._thumbs[i];
345                        if(origOffset - img[pos] > this._scrollerSize){
346                                this._showThumbs(i + 1);
347                                return;
348                        }
349                }
350                this._showThumbs(0);
351        },
352
353        _checkLoad: function(img, index){
354                // summary:
355                //              Checks if an image is loaded.
356                dojo.publish(this.getShowTopicName(), [{index:index}]);
357                this._updateNavControls();
358                this._loadingImages = {};
359       
360                this._thumbIndex = index;
361       
362                //If we have not already requested the data from the store, do so.
363                if(this.thumbsNode.offsetWidth - img.offsetLeft < (this._scrollerSize * 2)){
364                        this._loadNextPage();
365                }
366        },
367
368        _showThumbs: function(index){
369                // summary:
370                //              Displays thumbnail images, starting at position 'index'
371                // index: Number
372                //              The index of the first thumbnail
373
374//FIXME: When is this be called with an invalid index?  Do we need this check at all?
375//              if(typeof index != "number"){ index = this._thumbIndex; }
376                index = Math.min(Math.max(index, 0), this._maxPhotos);
377               
378                if(index >= this._maxPhotos){ return; }
379               
380                var img = this._thumbs[index];
381                if(!img){ return; }
382               
383                var left = img.offsetLeft - this.thumbsNode.offsetLeft;
384                var top = img.offsetTop - this.thumbsNode.offsetTop;
385                var offset = this.isHorizontal ? left : top;
386                               
387                if(     (offset >= this.thumbScroller[this._scrollAttr]) &&
388                        (offset + img[this._sizeAttr] <= this.thumbScroller[this._scrollAttr] + this._scrollerSize)
389                ){
390                        // FIXME: WTF is this checking for?
391                        return;
392                }
393               
394               
395                if(this.isScrollable){
396                        var target = this.isHorizontal ? {x: left, y: 0} : { x:0, y:top};
397                        dojox.fx.smoothScroll({
398                                target: target,
399                                win: this.thumbScroller,
400                                duration:300,
401                                easing:dojo.fx.easing.easeOut,
402                                onEnd: dojo.hitch(this, "_checkLoad", img, index)
403                        }).play(10);
404                }else{
405                        if(this.isHorizontal){
406                                this.thumbScroller.scrollLeft = left;
407                        }else{
408                                this.thumbScroller.scrollTop = top;
409                        }
410                        this._checkLoad(img, index);
411                }
412        },
413       
414        markImageLoaded: function(index){
415                // summary:
416                //              Changes a visual cue to show the image is loaded
417                // description:
418                //              If 'useLoadNotifier' is set to true, then a visual cue is
419                //              given to state whether the image is loaded or not.      Calling this function
420                //              marks an image as loaded.
421                var thumbNotifier = dojo.byId("loadingDiv_"+this.id+"_"+index);
422                if(thumbNotifier){this._setThumbClass(thumbNotifier, "thumbLoaded");}
423                this._loadedImages[index] = true;
424        },
425
426        _setThumbClass: function(thumb, className){
427                // summary:
428                //              Adds a CSS class to a thumbnail, only if 'autoLoad' is true
429                // thumb: DomNode
430                //              The thumbnail DOM node to set the class on
431                // className: String
432                //              The CSS class to add to the DOM node.
433                if(!this.autoLoad){ return; }
434                dojo.addClass(thumb, className);
435        },
436                                                 
437        _loadNextPage: function(){
438                // summary:
439                //              Loads the next page of thumbnail images
440                if(this._loadInProgress){return;}
441                this._loadInProgress = true;
442                var start = this.request.start + (this._noImages ? 0 : this.pageSize);
443               
444                var pos = start;
445                while(pos < this._thumbs.length && this._thumbs[pos]){pos ++;}
446               
447                var store = this.imageStore;
448               
449                //Define the function to call when the items have been
450                //returned from the data store.
451                var complete = function(items, request){
452                        if(store != this.imageStore){
453                                // If the store has been changed, ignore this callback.
454                                return;
455                        }
456                        if(items && items.length){
457                                var itemCounter = 0;
458                                var loadNext = dojo.hitch(this, function(){
459                                        if(itemCounter >= items.length){
460                                                this._loadInProgress = false;
461                                                return;
462                                        }
463                                        var counter = itemCounter++;
464
465                                        this._loadImage(items[counter], pos + counter, loadNext);
466                                });
467                                loadNext();
468
469                                //Show or hide the navigation arrows on the thumbnails,
470                                //depending on whether or not the widget is at the start,
471                                //end, or middle of the list of images.
472                                this._updateNavControls();
473                        }else{
474                                this._loadInProgress = false;
475                        }
476                };
477       
478                //Define the function to call if the store reports an error.
479                var error = function(){
480                        this._loadInProgress = false;
481                        console.log("Error getting items");
482                };
483
484                this.request.onComplete = dojo.hitch(this, complete);
485                this.request.onError = dojo.hitch(this, error);
486       
487                //Increment the start parameter. This is the dojo.data API's
488                //version of paging.
489                this.request.start = start;
490                this._noImages = false;
491               
492                //Execute the request for data.
493                this.imageStore.fetch(this.request);
494        },
495
496        _loadImage: function(data, index, callback){
497                // summary:
498                //              Loads an image.
499
500                var store = this.imageStore;
501                var url = store.getValue(data,this.imageThumbAttr);
502               
503                var imgContainer = dojo.create("div", {
504                        id: "img_" + this.id + "_" + index,
505                        "class": this.cellClass
506                });
507               
508                var img = dojo.create("img", {}, imgContainer);
509                img._index = index;
510                img._data = data;
511       
512                this._thumbs[index] = imgContainer;
513                var loadingDiv;
514                if(this.useLoadNotifier){
515                        loadingDiv = dojo.create("div", {
516                                id: "loadingDiv_" + this.id+"_" + index
517                        }, imgContainer);
518       
519                        //If this widget was previously told that the main image for this
520                        //thumb has been loaded, make the loading indicator transparent.
521                        this._setThumbClass(loadingDiv,
522                                this._loadedImages[index] ? "thumbLoaded":"thumbNotifier");
523                }
524                var size = dojo.marginBox(this.thumbsNode);
525                var defaultSize;
526                var sizeParam;
527                if(this.isHorizontal){
528                        defaultSize = this.thumbWidth;
529                        sizeParam = 'w';
530                } else{
531                        defaultSize = this.thumbHeight;
532                        sizeParam = 'h';
533                }
534                size = size[sizeParam];
535                var sl = this.thumbScroller.scrollLeft, st = this.thumbScroller.scrollTop;
536
537                dojo.style(this.thumbsNode, this._sizeProperty, (size + defaultSize + 20) + "px");
538
539                //Remember the scroll values, as changing the size can alter them
540                this.thumbScroller.scrollLeft = sl;
541                this.thumbScroller.scrollTop = st;
542                this.thumbsNode.appendChild(imgContainer);
543       
544                dojo.connect(img, "onload", this, dojo.hitch(this, function(){
545                        if(store != this.imageStore){
546                                // If the store has changed, ignore this load event
547                                return false;
548                        }
549                        this.resize();
550                                               
551                        // Have to use a timeout here to prevent a call stack that gets
552                        // so deep that IE throws stack overflow errors
553                        setTimeout(callback, 0);
554                        return false;
555                }));
556       
557                dojo.connect(img, "onclick", this, function(evt){
558                        dojo.publish(this.getClickTopicName(),  [{
559                                index: evt.target._index,
560                                data: evt.target._data,
561                                url: img.getAttribute("src"),
562                                largeUrl: this.imageStore.getValue(data,this.imageLargeAttr),
563                                title: this.imageStore.getValue(data,this.titleAttr),
564                                link: this.imageStore.getValue(data,this.linkAttr)
565                        }]);
566                        //
567                        dojo.query("." + this.cellClass, this.thumbsNode).removeClass(this.cellClass + "Selected");
568                        dojo.addClass(evt.target.parentNode, this.cellClass + "Selected");
569                        return false;
570                });
571                dojo.addClass(img, "imageGalleryThumb");
572                img.setAttribute("src", url);
573                var title = this.imageStore.getValue(data, this.titleAttr);
574                if(title){ img.setAttribute("title",title); }
575                this._updateNavControls();
576       
577        },
578
579        _updateNavControls: function(){
580                // summary:
581                //              Updates the navigation controls to hide/show them when at
582                //              the first or last images.
583                var change = function(node, add){
584                        var fn = add ? "addClass" : "removeClass";
585                        dojo[fn](node,"enabled");
586                        dojo[fn](node,"thumbClickable");
587                };
588               
589                var pos = this.isHorizontal ? "scrollLeft" : "scrollTop";
590                var size = this.isHorizontal ? "offsetWidth" : "offsetHeight";
591                change(this.navPrev, (this.thumbScroller[pos] > 0));
592               
593                var addClass = (this.thumbScroller[pos] + this._scrollerSize < this.thumbsNode[size]);
594                change(this.navNext, addClass);
595        }
596});
Note: See TracBrowser for help on using the repository browser.