[TemporaryStorage] add files required for SDK build
[samples/web/TemporaryStorage.git] / tizen-web-ui-fw / latest / js / src / widgets / popup.js
1 (function( $, undefined ) {
2
3         function fitSegmentInsideSegment( winSize, segSize, offset, desired ) {
4                 var ret = desired;
5
6                 if ( winSize < segSize ) {
7                         // Center segment if it's bigger than the window
8                         ret = offset + ( winSize - segSize ) / 2;
9                 } else {
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 );
12                 }
13
14                 return ret;
15         }
16
17         function windowCoords() {
18                 var $win = $.mobile.$window;
19
20                 return {
21                         x: $win.scrollLeft(),
22                         y: $win.scrollTop(),
23                         cx: ( window.innerWidth || $win.width() ),
24                         cy: ( window.innerHeight || $win.height() )
25                 };
26         }
27
28         $.widget( "mobile.popup", $.mobile.widget, {
29                 options: {
30                         theme: null,
31                         overlayTheme: null,
32                         shadow: true,
33                         corners: true,
34                         transition: "pop",
35                         positionTo: "origin",
36                         tolerance: null,
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",
42
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
46                         //
47                         // NOTE this option is modified in _create!
48                         history: false
49                 },
50
51                 _eatEventAndClose: function( e ) {
52                         e.preventDefault();
53                         e.stopImmediatePropagation();
54                         this.close();
55                         return false;
56                 },
57
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 );
61
62                         this._ui.screen.removeAttr( "style" );
63                         if ( popupHeight > this._ui.screen.height() ) {
64                                 this._ui.screen.height( popupHeight );
65                         }
66                 },
67
68                 _handleWindowKeyUp: function( e ) {
69                         if ( this._isOpen && e.keyCode === $.mobile.keyCode.ESCAPE ) {
70                                 return this._eatEventAndClose( e );
71                         }
72                 },
73
74                 _maybeRefreshTimeout: function() {
75                         var winCoords = windowCoords();
76
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
83                                         return false;
84                                 } else {
85                                         // clear existing timeout - it will be refreshed below
86                                         clearTimeout( this._resizeData.timeoutId );
87                                 }
88                         }
89
90                         this._resizeData = {
91                                 timeoutId: setTimeout( $.proxy( this, "_resizeTimeout" ), 200 ),
92                                 winCoords: winCoords
93                         };
94
95                         return true;
96                 },
97
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" );
102                                 this._ui.container
103                                         .removeClass( "ui-selectmenu-hidden" )
104                                         .offset( this._placementCoords( this._desiredCoords( undefined, undefined, "window" ) ) );
105
106                                 this._resizeScreen();
107                                 this._resizeData = null;
108                                 this._orientationchangeInProgress = false;
109                         }
110                 },
111
112                 _handleWindowResize: function( e ) {
113                         if ( this._isOpen ) {
114                                 // Context popup close when Window resize event
115                                 if( this.positionTo !== "window" ) {
116                                         this.close();
117                                         return false;
118                                 }
119                                 this._maybeRefreshTimeout();
120                         }
121                 },
122
123                 _handleWindowOrientationchange: function( e ) {
124
125                         if ( !this._orientationchangeInProgress ) {
126                                 // effectively rapid-close the popup while leaving the screen intact
127                                 this._ui.container
128                                         .addClass( "ui-selectmenu-hidden" )
129                                         .removeAttr( "style" );
130
131                                 this._orientationchangeInProgress = true;
132                         }
133                 },
134
135                 _create: function() {
136                         var ui = {
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>")
141                                 },
142                                 thisPage = this.element.closest( ".ui-page" ),
143                                 myId = this.element.attr( "id" ),
144                                 self = this;
145
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;
150
151                         if ( thisPage.length === 0 ) {
152                                 thisPage = $( "body" );
153                         }
154
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;
158
159                         // Apply the proto
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 );
164                         if ( myId ) {
165                                 ui.screen.attr( "id", myId + "-screen" );
166                                 ui.container.attr( "id", myId + "-popup" );
167                                 ui.placeholder.html( "<!-- placeholder for " + myId + " -->" );
168                         }
169                         ui.container.append( this.element );
170                         ui.container.append( ui.arrow );
171                         // Add class to popup element
172                         this.element.addClass( "ui-popup" );
173
174                         // Define instance variables
175                         $.extend( this, {
176                                 _page: thisPage,
177                                 _ui: ui,
178                                 _fallbackTransition: "",
179                                 _currentTransition: false,
180                                 _prereqs: null,
181                                 _isOpen: false,
182                                 _tolerance: null,
183                                 _resizeData: null,
184                                 _orientationchangeInProgress: false,
185                                 _globalHandlers: [
186                                         {
187                                                 src: $.mobile.$window,
188                                                 handler: {
189                                                         orientationchange: $.proxy( this, "_handleWindowOrientationchange" ),
190                                                         resize: $.proxy( this, "_handleWindowResize" ),
191                                                         keyup: $.proxy( this, "_handleWindowKeyUp" )
192                                                 }
193                                         }
194                                 ]
195                         });
196
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 );
202                         });
203
204                         ui.screen.bind( "vclick", $.proxy( this, "_eatEventAndClose" ) );
205
206                         $.each( this._globalHandlers, function( idx, value ) {
207                                 value.src.bind( value.handler );
208                         });
209                 },
210
211                 _applyTheme: function( dst, theme, prefix ) {
212                         var classes = ( dst.attr( "class" ) || "").split( " " ),
213                                 alreadyAdded = true,
214                                 currentTheme = null,
215                                 matches,
216                                 themeStr = String( theme );
217
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 ];
223                                         break;
224                                 } else {
225                                         currentTheme = null;
226                                 }
227                         }
228
229                         if ( theme !== currentTheme ) {
230                                 dst.removeClass( "ui-" + prefix + "-" + currentTheme );
231                                 if ( ! ( theme === null || theme === "none" ) ) {
232                                         dst.addClass( "ui-" + prefix + "-" + themeStr );
233                                 }
234                         }
235                 },
236
237                 _setTheme: function( value ) {
238                         this._applyTheme( this.element, value, "body" );
239                 },
240
241                 _setOverlayTheme: function( value ) {
242                         this._applyTheme( this._ui.screen, value, "overlay" );
243
244                         if ( this._isOpen ) {
245                                 this._ui.screen.addClass( "in" );
246                         }
247                 },
248
249                 _setShadow: function( value ) {
250                         this.element.toggleClass( "ui-overlay-shadow", value );
251                 },
252
253                 _setCorners: function( value ) {
254                         this.element.toggleClass( "ui-corner-all", value );
255                 },
256
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 );
262                         }
263                 },
264
265                 _setTransition: function( value ) {
266                         if ( !this._currentTransition ) {
267                                 this._applyTransition( value );
268                         }
269                 },
270
271                 _setTolerance: function( value ) {
272                         var tol = { t: 5, r: 5, b: 5, l: 5 };
273
274                         if ( value ) {
275                                 var ar = String( value ).split( "," );
276
277                                 $.each( ar, function( idx, val ) { ar[ idx ] = parseInt( val, 10 ); } );
278
279                                 switch( ar.length ) {
280                                         // All values are to be the same
281                                         case 1:
282                                                 if ( !isNaN( ar[ 0 ] ) ) {
283                                                         tol.t = tol.r = tol.b = tol.l = ar[ 0 ];
284                                                 }
285                                                 break;
286
287                                         // The first value denotes top/bottom tolerance, and the second value denotes left/right tolerance
288                                         case 2:
289                                                 if ( !isNaN( ar[ 0 ] ) ) {
290                                                         tol.t = tol.b = ar[ 0 ];
291                                                 }
292                                                 if ( !isNaN( ar[ 1 ] ) ) {
293                                                         tol.l = tol.r = ar[ 1 ];
294                                                 }
295                                                 break;
296
297                                         // The array contains values in the order top, right, bottom, left
298                                         case 4:
299                                                 if ( !isNaN( ar[ 0 ] ) ) {
300                                                         tol.t = ar[ 0 ];
301                                                 }
302                                                 if ( !isNaN( ar[ 1 ] ) ) {
303                                                         tol.r = ar[ 1 ];
304                                                 }
305                                                 if ( !isNaN( ar[ 2 ] ) ) {
306                                                         tol.b = ar[ 2 ];
307                                                 }
308                                                 if ( !isNaN( ar[ 3 ] ) ) {
309                                                         tol.l = ar[ 3 ];
310                                                 }
311                                                 break;
312
313                                         default:
314                                                 break;
315                                 }
316                         }
317
318                         this._tolerance = tol;
319                 },
320
321                 _setOption: function( key, value ) {
322                         var exclusions, setter = "_set" + key.charAt( 0 ).toUpperCase() + key.slice( 1 );
323
324                         if ( this[ setter ] !== undefined ) {
325                                 this[ setter ]( value );
326                         }
327
328                         // TODO REMOVE FOR 1.2.1 by moving them out to a default options object
329                         exclusions = [
330                                 "initSelector",
331                                 "closeLinkSelector",
332                                 "closeLinkEvents",
333                                 "navigateEvents",
334                                 "closeEvents",
335                                 "history",
336                                 "container"
337                         ];
338
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 );
343                         }
344                 },
345
346                 // Try and center the overlay over the given coordinates
347                 _placementCoords: function( desired ) {
348                         // rectangle within which the popup must fit
349                         var
350                                 winCoords = windowCoords(),
351                                 rc = {
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
356                                 },
357                                 menuSize, ret,
358                                 linkOffset = $(this.link).offset(),
359                                 positionOffsets = [],
360                                 correctionValue = [0,0],
361                                 arrayIdx;
362
363                         // Clamp the width of the menu before grabbing its size
364                         this._ui.container.css( "max-width", rc.cx );
365                         menuSize = {
366                                 cx: this._ui.container.outerWidth( true ),
367                                 cy: this._ui.container.outerHeight( true )
368                         };
369
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.
372                         ret = {
373                                 x: fitSegmentInsideSegment( rc.cx, menuSize.cx, rc.x, desired.x ),
374                                 y: fitSegmentInsideSegment( rc.cy, menuSize.cy, rc.y, desired.y )
375                         };
376
377                         // Make sure the top of the menu is visible
378                         ret.y = Math.max( 0, ret.y );
379
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
382
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 );
386
387                         ret.y -= Math.min( ret.y, Math.max( 0, ret.y + menuSize.cy - docHeight ) );
388
389                         if ( this.positionTo !== "origin" )
390                         {
391                                 return { left: ret.x, top: ret.y , arrowleft: 0 , arrowtop: 0};
392                         }
393
394                         positionOffsets = [ linkOffset.left,
395                                                                 linkOffset.top,
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));
399
400                         switch( arrayIdx )
401                         {
402                                 case 0:
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" )
408                                         break;
409                                 case 1:
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" );
415                                         break;
416                                 case 2:
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");
422                                         break;
423                                 case 3:
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");
429                                         break;
430                         }
431
432                         return { left: ret.x + correctionValue[0], top: ret.y + correctionValue[1] , arrowleft: arrowleft , arrowtop: arrowtop };
433                 },
434
435                 _createPrereqs: function( screenPrereq, containerPrereq, whenDone ) {
436                         var self = this, prereqs;
437
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.
446
447                         prereqs = {
448                                 screen: $.Deferred(),
449                                 container: $.Deferred()
450                         };
451
452                         prereqs.screen.then( function() {
453                                 if ( prereqs === self._prereqs ) {
454                                         screenPrereq();
455                                 }
456                         });
457
458                         prereqs.container.then( function() {
459                                 if ( prereqs === self._prereqs ) {
460                                         containerPrereq();
461                                 }
462                         });
463
464                         $.when( prereqs.screen, prereqs.container ).done( function() {
465                                 if ( prereqs === self._prereqs ) {
466                                         self._prereqs = null;
467                                         whenDone();
468                                 }
469                         });
470
471                         self._prereqs = prereqs;
472                 },
473
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
479                         this._ui.screen
480                                 .removeClass( args.classToRemove )
481                                 .addClass( args.screenClassToAdd );
482
483                         args.prereqs.screen.resolve();
484
485                         if ( args.transition && args.transition !== "none" ) {
486                                 if ( args.applyTransition ) {
487                                         this._applyTransition( args.transition );
488                                 }
489                                 this._ui.container
490                                         .animationComplete( $.proxy( args.prereqs.container, "resolve" ) )
491                                         .addClass( args.containerClassToAdd )
492                                         .removeClass( args.classToRemove );
493                         } else {
494                                 args.prereqs.container.resolve();
495                         }
496                 },
497
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();
503
504                         self.positionTo = positionTo;
505
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;
511                                 } else {
512                                         try {
513                                                 dst = $( positionTo );
514                                         } catch( e ) {
515                                                 dst = null;
516                                         }
517                                         if ( dst ) {
518                                                 dst.filter( ":visible" );
519                                                 if ( dst.length === 0 ) {
520                                                         dst = null;
521                                                 }
522                                         }
523                                 }
524                         }
525
526                         // If an element was found, center over it
527                         if ( dst ) {
528                                 offset = dst.offset();
529                                 x = offset.left + dst.outerWidth() / 2;
530                                 y = offset.top + dst.outerHeight() / 2;
531                         }
532
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;
536                         }
537                         if ( $.type( y ) !== "number" || isNaN( y ) ) {
538                                 y = winCoords.cy / 2 + winCoords.y;
539                         }
540
541                         return { x: x, y: y };
542                 },
543
544                 _reposition: function() {
545                         var self = this,
546                                         coords;
547
548                         if( self._isOpen
549                                 && self.link
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" ) );
552                                         self._ui.container
553                                                 .offset( { top : coords.top } );
554                         }
555                 },
556
557                 _openPrereqsComplete: function() {
558                         var self = this;
559
560                         self._ui.container.addClass( "ui-popup-active" );
561                         self._isOpen = true;
562                         self._resizeScreen();
563
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" );
570                                 self._reposition();
571                         });
572                 },
573
574                 _open: function( options ) {
575                         var coords, transition,
576                                 androidBlacklist = ( function() {
577                                         var w = window,
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;
585
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 ) {
588                                                 return true;
589                                         }
590                                         return false;
591                                 }());
592
593                         // Make sure options is defined
594                         options = ( options || {} );
595
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;
598
599                         // Give applications a chance to modify the contents of the container before it appears
600                         this._trigger( "beforeposition" );
601
602                         coords = this._placementCoords( this._desiredCoords( options.x, options.y, options.positionTo || this.options.positionTo || "origin" ) );
603
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())
607                         this._createPrereqs(
608                                 $.noop,
609                                 $.noop,
610                                 $.proxy( this, "_openPrereqsComplete" ) );
611
612                         if ( transition ) {
613                                 this._currentTransition = transition;
614                                 this._applyTransition( transition );
615                         } else {
616                                 transition = this.options.transition;
617                         }
618
619                         if ( !this.options.theme ) {
620                                 this._setTheme( this._page.jqmData( "theme" ) || $.mobile.getInheritedTheme( this._page, "c" ) );
621                         }
622
623                         this._ui.screen.removeClass( "ui-screen-hidden" );
624
625                         this._ui.container
626                                 .removeClass( "ui-selectmenu-hidden" )
627                                 .offset( coords );
628                         this._ui.arrow.css( { top : coords.arrowtop, left : coords.arrowleft } );
629                         if ( this.options.overlayTheme && androidBlacklist ) {
630                                 /* TODO:
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
635
636                                 This fix closes the following bugs ( I use "closes" with reluctance, and stress that this issue should be revisited as soon as possible ):
637
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
641                                 */
642
643                                 // TODO sort out why this._page isn't working
644                                 this.element.closest( ".ui-page" ).addClass( "ui-popup-open" );
645                         }
646                         this._animate({
647                                 additionalCondition: true,
648                                 transition: transition,
649                                 classToRemove: "",
650                                 screenClassToAdd: "in",
651                                 containerClassToAdd: "in",
652                                 applyTransition: false,
653                                 prereqs: this._prereqs
654                         });
655                 },
656
657                 _closePrereqScreen: function() {
658                         this._ui.screen
659                                 .removeClass( "out" )
660                                 .addClass( "ui-screen-hidden" );
661                 },
662
663                 _closePrereqContainer: function() {
664                         this._ui.container
665                                 .removeClass( "reverse out" )
666                                 .addClass( "ui-selectmenu-hidden" )
667                                 .removeAttr( "style" );
668                 },
669
670                 _closePrereqsDone: function() {
671                         var self = this, opts = self.options;
672
673                         self._ui.container.removeAttr( "tabindex" );
674
675                         // remove nav bindings if they are still present
676                         opts.container.unbind( opts.closeEvents );
677
678                         // unbind click handlers added when history is disabled
679                         self.element.undelegate( opts.closeLinkSelector, opts.closeLinkEvents );
680
681                         // remove the global mutex for popups
682                         $.mobile.popup.active = undefined;
683
684                         // alert users that the popup is closed
685                         self._trigger( "afterclose" );
686                 },
687
688                 _close: function() {
689                         this._ui.container.removeClass( "ui-popup-active" );
690                         this._page.removeClass( "ui-popup-open" );
691
692                         this._isOpen = false;
693
694                         // IME hide when popup is closed
695                         this.element.find("input").blur();
696
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())
700                         this._createPrereqs(
701                                 $.proxy( this, "_closePrereqScreen" ),
702                                 $.proxy( this, "_closePrereqContainer" ),
703                                 $.proxy( this, "_closePrereqsDone" ) );
704
705                         this._animate( {
706                                 additionalCondition: this._ui.screen.hasClass( "in" ),
707                                 transition: ( this._currentTransition || this.options.transition ),
708                                 classToRemove: "in",
709                                 screenClassToAdd: "out",
710                                 containerClassToAdd: "reverse out",
711                                 applyTransition: true,
712                                 prereqs: this._prereqs
713                         });
714                 },
715
716                 _destroy: function() {
717                         var self = this;
718
719                         // hide and remove bindings
720                         self._close();
721
722                         // Put the element back to where the placeholder was and remove the "ui-popup" class
723                         self._setTheme( "none" );
724                         self.element
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();
730
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 );
735                                 });
736                         });
737                 },
738
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() {
743                         var self = this;
744
745                         self.options.container
746                                 .one( self.options.closeEvents, $.proxy( self._close, self ));
747                 },
748
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 ) {
756                                 return;
757                         }
758                         // set the global popup mutex
759                         $.mobile.popup.active = this;
760                         if( !options ) {
761                                 options = [];
762                         }
763
764                         if ( !options.link ) {
765                                 if ( !event ) {
766                                         self.positionTo = "window";
767                                 } else {
768                                         self.link = ( $(event.target).closest('a')[0] || $(event.target).closest('div')[0] );
769                                 }
770                         } else {
771                                 self.link = options.link;
772                         }
773                         if ( event ) {
774                                 self.positionTo = ( options != null && options.positionTo != null ) ? options.positionTo : "origin";
775                         }
776
777                         if ( $(self.link).jqmData("position-to") !== "window"
778                                         && self.positionTo !== "window" ) {
779
780                                 $(self.element).addClass("ui-ctxpopup");
781                                 $(self._ui.container).removeClass("ui-popup-container")
782                                         .addClass("ui-ctxpopup-container");
783
784                                 if( self.positionTo !== "origin" ) {
785                                         $(self._ui.arrow).hide();
786                                 } else {
787                                         $(self._ui.arrow).show();
788                                 }
789                         } else {
790                                 $(self._ui.arrow).hide();
791                                 // apply opacity back screen
792                                 this._setOverlayTheme( "dim" );
793                         }
794                         if( !options.x
795                                 && self.positionTo === "origin" ) {
796                                 options.x = $(self.link).offset().left + $(self.link).outerWidth() / 2;
797                         }
798                         if( !options.y
799                                 && self.positionTo === "origin" ) {
800                                 options.y = $(self.link).offset().top + $(self.link).outerHeight() / 2;
801                         }
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();
807
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
811                                 self.element
812                                         .delegate( opts.closeLinkSelector, opts.closeLinkEvents, function( e ) {
813                                                 self._close();
814
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
818                                                 return false;
819                                         });
820
821                                 return;
822                         }
823
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;
831
832                         if ( hasHash ) {
833                                 self._open( options );
834                                 self._bindContainerClose();
835                                 return;
836                         }
837
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 ){
841                                 url = url + hashkey;
842                         } else {
843                                 url = $.mobile.path.parseLocation().hash + hashkey;
844                         }
845
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 ) {
848                                 url += hashkey;
849                         }
850
851                         // swallow the the initial navigation event, and bind for the next
852                         opts.container.one( opts.navigateEvents, function( e ) {
853                                 e.preventDefault();
854                                 self._open( options );
855                                 self._bindContainerClose();
856                         });
857
858                         urlHistory.ignoreNextHashChange = currentIsDialog;
859
860                         // Gotta love methods with 1mm args :(
861                         urlHistory.addNew( url, undefined, undefined, undefined, "dialog" );
862
863                         // set the new url with (or without) the new dialog hash key
864                         $.mobile.path.set( url );
865                 },
866
867                 close: function() {
868                         // make sure close is idempotent
869                         if( !$.mobile.popup.active ){
870                                 return;
871                         }
872
873                         if( this.options.history ) {
874                                 $.mobile.back();
875                         } else {
876                                 this._close();
877                         }
878                 }
879         });
880
881
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] ),
889                         offset;
890
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" ),
898                                 link: $link
899                         });
900                 }
901
902                 //remove after delay
903                 setTimeout( function() {
904                         $link.removeClass( $.mobile.activeBtnClass );
905                 }, 300 );
906         };
907
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 );
912                         e.preventDefault();
913                 }
914         });
915
916         //delegate self-init widgets
917         $.delegateSelfInitWithSingleSelector( $.mobile.popup, true );
918
919 })( jQuery );