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 ) ||
61 ( w.operamini && ({}).toString.call( w.operamini ) === "[object OperaMini]" ) ||
62 ( operammobilematch && omversion < 7458 ) ||
63 //Android lte 2.1: Platform is Android and Webkit version is less than 533 (Android 2.2)
64 ( ua.indexOf( "Android" ) > -1 && wkversion && wkversion < 533 ) ||
65 // Firefox Mobile before 6.0 -
66 ( ffversion && ffversion < 6 ) ||
68 ( window.palmGetResource !== undefined && wkversion && wkversion < 534 ) ||
70 ( ua.indexOf( "MeeGo" ) > -1 && ua.indexOf( "NokiaBrowser/8.5.0" ) > -1 )
77 initSelector: ":jqmData(role='content')"
80 _create: function () {
86 // Feature detecting support for
87 if ( o.supportBlacklist() ) {
92 self._addFixedClass();
93 self._addTransitionClass();
94 self._bindPageEvents();
97 self._bindContentControlEvents();
100 /* add minimum fixed css style to bar(header/footer) and content
101 * it need to update when core source modified(jquery.mobile.page.section.js)
102 * modified from core source cuz initSelector different */
103 _addFixedClass: function () {
107 $elHeader = $el.siblings( ":jqmData(role='header')" ),
108 $elFooter = $el.siblings( ":jqmData(role='footer')" ),
109 $elPage = $el.closest(".ui-page");
111 $elHeader.addClass( "ui-header-fixed" );
112 $elFooter.addClass( "ui-footer-fixed" );
114 // "fullscreen" overlay positioning
115 if ( o.fullscreen ) {
116 $elHeader.addClass( "ui-header-fullscreen" );
117 $elFooter.addClass( "ui-footer-fullscreen" );
119 .addClass( "ui-page-header-fullscreen" )
120 .addClass( "ui-page-footer-fullscreen" );
122 // If not fullscreen, add class to page to set top or bottom padding
123 $elPage.addClass( "ui-page-header-fixed" )
124 .addClass( "ui-page-footer-fixed" );
128 /* original core source(jquery.mobile.fixedToolbar.js)
130 _addTransitionClass: function () {
131 var tclass = this.options.transition;
133 if ( tclass && tclass !== "none" ) {
134 // use appropriate slide for header or footer
135 if ( tclass === "slide" ) {
136 tclass = this.element.is( ".ui-header" ) ? "slidedown" : "slideup";
139 this.element.addClass( tclass );
144 /* Set default page positon
145 * 1. add title style to header
146 * 2. Set default header/footer position */
147 setHeaderFooter: function ( thisPage ) {
148 var $elPage = $( thisPage ),
149 $elHeader = $elPage.find( ":jqmData(role='header')" ).length ? $elPage.find( ":jqmData(role='header')") : $elPage.siblings( ":jqmData(role='header')"),
150 $elContent = $elPage.find( ".ui-content" ),
151 $elFooter = $elPage.find( ":jqmData(role='footer')" ),
152 $elFooterGroup = $elFooter.find( ":jqmData(role='fieldcontain')" ),
153 $elFooterControlGroup = $elFooter.find( ".ui-controlgroup" );
155 // divide content mode scrollview and non-scrollview
156 if ( !$elPage.is( ".ui-dialog" ) ) {
157 if ( $elHeader.jqmData("position") == "fixed" || ( $.support.scrollview && $.tizen.frameworkData.theme.match(/tizen/) ) ) {
159 .css( "position", "fixed" )
160 .css( "top", "0px" );
161 } else if ( !$.support.scrollview && $elHeader.jqmData("position") != "fixed" ) {
162 $elHeader.css( "position", "relative" );
166 /* set Title style */
167 if ( $elHeader.find("span.ui-title-text-sub").length ) {
168 $elHeader.addClass( "ui-title-multiline");
171 if ( $elFooterGroup.find( "div" ).is( ".ui-controlgroup-label" ) ) {
172 $elFooterGroup.find( "div.ui-controlgroup-label" ).remove();
175 if ( $elFooterControlGroup.length ) {
176 var anchorPer = 100 / $elFooterControlGroup.find( "a" ).length;
177 $elFooterControlGroup.find( "a" ).each( function ( i ) {
178 $elFooterControlGroup.find( "a" ).eq( i ).width( anchorPer + "%" );
183 _bindPageEvents: function () {
189 //page event bindings
190 // Fixed toolbars require page zoom to be disabled, otherwise usability issues crop up
191 // This method is meant to disable zoom while a fixed-positioned toolbar page is visible
192 $el.closest( ".ui-page" )
193 .bind( "pagebeforeshow", function ( event ) {
195 if ( o.disablePageZoom ) {
196 $.mobile.zoom.disable( true );
198 if ( !o.visibleOnPageShow ) {
201 self.setHeaderFooter( thisPage );
202 self._setContentMinHeight( thisPage );
204 .bind( "webkitAnimationStart animationstart updatelayout", function ( e, data ) {
206 if ( o.updatePagePadding ) {
207 self.updatePagePadding(thisPage);
208 self.updatePageLayout( thisPage, data);
212 .bind( "pageshow", function ( event ) {
214 self._setContentMinHeight( thisPage );
215 self.updatePagePadding( thisPage );
216 self._updateHeaderArea( thisPage );
217 if ( o.updatePagePadding ) {
218 $( window ).bind( "throttledresize." + self.widgetName, function () {
219 self.updatePagePadding(thisPage);
221 self.updatePageLayout( thisPage, false);
222 self._updateHeaderArea( thisPage );
223 self._setContentMinHeight( thisPage );
228 .bind( "pagebeforehide", function ( e, ui ) {
229 if ( o.disablePageZoom ) {
230 $.mobile.zoom.enable( true );
232 if ( o.updatePagePadding ) {
233 $( window ).unbind( "throttledresize." + self.widgetName );
236 if ( o.trackPersistentToolbars ) {
237 var thisFooter = $( ".ui-footer-fixed:jqmData(id)", this ),
238 thisHeader = $( ".ui-header-fixed:jqmData(id)", this ),
239 nextFooter = thisFooter.length && ui.nextPage && $( ".ui-footer-fixed:jqmData(id='" + thisFooter.jqmData( "id" ) + "')", ui.nextPage ),
240 nextHeader = thisHeader.length && ui.nextPage && $( ".ui-header-fixed:jqmData(id='" + thisHeader.jqmData( "id" ) + "')", ui.nextPage );
242 nextFooter = nextFooter || $();
244 if ( nextFooter.length || nextHeader.length ) {
246 nextFooter.add( nextHeader ).appendTo( $.mobile.pageContainer );
248 ui.nextPage.one( "pageshow", function () {
249 nextFooter.add( nextHeader ).appendTo( this );
255 window.addEventListener( "softkeyboardchange", function ( e ) {
256 var thisPage = $( ".ui-page-active" );
258 if ( e.state == "on" ) {
259 $elCurrentFooter = $( ".ui-page-active .ui-footer" );
260 $elCurrentFooter.hide();
261 } else if (e.state == "off") {
262 $elCurrentFooter.show();
264 self.updatePagePadding( thisPage );
265 self.updatePageLayout( thisPage, true );
269 _bindContentControlEvents: function () {
274 $el.closest( ".ui-page" )
275 .bind( "pagebeforeshow", function ( event ) {
280 _setContentMinHeight : function ( thisPage ) {
281 var $elPage = $( thisPage ),
282 $elHeader = $elPage.find( ":jqmData(role='header')" ),
283 $elFooter = $elPage.find( ":jqmData(role='footer')" ),
284 $elContent = $elPage.find( ":jqmData(role='content')" ),
287 resultMinHeight = window.innerHeight - $elHeader.height() - $elFooter.height();
289 $elContent.css( "min-height", resultMinHeight - parseFloat( $elContent.css("padding-top") ) - parseFloat( $elContent.css("padding-bottom") ) + "px" );
292 _updateHeaderArea : function ( thisPage ) {
293 var $elPage = $( thisPage ),
294 $elHeader = $elPage.find( ":jqmData(role='header')" ).length ? $elPage.find( ":jqmData(role='header')") : $elPage.siblings( ":jqmData(role='header')"),
295 headerBtnNum = $elHeader.children("a").length,
296 headerSrcNum = $elHeader.children("img").length;
298 if ( !$elPage.is( ".ui-dialog" ) ) {
299 $elHeader.find( "h1" ).css( "width", window.innerWidth - parseInt( $elHeader.find( "h1" ).css( "margin-left" ), 10 ) * 2 - $elHeader.children( "a" ).width() * headerBtnNum - $elHeader.children( "a" ).width() / 4 - $elHeader.children( "img" ).width() * headerSrcNum * 4 );
301 /* add half width for default space between text and button, and img tag area is too narrow, so multiply three for img width*/
306 // This will set the content element's top or bottom padding equal to the toolbar's height
307 updatePagePadding: function ( tbPage ) {
308 var $el = this.element,
309 header = $el.siblings( ".ui-header" ).length,
310 footer = $el.siblings( ".ui-footer" ).length;
312 // This behavior only applies to "fixed", not "fullscreen"
313 if ( this.options.fullscreen ) {
317 tbPage = tbPage || $el.closest( ".ui-page" );
319 if ( $el.siblings( ".ui-header" ).jqmData("position") == "fixed" || $.support.scrollview ) {
320 $( tbPage ).css( "padding-top", ( header ? $el.siblings( ".ui-header" ).outerHeight() : 0 ) );
322 $( tbPage ).css( "padding-bottom", ( footer ? $el.siblings( ".ui-footer" ).outerHeight() : 0 ) );
325 /* 1. Calculate and update content height */
326 updatePageLayout: function ( thisPage, receiveType ) {
328 $elPage = $( thisPage ),
329 $elHeader = $elPage.find( ":jqmData(role='header')" ),
330 $elContent = $elPage.find( ":jqmData(role='content')" ),
331 resultContentHeight = 0,
332 resultFooterHeight = 0,
333 resultHeaderHeight = 0;
335 if ( $elPage.length ) {
336 $elFooter = $elPage.find( ":jqmData(role='footer')" );
338 $elFooter = $( document ).find( ":jqmData(role='footer')" ).eq( 0 );
341 // calculate footer height
342 resultFooterHeight = ( $elFooter.css( "display" ) == "none" || $elFooter.length == 0 ) ? 0 : $elFooter.height();
343 resultHeaderHeight = ( $elHeader.css( "display" ) == "none" || $elHeader.length == 0 ) ? 0 : $elHeader.height();
345 if (resultFooterHeight != 0 ) {
346 $elFooter.css( "bottom", 0 );
349 resultContentHeight = window.innerHeight - resultFooterHeight - resultHeaderHeight;
351 if ( $.support.scrollview ) {
352 $elContent.height( resultContentHeight -
353 parseFloat( $elContent.css("padding-top") ) -
354 parseFloat( $elContent.css("padding-bottom") ) );
357 // External call page( "refresh") - in case title changed
360 .css( "min-height", resultContentHeight )
361 .css( "padding-top", resultHeaderHeight )
362 .css( "padding-bottom", resultFooterHeight );
366 show: function ( notransition ) {
367 /* blank function: deprecated */
370 hide: function ( notransition ) {
371 /* blank function: deprecated */
374 toggle: function () {
375 this[ this._visible ? "hide" : "show" ]();
378 destroy: function () {
379 this.element.removeClass( "ui-header-fixed ui-footer-fixed ui-header-fullscreen ui-footer-fullscreen in out fade slidedown slideup ui-fixed-hidden" );
380 this.element.closest( ".ui-page" ).removeClass( "ui-page-header-fixed ui-page-footer-fixed ui-page-header-fullscreen ui-page-footer-fullscreen" );
384 //auto self-init widgets
386 .bind( "pagecreate create", function ( e ) {
387 // DEPRECATED in 1.1: support for data-fullscreen=true|false on the page element.
388 // This line ensures it still works, but we recommend moving the attribute to the toolbars themselves.
389 if ( $( e.target ).jqmData( "fullscreen" ) ) {
390 $( $.mobile.pagelayout.prototype.options.initSelector, e.target ).not( ":jqmData(fullscreen)" ).jqmData( "fullscreen", true );
392 $.mobile.pagelayout.prototype.enhanceWithin( e.target );