Code Duplication    Length = 949-949 lines in 2 locations

public/lib/semantic/semantic.js 1 location

@@ 16697-17645 (lines=949) @@
16694
 *
16695
 */
16696
16697
;(function ($, window, document, undefined) {
16698
16699
"use strict";
16700
16701
window = (typeof window != 'undefined' && window.Math == Math)
16702
  ? window
16703
  : (typeof self != 'undefined' && self.Math == Math)
16704
    ? self
16705
    : Function('return this')()
16706
;
16707
16708
$.fn.sticky = function(parameters) {
16709
  var
16710
    $allModules    = $(this),
16711
    moduleSelector = $allModules.selector || '',
16712
16713
    time           = new Date().getTime(),
16714
    performance    = [],
16715
16716
    query          = arguments[0],
16717
    methodInvoked  = (typeof query == 'string'),
16718
    queryArguments = [].slice.call(arguments, 1),
16719
    returnedValue
16720
  ;
16721
16722
  $allModules
16723
    .each(function() {
16724
      var
16725
        settings              = ( $.isPlainObject(parameters) )
16726
          ? $.extend(true, {}, $.fn.sticky.settings, parameters)
16727
          : $.extend({}, $.fn.sticky.settings),
16728
16729
        className             = settings.className,
16730
        namespace             = settings.namespace,
16731
        error                 = settings.error,
16732
16733
        eventNamespace        = '.' + namespace,
16734
        moduleNamespace       = 'module-' + namespace,
16735
16736
        $module               = $(this),
16737
        $window               = $(window),
16738
        $scroll               = $(settings.scrollContext),
16739
        $container,
16740
        $context,
16741
16742
        selector              = $module.selector || '',
16743
        instance              = $module.data(moduleNamespace),
16744
16745
        requestAnimationFrame = window.requestAnimationFrame
16746
          || window.mozRequestAnimationFrame
16747
          || window.webkitRequestAnimationFrame
16748
          || window.msRequestAnimationFrame
16749
          || function(callback) { setTimeout(callback, 0); },
16750
16751
        element         = this,
16752
16753
        documentObserver,
16754
        observer,
16755
        module
16756
      ;
16757
16758
      module      = {
16759
16760
        initialize: function() {
16761
16762
          module.determineContainer();
16763
          module.determineContext();
16764
          module.verbose('Initializing sticky', settings, $container);
16765
16766
          module.save.positions();
16767
          module.checkErrors();
16768
          module.bind.events();
16769
16770
          if(settings.observeChanges) {
16771
            module.observeChanges();
16772
          }
16773
          module.instantiate();
16774
        },
16775
16776
        instantiate: function() {
16777
          module.verbose('Storing instance of module', module);
16778
          instance = module;
16779
          $module
16780
            .data(moduleNamespace, module)
16781
          ;
16782
        },
16783
16784
        destroy: function() {
16785
          module.verbose('Destroying previous instance');
16786
          module.reset();
16787
          if(documentObserver) {
16788
            documentObserver.disconnect();
16789
          }
16790
          if(observer) {
16791
            observer.disconnect();
16792
          }
16793
          $window
16794
            .off('load' + eventNamespace, module.event.load)
16795
            .off('resize' + eventNamespace, module.event.resize)
16796
          ;
16797
          $scroll
16798
            .off('scrollchange' + eventNamespace, module.event.scrollchange)
16799
          ;
16800
          $module.removeData(moduleNamespace);
16801
        },
16802
16803
        observeChanges: function() {
16804
          if('MutationObserver' in window) {
16805
            documentObserver = new MutationObserver(module.event.documentChanged);
16806
            observer         = new MutationObserver(module.event.changed);
16807
            documentObserver.observe(document, {
16808
              childList : true,
16809
              subtree   : true
16810
            });
16811
            observer.observe(element, {
16812
              childList : true,
16813
              subtree   : true
16814
            });
16815
            observer.observe($context[0], {
16816
              childList : true,
16817
              subtree   : true
16818
            });
16819
            module.debug('Setting up mutation observer', observer);
16820
          }
16821
        },
16822
16823
        determineContainer: function() {
16824
          if(settings.container) {
16825
            $container = $(settings.container);
16826
          }
16827
          else {
16828
            $container = $module.offsetParent();
16829
          }
16830
        },
16831
16832
        determineContext: function() {
16833
          if(settings.context) {
16834
            $context = $(settings.context);
16835
          }
16836
          else {
16837
            $context = $container;
16838
          }
16839
          if($context.length === 0) {
16840
            module.error(error.invalidContext, settings.context, $module);
16841
            return;
16842
          }
16843
        },
16844
16845
        checkErrors: function() {
16846
          if( module.is.hidden() ) {
16847
            module.error(error.visible, $module);
16848
          }
16849
          if(module.cache.element.height > module.cache.context.height) {
16850
            module.reset();
16851
            module.error(error.elementSize, $module);
16852
            return;
16853
          }
16854
        },
16855
16856
        bind: {
16857
          events: function() {
16858
            $window
16859
              .on('load' + eventNamespace, module.event.load)
16860
              .on('resize' + eventNamespace, module.event.resize)
16861
            ;
16862
            // pub/sub pattern
16863
            $scroll
16864
              .off('scroll' + eventNamespace)
16865
              .on('scroll' + eventNamespace, module.event.scroll)
16866
              .on('scrollchange' + eventNamespace, module.event.scrollchange)
16867
            ;
16868
          }
16869
        },
16870
16871
        event: {
16872
          changed: function(mutations) {
16873
            clearTimeout(module.timer);
16874
            module.timer = setTimeout(function() {
16875
              module.verbose('DOM tree modified, updating sticky menu', mutations);
16876
              module.refresh();
16877
            }, 100);
16878
          },
16879
          documentChanged: function(mutations) {
16880
            [].forEach.call(mutations, function(mutation) {
16881
              if(mutation.removedNodes) {
16882
                [].forEach.call(mutation.removedNodes, function(node) {
16883
                  if(node == element || $(node).find(element).length > 0) {
16884
                    module.debug('Element removed from DOM, tearing down events');
16885
                    module.destroy();
16886
                  }
16887
                });
16888
              }
16889
            });
16890
          },
16891
          load: function() {
16892
            module.verbose('Page contents finished loading');
16893
            requestAnimationFrame(module.refresh);
16894
          },
16895
          resize: function() {
16896
            module.verbose('Window resized');
16897
            requestAnimationFrame(module.refresh);
16898
          },
16899
          scroll: function() {
16900
            requestAnimationFrame(function() {
16901
              $scroll.triggerHandler('scrollchange' + eventNamespace, $scroll.scrollTop() );
16902
            });
16903
          },
16904
          scrollchange: function(event, scrollPosition) {
16905
            module.stick(scrollPosition);
16906
            settings.onScroll.call(element);
16907
          }
16908
        },
16909
16910
        refresh: function(hardRefresh) {
16911
          module.reset();
16912
          if(!settings.context) {
16913
            module.determineContext();
16914
          }
16915
          if(hardRefresh) {
16916
            module.determineContainer();
16917
          }
16918
          module.save.positions();
16919
          module.stick();
16920
          settings.onReposition.call(element);
16921
        },
16922
16923
        supports: {
16924
          sticky: function() {
16925
            var
16926
              $element = $('<div/>'),
16927
              element = $element[0]
16928
            ;
16929
            $element.addClass(className.supported);
16930
            return($element.css('position').match('sticky'));
16931
          }
16932
        },
16933
16934
        save: {
16935
          lastScroll: function(scroll) {
16936
            module.lastScroll = scroll;
16937
          },
16938
          elementScroll: function(scroll) {
16939
            module.elementScroll = scroll;
16940
          },
16941
          positions: function() {
16942
            var
16943
              scrollContext = {
16944
                height : $scroll.height()
16945
              },
16946
              element = {
16947
                margin: {
16948
                  top    : parseInt($module.css('margin-top'), 10),
16949
                  bottom : parseInt($module.css('margin-bottom'), 10),
16950
                },
16951
                offset : $module.offset(),
16952
                width  : $module.outerWidth(),
16953
                height : $module.outerHeight()
16954
              },
16955
              context = {
16956
                offset : $context.offset(),
16957
                height : $context.outerHeight()
16958
              },
16959
              container = {
16960
                height: $container.outerHeight()
16961
              }
16962
            ;
16963
            if( !module.is.standardScroll() ) {
16964
              module.debug('Non-standard scroll. Removing scroll offset from element offset');
16965
16966
              scrollContext.top  = $scroll.scrollTop();
16967
              scrollContext.left = $scroll.scrollLeft();
16968
16969
              element.offset.top  += scrollContext.top;
16970
              context.offset.top  += scrollContext.top;
16971
              element.offset.left += scrollContext.left;
16972
              context.offset.left += scrollContext.left;
16973
            }
16974
            module.cache = {
16975
              fits          : ( (element.height + settings.offset) <= scrollContext.height),
16976
              sameHeight    : (element.height == context.height),
16977
              scrollContext : {
16978
                height : scrollContext.height
16979
              },
16980
              element: {
16981
                margin : element.margin,
16982
                top    : element.offset.top - element.margin.top,
16983
                left   : element.offset.left,
16984
                width  : element.width,
16985
                height : element.height,
16986
                bottom : element.offset.top + element.height
16987
              },
16988
              context: {
16989
                top           : context.offset.top,
16990
                height        : context.height,
16991
                bottom        : context.offset.top + context.height
16992
              }
16993
            };
16994
            module.set.containerSize();
16995
16996
            module.stick();
16997
            module.debug('Caching element positions', module.cache);
16998
          }
16999
        },
17000
17001
        get: {
17002
          direction: function(scroll) {
17003
            var
17004
              direction = 'down'
17005
            ;
17006
            scroll = scroll || $scroll.scrollTop();
17007
            if(module.lastScroll !== undefined) {
17008
              if(module.lastScroll < scroll) {
17009
                direction = 'down';
17010
              }
17011
              else if(module.lastScroll > scroll) {
17012
                direction = 'up';
17013
              }
17014
            }
17015
            return direction;
17016
          },
17017
          scrollChange: function(scroll) {
17018
            scroll = scroll || $scroll.scrollTop();
17019
            return (module.lastScroll)
17020
              ? (scroll - module.lastScroll)
17021
              : 0
17022
            ;
17023
          },
17024
          currentElementScroll: function() {
17025
            if(module.elementScroll) {
17026
              return module.elementScroll;
17027
            }
17028
            return ( module.is.top() )
17029
              ? Math.abs(parseInt($module.css('top'), 10))    || 0
17030
              : Math.abs(parseInt($module.css('bottom'), 10)) || 0
17031
            ;
17032
          },
17033
17034
          elementScroll: function(scroll) {
17035
            scroll = scroll || $scroll.scrollTop();
17036
            var
17037
              element        = module.cache.element,
17038
              scrollContext  = module.cache.scrollContext,
17039
              delta          = module.get.scrollChange(scroll),
17040
              maxScroll      = (element.height - scrollContext.height + settings.offset),
17041
              elementScroll  = module.get.currentElementScroll(),
17042
              possibleScroll = (elementScroll + delta)
17043
            ;
17044
            if(module.cache.fits || possibleScroll < 0) {
17045
              elementScroll = 0;
17046
            }
17047
            else if(possibleScroll > maxScroll ) {
17048
              elementScroll = maxScroll;
17049
            }
17050
            else {
17051
              elementScroll = possibleScroll;
17052
            }
17053
            return elementScroll;
17054
          }
17055
        },
17056
17057
        remove: {
17058
          lastScroll: function() {
17059
            delete module.lastScroll;
17060
          },
17061
          elementScroll: function(scroll) {
17062
            delete module.elementScroll;
17063
          },
17064
          minimumSize: function() {
17065
            $container
17066
              .css('min-height', '')
17067
            ;
17068
          },
17069
          offset: function() {
17070
            $module.css('margin-top', '');
17071
          }
17072
        },
17073
17074
        set: {
17075
          offset: function() {
17076
            module.verbose('Setting offset on element', settings.offset);
17077
            $module
17078
              .css('margin-top', settings.offset)
17079
            ;
17080
          },
17081
          containerSize: function() {
17082
            var
17083
              tagName = $container.get(0).tagName
17084
            ;
17085
            if(tagName === 'HTML' || tagName == 'body') {
17086
              // this can trigger for too many reasons
17087
              //module.error(error.container, tagName, $module);
17088
              module.determineContainer();
17089
            }
17090
            else {
17091
              if( Math.abs($container.outerHeight() - module.cache.context.height) > settings.jitter) {
17092
                module.debug('Context has padding, specifying exact height for container', module.cache.context.height);
17093
                $container.css({
17094
                  height: module.cache.context.height
17095
                });
17096
              }
17097
            }
17098
          },
17099
          minimumSize: function() {
17100
            var
17101
              element   = module.cache.element
17102
            ;
17103
            $container
17104
              .css('min-height', element.height)
17105
            ;
17106
          },
17107
          scroll: function(scroll) {
17108
            module.debug('Setting scroll on element', scroll);
17109
            if(module.elementScroll == scroll) {
17110
              return;
17111
            }
17112
            if( module.is.top() ) {
17113
              $module
17114
                .css('bottom', '')
17115
                .css('top', -scroll)
17116
              ;
17117
            }
17118
            if( module.is.bottom() ) {
17119
              $module
17120
                .css('top', '')
17121
                .css('bottom', scroll)
17122
              ;
17123
            }
17124
          },
17125
          size: function() {
17126
            if(module.cache.element.height !== 0 && module.cache.element.width !== 0) {
17127
              element.style.setProperty('width',  module.cache.element.width  + 'px', 'important');
17128
              element.style.setProperty('height', module.cache.element.height + 'px', 'important');
17129
            }
17130
          }
17131
        },
17132
17133
        is: {
17134
          standardScroll: function() {
17135
            return ($scroll[0] == window);
17136
          },
17137
          top: function() {
17138
            return $module.hasClass(className.top);
17139
          },
17140
          bottom: function() {
17141
            return $module.hasClass(className.bottom);
17142
          },
17143
          initialPosition: function() {
17144
            return (!module.is.fixed() && !module.is.bound());
17145
          },
17146
          hidden: function() {
17147
            return (!$module.is(':visible'));
17148
          },
17149
          bound: function() {
17150
            return $module.hasClass(className.bound);
17151
          },
17152
          fixed: function() {
17153
            return $module.hasClass(className.fixed);
17154
          }
17155
        },
17156
17157
        stick: function(scroll) {
17158
          var
17159
            cachedPosition = scroll || $scroll.scrollTop(),
17160
            cache          = module.cache,
17161
            fits           = cache.fits,
17162
            sameHeight     = cache.sameHeight,
17163
            element        = cache.element,
17164
            scrollContext  = cache.scrollContext,
17165
            context        = cache.context,
17166
            offset         = (module.is.bottom() && settings.pushing)
17167
              ? settings.bottomOffset
17168
              : settings.offset,
17169
            scroll         = {
17170
              top    : cachedPosition + offset,
17171
              bottom : cachedPosition + offset + scrollContext.height
17172
            },
17173
            direction      = module.get.direction(scroll.top),
17174
            elementScroll  = (fits)
17175
              ? 0
17176
              : module.get.elementScroll(scroll.top),
17177
17178
            // shorthand
17179
            doesntFit      = !fits,
17180
            elementVisible = (element.height !== 0)
17181
          ;
17182
          if(elementVisible && !sameHeight) {
17183
17184
            if( module.is.initialPosition() ) {
17185
              if(scroll.top >= context.bottom) {
17186
                module.debug('Initial element position is bottom of container');
17187
                module.bindBottom();
17188
              }
17189
              else if(scroll.top > element.top) {
17190
                if( (element.height + scroll.top - elementScroll) >= context.bottom ) {
17191
                  module.debug('Initial element position is bottom of container');
17192
                  module.bindBottom();
17193
                }
17194
                else {
17195
                  module.debug('Initial element position is fixed');
17196
                  module.fixTop();
17197
                }
17198
              }
17199
17200
            }
17201
            else if( module.is.fixed() ) {
17202
17203
              // currently fixed top
17204
              if( module.is.top() ) {
17205
                if( scroll.top <= element.top ) {
17206
                  module.debug('Fixed element reached top of container');
17207
                  module.setInitialPosition();
17208
                }
17209
                else if( (element.height + scroll.top - elementScroll) >= context.bottom ) {
17210
                  module.debug('Fixed element reached bottom of container');
17211
                  module.bindBottom();
17212
                }
17213
                // scroll element if larger than screen
17214
                else if(doesntFit) {
17215
                  module.set.scroll(elementScroll);
17216
                  module.save.lastScroll(scroll.top);
17217
                  module.save.elementScroll(elementScroll);
17218
                }
17219
              }
17220
17221
              // currently fixed bottom
17222
              else if(module.is.bottom() ) {
17223
17224
                // top edge
17225
                if( (scroll.bottom - element.height) <= element.top) {
17226
                  module.debug('Bottom fixed rail has reached top of container');
17227
                  module.setInitialPosition();
17228
                }
17229
                // bottom edge
17230
                else if(scroll.bottom >= context.bottom) {
17231
                  module.debug('Bottom fixed rail has reached bottom of container');
17232
                  module.bindBottom();
17233
                }
17234
                // scroll element if larger than screen
17235
                else if(doesntFit) {
17236
                  module.set.scroll(elementScroll);
17237
                  module.save.lastScroll(scroll.top);
17238
                  module.save.elementScroll(elementScroll);
17239
                }
17240
17241
              }
17242
            }
17243
            else if( module.is.bottom() ) {
17244
              if( scroll.top <= element.top ) {
17245
                module.debug('Jumped from bottom fixed to top fixed, most likely used home/end button');
17246
                module.setInitialPosition();
17247
              }
17248
              else {
17249
                if(settings.pushing) {
17250
                  if(module.is.bound() && scroll.bottom <= context.bottom ) {
17251
                    module.debug('Fixing bottom attached element to bottom of browser.');
17252
                    module.fixBottom();
17253
                  }
17254
                }
17255
                else {
17256
                  if(module.is.bound() && (scroll.top <= context.bottom - element.height) ) {
17257
                    module.debug('Fixing bottom attached element to top of browser.');
17258
                    module.fixTop();
17259
                  }
17260
                }
17261
              }
17262
            }
17263
          }
17264
        },
17265
17266
        bindTop: function() {
17267
          module.debug('Binding element to top of parent container');
17268
          module.remove.offset();
17269
          $module
17270
            .css({
17271
              left         : '',
17272
              top          : '',
17273
              marginBottom : ''
17274
            })
17275
            .removeClass(className.fixed)
17276
            .removeClass(className.bottom)
17277
            .addClass(className.bound)
17278
            .addClass(className.top)
17279
          ;
17280
          settings.onTop.call(element);
17281
          settings.onUnstick.call(element);
17282
        },
17283
        bindBottom: function() {
17284
          module.debug('Binding element to bottom of parent container');
17285
          module.remove.offset();
17286
          $module
17287
            .css({
17288
              left         : '',
17289
              top          : ''
17290
            })
17291
            .removeClass(className.fixed)
17292
            .removeClass(className.top)
17293
            .addClass(className.bound)
17294
            .addClass(className.bottom)
17295
          ;
17296
          settings.onBottom.call(element);
17297
          settings.onUnstick.call(element);
17298
        },
17299
17300
        setInitialPosition: function() {
17301
          module.debug('Returning to initial position');
17302
          module.unfix();
17303
          module.unbind();
17304
        },
17305
17306
17307
        fixTop: function() {
17308
          module.debug('Fixing element to top of page');
17309
          if(settings.setSize) {
17310
            module.set.size();
17311
          }
17312
          module.set.minimumSize();
17313
          module.set.offset();
17314
          $module
17315
            .css({
17316
              left         : module.cache.element.left,
17317
              bottom       : '',
17318
              marginBottom : ''
17319
            })
17320
            .removeClass(className.bound)
17321
            .removeClass(className.bottom)
17322
            .addClass(className.fixed)
17323
            .addClass(className.top)
17324
          ;
17325
          settings.onStick.call(element);
17326
        },
17327
17328
        fixBottom: function() {
17329
          module.debug('Sticking element to bottom of page');
17330
          if(settings.setSize) {
17331
            module.set.size();
17332
          }
17333
          module.set.minimumSize();
17334
          module.set.offset();
17335
          $module
17336
            .css({
17337
              left         : module.cache.element.left,
17338
              bottom       : '',
17339
              marginBottom : ''
17340
            })
17341
            .removeClass(className.bound)
17342
            .removeClass(className.top)
17343
            .addClass(className.fixed)
17344
            .addClass(className.bottom)
17345
          ;
17346
          settings.onStick.call(element);
17347
        },
17348
17349
        unbind: function() {
17350
          if( module.is.bound() ) {
17351
            module.debug('Removing container bound position on element');
17352
            module.remove.offset();
17353
            $module
17354
              .removeClass(className.bound)
17355
              .removeClass(className.top)
17356
              .removeClass(className.bottom)
17357
            ;
17358
          }
17359
        },
17360
17361
        unfix: function() {
17362
          if( module.is.fixed() ) {
17363
            module.debug('Removing fixed position on element');
17364
            module.remove.minimumSize();
17365
            module.remove.offset();
17366
            $module
17367
              .removeClass(className.fixed)
17368
              .removeClass(className.top)
17369
              .removeClass(className.bottom)
17370
            ;
17371
            settings.onUnstick.call(element);
17372
          }
17373
        },
17374
17375
        reset: function() {
17376
          module.debug('Resetting elements position');
17377
          module.unbind();
17378
          module.unfix();
17379
          module.resetCSS();
17380
          module.remove.offset();
17381
          module.remove.lastScroll();
17382
        },
17383
17384
        resetCSS: function() {
17385
          $module
17386
            .css({
17387
              width  : '',
17388
              height : ''
17389
            })
17390
          ;
17391
          $container
17392
            .css({
17393
              height: ''
17394
            })
17395
          ;
17396
        },
17397
17398
        setting: function(name, value) {
17399
          if( $.isPlainObject(name) ) {
17400
            $.extend(true, settings, name);
17401
          }
17402
          else if(value !== undefined) {
17403
            settings[name] = value;
17404
          }
17405
          else {
17406
            return settings[name];
17407
          }
17408
        },
17409
        internal: function(name, value) {
17410
          if( $.isPlainObject(name) ) {
17411
            $.extend(true, module, name);
17412
          }
17413
          else if(value !== undefined) {
17414
            module[name] = value;
17415
          }
17416
          else {
17417
            return module[name];
17418
          }
17419
        },
17420
        debug: function() {
17421
          if(!settings.silent && settings.debug) {
17422
            if(settings.performance) {
17423
              module.performance.log(arguments);
17424
            }
17425
            else {
17426
              module.debug = Function.prototype.bind.call(console.info, console, settings.name + ':');
17427
              module.debug.apply(console, arguments);
17428
            }
17429
          }
17430
        },
17431
        verbose: function() {
17432
          if(!settings.silent && settings.verbose && settings.debug) {
17433
            if(settings.performance) {
17434
              module.performance.log(arguments);
17435
            }
17436
            else {
17437
              module.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':');
17438
              module.verbose.apply(console, arguments);
17439
            }
17440
          }
17441
        },
17442
        error: function() {
17443
          if(!settings.silent) {
17444
            module.error = Function.prototype.bind.call(console.error, console, settings.name + ':');
17445
            module.error.apply(console, arguments);
17446
          }
17447
        },
17448
        performance: {
17449
          log: function(message) {
17450
            var
17451
              currentTime,
17452
              executionTime,
17453
              previousTime
17454
            ;
17455
            if(settings.performance) {
17456
              currentTime   = new Date().getTime();
17457
              previousTime  = time || currentTime;
17458
              executionTime = currentTime - previousTime;
17459
              time          = currentTime;
17460
              performance.push({
17461
                'Name'           : message[0],
17462
                'Arguments'      : [].slice.call(message, 1) || '',
17463
                'Element'        : element,
17464
                'Execution Time' : executionTime
17465
              });
17466
            }
17467
            clearTimeout(module.performance.timer);
17468
            module.performance.timer = setTimeout(module.performance.display, 0);
17469
          },
17470
          display: function() {
17471
            var
17472
              title = settings.name + ':',
17473
              totalTime = 0
17474
            ;
17475
            time = false;
17476
            clearTimeout(module.performance.timer);
17477
            $.each(performance, function(index, data) {
17478
              totalTime += data['Execution Time'];
17479
            });
17480
            title += ' ' + totalTime + 'ms';
17481
            if(moduleSelector) {
17482
              title += ' \'' + moduleSelector + '\'';
17483
            }
17484
            if( (console.group !== undefined || console.table !== undefined) && performance.length > 0) {
17485
              console.groupCollapsed(title);
17486
              if(console.table) {
17487
                console.table(performance);
17488
              }
17489
              else {
17490
                $.each(performance, function(index, data) {
17491
                  console.log(data['Name'] + ': ' + data['Execution Time']+'ms');
17492
                });
17493
              }
17494
              console.groupEnd();
17495
            }
17496
            performance = [];
17497
          }
17498
        },
17499
        invoke: function(query, passedArguments, context) {
17500
          var
17501
            object = instance,
17502
            maxDepth,
17503
            found,
17504
            response
17505
          ;
17506
          passedArguments = passedArguments || queryArguments;
17507
          context         = element         || context;
17508
          if(typeof query == 'string' && object !== undefined) {
17509
            query    = query.split(/[\. ]/);
17510
            maxDepth = query.length - 1;
17511
            $.each(query, function(depth, value) {
17512
              var camelCaseValue = (depth != maxDepth)
17513
                ? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1)
17514
                : query
17515
              ;
17516
              if( $.isPlainObject( object[camelCaseValue] ) && (depth != maxDepth) ) {
17517
                object = object[camelCaseValue];
17518
              }
17519
              else if( object[camelCaseValue] !== undefined ) {
17520
                found = object[camelCaseValue];
17521
                return false;
17522
              }
17523
              else if( $.isPlainObject( object[value] ) && (depth != maxDepth) ) {
17524
                object = object[value];
17525
              }
17526
              else if( object[value] !== undefined ) {
17527
                found = object[value];
17528
                return false;
17529
              }
17530
              else {
17531
                return false;
17532
              }
17533
            });
17534
          }
17535
          if ( $.isFunction( found ) ) {
17536
            response = found.apply(context, passedArguments);
17537
          }
17538
          else if(found !== undefined) {
17539
            response = found;
17540
          }
17541
          if($.isArray(returnedValue)) {
17542
            returnedValue.push(response);
17543
          }
17544
          else if(returnedValue !== undefined) {
17545
            returnedValue = [returnedValue, response];
17546
          }
17547
          else if(response !== undefined) {
17548
            returnedValue = response;
17549
          }
17550
          return found;
17551
        }
17552
      };
17553
17554
      if(methodInvoked) {
17555
        if(instance === undefined) {
17556
          module.initialize();
17557
        }
17558
        module.invoke(query);
17559
      }
17560
      else {
17561
        if(instance !== undefined) {
17562
          instance.invoke('destroy');
17563
        }
17564
        module.initialize();
17565
      }
17566
    })
17567
  ;
17568
17569
  return (returnedValue !== undefined)
17570
    ? returnedValue
17571
    : this
17572
  ;
17573
};
17574
17575
$.fn.sticky.settings = {
17576
17577
  name           : 'Sticky',
17578
  namespace      : 'sticky',
17579
17580
  silent         : false,
17581
  debug          : false,
17582
  verbose        : true,
17583
  performance    : true,
17584
17585
  // whether to stick in the opposite direction on scroll up
17586
  pushing        : false,
17587
17588
  context        : false,
17589
  container      : false,
17590
17591
  // Context to watch scroll events
17592
  scrollContext  : window,
17593
17594
  // Offset to adjust scroll
17595
  offset         : 0,
17596
17597
  // Offset to adjust scroll when attached to bottom of screen
17598
  bottomOffset   : 0,
17599
17600
  // will only set container height if difference between context and container is larger than this number
17601
  jitter         : 5,
17602
17603
  // set width of sticky element when it is fixed to page (used to make sure 100% width is maintained if no fixed size set)
17604
  setSize        : true,
17605
17606
  // Whether to automatically observe changes with Mutation Observers
17607
  observeChanges : false,
17608
17609
  // Called when position is recalculated
17610
  onReposition   : function(){},
17611
17612
  // Called on each scroll
17613
  onScroll       : function(){},
17614
17615
  // Called when element is stuck to viewport
17616
  onStick        : function(){},
17617
17618
  // Called when element is unstuck from viewport
17619
  onUnstick      : function(){},
17620
17621
  // Called when element reaches top of context
17622
  onTop          : function(){},
17623
17624
  // Called when element reaches bottom of context
17625
  onBottom       : function(){},
17626
17627
  error         : {
17628
    container      : 'Sticky element must be inside a relative container',
17629
    visible        : 'Element is hidden, you must call refresh after element becomes visible. Use silent setting to surpress this warning in production.',
17630
    method         : 'The method you called is not defined.',
17631
    invalidContext : 'Context specified does not exist',
17632
    elementSize    : 'Sticky element is larger than its container, cannot create sticky.'
17633
  },
17634
17635
  className : {
17636
    bound     : 'bound',
17637
    fixed     : 'fixed',
17638
    supported : 'native',
17639
    top       : 'top',
17640
    bottom    : 'bottom'
17641
  }
17642
17643
};
17644
17645
})( jQuery, window, document );
17646
17647
/*!
17648
 * # Semantic UI 2.2.11 - Tab

public/lib/semantic/components/sticky.js 1 location

@@ 11-959 (lines=949) @@
8
 *
9
 */
