PageSourceSearch

https://www.nva-flieger.de/plugins/slideshow2/js/slideshow.js

js nva-flieger.de collected 2026-10-02 21:32:35 UTC 32,605 bytes, 1,028 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) 2011 [Aeron Glemann](http://www.electricprism.com/aeron/).
10
11Dependencies:
12	Mootools 1.3.1 Core: Fx.Morph, Fx.Tween, Selectors, Element.Dimensions.
13	Mootools 1.3.1.1 More: Assets.
14*/
15(function(){
16	WhenPaused = 1 << 0;
17	WhenPlaying = 1 << 1;
18	OnStart = 1 << 2;
19	
20	Slideshow = new Class({
21		Implements: [Chain, Events, Options],
22
23		options: {/*
24			onComplete: $empty,
25			onEnd: $empty,
26			onStart: $empty,*/
27		    accesskeys: {'first': {'key': 'shift left', 'label': 'Shift + Leftwards Arrow'}, 'prev': {'key': 'left', 'label': 'Leftwards Arrow'}, 'pause': {'key': 'p', 'label': 'P'}, 'next': {'key': 'right', 'label': 'Rightwards Arrow'}, 'last': {'key': 'shift right', 'label': 'Shift + Rightwards Arrow'}},
28			captions: true,
29			center: true,
30			classes: [/*'slideshow', 'first', 'prev', 'play', 'pause', 'next', 'last', 'images', 'captions', 'controller', 'thumbnails', 'hidden', 'visible', 'inactive', 'active', 'loader'*/],
31			controller: true,
32			data: null,
33			delay: 2000,
34			duration: 1000,
35			fast: false,
36			height: false,
37			href: '',
38			hu: '',
39			linked: false,
40			loader: true,
41			loop: true,
42			match: /\?slide=(\d+)$/,
43			overlap: true,
44			paused: false,
45			random: false,
46			replace: [/(\.[^\.]+)$/, 't$1'],
47			resize: 'fill',
48			slide: 0,
49			thumbnails: true,
50			titles: false,
51			transition: 'sine:in:out',
52			width: false
53		},
54
55	/**
56	Constructor: initialize
57		Creates an instance of the Slideshow class.
58
59	Arguments:
60		element - (element) The wrapper element.
61		data - (array or object) The images and optional thumbnails, captions and links for the show.
62		options - (object) The options below.
63
64	Syntax:
65		var myShow = new Slideshow(element, data, options);
66	*/
67
68		initialize: function(el, data, options){
69			this.setOptions(options);
70			this.el = document.id(el);
71			if (!this.el) 
72				return;
73			var match = window.location.href.match(this.options.match);
74			this.slide = this._slide = this.options.match && match ? match[1].toInt() 
75				: this.options.slide;
76			this.counter = this.timeToNextTransition = this.timeToTransitionComplete = 0;
77			this.direction = 'left';
78			this.cache = {};
79			this.paused = false;
80			if (!this.options.overlap)
81				this.options.duration *= 2;
82			var anchor = this.el.getElement('a') || new Element('a');
83			if (!this.options.href)
84				this.options.href = anchor.get('href') || '';
85			if (this.options.hu.length && !this.options.hu.test(/\/$/)) 
86				this.options.hu += '/';
87			if (this.options.fast === true)
88				this.options.fast = WhenPaused | WhenPlaying;
89
90			// styles
91
92			var keys = 'slideshow first prev play pause next last images captions controller thumbnails hidden visible inactive active loader'.split(' '),
93				values = keys.map(function(key, i){
94					return this.options.classes[i] || key;
95				}, this);
96			this.classes = values.associate(keys);
97			this.classes.get = function(){
98				var str = '.' + this.slideshow;
99				for (var i = 0, l = arguments.length; i < l; i++)
100					str += '-' + this[arguments[i]];
101				return str;
102			}.bind(this.classes);
103
104			// data	
105			
106			if (!data){
107				this.options.hu = '';
108				data = {};
109				var thumbnails = this.el.getElements(this.classes.get('thumbnails') + ' img');
110				this.el.getElements(this.classes.get('images') + ' img').each(function(img, i){
111					var src = img.src,
112						caption = img.alt || img.title,
113						href = img.getParent().href,
114						thumbnail = thumbnails[i] ? thumbnails[i].src 
115							: '';
116					data[src] = {'caption': caption, 'href': href, 'thumbnail': thumbnail};
117				});
118			}
119			var loaded = this.load(data);
120			if (!loaded)
121				return; 
122
123			// events
124
125			this.events = {};
126			this.events.push = function(type, fn){
127				if (!this[type])
128					this[type] = [];
129				this[type].push(fn);
130				document.addEvent(type, fn);
131				return this;
132			}.bind(this.events);
133
134			this.accesskeys = {};
135			for (action in this.options.accesskeys){
136				var obj = this.options.accesskeys[action];
137				this.accesskeys[action] = accesskey = {'label': obj.label};
138				['shift', 'control', 'alt'].each(function(modifier){
139					var re = new RegExp(modifier, 'i');
140					accesskey[modifier] = obj.key.test(re);
141					obj.key = obj.key.replace(re, '');
142				});
143				accesskey.key = obj.key.trim();
144			}
145
146			this.events.push('keyup', function(e){
147				Object.each(this.accesskeys, function(accesskey, action){
148					if (e.key == accesskey.key && e.shift == accesskey.shift && e.control == accesskey.control && e.alt == accesskey.alt)
149						this[action]();
150				}, this);			
151			}.bind(this));	 
152
153			// required elements
154
155			var el = this.el.getElement(this.classes.get('images')),
156				img = this.el.getElement('img') || new Element('img'),
157				images = el ? el.empty() 
158					: new Element('div', {'class': this.classes.get('images').substr(1)}).inject(this.el),
159				div = images.getSize();
160			this.height = this.options.height || div.y;		
161			this.width = this.options.width || div.x;
162			images.set({'styles': {'height': this.height, 'width': this.width}});
163			this.el.store('images', images);
164			this.a = this.image = img;
165			if (Browser.ie && Browser.version >= 7)
166				this.a.style.msInterpolationMode = 'bicubic';
167			this.a.set('styles', {'display': 'none'});
168			this.b = this.a.clone();
169			[this.a, this.b].each(function(img){
170				anchor.clone().cloneEvents(anchor).grab(img).inject(images);
171			});
172
173			// optional elements
174
175			this.options.captions && new Caption(this);
176			this.options.controller && new Controller(this);
177			this.options.loader && new Loader(this);
178			this.options.thumbnails && new Thumbnails(this);
179
180			// begin show
181
182			this._preload(this.options.fast & OnStart);
183		},
184
185	/**
186	Public method: go
187		Jump directly to a slide in the show.
188
189	Arguments:
190		n - (integer) The index number of the image to jump to, 0 being the first image in the show.
191
192	Syntax:
193		myShow.go(n);	
194	*/
195
196		go: function(n, direction){
197			var nextSlide = (this.slide + this.data.images.length) % this.data.images.length;
198			if (n == nextSlide || Date.now() < this.timeToTransitionComplete)
199				return;		
200			clearTimeout(this.timer);
201			this.timeToNextTransition = 0;		
202			this.direction = direction ? direction 
203				: n < this._slide ? 'right' 
204				: 'left';
205			this.slide = this._slide = n;
206			if (this.preloader) 
207				this.preloader = this.preloader.destroy();
208			this._preload((this.options.fast & WhenPlaying) || (this.paused && this.options.fast & WhenPaused));
209		},
210
211	/**
212	Public method: first
213		Goes to the first image in the show.
214
215	Syntax:
216		myShow.first();	
217	*/
218
219		first: function(){
220			this.prev(true); 
221		},
222
223	/**
224	Public method: prev
225		Goes to the previous image in the show.
226
227	Syntax:
228		myShow.prev();	
229	*/
230
231		prev: function(first){
232			var n = 0;
233			if (!first){
234				if (this.options.random){
235					if (this.showed.i < 2)
236						return;
237					this.showed.i -= 2;
238					n = this.showed.array[this.showed.i];
239				}
240				else
241					n = (this.slide - 1 + this.data.images.length) % this.data.images.length;									
242			}
243			this.go(n, 'right');
244		},
245
246	/**
247	Public method: pause
248		Toggles play / pause state of the show.
249
250	Arguments:
251		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.
252
253	Syntax:
254		myShow.pause(p);	
255	*/
256
257		pause: function(p){
258			if (p != undefined)
259				this.paused = p ? false 
260					: true;
261			if (this.paused){ // play
262				this.paused = false;
263				this.timeToTransitionComplete = Date.now() + this.timeToTransitionComplete;		
264				this.timer = this._preload.delay(50, this);
265				[this.a, this.b].each(function(img){
266					['morph', 'tween'].each(function(p){
267						if (this.retrieve(p)) this.get(p).resume();
268					}, img);
269				});
270				this.controller && this.el.retrieve('pause').getParent().removeClass(this.classes.play);
271			} 
272			else { // pause
273				this.paused = true;
274				this.timeToTransitionComplete = this.timeToTransitionComplete - Date.now();
275				clearTimeout(this.timer);
276				[this.a, this.b].each(function(img){
277					['morph', 'tween'].each(function(p){
278						if (this.retrieve(p)) this.get(p).pause();
279					}, img);
280				});
281				this.controller && this.el.retrieve('pause').getParent().addClass(this.classes.play);
282			}
283		},
284
285	/**
286	Public method: next
287		Goes to the next image in the show.
288
289	Syntax:
290		myShow.next();	
291	*/
292
293		next: function(last){
294			var n = last ? this.data.images.length - 1 
295				: this._slide;
296			this.go(n, 'left');
297		},
298
299	/**
300	Public method: last
301		Goes to the last image in the show.
302
303	Syntax:
304		myShow.last();	
305	*/
306
307		last: function(){
308			this.next(true); 
309		},
310
311	/**
312	Public method: load
313		Loads a new data set into the show: will stop the current show, rewind and rebuild thumbnails if applicable.
314
315	Arguments:
316		data - (array or object) The images and optional thumbnails, captions and links for the show.
317
318	Syntax:
319		myShow.load(data);
320	*/
321
322		load: function(data){
323			this.firstrun = true;
324			this.showed = {'array': [], 'i': 0};
325			if (typeOf(data) == 'array'){
326				this.options.captions = false;			
327				data = new Array(data.length).associate(data.map(function(image, i){ return image + '?' + i })); 
328			}
329			this.data = {'images': [], 'captions': [], 'hrefs': [], 'thumbnails': [], 'targets': [], 'titles': []};
330			for (var image in data){
331				var obj = data[image] || {},
332					image = this.options.hu + image,
333					caption = obj.caption ? obj.caption.trim() 
334						: '',
335					href = obj.href ? obj.href.trim() 
336						: this.options.linked ? image 
337						: this.options.href,
338					target = obj.target ? obj.target.trim() 
339						: '_self',
340					thumbnail = obj.thumbnail ? this.options.hu + obj.thumbnail.trim() 
341						: image.replace(this.options.replace[0], this.options.replace[1]),
342					title = caption.replace(/<.+?>/gm, '').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, "'");
343				this.data.images.push(image);
344				this.data.captions.push(caption);
345				this.data.hrefs.push(href);
346				this.data.targets.push(target);
347				this.data.thumbnails.push(thumbnail);
348				this.data.titles.push(title);
349			}
350			if (this.options.random)
351				this.slide = this._slide = Number.random(0, this.data.images.length - 1);
352
353			// only run when data is loaded dynamically into an existing slideshow instance
354
355			if (this.options.thumbnails && this.el.retrieve('thumbnails'))
356				this._thumbnails();
357			if (this.el.retrieve('images')){
358				[this.a, this.b].each(function(img){
359					['morph', 'tween'].each(function(p){
360						if (this.retrieve(p)) this.get(p).cancel();
361					}, img);
362				});
363				this.slide = this._slide = this.timeToTransitionComplete = 0;
364				this.go(0);		
365			}
366			return this.data.images.length;
367		},
368
369	/**
370	Public method: destroy
371		Destroys a Slideshow instance.
372
373	Arguments:
374		p - (string) The images and optional thumbnails, captions and links for the show.
375
376	Syntax:
377		myShow.destroy(p);
378	*/
379
380		destroy: function(p){
381			Object.each(this.events, function(array, e){
382				if ('each' in array)
383					array.each(function(fn){ document.removeEvent(e, fn); });
384			});
385			this.pause(1);
386			'caption loader thumbnails'.split(' ').each(function(i, timer){
387				this.options[i] && (timer = this[i].retrieve('timer')) && clearTimeout(timer);
388			}, this);
389			typeOf(this.el[p]) == 'function' && this.el[p]();
390			delete this.el.uid;
391		},
392
393	/**
394	Private method: preload
395		Preloads the next slide in the show, once loaded triggers the show, updates captions, thumbnails, etc.
396	*/
397
398		_preload: function(fast){
399			var src = this.data.images[this._slide].replace(/([^?]+).*/, '$1'),
400				cached = loaded = !!this.cache[src];
401			if (!cached){
402				if (!this.preloader)
403				 	this.preloader = new Asset.image(src, {
404						'onerror': function(){
405							// do something
406						},
407						'onload': function(){
408							this.store('loaded', true);
409						}
410					});
411				loaded = this.preloader.retrieve('loaded') && this.preloader.get('width');
412			}
413			if (loaded && Date.now() > this.timeToNextTransition && Date.now() > this.timeToTransitionComplete){
414				var src = this.data.images[this._slide].replace(/([^?]+).*/, '$1');
415				if (this.preloader){
416					this.cache[src] = {
417						'height': this.preloader.get('height'),
418						'src': src,
419						'width': this.preloader.get('width')
420					}
421				}
422				if (this.stopped){
423					if (this.options.captions)
424						this.caption.get('morph').cancel().start(this.classes.get('captions', 'hidden'));
425					this.pause(1);
426					if (this.end)
427						this.fireEvent('end');
428					this.stopped = this.end = false;
429					return;				
430				}
431				this.image = this.counter % 2 ? this.b 
432					: this.a;
433				this.image.set('styles', {'display': 'block', 'height': null, 'visibility': 'hidden', 'width': null, 'zIndex': this.counter});
434				this.image.set(this.cache[src]);
435				this.image.width = this.cache[src].width;
436				this.image.height = this.cache[src].height;
437				this.options.resize && this._resize(this.image);
438				this.options.center && this._center(this.image);
439				var anchor = this.image.getParent();
440				if (this.data.hrefs[this._slide]){
441					anchor.set('href', this.data.hrefs[this._slide]);
442					anchor.set('target', this.data.targets[this._slide]);			
443				} 
444				else {
445					anchor.erase('href');
446					anchor.erase('target');
447				}
448				var title = this.data.titles[this._slide];
449				this.image.set('alt', title);		
450				this.options.titles && anchor.set('title', title);
451				this.options.loader && this.loader.fireEvent('hide');
452				this.options.captions && this.caption.fireEvent('update', fast);				
453				this.options.thumbnails && this.thumbnails.fireEvent('update', fast); 			
454				this._show(fast);
455				this._loaded(fast);
456			} 
457			else {
458				if (Date.now() > this.timeToNextTransition && this.options.loader)
459					this.loader.fireEvent('show');
460				this.timer = this._preload.delay(50, this, fast); 
461			}
462		},
463
464	/**
465	Private method: show
466		Does the slideshow effect.
467	*/
468
469		_show: function(fast){
470			if (!this.image.retrieve('morph')){
471				var options =  this.options.overlap ? {'link': 'cancel'} : {'link': 'chain'};
472				$$(this.a, this.b).set('morph', Object.merge(options, {'duration': this.options.duration, 'onStart': this._start.bind(this), 'onComplete': this._complete.bind(this), 'transition': this.options.transition}));
473			}
474			var hidden = this.classes.get('images', (this.direction == 'left' ? 'next' 
475				: 'prev')),
476				visible = this.classes.get('images', 'visible'),
477				img = this.counter % 2 ? this.a 
478					: this.b;
479			if (fast){			
480				img.get('morph').cancel().set(hidden);
481				this.image.get('morph').cancel().set(visible); 			
482			} 
483			else {
484				if (this.options.overlap){
485					img.get('morph').set(visible);
486					this.image.get('morph').set(hidden).start(visible);
487				} 
488				else {
489					var fn = function(visible){
490						this.image.get('morph').start(visible);
491					}.pass(visible, this);
492					if (this.firstrun)
493						return fn();
494					hidden = this.classes.get('images', (this.direction == 'left' ? 'prev' 
495						: 'next'));
496					this.image.get('morph').set(hidden);				
497					img.get('morph').set(visible).start(hidden).chain(fn);
498				}
499			}
500		},
501
502	/**
503	Private method: loaded
504		Run after the current image has been loaded, sets up the next image to be shown.
505	*/
506
507		_loaded: function(fast){
508			this.counter++;
509			this.timeToNextTransition = Date.now() + this.options.duration + this.options.delay;
510			this.direction = 'left';			
511			this.timeToTransitionComplete = fast ? 0 
512				: Date.now() + this.options.duration;
513			if (this._slide == (this.data.images.length - 1) && !this.options.loop && !this.options.random)
514				this.stopped = this.end = true;
515			if (this.options.random){
516				this.showed.i++;
517				if (this.showed.i >= this.showed.array.length){
518					var n = this._slide;
519					if (this.showed.array.getLast() != n) this.showed.array.push(n);
520					while (this._slide == n)
521						this.slide = this._slide = Number.random(0, this.data.images.length - 1);				
522				}
523				else
524					this.slide = this._slide = this.showed.array[this.showed.i];
525			}
526			else {
527				this.slide = this._slide;
528				this._slide = (this.slide + 1) % this.data.images.length;
529			}
530			if (this.image.getStyle('visibility') != 'visible')
531				(function(){ this.image.setStyle('visibility', 'visible'); }).delay(1, this);			
532			if (this.preloader) 
533				this.preloader = this.preloader.destroy();
534			this.paused || this._preload();
535		},
536
537	/**
538	Private method: center
539		Center an image.
540	*/
541
542		_center: function(img){
543			var size = img.getSize(), 
544				h = size.y, w = size.x; 
545			img.set('styles', {'left': (w - this.width) / -2, 'top': (h - this.height) / -2});
546		},
547
548	/**
549	Private method: resize
550		Resizes an image.
551	*/
552
553		_resize: function(img){
554			var h = img.get('height').toFloat(), w = img.get('width').toFloat(),
555				dh = this.height / h, dw = this.width / w;
556			if (this.options.resize == 'fit')
557				dh = dw = dh > dw ? dw 
558					: dh;
559			if (this.options.resize == 'fill')
560				dh = dw = dh > dw ? dh 
561					: dw;
562			img.set('styles', {'height': Math.ceil(h * dh), 'width': Math.ceil(w * dw)});
563		},
564
565	/**
566	Private method: start
567		Callback on start of slide change.
568	*/
569
570		_start: function(){		
571			this.fireEvent('start');
572		},
573
574	/**
575	Private method: complete
576		Callback on start of slide change.
577	*/
578
579		_complete: function(){
580			if (this.firstrun && this.options.paused)
581				this.pause(1);
582			this.firstrun = false;
583			this.fireEvent('complete');
584		}	
585	});
586
587	/**
588	Private method: captions
589		Builds the optional caption element, adds interactivity.
590		This method can safely be removed if the captions option is not enabled.
591	*/
592
593	var Caption = new Class({
594		Implements: [Chain, Events, Options],
595
596		options: {/*
597			duration: 500,
598			fps: 50,
599			transition: 'sine:in:out',
600			unit: false, */
601			delay: 0,
602			link: 'cancel'			
603		},
604
605		initialize: function(slideshow){
606			if (!slideshow)
607				return;
608			var options = slideshow.options.captions;
609			if (options === true)
610				options = {};
611			this.setOptions(options);
612			var el = slideshow.el.getElement(slideshow.classes.get('captions')),
613				caption = el ? el.dispose().empty()
614					: new Element('div', {'class': slideshow.classes.get('captions').substr(1)});
615			slideshow.caption = caption;
616			caption.set({
617		    	'aria-busy': false,
618		    	'aria-hidden': false,
619				'events': { 'update': this.update.bind(slideshow) },
620				'morph': this.options,
621				'role': 'description'
622			}).store('delay', this.options.delay);
623		    if (!caption.get('id'))
624		    	caption.set('id', 'Slideshow-' + Date.now());
625		    slideshow.el.retrieve('images').set('aria-labelledby', caption.get('id'));
626			caption.inject(slideshow.el);
627		},
628
629		update: function(fast){
630		    var empty = !this.data.captions[this._slide].length, timer;
631			if (timer = this.caption.retrieve('timer'))
632				clearTimeout(timer);
633		    if (fast){
634		      var p = empty ? 'hidden' 
635				: 'visible';
636		      this.caption.set({'aria-hidden': empty, 'html': this.data.captions[this._slide]}).get('morph').cancel().set(this.classes.get('captions', p));
637		    }
638		    else {
639		      var fn1 = empty ? function(){} 
640				: function(caption){
641				this.caption.store('timer', setTimeout(function(caption){
642			        this.caption.set('html', caption).morph(this.classes.get('captions', 'visible'));
643				}.pass(caption, this), this.caption.retrieve('delay')));
644		      }.pass(this.data.captions[this._slide], this);    
645		      var fn2 = function(){ 
646		        this.caption.set('aria-busy', false); 
647		      }.bind(this);
648		      this.caption.set('aria-busy', true).get('morph').cancel().start(this.classes.get('captions', 'hidden')).chain(fn1, fn2);
649		    }
650		}
651	});
652
653	/**
654	Private method: controller
655	  Builds the optional controller element, adds interactivity.
656	  This method can safely be removed if the controller option is not enabled.
657	*/
658
659	var Controller = new Class({
660		Implements: [Chain, Events, Options],
661
662		options: {/*
663			duration: 500,
664			fps: 50,
665			transition: 'sine:in:out',
666			unit: false, */
667			link: 'cancel'			
668		},
669
670		initialize: function(slideshow){
671			if (!slideshow)
672				return;
673			var options = slideshow.options.captions;
674			if (options === true)
675				options = {};
676			this.setOptions(options);
677			var el = slideshow.el.getElement(slideshow.classes.get('controller')),
678				controller = el ? el.dispose().empty()
679					: new Element('div', {'class': slideshow.classes.get('controller').substr(1)});
680			slideshow.controller = controller;
681			controller.set({
682				'aria-hidden': false, 
683				'role': 'menubar'
684			});
685			var ul = new Element('ul', {'role': 'menu'}).inject(controller),
686				i = 0;
687			Object.each(slideshow.accesskeys, function(accesskey, action){
688				var li = new Element('li', {
689					'class': (action == 'pause' && this.options.paused) ? this.classes.play + ' ' + this.classes[action] 
690						: this.classes[action]
691				}).inject(ul);
692				var a = this.el.retrieve(action, new Element('a', {
693					'role': 'menuitem', 'tabindex': i++, 'title': accesskey.label
694				}).inject(li));
695				a.set('events', {
696					'click': function(action){
697						this[action]()
698					}.pass(action, this),
699					'mouseenter': function(active){
700						this.addClass(active)
701					}.pass(this.classes.active, a),
702					'mouseleave': function(active){
703						this.removeClass(active)
704					}.pass(this.classes.active, a)
705				});		
706			}, slideshow);
707			controller.set({
708				'events': {
709					'hide': this.hide.pass(slideshow.classes.get('controller', 'hidden'), controller),
710					'show': this.show.pass(slideshow.classes.get('controller', 'visible'), controller)
711				},
712				'morph': this.options
713			}).store('hidden', false);
714			slideshow.events
715				.push('keydown', this.keydown.bind(slideshow))
716				.push('keyup',	this.keyup.bind(slideshow))
717				.push('mousemove',	this.mousemove.bind(slideshow));
718			controller.inject(slideshow.el).fireEvent('hide');
719		},
720
721		hide: function(hidden){
722			if (this.get('aria-hidden') == 'false')
723				this.set('aria-hidden', true).morph(hidden);
724		},
725
726		keydown: function(e){
727			Object.each(this.accesskeys, function(accesskey, action){
728				if (e.key == accesskey.key && e.shift == accesskey.shift && e.control == accesskey.control && e.alt == accesskey.alt){
729					if (this.controller.get('aria-hidden') == 'true')
730						this.controller.get('morph').set(this.classes.get('controller', 'visible'));
731					this.el.retrieve(action).fireEvent('mouseenter');
732				}					
733			}, this);		
734		},
735
736		keyup: function(e){
737			Object.each(this.accesskeys, function(accesskey, action){
738				if (e.key == accesskey.key && e.shift == accesskey.shift && e.control == accesskey.control && e.alt == accesskey.alt){
739					if (this.controller.get('aria-hidden') == 'true')
740						this.controller.set('aria-hidden', false).fireEvent('hide'); 
741					this.el.retrieve(action).fireEvent('mouseleave');
742				}					
743			}, this);			
744		},
745
746		mousemove: function(e){
747			var images = this.el.retrieve('images').getCoordinates(),
748				action = (e.page.x > images.left && e.page.x < images.right && e.page.y > images.top && e.page.y < images.bottom) ? 'show' 
749					: 'hide';
750			this.controller.fireEvent(action);
751		},
752
753		show: function(visible){
754			if (this.get('aria-hidden') == 'true')
755				this.set('aria-hidden', false).morph(visible);
756		}
757	});
758
759	/**
760	Private method: loader
761		Builds the optional loader element, adds interactivity.
762		This method can safely be removed if the loader option is not enabled.
763	*/
764
765	var Loader = new Class({
766		Implements: [Chain, Events, Options],
767
768		options: {/*
769			duration: 500,
770			transition: 'sine:in:out',
771			unit: false, */
772			fps: 20,
773			link: 'cancel'			
774		},
775
776		initialize: function(slideshow){
777			if (!slideshow)
778				return;
779			var options = slideshow.options.loader;
780			if (options === true)
781				options = {};
782			this.setOptions(options);
783			var loader = new Element('div', {
784				'aria-hidden': false,
785				'class': slideshow.classes.get('loader').substr(1),				
786				'morph': this.options,
787				'role': 'progressbar'
788			}).store('animate', false).store('i', 0).store('delay', 1000 / this.options.fps).inject(slideshow.el);
789			slideshow.loader = loader;
790			var url = loader.getStyle('backgroundImage').replace(/url\(['"]?(.*?)['"]?\)/, '$1').trim();
791			if (url){
792				if (url.test(/\.png$/) && Browser.ie && Browser.version < 7)
793					loader.setStyles({'backgroundImage': 'none', 'filter': 'progid:DXImageTransform.Microsoft.AlphaImageLoader(src="' + url + '", sizingMethod="crop")'});					
794				new Asset.image(url, {'onload': function(){
795					var size = loader.getSize(),
796						width = this.get('width'), 
797						height = this.get('height');
798					if (width > size.x)
799						loader.store('x', size.x).store('animate', 'x').store('frames', (width / size.x).toInt());
800					if (height > size.y)
801						loader.store('y', size.y).store('animate', 'y').store('frames', (height / size.y).toInt());
802				}});
803			}
804			loader.set('events', {
805				'animate': this.animate.bind(loader),
806				'hide': this.hide.pass(slideshow.classes.get('loader', 'hidden'), loader),
807				'show': this.show.pass(slideshow.classes.get('loader', 'visible'), loader)
808			});
809			loader.fireEvent('hide');
810		},
811
812		animate: function(){
813			var animate = this.retrieve('animate');
814			if (!animate)
815				return;
816			var i = (this.retrieve('i').toInt() + 1) % this.retrieve('frames');
817			this.store('i', i);
818			var n = (i * this.retrieve(animate)) + 'px';
819			if (animate == 'x')
820				this.setStyle('backgroundPosition', n + ' 0px');			
821			if (animate == 'y')
822				this.setStyle('backgroundPosition', '0px ' + n);			
823		},
824
825		hide: function(hidden){
826			if (this.get('aria-hidden') == 'false'){
827				this.set('aria-hidden', true).morph(hidden);
828				if (this.retrieve('animate'))
829					clearTimeout(this.retrieve('timer'));
830			}
831		},
832
833		show: function(visible){
834			if (this.get('aria-hidden') == 'true'){
835				this.set('aria-hidden', false).morph(visible);
836				if (this.retrieve('animate')){
837					this.store('timer', function(){ 
838						this.fireEvent('animate') 
839					}.periodical(this.retrieve('delay'), this));			
840				}
841			}
842		}
843	});
844
845	/**
846	Private method: thumbnails
847		Builds the optional thumbnails element, adds interactivity.
848		This method can safely be removed if the thumbnails option is not enabled.
849	*/
850
851	var Thumbnails = new Class({
852		Implements: [Chain, Events, Options],
853
854		options: {/*
855			duration: 500,
856			transition: 'sine:in:out',
857			unit: false, */
858			columns: null,
859			fps: 50,
860			link: 'cancel',
861			position: null,
862			rows: null,
863			scroll: null
864		},
865
866		initialize: function(slideshow){
867			var options = (slideshow.options.thumbnails === true) ? {} 
868				: slideshow.options.thumbnails;
869			this.setOptions(options);
870			var el = slideshow.el.getElement(slideshow.classes.get('thumbnails')),
871				thumbnails = el ? el.empty() 
872					: new Element('div', {'class': slideshow.classes.get('thumbnails').substr(1)});
873			slideshow.thumbnails = thumbnails;
874			thumbnails.set({'role': 'menubar', 'styles': {'overflow': 'hidden'}});
875			var uid = thumbnails.retrieve('uid', 'Slideshow-' + Date.now()),
876				ul = new Element('ul', {'role': 'menu', 'styles': {'left': 0, 'position': 'absolute', 'top': 0}, 'tween': {'link': 'cancel'}}).inject(thumbnails);
877			slideshow.data.thumbnails.each(function(thumbnail, i){
878				var li = new Element('li', {'id': uid + i}).inject(ul),
879					a = new Element('a', {
880						'class': slideshow.classes.get('thumbnails', 'hidden').substr(1),
881						'events': {
882							'click': this.click.pass(i, slideshow)
883						},
884						'href': slideshow.data.images[i],
885						'morph': this.options,
886						'role': 'menuitem',
887						'tabindex': i
888					}).store('uid', i).inject(li);
889				if (slideshow.options.titles)
890					a.set('title', slideshow.data.titles[i]);
891				new Asset.image(thumbnail, {
892					'onload': this.onload.pass(i, slideshow)
893				}).inject(a);
894			}, this);
895			thumbnails.set('events', {
896				'scroll': this.scroll.bind(thumbnails),
897				'update': this.update.bind(slideshow)
898			});
899			var coords = thumbnails.getCoordinates();
900			if (!options.scroll)
901				options.scroll = (coords.height > coords.width) ? 'y' 
902					: 'x';
903			var props = (options.scroll == 'y') ? 'top bottom height y width'.split(' ') 
904				: 'left right width x height'.split(' ');
905			thumbnails.store('props', props).store('delay', 1000 / this.options.fps);
906			slideshow.events.push('mousemove', this.mousemove.bind(thumbnails));
907			thumbnails.inject(slideshow.el);
908		},
909
910		click: function(i){
911			this.go(i); 
912			return false; 
913		},
914
915		mousemove: function(e){
916			var coords = this.getCoordinates();
917			if (e.page.x > coords.left && e.page.x < coords.right && e.page.y > coords.top && e.page.y < coords.bottom){
918				this.store('page', e.page);			
919				if (!this.retrieve('mouseover')){
920					this.store('mouseover', true);
921					this.store('timer', function(){this.fireEvent('scroll');}.periodical(this.retrieve('delay'), this));
922				}
923			}
924			else {
925				if (this.retrieve('mouseover')){
926					this.store('mouseover', false);				
927					clearTimeout(this.retrieve('timer'));
928				}
929			}			
930		},
931
932		onload: function(i){
933			var thumbnails = this.thumbnails,
934				a = thumbnails.getElements('a')[i];
935			if (a){
936				(function(a){
937					var visible = i == this.slide ? 'active' 
938						: 'inactive';					
939					a.store('loaded', true).get('morph').set(this.classes.get('thumbnails', 'hidden')).start(this.classes.get('thumbnails', visible));	
940				}).delay(Math.max(1000 / this.data.thumbnails.length, 100), this, a);
941			}					
942			if (thumbnails.retrieve('limit'))
943				return;
944			var props = thumbnails.retrieve('props'), 
945				options = this.options.thumbnails,
946				pos = props[1], 
947				length = props[2], 
948				width = props[4],
949				li = thumbnails.getElement('li:nth-child(' + (i + 1) + ')').getCoordinates();
950			if (options.columns || options.rows){
951				thumbnails.setStyles({'height': this.height, 'width': this.width});
952				if (options.columns.toInt())
953					thumbnails.setStyle('width', li.width * options.columns.toInt());
954				if (options.rows.toInt())
955					thumbnails.setStyle('height', li.height * options.rows.toInt());
956			}
957			var div = thumbnails.getCoordinates();
958			if (options.position){
959				if (options.position.test(/bottom|top/))
960					thumbnails.setStyles({'bottom': 'auto', 'top': 'auto'}).setStyle(options.position, -div.height);
961				if (options.position.test(/left|right/))
962					thumbnails.setStyles({'left': 'auto', 'right': 'auto'}).setStyle(options.position, -div.width);
963			}
964			var units = Math.floor(div[width] / li[width]),
965				x = Math.ceil(this.data.images.length / units),
966				r = this.data.images.length % units,
967				len = x * li[length],
968				ul = thumbnails.getElement('ul').setStyle(length, len);
969			ul.getElements('li').setStyles({'height': li.height, 'width': li.width});
970			thumbnails.store('limit', div[length] - len);
971		},
972
973		scroll: function(n, fast){
974			var div = this.getCoordinates(),
975				ul = this.getElement('ul').getPosition(),
976				props = this.retrieve('props'),
977				axis = props[3], delta, pos = props[0], size = props[2], value,			
978				tween = this.getElement('ul').set('tween', {'property': pos}).get('tween');	
979			if (n != undefined){
980				var uid = this.retrieve('uid'),
981					li = document.id(uid + n).getCoordinates();
982				delta = div[pos] + (div[size] / 2) - (li[size] / 2) - li[pos];
983				value = (ul[axis] - div[pos] + delta).limit(this.retrieve('limit'), 0);
984				tween[fast ? 'set' : 'start'](value);
985			}
986			else{
987				var area = div[props[2]] / 3, 
988					page = this.retrieve('page'), 
989					velocity = -(this.retrieve('delay') * 0.01);			
990				if (page[axis] < (div[pos] + area))
991					delta = (page[axis] - div[pos] - area) * velocity;
992				else if (page[axis] > (div[pos] + div[size] - area))
993					delta = (page[axis] - div[pos] - div[size] + area) * velocity;			
994				if (delta){			
995					value = (ul[axis] - div[pos] + delta).limit(this.retrieve('limit'), 0);
996					tween.set(value);
997				}
998			}				
999		},
1000
1001		update: function(fast){
1002			var thumbnails = this.thumbnails,
1003				uid = thumbnails.retrieve('uid');
1004			thumbnails.getElements('a').each(function(a, i){
1005				if (a.retrieve('loaded')){
1006					if (a.retrieve('uid') == this._slide){
1007						if (!a.retrieve('active', false)){
1008							a.store('active', true);
1009							var active = this.classes.get('thumbnails', 'active');							
1010							if (fast) a.get('morph').set(active);
1011							else a.morph(active);
1012						}
1013					} 
1014					else {
1015						if (a.retrieve('active', true)){
1016							a.store('active', false);
1017							var inactive = this.classes.get('thumbnails', 'inactive');						
1018							if (fast) a.get('morph').set(inactive);
1019							else a.morph(inactive);
1020						}
1021					}
1022				}
1023			}, this);
1024			if (!thumbnails.retrieve('mouseover'))
1025				thumbnails.fireEvent('scroll', [this._slide, fast]);
1026		}
1027	});
1028})();

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.