public/lib/semantic/components/popup.js   F
last analyzed

Complexity

Total Complexity 302
Complexity/F 2.88

Size

Lines of Code 1476
Function Count 105

Duplication

Duplicated Lines 1476
Ratio 100 %

Importance

Changes 0
Metric Value
cc 0
eloc 976
nc 0
dl 1476
loc 1476
rs 1.624
c 0
b 0
f 0
wmc 302
mnd 5
bc 235
fnc 105
bpm 2.238
cpm 2.8761
noi 22

10 Functions

Rating   Name   Duplication   Size   Complexity  
A $.fn.popup.settings.onCreate 1 1 1
A $.fn.popup.settings.onVisible 1 1 1
A $.fn.popup.settings.onHidden 1 1 1
A $.fn.popup.settings.onUnplaceable 1 1 1
A $.fn.popup.settings.onShow 1 1 1
A $.fn.popup.settings.onRemove 1 1 1
B $.fn.popup 1262 1262 2
A $.fn.popup.settings.onHide 1 1 1
B $.fn.popup.settings.templates.popup 17 17 6
A $.fn.popup.settings.templates.escape 21 21 2

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complexity

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like public/lib/semantic/components/popup.js often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
/*!
2
 * # Semantic UI 2.2.11 - Popup
3
 * http://github.com/semantic-org/semantic-ui/
4
 *
5
 *
6
 * Released under the MIT license
7
 * http://opensource.org/licenses/MIT
8
 *
9
 */
10
11 View Code Duplication
;(function ($, window, document, undefined) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
12
13
"use strict";
14
15
window = (typeof window != 'undefined' && window.Math == Math)
16
  ? window
17
  : (typeof self != 'undefined' && self.Math == Math)
0 ignored issues
show
Bug introduced by
The variable self seems to be never declared. If this is a global, consider adding a /** global: self */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
18
    ? self
19
    : Function('return this')()
0 ignored issues
show
Performance Best Practice introduced by
Using new Function() to create a function is slow and difficult to debug. Such functions do not create a closure. Consider using another way to define your function.
Loading history...
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);
0 ignored issues
show
Bug introduced by
The variable MutationObserver seems to be never declared. If this is a global, consider adding a /** global: MutationObserver */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
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);
0 ignored issues
show
Bug introduced by
The variable $popup does not seem to be initialized in case settings.inline on line 121 is false. Are you sure this can never be the case?
Loading history...
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) {
0 ignored issues
show
Unused Code introduced by
The parameter event is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
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) {
0 ignored issues
show
Unused Code introduced by
The parameter event is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
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 {
0 ignored issues
show
Comprehensibility introduced by
else is not necessary here since all if branches return, consider removing it to reduce nesting and make code more readable.
Loading history...
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');
0 ignored issues
show
Bug introduced by
The variable SVGGraphicsElement seems to be never declared. If this is a global, consider adding a /** global: SVGGraphicsElement */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
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   = {},
0 ignored issues
show
Unused Code introduced by
The assignment to variable calculations seems to be never used. Consider removing it.
Loading history...
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;
0 ignored issues
show
Comprehensibility Best Practice introduced by
Are you sure this return statement is not missing an argument? If this is intended, consider adding an explicit undefined like return undefined;.
Loading history...
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) {
0 ignored issues
show
Bug introduced by
The variable positioning seems to not be initialized for all possible execution paths.
Loading history...
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 {
0 ignored issues
show
Comprehensibility introduced by
else is not necessary here since all if branches return, consider removing it to reduce nesting and make code more readable.
Loading history...
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 {
0 ignored issues
show
Comprehensibility introduced by
else is not necessary here since all if branches return, consider removing it to reduce nesting and make code more readable.
Loading history...
1065
              return false;
1066
            }
1067
          },
1068
          svg: function(element) {
1069
            return module.supports.svg() && (element instanceof SVGGraphicsElement);
0 ignored issues
show
Bug introduced by
The variable SVGGraphicsElement seems to be never declared. If this is a global, consider adding a /** global: SVGGraphicsElement */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
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);
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
1111
          }
1112
          else if(value !== undefined) {
1113
            settings[name] = value;
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
1114
          }
1115
          else {
1116
            return settings[name];
1117
          }
1118
        },
1119
        internal: function(name, value) {
1120
          if( $.isPlainObject(name) ) {
1121
            $.extend(true, module, name);
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
1122
          }
1123
          else if(value !== undefined) {
1124
            module[name] = value;
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
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');
0 ignored issues
show
Debugging Code introduced by
console.log looks like debug code. Are you sure you do not want to remove it?
Loading history...
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];
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
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];
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
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);
0 ignored issues
show
Bug introduced by
The variable response does not seem to be initialized in case found !== undefined on line 1248 is false. Are you sure the function push handles undefined variables?
Loading history...
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