Export 0.2.3
[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( 'name' ) == "tizen-theme" ) {
110                                                 l = cssLinks[idx];
111                                                 break;
112                                         }
113                                 }
114                                 if ( l ) {      // Found the link element!
115                                         l.setAttribute( 'href', path );
116                                 } else {
117                                         this.addElementToHead( this.makeLink( path ) );
118                                 }
119                         }
120                 },
121
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.
125                          */
126                         // Find current <script> tag element
127                         var scriptElems = document.getElementsByTagName( 'script' ),
128                                 val = null,
129                                 foundScriptTag = false,
130                                 idx,
131                                 elem,
132                                 src,
133                                 tokens,
134                                 version_idx;
135
136                         function getTizenTheme( ) {
137                                 var t = navigator.theme ? navigator.theme.split( ':' )[0] : null;
138                                 if ( t ) {
139                                         t = t.replace('-hd', '');
140                                         if( ! t.match( /^tizen-/ ) ) {
141                                                 t = 'tizen-' + t;
142                                         }
143                                 }
144                                 return t;
145                         }
146
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(/[\/\\]/);
153                                         version_idx = -3;
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' )
161                                                 || getTizenTheme( )
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;
170                                         break;
171                                 }
172                         }
173                         return foundScriptTag;
174                 },
175
176                 loadTheme: function ( theme ) {
177                         var themePath, cssPath, jsPath;
178
179                         if ( ! theme ) {
180                                 theme = tizen.frameworkData.theme;
181                         }
182                         themePath = [
183                                         tizen.frameworkData.rootDir,
184                                         tizen.frameworkData.version,
185                                         'themes',
186                                         theme
187                                 ].join( '/' ),
188
189                         jsPath = [themePath, 'theme.js'].join( '/' );
190
191                         if( tizen.frameworkData.minified ) {
192                                 cssPath = [themePath, 'tizen-web-ui-fw-theme.min.css'].join( '/' );
193                         } else {
194                                 cssPath = [themePath, 'tizen-web-ui-fw-theme.css'].join( '/' );
195                         }
196                         tizen.css.load( cssPath );
197                         tizen.util.loadScriptSync( jsPath );
198                 },
199
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->
205                  */
206                 loadGlobalizeCulture: function ( language, cultureDic ) {
207                         var self = this,
208                                 cFPath,
209                                 lang,
210                                 mockJSXHR;
211
212                         function getLang ( language ) {
213                                 var lang = language
214                                                 || $( 'html' ).attr( 'lang' )
215                                                 || window.navigator.language.split( '.' )[0]    // Webkit, Safari + workaround for Tizen
216                                                 || window.navigator.userLanguage        // IE
217                                                 || 'en',
218                                         countryCode = null,
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( '-' );
227                                         }
228                                 }
229                                 // NOTE: 'en' to 'en-US', because globalize has no 'en' culture file.
230                                 lang = lang == 'en' ? 'en-US' : lang;
231                                 return lang;
232                         }
233
234                         function getNeutralLang ( lang ) {
235                                 var neutralLangIdx = lang.lastIndexOf( '-' ),
236                                         neutralLang;
237                                 if ( neutralLangIdx != -1 ) {
238                                         neutralLang = lang.substr( 0, neutralLangIdx );
239                                 }
240                                 return neutralLang;
241                         }
242
243                         function getCultureFilePath ( lang, cFDic ) {
244                                 var cFPath = null;      // error value
245
246                                 if ( "string" != typeof lang ) {
247                                         return null;
248                                 }
249                                 if ( cFDic ) {
250                                         if ( cFDic[lang] ) cFPath = cFDic[lang];
251                                 } else {
252                                         // Default Globalize culture file path
253                                         cFPath = [
254                                                 self.frameworkData.rootDir,
255                                                 self.frameworkData.version,
256                                                 'js',
257                                                 'cultures',
258                                                 ['globalize.culture.', lang, '.js'].join( '' ),
259                                         ].join( '/' );
260                                 }
261                                 return cFPath;
262                         }
263
264                         function printLoadError( cFPath, jqXHR ) {
265                                 console.log( "Error " + jqXHR.status + ": " + jqXHR.statusText );
266                                 console.log( "::Culture file (" + cFPath + ") is failed to load.");
267                         }
268
269                         function loadCultureFile ( cFPath, errCB ) {
270                                 function _successCB ( ) {
271                                         console.log( "Culture file (" + cFPath + ") is loaded successfully.");
272                                 }
273                                 function _errCB ( jqXHR, textStatus, err ) {
274                                         if( errCB ) {
275                                                 errCB( jqXHR, textStatus, err );
276                                         }
277                                         else {
278                                                 printLoadError( cFPath, jqXHR );
279                                         }
280                                 }
281
282                                 if( ! cFPath ) {        // Invalid cFPath -> Regard it as '404 Not Found' error.
283                                         mockJSXHR = {
284                                                 status: 404,
285                                                 statusText: "Not Found"
286                                         };
287                                         _errCB( mockJSXHR, null, null );
288                                 } else {
289                                         $.ajax( {
290                                                 url: cFPath,
291                                                 dataType: 'script',
292                                                 cache: true,
293                                                 async: false,
294                                                 success: _successCB,
295                                                 error: _errCB
296                                         } );
297                                 }
298                         }
299
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 );
309                                         } else {
310                                                 printLoadError( cFPath, jqXHR );
311                                         }
312                                 } );
313
314                         return lang;
315                 },
316                 setGlobalize: function ( ) {
317                         var lang = this.loadGlobalizeCulture( );
318
319                         // Set culture
320                         // NOTE: It is not needed to set with neutral lang.
321                         //       Globalize automatically deals with it.
322                         Globalize.culture( lang );
323                 },
324                 /**
325                  * Load custom globalize culture file
326                  * Find current system language, and load appropriate culture file from given colture file list.
327                  *
328                  * @param[in]   cultureDic      collection of 'language':'culture file path' key-val pair.
329                  * @example
330                  * var myCultures = {
331                  *              "en"    : "culture/en.js",
332                  *              "fr"    : "culture/fr.js",
333                  *              "ko-KR" : "culture/ko-KR.js"
334                  * };
335                  * loadCultomGlobalizeCulture( myCultures );
336                  *
337                  * ex) culture/fr.js
338                  * -------------------------------
339                  * Globalize.addCultureInfo( "fr", {
340                  *   messages: {
341                  *     "hello" : "bonjour",
342                  *     "translate" : "traduire"
343                  *   }
344                  * } );
345                  * -------------------------------
346                  */
347                 loadCustomGlobalizeCulture: function ( cultureDic ) {
348                         tizen.loadGlobalizeCulture( null, cultureDic );
349                 },
350
351                 /** Set viewport meta tag for mobile devices.
352                  *
353                  * @param[in]   viewportWidth   viewport width. "device-width" is OK.
354                  */
355                 setViewport: function ( viewportWidth ) {
356                         var meta = null,
357                                 head,
358                                 content;
359
360                         // Do nothing if viewport setting code is already in the code.
361                         $( "meta[name=viewport]" ).each( function ( ) {
362                                 meta = this;
363                                 return;
364                         });
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" )
369                         } else {
370                                 // Create a meta tag
371                                 meta = document.createElement( "meta" );
372                                 if ( 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( "" );
379                                         }
380                                         meta.content = content;
381                                         console.log( content );
382                                         head = document.getElementsByTagName( 'head' ).item( 0 );
383                                         head.insertBefore( meta, head.firstChild );
384                                 }
385                         }
386                         return viewportWidth;
387                 },
388
389                 /**     Read body's font-size, scale it, and reset it.
390                  *  param[in]   desired font-size / base font-size.
391                  */
392                 scaleBaseFontSize: function ( themeDefaultFontSize, ratio ) {
393                         console.log( "themedefaultfont size: " + themeDefaultFontSize + ", ratio: " + ratio );
394                         var scaledFontSize = Math.round( themeDefaultFontSize * ratio );
395
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" } );
400                         } );
401                 },
402
403                 setScaling: function ( ) {
404                         var viewportWidth = this.frameworkData.viewportWidth,
405                                 themeDefaultFontSize = this.frameworkData.defaultFontSize, // comes from theme.js
406                                 ratio = 1;
407
408                         // Keep original font size
409                         $( 'body' ).attr( 'data-tizen-theme-default-font-size', themeDefaultFontSize );
410
411                         // Legacy support: tizen.frameworkData.viewportScale
412                         if ( this.frameworkData.viewportScale == true ) {
413                                 viewportWidth = "screen-width";
414                         }
415
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,
420                         }
421
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 );
425                         }
426                         this.scaleBaseFontSize( themeDefaultFontSize, ratio );
427                 }
428         };
429
430         function export2TizenNS ( $, tizen ) {
431                 if ( undefined == typeof $.tizen ) {
432                         $.tizen = { };
433                 }
434
435                 $.tizen.frameworkData = tizen.frameworkData;
436                 $.tizen.loadCustomGlobalizeCulture = tizen.loadCustomGlobalizeCulture;
437                 $.tizen.loadTheme = tizen.loadTheme;
438
439                 $.tizen.__tizen__ = tizen;      // for unit-test
440         }
441
442         export2TizenNS( $, tizen );
443
444         tizen.getParams( );
445         tizen.loadTheme( );
446         tizen.setScaling( );    // Run after loadTheme(), for the default font size.
447         tizen.setGlobalize( );
448
449         // Turn off JQM's auto initialization option.
450         // NOTE: This job must be done before domready.
451         $.mobile.autoInitializePage = false;
452
453         $(document).ready( function ( ) {
454                 $.mobile.initializePage( );
455         });
456
457 } ( jQuery, window.Globalize, window ) );