10
11
;(function ($, window, document, undefined) {
12
13
"use strict";
14
15
window = (typeof window != 'undefined' && window.Math == Math)
16
  ? window
17
  : (typeof self != 'undefined' && self.Math == Math)
18
    ? self
19
    : Function('return this')()
20
;
21
22
$.fn.sticky = function(parameters) {
23
  var
24
    $allModules    = $(this),
25
    moduleSelector = $allModules.selector || '',
26
27
    time           = new Date().getTime(),
28
    performance    = [],
29
30
    query          = arguments[0],
31
    methodInvoked  = (typeof query == 'string'),
32
    queryArguments = [].slice.call(arguments, 1),
33
    returnedValue
34
  ;
35
36
  $allModules
37
    .each(function() {
38
      var
39
        settings              = ( $.isPlainObject(parameters) )
40
          ? $.extend(true, {}, $.fn.sticky.settings, parameters)
41
          : $.extend({}, $.fn.sticky.settings),
42
43
        className             = settings.className,
44
        namespace             = settings.namespace,
45
        error                 = settings.error,
46
47
        eventNamespace        = '.' + namespace,
48
        moduleNamespace       = 'module-' + namespace,
49
50
        $module               = $(this),
51
        $window               = $(window),
52
        $scroll               = $(settings.scrollContext),
53
        $container,
54
        $context,
55
56
        selector              = $module.selector || '',
57
        instance              = $module.data(moduleNamespace),
58
59
        requestAnimationFrame = window.requestAnimationFrame
60
          || window.mozRequestAnimationFrame
61
          || window.webkitRequestAnimationFrame
62
          || window.msRequestAnimationFrame
63
          || function(callback) { setTimeout(callback, 0); },
64
65
        element         = this,
66
67
        documentObserver,
68
        observer,
69
        module
70
      ;
71
72
      module      = {
73
74
        initialize: function() {
75
76
          module.determineContainer();
77
          module.determineContext();
78
          module.verbose('Initializing sticky', settings, $container);
79
80
          module.save.positions();
81
          module.checkErrors();
82
          module.bind.events();
83
84
          if(settings.observeChanges) {
85
            module.observeChanges();
86
          }
87
          module.instantiate();
88
        },
89
90
        instantiate: function() {
91
          module.verbose('Storing instance of module', module);
92
          instance = module;
93
          $module
94
            .data(moduleNamespace, module)
95
          ;
96
        },
97
98
        destroy: function() {
99
          module.verbose('Destroying previous instance');
100
          module.reset();
101
          if(documentObserver) {
102
            documentObserver.disconnect();
103
          }
104
          if(observer) {
105
            observer.disconnect();
106
          }
107
          $window
108
            .off('load' + eventNamespace, module.event.load)
109
            .off('resize' + eventNamespace, module.event.resize)
110
          ;
111
          $scroll
112
            .off('scrollchange' + eventNamespace, module.event.scrollchange)
113
          ;
114
          $module.removeData(moduleNamespace);
115
        },
116
117
        observeChanges: function() {
118
          if('MutationObserver' in window) {
119
            documentObserver = new MutationObserver(module.event.documentChanged);
120
            observer         = new MutationObserver(module.event.changed);
121
            documentObserver.observe(document, {
122
              childList : true,
123
              subtree   : true
124
            });
125
            observer.observe(element, {
126
              childList : true,
127
              subtree   : true
128
            });
129
            observer.observe($context[0], {
130
              childList : true,
131
              subtree   : true
132
            });
133
            module.debug('Setting up mutation observer', observer);
134
          }
135
        },
136
137
        determineContainer: function() {
138
          if(settings.container) {
139
            $container = $(settings.container);
140
          }
141
          else {
142
            $container = $module.offsetParent();
143
          }
144
        },
145
146
        determineContext: function() {
147
          if(settings.context) {
148
            $context = $(settings.context);
149
          }
150
          else {
151
            $context = $container;
152
          }
153
          if($context.length === 0) {
154
            module.error(error.invalidContext, settings.context, $module);
155
            return;
156
          }
157
        },
158
159
        checkErrors: function() {
160
          if( module.is.hidden() ) {
161
            module.error(error.visible, $module);
162
          }
163
          if(module.cache.element.height > module.cache.context.height) {
164
            module.reset();
165
            module.error(error.elementSize, $module);
166
            return;
167
          }
168
        },
169
170
        bind: {
171
          events: function() {
172
            $window
173
              .on('load' + eventNamespace, module.event.load)
174
              .on('resize' + eventNamespace, module.event.resize)
175
            ;
176
            // pub/sub pattern
177
            $scroll
178
              .off('scroll' + eventNamespace)
179
              .on('scroll' + eventNamespace, module.event.scroll)
180
              .on('scrollchange' + eventNamespace, module.event.scrollchange)
181
            ;
182
          }
183
        },
184
185
        event: {
186
          changed: function(mutations) {
187
            clearTimeout(module.timer);
188
            module.timer = setTimeout(function() {
189
              module.verbose('DOM tree modified, updating sticky menu', mutations);
190
              module.refresh();
191
            }, 100);
192
          },
193
          documentChanged: function(mutations) {
194
            [].forEach.call(mutations, function(mutation) {
195
              if(mutation.removedNodes) {
196
                [].forEach.call(mutation.removedNodes, function(node) {
197
                  if(node == element || $(node).find(element).length > 0) {
198
                    module.debug('Element removed from DOM, tearing down events');
199
                    module.destroy();
200
                  }
201
                });
202
              }
203
            });
204
          },
205
          load: function() {
206
            module.verbose('Page contents finished loading');
207
            requestAnimationFrame(module.refresh);
208
          },
209
          resize: function() {
210
            module.verbose('Window resized');
211
            requestAnimationFrame(module.refresh);
212
          },
213
          scroll: function() {
214
            requestAnimationFrame(function() {
215
              $scroll.triggerHandler('scrollchange' + eventNamespace, $scroll.scrollTop() );
216
            });
217
          },
218
          scrollchange: function(event, scrollPosition) {
219
            module.stick(scrollPosition);
220
            settings.onScroll.call(element);
221
          }
222
        },
223
224
        refresh: function(hardRefresh) {
225
          module.reset();
226
          if(!settings.context) {
227
            module.determineContext();
228
          }
229
          if(hardRefresh) {
230
            module.determineContainer();
231
          }
232
          module.save.positions();
233
          module.stick();
234
          settings.onReposition.call(element);
235
        },
236
237
        supports: {
238
          sticky: function() {
239
            var
240
              $element = $('<div/>'),
241
              element = $element[0]
242
            ;
243
            $element.addClass(className.supported);
244
            return($element.css('position').match('sticky'));
245
          }
246
        },
247
248
        save: {
249
          lastScroll: function(scroll) {
250
            module.lastScroll = scroll;
251
          },
252
          elementScroll: function(scroll) {
253
            module.elementScroll = scroll;
254
          },
255
          positions: function() {
256
            var
257
              scrollContext = {
258
                height : $scroll.height()
259
              },
260
              element = {
261
                margin: {
262
                  top    : parseInt($module.css('margin-top'), 10),
263
                  bottom : parseInt($module.css('margin-bottom'), 10),
264
                },
265
                offset : $module.offset(),
266
                width  : $module.outerWidth(),
267
                height : $module.outerHeight()
268
              },
269
              context = {
270
                offset : $context.offset(),
271
                height : $context.outerHeight()
272
              },
273
              container = {
274
                height: $container.outerHeight()
275
              }
276
            ;
277
            if( !module.is.standardScroll() ) {
278
              module.debug('Non-standard scroll. Removing scroll offset from element offset');
279
280
              scrollContext.top  = $scroll.scrollTop();
281
              scrollContext.left = $scroll.scrollLeft();
282
283
              element.offset.top  += scrollContext.top;
284
              context.offset.top  += scrollContext.top;
285
              element.offset.left += scrollContext.left;
286
              context.offset.left += scrollContext.left;
287
            }
288
            module.cache = {
289
              fits          : ( (element.height + settings.offset) <= scrollContext.height),
290
              sameHeight    : (element.height == context.height),
291
              scrollContext : {
292
                height : scrollContext.height
293
              },
294
              element: {
295
                margin : element.margin,
296
                top    : element.offset.top - element.margin.top,
297
                left   : element.offset.left,
298
                width  : element.width,
299
                height : element.height,
300
                bottom : element.offset.top + element.height
301
              },
302
              context: {
303
                top           : context.offset.top,
304
                height        : context.height,
305
                bottom        : context.offset.top + context.height
306
              }
307
            };
308
            module.set.containerSize();
309
310
            module.stick();
311
            module.debug('Caching element positions', module.cache);
312
          }
313
        },
314
315
        get: {
316
          direction: function(scroll) {
317
            var
318
              direction = 'down'
319
            ;
320
            scroll = scroll || $scroll.scrollTop();
321
            if(module.lastScroll !== undefined) {
322
              if(module.lastScroll < scroll) {
323
                direction = 'down';
324
              }
325
              else if(module.lastScroll > scroll) {
326
                direction = 'up';
327
              }
328
            }
329
            return direction;
330
          },
331
          scrollChange: function(scroll) {
332
            scroll = scroll || $scroll.scrollTop();
333
            return (module.lastScroll)
334
              ? (scroll - module.lastScroll)
335
              : 0
336
            ;
337
          },
338
          currentElementScroll: function() {
339
            if(module.elementScroll) {
340
              return module.elementScroll;
341
            }
342
            return ( module.is.top() )
343
              ? Math.abs(parseInt($module.css('top'), 10))    || 0
344
              : Math.abs(parseInt($module.css('bottom'), 10)) || 0
345
            ;
346
          },
347
348
          elementScroll: function(scroll) {
349
            scroll = scroll || $scroll.scrollTop();
350
            var
351
              element        = module.cache.element,
352
              scrollContext  = module.cache.scrollContext,
353
              delta          = module.get.scrollChange(scroll),
354
              maxScroll      = (element.height - scrollContext.height + settings.offset),
355
              elementScroll  = module.get.currentElementScroll(),
356
              possibleScroll = (elementScroll + delta)
357
            ;
358
            if(module.cache.fits || possibleScroll < 0) {
359
              elementScroll = 0;
360
            }
361
            else if(possibleScroll > maxScroll ) {
362
              elementScroll = maxScroll;
363
            }
364
            else {
365
              elementScroll = possibleScroll;
366
            }
367
            return elementScroll;
368
          }
369
        },
370
371
        remove: {
372
          lastScroll: function() {
373
            delete module.lastScroll;
374
          },
375
          elementScroll: function(scroll) {
376
            delete module.elementScroll;
377
          },
378
          minimumSize: function() {
379
            $container
380
              .css('min-height', '')
381
            ;
382
          },
383
          offset: function() {
384
            $module.css('margin-top', '');
385
          }
386
        },
387
388
        set: {
389
          offset: function() {
390
            module.verbose('Setting offset on element', settings.offset);
391
            $module
392
              .css('margin-top', settings.offset)
393
            ;
394
          },
395
          containerSize: function() {
396
            var
397
              tagName = $container.get(0).tagName
398
            ;
399
            if(tagName === 'HTML' || tagName == 'body') {
400
              // this can trigger for too many reasons
401
              //module.error(error.container, tagName, $module);
402
              module.determineContainer();
403
            }
404
            else {
405
              if( Math.abs($container.outerHeight() - module.cache.context.height) > settings.jitter) {
406
                module.debug('Context has padding, specifying exact height for container', module.cache.context.height);
407
                $container.css({
408
                  height: module.cache.context.height
409
                });
410
              }
411
            }
412
          },
413
          minimumSize: function() {
414
            var
415
              element   = module.cache.element
416
            ;
417
            $container
418
              .css('min-height', element.height)
419
            ;
420
          },
421
          scroll: function(scroll) {
422
            module.debug('Setting scroll on element', scroll);
423
            if(module.elementScroll == scroll) {
424
              return;
425
            }
426
            if( module.is.top() ) {
427
              $module
428
                .css('bottom', '')
429
                .css('top', -scroll)
430
              ;
431
            }
432
            if( module.is.bottom() ) {
433
              $module
434
                .css('top', '')
435
                .css('bottom', scroll)
436
              ;
437
            }
438
          },
439
          size: function() {
440
            if(module.cache.element.height !== 0 && module.cache.element.width !== 0) {
441
              element.style.setProperty('width',  module.cache.element.width  + 'px', 'important');
442
              element.style.setProperty('height', module.cache.element.height + 'px', 'important');
443
            }
444
          }
445
        },
446
447
        is: {
448
          standardScroll: function() {
449
            return ($scroll[0] == window);
450
          },
451
          top: function() {
452
            return $module.hasClass(className.top);
453
          },
454
          bottom: function() {
455
            return $module.hasClass(className.bottom);
456
          },
457
          initialPosition: function() {
458
            return (!module.is.fixed() && !module.is.bound());
459
          },
460
          hidden: function() {
461
            return (!$module.is(':visible'));
462
          },
463
          bound: function() {
464
            return $module.hasClass(className.bound);
465
          },
466
          fixed: function() {
467
            return $module.hasClass(className.fixed);
468
          }
469
        },
470
471
        stick: function(scroll) {
472
          var
473
            cachedPosition = scroll || $scroll.scrollTop(),
474
            cache          = module.cache,
475
            fits           = cache.fits,
476
            sameHeight     = cache.sameHeight,
477
            element        = cache.element,
478
            scrollContext  = cache.scrollContext,
479
            context        = cache.context,
480
            offset         = (module.is.bottom() && settings.pushing)
481
              ? settings.bottomOffset
482
              : settings.offset,
483
            scroll         = {
484
              top    : cachedPosition + offset,
485
              bottom : cachedPosition + offset + scrollContext.height
486
            },
487
            direction      = module.get.direction(scroll.top),
488
            elementScroll  = (fits)
489
              ? 0
490
              : module.get.elementScroll(scroll.top),
491
492
            // shorthand
493
            doesntFit      = !fits,
494
            elementVisible = (element.height !== 0)
495
          ;
496
          if(elementVisible && !sameHeight) {
497
498
            if( module.is.initialPosition() ) {
499
              if(scroll.top >= context.bottom) {
500
                module.debug('Initial element position is bottom of container');
501
                module.bindBottom();
502
              }
503
              else if(scroll.top > element.top) {
504
                if( (element.height + scroll.top - elementScroll) >= context.bottom ) {
505
                  module.debug('Initial element position is bottom of container');
506
                  module.bindBottom();
507
                }
508
                else {
509
                  module.debug('Initial element position is fixed');
510
                  module.fixTop();
511
                }
512
              }
513
514
            }
515
            else if( module.is.fixed() ) {
516
517
              // currently fixed top
518
              if( module.is.top() ) {
519
                if( scroll.top <= element.top ) {
520
                  module.debug('Fixed element reached top of container');
521
                  module.setInitialPosition();
522
                }
523
                else if( (element.height + scroll.top - elementScroll) >= context.bottom ) {
524
                  module.debug('Fixed element reached bottom of container');
525
                  module.bindBottom();
526
                }
527
                // scroll element if larger than screen
528
                else if(doesntFit) {
529
                  module.set.scroll(elementScroll);
530
                  module.save.lastScroll(scroll.top);
531
                  module.save.elementScroll(elementScroll);
532
                }
533
              }
534
535
              // currently fixed bottom
536
              else if(module.is.bottom() ) {
537
538
                // top edge
539
                if( (scroll.bottom - element.height) <= element.top) {
540
                  module.debug('Bottom fixed rail has reached top of container');
541
                  module.setInitialPosition();
542
                }
543
                // bottom edge
544
                else if(scroll.bottom >= context.bottom) {
545
                  module.debug('Bottom fixed rail has reached bottom of container');
546
                  module.bindBottom();
547
                }
548
                // scroll element if larger than screen
549
                else if(doesntFit) {
550
                  module.set.scroll(elementScroll);
551
                  module.save.lastScroll(scroll.top);
552
                  module.save.elementScroll(elementScroll);
553
                }
554
555
              }
556
            }
557
            else if( module.is.bottom() ) {
558
              if( scroll.top <= element.top ) {
559
                module.debug('Jumped from bottom fixed to top fixed, most likely used home/end button');
560
                module.setInitialPosition();
561
              }
562
              else {
563
                if(settings.pushing) {
564
                  if(module.is.bound() && scroll.bottom <= context.bottom ) {
565
                    module.debug('Fixing bottom attached element to bottom of browser.');
566
                    module.fixBottom();
567
                  }
568
                }
569
                else {
570
                  if(module.is.bound() && (scroll.top <= context.bottom - element.height) ) {
571
                    module.debug('Fixing bottom attached element to top of browser.');
572
                    module.fixTop();
573
                  }
574
                }
575
              }
576
            }
577
          }
578
        },
579
580
        bindTop: function() {
581
          module.debug('Binding element to top of parent container');
582
          module.remove.offset();
583
          $module
584
            .css({
585
              left         : '',
586
              top          : '',
587
              marginBottom : ''
588
            })
589
            .removeClass(className.fixed)
590
            .removeClass(className.bottom)
591
            .addClass(className.bound)
592
            .addClass(className.top)
593
          ;
594
          settings.onTop.call(element);
595
          settings.onUnstick.call(element);
596
        },
597
        bindBottom: function() {
598
          module.debug('Binding element to bottom of parent container');
599
          module.remove.offset();
600
          $module
601
            .css({
602
              left         : '',
603
              top          : ''
604
            })
605
            .removeClass(className.fixed)
606
            .removeClass(className.top)
607
            .addClass(className.bound)
608
            .addClass(className.bottom)
609
          ;
610
          settings.onBottom.call(element);
611
          settings.onUnstick.call(element);
612
        },
613
614
        setInitialPosition: function() {
615
          module.debug('Returning to initial position');
616
          module.unfix();
617
          module.unbind();
618
        },
619
620
621
        fixTop: function() {
622
          module.debug('Fixing element to top of page');
623
          if(settings.setSize) {
624
            module.set.size();
625
          }
626
          module.set.minimumSize();
627
          module.set.offset();
628
          $module
629
            .css({
630
              left         : module.cache.element.left,
631
              bottom       : '',
632
              marginBottom : ''
633
            })
634
            .removeClass(className.bound)
635
            .removeClass(className.bottom)
636
            .addClass(className.fixed)
637
            .addClass(className.top)
638
          ;
639
          settings.onStick.call(element);
640
        },
641
642
        fixBottom: function() {
643
          module.debug('Sticking element to bottom of page');
644
          if(settings.setSize) {
645
            module.set.size();
646
          }
647
          module.set.minimumSize();
648
          module.set.offset();
649
          $module
650
            .css({
651
              left         : module.cache.element.left,
652
              bottom       : '',
653
              marginBottom : ''
654
            })
655
            .removeClass(className.bound)
656
            .removeClass(className.top)
657
            .addClass(className.fixed)
658
            .addClass(className.bottom)
659
          ;
660
          settings.onStick.call(element);
661
        },
662
663
        unbind: function() {
664
          if( module.is.bound() ) {
665
            module.debug('Removing container bound position on element');
666
            module.remove.offset();
667
            $module
668
              .removeClass(className.bound)
669
              .removeClass(className.top)
670
              .removeClass(className.bottom)
671
            ;
672
          }
673
        },
674
675
        unfix: function() {
676
          if( module.is.fixed() ) {
677
            module.debug('Removing fixed position on element');
678
            module.remove.minimumSize();
679
            module.remove.offset();
680
            $module
681
              .removeClass(className.fixed)
682
              .removeClass(className.top)
683
              .removeClass(className.bottom)
684
            ;
685
            settings.onUnstick.call(element);
686
          }
687
        },
688
689
        reset: function() {
690
          module.debug('Resetting elements position');
691
          module.unbind();
692
          module.unfix();
693
          module.resetCSS();
694
          module.remove.offset();
695
          module.remove.lastScroll();
696
        },
697
698
        resetCSS: function() {
699
          $module
700
            .css({
701
              width  : '',
702
              height : ''
703
            })
704
          ;
705
          $container
706
            .css({
707
              height: ''
708
            })
709
          ;
710
        },
711
712
        setting: function(name, value) {
713
          if( $.isPlainObject(name) ) {
714
            $.extend(true, settings, name);
715
          }
716
          else if(value !== undefined) {
717
            settings[name] = value;
718
          }
719
          else {
720
            return settings[name];
721
          }
722
        },
723
        internal: function(name, value) {
724
          if( $.isPlainObject(name) ) {
725
            $.extend(true, module, name);
726
          }
727
          else if(value !== undefined) {
728
            module[name] = value;
729
          }
730
          else {
731
            return module[name];
732
          }
733
        },
734
        debug: function() {
735
          if(!settings.silent && settings.debug) {
736
            if(settings.performance) {
737
              module.performance.log(arguments);
738
            }
739
            else {
740
              module.debug = Function.prototype.bind.call(console.info, console, settings.name + ':');
741
              module.debug.apply(console, arguments);
742
            }
743
          }
744
        },
745
        verbose: function() {
746
          if(!settings.silent && settings.verbose && settings.debug) {
747
            if(settings.performance) {
748
              module.performance.log(arguments);
749
            }
750
            else {
751
              module.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':');
752
              module.verbose.apply(console, arguments);
753
            }
754
          }
755
        },
756
        error: function() {
757
          if(!settings.silent) {
758
            module.error = Function.prototype.bind.call(console.error, console, settings.name + ':');
759
            module.error.apply(console, arguments);
760
          }
761
        },
762
        performance: {
763
          log: function(message) {
764
            var
765
              currentTime,
766
              executionTime,
767
              previousTime
768
            ;
769
            if(settings.performance) {
770
              currentTime   = new Date().getTime();
771
              previousTime  = time || currentTime;
772
              executionTime = currentTime - previousTime;
773
              time          = currentTime;
774
              performance.push({
775
                'Name'           : message[0],
776
                'Arguments'      : [].slice.call(message, 1) || '',
777
                'Element'        : element,
778
                'Execution Time' : executionTime
779
              });
780
            }
781
            clearTimeout(module.performance.timer);
782
            module.performance.timer = setTimeout(module.performance.display, 0);
783
          },
784
          display: function() {
785
            var
786
              title = settings.name + ':',
787
              totalTime = 0
788
            ;
789
            time = false;
790
            clearTimeout(module.performance.timer);
791
            $.each(performance, function(index, data) {
792
              totalTime += data['Execution Time'];
793
            });
794
            title += ' ' + totalTime + 'ms';
795
            if(moduleSelector) {
796
              title += ' \'' + moduleSelector + '\'';
797
            }
798
            if( (console.group !== undefined || console.table !== undefined) && performance.length > 0) {
799
              console.groupCollapsed(title);
800
              if(console.table) {
801
                console.table(performance);
802
              }
803
              else {
804
                $.each(performance, function(index, data) {
805
                  console.log(data['Name'] + ': ' + data['Execution Time']+'ms');
806
                });
807
              }
808
              console.groupEnd();
809
            }
810
            performance = [];
811
          }
812
        },
813
        invoke: function(query, passedArguments, context) {
814
          var
815
            object = instance,
816
            maxDepth,
817
            found,
818
            response
819
          ;
820
          passedArguments = passedArguments || queryArguments;
821
          context         = element         || context;
822
          if(typeof query == 'string' && object !== undefined) {
823
            query    = query.split(/[\. ]/);
824
            maxDepth = query.length - 1;
825
            $.each(query, function(depth, value) {
826
              var camelCaseValue = (depth != maxDepth)
827
                ? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1)
828
                : query
829
              ;
830
              if( $.isPlainObject( object[camelCaseValue] ) && (depth != maxDepth) ) {
831
                object = object[camelCaseValue];
832
              }
833
              else if( object[camelCaseValue] !== undefined ) {
834
                found = object[camelCaseValue];
835
                return false;
836
              }
837
              else if( $.isPlainObject( object[value] ) && (depth != maxDepth) ) {
838
                object = object[value];
839
              }
840
              else if( object[value] !== undefined ) {
841
                found = object[value];
842
                return false;
843
              }
844
              else {
845
                return false;
846
              }
847
            });
848
          }
849
          if ( $.isFunction( found ) ) {
850
            response = found.apply(context, passedArguments);
851
          }
852
          else if(found !== undefined) {
853
            response = found;
854
          }
855
          if($.isArray(returnedValue)) {
856
            returnedValue.push(response);
857
          }
858
          else if(returnedValue !== undefined) {
859
            returnedValue = [returnedValue, response];
860
          }
861
          else if(response !== undefined) {
862
            returnedValue = response;
863
          }
864
          return found;
865
        }
866
      };
867
868
      if(methodInvoked) {
869
        if(instance === undefined) {
870
          module.initialize();
871
        }
872
        module.invoke(query);
873
      }
874
      else {
875
        if(instance !== undefined) {
876
          instance.invoke('destroy');
877
        }
878
        module.initialize();
879
      }
880
    })
881
  ;
882
883
  return (returnedValue !== undefined)
884
    ? returnedValue
885
    : this
886
  ;
887
};
888
889
$.fn.sticky.settings = {
890
891
  name           : 'Sticky',
892
  namespace      : 'sticky',
893
894
  silent         : false,
895
  debug          : false,
896
  verbose        : true,
897
  performance    : true,
898
899
  // whether to stick in the opposite direction on scroll up
900
  pushing        : false,
901
902
  context        : false,
903
  container      : false,
904
905
  // Context to watch scroll events
906
  scrollContext  : window,
907
908
  // Offset to adjust scroll
909
  offset         : 0,
910
911
  // Offset to adjust scroll when attached to bottom of screen
912
  bottomOffset   : 0,
913
914
  // will only set container height if difference between context and container is larger than this number
915
  jitter         : 5,
916
917
  // set width of sticky element when it is fixed to page (used to make sure 100% width is maintained if no fixed size set)
918
  setSize        : true,
919
920
  // Whether to automatically observe changes with Mutation Observers
921
  observeChanges : false,
922
923
  // Called when position is recalculated
924
  onReposition   : function(){},
925
926
  // Called on each scroll
927
  onScroll       : function(){},
928
929
  // Called when element is stuck to viewport
930
  onStick        : function(){},
931
932
  // Called when element is unstuck from viewport
933
  onUnstick      : function(){},
934
935
  // Called when element reaches top of context
936
  onTop          : function(){},
937
938
  // Called when element reaches bottom of context
939
  onBottom       : function(){},
940
941
  error         : {
942
    container      : 'Sticky element must be inside a relative container',
943
    visible        : 'Element is hidden, you must call refresh after element becomes visible. Use silent setting to surpress this warning in production.',
944
    method         : 'The method you called is not defined.',
945
    invalidContext : 'Context specified does not exist',
946
    elementSize    : 'Sticky element is larger than its container, cannot create sticky.'
947
  },
948
949
  className : {
950
    bound     : 'bound',
951
    fixed     : 'fixed',
952
    supported : 'native',
953
    top       : 'top',
954
    bottom    : 'bottom'
955
  }
956
957
};
958
959
})( jQuery, window, document );
960