1 //>>excludeStart("jqmBuildExclude", pragmas.jqmBuildExclude);
2 //>>description: Popup windows
5 //>>css.theme: ../css/themes/default/jquery.mobile.theme.css
6 //>>css.structure: ../css/structure/jquery.mobile.popup.css,../css/structure/jquery.mobile.transition.css,../css/structure/jquery.mobile.transition.fade.css
9 "../jquery.mobile.widget",
10 "../jquery.mobile.support",
11 "../jquery.mobile.navigation",
12 "depend!../jquery.hashchange[jquery]" ], function( $ ) {
13 //>>excludeEnd("jqmBuildExclude");
14 (function( $, undefined ) {
16 function fitSegmentInsideSegment( winSize, segSize, offset, desired ) {
19 if ( winSize < segSize ) {
20 // Center segment if it's bigger than the window
21 ret = offset + ( winSize - segSize ) / 2;
23 // Otherwise center it at the desired coordinate while keeping it completely inside the window
24 ret = Math.min( Math.max( offset, desired - segSize / 2 ), offset + winSize - segSize );
30 function windowCoords() {
31 var $win = $.mobile.$window;
36 cx: ( window.innerWidth || $win.width() ),
37 cy: ( window.innerHeight || $win.height() )
41 $.widget( "mobile.popup", $.mobile.widget, {
50 initSelector: ":jqmData(role='popup')",
51 closeLinkSelector: "a:jqmData(rel='back')",
52 closeLinkEvents: "click.popup",
53 navigateEvents: "navigate.popup",
54 closeEvents: "navigate.popup pagebeforechange.popup",
56 // NOTE Windows Phone 7 has a scroll position caching issue that
57 // requires us to disable popup history management by default
58 // https://github.com/jquery/jquery-mobile/issues/4784
60 // NOTE this option is modified in _create!
64 _eatEventAndClose: function( e ) {
66 e.stopImmediatePropagation();
71 // Make sure the screen size is increased beyond the page height if the popup's causes the document to increase in height
72 _resizeScreen: function() {
73 var popupHeight = this._ui.container.outerHeight( true );
75 this._ui.screen.removeAttr( "style" );
76 if ( popupHeight > this._ui.screen.height() ) {
77 this._ui.screen.height( popupHeight );
81 _handleWindowKeyUp: function( e ) {
82 if ( this._isOpen && e.keyCode === $.mobile.keyCode.ESCAPE ) {
83 return this._eatEventAndClose( e );
87 _maybeRefreshTimeout: function() {
88 var winCoords = windowCoords();
90 if ( this._resizeData ) {
91 if ( winCoords.x === this._resizeData.winCoords.x &&
92 winCoords.y === this._resizeData.winCoords.y &&
93 winCoords.cx === this._resizeData.winCoords.cx &&
94 winCoords.cy === this._resizeData.winCoords.cy ) {
95 // timeout not refreshed
98 // clear existing timeout - it will be refreshed below
99 clearTimeout( this._resizeData.timeoutId );
104 timeoutId: setTimeout( $.proxy( this, "_resizeTimeout" ), 200 ),
111 _resizeTimeout: function() {
112 if ( !this._maybeRefreshTimeout() && this.positionTo === "window" ) {
113 // effectively rapid-open the popup while leaving the screen intact
114 this._trigger( "beforeposition" );
116 .removeClass( "ui-selectmenu-hidden" )
117 .offset( this._placementCoords( this._desiredCoords( undefined, undefined, "window" ) ) );
119 this._resizeScreen();
120 this._resizeData = null;
121 this._orientationchangeInProgress = false;
125 _handleWindowResize: function( e ) {
126 if ( this._isOpen ) {
127 // Context popup close when Window resize event
128 if( this.positionTo !== "window" ) {
132 this._maybeRefreshTimeout();
136 _handleWindowOrientationchange: function( e ) {
138 if ( !this._orientationchangeInProgress ) {
139 // effectively rapid-close the popup while leaving the screen intact
141 .addClass( "ui-selectmenu-hidden" )
142 .removeAttr( "style" );
144 this._orientationchangeInProgress = true;
148 _create: function() {
150 screen: $( "<div class='ui-screen-hidden ui-popup-screen'></div>" ),
151 placeholder: $( "<div style='display: none;'><!-- placeholder --></div>" ),
152 container: $( "<div class='ui-popup-container ui-selectmenu-hidden'></div>" ),
153 arrow : $("<div class='ui-arrow'></div>")
155 thisPage = this.element.closest( ".ui-page" ),
156 myId = this.element.attr( "id" ),
159 // We need to adjust the history option to be false if there's no AJAX nav.
160 // We can't do it in the option declarations because those are run before
161 // it is determined whether there shall be AJAX nav.
162 this.options.history = this.options.history && $.mobile.ajaxEnabled && $.mobile.hashListeningEnabled;
164 if ( thisPage.length === 0 ) {
165 thisPage = $( "body" );
168 // define the container for navigation event bindings
169 // TODO this would be nice at the the mobile widget level
170 this.options.container = this.options.container || $.mobile.pageContainer;
173 thisPage.append( ui.screen );
174 ui.container.insertAfter( ui.screen );
175 // Leave a placeholder where the element used to be
176 ui.placeholder.insertAfter( this.element );
178 ui.screen.attr( "id", myId + "-screen" );
179 ui.container.attr( "id", myId + "-popup" );
180 ui.placeholder.html( "<!-- placeholder for " + myId + " -->" );
182 ui.container.append( this.element );
183 ui.container.append( ui.arrow );
184 // Add class to popup element
185 this.element.addClass( "ui-popup" );
187 // Define instance variables
191 _fallbackTransition: "",
192 _currentTransition: false,
197 _orientationchangeInProgress: false,
200 src: $.mobile.$window,
202 orientationchange: $.proxy( this, "_handleWindowOrientationchange" ),
203 resize: $.proxy( this, "_handleWindowResize" ),
204 keyup: $.proxy( this, "_handleWindowKeyUp" )
210 $.each( this.options, function( key, value ) {
211 // Cause initial options to be applied by their handler by temporarily setting the option to undefined
212 // - the handler then sets it to the initial value
213 self.options[ key ] = undefined;
214 self._setOption( key, value, true );
217 ui.screen.bind( "vclick", $.proxy( this, "_eatEventAndClose" ) );
219 $.each( this._globalHandlers, function( idx, value ) {
220 value.src.bind( value.handler );
224 _applyTheme: function( dst, theme, prefix ) {
225 var classes = ( dst.attr( "class" ) || "").split( " " ),
229 themeStr = String( theme );
231 while ( classes.length > 0 ) {
232 currentTheme = classes.pop();
233 matches = ( new RegExp( "^ui-" + prefix + "-([a-z])$" ) ).exec( currentTheme );
234 if ( matches && matches.length > 1 ) {
235 currentTheme = matches[ 1 ];
242 if ( theme !== currentTheme ) {
243 dst.removeClass( "ui-" + prefix + "-" + currentTheme );
244 if ( ! ( theme === null || theme === "none" ) ) {
245 dst.addClass( "ui-" + prefix + "-" + themeStr );
250 _setTheme: function( value ) {
251 this._applyTheme( this.element, value, "body" );
254 _setOverlayTheme: function( value ) {
255 this._applyTheme( this._ui.screen, value, "overlay" );
257 if ( this._isOpen ) {
258 this._ui.screen.addClass( "in" );
262 _setShadow: function( value ) {
263 this.element.toggleClass( "ui-overlay-shadow", value );
266 _setCorners: function( value ) {
267 this.element.toggleClass( "ui-corner-all", value );
270 _applyTransition: function( value ) {
271 this._ui.container.removeClass( this._fallbackTransition );
272 if ( value && value !== "none" ) {
273 this._fallbackTransition = $.mobile._maybeDegradeTransition( value );
274 this._ui.container.addClass( this._fallbackTransition );
278 _setTransition: function( value ) {
279 if ( !this._currentTransition ) {
280 this._applyTransition( value );
284 _setTolerance: function( value ) {
285 var tol = { t: 5, r: 5, b: 5, l: 5 };
288 var ar = String( value ).split( "," );
290 $.each( ar, function( idx, val ) { ar[ idx ] = parseInt( val, 10 ); } );
292 switch( ar.length ) {
293 // All values are to be the same
295 if ( !isNaN( ar[ 0 ] ) ) {
296 tol.t = tol.r = tol.b = tol.l = ar[ 0 ];
300 // The first value denotes top/bottom tolerance, and the second value denotes left/right tolerance
302 if ( !isNaN( ar[ 0 ] ) ) {
303 tol.t = tol.b = ar[ 0 ];
305 if ( !isNaN( ar[ 1 ] ) ) {
306 tol.l = tol.r = ar[ 1 ];
310 // The array contains values in the order top, right, bottom, left
312 if ( !isNaN( ar[ 0 ] ) ) {
315 if ( !isNaN( ar[ 1 ] ) ) {
318 if ( !isNaN( ar[ 2 ] ) ) {
321 if ( !isNaN( ar[ 3 ] ) ) {
331 this._tolerance = tol;
334 _setOption: function( key, value ) {
335 var exclusions, setter = "_set" + key.charAt( 0 ).toUpperCase() + key.slice( 1 );
337 if ( this[ setter ] !== undefined ) {
338 this[ setter ]( value );
341 // TODO REMOVE FOR 1.2.1 by moving them out to a default options object
352 $.mobile.widget.prototype._setOption.apply( this, arguments );
353 if ( $.inArray( key, exclusions ) === -1 ) {
354 // Record the option change in the options and in the DOM data-* attributes
355 this.element.attr( "data-" + ( $.mobile.ns || "" ) + ( key.replace( /([A-Z])/, "-$1" ).toLowerCase() ), value );
359 // Try and center the overlay over the given coordinates
360 _placementCoords: function( desired ) {
361 // rectangle within which the popup must fit
363 winCoords = windowCoords(),
365 x: this._tolerance.l,
366 y: winCoords.y + this._tolerance.t,
367 cx: winCoords.cx - this._tolerance.l - this._tolerance.r,
368 cy: winCoords.cy - this._tolerance.t - this._tolerance.b
371 linkOffset = $(this.link).offset(),
372 positionOffsets = [],
373 correctionValue = [0,0],
376 // Clamp the width of the menu before grabbing its size
377 this._ui.container.css( "max-width", rc.cx );
379 cx: this._ui.container.outerWidth( true ),
380 cy: this._ui.container.outerHeight( true )
383 // Center the menu over the desired coordinates, while not going outside
384 // the window tolerances. This will center wrt. the window if the popup is too large.
386 x: fitSegmentInsideSegment( rc.cx, menuSize.cx, rc.x, desired.x ),
387 y: fitSegmentInsideSegment( rc.cy, menuSize.cy, rc.y, desired.y )
390 // Make sure the top of the menu is visible
391 ret.y = Math.max( 0, ret.y );
393 // If the height of the menu is smaller than the height of the document
394 // align the bottom with the bottom of the document
396 // fix for $( document ).height() bug in core 1.7.2.
397 var docEl = document.documentElement, docBody = document.body,
398 docHeight = Math.max( docEl.clientHeight, docBody.scrollHeight, docBody.offsetHeight, docEl.scrollHeight, docEl.offsetHeight );
400 ret.y -= Math.min( ret.y, Math.max( 0, ret.y + menuSize.cy - docHeight ) );
402 if ( this.positionTo !== "origin" )
404 return { left: ret.x, top: ret.y , arrowleft: 0 , arrowtop: 0};
407 positionOffsets = [ linkOffset.left,
409 docEl.clientHeight - ( linkOffset.top + $(this.link).height() ),
410 docEl.clientWidth - ( linkOffset.left + $(this.link).width() )];
411 arrayIdx = positionOffsets.indexOf(Math.max.apply(window,positionOffsets));
416 correctionValue = [ -$(this.link).width() , 0];
417 arrowtop = ( linkOffset.top - ret.y ) + ( $(this.link).height() / 2 ) - parseInt( $(this._ui.arrow).css("border-width") ) ;
418 arrowleft = menuSize.cx;
419 $(this._ui.arrow).attr( "class", "" )
420 .addClass( "ui-arrow left" )
423 correctionValue = [ 0 , -(ret.y + menuSize.cy - linkOffset.top)];
424 arrowtop = menuSize.cy - 2;
425 arrowleft = (linkOffset.left - ret.x + correctionValue[0]) + ( $(this.link).width() / 2 ) - parseInt( $(this._ui.arrow).css("border-width") ) / 2;
426 $(this._ui.arrow).attr( "class", "" )
427 .addClass( "ui-arrow bottom" );
430 correctionValue = [ 0 , ( linkOffset.top + $(this.link).height() - ret.y ) ];
431 arrowtop = - parseInt( $(this._ui.arrow).css("border-width") ) * 2 + 1;
432 arrowleft = (linkOffset.left - ret.x + correctionValue[0]) + ( $(this.link).width() / 2 ) - parseInt( $(this._ui.arrow).css("border-width") ) / 2;
433 $(this._ui.arrow).attr( "class", "" )
434 .addClass("ui-arrow top");
437 correctionValue = [ ( menuSize.cx < $(this.link).width() ) ? ( $(this.link).width() / 2 ) + ( menuSize.cx / 2) : $(this.link).width() , 0];
438 arrowtop = ( linkOffset.top - ret.y ) + ( $(this.link).height() / 2 ) - parseInt( $(this._ui.arrow).css("border-width") ) ;
439 arrowleft = - parseInt( $(this._ui.arrow).css("border-width") ) * 2;
440 $(this._ui.arrow).attr( "class", "" )
441 .addClass("ui-arrow right");
445 return { left: ret.x + correctionValue[0], top: ret.y + correctionValue[1] , arrowleft: arrowleft , arrowtop: arrowtop };
448 _createPrereqs: function( screenPrereq, containerPrereq, whenDone ) {
449 var self = this, prereqs;
451 // It is important to maintain both the local variable prereqs and self._prereqs. The local variable remains in
452 // the closure of the functions which call the callbacks passed in. The comparison between the local variable and
453 // self._prereqs is necessary, because once a function has been passed to .animationComplete() it will be called
454 // next time an animation completes, even if that's not the animation whose end the function was supposed to catch
455 // (for example, if an abort happens during the opening animation, the .animationComplete handler is not called for
456 // that animation anymore, but the handler remains attached, so it is called the next time the popup is opened
457 // - making it stale. Comparing the local variable prereqs to the widget-level variable self._prereqs ensures that
458 // callbacks triggered by a stale .animationComplete will be ignored.
461 screen: $.Deferred(),
462 container: $.Deferred()
465 prereqs.screen.then( function() {
466 if ( prereqs === self._prereqs ) {
471 prereqs.container.then( function() {
472 if ( prereqs === self._prereqs ) {
477 $.when( prereqs.screen, prereqs.container ).done( function() {
478 if ( prereqs === self._prereqs ) {
479 self._prereqs = null;
484 self._prereqs = prereqs;
487 _animate: function( args ) {
488 // NOTE before removing the default animation of the screen
489 // this had an animate callback that would relove the deferred
490 // now the deferred is resolved immediately
491 // TODO remove the dependency on the screen deferred
493 .removeClass( args.classToRemove )
494 .addClass( args.screenClassToAdd );
496 args.prereqs.screen.resolve();
498 if ( args.transition && args.transition !== "none" ) {
499 if ( args.applyTransition ) {
500 this._applyTransition( args.transition );
503 .animationComplete( $.proxy( args.prereqs.container, "resolve" ) )
504 .addClass( args.containerClassToAdd )
505 .removeClass( args.classToRemove );
507 args.prereqs.container.resolve();
511 // The desired coordinates passed in will be returned untouched if no reference element can be identified via
512 // desiredPosition.positionTo. Nevertheless, this function ensures that its return value always contains valid
513 // x and y coordinates by specifying the center middle of the window if the coordinates are absent.
514 _desiredCoords: function( x, y, positionTo ) {
515 var dst = null, offset, winCoords = windowCoords();
517 self.positionTo = positionTo;
519 // Establish which element will serve as the reference
520 if ( positionTo && positionTo !== "origin" ) {
521 if ( positionTo === "window" ) {
522 x = winCoords.cx / 2 + winCoords.x;
523 y = winCoords.cy / 2 + winCoords.y;
526 dst = $( positionTo );
531 dst.filter( ":visible" );
532 if ( dst.length === 0 ) {
539 // If an element was found, center over it
541 offset = dst.offset();
542 x = offset.left + dst.outerWidth() / 2;
543 y = offset.top + dst.outerHeight() / 2;
546 // Make sure x and y are valid numbers - center over the window
547 if ( $.type( x ) !== "number" || isNaN( x ) ) {
548 x = winCoords.cx / 2 + winCoords.x;
550 if ( $.type( y ) !== "number" || isNaN( y ) ) {
551 y = winCoords.cy / 2 + winCoords.y;
554 return { x: x, y: y };
557 _reposition: function() {
563 && self.positionTo !== "window") {
564 coords = self._placementCoords( self._desiredCoords( $(self.link).offset().left + $(self.link).outerWidth() /2 , $(self.link).offset().top + $(self.link).outerHeight() /2 , self.positionTo || self.options.positionTo || "origin" ) );
566 .offset( { top : coords.top } );
570 _openPrereqsComplete: function() {
573 self._ui.container.addClass( "ui-popup-active" );
575 self._resizeScreen();
577 // Android appears to trigger the animation complete before the popup
578 // is visible. Allowing the stack to unwind before applying focus prevents
579 // the "blue flash" of element focus in android 4.0
580 setTimeout(function(){
581 self._ui.container.attr( "tabindex", "0" ).focus();
582 self._trigger( "afteropen" );
587 _open: function( options ) {
588 var coords, transition,
589 androidBlacklist = ( function() {
591 ua = navigator.userAgent,
592 // Rendering engine is Webkit, and capture major version
593 wkmatch = ua.match( /AppleWebKit\/([0-9\.]+)/ ),
594 wkversion = !!wkmatch && wkmatch[ 1 ],
595 androidmatch = ua.match( /Android (\d+(?:\.\d+))/ ),
596 andversion = !!androidmatch && androidmatch[ 1 ],
597 chromematch = ua.indexOf( "Chrome" ) > -1;
599 // Platform is Android, WebKit version is greater than 534.13 ( Android 3.2.1 ) and not Chrome.
600 if( androidmatch !== null && andversion === "4.0" && wkversion && wkversion > 534.13 && !chromematch ) {
606 // Make sure options is defined
607 options = ( options || {} );
609 // Copy out the transition, because we may be overwriting it later and we don't want to pass that change back to the caller
610 transition = options.transition || this.options.transition;
612 // Give applications a chance to modify the contents of the container before it appears
613 this._trigger( "beforeposition" );
615 coords = this._placementCoords( this._desiredCoords( options.x, options.y, options.positionTo || this.options.positionTo || "origin" ) );
617 // Count down to triggering "popupafteropen" - we have two prerequisites:
618 // 1. The popup window animation completes (container())
619 // 2. The screen opacity animation completes (screen())
623 $.proxy( this, "_openPrereqsComplete" ) );
626 this._currentTransition = transition;
627 this._applyTransition( transition );
629 transition = this.options.transition;
632 if ( !this.options.theme ) {
633 this._setTheme( this._page.jqmData( "theme" ) || $.mobile.getInheritedTheme( this._page, "c" ) );
636 this._ui.screen.removeClass( "ui-screen-hidden" );
639 .removeClass( "ui-selectmenu-hidden" )
641 this._ui.arrow.css( { top : coords.arrowtop, left : coords.arrowleft } );
642 if ( this.options.overlayTheme && androidBlacklist ) {
644 The native browser on Android 4.0.X ("Ice Cream Sandwich") suffers from an issue where the popup overlay appears to be z-indexed
645 above the popup itself when certain other styles exist on the same page -- namely, any element set to `position: fixed` and certain
646 types of input. These issues are reminiscent of previously uncovered bugs in older versions of Android's native browser:
647 https://github.com/scottjehl/Device-Bugs/issues/3
649 This fix closes the following bugs ( I use "closes" with reluctance, and stress that this issue should be revisited as soon as possible ):
651 https://github.com/jquery/jquery-mobile/issues/4816
652 https://github.com/jquery/jquery-mobile/issues/4844
653 https://github.com/jquery/jquery-mobile/issues/4874
656 // TODO sort out why this._page isn't working
657 this.element.closest( ".ui-page" ).addClass( "ui-popup-open" );
660 additionalCondition: true,
661 transition: transition,
663 screenClassToAdd: "in",
664 containerClassToAdd: "in",
665 applyTransition: false,
666 prereqs: this._prereqs
670 _closePrereqScreen: function() {
672 .removeClass( "out" )
673 .addClass( "ui-screen-hidden" );
676 _closePrereqContainer: function() {
678 .removeClass( "reverse out" )
679 .addClass( "ui-selectmenu-hidden" )
680 .removeAttr( "style" );
683 _closePrereqsDone: function() {
684 var self = this, opts = self.options;
686 self._ui.container.removeAttr( "tabindex" );
688 // remove nav bindings if they are still present
689 opts.container.unbind( opts.closeEvents );
691 // unbind click handlers added when history is disabled
692 self.element.undelegate( opts.closeLinkSelector, opts.closeLinkEvents );
694 // remove the global mutex for popups
695 $.mobile.popup.active = undefined;
697 // alert users that the popup is closed
698 self._trigger( "afterclose" );
702 this._ui.container.removeClass( "ui-popup-active" );
703 this._page.removeClass( "ui-popup-open" );
705 this._isOpen = false;
707 // IME hide when popup is closed
708 this.element.find("input").blur();
710 // Count down to triggering "popupafterclose" - we have two prerequisites:
711 // 1. The popup window reverse animation completes (container())
712 // 2. The screen opacity animation completes (screen())
714 $.proxy( this, "_closePrereqScreen" ),
715 $.proxy( this, "_closePrereqContainer" ),
716 $.proxy( this, "_closePrereqsDone" ) );
719 additionalCondition: this._ui.screen.hasClass( "in" ),
720 transition: ( this._currentTransition || this.options.transition ),
722 screenClassToAdd: "out",
723 containerClassToAdd: "reverse out",
724 applyTransition: true,
725 prereqs: this._prereqs
729 _destroy: function() {
732 // hide and remove bindings
735 // Put the element back to where the placeholder was and remove the "ui-popup" class
736 self._setTheme( "none" );
738 .insertAfter( self._ui.placeholder )
739 .removeClass( "ui-popup ui-overlay-shadow ui-corner-all" );
740 self._ui.screen.remove();
741 self._ui.container.remove();
742 self._ui.placeholder.remove();
744 // Unbind handlers that were bound to elements outside self.element (the window, in self case)
745 $.each( self._globalHandlers, function( idx, oneSrc ) {
746 $.each( oneSrc.handler, function( eventType, handler ) {
747 oneSrc.src.unbind( eventType, handler );
752 // any navigation event after a popup is opened should close the popup
753 // NOTE the pagebeforechange is bound to catch navigation events that don't
754 // alter the url (eg, dialogs from popups)
755 _bindContainerClose: function() {
758 self.options.container
759 .one( self.options.closeEvents, $.proxy( self._close, self ));
762 // TODO no clear deliniation of what should be here and
763 // what should be in _open. Seems to be "visual" vs "history" for now
764 open: function( options ) {
765 var self = this, opts = this.options, url, hashkey, activePage, currentIsDialog, hasHash, urlHistory;
766 // self.link = ( $(event.target).attr('data-role') === 'button') ? event.target : $(event.target).closest('[data-role="button"]')[0];
767 // make sure open is idempotent
768 if( $.mobile.popup.active ) {
771 // set the global popup mutex
772 $.mobile.popup.active = this;
777 if ( !options.link ) {
779 self.positionTo = "window";
781 self.link = ( $(event.target).closest('a')[0] || $(event.target).closest('div')[0] );
784 self.link = options.link;
787 self.positionTo = ( options != null && options.positionTo != null ) ? options.positionTo : "origin";
790 if ( $(self.link).jqmData("position-to") !== "window"
791 && self.positionTo !== "window" ) {
793 $(self.element).addClass("ui-ctxpopup");
794 $(self._ui.container).removeClass("ui-popup-container")
795 .addClass("ui-ctxpopup-container");
797 if( self.positionTo !== "origin" ) {
798 $(self._ui.arrow).hide();
800 $(self._ui.arrow).show();
803 $(self._ui.arrow).hide();
804 // apply opacity back screen
805 this._setOverlayTheme( "dim" );
808 && self.positionTo === "origin" ) {
809 options.x = $(self.link).offset().left + $(self.link).outerWidth() / 2;
812 && self.positionTo === "origin" ) {
813 options.y = $(self.link).offset().top + $(self.link).outerHeight() / 2;
815 // if history alteration is disabled close on navigate events
816 // and leave the url as is
817 if( !( opts.history ) ) {
818 self._open( options );
819 self._bindContainerClose();
821 // When histoy is disabled we have to grab the data-rel
822 // back link clicks so we can close the popup instead of
823 // relying on history to do it for us
825 .delegate( opts.closeLinkSelector, opts.closeLinkEvents, function( e ) {
828 // NOTE prevent the browser and navigation handlers from
829 // working with the link's rel=back. This may cause
830 // issues for developers expecting the event to bubble
837 // cache some values for min/readability
838 hashkey = $.mobile.dialogHashKey;
839 activePage = $.mobile.activePage;
840 currentIsDialog = activePage.is( ".ui-dialog" );
841 url = $.mobile.urlHistory.getActive().url;
842 hasHash = ( url.indexOf( hashkey ) > -1 ) && !currentIsDialog;
843 urlHistory = $.mobile.urlHistory;
846 self._open( options );
847 self._bindContainerClose();
851 // if the current url has no dialog hash key proceed as normal
852 // otherwise, if the page is a dialog simply tack on the hash key
853 if ( url.indexOf( hashkey ) === -1 && !currentIsDialog ){
856 url = $.mobile.path.parseLocation().hash + hashkey;
859 // Tack on an extra hashkey if this is the first page and we've just reconstructed the initial hash
860 if ( urlHistory.activeIndex === 0 && url === urlHistory.initialDst ) {
864 // swallow the the initial navigation event, and bind for the next
865 opts.container.one( opts.navigateEvents, function( e ) {
867 self._open( options );
868 self._bindContainerClose();
871 urlHistory.ignoreNextHashChange = currentIsDialog;
873 // Gotta love methods with 1mm args :(
874 urlHistory.addNew( url, undefined, undefined, undefined, "dialog" );
876 // set the new url with (or without) the new dialog hash key
877 $.mobile.path.set( url );
881 // make sure close is idempotent
882 if( !$.mobile.popup.active ){
886 if( this.options.history ) {
895 // TODO this can be moved inside the widget
896 $.mobile.popup.handleLink = function( $link ) {
897 var closestPage = $link.closest( ":jqmData(role='page')" ),
898 scope = ( ( closestPage.length === 0 ) ? $( "body" ) : closestPage ),
899 // NOTE make sure to get only the hash, ie7 (wp7) return the absolute href
900 // in this case ruining the element selection
901 popup = $( $.mobile.path.parseUrl($link.attr( "href" )).hash, scope[0] ),
904 if ( popup.data( "popup" ) ) {
905 offset = $link.offset();
906 popup.popup( "open", {
907 x: offset.left + $link.outerWidth() / 2,
908 y: offset.top + $link.outerHeight() / 2,
909 transition: $.mobile.getAttrFixed( $link[0], "data-" + $.mobile.ns + "transition" ),
910 positionTo: $.mobile.getAttrFixed( $link[0], "data-" + $.mobile.ns + "position-to" ),
916 setTimeout( function() {
917 $link.removeClass( $.mobile.activeBtnClass );
921 // TODO move inside _create
922 $.mobile.$document.bind( "pagebeforechange", function( e, data ) {
923 if ( data.options.role === "popup" ) {
924 $.mobile.popup.handleLink( data.options.link );
929 $.mobile.$document.bind( "pagecreate create", function( e ) {
930 $.mobile.popup.prototype.enhanceWithin( e.target, true );
934 //>>excludeStart("jqmBuildExclude", pragmas.jqmBuildExclude);
936 //>>excludeEnd("jqmBuildExclude");