5 * Youmin Ha <youmin.ha@samsung.com>
10 Web UI scaling concept in Tizen Web UI
12 Generally, web applications must be designed to be showed acceptable on various size and resolution of screens, and web winsets have to be scaled well. Tizen Web UI Framework supports various viewport settings, and Tizen Web UI widgets are designed to be scalable on various screen sizes. In order to make web applications scalable on many devices which have different screen size, it is necessary to understand how mobile web browsers deal with screen resolution, and how Tizen Web UI Framework supports scaling for web applications.
15 * Viewport on mobile web browser
17 Viewport is an area showing web content on the browser. Unlike desktop browsers, mobile browsers support logical viewport seting, which means that application can set viewport width/height and zoom level by itself.
18 The very important thing that to be remembered is that the viewport resolution in pixel is 'Logical', not physical. For example, if the viewport width is set to 480 on a mobile device having 720px screen width, the viewport width is considered to 480px logically. All elements put on right side from 480px horizontal position will not be shown on the viewport.
19 Most mobile browsers set viewport with given content attribute with <meta name="viewport" content="..."> tag in <head> section in the application source html, whereas desktop browsers ignore the tag.
20 Detailed usage of viewport meta tag is found in here: http://www.w3.org/TR/mwabp/#bp-viewport
23 * Viewport setting by application developers
25 When developers write <meta name="viewport" content="..."> in the <head> section of the web application HTML file, Tizen Web UI Framework does not add another viewport meta tag, nor modify developer-defined viewport.
28 * Automatic viewport setting by Tizen Web UI Framework
30 If developers do not give a viewport meta tag, Tizen Web UI Framework automatically add a viewport meta tag with default viewport setting.
33 * Portrait/landscape mode
36 * Tizen Web UI widgets scaling
40 ( function ($, Globalize, window, undefined) {
43 libFileName : "tizen-web-ui-fw(.min)?.js",
46 rootDir: '/usr/lib/tizen-web-ui-fw',
49 viewportWidth: "device-width",
59 debug : function ( msg ) {
60 if ( tizen.frameworkData.debug ) {
64 warn : function ( msg ) {
67 error : function ( msg ) {
70 alert : function ( msg ) {
77 loadScriptSync : function ( scriptPath, successCB, errorCB ) {
84 error: function ( jqXHR, textStatus, errorThrown ) {
86 errorCB( jqXHR, textStatus, errorThrown );
88 var ignoreStatusList = [ 404 ], // 404: not found
89 errmsg = ( 'Error while loading ' + scriptPath + '\n' + jqXHR.status + ':' + jqXHR.statusText );
90 if ( -1 == $.inArray( jqXHR.status, ignoreStatusList ) ) {
91 tizen.log.alert( errmsg );
93 tizen.log.warn( errmsg );
99 isMobileBrowser: function ( ) {
100 var mobileIdx = window.navigator.appVersion.indexOf("Mobile"),
101 isMobile = -1 < mobileIdx;
107 cacheBust: ( document.location.href.match( /debug=true/ ) ) ?
108 '?cacheBust=' + ( new Date( ) ).getTime( ) :
110 addElementToHead : function ( elem ) {
111 var head = document.getElementsByTagName( 'head' )[0];
113 $( head ).prepend( elem );
116 makeLink : function ( href ) {
117 var cssLink = document.createElement( 'link' );
118 cssLink.setAttribute( 'rel', 'stylesheet' );
119 cssLink.setAttribute( 'href', href );
120 cssLink.setAttribute( 'name', 'tizen-theme' );
123 load: function ( path ) {
124 var head = document.getElementsByTagName( 'head' )[0],
125 cssLinks = head.getElementsByTagName( 'link' ),
128 // Find css link element
129 for ( idx = 0; idx < cssLinks.length; idx++ ) {
130 if ( cssLinks[idx].getAttribute( 'rel' ) != "stylesheet" ) {
133 if ( cssLinks[idx].getAttribute( 'name' ) == "tizen-theme"
134 || cssLinks[idx].getAttribute( 'href' ) == path ) {
139 if ( l ) { // Found the link element!
140 if ( l.getAttribute( 'href' ) == path ) {
141 tizen.log.warn( "Theme is already loaded. Skip theme loading in the framework." );
143 l.setAttribute( 'href', path );
146 this.addElementToHead( this.makeLink( path ) );
151 getParams: function ( ) {
152 /* Get data-* params from <script> tag, and set tizen.frameworkData.* values
153 * Returns true if proper <script> tag is found, or false if not.
155 // Find current <script> tag element
156 var scriptElems = document.getElementsByTagName( 'script' ),
158 foundScriptTag = false,
165 function getTizenTheme( ) {
166 var t = navigator.theme ? navigator.theme.split( ':' )[0] : null;
168 t = t.replace('-hd', '');
169 if ( ! t.match( /^tizen-/ ) ) {
176 for ( idx in scriptElems ) {
177 elem = scriptElems[idx];
178 src = elem.src ? elem.getAttribute( 'src' ) : undefined;
179 if (src && src.match( this.libFileName )) {
180 // Set framework data, only when they are given.
181 tokens = src.split(/[\/\\]/);
183 this.frameworkData.rootDir = ( elem.getAttribute( 'data-framework-root' )
184 || tokens.slice( 0, tokens.length + version_idx ).join( '/' )
185 || this.frameworkData.rootDir ).replace( /^file:(\/\/)?/, '' );
186 this.frameworkData.version = elem.getAttribute( 'data-framework-version' )
187 || tokens[ tokens.length + version_idx ]
188 || this.frameworkData.version;
189 this.frameworkData.theme = elem.getAttribute( 'data-framework-theme' )
191 || this.frameworkData.theme;
192 this.frameworkData.viewportWidth = elem.getAttribute( 'data-framework-viewport-width' )
193 || this.frameworkData.viewportWidth;
194 this.frameworkData.viewportScale =
195 "true" === elem.getAttribute( 'data-framework-viewport-scale' ) ? true
196 : this.frameworkData.viewportScale;
197 this.frameworkData.minified = src.search(/\.min\.js$/) > -1 ? true : false;
198 this.frameworkData.debug = "true" === elem.getAttribute( 'data-framework-debug' ) ? true
199 : this.frameworkData.debug;
200 foundScriptTag = true;
204 return foundScriptTag;
207 loadTheme: function ( theme ) {
213 theme = tizen.frameworkData.theme;
216 tizen.frameworkData.rootDir,
217 tizen.frameworkData.version,
222 jsPath = [ themePath, 'theme.js' ].join( '/' );
224 if ( tizen.frameworkData.minified ) {
225 cssPath = [themePath, 'tizen-web-ui-fw-theme.min.css'].join( '/' );
227 cssPath = [themePath, 'tizen-web-ui-fw-theme.css'].join( '/' );
229 tizen.css.load( cssPath );
230 tizen.util.loadScriptSync( jsPath );
233 /** Load Globalize culture file, and set default culture.
234 * @param[in] language (optional) Language code. ex) en-US, en, ko-KR, ko
235 * If language is not given, read language from html 'lang' attribute,
236 * or from system setting.
237 * @param[in] cultureDic (optional) Dictionary having language code->
239 loadGlobalizeCulture: function ( language, cultureDic ) {
245 function getLang ( language ) {
247 || $( 'html' ).attr( 'lang' )
248 || window.navigator.language.split( '.' )[0] // Webkit, Safari + workaround for Tizen
249 || window.navigator.userLanguage // IE
252 countryCodeIdx = lang.lastIndexOf('-'),
253 ignoreCodes = ['Cyrl', 'Latn', 'Mong']; // Not country code!
254 if ( countryCodeIdx != -1 ) { // Found country code!
255 countryCode = lang.substr( countryCodeIdx + 1 );
256 if ( ignoreCodes.join( '-' ).indexOf( countryCode ) < 0 ) {
257 // countryCode is not found from ignoreCodes.
258 // Make countryCode to uppercase.
259 lang = [ lang.substr( 0, countryCodeIdx ), countryCode.toUpperCase( ) ].join( '-' );
262 // NOTE: 'en' to 'en-US', because globalize has no 'en' culture file.
263 lang = lang == 'en' ? 'en-US' : lang;
267 function getNeutralLang ( lang ) {
268 var neutralLangIdx = lang.lastIndexOf( '-' ),
270 if ( neutralLangIdx != -1 ) {
271 neutralLang = lang.substr( 0, neutralLangIdx );
276 function getCultureFilePath ( lang, cFDic ) {
277 var cFPath = null; // error value
279 if ( "string" != typeof lang ) {
282 if ( cFDic && cFDic[lang] ) {
283 cFPath = cFDic[lang];
285 // Default Globalize culture file path
287 self.frameworkData.rootDir,
288 self.frameworkData.version,
291 ['globalize.culture.', lang, '.js'].join( '' ),
297 function printLoadError( cFPath, jqXHR ) {
298 tizen.log.error( "Error " + jqXHR.status + ": " + jqXHR.statusText
299 + "::Culture file (" + cFPath + ") is failed to load.");
302 function loadCultureFile ( cFPath, errCB ) {
303 function _successCB ( ) {
304 tizen.log.debug( "Culture file (" + cFPath + ") is loaded successfully." );
306 function _errCB ( jqXHR, textStatus, err ) {
308 errCB( jqXHR, textStatus, err );
310 printLoadError( cFPath, jqXHR );
314 if ( ! cFPath ) { // Invalid cFPath -> Regard it as '404 Not Found' error.
317 statusText: "Not Found"
319 _errCB( mockJSXHR, null, null );
332 lang = getLang( language );
333 cFPath = getCultureFilePath( lang, cultureDic );
334 loadCultureFile( cFPath,
335 function ( jqXHR, textStatus, err ) {
336 if ( jqXHR.status == 404 ) {
337 // If culture file is not found, try once more with neutral lang.
338 var nLang = getNeutralLang( lang ),
339 ncFPath = getCultureFilePath( nLang, cultureDic );
340 loadCultureFile( ncFPath, null );
342 printLoadError( cFPath, jqXHR );
348 setGlobalize: function ( ) {
349 var lang = this.loadGlobalizeCulture( );
352 // NOTE: It is not needed to set with neutral lang.
353 // Globalize automatically deals with it.
354 Globalize.culture( lang );
357 * Load custom globalize culture file
358 * Find current system language, and load appropriate culture file from given colture file list.
360 * @param[in] cultureDic collection of 'language':'culture file path' key-val pair.
363 * "en" : "culture/en.js",
364 * "fr" : "culture/fr.js",
365 * "ko-KR" : "culture/ko-KR.js"
367 * loadCultomGlobalizeCulture( myCultures );
370 * -------------------------------
371 * Globalize.addCultureInfo( "fr", {
373 * "hello" : "bonjour",
374 * "translate" : "traduire"
377 * -------------------------------
379 loadCustomGlobalizeCulture: function ( cultureDic ) {
380 tizen.loadGlobalizeCulture( null, cultureDic );
383 /** Set viewport meta tag for mobile devices.
385 * @param[in] viewportWidth viewport width. "device-width" is OK.
387 setViewport: function ( viewportWidth ) {
392 // Do nothing if viewport setting code is already in the code.
393 $( "meta[name=viewport]" ).each( function ( ) {
397 if ( meta ) { // Found custom viewport!
398 content = $( meta ).prop( "content" );
399 viewportWidth = content.replace( /.*width=(device-width|\d+)\s*,?.*$/gi, "$1" );
400 tizen.log.warn( "Viewport is set to '" + viewportWidth + "' in a meta tag. Framework skips viewport setting." );
403 meta = document.createElement( "meta" );
405 meta.name = "viewport";
406 content = [ "width=", viewportWidth, ", user-scalable=no" ].join( "" );
407 if ( ! isNaN( viewportWidth ) ) {
408 // Fix scale to 1.0, if viewport width is set to fixed value.
409 // NOTE: Works wrong in Tizen browser!
410 //content = [ content, ", initial-scale=1.0, maximum-scale=1.0" ].join( "" );
412 meta.content = content;
413 tizen.log.debug( content );
414 head = document.getElementsByTagName( 'head' ).item( 0 );
415 head.insertBefore( meta, head.firstChild );
418 return viewportWidth;
421 /** Read body's font-size, scale it, and reset it.
422 * param[in] desired font-size / base font-size.
424 scaleBaseFontSize: function ( themeDefaultFontSize, ratio ) {
425 tizen.log.debug( "themedefaultfont size: " + themeDefaultFontSize + ", ratio: " + ratio );
426 var scaledFontSize = Math.round( themeDefaultFontSize * ratio );
428 $( 'html.ui-mobile' ).css( { 'font-size': scaledFontSize + "px" } );
429 tizen.log.debug( 'html:font size is set to ' + scaledFontSize );
430 $( document ).ready( function ( ) {
431 $( '.ui-mobile' ).children( 'body' ).css( { 'font-size': scaledFontSize + "px" } );
435 setScaling: function ( ) {
436 var viewportWidth = this.frameworkData.viewportWidth,
437 themeDefaultFontSize = this.frameworkData.defaultFontSize, // comes from theme.js
440 // Keep original font size
441 $( 'body' ).attr( 'data-tizen-theme-default-font-size', themeDefaultFontSize );
443 // Legacy support: tizen.frameworkData.viewportScale
444 if ( this.frameworkData.viewportScale == true ) {
445 viewportWidth = "screen-width";
448 if ( "screen-width" == viewportWidth ) {
449 viewportWidth = document.documentElement.clientWidth;
452 viewportWidth = this.setViewport( viewportWidth ); // If custom viewport setting exists, get viewport width
453 if ( ! isNaN( viewportWidth ) ) { // fixed width!
454 ratio = parseFloat( viewportWidth / this.frameworkData.defaultViewportWidth );
456 this.scaleBaseFontSize( themeDefaultFontSize, ratio );
460 function export2TizenNS ( $, tizen ) {
461 if ( undefined == typeof $.tizen ) {
465 $.tizen.frameworkData = tizen.frameworkData;
466 $.tizen.loadCustomGlobalizeCulture = tizen.loadCustomGlobalizeCulture;
467 $.tizen.loadTheme = tizen.loadTheme;
469 $.tizen.__tizen__ = tizen; // for unit-test
472 export2TizenNS( $, tizen );
476 tizen.setScaling( ); // Run after loadTheme(), for the default font size.
477 tizen.setGlobalize( );
479 // Turn off JQM's auto initialization option.
480 // NOTE: This job must be done before domready.
481 $.mobile.autoInitializePage = false;
483 $(document).ready( function ( ) {
484 $.mobile.initializePage( );
487 } ( jQuery, window.Globalize, window ) );