1 /* ***************************************************************************
2 * Copyright (c) 2000 - 2011 Samsung Electronics Co., Ltd.
4 * Permission is hereby granted, free of charge, to any person obtaining a
5 * copy of this software and associated documentation files (the "Software"),
6 * to deal in the Software without restriction, including without limitation
7 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
8 * and/or sell copies of the Software, and to permit persons to whom the
9 * Software is furnished to do so, subject to the following conditions:
11 * The above copyright notice and this permission notice shall be included in
12 * all copies or substantial portions of the Software.
14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
19 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
20 * DEALINGS IN THE SOFTWARE.
21 * ***************************************************************************
23 * Author: Jinhyuk Jun <jinhyuk.jun@samsung.com>
26 (function ( $, undefined ) {
28 $.widget( "mobile.pagelayout", $.mobile.widget, {
30 visibleOnPageShow: true,
31 disablePageZoom: true,
32 transition: "slide", //can be none, fade, slide (slide maps to slideup or slidedown)
35 tapToggleBlacklist: "a, input, select, textarea, .ui-header-fixed, .ui-footer-fixed",
36 hideDuringFocus: "input, textarea, select",
37 updatePagePadding: true,
38 trackPersistentToolbars: true,
39 // Browser detection! Weeee, here we go...
40 // Unfortunately, position:fixed is costly, not to mention probably impossible, to feature-detect accurately.
41 // Some tests exist, but they currently return false results in critical devices and browsers, which could lead to a broken experience.
42 // Testing fixed positioning is also pretty obtrusive to page load, requiring injected elements and scrolling the window
43 // The following function serves to rule out some popular browsers with known fixed-positioning issues
44 // This is a plugin option like any other, so feel free to improve or overwrite it
45 supportBlacklist: function () {
47 ua = navigator.userAgent,
48 platform = navigator.platform,
49 // Rendering engine is Webkit, and capture major version
50 wkmatch = ua.match( /AppleWebKit\/([0-9]+)/ ),
51 wkversion = !!wkmatch && wkmatch[ 1 ],
52 ffmatch = ua.match( /Fennec\/([0-9]+)/ ),
53 ffversion = !!ffmatch && ffmatch[ 1 ],
54 operammobilematch = ua.match( /Opera Mobi\/([0-9]+)/ ),
55 omversion = !!operammobilematch && operammobilematch[ 1 ];
58 // iOS 4.3 and older : Platform is iPhone/Pad/Touch and Webkit version is less than 534 (ios5)
59 ( ( platform.indexOf( "iPhone" ) > -1 || platform.indexOf( "iPad" ) > -1 || platform.indexOf( "iPod" ) > -1 ) && wkversion && wkversion < 534 )
62 ( w.operamini && ({}).toString.call( w.operamini ) === "[object OperaMini]" )
64 ( operammobilematch && omversion < 7458 )
66 //Android lte 2.1: Platform is Android and Webkit version is less than 533 (Android 2.2)
67 ( ua.indexOf( "Android" ) > -1 && wkversion && wkversion < 533 )
69 // Firefox Mobile before 6.0 -
70 ( ffversion && ffversion < 6 )
73 ( "palmGetResource" in window && wkversion && wkversion < 534 )
76 ( ua.indexOf( "MeeGo" ) > -1 && ua.indexOf( "NokiaBrowser/8.5.0" ) > -1 )
83 initSelector: ":jqmData(role='content')"
86 _create: function () {
92 // Feature detecting support for
93 if ( o.supportBlacklist() ) {
98 self._addFixedClass();
99 self._addTransitionClass();
100 self._bindPageEvents();
103 self._bindContentControlEvents();
106 /* add minimum fixed css style to bar(header/footer) and content
107 * it need to update when core source modified(jquery.mobile.page.section.js)
108 * modified from core source cuz initSelector different */
109 _addFixedClass: function () {
113 $elHeader = $el.siblings( ":jqmData(role='header')" ),
114 $elFooter = $el.siblings( ":jqmData(role='footer')" ),
115 $elPage = $el.closest(".ui-page");
117 $elHeader.addClass( "ui-header-fixed" );
118 $elFooter.addClass( "ui-footer-fixed" );
120 // "fullscreen" overlay positioning
121 if ( o.fullscreen ) {
122 $elHeader.addClass( "ui-header-fullscreen" );
123 $elFooter.addClass( "ui-footer-fullscreen" );
125 .addClass( "ui-page-header-fullscreen" )
126 .addClass( "ui-page-footer-fullscreen" );
128 // If not fullscreen, add class to page to set top or bottom padding
129 $elPage.addClass( "ui-page-header-fixed" )
130 .addClass( "ui-page-footer-fixed" );
134 /* original core source(jquery.mobile.fixedToolbar.js)
136 _addTransitionClass: function () {
137 var tclass = this.options.transition;
139 if ( tclass && tclass !== "none" ) {
140 // use appropriate slide for header or footer
141 if ( tclass === "slide" ) {
142 tclass = this.element.is( ".ui-header" ) ? "slidedown" : "slideup";
145 this.element.addClass( tclass );
150 /* Set default page positon
151 * 1. add title style to header
152 * 2. Set default header/footer position */
153 setHeaderFooter: function ( event ) {
154 var $elPage = $( event.target ),
155 $elHeader = $elPage.find( ":jqmData(role='header')" ).length ? $elPage.find( ":jqmData(role='header')") : $elPage.siblings( ":jqmData(role='header')"),
156 $elFieldcontain = $elHeader.find( ":jqmData(role='fieldcontain')" ),
157 $elControlgroup = $elHeader.find( ":jqmData(role='controlgroup')" ),
158 $elContent = $elPage.find( ".ui-content" ),
167 if ( $elFieldcontain.length != 0 || $elControlgroup.length != 0 ) {
171 if ( $elHeader.jqmData("position") == "fixed" || $.tizen.frameworkData.theme.match(/tizen/) || $elHeader.css("position") == "fixed" ) {
173 .css( "position", "fixed" )
174 .css( "top", "0px" );
176 if ( $elHeader.children().is(".ui-navbar") ) {
177 $elHeader.addClass( "ui-title-controlbar-height" );
178 $( event.target ).find( ".ui-content" ).addClass( "ui-title-content-controlbar-height" );
180 if ( $elHeader.length ) {
181 $( event.target ).find( ".ui-content" ).addClass( "ui-title-content-" + tStyle + "-height" );
183 $( event.target ).find( ".ui-content" ).addClass( "ui-title-content-no-height" );
188 if ( $elHeader.children().is(".ui-option-header") ) {
189 $elContent.removeClass( "ui-title-content-" + tStyle + "-height" );
190 if ( $.tizen.optionheader.prototype.options.collapseOnInit == true ) {
191 $elContent.addClass( "ui-title-content-option-header-collapsed-1line-height" );
193 $elContent.addClass( "ui-title-content-option-header-expanded-1line-height" );
195 } else if ( $elHeader.find("input").attr("type") === "search" || $elHeader.find("input").attr("type") === "tizen-search" || $elHeader.find("input").jqmData("type") == "search" || $elHeader.find("input").jqmData("type") == "tizen-search" ) {
196 $elContent.removeClass( "ui-title-content-" + tStyle + "-height" ).addClass( "ui-title-content-search" );
199 headerBtnNum = $elHeader.children("a").length;
200 if ( headerBtnNum > 0 || $elHeader.children().find(".ui-radio").length != 0 ) {
201 if ( tStyle != "normal" ) {
202 gLength = $elFieldcontain.length ? $elFieldcontain.find( ".ui-radio" ).length : $elControlgroup.find( "a" ).length;
204 $elHeader.addClass( "ui-title-extended-height" );
206 $elFieldcontain.length ? $elFieldcontain.find( ".ui-controlgroup" ).addClass( "ui-title-extended-controlgroup" ).addClass( "ui-extended-controlgroup" ) : $elControlgroup.addClass( "ui-title-extended-button-controlgroup" ).addClass( "ui-extended-controlgroup" );
208 $elFieldcontain.length ? $elFieldcontain.addClass( "ui-title-extended-segment-style" ) : $elControlgroup.addClass( "ui-title-extended-segment-style" );
210 if ( gLength == 2 || gLength == 3 || gLength == 4 ) {
211 $elFieldcontain.length ? $elFieldcontain.addClass( "ui-title-extended-controlgroup-" + gLength + "btn" ) : $elControlgroup.addClass( "ui-title-extended-controlgroup-" + gLength + "btn" );
214 $elContent.addClass( "ui-title-content-" + tStyle + "-height" );
217 // divide content mode scrollview and non-scrollview
218 // recalculate content area when resize callback occur
219 if ( $.support.scrollview ) {
220 if ( $elHeader.css( "position" ) != "fixed" ) {
221 $elHeader.css( "position", "fixed" );
225 if ( $elHeader.css("position") != "fixed" ) {
226 $elHeader.css( "position", "relative" );
230 $elFooter = $( document ).find( ":jqmData(role='footer')" );
232 if ( $elFooter.find(".ui-navbar").is(".ui-controlbar-s") ) {
238 if ( $elFooter.children().find(".ui-radio").length != 0 ) {
239 $elFooterGroup = $elFooter.find( ":jqmData(role='fieldcontain')" );
240 gLength = $elFooterGroup.find( ".ui-radio" ).length;
242 $elFooterGroup.find( ".ui-controlgroup" )
243 .addClass( "ui-extended-controlgroup" )
244 .addClass( "ui-footer-extended-controlgroup" )
245 .css( "display", "inline" );
247 /* Groupcontrol cannot initialize inline property at first page */
248 $elFooterGroup.addClass( "ui-footer-extended-controlgroup-" + gLength + "btn" );
251 footerButton = $elFooter.children( "a" );
252 footerButton.each( function ( i ) {
253 if ( footerButton.eq( i ).is(".ui-btn") && !footerButton.eq( i ).is(".ui-btn-back") ) {
255 .removeClass( "ui-btn-left" )
256 .addClass( "ui-btn-footer-right" );
260 if ( $elFooter.is(".ui-footer-fixed") ) {
261 $elFooter.css( "bottom", 0 );
264 /* Increase Content size with dummy <div> because of footer height */
265 if ( $elFooter.length != 0 && $( event.target ).find( ".dummy-div" ).length == 0 ) {
266 $( event.target ).find( ":jqmData(role='content')" ).append( '<div class="dummy-div"></div>' );
268 .css( "width", $elFooter.width() )
269 .css( "height", $elFooter.height() );
274 /* Header position fix(remove transition) */
275 next_id = $( event.target ).attr( "id" );
277 $( "#" + next_id ).find( ":jqmData(role='header')" )
278 .removeClass( "fade in out" )
279 .appendTo( $.mobile.pageContainer );
282 _bindPageEvents: function () {
287 //page event bindings
288 // Fixed toolbars require page zoom to be disabled, otherwise usability issues crop up
289 // This method is meant to disable zoom while a fixed-positioned toolbar page is visible
290 $el.closest( ".ui-page" )
291 .bind( "pagebeforeshow", function ( event ) {
292 if ( o.disablePageZoom ) {
293 $.mobile.zoom.disable( true );
295 if ( !o.visibleOnPageShow ) {
298 self._IMEShown = false;
299 self.setHeaderFooter( event );
301 .bind( "webkitAnimationStart animationstart updatelayout", function ( e, data ) {
302 if ( o.updatePagePadding ) {
303 self.updatePagePadding(data); // FIXME: unused function.
304 self.updatePageLayout(data);
308 .bind( "pageshow", function ( event ) {
309 self.updatePagePadding(); // FIXME: unused function.
310 self._updateHeaderArea();
311 if ( o.updatePagePadding ) {
312 $( window ).bind( "throttledresize." + self.widgetName, function () {
313 self.updatePagePadding(); // FIXME: unused function.
314 self.layoutPageIME(); // IME/resize reposition
315 self.updatePageLayout();
316 self._updateHeaderArea();
320 /* Header position fix(remove transition) */
321 $( "body" ).children( ":jqmData(role='header')" )
322 .insertBefore( $(event.target).find(":jqmData(role='content')").eq( 0 ) );
326 .bind( "pagebeforehide", function ( e, ui ) {
327 if ( o.disablePageZoom ) {
328 $.mobile.zoom.enable( true );
330 if ( o.updatePagePadding ) {
331 $( window ).unbind( "throttledresize." + self.widgetName );
334 if ( o.trackPersistentToolbars ) {
335 var thisFooter = $( ".ui-footer-fixed:jqmData(id)", this ),
336 thisHeader = $( ".ui-header-fixed:jqmData(id)", this ),
337 nextFooter = thisFooter.length && ui.nextPage && $( ".ui-footer-fixed:jqmData(id='" + thisFooter.jqmData( "id" ) + "')", ui.nextPage ),
338 nextHeader = thisHeader.length && ui.nextPage && $( ".ui-header-fixed:jqmData(id='" + thisHeader.jqmData( "id" ) + "')", ui.nextPage );
340 nextFooter = nextFooter || $();
342 if ( nextFooter.length || nextHeader.length ) {
344 nextFooter.add( nextHeader ).appendTo( $.mobile.pageContainer );
346 ui.nextPage.one( "pageshow", function () {
347 nextFooter.add( nextHeader ).appendTo( this );
354 _bindContentControlEvents: function () {
359 $el.closest( ".ui-page" )
360 .bind( "pagebeforeshow", function ( event ) {
365 _updateHeaderArea : function() {
366 var $elPage = $( ".ui-page-active" ),
367 $elHeader = $elPage.find( ":jqmData(role='header')" ).length ? $elPage.find( ":jqmData(role='header')") : $elPage.siblings( ":jqmData(role='header')"),
368 headerBtnNum = $elHeader.children("a").length,
369 headerSrcNum = $elHeader.children("img").length;
371 $elHeader.find( "h1" ).css( "width", window.innerWidth - $elHeader.children( "a" ).width() * headerBtnNum - $elHeader.children( "a" ).width() / 4 - $elHeader.children( "img" ).width() * headerSrcNum * 3 );
372 /* add half width for default space between text and button, and img tag area is too narrow, so multiply three for img width*/
377 _IMEindicatorHeight : window.outerHeight - window.innerHeight,
379 layoutPageIME: function () {
380 if ( $( document.activeElement ).is( "input" ) || $( document.activeElement ).is( "textarea" )
381 || $(".ui-page-active .ui-header .input-search-bar").length
382 || $(".ui-page-active .ui-content").find("input").length
383 || $(".ui-page-active .ui-content").find("textarea").length) {
384 /* Check vertical and horizontal ratio.
385 * If focus on input and two values are different, IME is drawed. */
387 if ( ( window.innerHeight + this._IMEindicatorHeight ) < window.outerHeight && window.innerWidth == window.outerWidth ) {
388 if ( this._IMEShown === false ) {
389 $( ".ui-page-active .ui-footer" ).hide();
390 this._IMEShown = true;
392 } else if ( ( window.innerHeight + this._IMEindicatorHeight ) >= window.outerHeight ) {
393 if ( this._IMEShown === true ) {
394 $( ".ui-page-active .ui-footer" ).show();
395 this._IMEShown = false;
399 if ( ( window.innerHeight + this._IMEindicatorHeight ) >= window.outerHeight ) {
400 if ( this._IMEShown === true ) {
401 $( ".ui-page-active .ui-footer" ).show();
402 this._IMEShown = false;
408 // This will set the content element's top or bottom padding equal to the toolbar's height
409 updatePagePadding: function (data) {
410 var $el = this.element,
411 header = $el.is( ".ui-header" );
413 // This behavior only applies to "fixed", not "fullscreen"
414 if ( this.options.fullscreen ) { return; }
416 // $el.closest( ".ui-page" ).css( "padding-" + ( header ? "top" : "bottom" ), $el.outerHeight() );
420 /* 1. Calculate toolbar width(only controlbar)
421 * 2. Calculate and update content height */
422 updatePageLayout: function ( receiveType ) {
425 $elPage = $( document ).find( ".ui-page-active" ),
426 $elHeader = $elPage.find( ":jqmData(role='header')" ),
427 $elContent = $elPage.find( ":jqmData(role='content')" ),
428 resultContentHeight = 0,
429 resultFooterHeight = 0,
430 resultHeaderHeight = 0;
432 if ( $elPage.length ) {
433 $elFooter = $( document ).find( ".ui-page-active" ).find( ":jqmData(role='footer')" );
435 $elFooter = $( document ).find( ":jqmData(role='footer')" ).eq( 0 );
437 $elFooterControlbar = $elFooter.find( ".ui-navbar" );
439 // calculate footer height
440 resultFooterHeight = ( $elFooter.css( "display" ) == "none" ) ? 0 : $elFooter.height();
441 resultHeaderHeight = ( $elHeader.css( "display" ) == "none" ) ? 0 : $elHeader.height();
443 if (resultFooterHeight != 0 ) {
444 $elFooter.css( "bottom", 0 );
446 if ( $elFooterControlbar.jqmData("style") == "toolbar" ) {
447 $elFooterControlbar.css( "width", window.innerWidth - $elFooterControlbar.siblings( ".ui-btn" ).width() - parseInt($elFooterControlbar.siblings(".ui-btn").css("right"), 10 ) * 2 );
450 resultContentHeight = window.innerHeight - resultFooterHeight - resultHeaderHeight;
452 if ( $.support.scrollview ) {
453 if ( $elHeader.css("position") != "fixed" ) {
454 $elHeader.css( "position", "fixed" );
457 $elContent.height( resultContentHeight -
458 parseFloat( $elContent.css("padding-top") ) -
459 parseFloat( $elContent.css("padding-bottom") ) );
461 if ( $elHeader.css("position") != "fixed" ) {
462 $elHeader.css( "position", "relative" );
464 $elContent.height( resultContentHeight );
468 // check this line need
469 // because another style title will be not supported to updatePageLayout
471 // in case title changed
473 $elContent.css( "top", resultHeaderHeight + "px" );
477 _useTransition: function ( notransition ) {
478 var $win = $( window ),
480 scroll = $win.scrollTop(),
481 elHeight = $el.height(),
482 pHeight = $el.closest( ".ui-page" ).height(),
483 viewportHeight = $.mobile.getScreenHeight(),
484 tbtype = $el.is( ":jqmData(role='header')" ) ? "header" : "footer";
486 return !notransition &&
487 ( this.options.transition && this.options.transition !== "none" &&
489 ( tbtype === "header" && !this.options.fullscreen && scroll > elHeight ) ||
490 ( tbtype === "footer" && !this.options.fullscreen && scroll + viewportHeight < pHeight - elHeight )
491 ) || this.options.fullscreen
495 show: function ( notransition ) {
496 /* var hideClass = "ui-fixed-hidden",
499 if ( this._useTransition( notransition ) ){
501 .removeClass( "out " + hideClass )
505 $el.removeClass( hideClass );
507 this._visible = true;*/
510 hide: function ( notransition ) {
511 /* var hideClass = "ui-fixed-hidden",
513 // if it's a slide transition, our new transitions need the reverse class as well to slide outward
514 outclass = "out" + ( this.options.transition === "slide" ? " reverse" : "" );
516 if ( this._useTransition( notransition ) ){
518 .addClass( outclass )
520 .animationComplete( function () {
521 $el.addClass( hideClass ).removeClass( outclass );
525 $el.addClass( hideClass ).removeClass( outclass );
527 this._visible = false;*/
530 toggle: function () {
531 this[ this._visible ? "hide" : "show" ]();
534 /* support external api for adding backbutton via javascript */
535 /* backButton: function ( target, status ){
536 this._addBackbutton( target, "external" );
539 destroy: function () {
540 this.element.removeClass( "ui-header-fixed ui-footer-fixed ui-header-fullscreen ui-footer-fullscreen in out fade slidedown slideup ui-fixed-hidden" );
541 this.element.closest( ".ui-page" ).removeClass( "ui-page-header-fixed ui-page-footer-fixed ui-page-header-fullscreen ui-page-footer-fullscreen" );
546 //auto self-init widgets
548 .bind( "pagecreate create", function ( e ) {
549 // DEPRECATED in 1.1: support for data-fullscreen=true|false on the page element.
550 // This line ensures it still works, but we recommend moving the attribute to the toolbars themselves.
551 if ( $( e.target ).jqmData( "fullscreen" ) ) {
552 $( $.mobile.pagelayout.prototype.options.initSelector, e.target ).not( ":jqmData(fullscreen)" ).jqmData( "fullscreen", true );
554 $.mobile.pagelayout.prototype.enhanceWithin( e.target );