Subversion Repositories Applications.papyrus

Rev

Details | Last modification | View Log | RSS feed

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