loader: Viewport width is set to document width
[platform/framework/web/web-ui-fw.git] / src / loader / loader.js
1 /**
2  * @class core
3  * loader.js
4  *
5  * Youmin Ha <youmin.ha@samsung.com>
6  *
7  *
8  */
9 /*
10         Web UI scaling concept in Tizen Web UI
11
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.
13
14
15 * Viewport on mobile web browser
16
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
21
22
23 * Viewport setting by application developers
24
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.
26
27
28 * Automatic viewport setting by Tizen Web UI Framework
29
30 If developers do not give a viewport meta tag, Tizen Web UI Framework automatically add a viewport meta tag with default viewport setting.
31
32
33 * Portrait/landscape mode
34
35
36 * Tizen Web UI widgets scaling
37
38
39  */
40 ( function ($, Globalize, window, undefined) {
41
42          var tizen = {
43                 libFileName : "tizen-web-ui-fw(.min)?.js",
44
45                 frameworkData : {
46                         rootDir: '/usr/lib/tizen-web-ui-fw',
47                         version: '0.1',
48                         theme: "tizen-white",
49                         viewportWidth: "device-width",
50                         viewportScale: false,
51
52                         defaultFontSize: 22,
53                         minified: false
54                 },
55
56                 util : {
57                         loadScriptSync : function ( scriptPath, successCB, errorCB ) {
58                                 $.ajax( {
59                                         url: scriptPath,
60                                         dataType: 'script',
61                                         async: false,
62                                         crossDomain: false,
63                                         success: successCB,
64                                         error: function ( jqXHR, textStatus, errorThrown ) {
65                                                 if ( errorCB ) {
66                                                         errorCB( jqXHR, textStatus, errorThrown );
67                                                 } else {
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 );
71                                                         } else {
72                                                                 console.log( 'Error while loading ' + scriptPath + '\n' + jqXHR.status + ':' + jqXHR.statusText );
73                                                         }
74                                                 }
75                                         }
76                                 } );
77                         },
78                         isMobileBrowser: function ( ) {
79                                 var mobileIdx = window.navigator.appVersion.indexOf("Mobile"),
80                                         isMobile = -1 < mobileIdx;
81                                 return isMobile;
82                         }
83                 },
84
85                 css : {
86                         cacheBust: ( document.location.href.match( /debug=true/ ) ) ?
87                                         '?cacheBust=' + ( new Date( ) ).getTime( ) :
88                                         '',
89                         addElementToHead : function ( elem ) {
90                                 var head = document.getElementsByTagName( 'head' )[0];
91                                 if( head ) {
92                                         $( head ).prepend( elem );
93                                 }
94                         },
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' );
100                                 return cssLink;
101                         },
102                         load: function ( path ) {
103                                 var head = document.getElementsByTagName( 'head' )[0],
104                                         cssLinks = head.getElementsByTagName( 'link' ),
105                                         idx,
106                                         l = null;
107                                 // Find css link element
108                                 for ( idx = 0; idx < cssLinks.length; idx++ ) {
109                                         if( cssLinks[idx].getAttribute( 'rel' ) != "stylesheet" ) {
110                                                 continue;
111                                         }
112                                         if( cssLinks[idx].getAttribute( 'name' ) == "tizen-theme" 
113                                                         || cssLinks[idx].getAttribute( 'href' ) == path ) {
114                                                 l = cssLinks[idx];
115                                                 break;
116                                         }
117                                 }
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." );
121                                         } else {
122                                                 l.setAttribute( 'href', path );
123                                         }
124                                 } else {
125                                         this.addElementToHead( this.makeLink( path ) );
126                                 }
127                         }
128                 },
129
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.
133                          */
134                         // Find current <script> tag element
135                         var scriptElems = document.getElementsByTagName( 'script' ),
136                                 val = null,
137                                 foundScriptTag = false,
138                                 idx,
139                                 elem,
140                                 src,
141                                 tokens,
142                                 version_idx;
143
144                         function getTizenTheme( ) {
145                                 var t = navigator.theme ? navigator.theme.split( ':' )[0] : null;
146                                 if ( t ) {
147                                         t = t.replace('-hd', '');
148                                         if( ! t.match( /^tizen-/ ) ) {
149                                                 t = 'tizen-' + t;
150                                         }
151                                 }
152                                 return t;
153                         }
154
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(/[\/\\]/);
161                                         version_idx = -3;
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' )
169                                                 || getTizenTheme( )
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;
178                                         break;
179                                 }
180                         }
181                         return foundScriptTag;
182                 },
183
184                 loadTheme: function ( theme ) {
185                         var themePath, cssPath, jsPath;
186
187                         if ( ! theme ) {
188                                 theme = tizen.frameworkData.theme;
189                         }
190                         themePath = [
191                                         tizen.frameworkData.rootDir,
192                                         tizen.frameworkData.version,
193                                         'themes',
194                                         theme
195                                 ].join( '/' ),
196
197                         jsPath = [themePath, 'theme.js'].join( '/' );
198
199                         if( tizen.frameworkData.minified ) {
200                                 cssPath = [themePath, 'tizen-web-ui-fw-theme.min.css'].join( '/' );
201                         } else {
202                                 cssPath = [themePath, 'tizen-web-ui-fw-theme.css'].join( '/' );
203                         }
204                         tizen.css.load( cssPath );
205                         tizen.util.loadScriptSync( jsPath );
206                 },
207
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->
213                  */
214                 loadGlobalizeCulture: function ( language, cultureDic ) {
215                         var self = this,
216                                 cFPath,
217                                 lang,
218                                 mockJSXHR;
219
220                         function getLang ( language ) {
221                                 var lang = language
222                                                 || $( 'html' ).attr( 'lang' )
223                                                 || window.navigator.language.split( '.' )[0]    // Webkit, Safari + workaround for Tizen
224                                                 || window.navigator.userLanguage        // IE
225                                                 || 'en',
226                                         countryCode = null,
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( '-' );
235                                         }
236                                 }
237                                 // NOTE: 'en' to 'en-US', because globalize has no 'en' culture file.
238                                 lang = lang == 'en' ? 'en-US' : lang;
239                                 return lang;
240                         }
241
242                         function getNeutralLang ( lang ) {
243                                 var neutralLangIdx = lang.lastIndexOf( '-' ),
244                                         neutralLang;
245                                 if ( neutralLangIdx != -1 ) {
246                                         neutralLang = lang.substr( 0, neutralLangIdx );
247                                 }
248                                 return neutralLang;
249                         }
250
251                         function getCultureFilePath ( lang, cFDic ) {
252                                 var cFPath = null;      // error value
253
254                                 if ( "string" != typeof lang ) {
255                                         return null;
256                                 }
257                                 if ( cFDic ) {
258                                         if ( cFDic[lang] ) cFPath = cFDic[lang];
259                                 } else {
260                                         // Default Globalize culture file path
261                                         cFPath = [
262                                                 self.frameworkData.rootDir,
263                                                 self.frameworkData.version,
264                                                 'js',
265                                                 'cultures',
266                                                 ['globalize.culture.', lang, '.js'].join( '' ),
267                                         ].join( '/' );
268                                 }
269                                 return cFPath;
270                         }
271
272                         function printLoadError( cFPath, jqXHR ) {
273                                 console.log( "Error " + jqXHR.status + ": " + jqXHR.statusText );
274                                 console.log( "::Culture file (" + cFPath + ") is failed to load.");
275                         }
276
277                         function loadCultureFile ( cFPath, errCB ) {
278                                 function _successCB ( ) {
279                                         console.log( "Culture file (" + cFPath + ") is loaded successfully.");
280                                 }
281                                 function _errCB ( jqXHR, textStatus, err ) {
282                                         if( errCB ) {
283                                                 errCB( jqXHR, textStatus, err );
284                                         }
285                                         else {
286                                                 printLoadError( cFPath, jqXHR );
287                                         }
288                                 }
289
290                                 if( ! cFPath ) {        // Invalid cFPath -> Regard it as '404 Not Found' error.
291                                         mockJSXHR = {
292                                                 status: 404,
293                                                 statusText: "Not Found"
294                                         };
295                                         _errCB( mockJSXHR, null, null );
296                                 } else {
297                                         $.ajax( {
298                                                 url: cFPath,
299                                                 dataType: 'script',
300                                                 cache: true,
301                                                 async: false,
302                                                 success: _successCB,
303                                                 error: _errCB
304                                         } );
305                                 }
306                         }
307
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 );
317                                         } else {
318                                                 printLoadError( cFPath, jqXHR );
319                                         }
320                                 } );
321
322                         return lang;
323                 },
324                 setGlobalize: function ( ) {
325                         var lang = this.loadGlobalizeCulture( );
326
327                         // Set culture
328                         // NOTE: It is not needed to set with neutral lang.
329                         //       Globalize automatically deals with it.
330                         Globalize.culture( lang );
331                 },
332                 /**
333                  * Load custom globalize culture file
334                  * Find current system language, and load appropriate culture file from given colture file list.
335                  *
336                  * @param[in]   cultureDic      collection of 'language':'culture file path' key-val pair.
337                  * @example
338                  * var myCultures = {
339                  *              "en"    : "culture/en.js",
340                  *              "fr"    : "culture/fr.js",
341                  *              "ko-KR" : "culture/ko-KR.js"
342                  * };
343                  * loadCultomGlobalizeCulture( myCultures );
344                  *
345                  * ex) culture/fr.js
346                  * -------------------------------
347                  * Globalize.addCultureInfo( "fr", {
348                  *   messages: {
349                  *     "hello" : "bonjour",
350                  *     "translate" : "traduire"
351                  *   }
352                  * } );
353                  * -------------------------------
354                  */
355                 loadCustomGlobalizeCulture: function ( cultureDic ) {
356                         tizen.loadGlobalizeCulture( null, cultureDic );
357                 },
358
359                 /** Set viewport meta tag for mobile devices.
360                  *
361                  * @param[in]   viewportWidth   viewport width. "device-width" is OK.
362                  */
363                 setViewport: function ( viewportWidth ) {
364                         var meta = null,
365                                 head,
366                                 content;
367
368                         // Do nothing if viewport setting code is already in the code.
369                         $( "meta[name=viewport]" ).each( function ( ) {
370                                 meta = this;
371                                 return;
372                         });
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" )
377                         } else {
378                                 // Create a meta tag
379                                 meta = document.createElement( "meta" );
380                                 if ( 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( "" );
387                                         }
388                                         meta.content = content;
389                                         console.log( content );
390                                         head = document.getElementsByTagName( 'head' ).item( 0 );
391                                         head.insertBefore( meta, head.firstChild );
392                                 }
393                         }
394                         return viewportWidth;
395                 },
396
397                 /**     Read body's font-size, scale it, and reset it.
398                  *  param[in]   desired font-size / base font-size.
399                  */
400                 scaleBaseFontSize: function ( themeDefaultFontSize, ratio ) {
401                         console.log( "themedefaultfont size: " + themeDefaultFontSize + ", ratio: " + ratio );
402                         var scaledFontSize = Math.round( themeDefaultFontSize * ratio );
403
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" } );
408                         } );
409                 },
410
411                 setScaling: function ( ) {
412                         var viewportWidth = this.frameworkData.viewportWidth,
413                                 themeDefaultFontSize = this.frameworkData.defaultFontSize, // comes from theme.js
414                                 ratio = 1;
415
416                         // Keep original font size
417                         $( 'body' ).attr( 'data-tizen-theme-default-font-size', themeDefaultFontSize );
418
419                         // Legacy support: tizen.frameworkData.viewportScale
420                         if ( this.frameworkData.viewportScale == true ) {
421                                 viewportWidth = "screen-width";
422                         }
423
424                         if ( "screen-width" == viewportWidth ) {
425                                 viewportWidth = document.documentElement.clientWidth;
426                         }
427
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 );
431                         }
432                         this.scaleBaseFontSize( themeDefaultFontSize, ratio );
433                 }
434         };
435
436         function export2TizenNS ( $, tizen ) {
437                 if ( undefined == typeof $.tizen ) {
438                         $.tizen = { };
439                 }
440
441                 $.tizen.frameworkData = tizen.frameworkData;
442                 $.tizen.loadCustomGlobalizeCulture = tizen.loadCustomGlobalizeCulture;
443                 $.tizen.loadTheme = tizen.loadTheme;
444
445                 $.tizen.__tizen__ = tizen;      // for unit-test
446         }
447
448         export2TizenNS( $, tizen );
449
450         tizen.getParams( );
451         tizen.loadTheme( );
452         tizen.setScaling( );    // Run after loadTheme(), for the default font size.
453         tizen.setGlobalize( );
454
455         // Turn off JQM's auto initialization option.
456         // NOTE: This job must be done before domready.
457         $.mobile.autoInitializePage = false;
458
459         $(document).ready( function ( ) {
460                 $.mobile.initializePage( );
461         });
462
463 } ( jQuery, window.Globalize, window ) );