1 (function( $, undefined ) {
3 function fitSegmentInsideSegment( winSize, segSize, offset, desired ) {
6 if ( winSize < segSize ) {
7 // Center segment if it's bigger than the window
8 ret = offset + ( winSize - segSize ) / 2;
10 // Otherwise center it at the desired coordinate while keeping it completely inside the window
11 ret = Math.min( Math.max( offset, desired - segSize / 2 ), offset + winSize - segSize );
17 function windowCoords() {
18 var $win = $.mobile.$window;
23 cx: ( window.innerWidth || $win.width() ),
24 cy: ( window.innerHeight || $win.height() )
28 $.widget( "mobile.popup", $.mobile.widget, {
37 initSelector: ":jqmData(role='popup')",
38 closeLinkSelector: "a:jqmData(rel='back')",
39 closeLinkEvents: "click.popup",
40 navigateEvents: "navigate.popup",
41 closeEvents: "navigate.popup pagebeforechange.popup",
43 // NOTE Windows Phone 7 has a scroll position caching issue that
44 // requires us to disable popup history management by default
45 // https://github.com/jquery/jquery-mobile/issues/4784
47 // NOTE this option is modified in _create!
51 _eatEventAndClose: function( e ) {
53 e.stopImmediatePropagation();
58 // Make sure the screen size is increased beyond the page height if the popup's causes the document to increase in height
59 _resizeScreen: function() {
60 var popupHeight = this._ui.container.outerHeight( true );
62 this._ui.screen.removeAttr( "style" );
63 if ( popupHeight > this._ui.screen.height() ) {
64 this._ui.screen.height( popupHeight );
68 _handleWindowKeyUp: function( e ) {
69 if ( this._isOpen && e.keyCode === $.mobile.keyCode.ESCAPE ) {
70 return this._eatEventAndClose( e );
74 _maybeRefreshTimeout: function() {
75 var winCoords = windowCoords();
77 if ( this._resizeData ) {
78 if ( winCoords.x === this._resizeData.winCoords.x &&
79 winCoords.y === this._resizeData.winCoords.y &&
80 winCoords.cx === this._resizeData.winCoords.cx &&
81 winCoords.cy === this._resizeData.winCoords.cy ) {
82 // timeout not refreshed
85 // clear existing timeout - it will be refreshed below
86 clearTimeout( this._resizeData.timeoutId );
91 timeoutId: setTimeout( $.proxy( this, "_resizeTimeout" ), 200 ),
98 _resizeTimeout: function() {
99 if ( !this._maybeRefreshTimeout() && this.positionTo === "window" ) {
100 // effectively rapid-open the popup while leaving the screen intact
101 this._trigger( "beforeposition" );
103 .removeClass( "ui-selectmenu-hidden" )
104 .offset( this._placementCoords( this._desiredCoords( undefined, undefined, "window" ) ) );
106 this._resizeScreen();
107 this._resizeData = null;
108 this._orientationchangeInProgress = false;
112 _handleWindowResize: function( e ) {
113 if ( this._isOpen ) {
114 // Context popup close when Window resize event
115 if( this.positionTo !== "window" ) {
119 this._maybeRefreshTimeout();
123 _handleWindowOrientationchange: function( e ) {
125 if ( !this._orientationchangeInProgress ) {
126 // effectively rapid-close the popup while leaving the screen intact
128 .addClass( "ui-selectmenu-hidden" )
129 .removeAttr( "style" );
131 this._orientationchangeInProgress = true;
135 _create: function() {
137 screen: $( "<div class='ui-screen-hidden ui-popup-screen'></div>" ),
138 placeholder: $( "<div style='display: none;'><!-- placeholder --></div>" ),
139 container: $( "<div class='ui-popup-container ui-selectmenu-hidden'></div>" ),
140 arrow : $("<div class='ui-arrow'></div>")
142 thisPage = this.element.closest( ".ui-page" ),
143 myId = this.element.attr( "id" ),
146 // We need to adjust the history option to be false if there's no AJAX nav.
147 // We can't do it in the option declarations because those are run before
148 // it is determined whether there shall be AJAX nav.
149 this.options.history = this.options.history && $.mobile.ajaxEnabled && $.mobile.hashListeningEnabled;
151 if ( thisPage.length === 0 ) {
152 thisPage = $( "body" );
155 // define the container for navigation event bindings
156 // TODO this would be nice at the the mobile widget level
157 this.options.container = this.options.container || $.mobile.pageContainer;
160 thisPage.append( ui.screen );
161 ui.container.insertAfter( ui.screen );
162 // Leave a placeholder where the element used to be
163 ui.placeholder.insertAfter( this.element );
165 ui.screen.attr( "id", myId + "-screen" );
166 ui.container.attr( "id", myId + "-popup" );
167 ui.placeholder.html( "<!-- placeholder for " + myId + " -->" );
169 ui.container.append( this.element );
170 ui.container.append( ui.arrow );
171 // Add class to popup element
172 this.element.addClass( "ui-popup" );
174 // Define instance variables
178 _fallbackTransition: "",
179 _currentTransition: false,
184 _orientationchangeInProgress: false,
187 src: $.mobile.$window,
189 orientationchange: $.proxy( this, "_handleWindowOrientationchange" ),
190 resize: $.proxy( this, "_handleWindowResize" ),
191 keyup: $.proxy( this, "_handleWindowKeyUp" )
197 $.each( this.options, function( key, value ) {
198 // Cause initial options to be applied by their handler by temporarily setting the option to undefined
199 // - the handler then sets it to the initial value
200 self.options[ key ] = undefined;
201 self._setOption( key, value, true );
204 ui.screen.bind( "vclick", $.proxy( this, "_eatEventAndClose" ) );
206 $.each( this._globalHandlers, function( idx, value ) {
207 value.src.bind( value.handler );
211 _applyTheme: function( dst, theme, prefix ) {
212 var classes = ( dst.attr( "class" ) || "").split( " " ),
216 themeStr = String( theme );
218 while ( classes.length > 0 ) {
219 currentTheme = classes.pop();
220 matches = ( new RegExp( "^ui-" + prefix + "-([a-z])$" ) ).exec( currentTheme );
221 if ( matches && matches.length > 1 ) {
222 currentTheme = matches[ 1 ];
229 if ( theme !== currentTheme ) {
230 dst.removeClass( "ui-" + prefix + "-" + currentTheme );
231 if ( ! ( theme === null || theme === "none" ) ) {
232 dst.addClass( "ui-" + prefix + "-" + themeStr );
237 _setTheme: function( value ) {
238 this._applyTheme( this.element, value, "body" );
241 _setOverlayTheme: function( value ) {
242 this._applyTheme( this._ui.screen, value, "overlay" );
244 if ( this._isOpen ) {
245 this._ui.screen.addClass( "in" );
249 _setShadow: function( value ) {
250 this.element.toggleClass( "ui-overlay-shadow", value );
253 _setCorners: function( value ) {
254 this.element.toggleClass( "ui-corner-all", value );
257 _applyTransition: function( value ) {
258 this._ui.container.removeClass( this._fallbackTransition );
259 if ( value && value !== "none" ) {
260 this._fallbackTransition = $.mobile._maybeDegradeTransition( value );
261 this._ui.container.addClass( this._fallbackTransition );
265 _setTransition: function( value ) {
266 if ( !this._currentTransition ) {
267 this._applyTransition( value );
271 _setTolerance: function( value ) {
272 var tol = { t: 5, r: 5, b: 5, l: 5 };
275 var ar = String( value ).split( "," );
277 $.each( ar, function( idx, val ) { ar[ idx ] = parseInt( val, 10 ); } );
279 switch( ar.length ) {
280 // All values are to be the same
282 if ( !isNaN( ar[ 0 ] ) ) {
283 tol.t = tol.r = tol.b = tol.l = ar[ 0 ];
287 // The first value denotes top/bottom tolerance, and the second value denotes left/right tolerance
289 if ( !isNaN( ar[ 0 ] ) ) {
290 tol.t = tol.b = ar[ 0 ];
292 if ( !isNaN( ar[ 1 ] ) ) {
293 tol.l = tol.r = ar[ 1 ];
297 // The array contains values in the order top, right, bottom, left
299 if ( !isNaN( ar[ 0 ] ) ) {
302 if ( !isNaN( ar[ 1 ] ) ) {
305 if ( !isNaN( ar[ 2 ] ) ) {
308 if ( !isNaN( ar[ 3 ] ) ) {
318 this._tolerance = tol;
321 _setOption: function( key, value ) {
322 var exclusions, setter = "_set" + key.charAt( 0 ).toUpperCase() + key.slice( 1 );
324 if ( this[ setter ] !== undefined ) {
325 this[ setter ]( value );
328 // TODO REMOVE FOR 1.2.1 by moving them out to a default options object
339 $.mobile.widget.prototype._setOption.apply( this, arguments );
340 if ( $.inArray( key, exclusions ) === -1 ) {
341 // Record the option change in the options and in the DOM data-* attributes
342 this.element.attr( "data-" + ( $.mobile.ns || "" ) + ( key.replace( /([A-Z])/, "-$1" ).toLowerCase() ), value );
346 // Try and center the overlay over the given coordinates
347 _placementCoords: function( desired ) {
348 // rectangle within which the popup must fit
350 winCoords = windowCoords(),
352 x: this._tolerance.l,
353 y: winCoords.y + this._tolerance.t,
354 cx: winCoords.cx - this._tolerance.l - this._tolerance.r,
355 cy: winCoords.cy - this._tolerance.t - this._tolerance.b
358 linkOffset = $(this.link).offset(),
359 positionOffsets = [],
360 correctionValue = [0,0],
363 // Clamp the width of the menu before grabbing its size
364 this._ui.container.css( "max-width", rc.cx );
366 cx: this._ui.container.outerWidth( true ),
367 cy: this._ui.container.outerHeight( true )
370 // Center the menu over the desired coordinates, while not going outside
371 // the window tolerances. This will center wrt. the window if the popup is too large.
373 x: fitSegmentInsideSegment( rc.cx, menuSize.cx, rc.x, desired.x ),
374 y: fitSegmentInsideSegment( rc.cy, menuSize.cy, rc.y, desired.y )
377 // Make sure the top of the menu is visible
378 ret.y = Math.max( 0, ret.y );
380 // If the height of the menu is smaller than the height of the document
381 // align the bottom with the bottom of the document
383 // fix for $( document ).height() bug in core 1.7.2.
384 var docEl = document.documentElement, docBody = document.body,
385 docHeight = Math.max( docEl.clientHeight, docBody.scrollHeight, docBody.offsetHeight, docEl.scrollHeight, docEl.offsetHeight );
387 ret.y -= Math.min( ret.y, Math.max( 0, ret.y + menuSize.cy - docHeight ) );
389 if ( this.positionTo !== "origin" )
391 return { left: ret.x, top: ret.y , arrowleft: 0 , arrowtop: 0};
394 positionOffsets = [ linkOffset.left,
396 docEl.clientHeight - ( linkOffset.top + $(this.link).height() ),
397 docEl.clientWidth - ( linkOffset.left + $(this.link).width() )];
398 arrayIdx = positionOffsets.indexOf(Math.max.apply(window,positionOffsets));
403 correctionValue = [ -$(this.link).width() , 0];
404 arrowtop = ( linkOffset.top - ret.y ) + ( $(this.link).height() / 2 ) - parseInt( $(this._ui.arrow).css("border-width") ) ;
405 arrowleft = menuSize.cx;
406 $(this._ui.arrow).attr( "class", "" )
407 .addClass( "ui-arrow left" )
410 correctionValue = [ 0 , -(ret.y + menuSize.cy - linkOffset.top)];
411 arrowtop = menuSize.cy - 2;
412 arrowleft = (linkOffset.left - ret.x + correctionValue[0]) + ( $(this.link).width() / 2 ) - parseInt( $(this._ui.arrow).css("border-width") ) / 2;
413 $(this._ui.arrow).attr( "class", "" )
414 .addClass( "ui-arrow bottom" );
417 correctionValue = [ 0 , ( linkOffset.top + $(this.link).height() - ret.y ) ];
418 arrowtop = - parseInt( $(this._ui.arrow).css("border-width") ) * 2 + 1;
419 arrowleft = (linkOffset.left - ret.x + correctionValue[0]) + ( $(this.link).width() / 2 ) - parseInt( $(this._ui.arrow).css("border-width") ) / 2;
420 $(this._ui.arrow).attr( "class", "" )
421 .addClass("ui-arrow top");
424 correctionValue = [ ( menuSize.cx < $(this.link).width() ) ? ( $(this.link).width() / 2 ) + ( menuSize.cx / 2) : $(this.link).width() , 0];
425 arrowtop = ( linkOffset.top - ret.y ) + ( $(this.link).height() / 2 ) - parseInt( $(this._ui.arrow).css("border-width") ) ;
426 arrowleft = - parseInt( $(this._ui.arrow).css("border-width") ) * 2;
427 $(this._ui.arrow).attr( "class", "" )
428 .addClass("ui-arrow right");
432 return { left: ret.x + correctionValue[0], top: ret.y + correctionValue[1] , arrowleft: arrowleft , arrowtop: arrowtop };
435 _createPrereqs: function( screenPrereq, containerPrereq, whenDone ) {
436 var self = this, prereqs;
438 // It is important to maintain both the local variable prereqs and self._prereqs. The local variable remains in
439 // the closure of the functions which call the callbacks passed in. The comparison between the local variable and
440 // self._prereqs is necessary, because once a function has been passed to .animationComplete() it will be called
441 // next time an animation completes, even if that's not the animation whose end the function was supposed to catch
442 // (for example, if an abort happens during the opening animation, the .animationComplete handler is not called for
443 // that animation anymore, but the handler remains attached, so it is called the next time the popup is opened
444 // - making it stale. Comparing the local variable prereqs to the widget-level variable self._prereqs ensures that
445 // callbacks triggered by a stale .animationComplete will be ignored.
448 screen: $.Deferred(),
449 container: $.Deferred()
452 prereqs.screen.then( function() {
453 if ( prereqs === self._prereqs ) {
458 prereqs.container.then( function() {
459 if ( prereqs === self._prereqs ) {
464 $.when( prereqs.screen, prereqs.container ).done( function() {
465 if ( prereqs === self._prereqs ) {
466 self._prereqs = null;
471 self._prereqs = prereqs;
474 _animate: function( args ) {
475 // NOTE before removing the default animation of the screen
476 // this had an animate callback that would relove the deferred
477 // now the deferred is resolved immediately
478 // TODO remove the dependency on the screen deferred
480 .removeClass( args.classToRemove )
481 .addClass( args.screenClassToAdd );
483 args.prereqs.screen.resolve();
485 if ( args.transition && args.transition !== "none" ) {
486 if ( args.applyTransition ) {
487 this._applyTransition( args.transition );
490 .animationComplete( $.proxy( args.prereqs.container, "resolve" ) )
491 .addClass( args.containerClassToAdd )
492 .removeClass( args.classToRemove );
494 args.prereqs.container.resolve();
498 // The desired coordinates passed in will be returned untouched if no reference element can be identified via
499 // desiredPosition.positionTo. Nevertheless, this function ensures that its return value always contains valid
500 // x and y coordinates by specifying the center middle of the window if the coordinates are absent.
501 _desiredCoords: function( x, y, positionTo ) {
502 var dst = null, offset, winCoords = windowCoords();
504 self.positionTo = positionTo;
506 // Establish which element will serve as the reference
507 if ( positionTo && positionTo !== "origin" ) {
508 if ( positionTo === "window" ) {
509 x = winCoords.cx / 2 + winCoords.x;
510 y = winCoords.cy / 2 + winCoords.y;
513 dst = $( positionTo );
518 dst.filter( ":visible" );
519 if ( dst.length === 0 ) {
526 // If an element was found, center over it
528 offset = dst.offset();
529 x = offset.left + dst.outerWidth() / 2;
530 y = offset.top + dst.outerHeight() / 2;
533 // Make sure x and y are valid numbers - center over the window
534 if ( $.type( x ) !== "number" || isNaN( x ) ) {
535 x = winCoords.cx / 2 + winCoords.x;
537 if ( $.type( y ) !== "number" || isNaN( y ) ) {
538 y = winCoords.cy / 2 + winCoords.y;
541 return { x: x, y: y };
544 _reposition: function() {
550 && self.positionTo !== "window") {
551 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" ) );
553 .offset( { top : coords.top } );
557 _openPrereqsComplete: function() {
560 self._ui.container.addClass( "ui-popup-active" );
562 self._resizeScreen();
564 // Android appears to trigger the animation complete before the popup
565 // is visible. Allowing the stack to unwind before applying focus prevents
566 // the "blue flash" of element focus in android 4.0
567 setTimeout(function(){
568 self._ui.container.attr( "tabindex", "0" ).focus();
569 self._trigger( "afteropen" );
574 _open: function( options ) {
575 var coords, transition,
576 androidBlacklist = ( function() {
578 ua = navigator.userAgent,
579 // Rendering engine is Webkit, and capture major version
580 wkmatch = ua.match( /AppleWebKit\/([0-9\.]+)/ ),
581 wkversion = !!wkmatch && wkmatch[ 1 ],
582 androidmatch = ua.match( /Android (\d+(?:\.\d+))/ ),
583 andversion = !!androidmatch && androidmatch[ 1 ],
584 chromematch = ua.indexOf( "Chrome" ) > -1;
586 // Platform is Android, WebKit version is greater than 534.13 ( Android 3.2.1 ) and not Chrome.
587 if( androidmatch !== null && andversion === "4.0" && wkversion && wkversion > 534.13 && !chromematch ) {
593 // Make sure options is defined
594 options = ( options || {} );
596 // Copy out the transition, because we may be overwriting it later and we don't want to pass that change back to the caller
597 transition = options.transition || this.options.transition;
599 // Give applications a chance to modify the contents of the container before it appears
600 this._trigger( "beforeposition" );
602 coords = this._placementCoords( this._desiredCoords( options.x, options.y, options.positionTo || this.options.positionTo || "origin" ) );
604 // Count down to triggering "popupafteropen" - we have two prerequisites:
605 // 1. The popup window animation completes (container())
606 // 2. The screen opacity animation completes (screen())
610 $.proxy( this, "_openPrereqsComplete" ) );
613 this._currentTransition = transition;
614 this._applyTransition( transition );
616 transition = this.options.transition;
619 if ( !this.options.theme ) {
620 this._setTheme( this._page.jqmData( "theme" ) || $.mobile.getInheritedTheme( this._page, "c" ) );
623 this._ui.screen.removeClass( "ui-screen-hidden" );
626 .removeClass( "ui-selectmenu-hidden" )
628 this._ui.arrow.css( { top : coords.arrowtop, left : coords.arrowleft } );
629 if ( this.options.overlayTheme && androidBlacklist ) {
631 The native browser on Android 4.0.X ("Ice Cream Sandwich") suffers from an issue where the popup overlay appears to be z-indexed
632 above the popup itself when certain other styles exist on the same page -- namely, any element set to `position: fixed` and certain
633 types of input. These issues are reminiscent of previously uncovered bugs in older versions of Android's native browser:
634 https://github.com/scottjehl/Device-Bugs/issues/3
636 This fix closes the following bugs ( I use "closes" with reluctance, and stress that this issue should be revisited as soon as possible ):
638 https://github.com/jquery/jquery-mobile/issues/4816
639 https://github.com/jquery/jquery-mobile/issues/4844
640 https://github.com/jquery/jquery-mobile/issues/4874
643 // TODO sort out why this._page isn't working
644 this.element.closest( ".ui-page" ).addClass( "ui-popup-open" );
647 additionalCondition: true,
648 transition: transition,
650 screenClassToAdd: "in",
651 containerClassToAdd: "in",
652 applyTransition: false,
653 prereqs: this._prereqs
657 _closePrereqScreen: function() {
659 .removeClass( "out" )
660 .addClass( "ui-screen-hidden" );
663 _closePrereqContainer: function() {
665 .removeClass( "reverse out" )
666 .addClass( "ui-selectmenu-hidden" )
667 .removeAttr( "style" );
670 _closePrereqsDone: function() {
671 var self = this, opts = self.options;
673 self._ui.container.removeAttr( "tabindex" );
675 // remove nav bindings if they are still present
676 opts.container.unbind( opts.closeEvents );
678 // unbind click handlers added when history is disabled
679 self.element.undelegate( opts.closeLinkSelector, opts.closeLinkEvents );
681 // remove the global mutex for popups
682 $.mobile.popup.active = undefined;
684 // alert users that the popup is closed
685 self._trigger( "afterclose" );
689 this._ui.container.removeClass( "ui-popup-active" );
690 this._page.removeClass( "ui-popup-open" );
692 this._isOpen = false;
694 // IME hide when popup is closed
695 this.element.find("input").blur();
697 // Count down to triggering "popupafterclose" - we have two prerequisites:
698 // 1. The popup window reverse animation completes (container())
699 // 2. The screen opacity animation completes (screen())
701 $.proxy( this, "_closePrereqScreen" ),
702 $.proxy( this, "_closePrereqContainer" ),
703 $.proxy( this, "_closePrereqsDone" ) );
706 additionalCondition: this._ui.screen.hasClass( "in" ),
707 transition: ( this._currentTransition || this.options.transition ),
709 screenClassToAdd: "out",
710 containerClassToAdd: "reverse out",
711 applyTransition: true,
712 prereqs: this._prereqs
716 _destroy: function() {
719 // hide and remove bindings
722 // Put the element back to where the placeholder was and remove the "ui-popup" class
723 self._setTheme( "none" );
725 .insertAfter( self._ui.placeholder )
726 .removeClass( "ui-popup ui-overlay-shadow ui-corner-all" );
727 self._ui.screen.remove();
728 self._ui.container.remove();
729 self._ui.placeholder.remove();
731 // Unbind handlers that were bound to elements outside self.element (the window, in self case)
732 $.each( self._globalHandlers, function( idx, oneSrc ) {
733 $.each( oneSrc.handler, function( eventType, handler ) {
734 oneSrc.src.unbind( eventType, handler );
739 // any navigation event after a popup is opened should close the popup
740 // NOTE the pagebeforechange is bound to catch navigation events that don't
741 // alter the url (eg, dialogs from popups)
742 _bindContainerClose: function() {
745 self.options.container
746 .one( self.options.closeEvents, $.proxy( self._close, self ));
749 // TODO no clear deliniation of what should be here and
750 // what should be in _open. Seems to be "visual" vs "history" for now
751 open: function( options ) {
752 var self = this, opts = this.options, url, hashkey, activePage, currentIsDialog, hasHash, urlHistory;
753 // self.link = ( $(event.target).attr('data-role') === 'button') ? event.target : $(event.target).closest('[data-role="button"]')[0];
754 // make sure open is idempotent
755 if( $.mobile.popup.active ) {
758 // set the global popup mutex
759 $.mobile.popup.active = this;
764 if ( !options.link ) {
766 self.positionTo = "window";
768 self.link = ( $(event.target).closest('a')[0] || $(event.target).closest('div')[0] );
771 self.link = options.link;
774 self.positionTo = ( options != null && options.positionTo != null ) ? options.positionTo : "origin";
777 if ( $(self.link).jqmData("position-to") !== "window"
778 && self.positionTo !== "window" ) {
780 $(self.element).addClass("ui-ctxpopup");
781 $(self._ui.container).removeClass("ui-popup-container")
782 .addClass("ui-ctxpopup-container");
784 if( self.positionTo !== "origin" ) {
785 $(self._ui.arrow).hide();
787 $(self._ui.arrow).show();
790 $(self._ui.arrow).hide();
791 // apply opacity back screen
792 this._setOverlayTheme( "dim" );
795 && self.positionTo === "origin" ) {
796 options.x = $(self.link).offset().left + $(self.link).outerWidth() / 2;
799 && self.positionTo === "origin" ) {
800 options.y = $(self.link).offset().top + $(self.link).outerHeight() / 2;
802 // if history alteration is disabled close on navigate events
803 // and leave the url as is
804 if( !( opts.history ) ) {
805 self._open( options );
806 self._bindContainerClose();
808 // When histoy is disabled we have to grab the data-rel
809 // back link clicks so we can close the popup instead of
810 // relying on history to do it for us
812 .delegate( opts.closeLinkSelector, opts.closeLinkEvents, function( e ) {
815 // NOTE prevent the browser and navigation handlers from
816 // working with the link's rel=back. This may cause
817 // issues for developers expecting the event to bubble
824 // cache some values for min/readability
825 hashkey = $.mobile.dialogHashKey;
826 activePage = $.mobile.activePage;
827 currentIsDialog = activePage.is( ".ui-dialog" );
828 url = $.mobile.urlHistory.getActive().url;
829 hasHash = ( url.indexOf( hashkey ) > -1 ) && !currentIsDialog;
830 urlHistory = $.mobile.urlHistory;
833 self._open( options );
834 self._bindContainerClose();
838 // if the current url has no dialog hash key proceed as normal
839 // otherwise, if the page is a dialog simply tack on the hash key
840 if ( url.indexOf( hashkey ) === -1 && !currentIsDialog ){
843 url = $.mobile.path.parseLocation().hash + hashkey;
846 // Tack on an extra hashkey if this is the first page and we've just reconstructed the initial hash
847 if ( urlHistory.activeIndex === 0 && url === urlHistory.initialDst ) {
851 // swallow the the initial navigation event, and bind for the next
852 opts.container.one( opts.navigateEvents, function( e ) {
854 self._open( options );
855 self._bindContainerClose();
858 urlHistory.ignoreNextHashChange = currentIsDialog;
860 // Gotta love methods with 1mm args :(
861 urlHistory.addNew( url, undefined, undefined, undefined, "dialog" );
863 // set the new url with (or without) the new dialog hash key
864 $.mobile.path.set( url );
868 // make sure close is idempotent
869 if( !$.mobile.popup.active ){
873 if( this.options.history ) {
882 // TODO this can be moved inside the widget
883 $.mobile.popup.handleLink = function( $link ) {
884 var closestPage = $link.closest( ":jqmData(role='page')" ),
885 scope = ( ( closestPage.length === 0 ) ? $( "body" ) : closestPage ),
886 // NOTE make sure to get only the hash, ie7 (wp7) return the absolute href
887 // in this case ruining the element selection
888 popup = $( $.mobile.path.parseUrl($link.attr( "href" )).hash, scope[0] ),
891 if ( popup.data( "popup" ) ) {
892 offset = $link.offset();
893 popup.popup( "open", {
894 x: offset.left + $link.outerWidth() / 2,
895 y: offset.top + $link.outerHeight() / 2,
896 transition: $.mobile.getAttrFixed( $link[0], "data-" + $.mobile.ns + "transition" ),
897 positionTo: $.mobile.getAttrFixed( $link[0], "data-" + $.mobile.ns + "position-to" ),
903 setTimeout( function() {
904 $link.removeClass( $.mobile.activeBtnClass );
908 // TODO move inside _create
909 $.mobile.$document.bind( "pagebeforechange", function( e, data ) {
910 if ( data.options.role === "popup" ) {
911 $.mobile.popup.handleLink( data.options.link );
916 //delegate self-init widgets
917 $.delegateSelfInitWithSingleSelector( $.mobile.popup, true );