3
* http://github.com/jlukic/semantic-ui/
6
* Copyright 2014 Contributors
7
* Released under the MIT license
8
* http://opensource.org/licenses/MIT
12
;(function ($, window, document, undefined) {
14
$.fn.popup = function(parameters) {
16
$allModules = $(this),
17
$document = $(document),
19
moduleSelector = $allModules.selector || '',
21
time = new Date().getTime(),
25
methodInvoked = (typeof query == 'string'),
26
queryArguments = [].slice.call(arguments, 1),
33
settings = ( $.isPlainObject(parameters) )
34
? $.extend(true, {}, $.fn.popup.settings, parameters)
35
: $.extend({}, $.fn.popup.settings),
37
selector = settings.selector,
38
className = settings.className,
39
error = settings.error,
40
metadata = settings.metadata,
41
namespace = settings.namespace,
43
eventNamespace = '.' + settings.namespace,
44
moduleNamespace = 'module-' + namespace,
47
$context = $(settings.context),
48
$target = (settings.target)
54
$offsetParent = (settings.inline)
55
? $target.offsetParent()
57
$popup = (settings.inline)
58
? $target.next(settings.selector.popup)
59
: $window.children(settings.selector.popup).last(),
64
instance = $module.data(moduleNamespace),
71
initialize: function() {
72
module.debug('Initializing module', $module);
73
if(settings.on == 'click') {
75
.on('click', module.toggle)
80
.on(module.get.startEvent() + eventNamespace, module.event.start)
81
.on(module.get.endEvent() + eventNamespace, module.event.end)
85
module.debug('Target set to element', $target);
88
.on('resize' + eventNamespace, module.event.resize)
93
instantiate: function() {
94
module.verbose('Storing instance of module', module);
97
.data(moduleNamespace, instance)
101
refresh: function() {
102
if(settings.inline) {
103
$popup = $target.next(selector.popup);
104
$offsetParent = $target.offsetParent();
107
$popup = $window.children(selector.popup).last();
111
destroy: function() {
112
module.debug('Destroying previous module');
121
.removeData(moduleNamespace)
126
start: function(event) {
127
module.timer = setTimeout(function() {
128
if( module.is.hidden() ) {
134
clearTimeout(module.timer);
135
if( module.is.visible() ) {
140
if( module.is.visible() ) {
141
module.set.position();
146
// generates popup html from metadata
148
module.debug('Creating pop-up html');
150
html = $module.data(metadata.html) || settings.html,
151
variation = $module.data(metadata.variation) || settings.variation,
152
title = $module.data(metadata.title) || settings.title,
153
content = $module.data(metadata.content) || $module.attr('title') || settings.content
155
if(html || content || title) {
157
html = settings.template({
163
.addClass(className.popup)
167
if(settings.inline) {
168
module.verbose('Inserting popup element inline', $popup);
170
.data(moduleNamespace, instance)
171
.insertAfter($module)
175
module.verbose('Appending popup element to body', $popup);
177
.data(moduleNamespace, instance)
178
.appendTo( $context )
181
$.proxy(settings.onCreate, $popup)();
184
module.error(error.content);
188
// determines popup state
190
module.debug('Toggling pop-up');
191
if( module.is.hidden() ) {
192
module.debug('Popup is hidden, showing pop-up');
193
module.unbind.close();
198
module.debug('Popup is visible, hiding pop-up');
203
show: function(callback) {
204
callback = callback || function(){};
205
module.debug('Showing pop-up', settings.transition);
206
if(!settings.preserve) {
209
if( !module.exists() ) {
212
module.save.conditions();
213
module.set.position();
214
module.animate.show(callback);
218
hide: function(callback) {
219
callback = callback || function(){};
221
.removeClass(className.visible)
223
module.restore.conditions();
224
module.unbind.close();
225
if( module.is.visible() ) {
226
module.animate.hide(callback);
230
hideAll: function() {
237
hideGracefully: function(event) {
238
// don't close on clicks inside popup
239
if(event && $(event.target).closest(selector.popup).size() === 0) {
240
module.debug('Click occurred outside popup hiding popup');
244
module.debug('Click was inside popup, keeping popup open');
249
if(settings.inline) {
250
return ( $popup.size() !== 0 );
253
return ( $popup.parent($context).size() );
258
module.debug('Removing popup');
262
$.proxy(settings.onRemove, $popup)();
266
conditions: function() {
268
title: $module.attr('title')
270
if (module.cache.title) {
271
$module.removeAttr('title');
273
module.verbose('Saving original attributes', module.cache.title);
277
conditions: function() {
278
if(module.cache && module.cache.title) {
279
$module.attr('title', module.cache.title);
280
module.verbose('Restoring original attributes', module.cache.title);
286
show: function(callback) {
287
callback = callback || function(){};
289
.addClass(className.visible)
291
if(settings.transition && $.fn.transition !== undefined && $module.transition('is supported')) {
293
.transition(settings.transition + ' in', settings.duration, function() {
295
$.proxy(callback, element)();
302
.fadeIn(settings.duration, settings.easing, function() {
304
$.proxy(callback, element)();
308
$.proxy(settings.onShow, element)();
310
hide: function(callback) {
311
callback = callback || function(){};
312
module.debug('Hiding pop-up');
313
if(settings.transition && $.fn.transition !== undefined && $module.transition('is supported')) {
315
.transition(settings.transition + ' out', settings.duration, function() {
324
.fadeOut(settings.duration, settings.easing, function() {
330
$.proxy(settings.onHide, element)();
335
startEvent: function() {
336
if(settings.on == 'hover') {
339
else if(settings.on == 'focus') {
343
endEvent: function() {
344
if(settings.on == 'hover') {
347
else if(settings.on == 'focus') {
351
offstagePosition: function() {
354
top : $(window).scrollTop(),
355
bottom : $(window).scrollTop() + $(window).height(),
357
right : $(window).width()
360
width : $popup.width(),
361
height : $popup.outerHeight(),
362
position : $popup.offset()
365
offstagePositions = []
369
top : (popup.position.top < boundary.top),
370
bottom : (popup.position.top + popup.height > boundary.bottom),
371
right : (popup.position.left + popup.width > boundary.right),
372
left : (popup.position.left < boundary.left)
375
module.verbose('Checking if outside viewable area', popup.position);
376
// return only boundaries that have been surpassed
377
$.each(offstage, function(direction, isOffstage) {
379
offstagePositions.push(direction);
382
return (offstagePositions.length > 0)
383
? offstagePositions.join(' ')
387
nextPosition: function(position) {
390
position = 'bottom left';
393
position = 'top right';
396
position = 'bottom right';
399
position = 'top center';
402
position = 'bottom center';
404
case 'bottom center':
405
position = 'right center';
408
position = 'left center';
411
position = 'top center';
419
position: function(position, arrowOffset) {
421
windowWidth = $(window).width(),
422
windowHeight = $(window).height(),
424
width = $target.outerWidth(),
425
height = $target.outerHeight(),
427
popupWidth = $popup.width(),
428
popupHeight = $popup.outerHeight(),
430
parentWidth = $offsetParent.outerWidth(),
431
parentHeight = $offsetParent.outerHeight(),
433
distanceAway = settings.distanceAway,
435
offset = (settings.inline)
442
position = position || $module.data(metadata.position) || settings.position;
443
arrowOffset = arrowOffset || $module.data(metadata.offset) || settings.offset;
444
// adjust for margin when inline
445
if(settings.inline) {
446
if(position == 'left center' || position == 'right center') {
447
arrowOffset += parseInt( window.getComputedStyle(element).getPropertyValue('margin-top'), 10);
448
distanceAway += -parseInt( window.getComputedStyle(element).getPropertyValue('margin-left'), 10);
451
arrowOffset += parseInt( window.getComputedStyle(element).getPropertyValue('margin-left'), 10);
452
distanceAway += parseInt( window.getComputedStyle(element).getPropertyValue('margin-top'), 10);
455
module.debug('Calculating offset for position', position);
459
bottom : parentHeight - offset.top + distanceAway,
460
right : parentWidth - offset.left - arrowOffset,
467
bottom : parentHeight - offset.top + distanceAway,
468
left : offset.left + (width / 2) - (popupWidth / 2) + arrowOffset,
476
bottom : parentHeight - offset.top + distanceAway,
477
left : offset.left + width + arrowOffset,
483
top : offset.top + (height / 2) - (popupHeight / 2) + arrowOffset,
484
right : parentWidth - offset.left + distanceAway,
491
top : offset.top + (height / 2) - (popupHeight / 2) + arrowOffset,
492
left : offset.left + width + distanceAway,
499
top : offset.top + height + distanceAway,
500
right : parentWidth - offset.left - arrowOffset,
505
case 'bottom center':
507
top : offset.top + height + distanceAway,
508
left : offset.left + (width / 2) - (popupWidth / 2) + arrowOffset,
515
top : offset.top + height + distanceAway,
516
left : offset.left + width + arrowOffset,
522
// tentatively place on stage
525
.removeClass(className.position)
527
.addClass(className.loading)
529
// check if is offstage
530
offstagePosition = module.get.offstagePosition();
532
// recursively find new positioning
533
if(offstagePosition) {
534
module.debug('Element is outside boundaries', offstagePosition);
535
if(searchDepth < settings.maxSearchDepth) {
536
position = module.get.nextPosition(position);
538
module.debug('Trying new position', position);
539
return module.set.position(position);
542
module.error(error.recursion);
545
$popup.removeClass(className.loading);
550
module.debug('Position is on stage', position);
552
$popup.removeClass(className.loading);
561
if(settings.on == 'click' && settings.closable) {
562
module.verbose('Binding popup close event to document');
564
.on('click' + eventNamespace, function(event) {
565
module.verbose('Pop-up clickaway intent detected');
566
$.proxy(module.hideGracefully, this)(event);
575
if(settings.on == 'click' && settings.closable) {
576
module.verbose('Removing close event from document');
578
.off('click' + eventNamespace)
585
animating: function() {
586
return ( $popup.is(':animated') || $popup.hasClass(className.animating) );
588
visible: function() {
589
return $popup.is(':visible');
592
return !module.is.visible();
601
if(!settings.preserve) {
606
setting: function(name, value) {
607
if( $.isPlainObject(name) ) {
608
$.extend(true, settings, name);
610
else if(value !== undefined) {
611
settings[name] = value;
614
return settings[name];
617
internal: function(name, value) {
618
if( $.isPlainObject(name) ) {
619
$.extend(true, module, name);
621
else if(value !== undefined) {
622
module[name] = value;
630
if(settings.performance) {
631
module.performance.log(arguments);
634
module.debug = Function.prototype.bind.call(console.info, console, settings.name + ':');
635
module.debug.apply(console, arguments);
639
verbose: function() {
640
if(settings.verbose && settings.debug) {
641
if(settings.performance) {
642
module.performance.log(arguments);
645
module.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':');
646
module.verbose.apply(console, arguments);
651
module.error = Function.prototype.bind.call(console.error, console, settings.name + ':');
652
module.error.apply(console, arguments);
655
log: function(message) {
661
if(settings.performance) {
662
currentTime = new Date().getTime();
663
previousTime = time || currentTime;
664
executionTime = currentTime - previousTime;
669
'Arguments' : [].slice.call(message, 1) || '',
670
'Execution Time' : executionTime
673
clearTimeout(module.performance.timer);
674
module.performance.timer = setTimeout(module.performance.display, 100);
676
display: function() {
678
title = settings.name + ':',
682
clearTimeout(module.performance.timer);
683
$.each(performance, function(index, data) {
684
totalTime += data['Execution Time'];
686
title += ' ' + totalTime + 'ms';
688
title += ' \'' + moduleSelector + '\'';
690
if( (console.group !== undefined || console.table !== undefined) && performance.length > 0) {
691
console.groupCollapsed(title);
693
console.table(performance);
696
$.each(performance, function(index, data) {
697
console.log(data['Name'] + ': ' + data['Execution Time']+'ms');
705
invoke: function(query, passedArguments, context) {
712
passedArguments = passedArguments || queryArguments;
713
context = element || context;
714
if(typeof query == 'string' && object !== undefined) {
715
query = query.split(/[\. ]/);
716
maxDepth = query.length - 1;
717
$.each(query, function(depth, value) {
718
var camelCaseValue = (depth != maxDepth)
719
? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1)
722
if( $.isPlainObject( object[camelCaseValue] ) && (depth != maxDepth) ) {
723
object = object[camelCaseValue];
725
else if( object[camelCaseValue] !== undefined ) {
726
found = object[camelCaseValue];
729
else if( $.isPlainObject( object[value] ) && (depth != maxDepth) ) {
730
object = object[value];
732
else if( object[value] !== undefined ) {
733
found = object[value];
741
if ( $.isFunction( found ) ) {
742
response = found.apply(context, passedArguments);
744
else if(found !== undefined) {
747
if($.isArray(returnedValue)) {
748
returnedValue.push(response);
750
else if(returnedValue !== undefined) {
751
returnedValue = [returnedValue, response];
753
else if(response !== undefined) {
754
returnedValue = response;
761
if(instance === undefined) {
764
module.invoke(query);
767
if(instance !== undefined) {
775
return (returnedValue !== undefined)
781
$.fn.popup.settings = {
789
onCreate : function(){},
790
onRemove : function(){},
791
onShow : function(){},
792
onHide : function(){},
804
position : 'top center',
810
easing : 'easeOutQuad',
811
transition : 'scale',
818
content : 'Your popup has no content specified',
819
method : 'The method you called is not defined.',
820
recursion : 'Popup attempted to reposition element to fit, but could not find an adequate position.'
827
position : 'position',
829
variation : 'variation'
833
animating : 'animating',
836
position : 'top left center bottom right',
844
template: function(text) {
846
if(typeof text !== undefined) {
847
if(typeof text.title !== undefined && text.title) {
848
html += '<div class="header">' + text.title + '</div>';
850
if(typeof text.content !== undefined && text.content) {
851
html += '<div class="content">' + text.content + '</div>';
860
$.extend( $.easing, {
861
easeOutQuad: function (x, t, b, c, d) {
862
return -c *(t/=d)*(t-2) + b;
867
})( jQuery, window , document );