[TemporaryStorage] add files required for SDK build
[samples/web/TemporaryStorage.git] / tizen-web-ui-fw / latest / js / src / widgets / popup.js.orig
1 //>>excludeStart("jqmBuildExclude", pragmas.jqmBuildExclude);
2 //>>description: Popup windows
3 //>>label: Popups
4 //>>group: Widgets
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
7
8 define( [ "jquery",
9         "../jquery.mobile.widget",
10         "../jquery.mobile.support",
11         "../jquery.mobile.navigation",
12         "depend!../jquery.hashchange[jquery]" ], function( $ ) {
13 //>>excludeEnd("jqmBuildExclude");
14 (function( $, undefined ) {
15
16         function fitSegmentInsideSegment( winSize, segSize, offset, desired ) {
17                 var ret = desired;
18
19                 if ( winSize < segSize ) {
20                         // Center segment if it's bigger than the window
21                         ret = offset + ( winSize - segSize ) / 2;
22                 } else {
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 );
25                 }
26
27                 return ret;
28         }
29
30         function windowCoords() {
31                 var $win = $.mobile.$window;
32
33                 return {
34                         x: $win.scrollLeft(),
35                         y: $win.scrollTop(),
36                         cx: ( window.innerWidth || $win.width() ),
37                         cy: ( window.innerHeight || $win.height() )
38                 };
39         }
40
41         $.widget( "mobile.popup", $.mobile.widget, {
42                 options: {
43                         theme: null,
44                         overlayTheme: null,
45                         shadow: true,
46                         corners: true,
47                         transition: "pop",
48                         positionTo: "origin",
49                         tolerance: null,
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",
55
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
59                         //
60                         // NOTE this option is modified in _create!
61                         history: false
62                 },
63
64                 _eatEventAndClose: function( e ) {
65                         e.preventDefault();
66                         e.stopImmediatePropagation();
67                         this.close();
68                         return false;
69                 },
70
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 );
74
75                         this._ui.screen.removeAttr( "style" );
76                         if ( popupHeight > this._ui.screen.height() ) {
77                                 this._ui.screen.height( popupHeight );
78                         }
79                 },
80
81                 _handleWindowKeyUp: function( e ) {
82                         if ( this._isOpen && e.keyCode === $.mobile.keyCode.ESCAPE ) {
83                                 return this._eatEventAndClose( e );
84                         }
85                 },
86
87                 _maybeRefreshTimeout: function() {
88                         var winCoords = windowCoords();
89
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
96                                         return false;
97                                 } else {
98                                         // clear existing timeout - it will be refreshed below
99                                         clearTimeout( this._resizeData.timeoutId );
100                                 }
101                         }
102
103                         this._resizeData = {
104                                 timeoutId: setTimeout( $.proxy( this, "_resizeTimeout" ), 200 ),
105                                 winCoords: winCoords
106                         };
107
108                         return true;
109                 },
110
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" );
115                                 this._ui.container
116                                         .removeClass( "ui-selectmenu-hidden" )
117                                         .offset( this._placementCoords( this._desiredCoords( undefined, undefined, "window" ) ) );
118
119                                 this._resizeScreen();
120                                 this._resizeData = null;
121                                 this._orientationchangeInProgress = false;
122                         }
123                 },
124
125                 _handleWindowResize: function( e ) {
126                         if ( this._isOpen ) {
127                                 // Context popup close when Window resize event
128                                 if( this.positionTo !== "window" ) {
129                                         this.close();
130                                         return false;
131                                 }
132                                 this._maybeRefreshTimeout();
133                         }
134                 },
135
136                 _handleWindowOrientationchange: function( e ) {
137
138                         if ( !this._orientationchangeInProgress ) {
139                                 // effectively rapid-close the popup while leaving the screen intact
140                                 this._ui.container
141                                         .addClass( "ui-selectmenu-hidden" )
142                                         .removeAttr( "style" );
143
144                                 this._orientationchangeInProgress = true;
145                         }
146                 },
147
148                 _create: function() {
149                         var ui = {
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>")
154                                 },
155                                 thisPage = this.element.closest( ".ui-page" ),
156                                 myId = this.element.attr( "id" ),
157                                 self = this;
158
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;
163
164                         if ( thisPage.length === 0 ) {
165                                 thisPage = $( "body" );
166                         }
167
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;
171
172                         // Apply the proto
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 );
177                         if ( myId ) {
178                                 ui.screen.attr( "id", myId + "-screen" );
179                                 ui.container.attr( "id", myId + "-popup" );
180                                 ui.placeholder.html( "<!-- placeholder for " + myId + " -->" );
181                         }
182                         ui.container.append( this.element );
183                         ui.container.append( ui.arrow );
184                         // Add class to popup element
185                         this.element.addClass( "ui-popup" );
186
187                         // Define instance variables
188                         $.extend( this, {
189                                 _page: thisPage,
190                                 _ui: ui,
191                                 _fallbackTransition: "",
192                                 _currentTransition: false,
193                                 _prereqs: null,
194                                 _isOpen: false,
195                                 _tolerance: null,
196                                 _resizeData: null,
197                                 _orientationchangeInProgress: false,
198                                 _globalHandlers: [
199                                         {
200                                                 src: $.mobile.$window,
201                                                 handler: {
202                                                         orientationchange: $.proxy( this, "_handleWindowOrientationchange" ),
203                                                         resize: $.proxy( this, "_handleWindowResize" ),
204                                                         keyup: $.proxy( this, "_handleWindowKeyUp" )
205                                                 }
206                                         }
207                                 ]
208                         });
209
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 );
215                         });
216
217                         ui.screen.bind( "vclick", $.proxy( this, "_eatEventAndClose" ) );
218
219                         $.each( this._globalHandlers, function( idx, value ) {
220                                 value.src.bind( value.handler );
221                         });
222                 },
223
224                 _applyTheme: function( dst, theme, prefix ) {
225                         var classes = ( dst.attr( "class" ) || "").split( " " ),
226                                 alreadyAdded = true,
227                                 currentTheme = null,
228                                 matches,
229                                 themeStr = String( theme );
230
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 ];
236                                         break;
237                                 } else {
238                                         currentTheme = null;
239                                 }
240                         }
241
242                         if ( theme !== currentTheme ) {
243                                 dst.removeClass( "ui-" + prefix + "-" + currentTheme );
244                                 if ( ! ( theme === null || theme === "none" ) ) {
245                                         dst.addClass( "ui-" + prefix + "-" + themeStr );
246                                 }
247                         }
248                 },
249
250                 _setTheme: function( value ) {
251                         this._applyTheme( this.element, value, "body" );
252                 },
253
254                 _setOverlayTheme: function( value ) {
255                         this._applyTheme( this._ui.screen, value, "overlay" );
256
257                         if ( this._isOpen ) {
258                                 this._ui.screen.addClass( "in" );
259                         }
260                 },
261
262                 _setShadow: function( value ) {
263                         this.element.toggleClass( "ui-overlay-shadow", value );
264                 },
265
266                 _setCorners: function( value ) {
267                         this.element.toggleClass( "ui-corner-all", value );
268                 },
269
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 );
275                         }
276                 },
277
278                 _setTransition: function( value ) {
279                         if ( !this._currentTransition ) {
280                                 this._applyTransition( value );
281                         }
282                 },
283
284                 _setTolerance: function( value ) {
285                         var tol = { t: 5, r: 5, b: 5, l: 5 };
286
287                         if ( value ) {
288                                 var ar = String( value ).split( "," );
289
290                                 $.each( ar, function( idx, val ) { ar[ idx ] = parseInt( val, 10 ); } );
291
292                                 switch( ar.length ) {
293                                         // All values are to be the same
294                                         case 1:
295                                                 if ( !isNaN( ar[ 0 ] ) ) {
296                                                         tol.t = tol.r = tol.b = tol.l = ar[ 0 ];
297                                                 }
298                                                 break;
299
300                                         // The first value denotes top/bottom tolerance, and the second value denotes left/right tolerance
301                                         case 2:
302                                                 if ( !isNaN( ar[ 0 ] ) ) {
303                                                         tol.t = tol.b = ar[ 0 ];
304                                                 }
305                                                 if ( !isNaN( ar[ 1 ] ) ) {
306                                                         tol.l = tol.r = ar[ 1 ];
307                                                 }
308                                                 break;
309
310                                         // The array contains values in the order top, right, bottom, left
311                                         case 4:
312                                                 if ( !isNaN( ar[ 0 ] ) ) {
313                                                         tol.t = ar[ 0 ];
314                                                 }
315                                                 if ( !isNaN( ar[ 1 ] ) ) {
316                                                         tol.r = ar[ 1 ];
317                                                 }
318                                                 if ( !isNaN( ar[ 2 ] ) ) {
319                                                         tol.b = ar[ 2 ];
320                                                 }
321                                                 if ( !isNaN( ar[ 3 ] ) ) {
322                                                         tol.l = ar[ 3 ];
323                                                 }
324                                                 break;
325
326                                         default:
327                                                 break;
328                                 }
329                         }
330
331                         this._tolerance = tol;
332                 },
333
334                 _setOption: function( key, value ) {
335                         var exclusions, setter = "_set" + key.charAt( 0 ).toUpperCase() + key.slice( 1 );
336
337                         if ( this[ setter ] !== undefined ) {
338                                 this[ setter ]( value );
339                         }
340
341                         // TODO REMOVE FOR 1.2.1 by moving them out to a default options object
342                         exclusions = [
343                                 "initSelector",
344                                 "closeLinkSelector",
345                                 "closeLinkEvents",
346                                 "navigateEvents",
347                                 "closeEvents",
348                                 "history",
349                                 "container"
350                         ];
351
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 );
356                         }
357                 },
358
359                 // Try and center the overlay over the given coordinates
360                 _placementCoords: function( desired ) {
361                         // rectangle within which the popup must fit
362                         var
363                                 winCoords = windowCoords(),
364                                 rc = {
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
369                                 },
370                                 menuSize, ret,
371                                 linkOffset = $(this.link).offset(),
372                                 positionOffsets = [],
373                                 correctionValue = [0,0],
374                                 arrayIdx;
375
376                         // Clamp the width of the menu before grabbing its size
377                         this._ui.container.css( "max-width", rc.cx );
378                         menuSize = {
379                                 cx: this._ui.container.outerWidth( true ),
380                                 cy: this._ui.container.outerHeight( true )
381                         };
382
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.
385                         ret = {
386                                 x: fitSegmentInsideSegment( rc.cx, menuSize.cx, rc.x, desired.x ),
387                                 y: fitSegmentInsideSegment( rc.cy, menuSize.cy, rc.y, desired.y )
388                         };
389
390                         // Make sure the top of the menu is visible
391                         ret.y = Math.max( 0, ret.y );
392
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
395
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 );
399
400                         ret.y -= Math.min( ret.y, Math.max( 0, ret.y + menuSize.cy - docHeight ) );
401
402                         if ( this.positionTo !== "origin" )
403                         {
404                                 return { left: ret.x, top: ret.y , arrowleft: 0 , arrowtop: 0};
405                         }
406
407                         positionOffsets = [ linkOffset.left,
408                                                                 linkOffset.top,
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));
412
413                         switch( arrayIdx )
414                         {
415                                 case 0:
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" )
421                                         break;
422                                 case 1:
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" );
428                                         break;
429                                 case 2:
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");
435                                         break;
436                                 case 3:
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");
442                                         break;
443                         }
444
445                         return { left: ret.x + correctionValue[0], top: ret.y + correctionValue[1] , arrowleft: arrowleft , arrowtop: arrowtop };
446                 },
447
448                 _createPrereqs: function( screenPrereq, containerPrereq, whenDone ) {
449                         var self = this, prereqs;
450
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.
459
460                         prereqs = {
461                                 screen: $.Deferred(),
462                                 container: $.Deferred()
463                         };
464
465                         prereqs.screen.then( function() {
466                                 if ( prereqs === self._prereqs ) {
467                                         screenPrereq();
468                                 }
469                         });
470
471                         prereqs.container.then( function() {
472                                 if ( prereqs === self._prereqs ) {
473                                         containerPrereq();
474                                 }
475                         });
476
477                         $.when( prereqs.screen, prereqs.container ).done( function() {
478                                 if ( prereqs === self._prereqs ) {
479                                         self._prereqs = null;
480                                         whenDone();
481                                 }
482                         });
483
484                         self._prereqs = prereqs;
485                 },
486
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
492                         this._ui.screen
493                                 .removeClass( args.classToRemove )
494                                 .addClass( args.screenClassToAdd );
495
496                         args.prereqs.screen.resolve();
497
498                         if ( args.transition && args.transition !== "none" ) {
499                                 if ( args.applyTransition ) {
500                                         this._applyTransition( args.transition );
501                                 }
502                                 this._ui.container
503                                         .animationComplete( $.proxy( args.prereqs.container, "resolve" ) )
504                                         .addClass( args.containerClassToAdd )
505                                         .removeClass( args.classToRemove );
506                         } else {
507                                 args.prereqs.container.resolve();
508                         }
509                 },
510
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();
516
517                         self.positionTo = positionTo;
518
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;
524                                 } else {
525                                         try {
526                                                 dst = $( positionTo );
527                                         } catch( e ) {
528                                                 dst = null;
529                                         }
530                                         if ( dst ) {
531                                                 dst.filter( ":visible" );
532                                                 if ( dst.length === 0 ) {
533                                                         dst = null;
534                                                 }
535                                         }
536                                 }
537                         }
538
539                         // If an element was found, center over it
540                         if ( dst ) {
541                                 offset = dst.offset();
542                                 x = offset.left + dst.outerWidth() / 2;
543                                 y = offset.top + dst.outerHeight() / 2;
544                         }
545
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;
549                         }
550                         if ( $.type( y ) !== "number" || isNaN( y ) ) {
551                                 y = winCoords.cy / 2 + winCoords.y;
552                         }
553
554                         return { x: x, y: y };
555                 },
556
557                 _reposition: function() {
558                         var self = this,
559                                         coords;
560
561                         if( self._isOpen
562                                 && self.link
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" ) );
565                                         self._ui.container
566                                                 .offset( { top : coords.top } );
567                         }
568                 },
569
570                 _openPrereqsComplete: function() {
571                         var self = this;
572
573                         self._ui.container.addClass( "ui-popup-active" );
574                         self._isOpen = true;
575                         self._resizeScreen();
576
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" );
583                                 self._reposition();
584                         });
585                 },
586
587                 _open: function( options ) {
588                         var coords, transition,
589                                 androidBlacklist = ( function() {
590                                         var w = window,
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;
598
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 ) {
601                                                 return true;
602                                         }
603                                         return false;
604                                 }());
605
606                         // Make sure options is defined
607                         options = ( options || {} );
608
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;
611
612                         // Give applications a chance to modify the contents of the container before it appears
613                         this._trigger( "beforeposition" );
614
615                         coords = this._placementCoords( this._desiredCoords( options.x, options.y, options.positionTo || this.options.positionTo || "origin" ) );
616
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())
620                         this._createPrereqs(
621                                 $.noop,
622                                 $.noop,
623                                 $.proxy( this, "_openPrereqsComplete" ) );
624
625                         if ( transition ) {
626                                 this._currentTransition = transition;
627                                 this._applyTransition( transition );
628                         } else {
629                                 transition = this.options.transition;
630                         }
631
632                         if ( !this.options.theme ) {
633                                 this._setTheme( this._page.jqmData( "theme" ) || $.mobile.getInheritedTheme( this._page, "c" ) );
634                         }
635
636                         this._ui.screen.removeClass( "ui-screen-hidden" );
637
638                         this._ui.container
639                                 .removeClass( "ui-selectmenu-hidden" )
640                                 .offset( coords );
641                         this._ui.arrow.css( { top : coords.arrowtop, left : coords.arrowleft } );
642                         if ( this.options.overlayTheme && androidBlacklist ) {
643                                 /* TODO:
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
648
649                                 This fix closes the following bugs ( I use "closes" with reluctance, and stress that this issue should be revisited as soon as possible ):
650
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
654                                 */
655
656                                 // TODO sort out why this._page isn't working
657                                 this.element.closest( ".ui-page" ).addClass( "ui-popup-open" );
658                         }
659                         this._animate({
660                                 additionalCondition: true,
661                                 transition: transition,
662                                 classToRemove: "",
663                                 screenClassToAdd: "in",
664                                 containerClassToAdd: "in",
665                                 applyTransition: false,
666                                 prereqs: this._prereqs
667                         });
668                 },
669
670                 _closePrereqScreen: function() {
671                         this._ui.screen
672                                 .removeClass( "out" )
673                                 .addClass( "ui-screen-hidden" );
674                 },
675
676                 _closePrereqContainer: function() {
677                         this._ui.container
678                                 .removeClass( "reverse out" )
679                                 .addClass( "ui-selectmenu-hidden" )
680                                 .removeAttr( "style" );
681                 },
682
683                 _closePrereqsDone: function() {
684                         var self = this, opts = self.options;
685
686                         self._ui.container.removeAttr( "tabindex" );
687
688                         // remove nav bindings if they are still present
689                         opts.container.unbind( opts.closeEvents );
690
691                         // unbind click handlers added when history is disabled
692                         self.element.undelegate( opts.closeLinkSelector, opts.closeLinkEvents );
693
694                         // remove the global mutex for popups
695                         $.mobile.popup.active = undefined;
696
697                         // alert users that the popup is closed
698                         self._trigger( "afterclose" );
699                 },
700
701                 _close: function() {
702                         this._ui.container.removeClass( "ui-popup-active" );
703                         this._page.removeClass( "ui-popup-open" );
704
705                         this._isOpen = false;
706
707                         // IME hide when popup is closed
708                         this.element.find("input").blur();
709
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())
713                         this._createPrereqs(
714                                 $.proxy( this, "_closePrereqScreen" ),
715                                 $.proxy( this, "_closePrereqContainer" ),
716                                 $.proxy( this, "_closePrereqsDone" ) );
717
718                         this._animate( {
719                                 additionalCondition: this._ui.screen.hasClass( "in" ),
720                                 transition: ( this._currentTransition || this.options.transition ),
721                                 classToRemove: "in",
722                                 screenClassToAdd: "out",
723                                 containerClassToAdd: "reverse out",
724                                 applyTransition: true,
725                                 prereqs: this._prereqs
726                         });
727                 },
728
729                 _destroy: function() {
730                         var self = this;
731
732                         // hide and remove bindings
733                         self._close();
734
735                         // Put the element back to where the placeholder was and remove the "ui-popup" class
736                         self._setTheme( "none" );
737                         self.element
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();
743
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 );
748                                 });
749                         });
750                 },
751
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() {
756                         var self = this;
757
758                         self.options.container
759                                 .one( self.options.closeEvents, $.proxy( self._close, self ));
760                 },
761
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 ) {
769                                 return;
770                         }
771                         // set the global popup mutex
772                         $.mobile.popup.active = this;
773                         if( !options ) {
774                                 options = [];
775                         }
776
777                         if ( !options.link ) {
778                                 if ( !event ) {
779                                         self.positionTo = "window";
780                                 } else {
781                                         self.link = ( $(event.target).closest('a')[0] || $(event.target).closest('div')[0] );
782                                 }
783                         } else {
784                                 self.link = options.link;
785                         }
786                         if ( event ) {
787                                 self.positionTo = ( options != null && options.positionTo != null ) ? options.positionTo : "origin";
788                         }
789
790                         if ( $(self.link).jqmData("position-to") !== "window"
791                                         && self.positionTo !== "window" ) {
792
793                                 $(self.element).addClass("ui-ctxpopup");
794                                 $(self._ui.container).removeClass("ui-popup-container")
795                                         .addClass("ui-ctxpopup-container");
796
797                                 if( self.positionTo !== "origin" ) {
798                                         $(self._ui.arrow).hide();
799                                 } else {
800                                         $(self._ui.arrow).show();
801                                 }
802                         } else {
803                                 $(self._ui.arrow).hide();
804                                 // apply opacity back screen
805                                 this._setOverlayTheme( "dim" );
806                         }
807                         if( !options.x
808                                 && self.positionTo === "origin" ) {
809                                 options.x = $(self.link).offset().left + $(self.link).outerWidth() / 2;
810                         }
811                         if( !options.y
812                                 && self.positionTo === "origin" ) {
813                                 options.y = $(self.link).offset().top + $(self.link).outerHeight() / 2;
814                         }
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();
820
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
824                                 self.element
825                                         .delegate( opts.closeLinkSelector, opts.closeLinkEvents, function( e ) {
826                                                 self._close();
827
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
831                                                 return false;
832                                         });
833
834                                 return;
835                         }
836
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;
844
845                         if ( hasHash ) {
846                                 self._open( options );
847                                 self._bindContainerClose();
848                                 return;
849                         }
850
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 ){
854                                 url = url + hashkey;
855                         } else {
856                                 url = $.mobile.path.parseLocation().hash + hashkey;
857                         }
858
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 ) {
861                                 url += hashkey;
862                         }
863
864                         // swallow the the initial navigation event, and bind for the next
865                         opts.container.one( opts.navigateEvents, function( e ) {
866                                 e.preventDefault();
867                                 self._open( options );
868                                 self._bindContainerClose();
869                         });
870
871                         urlHistory.ignoreNextHashChange = currentIsDialog;
872
873                         // Gotta love methods with 1mm args :(
874                         urlHistory.addNew( url, undefined, undefined, undefined, "dialog" );
875
876                         // set the new url with (or without) the new dialog hash key
877                         $.mobile.path.set( url );
878                 },
879
880                 close: function() {
881                         // make sure close is idempotent
882                         if( !$.mobile.popup.active ){
883                                 return;
884                         }
885
886                         if( this.options.history ) {
887                                 $.mobile.back();
888                         } else {
889                                 this._close();
890                         }
891                 }
892         });
893
894
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] ),
902                         offset;
903
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" ),
911                                 link: $link
912                         });
913                 }
914
915                 //remove after delay
916                 setTimeout( function() {
917                         $link.removeClass( $.mobile.activeBtnClass );
918                 }, 300 );
919         };
920
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 );
925                         e.preventDefault();
926                 }
927         });
928
929         $.mobile.$document.bind( "pagecreate create", function( e )  {
930                 $.mobile.popup.prototype.enhanceWithin( e.target, true );
931         });
932
933 })( jQuery );
934 //>>excludeStart("jqmBuildExclude", pragmas.jqmBuildExclude);
935 });
936 //>>excludeEnd("jqmBuildExclude");