Code Duplication    Length = 1476-1476 lines in 2 locations

public/lib/semantic/semantic.js 1 location

@@ 10361-11836 (lines=1476) @@
10358
 *
10359
 */
10360
10361
;(function ($, window, document, undefined) {
10362
10363
"use strict";
10364
10365
window = (typeof window != 'undefined' && window.Math == Math)
10366
  ? window
10367
  : (typeof self != 'undefined' && self.Math == Math)
10368
    ? self
10369
    : Function('return this')()
10370
;
10371
10372
$.fn.popup = function(parameters) {
10373
  var
10374
    $allModules    = $(this),
10375
    $document      = $(document),
10376
    $window        = $(window),
10377
    $body          = $('body'),
10378
10379
    moduleSelector = $allModules.selector || '',
10380
10381
    hasTouch       = (true),
10382
    time           = new Date().getTime(),
10383
    performance    = [],
10384
10385
    query          = arguments[0],
10386
    methodInvoked  = (typeof query == 'string'),
10387
    queryArguments = [].slice.call(arguments, 1),
10388
10389
    returnedValue
10390
  ;
10391
  $allModules
10392
    .each(function() {
10393
      var
10394
        settings        = ( $.isPlainObject(parameters) )
10395
          ? $.extend(true, {}, $.fn.popup.settings, parameters)
10396
          : $.extend({}, $.fn.popup.settings),
10397
10398
        selector           = settings.selector,
10399
        className          = settings.className,
10400
        error              = settings.error,
10401
        metadata           = settings.metadata,
10402
        namespace          = settings.namespace,
10403
10404
        eventNamespace     = '.' + settings.namespace,
10405
        moduleNamespace    = 'module-' + namespace,
10406
10407
        $module            = $(this),
10408
        $context           = $(settings.context),
10409
        $scrollContext     = $(settings.scrollContext),
10410
        $boundary          = $(settings.boundary),
10411
        $target            = (settings.target)
10412
          ? $(settings.target)
10413
          : $module,
10414
10415
        $popup,
10416
        $offsetParent,
10417
10418
        searchDepth        = 0,
10419
        triedPositions     = false,
10420
        openedWithTouch    = false,
10421
10422
        element            = this,
10423
        instance           = $module.data(moduleNamespace),
10424
10425
        documentObserver,
10426
        elementNamespace,
10427
        id,
10428
        module
10429
      ;
10430
10431
      module = {
10432
10433
        // binds events
10434
        initialize: function() {
10435
          module.debug('Initializing', $module);
10436
          module.createID();
10437
          module.bind.events();
10438
          if(!module.exists() && settings.preserve) {
10439
            module.create();
10440
          }
10441
          if(settings.observeChanges) {
10442
            module.observeChanges();
10443
          }
10444
          module.instantiate();
10445
        },
10446
10447
        instantiate: function() {
10448
          module.verbose('Storing instance', module);
10449
          instance = module;
10450
          $module
10451
            .data(moduleNamespace, instance)
10452
          ;
10453
        },
10454
10455
        observeChanges: function() {
10456
          if('MutationObserver' in window) {
10457
            documentObserver = new MutationObserver(module.event.documentChanged);
10458
            documentObserver.observe(document, {
10459
              childList : true,
10460
              subtree   : true
10461
            });
10462
            module.debug('Setting up mutation observer', documentObserver);
10463
          }
10464
        },
10465
10466
        refresh: function() {
10467
          if(settings.popup) {
10468
            $popup = $(settings.popup).eq(0);
10469
          }
10470
          else {
10471
            if(settings.inline) {
10472
              $popup = $target.nextAll(selector.popup).eq(0);
10473
              settings.popup = $popup;
10474
            }
10475
          }
10476
          if(settings.popup) {
10477
            $popup.addClass(className.loading);
10478
            $offsetParent = module.get.offsetParent($target);
10479
            $popup.removeClass(className.loading);
10480
            if(settings.movePopup && module.has.popup() && module.get.offsetParent($popup)[0] !== $offsetParent[0]) {
10481
              module.debug('Moving popup to the same offset parent as target');
10482
              $popup
10483
                .detach()
10484
                .appendTo($offsetParent)
10485
              ;
10486
            }
10487
          }
10488
          else {
10489
            $offsetParent = (settings.inline)
10490
              ? module.get.offsetParent($target)
10491
              : module.has.popup()
10492
                ? module.get.offsetParent($target)
10493
                : $body
10494
            ;
10495
          }
10496
          if( $offsetParent.is('html') && $offsetParent[0] !== $body[0] ) {
10497
            module.debug('Setting page as offset parent');
10498
            $offsetParent = $body;
10499
          }
10500
          if( module.get.variation() ) {
10501
            module.set.variation();
10502
          }
10503
        },
10504
10505
        reposition: function() {
10506
          module.refresh();
10507
          module.set.position();
10508
        },
10509
10510
        destroy: function() {
10511
          module.debug('Destroying previous module');
10512
          if(documentObserver) {
10513
            documentObserver.disconnect();
10514
          }
10515
          // remove element only if was created dynamically
10516
          if($popup && !settings.preserve) {
10517
            module.removePopup();
10518
          }
10519
          // clear all timeouts
10520
          clearTimeout(module.hideTimer);
10521
          clearTimeout(module.showTimer);
10522
          // remove events
10523
          module.unbind.close();
10524
          module.unbind.events();
10525
          $module
10526
            .removeData(moduleNamespace)
10527
          ;
10528
        },
10529
10530
        event: {
10531
          start:  function(event) {
10532
            var
10533
              delay = ($.isPlainObject(settings.delay))
10534
                ? settings.delay.show
10535
                : settings.delay
10536
            ;
10537
            clearTimeout(module.hideTimer);
10538
            if(!openedWithTouch) {
10539
              module.showTimer = setTimeout(module.show, delay);
10540
            }
10541
          },
10542
          end:  function() {
10543
            var
10544
              delay = ($.isPlainObject(settings.delay))
10545
                ? settings.delay.hide
10546
                : settings.delay
10547
            ;
10548
            clearTimeout(module.showTimer);
10549
            module.hideTimer = setTimeout(module.hide, delay);
10550
          },
10551
          touchstart: function(event) {
10552
            openedWithTouch = true;
10553
            module.show();
10554
          },
10555
          resize: function() {
10556
            if( module.is.visible() ) {
10557
              module.set.position();
10558
            }
10559
          },
10560
          documentChanged: function(mutations) {
10561
            [].forEach.call(mutations, function(mutation) {
10562
              if(mutation.removedNodes) {
10563
                [].forEach.call(mutation.removedNodes, function(node) {
10564
                  if(node == element || $(node).find(element).length > 0) {
10565
                    module.debug('Element removed from DOM, tearing down events');
10566
                    module.destroy();
10567
                  }
10568
                });
10569
              }
10570
            });
10571
          },
10572
          hideGracefully: function(event) {
10573
            var
10574
              $target = $(event.target),
10575
              isInDOM = $.contains(document.documentElement, event.target),
10576
              inPopup = ($target.closest(selector.popup).length > 0)
10577
            ;
10578
            // don't close on clicks inside popup
10579
            if(event && !inPopup && isInDOM) {
10580
              module.debug('Click occurred outside popup hiding popup');
10581
              module.hide();
10582
            }
10583
            else {
10584
              module.debug('Click was inside popup, keeping popup open');
10585
            }
10586
          }
10587
        },
10588
10589
        // generates popup html from metadata
10590
        create: function() {
10591
          var
10592
            html      = module.get.html(),
10593
            title     = module.get.title(),
10594
            content   = module.get.content()
10595
          ;
10596
10597
          if(html || content || title) {
10598
            module.debug('Creating pop-up html');
10599
            if(!html) {
10600
              html = settings.templates.popup({
10601
                title   : title,
10602
                content : content
10603
              });
10604
            }
10605
            $popup = $('<div/>')
10606
              .addClass(className.popup)
10607
              .data(metadata.activator, $module)
10608
              .html(html)
10609
            ;
10610
            if(settings.inline) {
10611
              module.verbose('Inserting popup element inline', $popup);
10612
              $popup
10613
                .insertAfter($module)
10614
              ;
10615
            }
10616
            else {
10617
              module.verbose('Appending popup element to body', $popup);
10618
              $popup
10619
                .appendTo( $context )
10620
              ;
10621
            }
10622
            module.refresh();
10623
            module.set.variation();
10624
10625
            if(settings.hoverable) {
10626
              module.bind.popup();
10627
            }
10628
            settings.onCreate.call($popup, element);
10629
          }
10630
          else if($target.next(selector.popup).length !== 0) {
10631
            module.verbose('Pre-existing popup found');
10632
            settings.inline = true;
10633
            settings.popup  = $target.next(selector.popup).data(metadata.activator, $module);
10634
            module.refresh();
10635
            if(settings.hoverable) {
10636
              module.bind.popup();
10637
            }
10638
          }
10639
          else if(settings.popup) {
10640
            $(settings.popup).data(metadata.activator, $module);
10641
            module.verbose('Used popup specified in settings');
10642
            module.refresh();
10643
            if(settings.hoverable) {
10644
              module.bind.popup();
10645
            }
10646
          }
10647
          else {
10648
            module.debug('No content specified skipping display', element);
10649
          }
10650
        },
10651
10652
        createID: function() {
10653
          id = (Math.random().toString(16) + '000000000').substr(2, 8);
10654
          elementNamespace = '.' + id;
10655
          module.verbose('Creating unique id for element', id);
10656
        },
10657
10658
        // determines popup state
10659
        toggle: function() {
10660
          module.debug('Toggling pop-up');
10661
          if( module.is.hidden() ) {
10662
            module.debug('Popup is hidden, showing pop-up');
10663
            module.unbind.close();
10664
            module.show();
10665
          }
10666
          else {
10667
            module.debug('Popup is visible, hiding pop-up');
10668
            module.hide();
10669
          }
10670
        },
10671
10672
        show: function(callback) {
10673
          callback = callback || function(){};
10674
          module.debug('Showing pop-up', settings.transition);
10675
          if(module.is.hidden() && !( module.is.active() && module.is.dropdown()) ) {
10676
            if( !module.exists() ) {
10677
              module.create();
10678
            }
10679
            if(settings.onShow.call($popup, element) === false) {
10680
              module.debug('onShow callback returned false, cancelling popup animation');
10681
              return;
10682
            }
10683
            else if(!settings.preserve && !settings.popup) {
10684
              module.refresh();
10685
            }
10686
            if( $popup && module.set.position() ) {
10687
              module.save.conditions();
10688
              if(settings.exclusive) {
10689
                module.hideAll();
10690
              }
10691
              module.animate.show(callback);
10692
            }
10693
          }
10694
        },
10695
10696
10697
        hide: function(callback) {
10698
          callback = callback || function(){};
10699
          if( module.is.visible() || module.is.animating() ) {
10700
            if(settings.onHide.call($popup, element) === false) {
10701
              module.debug('onHide callback returned false, cancelling popup animation');
10702
              return;
10703
            }
10704
            module.remove.visible();
10705
            module.unbind.close();
10706
            module.restore.conditions();
10707
            module.animate.hide(callback);
10708
          }
10709
        },
10710
10711
        hideAll: function() {
10712
          $(selector.popup)
10713
            .filter('.' + className.popupVisible)
10714
            .each(function() {
10715
              $(this)
10716
                .data(metadata.activator)
10717
                  .popup('hide')
10718
              ;
10719
            })
10720
          ;
10721
        },
10722
        exists: function() {
10723
          if(!$popup) {
10724
            return false;
10725
          }
10726
          if(settings.inline || settings.popup) {
10727
            return ( module.has.popup() );
10728
          }
10729
          else {
10730
            return ( $popup.closest($context).length >= 1 )
10731
              ? true
10732
              : false
10733
            ;
10734
          }
10735
        },
10736
10737
        removePopup: function() {
10738
          if( module.has.popup() && !settings.popup) {
10739
            module.debug('Removing popup', $popup);
10740
            $popup.remove();
10741
            $popup = undefined;
10742
            settings.onRemove.call($popup, element);
10743
          }
10744
        },
10745
10746
        save: {
10747
          conditions: function() {
10748
            module.cache = {
10749
              title: $module.attr('title')
10750
            };
10751
            if (module.cache.title) {
10752
              $module.removeAttr('title');
10753
            }
10754
            module.verbose('Saving original attributes', module.cache.title);
10755
          }
10756
        },
10757
        restore: {
10758
          conditions: function() {
10759
            if(module.cache && module.cache.title) {
10760
              $module.attr('title', module.cache.title);
10761
              module.verbose('Restoring original attributes', module.cache.title);
10762
            }
10763
            return true;
10764
          }
10765
        },
10766
        supports: {
10767
          svg: function() {
10768
            return (typeof SVGGraphicsElement === 'undefined');
10769
          }
10770
        },
10771
        animate: {
10772
          show: function(callback) {
10773
            callback = $.isFunction(callback) ? callback : function(){};
10774
            if(settings.transition && $.fn.transition !== undefined && $module.transition('is supported')) {
10775
              module.set.visible();
10776
              $popup
10777
                .transition({
10778
                  animation  : settings.transition + ' in',
10779
                  queue      : false,
10780
                  debug      : settings.debug,
10781
                  verbose    : settings.verbose,
10782
                  duration   : settings.duration,
10783
                  onComplete : function() {
10784
                    module.bind.close();
10785
                    callback.call($popup, element);
10786
                    settings.onVisible.call($popup, element);
10787
                  }
10788
                })
10789
              ;
10790
            }
10791
            else {
10792
              module.error(error.noTransition);
10793
            }
10794
          },
10795
          hide: function(callback) {
10796
            callback = $.isFunction(callback) ? callback : function(){};
10797
            module.debug('Hiding pop-up');
10798
            if(settings.onHide.call($popup, element) === false) {
10799
              module.debug('onHide callback returned false, cancelling popup animation');
10800
              return;
10801
            }
10802
            if(settings.transition && $.fn.transition !== undefined && $module.transition('is supported')) {
10803
              $popup
10804
                .transition({
10805
                  animation  : settings.transition + ' out',
10806
                  queue      : false,
10807
                  duration   : settings.duration,
10808
                  debug      : settings.debug,
10809
                  verbose    : settings.verbose,
10810
                  onComplete : function() {
10811
                    module.reset();
10812
                    callback.call($popup, element);
10813
                    settings.onHidden.call($popup, element);
10814
                  }
10815
                })
10816
              ;
10817
            }
10818
            else {
10819
              module.error(error.noTransition);
10820
            }
10821
          }
10822
        },
10823
10824
        change: {
10825
          content: function(html) {
10826
            $popup.html(html);
10827
          }
10828
        },
10829
10830
        get: {
10831
          html: function() {
10832
            $module.removeData(metadata.html);
10833
            return $module.data(metadata.html) || settings.html;
10834
          },
10835
          title: function() {
10836
            $module.removeData(metadata.title);
10837
            return $module.data(metadata.title) || settings.title;
10838
          },
10839
          content: function() {
10840
            $module.removeData(metadata.content);
10841
            return $module.data(metadata.content) || $module.attr('title') || settings.content;
10842
          },
10843
          variation: function() {
10844
            $module.removeData(metadata.variation);
10845
            return $module.data(metadata.variation) || settings.variation;
10846
          },
10847
          popup: function() {
10848
            return $popup;
10849
          },
10850
          popupOffset: function() {
10851
            return $popup.offset();
10852
          },
10853
          calculations: function() {
10854
            var
10855
              targetElement    = $target[0],
10856
              isWindow         = ($boundary[0] == window),
10857
              targetPosition   = (settings.inline || (settings.popup && settings.movePopup))
10858
                ? $target.position()
10859
                : $target.offset(),
10860
              screenPosition = (isWindow)
10861
                ? { top: 0, left: 0 }
10862
                : $boundary.offset(),
10863
              calculations   = {},
10864
              scroll = (isWindow)
10865
                ? { top: $window.scrollTop(), left: $window.scrollLeft() }
10866
                : { top: 0, left: 0},
10867
              screen
10868
            ;
10869
            calculations = {
10870
              // element which is launching popup
10871
              target : {
10872
                element : $target[0],
10873
                width   : $target.outerWidth(),
10874
                height  : $target.outerHeight(),
10875
                top     : targetPosition.top,
10876
                left    : targetPosition.left,
10877
                margin  : {}
10878
              },
10879
              // popup itself
10880
              popup : {
10881
                width  : $popup.outerWidth(),
10882
                height : $popup.outerHeight()
10883
              },
10884
              // offset container (or 3d context)
10885
              parent : {
10886
                width  : $offsetParent.outerWidth(),
10887
                height : $offsetParent.outerHeight()
10888
              },
10889
              // screen boundaries
10890
              screen : {
10891
                top  : screenPosition.top,
10892
                left : screenPosition.left,
10893
                scroll: {
10894
                  top  : scroll.top,
10895
                  left : scroll.left
10896
                },
10897
                width  : $boundary.width(),
10898
                height : $boundary.height()
10899
              }
10900
            };
10901
10902
            // add in container calcs if fluid
10903
            if( settings.setFluidWidth && module.is.fluid() ) {
10904
              calculations.container = {
10905
                width: $popup.parent().outerWidth()
10906
              };
10907
              calculations.popup.width = calculations.container.width;
10908
            }
10909
10910
            // add in margins if inline
10911
            calculations.target.margin.top = (settings.inline)
10912
              ? parseInt( window.getComputedStyle(targetElement).getPropertyValue('margin-top'), 10)
10913
              : 0
10914
            ;
10915
            calculations.target.margin.left = (settings.inline)
10916
              ? module.is.rtl()
10917
                ? parseInt( window.getComputedStyle(targetElement).getPropertyValue('margin-right'), 10)
10918
                : parseInt( window.getComputedStyle(targetElement).getPropertyValue('margin-left'), 10)
10919
              : 0
10920
            ;
10921
            // calculate screen boundaries
10922
            screen = calculations.screen;
10923
            calculations.boundary = {
10924
              top    : screen.top + screen.scroll.top,
10925
              bottom : screen.top + screen.scroll.top + screen.height,
10926
              left   : screen.left + screen.scroll.left,
10927
              right  : screen.left + screen.scroll.left + screen.width
10928
            };
10929
            return calculations;
10930
          },
10931
          id: function() {
10932
            return id;
10933
          },
10934
          startEvent: function() {
10935
            if(settings.on == 'hover') {
10936
              return 'mouseenter';
10937
            }
10938
            else if(settings.on == 'focus') {
10939
              return 'focus';
10940
            }
10941
            return false;
10942
          },
10943
          scrollEvent: function() {
10944
            return 'scroll';
10945
          },
10946
          endEvent: function() {
10947
            if(settings.on == 'hover') {
10948
              return 'mouseleave';
10949
            }
10950
            else if(settings.on == 'focus') {
10951
              return 'blur';
10952
            }
10953
            return false;
10954
          },
10955
          distanceFromBoundary: function(offset, calculations) {
10956
            var
10957
              distanceFromBoundary = {},
10958
              popup,
10959
              boundary
10960
            ;
10961
            calculations = calculations || module.get.calculations();
10962
10963
            // shorthand
10964
            popup        = calculations.popup;
10965
            boundary     = calculations.boundary;
10966
10967
            if(offset) {
10968
              distanceFromBoundary = {
10969
                top    : (offset.top - boundary.top),
10970
                left   : (offset.left - boundary.left),
10971
                right  : (boundary.right - (offset.left + popup.width) ),
10972
                bottom : (boundary.bottom - (offset.top + popup.height) )
10973
              };
10974
              module.verbose('Distance from boundaries determined', offset, distanceFromBoundary);
10975
            }
10976
            return distanceFromBoundary;
10977
          },
10978
          offsetParent: function($target) {
10979
            var
10980
              element = ($target !== undefined)
10981
                ? $target[0]
10982
                : $module[0],
10983
              parentNode = element.parentNode,
10984
              $node    = $(parentNode)
10985
            ;
10986
            if(parentNode) {
10987
              var
10988
                is2D     = ($node.css('transform') === 'none'),
10989
                isStatic = ($node.css('position') === 'static'),
10990
                isHTML   = $node.is('html')
10991
              ;
10992
              while(parentNode && !isHTML && isStatic && is2D) {
10993
                parentNode = parentNode.parentNode;
10994
                $node    = $(parentNode);
10995
                is2D     = ($node.css('transform') === 'none');
10996
                isStatic = ($node.css('position') === 'static');
10997
                isHTML   = $node.is('html');
10998
              }
10999
            }
11000
            return ($node && $node.length > 0)
11001
              ? $node
11002
              : $()
11003
            ;
11004
          },
11005
          positions: function() {
11006
            return {
11007
              'top left'      : false,
11008
              'top center'    : false,
11009
              'top right'     : false,
11010
              'bottom left'   : false,
11011
              'bottom center' : false,
11012
              'bottom right'  : false,
11013
              'left center'   : false,
11014
              'right center'  : false
11015
            };
11016
          },
11017
          nextPosition: function(position) {
11018
            var
11019
              positions          = position.split(' '),
11020
              verticalPosition   = positions[0],
11021
              horizontalPosition = positions[1],
11022
              opposite = {
11023
                top    : 'bottom',
11024
                bottom : 'top',
11025
                left   : 'right',
11026
                right  : 'left'
11027
              },
11028
              adjacent = {
11029
                left   : 'center',
11030
                center : 'right',
11031
                right  : 'left'
11032
              },
11033
              backup = {
11034
                'top left'      : 'top center',
11035
                'top center'    : 'top right',
11036
                'top right'     : 'right center',
11037
                'right center'  : 'bottom right',
11038
                'bottom right'  : 'bottom center',
11039
                'bottom center' : 'bottom left',
11040
                'bottom left'   : 'left center',
11041
                'left center'   : 'top left'
11042
              },
11043
              adjacentsAvailable = (verticalPosition == 'top' || verticalPosition == 'bottom'),
11044
              oppositeTried = false,
11045
              adjacentTried = false,
11046
              nextPosition  = false
11047
            ;
11048
            if(!triedPositions) {
11049
              module.verbose('All available positions available');
11050
              triedPositions = module.get.positions();
11051
            }
11052
11053
            module.debug('Recording last position tried', position);
11054
            triedPositions[position] = true;
11055
11056
            if(settings.prefer === 'opposite') {
11057
              nextPosition  = [opposite[verticalPosition], horizontalPosition];
11058
              nextPosition  = nextPosition.join(' ');
11059
              oppositeTried = (triedPositions[nextPosition] === true);
11060
              module.debug('Trying opposite strategy', nextPosition);
11061
            }
11062
            if((settings.prefer === 'adjacent') && adjacentsAvailable ) {
11063
              nextPosition  = [verticalPosition, adjacent[horizontalPosition]];
11064
              nextPosition  = nextPosition.join(' ');
11065
              adjacentTried = (triedPositions[nextPosition] === true);
11066
              module.debug('Trying adjacent strategy', nextPosition);
11067
            }
11068
            if(adjacentTried || oppositeTried) {
11069
              module.debug('Using backup position', nextPosition);
11070
              nextPosition = backup[position];
11071
            }
11072
            return nextPosition;
11073
          }
11074
        },
11075
11076
        set: {
11077
          position: function(position, calculations) {
11078
11079
            // exit conditions
11080
            if($target.length === 0 || $popup.length === 0) {
11081
              module.error(error.notFound);
11082
              return;
11083
            }
11084
            var
11085
              offset,
11086
              distanceAway,
11087
              target,
11088
              popup,
11089
              parent,
11090
              positioning,
11091
              popupOffset,
11092
              distanceFromBoundary
11093
            ;
11094
11095
            calculations = calculations || module.get.calculations();
11096
            position     = position     || $module.data(metadata.position) || settings.position;
11097
11098
            offset       = $module.data(metadata.offset) || settings.offset;
11099
            distanceAway = settings.distanceAway;
11100
11101
            // shorthand
11102
            target = calculations.target;
11103
            popup  = calculations.popup;
11104
            parent = calculations.parent;
11105
11106
            if(target.width === 0 && target.height === 0 && !module.is.svg(target.element)) {
11107
              module.debug('Popup target is hidden, no action taken');
11108
              return false;
11109
            }
11110
11111
            if(settings.inline) {
11112
              module.debug('Adding margin to calculation', target.margin);
11113
              if(position == 'left center' || position == 'right center') {
11114
                offset       +=  target.margin.top;
11115
                distanceAway += -target.margin.left;
11116
              }
11117
              else if (position == 'top left' || position == 'top center' || position == 'top right') {
11118
                offset       += target.margin.left;
11119
                distanceAway -= target.margin.top;
11120
              }
11121
              else {
11122
                offset       += target.margin.left;
11123
                distanceAway += target.margin.top;
11124
              }
11125
            }
11126
11127
            module.debug('Determining popup position from calculations', position, calculations);
11128
11129
            if (module.is.rtl()) {
11130
              position = position.replace(/left|right/g, function (match) {
11131
                return (match == 'left')
11132
                  ? 'right'
11133
                  : 'left'
11134
                ;
11135
              });
11136
              module.debug('RTL: Popup position updated', position);
11137
            }
11138
11139
            // if last attempt use specified last resort position
11140
            if(searchDepth == settings.maxSearchDepth && typeof settings.lastResort === 'string') {
11141
              position = settings.lastResort;
11142
            }
11143
11144
            switch (position) {
11145
              case 'top left':
11146
                positioning = {
11147
                  top    : 'auto',
11148
                  bottom : parent.height - target.top + distanceAway,
11149
                  left   : target.left + offset,
11150
                  right  : 'auto'
11151
                };
11152
              break;
11153
              case 'top center':
11154
                positioning = {
11155
                  bottom : parent.height - target.top + distanceAway,
11156
                  left   : target.left + (target.width / 2) - (popup.width / 2) + offset,
11157
                  top    : 'auto',
11158
                  right  : 'auto'
11159
                };
11160
              break;
11161
              case 'top right':
11162
                positioning = {
11163
                  bottom :  parent.height - target.top + distanceAway,
11164
                  right  :  parent.width - target.left - target.width - offset,
11165
                  top    : 'auto',
11166
                  left   : 'auto'
11167
                };
11168
              break;
11169
              case 'left center':
11170
                positioning = {
11171
                  top    : target.top + (target.height / 2) - (popup.height / 2) + offset,
11172
                  right  : parent.width - target.left + distanceAway,
11173
                  left   : 'auto',
11174
                  bottom : 'auto'
11175
                };
11176
              break;
11177
              case 'right center':
11178
                positioning = {
11179
                  top    : target.top + (target.height / 2) - (popup.height / 2) + offset,
11180
                  left   : target.left + target.width + distanceAway,
11181
                  bottom : 'auto',
11182
                  right  : 'auto'
11183
                };
11184
              break;
11185
              case 'bottom left':
11186
                positioning = {
11187
                  top    : target.top + target.height + distanceAway,
11188
                  left   : target.left + offset,
11189
                  bottom : 'auto',
11190
                  right  : 'auto'
11191
                };
11192
              break;
11193
              case 'bottom center':
11194
                positioning = {
11195
                  top    : target.top + target.height + distanceAway,
11196
                  left   : target.left + (target.width / 2) - (popup.width / 2) + offset,
11197
                  bottom : 'auto',
11198
                  right  : 'auto'
11199
                };
11200
              break;
11201
              case 'bottom right':
11202
                positioning = {
11203
                  top    : target.top + target.height + distanceAway,
11204
                  right  : parent.width - target.left  - target.width - offset,
11205
                  left   : 'auto',
11206
                  bottom : 'auto'
11207
                };
11208
              break;
11209
            }
11210
            if(positioning === undefined) {
11211
              module.error(error.invalidPosition, position);
11212
            }
11213
11214
            module.debug('Calculated popup positioning values', positioning);
11215
11216
            // tentatively place on stage
11217
            $popup
11218
              .css(positioning)
11219
              .removeClass(className.position)
11220
              .addClass(position)
11221
              .addClass(className.loading)
11222
            ;
11223
11224
            popupOffset = module.get.popupOffset();
11225
11226
            // see if any boundaries are surpassed with this tentative position
11227
            distanceFromBoundary = module.get.distanceFromBoundary(popupOffset, calculations);
11228
11229
            if( module.is.offstage(distanceFromBoundary, position) ) {
11230
              module.debug('Position is outside viewport', position);
11231
              if(searchDepth < settings.maxSearchDepth) {
11232
                searchDepth++;
11233
                position = module.get.nextPosition(position);
11234
                module.debug('Trying new position', position);
11235
                return ($popup)
11236
                  ? module.set.position(position, calculations)
11237
                  : false
11238
                ;
11239
              }
11240
              else {
11241
                if(settings.lastResort) {
11242
                  module.debug('No position found, showing with last position');
11243
                }
11244
                else {
11245
                  module.debug('Popup could not find a position to display', $popup);
11246
                  module.error(error.cannotPlace, element);
11247
                  module.remove.attempts();
11248
                  module.remove.loading();
11249
                  module.reset();
11250
                  settings.onUnplaceable.call($popup, element);
11251
                  return false;
11252
                }
11253
              }
11254
            }
11255
            module.debug('Position is on stage', position);
11256
            module.remove.attempts();
11257
            module.remove.loading();
11258
            if( settings.setFluidWidth && module.is.fluid() ) {
11259
              module.set.fluidWidth(calculations);
11260
            }
11261
            return true;
11262
          },
11263
11264
          fluidWidth: function(calculations) {
11265
            calculations = calculations || module.get.calculations();
11266
            module.debug('Automatically setting element width to parent width', calculations.parent.width);
11267
            $popup.css('width', calculations.container.width);
11268
          },
11269
11270
          variation: function(variation) {
11271
            variation = variation || module.get.variation();
11272
            if(variation && module.has.popup() ) {
11273
              module.verbose('Adding variation to popup', variation);
11274
              $popup.addClass(variation);
11275
            }
11276
          },
11277
11278
          visible: function() {
11279
            $module.addClass(className.visible);
11280
          }
11281
        },
11282
11283
        remove: {
11284
          loading: function() {
11285
            $popup.removeClass(className.loading);
11286
          },
11287
          variation: function(variation) {
11288
            variation = variation || module.get.variation();
11289
            if(variation) {
11290
              module.verbose('Removing variation', variation);
11291
              $popup.removeClass(variation);
11292
            }
11293
          },
11294
          visible: function() {
11295
            $module.removeClass(className.visible);
11296
          },
11297
          attempts: function() {
11298
            module.verbose('Resetting all searched positions');
11299
            searchDepth    = 0;
11300
            triedPositions = false;
11301
          }
11302
        },
11303
11304
        bind: {
11305
          events: function() {
11306
            module.debug('Binding popup events to module');
11307
            if(settings.on == 'click') {
11308
              $module
11309
                .on('click' + eventNamespace, module.toggle)
11310
              ;
11311
            }
11312
            if(settings.on == 'hover' && hasTouch) {
11313
              $module
11314
                .on('touchstart' + eventNamespace, module.event.touchstart)
11315
              ;
11316
            }
11317
            if( module.get.startEvent() ) {
11318
              $module
11319
                .on(module.get.startEvent() + eventNamespace, module.event.start)
11320
                .on(module.get.endEvent() + eventNamespace, module.event.end)
11321
              ;
11322
            }
11323
            if(settings.target) {
11324
              module.debug('Target set to element', $target);
11325
            }
11326
            $window.on('resize' + elementNamespace, module.event.resize);
11327
          },
11328
          popup: function() {
11329
            module.verbose('Allowing hover events on popup to prevent closing');
11330
            if( $popup && module.has.popup() ) {
11331
              $popup
11332
                .on('mouseenter' + eventNamespace, module.event.start)
11333
                .on('mouseleave' + eventNamespace, module.event.end)
11334
              ;
11335
            }
11336
          },
11337
          close: function() {
11338
            if(settings.hideOnScroll === true || (settings.hideOnScroll == 'auto' && settings.on != 'click')) {
11339
              module.bind.closeOnScroll();
11340
            }
11341
            if(settings.on == 'hover' && openedWithTouch) {
11342
              module.bind.touchClose();
11343
            }
11344
            if(settings.on == 'click' && settings.closable) {
11345
              module.bind.clickaway();
11346
            }
11347
          },
11348
          closeOnScroll: function() {
11349
            module.verbose('Binding scroll close event to document');
11350
            $scrollContext
11351
              .one(module.get.scrollEvent() + elementNamespace, module.event.hideGracefully)
11352
            ;
11353
          },
11354
          touchClose: function() {
11355
            module.verbose('Binding popup touchclose event to document');
11356
            $document
11357
              .on('touchstart' + elementNamespace, function(event) {
11358
                module.verbose('Touched away from popup');
11359
                module.event.hideGracefully.call(element, event);
11360
              })
11361
            ;
11362
          },
11363
          clickaway: function() {
11364
            module.verbose('Binding popup close event to document');
11365
            $document
11366
              .on('click' + elementNamespace, function(event) {
11367
                module.verbose('Clicked away from popup');
11368
                module.event.hideGracefully.call(element, event);
11369
              })
11370
            ;
11371
          }
11372
        },
11373
11374
        unbind: {
11375
          events: function() {
11376
            $window
11377
              .off(elementNamespace)
11378
            ;
11379
            $module
11380
              .off(eventNamespace)
11381
            ;
11382
          },
11383
          close: function() {
11384
            $document
11385
              .off(elementNamespace)
11386
            ;
11387
            $scrollContext
11388
              .off(elementNamespace)
11389
            ;
11390
          },
11391
        },
11392
11393
        has: {
11394
          popup: function() {
11395
            return ($popup && $popup.length > 0);
11396
          }
11397
        },
11398
11399
        is: {
11400
          offstage: function(distanceFromBoundary, position) {
11401
            var
11402
              offstage = []
11403
            ;
11404
            // return boundaries that have been surpassed
11405
            $.each(distanceFromBoundary, function(direction, distance) {
11406
              if(distance < -settings.jitter) {
11407
                module.debug('Position exceeds allowable distance from edge', direction, distance, position);
11408
                offstage.push(direction);
11409
              }
11410
            });
11411
            if(offstage.length > 0) {
11412
              return true;
11413
            }
11414
            else {
11415
              return false;
11416
            }
11417
          },
11418
          svg: function(element) {
11419
            return module.supports.svg() && (element instanceof SVGGraphicsElement);
11420
          },
11421
          active: function() {
11422
            return $module.hasClass(className.active);
11423
          },
11424
          animating: function() {
11425
            return ($popup !== undefined && $popup.hasClass(className.animating) );
11426
          },
11427
          fluid: function() {
11428
            return ($popup !== undefined && $popup.hasClass(className.fluid));
11429
          },
11430
          visible: function() {
11431
            return ($popup !== undefined && $popup.hasClass(className.popupVisible));
11432
          },
11433
          dropdown: function() {
11434
            return $module.hasClass(className.dropdown);
11435
          },
11436
          hidden: function() {
11437
            return !module.is.visible();
11438
          },
11439
          rtl: function () {
11440
            return $module.css('direction') == 'rtl';
11441
          }
11442
        },
11443
11444
        reset: function() {
11445
          module.remove.visible();
11446
          if(settings.preserve) {
11447
            if($.fn.transition !== undefined) {
11448
              $popup
11449
                .transition('remove transition')
11450
              ;
11451
            }
11452
          }
11453
          else {
11454
            module.removePopup();
11455
          }
11456
        },
11457
11458
        setting: function(name, value) {
11459
          if( $.isPlainObject(name) ) {
11460
            $.extend(true, settings, name);
11461
          }
11462
          else if(value !== undefined) {
11463
            settings[name] = value;
11464
          }
11465
          else {
11466
            return settings[name];
11467
          }
11468
        },
11469
        internal: function(name, value) {
11470
          if( $.isPlainObject(name) ) {
11471
            $.extend(true, module, name);
11472
          }
11473
          else if(value !== undefined) {
11474
            module[name] = value;
11475
          }
11476
          else {
11477
            return module[name];
11478
          }
11479
        },
11480
        debug: function() {
11481
          if(!settings.silent && settings.debug) {
11482
            if(settings.performance) {
11483
              module.performance.log(arguments);
11484
            }
11485
            else {
11486
              module.debug = Function.prototype.bind.call(console.info, console, settings.name + ':');
11487
              module.debug.apply(console, arguments);
11488
            }
11489
          }
11490
        },
11491
        verbose: function() {
11492
          if(!settings.silent && settings.verbose && settings.debug) {
11493
            if(settings.performance) {
11494
              module.performance.log(arguments);
11495
            }
11496
            else {
11497
              module.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':');
11498
              module.verbose.apply(console, arguments);
11499
            }
11500
          }
11501
        },
11502
        error: function() {
11503
          if(!settings.silent) {
11504
            module.error = Function.prototype.bind.call(console.error, console, settings.name + ':');
11505
            module.error.apply(console, arguments);
11506
          }
11507
        },
11508
        performance: {
11509
          log: function(message) {
11510
            var
11511
              currentTime,
11512
              executionTime,
11513
              previousTime
11514
            ;
11515
            if(settings.performance) {
11516
              currentTime   = new Date().getTime();
11517
              previousTime  = time || currentTime;
11518
              executionTime = currentTime - previousTime;
11519
              time          = currentTime;
11520
              performance.push({
11521
                'Name'           : message[0],
11522
                'Arguments'      : [].slice.call(message, 1) || '',
11523
                'Element'        : element,
11524
                'Execution Time' : executionTime
11525
              });
11526
            }
11527
            clearTimeout(module.performance.timer);
11528
            module.performance.timer = setTimeout(module.performance.display, 500);
11529
          },
11530
          display: function() {
11531
            var
11532
              title = settings.name + ':',
11533
              totalTime = 0
11534
            ;
11535
            time = false;
11536
            clearTimeout(module.performance.timer);
11537
            $.each(performance, function(index, data) {
11538
              totalTime += data['Execution Time'];
11539
            });
11540
            title += ' ' + totalTime + 'ms';
11541
            if(moduleSelector) {
11542
              title += ' \'' + moduleSelector + '\'';
11543
            }
11544
            if( (console.group !== undefined || console.table !== undefined) && performance.length > 0) {
11545
              console.groupCollapsed(title);
11546
              if(console.table) {
11547
                console.table(performance);
11548
              }
11549
              else {
11550
                $.each(performance, function(index, data) {
11551
                  console.log(data['Name'] + ': ' + data['Execution Time']+'ms');
11552
                });
11553
              }
11554
              console.groupEnd();
11555
            }
11556
            performance = [];
11557
          }
11558
        },
11559
        invoke: function(query, passedArguments, context) {
11560
          var
11561
            object = instance,
11562
            maxDepth,
11563
            found,
11564
            response
11565
          ;
11566
          passedArguments = passedArguments || queryArguments;
11567
          context         = element         || context;
11568
          if(typeof query == 'string' && object !== undefined) {
11569
            query    = query.split(/[\. ]/);
11570
            maxDepth = query.length - 1;
11571
            $.each(query, function(depth, value) {
11572
              var camelCaseValue = (depth != maxDepth)
11573
                ? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1)
11574
                : query
11575
              ;
11576
              if( $.isPlainObject( object[camelCaseValue] ) && (depth != maxDepth) ) {
11577
                object = object[camelCaseValue];
11578
              }
11579
              else if( object[camelCaseValue] !== undefined ) {
11580
                found = object[camelCaseValue];
11581
                return false;
11582
              }
11583
              else if( $.isPlainObject( object[value] ) && (depth != maxDepth) ) {
11584
                object = object[value];
11585
              }
11586
              else if( object[value] !== undefined ) {
11587
                found = object[value];
11588
                return false;
11589
              }
11590
              else {
11591
                return false;
11592
              }
11593
            });
11594
          }
11595
          if ( $.isFunction( found ) ) {
11596
            response = found.apply(context, passedArguments);
11597
          }
11598
          else if(found !== undefined) {
11599
            response = found;
11600
          }
11601
          if($.isArray(returnedValue)) {
11602
            returnedValue.push(response);
11603
          }
11604
          else if(returnedValue !== undefined) {
11605
            returnedValue = [returnedValue, response];
11606
          }
11607
          else if(response !== undefined) {
11608
            returnedValue = response;
11609
          }
11610
          return found;
11611
        }
11612
      };
11613
11614
      if(methodInvoked) {
11615
        if(instance === undefined) {
11616
          module.initialize();
11617
        }
11618
        module.invoke(query);
11619
      }
11620
      else {
11621
        if(instance !== undefined) {
11622
          instance.invoke('destroy');
11623
        }
11624
        module.initialize();
11625
      }
11626
    })
11627
  ;
11628
11629
  return (returnedValue !== undefined)
11630
    ? returnedValue
11631
    : this
11632
  ;
11633
};
11634
11635
$.fn.popup.settings = {
11636
11637
  name           : 'Popup',
11638
11639
  // module settings
11640
  silent         : false,
11641
  debug          : false,
11642
  verbose        : false,
11643
  performance    : true,
11644
  namespace      : 'popup',
11645
11646
  // whether it should use dom mutation observers
11647
  observeChanges : true,
11648
11649
  // callback only when element added to dom
11650
  onCreate       : function(){},
11651
11652
  // callback before element removed from dom
11653
  onRemove       : function(){},
11654
11655
  // callback before show animation
11656
  onShow         : function(){},
11657
11658
  // callback after show animation
11659
  onVisible      : function(){},
11660
11661
  // callback before hide animation
11662
  onHide         : function(){},
11663
11664
  // callback when popup cannot be positioned in visible screen
11665
  onUnplaceable  : function(){},
11666
11667
  // callback after hide animation
11668
  onHidden       : function(){},
11669
11670
  // when to show popup
11671
  on             : 'hover',
11672
11673
  // element to use to determine if popup is out of boundary
11674
  boundary       : window,
11675
11676
  // whether to add touchstart events when using hover
11677
  addTouchEvents : true,
11678
11679
  // default position relative to element
11680
  position       : 'top left',
11681
11682
  // name of variation to use
11683
  variation      : '',
11684
11685
  // whether popup should be moved to context
11686
  movePopup      : true,
11687
11688
  // element which popup should be relative to
11689
  target         : false,
11690
11691
  // jq selector or element that should be used as popup
11692
  popup          : false,
11693
11694
  // popup should remain inline next to activator
11695
  inline         : false,
11696
11697
  // popup should be removed from page on hide
11698
  preserve       : false,
11699
11700
  // popup should not close when being hovered on
11701
  hoverable      : false,
11702
11703
  // explicitly set content
11704
  content        : false,
11705
11706
  // explicitly set html
11707
  html           : false,
11708
11709
  // explicitly set title
11710
  title          : false,
11711
11712
  // whether automatically close on clickaway when on click
11713
  closable       : true,
11714
11715
  // automatically hide on scroll
11716
  hideOnScroll   : 'auto',
11717
11718
  // hide other popups on show
11719
  exclusive      : false,
11720
11721
  // context to attach popups
11722
  context        : 'body',
11723
11724
  // context for binding scroll events
11725
  scrollContext  : window,
11726
11727
  // position to prefer when calculating new position
11728
  prefer         : 'opposite',
11729
11730
  // specify position to appear even if it doesn't fit
11731
  lastResort     : false,
11732
11733
  // delay used to prevent accidental refiring of animations due to user error
11734
  delay        : {
11735
    show : 50,
11736
    hide : 70
11737
  },
11738
11739
  // whether fluid variation should assign width explicitly
11740
  setFluidWidth  : true,
11741
11742
  // transition settings
11743
  duration       : 200,
11744
  transition     : 'scale',
11745
11746
  // distance away from activating element in px
11747
  distanceAway   : 0,
11748
11749
  // number of pixels an element is allowed to be "offstage" for a position to be chosen (allows for rounding)
11750
  jitter         : 2,
11751
11752
  // offset on aligning axis from calculated position
11753
  offset         : 0,
11754
11755
  // maximum times to look for a position before failing (9 positions total)
11756
  maxSearchDepth : 15,
11757
11758
  error: {
11759
    invalidPosition : 'The position you specified is not a valid position',
11760
    cannotPlace     : 'Popup does not fit within the boundaries of the viewport',
11761
    method          : 'The method you called is not defined.',
11762
    noTransition    : 'This module requires ui transitions <https://github.com/Semantic-Org/UI-Transition>',
11763
    notFound        : 'The target or popup you specified does not exist on the page'
11764
  },
11765
11766
  metadata: {
11767
    activator : 'activator',
11768
    content   : 'content',
11769
    html      : 'html',
11770
    offset    : 'offset',
11771
    position  : 'position',
11772
    title     : 'title',
11773
    variation : 'variation'
11774
  },
11775
11776
  className   : {
11777
    active       : 'active',
11778
    animating    : 'animating',
11779
    dropdown     : 'dropdown',
11780
    fluid        : 'fluid',
11781
    loading      : 'loading',
11782
    popup        : 'ui popup',
11783
    position     : 'top left center bottom right',
11784
    visible      : 'visible',
11785
    popupVisible : 'visible'
11786
  },
11787
11788
  selector    : {
11789
    popup    : '.ui.popup'
11790
  },
11791
11792
  templates: {
11793
    escape: function(string) {
11794
      var
11795
        badChars     = /[&<>"'`]/g,
11796
        shouldEscape = /[&<>"'`]/,
11797
        escape       = {
11798
          "&": "&amp;",
11799
          "<": "&lt;",
11800
          ">": "&gt;",
11801
          '"': "&quot;",
11802
          "'": "&#x27;",
11803
          "`": "&#x60;"
11804
        },
11805
        escapedChar  = function(chr) {
11806
          return escape[chr];
11807
        }
11808
      ;
11809
      if(shouldEscape.test(string)) {
11810
        return string.replace(badChars, escapedChar);
11811
      }
11812
      return string;
11813
    },
11814
    popup: function(text) {
11815
      var
11816
        html   = '',
11817
        escape = $.fn.popup.settings.templates.escape
11818
      ;
11819
      if(typeof text !== undefined) {
11820
        if(typeof text.title !== undefined && text.title) {
11821
          text.title = escape(text.title);
11822
          html += '<div class="header">' + text.title + '</div>';
11823
        }
11824
        if(typeof text.content !== undefined && text.content) {
11825
          text.content = escape(text.content);
11826
          html += '<div class="content">' + text.content + '</div>';
11827
        }
11828
      }
11829
      return html;
11830
    }
11831
  }
11832
11833
};
11834
11835
11836
})( jQuery, window, document );
11837
11838
/*!
11839
 * # Semantic UI 2.2.11 - Progress

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

@@ 11-1486 (lines=1476) @@
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.popup = function(parameters) {
23
  var
24
    $allModules    = $(this),
25
    $document      = $(document),
26
    $window        = $(window),
27
    $body          = $('body'),
28
29
    moduleSelector = $allModules.selector || '',
30
31
    hasTouch       = (true),
32
    time           = new Date().getTime(),
33
    performance    = [],
34
35
    query          = arguments[0],
36
    methodInvoked  = (typeof query == 'string'),
37
    queryArguments = [].slice.call(arguments, 1),
38
39
    returnedValue
40
  ;
41
  $allModules
42
    .each(function() {
43
      var
44
        settings        = ( $.isPlainObject(parameters) )
45
          ? $.extend(true, {}, $.fn.popup.settings, parameters)
46
          : $.extend({}, $.fn.popup.settings),
47
48
        selector           = settings.selector,
49
        className          = settings.className,
50
        error              = settings.error,
51
        metadata           = settings.metadata,
52
        namespace          = settings.namespace,
53
54
        eventNamespace     = '.' + settings.namespace,
55
        moduleNamespace    = 'module-' + namespace,
56
57
        $module            = $(this),
58
        $context           = $(settings.context),
59
        $scrollContext     = $(settings.scrollContext),
60
        $boundary          = $(settings.boundary),
61
        $target            = (settings.target)
62
          ? $(settings.target)
63
          : $module,
64
65
        $popup,
66
        $offsetParent,
67
68
        searchDepth        = 0,
69
        triedPositions     = false,
70
        openedWithTouch    = false,
71
72
        element            = this,
73
        instance           = $module.data(moduleNamespace),
74
75
        documentObserver,
76
        elementNamespace,
77
        id,
78
        module
79
      ;
80
81
      module = {
82
83
        // binds events
84
        initialize: function() {
85
          module.debug('Initializing', $module);
86
          module.createID();
87
          module.bind.events();
88
          if(!module.exists() && settings.preserve) {
89
            module.create();
90
          }
91
          if(settings.observeChanges) {
92
            module.observeChanges();
93
          }
94
          module.instantiate();
95
        },
96
97
        instantiate: function() {
98
          module.verbose('Storing instance', module);
99
          instance = module;
100
          $module
101
            .data(moduleNamespace, instance)
102
          ;
103
        },
104
105
        observeChanges: function() {
106
          if('MutationObserver' in window) {
107
            documentObserver = new MutationObserver(module.event.documentChanged);
108
            documentObserver.observe(document, {
109
              childList : true,
110
              subtree   : true
111
            });
112
            module.debug('Setting up mutation observer', documentObserver);
113
          }
114
        },
115
116
        refresh: function() {
117
          if(settings.popup) {
118
            $popup = $(settings.popup).eq(0);
119
          }
120
          else {
121
            if(settings.inline) {
122
              $popup = $target.nextAll(selector.popup).eq(0);
123
              settings.popup = $popup;
124
            }
125
          }
126
          if(settings.popup) {
127
            $popup.addClass(className.loading);
128
            $offsetParent = module.get.offsetParent($target);
129
            $popup.removeClass(className.loading);
130
            if(settings.movePopup && module.has.popup() && module.get.offsetParent($popup)[0] !== $offsetParent[0]) {
131
              module.debug('Moving popup to the same offset parent as target');
132
              $popup
133
                .detach()
134
                .appendTo($offsetParent)
135
              ;
136
            }
137
          }
138
          else {
139
            $offsetParent = (settings.inline)
140
              ? module.get.offsetParent($target)
141
              : module.has.popup()
142
                ? module.get.offsetParent($target)
143
                : $body
144
            ;
145
          }
146
          if( $offsetParent.is('html') && $offsetParent[0] !== $body[0] ) {
147
            module.debug('Setting page as offset parent');
148
            $offsetParent = $body;
149
          }
150
          if( module.get.variation() ) {
151
            module.set.variation();
152
          }
153
        },
154
155
        reposition: function() {
156
          module.refresh();
157
          module.set.position();
158
        },
159
160
        destroy: function() {
161
          module.debug('Destroying previous module');
162
          if(documentObserver) {
163
            documentObserver.disconnect();
164
          }
165
          // remove element only if was created dynamically
166
          if($popup && !settings.preserve) {
167
            module.removePopup();
168
          }
169
          // clear all timeouts
170
          clearTimeout(module.hideTimer);
171
          clearTimeout(module.showTimer);
172
          // remove events
173
          module.unbind.close();
174
          module.unbind.events();
175
          $module
176
            .removeData(moduleNamespace)
177
          ;
178
        },
179
180
        event: {
181
          start:  function(event) {
182
            var
183
              delay = ($.isPlainObject(settings.delay))
184
                ? settings.delay.show
185
                : settings.delay
186
            ;
187
            clearTimeout(module.hideTimer);
188
            if(!openedWithTouch) {
189
              module.showTimer = setTimeout(module.show, delay);
190
            }
191
          },
192
          end:  function() {
193
            var
194
              delay = ($.isPlainObject(settings.delay))
195
                ? settings.delay.hide
196
                : settings.delay
197
            ;
198
            clearTimeout(module.showTimer);
199
            module.hideTimer = setTimeout(module.hide, delay);
200
          },
201
          touchstart: function(event) {
202
            openedWithTouch = true;
203
            module.show();
204
          },
205
          resize: function() {
206
            if( module.is.visible() ) {
207
              module.set.position();
208
            }
209
          },
210
          documentChanged: function(mutations) {
211
            [].forEach.call(mutations, function(mutation) {
212
              if(mutation.removedNodes) {
213
                [].forEach.call(mutation.removedNodes, function(node) {
214
                  if(node == element || $(node).find(element).length > 0) {
215
                    module.debug('Element removed from DOM, tearing down events');
216
                    module.destroy();
217
                  }
218
                });
219
              }
220
            });
221
          },
222
          hideGracefully: function(event) {
223
            var
224
              $target = $(event.target),
225
              isInDOM = $.contains(document.documentElement, event.target),
226
              inPopup = ($target.closest(selector.popup).length > 0)
227
            ;
228
            // don't close on clicks inside popup
229
            if(event && !inPopup && isInDOM) {
230
              module.debug('Click occurred outside popup hiding popup');
231
              module.hide();
232
            }
233
            else {
234
              module.debug('Click was inside popup, keeping popup open');
235
            }
236
          }
237
        },
238
239
        // generates popup html from metadata
240
        create: function() {
241
          var
242
            html      = module.get.html(),
243
            title     = module.get.title(),
244
            content   = module.get.content()
245
          ;
246
247
          if(html || content || title) {
248
            module.debug('Creating pop-up html');
249
            if(!html) {
250
              html = settings.templates.popup({
251
                title   : title,
252
                content : content
253
              });
254
            }
255
            $popup = $('<div/>')
256
              .addClass(className.popup)
257
              .data(metadata.activator, $module)
258
              .html(html)
259
            ;
260
            if(settings.inline) {
261
              module.verbose('Inserting popup element inline', $popup);
262
              $popup
263
                .insertAfter($module)
264
              ;
265
            }
266
            else {
267
              module.verbose('Appending popup element to body', $popup);
268
              $popup
269
                .appendTo( $context )
270
              ;
271
            }
272
            module.refresh();
273
            module.set.variation();
274
275
            if(settings.hoverable) {
276
              module.bind.popup();
277
            }
278
            settings.onCreate.call($popup, element);
279
          }
280
          else if($target.next(selector.popup).length !== 0) {
281
            module.verbose('Pre-existing popup found');
282
            settings.inline = true;
283
            settings.popup  = $target.next(selector.popup).data(metadata.activator, $module);
284
            module.refresh();
285
            if(settings.hoverable) {
286
              module.bind.popup();
287
            }
288
          }
289
          else if(settings.popup) {
290
            $(settings.popup).data(metadata.activator, $module);
291
            module.verbose('Used popup specified in settings');
292
            module.refresh();
293
            if(settings.hoverable) {
294
              module.bind.popup();
295
            }
296
          }
297
          else {
298
            module.debug('No content specified skipping display', element);
299
          }
300
        },
301
302
        createID: function() {
303
          id = (Math.random().toString(16) + '000000000').substr(2, 8);
304
          elementNamespace = '.' + id;
305
          module.verbose('Creating unique id for element', id);
306
        },
307
308
        // determines popup state
309
        toggle: function() {
310
          module.debug('Toggling pop-up');
311
          if( module.is.hidden() ) {
312
            module.debug('Popup is hidden, showing pop-up');
313
            module.unbind.close();
314
            module.show();
315
          }
316
          else {
317
            module.debug('Popup is visible, hiding pop-up');
318
            module.hide();
319
          }
320
        },
321
322
        show: function(callback) {
323
          callback = callback || function(){};
324
          module.debug('Showing pop-up', settings.transition);
325
          if(module.is.hidden() && !( module.is.active() && module.is.dropdown()) ) {
326
            if( !module.exists() ) {
327
              module.create();
328
            }
329
            if(settings.onShow.call($popup, element) === false) {
330
              module.debug('onShow callback returned false, cancelling popup animation');
331
              return;
332
            }
333
            else if(!settings.preserve && !settings.popup) {
334
              module.refresh();
335
            }
336
            if( $popup && module.set.position() ) {
337
              module.save.conditions();
338
              if(settings.exclusive) {
339
                module.hideAll();
340
              }
341
              module.animate.show(callback);
342
            }
343
          }
344
        },
345
346
347
        hide: function(callback) {
348
          callback = callback || function(){};
349
          if( module.is.visible() || module.is.animating() ) {
350
            if(settings.onHide.call($popup, element) === false) {
351
              module.debug('onHide callback returned false, cancelling popup animation');
352
              return;
353
            }
354
            module.remove.visible();
355
            module.unbind.close();
356
            module.restore.conditions();
357
            module.animate.hide(callback);
358
          }
359
        },
360
361
        hideAll: function() {
362
          $(selector.popup)
363
            .filter('.' + className.popupVisible)
364
            .each(function() {
365
              $(this)
366
                .data(metadata.activator)
367
                  .popup('hide')
368
              ;
369
            })
370
          ;
371
        },
372
        exists: function() {
373
          if(!$popup) {
374
            return false;
375
          }
376
          if(settings.inline || settings.popup) {
377
            return ( module.has.popup() );
378
          }
379
          else {
380
            return ( $popup.closest($context).length >= 1 )
381
              ? true
382
              : false
383
            ;
384
          }
385
        },
386
387
        removePopup: function() {
388
          if( module.has.popup() && !settings.popup) {
389
            module.debug('Removing popup', $popup);
390
            $popup.remove();
391
            $popup = undefined;
392
            settings.onRemove.call($popup, element);
393
          }
394
        },
395
396
        save: {
397
          conditions: function() {
398
            module.cache = {
399
              title: $module.attr('title')
400
            };
401
            if (module.cache.title) {
402
              $module.removeAttr('title');
403
            }
404
            module.verbose('Saving original attributes', module.cache.title);
405
          }
406
        },
407
        restore: {
408
          conditions: function() {
409
            if(module.cache && module.cache.title) {
410
              $module.attr('title', module.cache.title);
411
              module.verbose('Restoring original attributes', module.cache.title);
412
            }
413
            return true;
414
          }
415
        },
416
        supports: {
417
          svg: function() {
418
            return (typeof SVGGraphicsElement === 'undefined');
419
          }
420
        },
421
        animate: {
422
          show: function(callback) {
423
            callback = $.isFunction(callback) ? callback : function(){};
424
            if(settings.transition && $.fn.transition !== undefined && $module.transition('is supported')) {
425
              module.set.visible();
426
              $popup
427
                .transition({
428
                  animation  : settings.transition + ' in',
429
                  queue      : false,
430
                  debug      : settings.debug,
431
                  verbose    : settings.verbose,
432
                  duration   : settings.duration,
433
                  onComplete : function() {
434
                    module.bind.close();
435
                    callback.call($popup, element);
436
                    settings.onVisible.call($popup, element);
437
                  }
438
                })
439
              ;
440
            }
441
            else {
442
              module.error(error.noTransition);
443
            }
444
          },
445
          hide: function(callback) {
446
            callback = $.isFunction(callback) ? callback : function(){};
447
            module.debug('Hiding pop-up');
448
            if(settings.onHide.call($popup, element) === false) {
449
              module.debug('onHide callback returned false, cancelling popup animation');
450
              return;
451
            }
452
            if(settings.transition && $.fn.transition !== undefined && $module.transition('is supported')) {
453
              $popup
454
                .transition({
455
                  animation  : settings.transition + ' out',
456
                  queue      : false,
457
                  duration   : settings.duration,
458
                  debug      : settings.debug,
459
                  verbose    : settings.verbose,
460
                  onComplete : function() {
461
                    module.reset();
462
                    callback.call($popup, element);
463
                    settings.onHidden.call($popup, element);
464
                  }
465
                })
466
              ;
467
            }
468
            else {
469
              module.error(error.noTransition);
470
            }
471
          }
472
        },
473
474
        change: {
475
          content: function(html) {
476
            $popup.html(html);
477
          }
478
        },
479
480
        get: {
481
          html: function() {
482
            $module.removeData(metadata.html);
483
            return $module.data(metadata.html) || settings.html;
484
          },
485
          title: function() {
486
            $module.removeData(metadata.title);
487
            return $module.data(metadata.title) || settings.title;
488
          },
489
          content: function() {
490
            $module.removeData(metadata.content);
491
            return $module.data(metadata.content) || $module.attr('title') || settings.content;
492
          },
493
          variation: function() {
494
            $module.removeData(metadata.variation);
495
            return $module.data(metadata.variation) || settings.variation;
496
          },
497
          popup: function() {
498
            return $popup;
499
          },
500
          popupOffset: function() {
501
            return $popup.offset();
502
          },
503
          calculations: function() {
504
            var
505
              targetElement    = $target[0],
506
              isWindow         = ($boundary[0] == window),
507
              targetPosition   = (settings.inline || (settings.popup && settings.movePopup))
508
                ? $target.position()
509
                : $target.offset(),
510
              screenPosition = (isWindow)
511
                ? { top: 0, left: 0 }
512
                : $boundary.offset(),
513
              calculations   = {},
514
              scroll = (isWindow)
515
                ? { top: $window.scrollTop(), left: $window.scrollLeft() }
516
                : { top: 0, left: 0},
517
              screen
518
            ;
519
            calculations = {
520
              // element which is launching popup
521
              target : {
522
                element : $target[0],
523
                width   : $target.outerWidth(),
524
                height  : $target.outerHeight(),
525
                top     : targetPosition.top,
526
                left    : targetPosition.left,
527
                margin  : {}
528
              },
529
              // popup itself
530
              popup : {
531
                width  : $popup.outerWidth(),
532
                height : $popup.outerHeight()
533
              },
534
              // offset container (or 3d context)
535
              parent : {
536
                width  : $offsetParent.outerWidth(),
537
                height : $offsetParent.outerHeight()
538
              },
539
              // screen boundaries
540
              screen : {
541
                top  : screenPosition.top,
542
                left : screenPosition.left,
543
                scroll: {
544
                  top  : scroll.top,
545
                  left : scroll.left
546
                },
547
                width  : $boundary.width(),
548
                height : $boundary.height()
549
              }
550
            };
551
552
            // add in container calcs if fluid
553
            if( settings.setFluidWidth && module.is.fluid() ) {
554
              calculations.container = {
555
                width: $popup.parent().outerWidth()
556
              };
557
              calculations.popup.width = calculations.container.width;
558
            }
559
560
            // add in margins if inline
561
            calculations.target.margin.top = (settings.inline)
562
              ? parseInt( window.getComputedStyle(targetElement).getPropertyValue('margin-top'), 10)
563
              : 0
564
            ;
565
            calculations.target.margin.left = (settings.inline)
566
              ? module.is.rtl()
567
                ? parseInt( window.getComputedStyle(targetElement).getPropertyValue('margin-right'), 10)
568
                : parseInt( window.getComputedStyle(targetElement).getPropertyValue('margin-left'), 10)
569
              : 0
570
            ;
571
            // calculate screen boundaries
572
            screen = calculations.screen;
573
            calculations.boundary = {
574
              top    : screen.top + screen.scroll.top,
575
              bottom : screen.top + screen.scroll.top + screen.height,
576
              left   : screen.left + screen.scroll.left,
577
              right  : screen.left + screen.scroll.left + screen.width
578
            };
579
            return calculations;
580
          },
581
          id: function() {
582
            return id;
583
          },
584
          startEvent: function() {
585
            if(settings.on == 'hover') {
586
              return 'mouseenter';
587
            }
588
            else if(settings.on == 'focus') {
589
              return 'focus';
590
            }
591
            return false;
592
          },
593
          scrollEvent: function() {
594
            return 'scroll';
595
          },
596
          endEvent: function() {
597
            if(settings.on == 'hover') {
598
              return 'mouseleave';
599
            }
600
            else if(settings.on == 'focus') {
601
              return 'blur';
602
            }
603
            return false;
604
          },
605
          distanceFromBoundary: function(offset, calculations) {
606
            var
607
              distanceFromBoundary = {},
608
              popup,
609
              boundary
610
            ;
611
            calculations = calculations || module.get.calculations();
612
613
            // shorthand
614
            popup        = calculations.popup;
615
            boundary     = calculations.boundary;
616
617
            if(offset) {
618
              distanceFromBoundary = {
619
                top    : (offset.top - boundary.top),
620
                left   : (offset.left - boundary.left),
621
                right  : (boundary.right - (offset.left + popup.width) ),
622
                bottom : (boundary.bottom - (offset.top + popup.height) )
623
              };
624
              module.verbose('Distance from boundaries determined', offset, distanceFromBoundary);
625
            }
626
            return distanceFromBoundary;
627
          },
628
          offsetParent: function($target) {
629
            var
630
              element = ($target !== undefined)
631
                ? $target[0]
632
                : $module[0],
633
              parentNode = element.parentNode,
634
              $node    = $(parentNode)
635
            ;
636
            if(parentNode) {
637
              var
638
                is2D     = ($node.css('transform') === 'none'),
639
                isStatic = ($node.css('position') === 'static'),
640
                isHTML   = $node.is('html')
641
              ;
642
              while(parentNode && !isHTML && isStatic && is2D) {
643
                parentNode = parentNode.parentNode;
644
                $node    = $(parentNode);
645
                is2D     = ($node.css('transform') === 'none');
646
                isStatic = ($node.css('position') === 'static');
647
                isHTML   = $node.is('html');
648
              }
649
            }
650
            return ($node && $node.length > 0)
651
              ? $node
652
              : $()
653
            ;
654
          },
655
          positions: function() {
656
            return {
657
              'top left'      : false,
658
              'top center'    : false,
659
              'top right'     : false,
660
              'bottom left'   : false,
661
              'bottom center' : false,
662
              'bottom right'  : false,
663
              'left center'   : false,
664
              'right center'  : false
665
            };
666
          },
667
          nextPosition: function(position) {
668
            var
669
              positions          = position.split(' '),
670
              verticalPosition   = positions[0],
671
              horizontalPosition = positions[1],
672
              opposite = {
673
                top    : 'bottom',
674
                bottom : 'top',
675
                left   : 'right',
676
                right  : 'left'
677
              },
678
              adjacent = {
679
                left   : 'center',
680
                center : 'right',
681
                right  : 'left'
682
              },
683
              backup = {
684
                'top left'      : 'top center',
685
                'top center'    : 'top right',
686
                'top right'     : 'right center',
687
                'right center'  : 'bottom right',
688
                'bottom right'  : 'bottom center',
689
                'bottom center' : 'bottom left',
690
                'bottom left'   : 'left center',
691
                'left center'   : 'top left'
692
              },
693
              adjacentsAvailable = (verticalPosition == 'top' || verticalPosition == 'bottom'),
694
              oppositeTried = false,
695
              adjacentTried = false,
696
              nextPosition  = false
697
            ;
698
            if(!triedPositions) {
699
              module.verbose('All available positions available');
700
              triedPositions = module.get.positions();
701
            }
702
703
            module.debug('Recording last position tried', position);
704
            triedPositions[position] = true;
705
706
            if(settings.prefer === 'opposite') {
707
              nextPosition  = [opposite[verticalPosition], horizontalPosition];
708
              nextPosition  = nextPosition.join(' ');
709
              oppositeTried = (triedPositions[nextPosition] === true);
710
              module.debug('Trying opposite strategy', nextPosition);
711
            }
712
            if((settings.prefer === 'adjacent') && adjacentsAvailable ) {
713
              nextPosition  = [verticalPosition, adjacent[horizontalPosition]];
714
              nextPosition  = nextPosition.join(' ');
715
              adjacentTried = (triedPositions[nextPosition] === true);
716
              module.debug('Trying adjacent strategy', nextPosition);
717
            }
718
            if(adjacentTried || oppositeTried) {
719
              module.debug('Using backup position', nextPosition);
720
              nextPosition = backup[position];
721
            }
722
            return nextPosition;
723
          }
724
        },
725
726
        set: {
727
          position: function(position, calculations) {
728
729
            // exit conditions
730
            if($target.length === 0 || $popup.length === 0) {
731
              module.error(error.notFound);
732
              return;
733
            }
734
            var
735
              offset,
736
              distanceAway,
737
              target,
738
              popup,
739
              parent,
740
              positioning,
741
              popupOffset,
742
              distanceFromBoundary
743
            ;
744
745
            calculations = calculations || module.get.calculations();
746
            position     = position     || $module.data(metadata.position) || settings.position;
747
748
            offset       = $module.data(metadata.offset) || settings.offset;
749
            distanceAway = settings.distanceAway;
750
751
            // shorthand
752
            target = calculations.target;
753
            popup  = calculations.popup;
754
            parent = calculations.parent;
755
756
            if(target.width === 0 && target.height === 0 && !module.is.svg(target.element)) {
757
              module.debug('Popup target is hidden, no action taken');
758
              return false;
759
            }
760
761
            if(settings.inline) {
762
              module.debug('Adding margin to calculation', target.margin);
763
              if(position == 'left center' || position == 'right center') {
764
                offset       +=  target.margin.top;
765
                distanceAway += -target.margin.left;
766
              }
767
              else if (position == 'top left' || position == 'top center' || position == 'top right') {
768
                offset       += target.margin.left;
769
                distanceAway -= target.margin.top;
770
              }
771
              else {
772
                offset       += target.margin.left;
773
                distanceAway += target.margin.top;
774
              }
775
            }
776
777
            module.debug('Determining popup position from calculations', position, calculations);
778
779
            if (module.is.rtl()) {
780
              position = position.replace(/left|right/g, function (match) {
781
                return (match == 'left')
782
                  ? 'right'
783
                  : 'left'
784
                ;
785
              });
786
              module.debug('RTL: Popup position updated', position);
787
            }
788
789
            // if last attempt use specified last resort position
790
            if(searchDepth == settings.maxSearchDepth && typeof settings.lastResort === 'string') {
791
              position = settings.lastResort;
792
            }
793
794
            switch (position) {
795
              case 'top left':
796
                positioning = {
797
                  top    : 'auto',
798
                  bottom : parent.height - target.top + distanceAway,
799
                  left   : target.left + offset,
800
                  right  : 'auto'
801
                };
802
              break;
803
              case 'top center':
804
                positioning = {
805
                  bottom : parent.height - target.top + distanceAway,
806
                  left   : target.left + (target.width / 2) - (popup.width / 2) + offset,
807
                  top    : 'auto',
808
                  right  : 'auto'
809
                };
810
              break;
811
              case 'top right':
812
                positioning = {
813
                  bottom :  parent.height - target.top + distanceAway,
814
                  right  :  parent.width - target.left - target.width - offset,
815
                  top    : 'auto',
816
                  left   : 'auto'
817
                };
818
              break;
819
              case 'left center':
820
                positioning = {
821
                  top    : target.top + (target.height / 2) - (popup.height / 2) + offset,
822
                  right  : parent.width - target.left + distanceAway,
823
                  left   : 'auto',
824
                  bottom : 'auto'
825
                };
826
              break;
827
              case 'right center':
828
                positioning = {
829
                  top    : target.top + (target.height / 2) - (popup.height / 2) + offset,
830
                  left   : target.left + target.width + distanceAway,
831
                  bottom : 'auto',
832
                  right  : 'auto'
833
                };
834
              break;
835
              case 'bottom left':
836
                positioning = {
837
                  top    : target.top + target.height + distanceAway,
838
                  left   : target.left + offset,
839
                  bottom : 'auto',
840
                  right  : 'auto'
841
                };
842
              break;
843
              case 'bottom center':
844
                positioning = {
845
                  top    : target.top + target.height + distanceAway,
846
                  left   : target.left + (target.width / 2) - (popup.width / 2) + offset,
847
                  bottom : 'auto',
848
                  right  : 'auto'
849
                };
850
              break;
851
              case 'bottom right':
852
                positioning = {
853
                  top    : target.top + target.height + distanceAway,
854
                  right  : parent.width - target.left  - target.width - offset,
855
                  left   : 'auto',
856
                  bottom : 'auto'
857
                };
858
              break;
859
            }
860
            if(positioning === undefined) {
861
              module.error(error.invalidPosition, position);
862
            }
863
864
            module.debug('Calculated popup positioning values', positioning);
865
866
            // tentatively place on stage
867
            $popup
868
              .css(positioning)
869
              .removeClass(className.position)
870
              .addClass(position)
871
              .addClass(className.loading)
872
            ;
873
874
            popupOffset = module.get.popupOffset();
875
876
            // see if any boundaries are surpassed with this tentative position
877
            distanceFromBoundary = module.get.distanceFromBoundary(popupOffset, calculations);
878
879
            if( module.is.offstage(distanceFromBoundary, position) ) {
880
              module.debug('Position is outside viewport', position);
881
              if(searchDepth < settings.maxSearchDepth) {
882
                searchDepth++;
883
                position = module.get.nextPosition(position);
884
                module.debug('Trying new position', position);
885
                return ($popup)
886
                  ? module.set.position(position, calculations)
887
                  : false
888
                ;
889
              }
890
              else {
891
                if(settings.lastResort) {
892
                  module.debug('No position found, showing with last position');
893
                }
894
                else {
895
                  module.debug('Popup could not find a position to display', $popup);
896
                  module.error(error.cannotPlace, element);
897
                  module.remove.attempts();
898
                  module.remove.loading();
899
                  module.reset();
900
                  settings.onUnplaceable.call($popup, element);
901
                  return false;
902
                }
903
              }
904
            }
905
            module.debug('Position is on stage', position);
906
            module.remove.attempts();
907
            module.remove.loading();
908
            if( settings.setFluidWidth && module.is.fluid() ) {
909
              module.set.fluidWidth(calculations);
910
            }
911
            return true;
912
          },
913
914
          fluidWidth: function(calculations) {
915
            calculations = calculations || module.get.calculations();
916
            module.debug('Automatically setting element width to parent width', calculations.parent.width);
917
            $popup.css('width', calculations.container.width);
918
          },
919
920
          variation: function(variation) {
921
            variation = variation || module.get.variation();
922
            if(variation && module.has.popup() ) {
923
              module.verbose('Adding variation to popup', variation);
924
              $popup.addClass(variation);
925
            }
926
          },
927
928
          visible: function() {
929
            $module.addClass(className.visible);
930
          }
931
        },
932
933
        remove: {
934
          loading: function() {
935
            $popup.removeClass(className.loading);
936
          },
937
          variation: function(variation) {
938
            variation = variation || module.get.variation();
939
            if(variation) {
940
              module.verbose('Removing variation', variation);
941
              $popup.removeClass(variation);
942
            }
943
          },
944
          visible: function() {
945
            $module.removeClass(className.visible);
946
          },
947
          attempts: function() {
948
            module.verbose('Resetting all searched positions');
949
            searchDepth    = 0;
950
            triedPositions = false;
951
          }
952
        },
953
954
        bind: {
955
          events: function() {
956
            module.debug('Binding popup events to module');
957
            if(settings.on == 'click') {
958
              $module
959
                .on('click' + eventNamespace, module.toggle)
960
              ;
961
            }
962
            if(settings.on == 'hover' && hasTouch) {
963
              $module
964
                .on('touchstart' + eventNamespace, module.event.touchstart)
965
              ;
966
            }
967
            if( module.get.startEvent() ) {
968
              $module
969
                .on(module.get.startEvent() + eventNamespace, module.event.start)
970
                .on(module.get.endEvent() + eventNamespace, module.event.end)
971
              ;
972
            }
973
            if(settings.target) {
974
              module.debug('Target set to element', $target);
975
            }
976
            $window.on('resize' + elementNamespace, module.event.resize);
977
          },
978
          popup: function() {
979
            module.verbose('Allowing hover events on popup to prevent closing');
980
            if( $popup && module.has.popup() ) {
981
              $popup
982
                .on('mouseenter' + eventNamespace, module.event.start)
983
                .on('mouseleave' + eventNamespace, module.event.end)
984
              ;
985
            }
986
          },
987
          close: function() {
988
            if(settings.hideOnScroll === true || (settings.hideOnScroll == 'auto' && settings.on != 'click')) {
989
              module.bind.closeOnScroll();
990
            }
991
            if(settings.on == 'hover' && openedWithTouch) {
992
              module.bind.touchClose();
993
            }
994
            if(settings.on == 'click' && settings.closable) {
995
              module.bind.clickaway();
996
            }
997
          },
998
          closeOnScroll: function() {
999
            module.verbose('Binding scroll close event to document');
1000
            $scrollContext
1001
              .one(module.get.scrollEvent() + elementNamespace, module.event.hideGracefully)
1002
            ;
1003
          },
1004
          touchClose: function() {
1005
            module.verbose('Binding popup touchclose event to document');
1006
            $document
1007
              .on('touchstart' + elementNamespace, function(event) {
1008
                module.verbose('Touched away from popup');
1009
                module.event.hideGracefully.call(element, event);
1010
              })
1011
            ;
1012
          },
1013
          clickaway: function() {
1014
            module.verbose('Binding popup close event to document');
1015
            $document
1016
              .on('click' + elementNamespace, function(event) {
1017
                module.verbose('Clicked away from popup');
1018
                module.event.hideGracefully.call(element, event);
1019
              })
1020
            ;
1021
          }
1022
        },
1023
1024
        unbind: {
1025
          events: function() {
1026
            $window
1027
              .off(elementNamespace)
1028
            ;
1029
            $module
1030
              .off(eventNamespace)
1031
            ;
1032
          },
1033
          close: function() {
1034
            $document
1035
              .off(elementNamespace)
1036
            ;
1037
            $scrollContext
1038
              .off(elementNamespace)
1039
            ;
1040
          },
1041
        },
1042
1043
        has: {
1044
          popup: function() {
1045
            return ($popup && $popup.length > 0);
1046
          }
1047
        },
1048
1049
        is: {
1050
          offstage: function(distanceFromBoundary, position) {
1051
            var
1052
              offstage = []
1053
            ;
1054
            // return boundaries that have been surpassed
1055
            $.each(distanceFromBoundary, function(direction, distance) {
1056
              if(distance < -settings.jitter) {
1057
                module.debug('Position exceeds allowable distance from edge', direction, distance, position);
1058
                offstage.push(direction);
1059
              }
1060
            });
1061
            if(offstage.length > 0) {
1062
              return true;
1063
            }
1064
            else {
1065
              return false;
1066
            }
1067
          },
1068
          svg: function(element) {
1069
            return module.supports.svg() && (element instanceof SVGGraphicsElement);
1070
          },
1071
          active: function() {
1072
            return $module.hasClass(className.active);
1073
          },
1074
          animating: function() {
1075
            return ($popup !== undefined && $popup.hasClass(className.animating) );
1076
          },
1077
          fluid: function() {
1078
            return ($popup !== undefined && $popup.hasClass(className.fluid));
1079
          },
1080
          visible: function() {
1081
            return ($popup !== undefined && $popup.hasClass(className.popupVisible));
1082
          },
1083
          dropdown: function() {
1084
            return $module.hasClass(className.dropdown);
1085
          },
1086
          hidden: function() {
1087
            return !module.is.visible();
1088
          },
1089
          rtl: function () {
1090
            return $module.css('direction') == 'rtl';
1091
          }
1092
        },
1093
1094
        reset: function() {
1095
          module.remove.visible();
1096
          if(settings.preserve) {
1097
            if($.fn.transition !== undefined) {
1098
              $popup
1099
                .transition('remove transition')
1100
              ;
1101
            }
1102
          }
1103
          else {
1104
            module.removePopup();
1105
          }
1106
        },
1107
1108
        setting: function(name, value) {
1109
          if( $.isPlainObject(name) ) {
1110
            $.extend(true, settings, name);
1111
          }
1112
          else if(value !== undefined) {
1113
            settings[name] = value;
1114
          }
1115
          else {
1116
            return settings[name];
1117
          }
1118
        },
1119
        internal: function(name, value) {
1120
          if( $.isPlainObject(name) ) {
1121
            $.extend(true, module, name);
1122
          }
1123
          else if(value !== undefined) {
1124
            module[name] = value;
1125
          }
1126
          else {
1127
            return module[name];
1128
          }
1129
        },
1130
        debug: function() {
1131
          if(!settings.silent && settings.debug) {
1132
            if(settings.performance) {
1133
              module.performance.log(arguments);
1134
            }
1135
            else {
1136
              module.debug = Function.prototype.bind.call(console.info, console, settings.name + ':');
1137
              module.debug.apply(console, arguments);
1138
            }
1139
          }
1140
        },
1141
        verbose: function() {
1142
          if(!settings.silent && settings.verbose && settings.debug) {
1143
            if(settings.performance) {
1144
              module.performance.log(arguments);
1145
            }
1146
            else {
1147
              module.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':');
1148
              module.verbose.apply(console, arguments);
1149
            }
1150
          }
1151
        },
1152
        error: function() {
1153
          if(!settings.silent) {
1154
            module.error = Function.prototype.bind.call(console.error, console, settings.name + ':');
1155
            module.error.apply(console, arguments);
1156
          }
1157
        },
1158
        performance: {
1159
          log: function(message) {
1160
            var
1161
              currentTime,
1162
              executionTime,
1163
              previousTime
1164
            ;
1165
            if(settings.performance) {
1166
              currentTime   = new Date().getTime();
1167
              previousTime  = time || currentTime;
1168
              executionTime = currentTime - previousTime;
1169
              time          = currentTime;
1170
              performance.push({
1171
                'Name'           : message[0],
1172
                'Arguments'      : [].slice.call(message, 1) || '',
1173
                'Element'        : element,
1174
                'Execution Time' : executionTime
1175
              });
1176
            }
1177
            clearTimeout(module.performance.timer);
1178
            module.performance.timer = setTimeout(module.performance.display, 500);
1179
          },
1180
          display: function() {
1181
            var
1182
              title = settings.name + ':',
1183
              totalTime = 0
1184
            ;
1185
            time = false;
1186
            clearTimeout(module.performance.timer);
1187
            $.each(performance, function(index, data) {
1188
              totalTime += data['Execution Time'];
1189
            });
1190
            title += ' ' + totalTime + 'ms';
1191
            if(moduleSelector) {
1192
              title += ' \'' + moduleSelector + '\'';
1193
            }
1194
            if( (console.group !== undefined || console.table !== undefined) && performance.length > 0) {
1195
              console.groupCollapsed(title);
1196
              if(console.table) {
1197
                console.table(performance);
1198
              }
1199
              else {
1200
                $.each(performance, function(index, data) {
1201
                  console.log(data['Name'] + ': ' + data['Execution Time']+'ms');
1202
                });
1203
              }
1204
              console.groupEnd();
1205
            }
1206
            performance = [];
1207
          }
1208
        },
1209
        invoke: function(query, passedArguments, context) {
1210
          var
1211
            object = instance,
1212
            maxDepth,
1213
            found,
1214
            response
1215
          ;
1216
          passedArguments = passedArguments || queryArguments;
1217
          context         = element         || context;
1218
          if(typeof query == 'string' && object !== undefined) {
1219
            query    = query.split(/[\. ]/);
1220
            maxDepth = query.length - 1;
1221
            $.each(query, function(depth, value) {
1222
              var camelCaseValue = (depth != maxDepth)
1223
                ? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1)
1224
                : query
1225
              ;
1226
              if( $.isPlainObject( object[camelCaseValue] ) && (depth != maxDepth) ) {
1227
                object = object[camelCaseValue];
1228
              }
1229
              else if( object[camelCaseValue] !== undefined ) {
1230
                found = object[camelCaseValue];
1231
                return false;
1232
              }
1233
              else if( $.isPlainObject( object[value] ) && (depth != maxDepth) ) {
1234
                object = object[value];
1235
              }
1236
              else if( object[value] !== undefined ) {
1237
                found = object[value];
1238
                return false;
1239
              }
1240
              else {
1241
                return false;
1242
              }
1243
            });
1244
          }
1245
          if ( $.isFunction( found ) ) {
1246
            response = found.apply(context, passedArguments);
1247
          }
1248
          else if(found !== undefined) {
1249
            response = found;
1250
          }
1251
          if($.isArray(returnedValue)) {
1252
            returnedValue.push(response);
1253
          }
1254
          else if(returnedValue !== undefined) {
1255
            returnedValue = [returnedValue, response];
1256
          }
1257
          else if(response !== undefined) {
1258
            returnedValue = response;
1259
          }
1260
          return found;
1261
        }
1262
      };
1263
1264
      if(methodInvoked) {
1265
        if(instance === undefined) {
1266
          module.initialize();
1267
        }
1268
        module.invoke(query);
1269
      }
1270
      else {
1271
        if(instance !== undefined) {
1272
          instance.invoke('destroy');
1273
        }
1274
        module.initialize();
1275
      }
1276
    })
1277
  ;
1278
1279
  return (returnedValue !== undefined)
1280
    ? returnedValue
1281
    : this
1282
  ;
1283
};
1284
1285
$.fn.popup.settings = {
1286
1287
  name           : 'Popup',
1288
1289
  // module settings
1290
  silent         : false,
1291
  debug          : false,
1292
  verbose        : false,
1293
  performance    : true,
1294
  namespace      : 'popup',
1295
1296
  // whether it should use dom mutation observers
1297
  observeChanges : true,
1298
1299
  // callback only when element added to dom
1300
  onCreate       : function(){},
1301
1302
  // callback before element removed from dom
1303
  onRemove       : function(){},
1304
1305
  // callback before show animation
1306
  onShow         : function(){},
1307
1308
  // callback after show animation
1309
  onVisible      : function(){},
1310
1311
  // callback before hide animation
1312
  onHide         : function(){},
1313
1314
  // callback when popup cannot be positioned in visible screen
1315
  onUnplaceable  : function(){},
1316
1317
  // callback after hide animation
1318
  onHidden       : function(){},
1319
1320
  // when to show popup
1321
  on             : 'hover',
1322
1323
  // element to use to determine if popup is out of boundary
1324
  boundary       : window,
1325
1326
  // whether to add touchstart events when using hover
1327
  addTouchEvents : true,
1328
1329
  // default position relative to element
1330
  position       : 'top left',
1331
1332
  // name of variation to use
1333
  variation      : '',
1334
1335
  // whether popup should be moved to context
1336
  movePopup      : true,
1337
1338
  // element which popup should be relative to
1339
  target         : false,
1340
1341
  // jq selector or element that should be used as popup
1342
  popup          : false,
1343
1344
  // popup should remain inline next to activator
1345
  inline         : false,
1346
1347
  // popup should be removed from page on hide
1348
  preserve       : false,
1349
1350
  // popup should not close when being hovered on
1351
  hoverable      : false,
1352
1353
  // explicitly set content
1354
  content        : false,
1355
1356
  // explicitly set html
1357
  html           : false,
1358
1359
  // explicitly set title
1360
  title          : false,
1361
1362
  // whether automatically close on clickaway when on click
1363
  closable       : true,
1364
1365
  // automatically hide on scroll
1366
  hideOnScroll   : 'auto',
1367
1368
  // hide other popups on show
1369
  exclusive      : false,
1370
1371
  // context to attach popups
1372
  context        : 'body',
1373
1374
  // context for binding scroll events
1375
  scrollContext  : window,
1376
1377
  // position to prefer when calculating new position
1378
  prefer         : 'opposite',
1379
1380
  // specify position to appear even if it doesn't fit
1381
  lastResort     : false,
1382
1383
  // delay used to prevent accidental refiring of animations due to user error
1384
  delay        : {
1385
    show : 50,
1386
    hide : 70
1387
  },
1388
1389
  // whether fluid variation should assign width explicitly
1390
  setFluidWidth  : true,
1391
1392
  // transition settings
1393
  duration       : 200,
1394
  transition     : 'scale',
1395
1396
  // distance away from activating element in px
1397
  distanceAway   : 0,
1398
1399
  // number of pixels an element is allowed to be "offstage" for a position to be chosen (allows for rounding)
1400
  jitter         : 2,
1401
1402
  // offset on aligning axis from calculated position
1403
  offset         : 0,
1404
1405
  // maximum times to look for a position before failing (9 positions total)
1406
  maxSearchDepth : 15,
1407
1408
  error: {
1409
    invalidPosition : 'The position you specified is not a valid position',
1410
    cannotPlace     : 'Popup does not fit within the boundaries of the viewport',
1411
    method          : 'The method you called is not defined.',
1412
    noTransition    : 'This module requires ui transitions <https://github.com/Semantic-Org/UI-Transition>',
1413
    notFound        : 'The target or popup you specified does not exist on the page'
1414
  },
1415
1416
  metadata: {
1417
    activator : 'activator',
1418
    content   : 'content',
1419
    html      : 'html',
1420
    offset    : 'offset',
1421
    position  : 'position',
1422
    title     : 'title',
1423
    variation : 'variation'
1424
  },
1425
1426
  className   : {
1427
    active       : 'active',
1428
    animating    : 'animating',
1429
    dropdown     : 'dropdown',
1430
    fluid        : 'fluid',
1431
    loading      : 'loading',
1432
    popup        : 'ui popup',
1433
    position     : 'top left center bottom right',
1434
    visible      : 'visible',
1435
    popupVisible : 'visible'
1436
  },
1437
1438
  selector    : {
1439
    popup    : '.ui.popup'
1440
  },
1441
1442
  templates: {
1443
    escape: function(string) {
1444
      var
1445
        badChars     = /[&<>"'`]/g,
1446
        shouldEscape = /[&<>"'`]/,
1447
        escape       = {
1448
          "&": "&amp;",
1449
          "<": "&lt;",
1450
          ">": "&gt;",
1451
          '"': "&quot;",
1452
          "'": "&#x27;",
1453
          "`": "&#x60;"
1454
        },
1455
        escapedChar  = function(chr) {
1456
          return escape[chr];
1457
        }
1458
      ;
1459
      if(shouldEscape.test(string)) {
1460
        return string.replace(badChars, escapedChar);
1461
      }
1462
      return string;
1463
    },
1464
    popup: function(text) {
1465
      var
1466
        html   = '',
1467
        escape = $.fn.popup.settings.templates.escape
1468
      ;
1469
      if(typeof text !== undefined) {
1470
        if(typeof text.title !== undefined && text.title) {
1471
          text.title = escape(text.title);
1472
          html += '<div class="header">' + text.title + '</div>';
1473
        }
1474
        if(typeof text.content !== undefined && text.content) {
1475
          text.content = escape(text.content);
1476
          html += '<div class="content">' + text.content + '</div>';
1477
        }
1478
      }
1479
      return html;
1480
    }
1481
  }
1482
1483
};
1484
1485
1486
})( jQuery, window, document );
1487