PageSourceSearch

https://mulloway.com/wp-content/themes/mulloway/js/slideshow/slideshow.js

js mulloway.com collected 2026-10-08 02:38:43 UTC 27,234 bytes, 820 lines download raw bytes

1/**
2Script: Slideshow.js
3	Slideshow - A javascript class for Mootools to stream and animate the presentation of images on your website.
4
5License:
6	MIT-style license.
7
8Copyright:
9	Copyright (c) 2008 [Aeron Glemann](http://www.electricprism.com/aeron/).
10
11Dependencies:
12	Mootools 1.2 Core: Fx.Morph, Fx.Tween, Selectors, Element.Dimensions.
13	Mootools 1.2 More: Assets.
14*/
15
16Slideshow = new Class({
17	Implements: [Chain, Events, Options],
18	
19	options: {/*
20		onComplete: $empty,
21		onEnd: $empty,
22		onStart: $empty,*/
23		captions: false,
24		center: true,
25		classes: [],
26		controller: false,
27		delay: 2000,
28		duration: 750,
29		fast: false,
30		height: false,
31		href: '',
32		hu: '',
33		linked: false,
34		loader: {'animate': ['css/loader-#.png', 12]},
35		loop: true,
36		match: /\?slide=(\d+)$/,
37		overlap: true,
38		paused: false,
39		properties: ['href', 'rel', 'rev', 'title'],
40		random: false,
41		replace: [/(\.[^\.]+)$/, 't$1'],
42		resize: 'width',
43		slide: 0,
44		thumbnails: false,
45		titles: true,
46		transition: function(p){return -(Math.cos(Math.PI * p) - 1) / 2;},
47		width: false
48	},
49	
50/**
51Constructor: initialize
52	Creates an instance of the Slideshow class.
53	
54Arguments:
55	element - (element) The wrapper element.
56	data - (array or object) The images and optional thumbnails, captions and links for the show.
57	options - (object) The options below.
58	
59Syntax:
60	var myShow = new Slideshow(element, data, options);
61*/
62
63	initialize: function(el, data, options){	
64		this.setOptions(options);
65		this.slideshow = $(el);
66		if (!this.slideshow) 
67			return;
68		this.slideshow.set('styles', {'display': 'block', 'position': 'relative', 'z-index': 0});
69		var match = window.location.href.match(this.options.match);
70		this.slide = (this.options.match && match) ? match[1].toInt() : this.options.slide;
71		this.counter = this.delay = this.transition = 0;
72		this.direction = 'left';
73		this.paused = false;
74		if (!this.options.overlap)
75			this.options.duration *= 2;
76		var anchor = this.slideshow.getElement('a') || new Element('a');
77		if (!this.options.href)
78			this.options.href = anchor.get('href') || '';
79		if (this.options.hu.length && !this.options.hu.test(/\/$/)) 
80			this.options.hu += '/';
81		if (this.options.fast === true)
82			this.options.fast = 2;
83			
84		// styles
85		
86		var keys = ['slideshow', 'first', 'prev', 'play', 'pause', 'next', 'last', 'images', 'captions', 'controller', 'thumbnails', 'hidden', 'visible', 'inactive', 'active', 'loader'];
87		var values = keys.map(function(key, i){
88			return this.options.classes[i] || key;
89		}, this);
90		this.classes = values.associate(keys);
91		this.classes.get = function(){
92			var str = '.' + this.slideshow;
93			for (var i = 0, l = arguments.length; i < l; i++)
94				str += ('-' + this[arguments[i]]);
95			return str;
96		}.bind(this.classes);
97			
98		// data	
99			
100		if (!data){
101			this.options.hu = '';
102			data = {};
103			var thumbnails = this.slideshow.getElements(this.classes.get('thumbnails') + ' img');
104			this.slideshow.getElements(this.classes.get('images') + ' img').each(function(img, i){
105				var src = img.get('src');
106				var caption = $pick(img.get('alt'), img.get('title'), '');
107				var parent = img.getParent();
108				var properties = (parent.get('tag') == 'a') ? parent.getProperties : {};
109				var href = img.getParent().get('href') || '';
110				var thumbnail = (thumbnails[i]) ? thumbnails[i].get('src') : '';
111				data[src] = {'caption': caption, 'href': href, 'thumbnail': thumbnail};
112			});
113		}
114		var loaded = this.load(data);
115		if (!loaded)
116			return; 
117		
118		// events
119		
120		this.events = $H({'keydown': [], 'keyup': [], 'mousemove': []});
121		var keyup = function(e){
122			switch(e.key){
123				case 'left': 
124					this.prev(e.shift); break;
125				case 'right': 
126					this.next(e.shift); break;
127				case 'p': 
128					this.pause(); break;
129			}
130		}.bind(this);		
131		this.events.keyup.push(keyup);
132		document.addEvent('keyup', keyup);
133
134		// required elements
135			
136		var el = this.slideshow.getElement(this.classes.get('images'));
137		var images = (el) ? el.empty() : new Element('div', {'class': this.classes.get('images').substr(1)}).inject(this.slideshow);
138		var div = images.getSize();
139		this.height = this.options.height || div.y;		
140		this.width = this.options.width || div.x;
141		images.set({'styles': {'display': 'block', 'height': this.height, 'overflow': 'hidden', 'position': 'relative', 'width': this.width}});
142		this.slideshow.store('images', images);
143		this.a = this.image = this.slideshow.getElement('img') || new Element('img');
144		if (Browser.Engine.trident && Browser.Engine.version > 4)
145			this.a.style.msInterpolationMode = 'bicubic';
146		this.a.set('styles', {'display': 'none', 'position': 'absolute', 'zIndex': 1});
147		this.b = this.a.clone();
148		[this.a, this.b].each(function(img){
149			anchor.clone().cloneEvents(anchor).grab(img).inject(images);
150		});
151		
152		// optional elements
153		
154		if (this.options.captions)
155 			this._captions();
156		if (this.options.controller)
157			this._controller();
158		if (this.options.loader)
159 			this._loader();
160		if (this.options.thumbnails)
161			this._thumbnails();
162			
163		// begin show
164		
165		this._preload();
166	},
167	
168/**
169Public method: go
170	Jump directly to a slide in the show.
171
172Arguments:
173	n - (integer) The index number of the image to jump to, 0 being the first image in the show.
174	
175Syntax:
176	myShow.go(n);	
177*/
178
179	go: function(n, direction){
180		if ((this.slide - 1 + this.data.images.length) % this.data.images.length == n || $time() < this.transition)
181			return;		
182		$clear(this.timer);
183		this.delay = 0;		
184		this.direction = (direction) ? direction : ((n < this.slide) ? 'right' : 'left');
185		this.slide = n;
186		if (this.preloader) 
187			this.preloader = this.preloader.destroy();
188		this._preload(this.options.fast == 2 || (this.options.fast == 1 && this.paused));
189	},
190
191/**
192Public method: first
193	Goes to the first image in the show.
194
195Syntax:
196	myShow.first();	
197*/
198
199	first: function(){
200		this.prev(true); 
201	},
202
203/**
204Public method: prev
205	Goes to the previous image in the show.
206
207Syntax:
208	myShow.prev();	
209*/
210
211	prev: function(first){
212		var n = 0;
213		if (!first){
214			if (this.options.random){
215				
216				// if it's a random show get the previous slide from the showed array
217
218				if (this.showed.i < 2)
219					return;
220				this.showed.i -= 2;
221				n = this.showed.array[this.showed.i];
222			}
223			else
224				n = (this.slide - 2 + this.data.images.length) % this.data.images.length;									
225		}
226		this.go(n, 'right');
227	},
228
229/**
230Public method: pause
231	Toggles play / pause state of the show.
232
233Arguments:
234	p - (undefined, 1 or 0) Call pause with no arguments to toggle the pause state. Call pause(1) to force pause, or pause(0) to force play.
235
236Syntax:
237	myShow.pause(p);	
238*/
239
240	pause: function(p){
241		if ($chk(p))
242			this.paused = (p) ? false : true;
243		if (this.paused){
244			this.paused = false;
245			this.delay = this.transition = 0;		
246			this.timer = this._preload.delay(100, this);
247			[this.a, this.b].each(function(img){
248				['morph', 'tween'].each(function(p){
249					if (this.retrieve(p)) this.get(p).resume();
250				}, img);
251			});
252			if (this.options.controller)
253				this.slideshow.getElement('.' + this.classes.pause).removeClass(this.classes.play);
254		} 
255		else {
256			this.paused = true;
257			this.delay = Number.MAX_VALUE;
258			this.transition = 0;
259			$clear(this.timer);
260			[this.a, this.b].each(function(img){
261				['morph', 'tween'].each(function(p){
262					if (this.retrieve(p)) this.get(p).pause();
263				}, img);
264			});
265			if (this.options.controller)
266				this.slideshow.getElement('.' + this.classes.pause).addClass(this.classes.play);
267		}
268	},
269	
270/**
271Public method: next
272	Goes to the next image in the show.
273
274Syntax:
275	myShow.next();	
276*/
277
278	next: function(last){
279		var n = (last) ? this.data.images.length - 1 : this.slide;
280		this.go(n, 'left');
281	},
282
283/**
284Public method: last
285	Goes to the last image in the show.
286
287Syntax:
288	myShow.last();	
289*/
290
291	last: function(){
292		this.next(true); 
293	},
294
295/**
296Public method: load
297	Loads a new data set into the show: will stop the current show, rewind and rebuild thumbnails if applicable.
298
299Arguments:
300	data - (array or object) The images and optional thumbnails, captions and links for the show.
301
302Syntax:
303	myShow.load(data);
304*/
305
306	load: function(data){
307		this.firstrun = true;
308		this.showed = {'array': [], 'i': 0};
309		if ($type(data) == 'array'){
310			this.options.captions = false;			
311			data = new Array(data.length).associate(data.map(function(image, i){ return image 
311+ '?' + i })); 
312		}
313		this.data = {'images': [], 'captions': [], 'hrefs': [], 'thumbnails': []};
314		for (var image in data){
315			var obj = data[image] || {};
316			var caption = (obj.caption) ? obj.caption.trim() : '';
317			var href = (obj.href) ? obj.href.trim() : ((this.options.linked) ? this.options.hu + image : this.options.href);
318			var thumbnail = (obj.thumbnail) ? obj.thumbnail.trim() : image.replace(this.options.replace[0], this.options.replace[1]);
319			this.data.images.push(image);
320			this.data.captions.push(caption);
321			this.data.hrefs.push(href);
322			this.data.thumbnails.push(thumbnail);
323		}
324		if (this.options.random)
325			this.slide = $random(0, this.data.images.length - 1);
326		
327		// only run when data is loaded dynamically into an existing slideshow instance
328		
329		if (this.options.thumbnails && this.slideshow.retrieve('thumbnails'))
330			this._thumbnails();
331		if (this.slideshow.retrieve('images')){
332			[this.a, this.b].each(function(img){
333				['morph', 'tween'].each(function(p){
334					if (this.retrieve(p)) this.get(p).cancel();
335				}, img);
336			});
337			this.slide = this.transition = 0;
338			this.go(0);		
339		}
340		return this.data.images.length;
341	},
342	
343/**
344Public method: destroy
345	Destroys a Slideshow instance.
346
347Arguments:
348	p - (string) The images and optional thumbnails, captions and links for the show.
349
350Syntax:
351	myShow.destroy(p);
352*/
353
354	destroy: function(p){
355		this.events.each(function(array, e){
356			array.each(function(fn){ document.removeEvent(e, fn); });
357		});
358		this.pause(1);
359		if (this.options.loader)
360			$clear(this.slideshow.retrieve('loader').retrieve('timer'));		
361		if (this.options.thumbnails)
362			$clear(this.slideshow.retrieve('thumbnails').retrieve('timer'));
363		this.slideshow.uid = Native.UID++;
364		if (p)
365			this.slideshow[p]();
366	},
367	
368/**
369Private method: preload
370	Preloads the next slide in the show, once loaded triggers the show, updates captions, thumbnails, etc.
371*/
372
373	_preload: function(fast){
374		if (!this.preloader)
375		 	this.preloader = new Asset.image(this.options.hu + this.data.images[this.slide], {'onload': function(){
376				this.store('loaded', true);
377			}});	
378		if (this.preloader.retrieve('loaded') && $time() > this.delay && $time() > this.transition){
379			if (this.stopped){
380				if (this.options.captions)
381					this.slideshow.retrieve('captions').get('morph').cancel().start(this.classes.get('captions', 'hidden'));
382				this.pause(1);
383				if (this.end)
384					this.fireEvent('end');
385				this.stopped = this.end = false;
386				return;				
387			}					
388			this.image = (this.counter % 2) ? this.b : this.a;
389			this.image.set('styles', {'display': 'block', 'height': 'auto', 'visibility': 'hidden', 'width': 'auto', 'zIndex': this.counter});
390			['src', 'height', 'width'].each(function(prop){
391				this.image.set(prop, this.preloader.get(prop));
392			}, this);
393			this._resize(this.image);
394			this._center(this.image);
395			var anchor = this.image.getParent();
396			if (this.data.hrefs[this.slide])
397				anchor.set('href', this.data.hrefs[this.slide]);			
398			else
399				anchor.erase('href');
400			var text = (this.data.captions[this.slide])
401				? this.data.captions[this.slide].replace(/<.+?>/gm, '').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, "'") 
402				: '';
403			this.image.set('alt', text);		
404			if (this.options.titles)
405				anchor.set('title', text);
406			if (this.options.loader)
407				this.slideshow.retrieve('loader').fireEvent('hide');
408			if (this.options.captions)
409				this.slideshow.retrieve('captions').fireEvent('update', fast);				
410			if (this.options.thumbnails)
411				this.slideshow.retrieve('thumbnails').fireEvent('update', fast); 			
412			this._show(fast);
413			this._loaded();
414		} 
415		else {
416			if ($time() > this.delay && this.options.loader)
417				this.slideshow.retrieve('loader').fireEvent('show');
418			this.timer = (this.paused && this.preloader.retrieve('loaded')) ? null : this._preload.delay(100, this, fast); 
419		}
420	},
421
422/**
423Private method: show
424	Does the slideshow effect.
425*/
426
427	_show: function(fast){
428		if (!this.image.retrieve('morph')){
429			var options = (this.options.overlap) ? {'duration': this.options.duration, 'link': 'cancel'} : {'duration': this.options.duration / 2, 'link': 'chain'};
430			$$(this.a, this.b).set('morph', $merge(options, {'onStart': this._start.bind(this), 'onComplete': this._complete.bind(this), 'transition': this.options.transition}));
431		}
432		var hidden = this.classes.get('images', ((this.direction == 'left') ? 'next' : 'prev'));
433		var visible = this.classes.get('images', 'visible');
434		var img = (this.counter % 2) ? this.a : this.b;
435		if (fast){			
436			img.get('morph').cancel().set(hidden);
437			this.image.get('morph').cancel().set(visible); 			
438		} 
439		else {
440			if (this.options.overlap){
441				img.get('morph').set(visible);
442				this.image.get('morph').set(hidden).start(visible);
443			} 
444			else	{
445				var fn = function(hidden, visible){
446					this.image.get('morph').set(hidden).start(visible);
447				}.pass([hidden, visible], this);
448				hidden = this.classes.get('images', ((this.direction == 'left') ? 'prev' : 'next'));
449				img.get('morph').set(visible).start(hidden).chain(fn);
450			}
451		}
452	},
453	
454/**
455Private method: loaded
456	Run after the current image has been loaded, sets up the next image to be shown.
457*/
458
459	_loaded: function(){
460		this.counter++;
461		this.delay = (this.paused) ? Number.MAX_VALUE : $time() + this.options.duration + this.options.delay;
462		this.direction = 'left';
463		this.transition = (this.options.fast == 2 || (this.options.fast == 1 && this.paused)) ? 0 : $time() + this.options.duration;			
464		if (this.slide + 1 == this.data.images.length && !this.options.loop && !this.options.random)
465			this.stopped = this.end = true;			
466		if (this.options.random){
467			this.showed.i++;
468			if (this.showed.i >= this.showed.array.length){
469				var n = this.slide;
470				if (this.showed.array.getLast() != n) this.showed.array.push(n);
471				while (this.slide == n)
472					this.slide = $random(0, this.data.images.length - 1);				
473			}
474			else
475				this.slide = this.showed.array[this.showed.i];
476		}
477		else
478			this.slide = (this.slide + 1) % this.data.images.length;
479		if (this.image.getStyle('visibility') != 'visible')
480			(function(){ this.image.setStyle('visibility', 'visible'); }).delay(1, this);			
481		if (this.preloader) 
482			this.preloader = this.preloader.destroy();
483		this._preload();
484	},
485
486/**
487Private method: center
488	Center an image.
489*/
490
491	_center: function(img){
492		if (this.options.center){
493			var size = img.getSize();
494			img.set('styles', {'left': (size.x - this.width) / -2, 'top': (size.y - this.height) / -2});
495		}
496	},
497
498/**
499Private method: resize
500	Resizes an image.
501*/
502
503	_resize: function(img){
504		if (this.options.resize){
505			var h = this.preloader.get('height'), w = this.preloader.get('width');
506			var dh = this.height / h, dw = this.width / w, d;
507			if (this.options.resize == 'length')
508				d = (dh > dw) ? dw : dh;
509			else
510				d = (dh > dw) ? dh : dw;
511			img.set('styles', {height: Math.ceil(h * d), width: Math.ceil(w * d)});
512		}	
513	},
514
515/**
516Private method: start
517	Callback on start of slide change.
518*/
519
520	_start: function(){		
521		this.fireEvent('start');
522	},
523
524/**
525Private method: complete
526	Callback on start of slide change.
527*/
528
529	_complete: function(){
530		if (this.firstrun && this.options.paused){
531			this.firstrun = false;
532			this.pause(1);
533		}
534		this.fireEvent('complete');
535	},
536
537/**
538Private method: captions
539	Builds the optional caption element, adds interactivity.
540	This method can safely be removed if the captions option is not enabled.
541*/
542
543	_captions: function(){
544 		if (this.options.captions === true) 
545 			this.options.captions = {};
546		var el = this.slideshow.getElement(this.classes.get('captions'));
547		var captions = (el) ? el.empty() : new Element('div', {'class': this.classes.get('captions').substr(1)}).inject(this.slideshow);
548		captions.set({
549			'events': {
550				'update': function(fast){	
551					var captions = this.slideshow.retrieve('captions');
552					var empty = (this.data.captions[this.slide] === '');
553					if (fast){
554						var p = (empty) ? 'hidden' : 'visible';
555						captions.set('html', this.data.captions[this.slide]).get('morph').cancel().set(this.classes.get('captions', p));
556					}
557					else {
558						var fn = (empty) ? $empty : function(n){
559							this.slideshow.retrieve('captions').set('html', this.data.captions[n]).morph(this.classes.get('captions', 'visible'))
560						}.pass(this.slide, this);		
561						captions.get('morph').cancel().start(this.classes.get('captions', 'hidden')).chain(fn);
562					}
563				}.bind(this)
564			},
565			'morph': $merge(this.options.captions, {'link': 'chain'})
566		});
567		this.slideshow.store('captions', captions);
568	},
569
570/**
571Private method: controller
572	Builds the optional controller element, adds interactivity.
573	This method can safely be removed if the controller option is not enabled.
574*/
575
576	_controller: function(){
577 		if (this.options.controller === true)
578 			this.options.controller = {};
579		var el = this.slideshow.getElement(this.classes.get('controller'));
580		var controller = (el) ? el.empty() : new Element('div', {'class': this.classes.get('controller').substr(1)}).inject(this.slideshow);
581		var ul = new Element('ul').inject(controller);
582		$H({'first': 'Shift + Leftwards Arrow', 'prev': 'Leftwards Arrow', 'pause': 'P', 'next': 'Rightwards Arrow', 'last': 'Shift + Rightwards Arrow'}).each(function(accesskey, action){
583			var li = new Element('li', {
584				'class': (action == 'pause' && this.options.paused) ? this.classes.play + ' ' + this.classes[action] : this.classes[action]
585			}).inject(ul);
586			var a = this.slideshow.retrieve(action, new Element('a', {
587				'title': ((action == 'pause') ? this.classes.play.capitalize() + ' / ' : '') + this.classes[action].capitalize() + ' [' + accesskey + ']'				
588			}).inject(li));
589			a.set('events', {
590				'click': function(action){this[action]();}.pass(action, this),
591				'mouseenter': function(active){this.addClass(active);}.pass(this.classes.active, a),
592				'mouseleave': function(active){this.removeClass(active);}.pass(this.classes.active, a)
593			});		
594		}, this);
595		controller.set({
596			'events': {
597				'hide': function(hidden){  
598					if (!this.retrieve('hidden'))
599						this.store('hidden', true).morph(hidden);
600				}.pass(this.classes.get('controller', 'hidden'), controller),
601				'show': function(visible){  
602					if (this.retrieve('hidden'))
603						this.store('hidden', false).morph(visible);
604				}.pass(this.classes.get('controller', 'visible'), controller)
605			},
606			'morph': $merge(this.options.controller, {'link': 'cancel'})
607		}).store('hidden', false);
608		var keydown = function(e){
609			if (['left', 'right', 'p'].contains(e.key)){
610				var controller = this.slideshow.retrieve('controller');
611				if (controller.retrieve('hidden'))
612					controller.get('morph').set(this.classes.get('controller', 'visible')); 			
613				switch(e.key){
614					case 'left': 
615						this.slideshow.retrieve((e.shift) ? 'first' : 'prev').fireEvent('mouseenter'); break;
616					case 'right':
617						this.slideshow.retrieve((e.shift) ? 'last' : 'next').fireEvent('mouseenter'); break;
618					default:
619						this.slideshow.retrieve('pause').fireEvent('mouseenter'); break;
620				}
621			}
622		}.bind(this);
623		this.events.keydown.push(keydown);
624		var keyup = function(e){
625			if (['left', 'right', 'p'].contains(e.key)){
626				var controller = this.slideshow.retrieve('controller');
627				if (controller.retrieve('hidden'))
628					controller.store('hidden', false).fireEvent('hide'); 
629				switch(e.key){
630					case 'left': 
631						this.slideshow.retrieve((e.shift) ? 'first' : 'prev').fireEvent('mouseleave'); break;
632					case 'right': 
633						this.slideshow.retrieve((e.shift) ? 'last' : 'next').fireEvent('mouseleave'); break;
634					default:
635						this.slideshow.retrieve('pause').fireEvent('mouseleave'); break;
636				}
637			}
638		}.bind(this);
639		this.events.keyup.push(keyup);
640		var mousemove = function(e){
641			var images = this.slideshow.retrieve('images').getCoordinates();
642			if (e.page.x > images.left && e.page.x < images.right && e.page.y > images.top && e.page.y < images.bottom)
643				this.slideshow.retrieve('controller').fireEvent('show');
644			else
645				this.slideshow.retrieve('controller').fireEvent('hide');
646		}.bind(this);
647		this.events.mousemove.push(mousemove);
648		document.addEvents({'keydown': keydown, 'keyup': keyup, 'mousemove': mousemove});
649		this.slideshow.retrieve('controller', controller).fireEvent('hide');
650	},
651
652/**
653Private method: loader
654	Builds the optional loader element, adds interactivity.
655	This method can safely be removed if the loader option is not enabled.
656*/
657
658	_loader: function(){
659 		if (this.options.loader === true) 
660 			this.options.loader = {};
661		var loader = new Element('div', {
662			'class': this.classes.get('loader').substr(1),				
663			'morph': $merge(this.options.loader, {'link': 'cancel'})
664		}).store('hidden', false).store('i', 1).inject(this.slideshow.retrieve('images'));
665		if (this.options.loader.animate){
666			for (var i = 0; i < this.options.loader.animate[1]; i++)
667				img = new Asset.image(this.options.loader.animate[0].replace(/#/, i));
668			if (Browser.Engine.trident4 && this.options.loader.animate[0].contains('png'))
669				loader.setStyle('backgroundImage', 'none');					
670		}
671		loader.set('events', {
672			'animate': function(){  
673				var loader = this.slideshow.retrieve('loader');				
674				var i = (loader.retrieve('i').toInt() + 1) % this.options.loader.animate[1];
675				loader.store('i', i);
676				var img = this.options.loader.animate[0].replace(/#/, i);
677				if (Browser.Engine.trident4 && this.options.loader.animate[0].contains('png'))
678					loader.style.filter = 'progid:DXImageTransform.Microsoft.AlphaImageLoader(src="' + img + '", sizingMethod="scale")';
679				else 
680					loader.setStyle('backgroundImage', 'url(' + img + ')');
681			}.bind(this),
682			'hide': function(){  
683				var loader = this.slideshow.retrieve('loader');
684				if (!loader.retrieve('hidden')){
685					loader.store('hidden', true).morph(this.classes.get('loader', 'hidden'));
686					if (this.options.loader.animate)
687						$clear(loader.retrieve('timer'));					
688				}
689			}.bind(this),
690			'show': function(){  
691				var loader = this.slideshow.retrieve('loader');
692				if (loader.retrieve('hidden')){
693					loader.store('hidden', false).morph(this.classes.get('loader', 'visible'));
694					if (this.options.loader.animate)
695						loader.store('timer', function(){this.fireEvent('animate');}.periodical(50, loader));
696				}
697			}.bind(this)
698		});
699		this.slideshow.retrieve('loader', loader).fireEvent('hide');
700	},
701	
702/**
703Private method: thumbnails
704	Builds the optional thumbnails element, adds interactivity.
705	This method can safely be removed if the thumbnails option is not enabled.
706*/
707
708	_thumbnails: function(){
709 		if (this.options.thumbnails === true) 
710 			this.options.thumbnails = {}; 
711		var el = this.slideshow.getElement(this.classes.get('thumbnails'));
712		var thumbnails = (el) ? el.empty() : new Element('div', {'class': this.classes.get('thumbnails').substr(1)}).inject(this.slideshow);
713		thumbnails.setStyle('overflow', 'hidden');
714		var ul = new Element('ul', {'tween': {'link': 'cancel'}}).inject(thumbnails);
715		this.data.thumbnails.each(function(thumbnail, i){
716			var li = new Element('li').inject(ul);
717			var a = new Element('a', {
718				'events': {
719					'click': function(i){
720						this.go(i); 
721						return false; 
722					}.pass(i, this),
723					'loaded': function(){
724						this.data.thumbnails.pop();
725						if (!this.data.thumbnails.length){
726							var div = thumbnails.getCoordinates();
727							var props = thumbnails.retrieve('props');			
728							var limit = 0, pos = props[1], size = props[2];		
729							thumbnails.getElements('li').each(function(li){			
730								var li = li.getCoordinates();		
731								if (li[pos] > limit) limit = li[pos];
732							}, this);			
733							thumbnails.store('limit', div[size] + div[props[0]] - limit);
734						}
735					}.bind(this)
736				},
737				'href': this.options.hu + this.data.images[i],
738				'morph': $merge(this.options.thumbnails, {'link': 'cancel'})
739			}).inject(li);
740			if (this.data.captions[i] && this.options.titles)
741				a.set('title', this.data.captions[i].replace(/<.+?>/gm, '').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, "'"));
742			var img = new Asset.image(this.options.hu + thumbnail, {
743				'onload': function(){this.fireEvent('loaded');}.bind(a) 
744			}).inject(a);
745		}, this);
746		thumbnails.set('events', {
747			'scroll': function(n, fast){
748				var div = this.getCoordinates();
749				var ul = this.getElement('ul').getPosition();
750				var props = this.retrieve('props');
751				var axis = props[3], delta, pos = props[0], size = props[2], value;				
752				var tween = this.getElement('ul').get('tween', {'property': pos});	
753				if ($chk(n)){
754					var li = this.getElements('li')[n].getCoordinates();
755					delta = div[pos] + (div[size] / 2) - (li[size] / 2) - li[pos]	
756					value = (ul[axis] - div[pos] + delta).limit(this.retrieve('limit'), 0);
757					if (fast)	
758						tween.set(value);
759					else						 
760						tween.start(value);
761				}
762				else{
763					var area = div[props[2]] / 3, page = this.retrieve('page'), velocity = -0.2;			
764					if (page[axis] < (div[pos] + area))
765						delta = (page[axis] - div[pos] - area) * velocity;
766					else if (page[axis] > (div[pos] + div[size] - area))
767						delta = (page[axis] - div[pos] - div[size] + area) * velocity;			
768					if (delta){			
769						value = (ul[axis] - div[pos] + delta).limit(this.retrieve('limit'), 0);
770						tween.set(value);
771					}
772				}				
773			}.bind(thumbnails),
774			'update': function(fast){
775				var thumbnails = this.slideshow.retrieve('thumbnails');
776				thumbnails.getElements('a').each(function(a, i){	
777					if (i == this.slide){
778						if (!a.retrieve('active', false)){
779							a.store('active', true);
780							var active = this.classes.get('thumbnails', 'active');							
781							if (fast) a.get('morph').set(active);
782							else a.morph(active);
783						}
784					} 
785					else {
786						if (a.retrieve('active', true)){
787							a.store('active', false);
788							var inactive = this.classes.get('thumbnails', 'inactive');						
789							if (fast) a.get('morph').set(inactive);
790							else a.morph(inactive);
791						}
792					}
793				}, this);
794				if (!thumbnails.retrieve('mouseover'))
795					thumbnails.fireEvent('scroll', [this.slide, fast]);
796			}.bind(this)
797		})
798		var div = thumbnails.getCoordinates();
799		thumbnails.store('props', (div.height > div.width) ? ['top', 'bottom', 'height', 'y'] : ['left', 'right', 'width', 'x']);
800		var mousemove = function(e){
801			var div = this.getCoordinates();
802			if (e.page.x > div.left && e.page.x < div.right && e.page.y > div.top && e.page.y < div.bottom){
803				this.store('page', e.page);			
804				if (!this.retrieve('mouseover')){
805					this.store('mouseover', true);
806					this.store('timer', function(){this.fireEvent('scroll');}
806.periodical(50, this));
807				}
808			}
809			else {
810				if (this.retrieve('mouseover')){
811					this.store('mouseover', false);				
812					$clear(this.retrieve('timer'));
813				}
814			}
815		}.bind(thumbnails);
816		this.events.mousemove.push(mousemove);
817		document.addEvent('mousemove', mousemove);
818		this.slideshow.store('thumbnails', thumbnails);
819	}
820});

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.