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",
57 loadScriptSync : function ( scriptPath, successCB, errorCB ) {
64 error: function ( jqXHR, textStatus, errorThrown ) {
66 errorCB( jqXHR, textStatus, errorThrown );
68 var ignoreStatusList = [ 404 ]; // 404: not found
69 if ( -1 == $.inArray( jqXHR.status, ignoreStatusList ) ) {
70 window.alert( 'Error while loading ' + scriptPath + '\n' + jqXHR.status + ':' + jqXHR.statusText );
72 console.log( 'Error while loading ' + scriptPath + '\n' + jqXHR.status + ':' + jqXHR.statusText );
78 isMobileBrowser: function ( ) {
79 var mobileIdx = window.navigator.appVersion.indexOf("Mobile"),
80 isMobile = -1 < mobileIdx;
86 cacheBust: ( document.location.href.match( /debug=true/ ) ) ?
87 '?cacheBust=' + ( new Date( ) ).getTime( ) :
89 addElementToHead : function ( elem ) {
90 var head = document.getElementsByTagName( 'head' )[0];
92 $( head ).prepend( elem );
95 makeLink : function ( href ) {
96 var cssLink = document.createElement( 'link' );
97 cssLink.setAttribute( 'rel', 'stylesheet' );
98 cssLink.setAttribute( 'href', href );
99 cssLink.setAttribute( 'name', 'tizen-theme' );
102 load: function ( path ) {
103 var head = document.getElementsByTagName( 'head' )[0],
104 cssLinks = head.getElementsByTagName( 'link' ),
107 // Find css link element
108 for ( idx = 0; idx < cssLinks.length; idx++ ) {
109 if( cssLinks[idx].getAttribute( 'rel' ) != "stylesheet" ) {
112 if( cssLinks[idx].getAttribute( 'name' ) == "tizen-theme"
113 || cssLinks[idx].getAttribute( 'href' ) == path ) {
118 if ( l ) { // Found the link element!
119 if ( l.getAttribute( 'href' ) == path ) {
120 console.log( "Theme is already loaded. Skip theme loading in the framework." );
122 l.setAttribute( 'href', path );
125 this.addElementToHead( this.makeLink( path ) );
130 getParams: function ( ) {
131 /* Get data-* params from <script> tag, and set tizen.frameworkData.* values
132 * Returns true if proper <script> tag is found, or false if not.
134 // Find current <script> tag element
135 var scriptElems = document.getElementsByTagName( 'script' ),
137 foundScriptTag = false,
144 function getTizenTheme( ) {
145 var t = navigator.theme ? navigator.theme.split( ':' )[0] : null;
147 t = t.replace('-hd', '');
148 if( ! t.match( /^tizen-/ ) ) {
155 for ( idx in scriptElems ) {
156 elem = scriptElems[idx];
157 src = elem.src ? elem.getAttribute( 'src' ) : undefined;
158 if (src && src.match( this.libFileName )) {
159 // Set framework data, only when they are given.
160 tokens = src.split(/[\/\\]/);
162 this.frameworkData.rootDir = ( elem.getAttribute( 'data-framework-root' )
163 || tokens.slice( 0, tokens.length + version_idx ).join( '/' )
164 || this.frameworkData.rootDir ).replace( /^file:(\/\/)?/, '' );
165 this.frameworkData.version = elem.getAttribute( 'data-framework-version' )
166 || tokens[ tokens.length + version_idx ]
167 || this.frameworkData.version;
168 this.frameworkData.theme = elem.getAttribute( 'data-framework-theme' )
170 || this.frameworkData.theme;
171 this.frameworkData.viewportWidth = elem.getAttribute( 'data-framework-viewport-width' )
172 || this.frameworkData.viewportWidth;
173 this.frameworkData.viewportScale =
174 "true" === elem.getAttribute( 'data-framework-viewport-scale' ) ? true
175 : this.frameworkData.viewportScale;
176 this.frameworkData.minified = src.search(/\.min\.js$/) > -1 ? true : false;
177 foundScriptTag = true;
181 return foundScriptTag;
184 loadTheme: function ( theme ) {
185 var themePath, cssPath, jsPath;
188 theme = tizen.frameworkData.theme;
191 tizen.frameworkData.rootDir,
192 tizen.frameworkData.version,
197 jsPath = [themePath, 'theme.js'].join( '/' );
199 if( tizen.frameworkData.minified ) {
200 cssPath = [themePath, 'tizen-web-ui-fw-theme.min.css'].join( '/' );
202 cssPath = [themePath, 'tizen-web-ui-fw-theme.css'].join( '/' );
204 tizen.css.load( cssPath );
205 tizen.util.loadScriptSync( jsPath );
208 /** Load Globalize culture file, and set default culture.
209 * @param[in] language (optional) Language code. ex) en-US, en, ko-KR, ko
210 * If language is not given, read language from html 'lang' attribute,
211 * or from system setting.
212 * @param[in] cultureDic (optional) Dictionary having language code->
214 loadGlobalizeCulture: function ( language, cultureDic ) {
220 function getLang ( language ) {
222 || $( 'html' ).attr( 'lang' )
223 || window.navigator.language.split( '.' )[0] // Webkit, Safari + workaround for Tizen
224 || window.navigator.userLanguage // IE
227 countryCodeIdx = lang.lastIndexOf('-'),
228 ignoreCodes = ['Cyrl', 'Latn', 'Mong']; // Not country code!
229 if ( countryCodeIdx != -1 ) { // Found country code!
230 countryCode = lang.substr( countryCodeIdx + 1 );
231 if ( ignoreCodes.join( '-' ).indexOf( countryCode ) < 0 ) {
232 // countryCode is not found from ignoreCodes.
233 // Make countryCode to uppercase.
234 lang = [ lang.substr( 0, countryCodeIdx ), countryCode.toUpperCase( ) ].join( '-' );
237 // NOTE: 'en' to 'en-US', because globalize has no 'en' culture file.
238 lang = lang == 'en' ? 'en-US' : lang;
242 function getNeutralLang ( lang ) {
243 var neutralLangIdx = lang.lastIndexOf( '-' ),
245 if ( neutralLangIdx != -1 ) {
246 neutralLang = lang.substr( 0, neutralLangIdx );
251 function getCultureFilePath ( lang, cFDic ) {
252 var cFPath = null; // error value
254 if ( "string" != typeof lang ) {
258 if ( cFDic[lang] ) cFPath = cFDic[lang];
260 // Default Globalize culture file path
262 self.frameworkData.rootDir,
263 self.frameworkData.version,
266 ['globalize.culture.', lang, '.js'].join( '' ),
272 function printLoadError( cFPath, jqXHR ) {
273 console.log( "Error " + jqXHR.status + ": " + jqXHR.statusText );
274 console.log( "::Culture file (" + cFPath + ") is failed to load.");
277 function loadCultureFile ( cFPath, errCB ) {
278 function _successCB ( ) {
279 console.log( "Culture file (" + cFPath + ") is loaded successfully.");
281 function _errCB ( jqXHR, textStatus, err ) {
283 errCB( jqXHR, textStatus, err );
286 printLoadError( cFPath, jqXHR );
290 if( ! cFPath ) { // Invalid cFPath -> Regard it as '404 Not Found' error.
293 statusText: "Not Found"
295 _errCB( mockJSXHR, null, null );
308 lang = getLang( language );
309 cFPath = getCultureFilePath( lang, cultureDic );
310 loadCultureFile( cFPath,
311 function ( jqXHR, textStatus, err ) {
312 if( jqXHR.status == 404 ) {
313 // If culture file is not found, try once more with neutral lang.
314 var nLang = getNeutralLang( lang ),
315 cFPath = getCultureFilePath( nLang, cultureDic );
316 loadCultureFile( cFPath, null );
318 printLoadError( cFPath, jqXHR );
324 setGlobalize: function ( ) {
325 var lang = this.loadGlobalizeCulture( );
328 // NOTE: It is not needed to set with neutral lang.
329 // Globalize automatically deals with it.
330 Globalize.culture( lang );
333 * Load custom globalize culture file
334 * Find current system language, and load appropriate culture file from given colture file list.
336 * @param[in] cultureDic collection of 'language':'culture file path' key-val pair.
339 * "en" : "culture/en.js",
340 * "fr" : "culture/fr.js",
341 * "ko-KR" : "culture/ko-KR.js"
343 * loadCultomGlobalizeCulture( myCultures );
346 * -------------------------------
347 * Globalize.addCultureInfo( "fr", {
349 * "hello" : "bonjour",
350 * "translate" : "traduire"
353 * -------------------------------
355 loadCustomGlobalizeCulture: function ( cultureDic ) {
356 tizen.loadGlobalizeCulture( null, cultureDic );
359 /** Set viewport meta tag for mobile devices.
361 * @param[in] viewportWidth viewport width. "device-width" is OK.
363 setViewport: function ( viewportWidth ) {
368 // Do nothing if viewport setting code is already in the code.
369 $( "meta[name=viewport]" ).each( function ( ) {
373 if( meta ) { // Found custom viewport!
374 content = $( meta ).prop( "content" );
375 console.log( "Viewport is already set. Framework skips viewport setting." );
376 viewportWidth = content.replace( /.*width=(device-width|\d+)\s*,?.*$/gi, "$1" )
379 meta = document.createElement( "meta" );
381 meta.name = "viewport";
382 content = [ "width=", viewportWidth, ", user-scalable=no" ].join( "" );
383 if ( ! isNaN( viewportWidth ) ) {
384 // Fix scale to 1.0, if viewport width is set to fixed value.
385 // NOTE: Works wrong in Tizen browser!
386 //content = [ content, ", initial-scale=1.0, maximum-scale=1.0" ].join( "" );
388 meta.content = content;
389 console.log( content );
390 head = document.getElementsByTagName( 'head' ).item( 0 );
391 head.insertBefore( meta, head.firstChild );
394 return viewportWidth;
397 /** Read body's font-size, scale it, and reset it.
398 * param[in] desired font-size / base font-size.
400 scaleBaseFontSize: function ( themeDefaultFontSize, ratio ) {
401 console.log( "themedefaultfont size: " + themeDefaultFontSize + ", ratio: " + ratio );
402 var scaledFontSize = Math.round( themeDefaultFontSize * ratio );
404 $( 'html.ui-mobile' ).css( { 'font-size': scaledFontSize + "px" } );
405 console.log('html:font size is set to ' + scaledFontSize );
406 $( document ).ready( function ( ) {
407 $( '.ui-mobile').children( 'body' ).css( { 'font-size': scaledFontSize + "px" } );
411 setScaling: function ( ) {
412 var viewportWidth = this.frameworkData.viewportWidth,
413 themeDefaultFontSize = this.frameworkData.defaultFontSize, // comes from theme.js
416 // Keep original font size
417 $( 'body' ).attr( 'data-tizen-theme-default-font-size', themeDefaultFontSize );
419 // Legacy support: tizen.frameworkData.viewportScale
420 if ( this.frameworkData.viewportScale == true ) {
421 viewportWidth = "screen-width";
424 if ( "screen-width" == viewportWidth ) {
425 viewportWidth = document.documentElement.clientWidth;
428 viewportWidth = this.setViewport( viewportWidth ); // If custom viewport setting exists, get viewport width
429 if ( ! isNaN( viewportWidth ) ) { // fixed width!
430 ratio = parseFloat( viewportWidth / this.frameworkData.defaultViewportWidth );
432 this.scaleBaseFontSize( themeDefaultFontSize, ratio );
436 function export2TizenNS ( $, tizen ) {
437 if ( undefined == typeof $.tizen ) {
441 $.tizen.frameworkData = tizen.frameworkData;
442 $.tizen.loadCustomGlobalizeCulture = tizen.loadCustomGlobalizeCulture;
443 $.tizen.loadTheme = tizen.loadTheme;
445 $.tizen.__tizen__ = tizen; // for unit-test
448 export2TizenNS( $, tizen );
452 tizen.setScaling( ); // Run after loadTheme(), for the default font size.
453 tizen.setGlobalize( );
455 // Turn off JQM's auto initialization option.
456 // NOTE: This job must be done before domready.
457 $.mobile.autoInitializePage = false;
459 $(document).ready( function ( ) {
460 $.mobile.initializePage( );
463 } ( jQuery, window.Globalize, window ) );