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