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, '<').replace(/>/g, '>').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.