PageSourceSearch

http://www.elbiro.net/module/jslib/grouppic/jquery.ad-gallery.js

js elbiro.net collected 2026-10-08 07:57:42 UTC 29,989 bytes, 869 lines download raw bytes

1(function($) {
2  $.fn.adGallery = function(options) {
3    var defaults = { 
4                     start_at_index: 0,
5                     description_wrapper: false,
6                     thumb_opacity: 0.7,
7                     animate_first_image: false,
8                     animation_speed: 0,
9                     width: false,
10                     height: false,
11                     display_next_and_prev: true,
12                     display_back_and_forward: true,
13                     scroll_jump: 100, // If 0, it jumps the width of the container
14                     slideshow: {
15                       enable: true,
16                       autostart: false,
17                       speed: 6000,
18                       start_label: 'Start',
19                       stop_label: 'Stop',
20                       stop_on_scroll: true,
21                       countdown_prefix: '(',
22                       countdown_sufix: ')',
23                       onStart: false,
24                       onStop: false
25                     },
26                     effect: 'slide-hori', // or 'slide-vert', 'fade', or 'resize', 'none'
27                     enable_keyboard_move: true,
28                     cycle: true,
29                     callbacks: {
30                       init: false,
31                       afterImageVisible: false,
32                       beforeImageVisible: false
33                     }
34    };
35    var settings = $.extend(false, defaults, options);
36    if(options && options.slideshow) {
37      settings.slideshow = $.extend(false, defaults.slideshow, options.slideshow);
38    };
39    if(!settings.slideshow.enable) {
40      settings.slideshow.autostart = false;
41    };
42    var galleries = [];
43    $(this).each(function() {
44      var gallery = new AdGallery(this, settings);
45      galleries[galleries.length] = gallery;
46    });
47    // Sorry, breaking the jQuery chain because the gallery instances
48    // are returned so you can fiddle with them
49    return galleries;
50  };
51
52  function VerticalSlideAnimation(img_container, direction, desc) {
53    var current_top = parseInt(img_container.css('top'), 10);
54    if(direction == 'left') {
55      var old_image_top = '-'+ this.image_wrapper_height +'px';
56      img_container.css('top', this.image_wrapper_height +'px');
57    } else {
58      var old_image_top = this.image_wrapper_height +'px';
59      img_container.css('top', '-'+ this.image_wrapper_height +'px');
60    };
61    if(desc) {
62      desc.css('bottom', '-'+ desc[0].offsetHeight +'px');
63      desc.animate({bottom: 0}, this.settings.animation_speed * 2);
64    };
65    if(this.current_description) {
66      this.current_description.animate({bottom: '-'+ this.current_description[0].offsetHeight +'px'}, this.settings.animation_speed * 2);
67    };
68    return {old_image: {top: old_image_top},
69            new_image: {top: current_top}};
70  };
71
72  function HorizontalSlideAnimation(img_container, direction, desc) {
73    var current_left = parseInt(img_container.css('left'), 10);
74    if(direction == 'left') {
75      var old_image_left = '-'+ this.image_wrapper_width +'px';
76      img_container.css('left',this.image_wrapper_width +'px');
77    } else {
78      var old_image_left = this.image_wrapper_width +'px';
79      img_container.css('left','-'+ this.image_wrapper_width +'px');
80    };
81    if(desc) {
82      desc.css('bottom', '-'+ desc[0].offsetHeight +'px');
83      desc.animate({bottom: 0}, this.settings.animation_speed * 2);
84    };
85    if(this.current_description) {
86      this.current_description.animate({bottom: '-'+ this.current_description[0].offsetHeight +'px'}, this.settings.animation_speed * 2);
87    };
88    return {old_image: {left: old_image_left},
89            new_image: {left: current_left}};
90  };
91
92  function ResizeAnimation(img_container, direction, desc) {
93    var image_width = img_container.width();
94    var image_height = img_container.height();
95    var current_left = parseInt(img_container.css('left'), 10);
96    var current_top = parseInt(img_container.css('top'), 10);
97    img_container.css({width: 0, height: 0, top: this.image_wrapper_height / 2, left: this.image_wrapper_width / 2});
98    return {old_image: {width: 0,
99                        height: 0,
100                        top: this.image_wrapper_height / 2,
101                        left: this.image_wrapper_width / 2},
102            new_image: {width: image_width,
103                        height: image_height,
104                        top: current_top,
105                        left: current_left}};
106  };
107
108  function FadeAnimation(img_container, direction, desc) {
109    img_container.css('opacity', 0);
110    return {old_image: {opacity: 0},
111            new_image: {opacity: 1}};
112  };
113
114  // Sort of a hack, will clean this up... eventually
115  function NoneAnimation(img_container, direction, desc) {
116    img_container.css('opacity', 0);
117    return {old_image: {opacity: 0},
118            new_image: {opacity: 1},
119            speed: 0};
120  };
121
122  function AdGallery(wrapper, settings) {
123    this.init(wrapper, settings);
124  };
125  AdGallery.prototype = {
126    // Elements
127    wrapper: false,
128    image_wrapper: false,
129    gallery_info: false,
130    nav: false,
131    loader: false,
132    preloads: false,
133    thumbs_wrapper: false,
134    scroll_back: false,
135    scroll_forward: false,
136    next_link: false,
137    prev_link: false,
138
139    slideshow: false,
140    image_wrapper_width: 0,
141    image_wrapper_height: 0,
142    current_index: 0,
143    current_image: false,
144    current_description: false,
145    nav_display_width: 0,
146    settings: false,
147    images: false,
148    in_transition: false,
149    animations: false,
150    init: function(wrapper, settings) {
151      var context = this;
152      this.wrapper = $(wrapper);
153      this.settings = settings;
154      this.setupElements();
155      this.setupAnimations();
156      if(this.settings.width) {
157        this.image_wrapper_width = this.settings.width;
158        this.image_wrapper.width(this.settings.width);
159        this.wrapper.width(this.settings.width);
160      } else {
161        this.image_wrapper_width = this.image_wrapper.width();
162      };
163      if(this.settings.height) {
164        this.image_wrapper_height = this.settings.height;
165        this.image_wrapper.height(this.settings.height);
166      } else {
167        this.image_wrapper_height = this.image_wrapper.height();
168      };
169      this.nav_display_width = this.nav.width();
170      this.current_index = 0;
171      this.current_image = false;
172      this.current_description = false;
173      this.in_transition = false;
174      this.findImages();
175      if(this.settings.display_next_and_prev) {
176        this.initNextAndPrev();
177      };
178      // The slideshow needs a callback to trigger the next image to be shown
179      // but we don't want to give it access to the whole gallery instance
180      var nextimage_callback = function(callback) {
181        return context.nextImage(callback);
182      };
183      this.slideshow = new AdGallerySlideshow(nextimage_callback, this.settings.slideshow);
184      this.controls.append(this.slideshow.create());
185      if(this.settings.slideshow.enable) {
186        this.slideshow.enable();
187      } else {
188        this.slideshow.disable();
189      };
190      if(this.settings.display_back_and_forward) {
191        this.initBackAndForward();
192      };
193      if(this.settings.enable_keyboard_move) {
194        this.initKeyEvents();
195      };
196      var start_at = parseInt(this.settings.start_at_index, 10);
197      if(window.location.hash && window.location.hash.indexOf('#ad-image') === 0) {
198        start_at = window.location.hash.replace(/[^0-9]+/g, '');
199        // Check if it's a number
200        if((start_at * 1) != start_at) {
201          start_at = this.settings.start_at_index;
202        };
203      };
204
205      this.loading(true);
206      this.showImage(start_at,
207        function() {
208          // We don't want to start the slideshow before the image has been
209          // displayed
210          if(context.settings.slideshow.autostart) {
211            context.preloadImage(start_at + 1);
212            context.slideshow.start();
213          };
214        }
215      );
216      this.fireCallback(this.settings.callbacks.init);
217    },
218    setupAnimations: function() {
219      this.animations = {
220        'slide-vert': VerticalSlideAnimation,
221        'slide-hori': HorizontalSlideAnimation,
222        'resize': ResizeAnimation,
223        'fade': FadeAnimation,
224        'none': NoneAnimation
225      };
226    },
227    setupElements: function() {
228      this.controls = this.wrapper.find('.ad-controls');
229      this.gallery_info = $('<p class="ad-info"></p>');
230      this.controls.append(this.gallery_info);
231      this.image_wrapper = this.wrapper.find('.ad-image-wrapper');
232      this.image_wrapper.empty();
233      this.nav = this.wrapper.find('.ad-nav');
234      this.thumbs_wrapper = this.nav.find('.ad-thumbs');
235      this.preloads = $('<div class="ad-preloads"></div>');
236      this.loader = $('<div class="ad-loader"></div>');
237      this.image_wrapper.append(this.loader);
238      this.loader.hide();
239      $(document.body).append(this.preloads);
240    },
241    loading: function(bool) {
242      if(bool) {
243        this.loader.show();
244      } else {
245        this.loader.hide();
246      };
247    },
248    addAnimation: function(name, fn) {
249      if($.isFunction(fn)) {
250        this.animations[name] = fn;
251      };
252    },
253    findImages: function() {
254      var context = this;
255      this.images = [];
256      var thumb_wrapper_width = 0;
257      var thumbs_loaded = 0;
258      var thumbs = this.thumbs_wrapper.find('a');
259      var thumb_count = thumbs.length;
260      if(this.settings.thumb_opacity < 1) {
261        thumbs.find('img').css('opacity', this.settings.thumb_opacity);
262      };
263      thumbs.each(
264        function(i) {
265          var link = $(this);
266          var image_src = link.attr('href');
267          var thumb = link.find('img');
268          // Check if the thumb has already loaded
269          if(!context.isImageLoaded(thumb[0])) {
270            thumb.load(
271              function() {
272                thumb_wrapper_width += this.parentNode.parentNode.offsetWidth;
273                thumbs_loaded++;
274              }
275            );
276          } else{
277            thumb_wrapper_width += thumb[0].parentNode.parentNode.offsetWidth;
278            thumbs_loaded++;
279          };
280          link.addClass('ad-thumb'+ i);
281          link.click(
282            function() {
283              context.showImage(i);
284              context.slideshow.stop();
285              return false;
286            }
287          ).hover(
288            function() {
289              if(!$(this).is('.ad-active') && context.settings.thumb_opacity < 1) {
290                $(this).find('img').fadeTo(300, 1);
291              };
292              context.preloadImage(i);
293            },
294            function() {
295              if(!$(this).is('.ad-active') && context.settings.thumb_opacity < 1) {
296                $(this).find('img').fadeTo(300, context.settings.thumb_opacity);
297              };
298            }
299          );
300          var link = false;
301          if(thumb.data('ad-link')) {
302            link = thumb.data('ad-link');
303          } else if(thumb.attr('longdesc') && thumb.attr('longdesc').length) {
304            link = thumb.attr('longdesc');
305          };
306          var desc = false;
307          if(thumb.data('ad-desc')) {
308            desc = thumb.data('ad-desc');
309          } else if(thumb.attr('alt') && thumb.attr('alt').length) {
310            desc = thumb.attr('alt');
311          };
312          var title = false;
313          if(thumb.data('ad-title')) {
314            title = thumb.data('ad-title');
315          } else if(thumb.attr('title') && thumb.attr('title').length) {
316            title = thumb.attr('title');
317          };
318          context.images[i] = { thumb: thumb.attr('src'), image: image_src, error: false,
319                                preloaded: false, desc: desc, title: title, size: false,
320                                link: link };
321        }
322      );
323      // Wait until all thumbs are loaded, and then set the width of the ul
324      var inter = setInterval(
325        function() {
326          if(thumb_count == thumbs_loaded) {
327            thumb_wrapper_width -= 100;
328            var list = context.nav.find('.ad-thumb-list');
329            list.css('width', thumb_wrapper_width +'px');
330            var i = 1;
331            var last_height = list.height();
332            while(i < 201) {
333              list.css('width', (thumb_wrapper_width + i) +'px');
334              if(last_height != list.height()) {
335                break;
336              }
337              last_height = list.height();
338              i++;
339            }
340            clearInterval(inter);
341          };
342        },
343        100
344      );
345    },
346    initKeyEvents: function() {
347      var context = this;
348      $(document).keydown(
349        function(e) {
350          if(e.keyCode == 39) {
351            // right arrow
352            context.nextImage();
353            context.slideshow.stop();
354          } else if(e.keyCode == 37) {
355            // left arrow
356            context.prevImage();
357            context.slideshow.stop();
358          };
359        }
360      );
361    },
362    initNextAndPrev: function() {
363      this.next_link = $('<div class="ad-next"><div class="ad-next-image"></div></div>');
364      this.prev_link = $('<div class="ad-prev"><div class="ad-prev-image"></div></div>');
365      this.image_wrapper.append(this.next_link);
366      this.image_wrapper.append(this.prev_link);
367      var context = this;
368      this.prev_link.add(this.next_link).mouseover(
369        function(e) {
370          // IE 6 hides the wrapper div, so we have to set it's width
371          $(this).css('height', context.image_wrapper_height);
372          $(this).find('div').show();
373        }
374      ).mouseout(
375        function(e) {
376          $(this).find('div').hide();
377        }
378      ).click(
379        function() {
380          if($(this).is('.ad-next')) {
381            context.nextImage();
382            context.slideshow.stop();
383          } else {
384            context.prevImage();
385            context.slideshow.stop();
386          };
387        }
388      ).find('div').css('opacity', 0.7);
389    },
390    initBackAndForward: function() {
391      var context = this;
392      this.scroll_forward = $('<div class="ad-forward"></div>');
393      this.scroll_back = $('<div class="ad-back"></div>');
394      this.nav.append(this.scroll_forward);
395      this.nav.prepend(this.scroll_back);
396//      this.nav.append(this.scroll_forward);
397//      this.nav.prepend(this.scroll_back);
398      var has_scrolled = 0;
399      var thumbs_scroll_interval = false;
400      $(this.scroll_back).add(this.scroll_forward).click(
401        function() {
402          // We don't want to jump the whole width, since an image
403          // might be cut at the edge
404          var width = context.nav_display_width - 50;
405          if(context.settings.scroll_jump > 0) {
406            var width = context.settings.scroll_jump;
407          };
408          if($(this).is('.ad-forward')) {
409            var left = context.thumbs_wrapper.scrollLeft() + width;
410          } else {
411            var left = context.thumbs_wrapper.scrollLeft() - width;
412          };
413          if(context.settings.slideshow.stop_on_scroll) {
414            context.slideshow.stop();
415          };
416          context.thumbs_wrapper.animate({scrollLeft: left +'px'});
417          return false;
418        }
419      ).css('opacity', 0.7).hover(
420        function() {
421          var direction = 'left';
422          if($(this).is('.ad-forward')) {
423            direction = 'right';
424          };
425          thumbs_scroll_interval = setInterval(
426            function() {
427              has_scrolled++;
428              // Don't want to stop the slideshow just because we scrolled a pixel or two
429              if(has_scrolled > 30 && context.settings.slideshow.stop_on_scroll) {
430                context.slideshow.stop();
431              };
432              var left = context.thumbs_wrapper.scrollLeft() + 1;
433              if(direction == 'left') {
434                left = context.thumbs_wrapper.scrollLeft() - 1;
435              };
436              context.thumbs_wrapper.scrollLeft(left);
437            },
438            10
439          );
440          $(this).css('opacity', 1);
441        },
442        function() {
443          has_scrolled = 0;
444          clearInterval(thumbs_scroll_interval);
445          $(this).css('opacity', 0.6);
446        }
447      );
448    },
449    _afterShow: function() {
450      this.gallery_info.html((this.current_index + 1) +' / '+ this.images.length);
451      if(!this.settings.cycle) {
452        // Needed for IE
453        this.prev_link.show().css('height', this.image_wrapper_height);
454        this.next_link.show().css('height', this.image_wrapper_height);
455        if(this.current_index == (this.images.length - 1)) {
456          this.next_link.hide();
457        };
458        if(this.current_index == 0) {
459          this.prev_link.hide();
460        };
461      };
462      this.fireCallback(this.settings.callbacks.afterImageVisible);
463    },
464    /**
465     * Checks if the image is small enough to fit inside the container
466     * If it's not, shrink it proportionally
467     */
468    _getContainedImageSize: function(image_width, image_height) {
469      if(image_height > this.image_wrapper_height) {
470        var ratio = image_width / image_height;
471        image_height = this.image_wrapper_height;
472        image_width = this.image_wrapper_height * ratio;
473      };
474      if(image_width > this.image_wrapper_width) {
475  	    var ratio = image_height / image_width;
476  	    image_width = this.image_wrapper_width;
477  	    image_height = this.image_wrapper_width * ratio;
478  	  };
479      return {width: image_width, height: image_height};
480    },
481    /**
482     * If the image dimensions are smaller than the wrapper, we position
483     * it in the middle anyway
484     */
485    _centerImage: function(img_container, image_width, image_height) {
486      img_container.css('top', '0px');
487      if(image_height < this.image_wrapper_height) {
488        var dif = this.image_wrapper_height - image_height;
489        img_container.css('top', (dif / 2) +'px');
490      };
491      img_container.css('left', '0px');
492      if(image_width < this.image_wrapper_width) {
493        var dif = this.image_wrapper_width - image_width;
494        img_container.css('left', (dif / 2) +'px');
495      };
496    },
497    _getDescription: function(image) {
498      var desc = false;
499      if(image.desc.length || image.title.length) {
500        var title = '';
501        if(image.title.length) {
502          title = '<span class="ad-description-title">'+ image.title +'</span>';
503        };
504        var desc = '';
505        if(image.desc.length) {
506          desc = '<span>'+ image.desc +'</span>';
507        };
508        desc = $('<p class="ad-image-description">'+ title +'</p>');
509      };
510      return desc;
511    },
512    /**
513     * @param function callback Gets fired when the image has loaded, is displaying
514     *                          and it's animation has finished
515     */
516    showImage: function(index, callback) {
517      if(this.images[index] && !this.in_transition) {
518        var context = this;
519        var image = this.images[index];
520        this.in_transition = true;
521        if(!image.preloaded) {
522          this.loading(true);
523          this.preloadImage(index, function() {
524            context.loading(false);
525            context._showWhenLoaded(index, callback);
526          });
527        } else {
528          this._showWhenLoaded(index, callback);
529        };
530      };
531    },
532    /**
533     * @param function callback Gets fired when the image has loaded, is displaying
534     *                          and it's animation has finished
535     */
536    _showWhenLoaded: function(index, callback) {
537      if(this.images[index]) {
538        var context = this;
539        var image = this.images[index];
540        var img_container = $(document.createElement('div')).addClass('ad-image');
541        var img = $(new Image()).attr('src', image.image);
542        if(image.link) {
543          var link = $('<a href="'+ image.link +'" target="_blank"></a>');
544          link.append(img);
545          img_container.append(link);
546        } else {
547          img_container.append(img);
548        }
549        this.image_wrapper.prepend(img_container);
550        var size = this._getContainedImageSize(image.size.width, image.size.height);
551        img.attr('width', size.width);
552        img.attr('height', size.height);
553        img_container.css({width: size.width +'px', height: size.height +'px'});
554        this._centerImage(img_container, size.width, size.height);
555        var desc = this._getDescription(image, img_container);
556        if(desc) {
557          if(!this.settings.description_wrapper) {
558            img_container.append(desc);
559            var width = size.width - parseInt(desc.css('padding-left'), 10) - parseInt(desc.css('padding-right'), 10);
560            desc.css('width', width +'px');
561          } else {
562            this.settings.description_wrapper.append(desc);
563          }
564        };
565        this.highLightThumb(this.nav.find('.ad-thumb'+ index));
566
567        var direction = 'right';
568        if(this.current_index < index) {
569          direction = 'left';
570        };
571        this.fireCallback(this.settings.callbacks.beforeImageVisible);
572        if(this.current_image || this.settings.animate_first_image) {
573          var animation_speed = this.settings.animation_speed;
574          var easing = 'swing';
575          var animation = this.animations[this.settings.effect].call(this, img_container, direction, desc);
576          if(typeof animation.speed != 'undefined') {
577            animation_speed = animation.speed;
578          };
579          if(typeof animation.easing != 'undefined') {
580            easing = animation.easing;
581          };
582          if(this.current_image) {
583            var old_image = this.current_image;
584            var old_description = this.current_description;
585            old_image.animate(animation.old_image, animation_speed, easing,
586              function() {
587                old_image.remove();
588                if(old_description) old_description.remove();
589              }
590            );
591          };
592          img_container.animate(animation.new_image, animation_speed, easing,
593            function() {
594              context.current_index = index;
595              context.current_image = img_container;
596              context.current_description = desc;
597              context.in_transition = false;
598              context._afterShow();
599              context.fireCallback(callback);
600            }
601          );
602        } else {
603          this.current_index = index;
604          this.current_image = img_container;
605          context.current_description = desc;
606          this.in_transition = false;
607          context._afterShow();
608          this.fireCallback(callback);
609        };
610      };
611    },
612    nextIndex: function() {
613      if(this.current_index == (this.images.length - 1)) {
614        if(!this.settings.cycle) {
615          return false;
616        };
617        var next = 0;
618      } else {
619        var next = this.current_index + 1;
620      };
621      return next;
622    },
623    nextImage: function(callback) {
624      var next = this.nextIndex();
625      if(next === false) return false;
626      this.preloadImage(next + 1);
627      this.showImage(next, callback);
628      return true;
629    },
630    prevIndex: function() {
631      if(this.current_index == 0) {
632        if(!this.settings.cycle) {
633          return false;
634        };
635        var prev = this.images.length - 1;
636      } else {
637        var prev = this.current_index - 1;
638      };
639      return prev;
640    },
641    prevImage: function(callback) {
642      var prev = this.prevIndex();
643      if(prev === false) return false;
644      this.preloadImage(prev - 1);
645      this.showImage(prev, callback);
646      return true;
647    },
648    preloadAll: function() {
649      var context = this;
650      var i = 0;
651      function preloadNext() {
652        if(i < context.images.length) {
653          i++;
654          context.preloadImage(i, preloadNext);
655        };
656      };
657      context.preloadImage(i, preloadNext);
658    },
659    preloadImage: function(index, callback) {
660      if(this.images[index]) {
661        var image = this.images[index];
662        if(!this.images[index].preloaded) {
663          var img = $(new Image());
664          img.attr('src', image.image);
665          if(!this.isImageLoaded(img[0])) {
666            this.preloads.append(img);
667            var context = this;
668            img.load(
669              function() {
670                image.preloaded = true;
671                image.size = { width: this.width, height: this.height };
672                context.fireCallback(callback);
673              }
674            ).error(
675              function() {
676                image.error = true;
677                image.preloaded = false;
678                image.size = false;
679              }
680            );
681          } else {
682            image.preloaded = true;
683            image.size = { width: img[0].width, height: img[0].height };
684            this.fireCallback(callback);
685          };
686        } else {
687          this.fireCallback(callback);
688        };
689      };
690    },
691    isImageLoaded: function(img) {
692      if(typeof img.complete != 'undefined' && !img.complete) {
693        return false;
694      };
695      if(typeof img.naturalWidth != 'undefined' && img.naturalWidth == 0) {
696        return false;
697      };
698      return true;
699    },
700    highLightThumb: function(thumb) {
701      this.thumbs_wrapper.find('.ad-active').removeClass('ad-active');
702      thumb.addClass('ad-active');
703      if(this.settings.thumb_opacity < 1) {
704        this.thumbs_wrapper.find('a:not(.ad-active) img').fadeTo(300, this.settings.thumb_opacity);
705        thumb.find('img').fadeTo(300, 1);
706      };
707      var left = thumb[0].parentNode.offsetLeft;
708      left -= (this.nav_display_width / 2) - (thumb[0].offsetWidth / 2);
709      this.thumbs_wrapper.animate({scrollLeft: left +'px'});
710    },
711    fireCallback: function(fn) {
712      if($.isFunction(fn)) {
713        fn.call(this);
714      };
715    }
716  };
717
718  function AdGallerySlideshow(nextimage_callback, settings) {
719    this.init(nextimage_callback, settings);
720  };
721  AdGallerySlideshow.prototype = {
722    start_link: false,
723    stop_link: false,
724    countdown: false,
725    controls: false,
726
727    settings: false,
728    nextimage_callback: false,
729    enabled: false,
730    running: false,
731    countdown_interval: false,
732    init: function(nextimage_callback, settings) {
733      var context = this;
734      this.nextimage_callback = nextimage_callback;
735      this.settings = settings;
736    },
737    create: function() {
738      this.start_link = $('<span class="ad-slideshow-start">'+ this.settings.start_label +'</span>');
739      this.stop_link = $('<span class="ad-slideshow-stop">'+ this.settings.stop_label +'</span>');
740      this.countdown = $('<span class="ad-slideshow-countdown"></span>');
741      this.controls = $('<div class="ad-slideshow-controls"></div>');
742      this.controls.append(this.start_link).append(this.stop_link).append(this.countdown);
743      this.countdown.hide();
744
745      var context = this;
746      this.start_link.click(
747        function() {
748          context.start();
749        }
750      );
751      this.stop_link.click(
752        function() {
753          context.stop();
754        }
755      );
756      $(document).keydown(
757        function(e) {
758          if(e.keyCode == 83) {
759            // 's'
760            if(context.running) {
761              context.stop();
762            } else {
763              context.start();
764            };
765          };
766        }
767      );
768      return this.controls;
769    },
770    disable: function() {
771      this.enabled = false;
772      this.stop();
773      this.controls.hide();
774    },
775    enable: function() {
776      this.enabled = true;
777      this.controls.show();
778    },
779    toggle: function() {
780      if(this.enabled) {
781        this.disable();
782      } else {
783        this.enable();
784      };
785    },
786    start: function() {
787      if(this.running || !this.enabled) return false;
788      var context = this;
789      this.running = true;
790      this.controls.addClass('ad-slideshow-running');
791      this._next();
792      this.fireCallback(this.settings.onStart);
793      return true;
794    },
795    stop: function() {
796      if(!this.running) return false;
797      this.running = false;
798      this.countdown.hide();
799      this.controls.removeClass('ad-slideshow-running');
800      clearInterval(this.countdown_interval);
801      this.fireCallback(this.settings.onStop);
802      return true;
803    },
804    _next: function() {
805      var context = this;
806      var pre = this.settings.countdown_prefix;
807      var su = this.settings.countdown_sufix;
808      clearInterval(context.countdown_interval);
809      this.countdown.show().html(pre + (this.settings.speed / 1000) + su);
810      var slide_timer = 0;
811      this.countdown_interval = setInterval(
812        function() {
813          slide_timer += 1000;
814          if(slide_timer >= context.settings.speed) {
815            var whenNextIsShown = function() {
816              // A check so the user hasn't stoped the slideshow during the
817              // animation
818              if(context.running) {
819                context._next();
820              };
821              slide_timer = 0;
822            };
823            if(!context.nextimage_callback(whenNextIsShown)) {
824              context.stop();
825            };
826            slide_timer = 0;
827          };
828          var sec = parseInt(context.countdown.text().replace(/[^0-9]/g, ''), 10);
829          sec--;
830          if(sec > 0) {
831            context.countdown.html(pre + sec + su);
832          };
833        },
834        1000
835      );
836    },
837    fireCallback: function(fn) {
838      if($.isFunction(fn)) {
839        fn.call(this);
840      };
841    }
842  };
843})(jQuery);
844
845  $(function() {
846    var galleries = $('.ad-gallery').adGallery();
847    $('#switch-effect').change(
848      function() {
849        galleries[0].settings.effect = $(this).val();
850        return false;
851      }
852    );
853    $('#toggle-slideshow').click(
854      function() {
855        galleries[0].slideshow.toggle();
856        return false;
857      }
858    );
859    $('#toggle-description').click(
860      function() {
861        if(!galleries[0].settings.description_wrapper) {
862          galleries[0].settings.description_wrapper = $('#descriptions');
863        } else {
864          galleries[0].settings.description_wrapper = false;
865        }
866        return false;
867      }
868    );
869  });

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.