diff --git a/src/definitions/modules/popup.js b/src/definitions/modules/popup.js index 5024cde80..a8de7c7f3 100644 --- a/src/definitions/modules/popup.js +++ b/src/definitions/modules/popup.js @@ -17,6 +17,8 @@ $.fn.popup = function(parameters) { var $allModules = $(this), $document = $(document), + $window = $(window), + $body = $('body'), moduleSelector = $allModules.selector || '', @@ -52,13 +54,12 @@ $.fn.popup = function(parameters) { ? $(settings.target) : $module, - $window = $(window), - $body = $('body'), $popup, $offsetParent, searchDepth = 0, triedPositions = false, + openedWithTouch = false, element = this, instance = $module.data(moduleNamespace), @@ -166,10 +167,24 @@ $.fn.popup = function(parameters) { clearTimeout(module.showTimer); module.hideTimer = setTimeout(module.hide, delay); }, + touchstart: function(event) { + openedWithTouch = true; + module.event.start(); + }, resize: function() { if( module.is.visible() ) { module.set.position(); } + }, + hideGracefully: function(event) { + // don't close on clicks inside popup + if(event && $(event.target).closest(selector.popup).length === 0) { + module.debug('Click occurred outside popup hiding popup'); + module.hide(); + } + else { + module.debug('Click was inside popup, keeping popup open'); + } } }, @@ -262,7 +277,7 @@ $.fn.popup = function(parameters) { }, show: function(callback) { - callback = $.isFunction(callback) ? callback : function(){}; + callback = callback || function(){}; module.debug('Showing pop-up', settings.transition); if(module.is.hidden() && !( module.is.active() && module.is.dropdown()) ) { @@ -288,7 +303,7 @@ $.fn.popup = function(parameters) { hide: function(callback) { - callback = $.isFunction(callback) ? callback : function(){}; + callback = callback || function(){}; if( module.is.visible() || module.is.animating() ) { if(settings.onHide.call($popup, element) === false) { module.debug('onHide callback returned false, cancelling popup animation'); @@ -312,18 +327,6 @@ $.fn.popup = function(parameters) { }) ; }, - - hideGracefully: function(event) { - // don't close on clicks inside popup - if(event && $(event.target).closest(selector.popup).length === 0) { - module.debug('Click occurred outside popup hiding popup'); - module.hide(); - } - else { - module.debug('Click was inside popup, keeping popup open'); - } - }, - exists: function() { if(!$popup) { return false; @@ -438,15 +441,73 @@ $.fn.popup = function(parameters) { $module.removeData(metadata.variation); return $module.data(metadata.variation) || settings.variation; }, + calculations: function() { + var + targetElement = $target[0], + targetPosition = (settings.inline || settings.popup) + ? $target.position() + : $target.offset(), + calculations = {}, + screen + ; + calculations = { + // element which is launching popup + target : { + element : $target[0], + width : $target.outerWidth(), + height : $target.outerHeight(), + top : targetPosition.top, + left : targetPosition.left, + margin : {} + }, + // popup itself + popup : { + width : $popup.outerWidth(), + height : $popup.outerHeight() + }, + // offset container (or 3d context) + parent : { + width : $offsetParent.outerWidth(), + height : $offsetParent.outerHeight() + }, + // screen boundaries + screen : { + scroll: { + top : $window.scrollTop(), + left : $window.scrollLeft() + }, + width : $window.width(), + height : $window.height() + } + }; + + // add in margins if inline + calculations.target.margin.top = (settings.inline) + ? parseInt( window.getComputedStyle(targetElement).getPropertyValue('margin-top'), 10) + : 0 + ; + calculations.target.margin.left = (settings.inline) + ? module.is.rtl() + ? parseInt( window.getComputedStyle(targetElement).getPropertyValue('margin-right'), 10) + : parseInt( window.getComputedStyle(targetElement).getPropertyValue('margin-left') , 10) + : 0 + ; + // calculate screen boundaries + screen = calculations.screen; + calculations.boundary = { + top : screen.scroll.top, + bottom : screen.scroll.top + screen.height, + left : screen.scroll.left, + right : screen.scroll.left + screen.width + }; + return calculations; + }, id: function() { return id; }, startEvent: function() { if(settings.on == 'hover') { - return (hasTouch && settings.addTouchEvents) - ? 'touchstart mouseenter' - : 'mouseenter' - ; + return 'mouseenter'; } else if(settings.on == 'focus') { return 'focus'; @@ -454,10 +515,7 @@ $.fn.popup = function(parameters) { return false; }, scrollEvent: function() { - return (hasTouch && settings.addTouchEvents) - ? 'touchmove scroll' - : 'scroll' - ; + return 'scroll'; }, endEvent: function() { if(settings.on == 'hover') { @@ -495,37 +553,28 @@ $.fn.popup = function(parameters) { : $() ; }, - offstagePosition: function(position) { + offstagePosition: function(position, calculations) { var - screen = { - top : $(window).scrollTop(), - left : $(window).scrollLeft(), - width : $(window).width(), - height : $(window).height() - }, - boundary = { - top : screen.top, - bottom : screen.top + screen.height, - left : screen.left, - right : screen.left + screen.width - }, - popup = { - width : $popup.width(), - height : $popup.height(), - offset : $popup.offset() - }, - offstage = {}, - offstagePositions = [] + offset = $popup.offset(), + offstage = {}, + offstagePositions = [], + popup, + boundary ; - position = position || false; - if(popup.offset && position) { - module.verbose('Checking if outside viewable area', popup.offset); + position = position || false; + calculations = calculations || module.get.calculations(); + // shorthand + popup = calculations.popup; + boundary = calculations.boundary; + + if(offset && position) { offstage = { - top : (popup.offset.top < boundary.top), - bottom : (popup.offset.top + popup.height > boundary.bottom), - right : (popup.offset.left + popup.width > boundary.right), - left : (popup.offset.left < boundary.left) + top : (offset.top < boundary.top), + bottom : (offset.top + popup.height > boundary.bottom), + right : (offset.left + popup.width > boundary.right), + left : (offset.left < boundary.left) }; + module.verbose('Offstage positions determined', offset, offstage); } // return only boundaries that have been surpassed $.each(offstage, function(direction, isOffstage) { @@ -610,7 +659,7 @@ $.fn.popup = function(parameters) { }, set: { - position: function(position, arrowOffset) { + position: function(position, calculations) { // exit conditions if($target.length === 0 || $popup.length === 0) { @@ -618,140 +667,125 @@ $.fn.popup = function(parameters) { return; } var - windowWidth = $(window).width(), - windowHeight = $(window).height(), - - targetWidth = $target.outerWidth(), - targetHeight = $target.outerHeight(), - - popupWidth = $popup.outerWidth(), - popupHeight = $popup.outerHeight(), - - parentWidth = $offsetParent.outerWidth(), - parentHeight = $offsetParent.outerHeight(), - - distanceAway = settings.distanceAway, - - targetElement = $target[0], - - marginTop = (settings.inline) - ? parseInt( window.getComputedStyle(targetElement).getPropertyValue('margin-top'), 10) - : 0, - marginLeft = (settings.inline) - ? module.is.rtl() - ? parseInt( window.getComputedStyle(targetElement).getPropertyValue('margin-right'), 10) - : parseInt( window.getComputedStyle(targetElement).getPropertyValue('margin-left') , 10) - : 0, - - target = (settings.inline || settings.popup) - ? $target.position() - : $target.offset(), - + offset, + distanceAway, + target, + popup, + parent, computedPosition, positioning, offstagePosition ; - position = position || $module.data(metadata.position) || settings.position; - arrowOffset = arrowOffset || $module.data(metadata.offset) || settings.offset; + calculations = calculations || module.get.calculations(); + position = position || $module.data(metadata.position) || settings.position; + + offset = $module.data(metadata.offset) || settings.offset; + distanceAway = settings.distanceAway; + + // shorthand + target = calculations.target; + popup = calculations.popup; + parent = calculations.parent; if(target.top === 0 && target.left === 0) { module.debug('Popup target is hidden, no action taken'); return false; } - if(searchDepth == settings.maxSearchDepth && settings.lastResort) { - module.debug('Using last resort position to display', settings.lastResort); - position = settings.lastResort; - } - if(settings.inline) { - module.debug('Adding targets margin to calculation'); + module.debug('Adding margin to calculation', target.margin); if(position == 'left center' || position == 'right center') { - arrowOffset += marginTop; - distanceAway += -marginLeft; + offset += target.margin.top; + distanceAway += -target.margin.left; } else if (position == 'top left' || position == 'top center' || position == 'top right') { - arrowOffset += marginLeft; - distanceAway -= marginTop; + offset += target.margin.left; + distanceAway -= target.margin.top; } else { - arrowOffset += marginLeft; - distanceAway += marginTop; + offset += target.margin.left; + distanceAway += target.margin.top; } } - module.debug('Calculating popup positioning', position); - computedPosition = position; + module.debug('Determining popup position from calculations', position, calculations); + if (module.is.rtl()) { - computedPosition = computedPosition.replace(/left|right/g, function (match) { + position = position.replace(/left|right/g, function (match) { return (match == 'left') ? 'right' : 'left' ; }); - module.debug('RTL: Popup positioning updated', computedPosition); + module.debug('RTL: Popup position updated', position); } - switch (computedPosition) { + + if(searchDepth == settings.maxSearchDepth && settings.lastResort) { + module.debug('Using "last resort" position to display', settings.lastResort); + position = settings.lastResort; + } + + switch (position) { case 'top left': positioning = { top : 'auto', - bottom : parentHeight - target.top + distanceAway, - left : target.left + arrowOffset, + bottom : parent.height - target.top + distanceAway, + left : target.left + offset, right : 'auto' }; break; case 'top center': positioning = { - bottom : parentHeight - target.top + distanceAway, - left : target.left + (targetWidth / 2) - (popupWidth / 2) + arrowOffset, + bottom : parent.height - target.top + distanceAway, + left : target.left + (target.width / 2) - (popup.width / 2) + offset, top : 'auto', right : 'auto' }; break; case 'top right': positioning = { - bottom : parentHeight - target.top + distanceAway, - right : parentWidth - target.left - targetWidth - arrowOffset, + bottom : parent.height - target.top + distanceAway, + right : parent.width - target.left - target.width - offset, top : 'auto', left : 'auto' }; break; case 'left center': positioning = { - top : target.top + (targetHeight / 2) - (popupHeight / 2) + arrowOffset, - right : parentWidth - target.left + distanceAway, + top : target.top + (target.height / 2) - (popup.height / 2) + offset, + right : parent.width - target.left + distanceAway, left : 'auto', bottom : 'auto' }; break; case 'right center': positioning = { - top : target.top + (targetHeight / 2) - (popupHeight / 2) + arrowOffset, - left : target.left + targetWidth + distanceAway, + top : target.top + (target.height / 2) - (popup.height / 2) + offset, + left : target.left + target.width + distanceAway, bottom : 'auto', right : 'auto' }; break; case 'bottom left': positioning = { - top : target.top + targetHeight + distanceAway, - left : target.left + arrowOffset, + top : target.top + target.height + distanceAway, + left : target.left + offset, bottom : 'auto', right : 'auto' }; break; case 'bottom center': positioning = { - top : target.top + targetHeight + distanceAway, - left : target.left + (targetWidth / 2) - (popupWidth / 2) + arrowOffset, + top : target.top + target.height + distanceAway, + left : target.left + (target.width / 2) - (popup.width / 2) + offset, bottom : 'auto', right : 'auto' }; break; case 'bottom right': positioning = { - top : target.top + targetHeight + distanceAway, - right : parentWidth - target.left - targetWidth - arrowOffset, + top : target.top + target.height + distanceAway, + right : parent.width - target.left - target.width - offset, left : 'auto', bottom : 'auto' }; @@ -771,7 +805,7 @@ $.fn.popup = function(parameters) { .addClass(className.loading) ; // check if is offstage - offstagePosition = module.get.offstagePosition(position); + offstagePosition = module.get.offstagePosition(position, calculations); // recursively find new positioning if(offstagePosition) { @@ -781,7 +815,7 @@ $.fn.popup = function(parameters) { position = module.get.nextPosition(position); module.debug('Trying new position', position); return ($popup) - ? module.set.position(position) + ? module.set.position(position, calculations) : false ; } @@ -797,14 +831,15 @@ $.fn.popup = function(parameters) { module.debug('Position is on stage', position); module.remove.attempts(); - module.set.fluidWidth(); + module.set.fluidWidth(calculations); module.remove.loading(); return true; }, - fluidWidth: function() { + fluidWidth: function(calculations) { + calculations = calculations || module.get.calculations(); if( settings.setFluidWidth && $popup.hasClass(className.fluid) ) { - $popup.css('width', $offsetParent.width()); + $popup.css('width', calculations.parent.width); } }, @@ -835,6 +870,11 @@ $.fn.popup = function(parameters) { .on('click' + eventNamespace, module.toggle) ; } + if(settings.on == 'hover' && hasTouch) { + $module + .on('touchstart' + eventNamespace, module.event.touchstart) + ; + } else if( module.get.startEvent() ) { $module .on(module.get.startEvent() + eventNamespace, module.event.start) @@ -855,21 +895,30 @@ $.fn.popup = function(parameters) { ; } }, - close:function() { - if(settings.hideOnScroll === true || settings.hideOnScroll == 'auto' && settings.on != 'click') { + close: function() { + if(settings.hideOnScroll === true || (settings.hideOnScroll == 'auto' && settings.on != 'click')) { $document - .one(module.get.scrollEvent() + elementNamespace, module.hideGracefully) + .one(module.get.scrollEvent() + elementNamespace, module.event.hideGracefully) ; $context - .one(module.get.scrollEvent() + elementNamespace, module.hideGracefully) + .one(module.get.scrollEvent() + elementNamespace, module.event.hideGracefully) + ; + } + if(settings.on == 'hover' && openedWithTouch) { + module.verbose('Binding popup close event to document'); + $document + .on('touchstart' + elementNamespace, function(event) { + module.verbose('Touched away from popup'); + module.event.hideGracefully.call(element, event); + }) ; } if(settings.on == 'click' && settings.closable) { module.verbose('Binding popup close event to document'); $document .on('click' + elementNamespace, function(event) { - module.verbose('Pop-up clickaway intent detected'); - module.hideGracefully.call(element, event); + module.verbose('Clicked away from popup'); + module.event.hideGracefully.call(element, event); }) ; } @@ -878,7 +927,7 @@ $.fn.popup = function(parameters) { unbind: { close: function() { - if(settings.hideOnScroll === true || settings.hideOnScroll == 'auto' && settings.on != 'click') { + if(settings.hideOnScroll === true || (settings.hideOnScroll == 'auto' && settings.on != 'click')) { $document .off('scroll' + elementNamespace, module.hide) ; @@ -886,6 +935,12 @@ $.fn.popup = function(parameters) { .off('scroll' + elementNamespace, module.hide) ; } + if(settings.on == 'hover' && openedWithTouch) { + $document + .off('touchstart' + elementNamespace) + ; + openedWithTouch = false; + } if(settings.on == 'click' && settings.closable) { module.verbose('Removing close event from document'); $document @@ -1123,14 +1178,19 @@ $.fn.popup.settings = { // callback only when element added to dom onCreate : function(){}, + // callback before element removed from dom onRemove : function(){}, + // callback before show animation onShow : function(){}, + // callback after show animation onVisible : function(){}, + // callback before hide animation onHide : function(){}, + // callback after hide animation onHidden : function(){}, @@ -1142,36 +1202,49 @@ $.fn.popup.settings = { // default position relative to element position : 'top left', + // name of variation to use variation : '', + // whether popup should be moved to context movePopup : true, + // element which popup should be relative to target : false, + // jq selector or element that should be used as popup popup : false, + // popup should remain inline next to activator inline : false, + // popup should be removed from page on hide preserve : false, + // popup should not close when being hovered on hoverable : false, // explicitly set content content : false, + // explicitly set html html : false, + // explicitly set title title : false, // whether automatically close on clickaway when on click closable : true, + // automatically hide on scroll hideOnScroll : 'auto', + // hide other popups on show exclusive : false, + // context to attach popups context : 'body', + // position to prefer when calculating new position prefer : 'opposite', @@ -1187,7 +1260,6 @@ $.fn.popup.settings = { // whether fluid variation should assign width explicitly setFluidWidth : true, - // transition settings duration : 200, transition : 'scale', @@ -1198,7 +1270,7 @@ $.fn.popup.settings = { // offset on aligning axis from calculated position offset : 0, - // maximum times to look for a position before failing + // maximum times to look for a position before failing (9 positions total) maxSearchDepth : 20, error: { @@ -1277,12 +1349,5 @@ $.fn.popup.settings = { }; -// Adds easing -$.extend( $.easing, { - easeOutQuad: function (x, t, b, c, d) { - return -c *(t/=d)*(t-2) + b; - } -}); - })( jQuery, window , document ); diff --git a/src/definitions/modules/popup.less b/src/definitions/modules/popup.less index 6b077b8a2..65b560aea 100755 --- a/src/definitions/modules/popup.less +++ b/src/definitions/modules/popup.less @@ -82,17 +82,43 @@ .ui.popup { margin: 0em; } -.ui.popup.bottom { - margin: @popupDistanceAway 0em 0em; -} -.ui.popup.top { + +/* Extending from Top */ +.ui.top.popup { margin: 0em 0em @popupDistanceAway; } -.ui.popup.left.center { +.ui.top.left.popup { + transform-origin: bottom left; +} +.ui.top.center.popup { + transform-origin: bottom center; +} +.ui.top.right.popup { + transform-origin: bottom right; +} + +/* Extending from Vertical Center */ +.ui.left.center.popup { margin: 0em @popupDistanceAway 0em 0em; + transform-origin: right 50%; } -.ui.popup.right.center { +.ui.right.center.popup { margin: 0em 0em 0em @popupDistanceAway; + transform-origin: left 50%; +} + +/* Extending from Bottom */ +.ui.bottom.popup { + margin: @popupDistanceAway 0em 0em; +} +.ui.bottom.left.popup { + transform-origin: top left; +} +.ui.bottom.center.popup { + transform-origin: top center; +} +.ui.bottom.right.popup { + transform-origin: top right; } /*--------------