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( 'name' ) == "tizen-theme" ) {
114 if ( l ) { // Found the link element!
115 l.setAttribute( 'href', path );
117 this.addElementToHead( this.makeLink( path ) );
122 getParams: function ( ) {
123 /* Get data-* params from <script> tag, and set tizen.frameworkData.* values
124 * Returns true if proper <script> tag is found, or false if not.
126 // Find current <script> tag element
127 var scriptElems = document.getElementsByTagName( 'script' ),
129 foundScriptTag = false,
136 function getTizenTheme( ) {
137 var t = navigator.theme ? navigator.theme.split( ':' )[0] : null;
139 t = t.replace('-hd', '');
140 if( ! t.match( /^tizen-/ ) ) {
147 for ( idx in scriptElems ) {
148 elem = scriptElems[idx];
149 src = elem.src ? elem.getAttribute( 'src' ) : undefined;
150 if (src && src.match( this.libFileName )) {
151 // Set framework data, only when they are given.
152 tokens = src.split(/[\/\\]/);
154 this.frameworkData.rootDir = ( elem.getAttribute( 'data-framework-root' )
155 || tokens.slice( 0, tokens.length + version_idx ).join( '/' )
156 || this.frameworkData.rootDir ).replace( /^file:(\/\/)?/, '' );
157 this.frameworkData.version = elem.getAttribute( 'data-framework-version' )
158 || tokens[ tokens.length + version_idx ]
159 || this.frameworkData.version;
160 this.frameworkData.theme = elem.getAttribute( 'data-framework-theme' )
162 || this.frameworkData.theme;
163 this.frameworkData.viewportWidth = elem.getAttribute( 'data-framework-viewport-width' )
164 || this.frameworkData.viewportWidth;
165 this.frameworkData.viewportScale =
166 "true" === elem.getAttribute( 'data-framework-viewport-scale' ) ? true
167 : this.frameworkData.viewportScale;
168 this.frameworkData.minified = src.search(/\.min\.js$/) > -1 ? true : false;
169 foundScriptTag = true;
173 return foundScriptTag;
176 loadTheme: function ( theme ) {
177 var themePath, cssPath, jsPath;
180 theme = tizen.frameworkData.theme;
183 tizen.frameworkData.rootDir,
184 tizen.frameworkData.version,
189 jsPath = [themePath, 'theme.js'].join( '/' );
191 if( tizen.frameworkData.minified ) {
192 cssPath = [themePath, 'tizen-web-ui-fw-theme.min.css'].join( '/' );
194 cssPath = [themePath, 'tizen-web-ui-fw-theme.css'].join( '/' );
196 tizen.css.load( cssPath );
197 tizen.util.loadScriptSync( jsPath );
200 /** Load Globalize culture file, and set default culture.
201 * @param[in] language (optional) Language code. ex) en-US, en, ko-KR, ko
202 * If language is not given, read language from html 'lang' attribute,
203 * or from system setting.
204 * @param[in] cultureDic (optional) Dictionary having language code->
206 loadGlobalizeCulture: function ( language, cultureDic ) {
212 function getLang ( language ) {
214 || $( 'html' ).attr( 'lang' )
215 || window.navigator.language.split( '.' )[0] // Webkit, Safari + workaround for Tizen
216 || window.navigator.userLanguage // IE
219 countryCodeIdx = lang.lastIndexOf('-'),
220 ignoreCodes = ['Cyrl', 'Latn', 'Mong']; // Not country code!
221 if ( countryCodeIdx != -1 ) { // Found country code!
222 countryCode = lang.substr( countryCodeIdx + 1 );
223 if ( ignoreCodes.join( '-' ).indexOf( countryCode ) < 0 ) {
224 // countryCode is not found from ignoreCodes.
225 // Make countryCode to uppercase.
226 lang = [ lang.substr( 0, countryCodeIdx ), countryCode.toUpperCase( ) ].join( '-' );
229 // NOTE: 'en' to 'en-US', because globalize has no 'en' culture file.
230 lang = lang == 'en' ? 'en-US' : lang;
234 function getNeutralLang ( lang ) {
235 var neutralLangIdx = lang.lastIndexOf( '-' ),
237 if ( neutralLangIdx != -1 ) {
238 neutralLang = lang.substr( 0, neutralLangIdx );
243 function getCultureFilePath ( lang, cFDic ) {
244 var cFPath = null; // error value
246 if ( "string" != typeof lang ) {
250 if ( cFDic[lang] ) cFPath = cFDic[lang];
252 // Default Globalize culture file path
254 self.frameworkData.rootDir,
255 self.frameworkData.version,
258 ['globalize.culture.', lang, '.js'].join( '' ),
264 function printLoadError( cFPath, jqXHR ) {
265 console.log( "Error " + jqXHR.status + ": " + jqXHR.statusText );
266 console.log( "::Culture file (" + cFPath + ") is failed to load.");
269 function loadCultureFile ( cFPath, errCB ) {
270 function _successCB ( ) {
271 console.log( "Culture file (" + cFPath + ") is loaded successfully.");
273 function _errCB ( jqXHR, textStatus, err ) {
275 errCB( jqXHR, textStatus, err );
278 printLoadError( cFPath, jqXHR );
282 if( ! cFPath ) { // Invalid cFPath -> Regard it as '404 Not Found' error.
285 statusText: "Not Found"
287 _errCB( mockJSXHR, null, null );
300 lang = getLang( language );
301 cFPath = getCultureFilePath( lang, cultureDic );
302 loadCultureFile( cFPath,
303 function ( jqXHR, textStatus, err ) {
304 if( jqXHR.status == 404 ) {
305 // If culture file is not found, try once more with neutral lang.
306 var nLang = getNeutralLang( lang ),
307 cFPath = getCultureFilePath( nLang, cultureDic );
308 loadCultureFile( cFPath, null );
310 printLoadError( cFPath, jqXHR );
316 setGlobalize: function ( ) {
317 var lang = this.loadGlobalizeCulture( );
320 // NOTE: It is not needed to set with neutral lang.
321 // Globalize automatically deals with it.
322 Globalize.culture( lang );
325 * Load custom globalize culture file
326 * Find current system language, and load appropriate culture file from given colture file list.
328 * @param[in] cultureDic collection of 'language':'culture file path' key-val pair.
331 * "en" : "culture/en.js",
332 * "fr" : "culture/fr.js",
333 * "ko-KR" : "culture/ko-KR.js"
335 * loadCultomGlobalizeCulture( myCultures );
338 * -------------------------------
339 * Globalize.addCultureInfo( "fr", {
341 * "hello" : "bonjour",
342 * "translate" : "traduire"
345 * -------------------------------
347 loadCustomGlobalizeCulture: function ( cultureDic ) {
348 tizen.loadGlobalizeCulture( null, cultureDic );
351 /** Set viewport meta tag for mobile devices.
353 * @param[in] viewportWidth viewport width. "device-width" is OK.
355 setViewport: function ( viewportWidth ) {
360 // Do nothing if viewport setting code is already in the code.
361 $( "meta[name=viewport]" ).each( function ( ) {
365 if( meta ) { // Found custom viewport!
366 content = $( meta ).prop( "content" );
367 console.log( "Viewport is already set. Framework skips viewport setting." );
368 viewportWidth = content.replace( /.*width=(device-width|\d+)\s*,?.*$/gi, "$1" )
371 meta = document.createElement( "meta" );
373 meta.name = "viewport";
374 content = [ "width=", viewportWidth, ", user-scalable=no" ].join( "" );
375 if ( ! isNaN( viewportWidth ) ) {
376 // Fix scale to 1.0, if viewport width is set to fixed value.
377 // NOTE: Works wrong in Tizen browser!
378 //content = [ content, ", initial-scale=1.0, maximum-scale=1.0" ].join( "" );
380 meta.content = content;
381 console.log( content );
382 head = document.getElementsByTagName( 'head' ).item( 0 );
383 head.insertBefore( meta, head.firstChild );
386 return viewportWidth;
389 /** Read body's font-size, scale it, and reset it.
390 * param[in] desired font-size / base font-size.
392 scaleBaseFontSize: function ( themeDefaultFontSize, ratio ) {
393 console.log( "themedefaultfont size: " + themeDefaultFontSize + ", ratio: " + ratio );
394 var scaledFontSize = Math.round( themeDefaultFontSize * ratio );
396 $( 'html.ui-mobile' ).css( { 'font-size': scaledFontSize + "px" } );
397 console.log('html:font size is set to ' + scaledFontSize );
398 $( document ).ready( function ( ) {
399 $( '.ui-mobile').children( 'body' ).css( { 'font-size': scaledFontSize + "px" } );
403 setScaling: function ( ) {
404 var viewportWidth = this.frameworkData.viewportWidth,
405 themeDefaultFontSize = this.frameworkData.defaultFontSize, // comes from theme.js
408 // Keep original font size
409 $( 'body' ).attr( 'data-tizen-theme-default-font-size', themeDefaultFontSize );
411 // Legacy support: tizen.frameworkData.viewportScale
412 if ( this.frameworkData.viewportScale == true ) {
413 viewportWidth = "screen-width";
416 if ( "screen-width" == viewportWidth ) {
417 viewportWidth = window.outerWidth;
418 // TODO : Above code will be replaced by below codes. But screen.availWidth has a webkit bug at this moment.
419 // viewportWidth = screen.availWidth,
422 viewportWidth = this.setViewport( viewportWidth ); // If custom viewport setting exists, get viewport width
423 if ( ! isNaN( viewportWidth ) ) { // fixed width!
424 ratio = parseFloat( viewportWidth / this.frameworkData.defaultViewportWidth );
426 this.scaleBaseFontSize( themeDefaultFontSize, ratio );
430 function export2TizenNS ( $, tizen ) {
431 if ( undefined == typeof $.tizen ) {
435 $.tizen.frameworkData = tizen.frameworkData;
436 $.tizen.loadCustomGlobalizeCulture = tizen.loadCustomGlobalizeCulture;
437 $.tizen.loadTheme = tizen.loadTheme;
439 $.tizen.__tizen__ = tizen; // for unit-test
442 export2TizenNS( $, tizen );
446 tizen.setScaling( ); // Run after loadTheme(), for the default font size.
447 tizen.setGlobalize( );
449 // Turn off JQM's auto initialization option.
450 // NOTE: This job must be done before domready.
451 $.mobile.autoInitializePage = false;
453 $(document).ready( function ( ) {
454 $.mobile.initializePage( );
457 } ( jQuery, window.Globalize, window ) );