dc14f898b1f9cfd3528d12d5d2532f6b2ff03a56
[platform/framework/web/web-ui-fw.git] / src / js / jquery.mobile.tizen.loader.js
1 //>>excludeStart("jqmBuildExclude", pragmas.jqmBuildExclude);
2 //>>description: Loader doing theme loading, viewport setting, globalize loading, etc.
3 //>>label: Loader
4 //>>group: Tizen:Core
5
6 define( [ 'jquery.mobile.tizen.core' ], function ( ) {
7 //>>excludeEnd("jqmBuildExclude");
8
9 /**
10  * @class core
11  * loader.js
12  *
13  * Youmin Ha <youmin.ha@samsung.com>
14  *
15  *
16  */
17 /*
18         Web UI scaling concept in Tizen Web UI
19
20 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.
21
22
23 * Viewport on mobile web browser
24
25 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.
26 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.
27 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.
28 Detailed usage of viewport meta tag is found in here: http://www.w3.org/TR/mwabp/#bp-viewport
29
30
31 * Viewport setting by application developers
32
33 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.
34
35
36 * Automatic viewport setting by Tizen Web UI Framework
37
38 If developers do not give a viewport meta tag, Tizen Web UI Framework automatically add a viewport meta tag with default viewport setting.
39
40
41 * Portrait/landscape mode
42
43
44 * Tizen Web UI widgets scaling
45
46
47  */
48 ( function ($, Globalize, window, undefined) {
49
50         var tizen = {
51                 libFileName : "tizen-web-ui-fw(.min)?.js",
52
53                 frameworkData : {
54                         rootDir: '/usr/lib/tizen-web-ui-fw',
55                         version: '0.1',
56                         theme: "tizen-white",
57                         viewportWidth: "device-width",
58                         viewportScale: false,
59
60                         defaultFontSize: 22,
61                         minified: false,
62
63                         debug: false
64                 },
65
66                 log : {
67                         debug : function ( msg ) {
68                                 if ( tizen.frameworkData.debug ) {
69                                         console.log( msg );
70                                 }
71                         },
72                         warn : function ( msg ) {
73                                 console.warn( msg );
74                         },
75                         error : function ( msg ) {
76                                 console.error( msg );
77                         },
78                         alert : function ( msg ) {
79                                 window.alert( msg );
80                         }
81                 },
82
83                 util : {
84
85                         loadScriptSync : function ( scriptPath, successCB, errorCB ) {
86                                 $.ajax( {
87                                         url: scriptPath,
88                                         dataType: 'script',
89                                         async: false,
90                                         crossDomain: false,
91                                         success: successCB,
92                                         error: function ( jqXHR, textStatus, errorThrown ) {
93                                                 if ( errorCB ) {
94                                                         errorCB( jqXHR, textStatus, errorThrown );
95                                                 } else {
96                                                         var ignoreStatusList = [ 404 ],  // 404: not found
97                                                                 errmsg = ( 'Error while loading ' + scriptPath + '\n' + jqXHR.status + ':' + jqXHR.statusText );
98                                                         if ( -1 == $.inArray( jqXHR.status, ignoreStatusList ) ) {
99                                                                 tizen.log.alert( errmsg );
100                                                         } else {
101                                                                 tizen.log.warn( errmsg );
102                                                         }
103                                                 }
104                                         }
105                                 } );
106                         },
107                         isMobileBrowser: function ( ) {
108                                 var mobileIdx = window.navigator.appVersion.indexOf("Mobile"),
109                                         isMobile = -1 < mobileIdx;
110                                 return isMobile;
111                         }
112                 },
113
114                 css : {
115                         cacheBust: ( document.location.href.match( /debug=true/ ) ) ?
116                                         '?cacheBust=' + ( new Date( ) ).getTime( ) :
117                                         '',
118                         addElementToHead : function ( elem ) {
119                                 var head = document.getElementsByTagName( 'head' )[0];
120                                 if ( head ) {
121                                         $( head ).prepend( elem );
122                                 }
123                         },
124                         makeLink : function ( href ) {
125                                 var cssLink = document.createElement( 'link' );
126                                 cssLink.setAttribute( 'rel', 'stylesheet' );
127                                 cssLink.setAttribute( 'href', href );
128                                 cssLink.setAttribute( 'name', 'tizen-theme' );
129                                 return cssLink;
130                         },
131                         load: function ( path ) {
132                                 var head = document.getElementsByTagName( 'head' )[0],
133                                         cssLinks = head.getElementsByTagName( 'link' ),
134                                         idx,
135                                         l = null;
136                                 // Find css link element
137                                 for ( idx = 0; idx < cssLinks.length; idx++ ) {
138                                         if ( cssLinks[idx].getAttribute( 'rel' ) != "stylesheet" ) {
139                                                 continue;
140                                         }
141                                         if ( cssLinks[idx].getAttribute( 'name' ) == "tizen-theme"
142                                                         || cssLinks[idx].getAttribute( 'href' ) == path ) {
143                                                 l = cssLinks[idx];
144                                                 break;
145                                         }
146                                 }
147                                 if ( l ) {      // Found the link element!
148                                         if ( l.getAttribute( 'href' ) == path ) {
149                                                 tizen.log.warn( "Theme is already loaded. Skip theme loading in the framework." );
150                                         } else {
151                                                 l.setAttribute( 'href', path );
152                                         }
153                                 } else {
154                                         this.addElementToHead( this.makeLink( path ) );
155                                 }
156                         }
157                 },
158
159                 getParams: function ( ) {
160                         /* Get data-* params from <script> tag, and set tizen.frameworkData.* values
161                          * Returns true if proper <script> tag is found, or false if not.
162                          */
163                         // Find current <script> tag element
164                         var scriptElems = document.getElementsByTagName( 'script' ),
165                                 val = null,
166                                 foundScriptTag = false,
167                                 idx,
168                                 elem,
169                                 src,
170                                 tokens,
171                                 version_idx;
172
173                         function getTizenTheme( ) {
174                                 var t = navigator.theme ? navigator.theme.split( ':' )[0] : null;
175                                 if ( t ) {
176                                         t = t.replace('-hd', '');
177                                         if ( ! t.match( /^tizen-/ ) ) {
178                                                 t = 'tizen-' + t;
179                                         }
180                                 }
181                                 return t;
182                         }
183
184                         for ( idx in scriptElems ) {
185                                 elem = scriptElems[idx];
186                                 src = elem.src ? elem.getAttribute( 'src' ) : undefined;
187                                 if (src && src.match( this.libFileName )) {
188                                         // Set framework data, only when they are given.
189                                         tokens = src.split(/[\/\\]/);
190                                         version_idx = -3;
191                                         this.frameworkData.rootDir = ( elem.getAttribute( 'data-framework-root' )
192                                                 || tokens.slice( 0, tokens.length + version_idx ).join( '/' )
193                                                 || this.frameworkData.rootDir ).replace( /^file:(\/\/)?/, '' );
194                                         this.frameworkData.version = elem.getAttribute( 'data-framework-version' )
195                                                 || tokens[ tokens.length + version_idx ]
196                                                 || this.frameworkData.version;
197                                         this.frameworkData.theme = elem.getAttribute( 'data-framework-theme' )
198                                                 || getTizenTheme( )
199                                                 || this.frameworkData.theme;
200                                         this.frameworkData.viewportWidth = elem.getAttribute( 'data-framework-viewport-width' )
201                                                 || this.frameworkData.viewportWidth;
202                                         this.frameworkData.viewportScale =
203                                                 "true" === elem.getAttribute( 'data-framework-viewport-scale' ) ? true
204                                                 : this.frameworkData.viewportScale;
205                                         this.frameworkData.minified = src.search(/\.min\.js$/) > -1 ? true : false;
206                                         this.frameworkData.debug = "true" === elem.getAttribute( 'data-framework-debug' ) ? true
207                                                 : this.frameworkData.debug;
208                                         foundScriptTag = true;
209                                         break;
210                                 }
211                         }
212                         return foundScriptTag;
213                 },
214
215                 loadTheme: function ( theme ) {
216                         var themePath,
217                         cssPath,
218                         jsPath;
219
220                         if ( ! theme ) {
221                                 theme = tizen.frameworkData.theme;
222                         }
223                         
224                         themePath = tizen.frameworkData.rootDir + '/' + tizen.frameworkData.version + '/themes/' + theme;
225                         
226                         jsPath = themePath + '/theme.js';
227         
228                         if ( tizen.frameworkData.minified ) {
229                                 cssPath = themePath + '/tizen-web-ui-fw-theme.min.css';
230                         } else {
231                                 cssPath = themePath + '/tizen-web-ui-fw-theme.css';
232                         }
233                         tizen.css.load( cssPath );
234                         tizen.util.loadScriptSync( jsPath );
235                 },
236
237                 /** Load Globalize culture file, and set default culture.
238                  *  @param[in]  language  (optional) Language code. ex) en-US, en, ko-KR, ko
239                  *                        If language is not given, read language from html 'lang' attribute, 
240                  *                        or from system setting.
241                  *  @param[in]  cultureDic (optional) Dictionary having language code->
242                  */
243                 loadGlobalizeCulture: function ( language, cultureDic ) {
244                         var self = this,
245                                 cFPath,
246                                 lang,
247                                 mockJSXHR;
248
249                         function getLang ( language ) {
250                                 var lang = language
251                                                 || $( 'html' ).attr( 'lang' )
252                                                 || window.navigator.language.split( '.' )[0]    // Webkit, Safari + workaround for Tizen
253                                                 || window.navigator.userLanguage        // IE
254                                                 || 'en',
255                                         countryCode = null,
256                                         countryCodeIdx = lang.lastIndexOf('-'),
257                                         ignoreCodes = ['Cyrl', 'Latn', 'Mong']; // Not country code!
258                                 if ( countryCodeIdx != -1 ) {   // Found country code!
259                                         countryCode = lang.substr( countryCodeIdx + 1 );
260                                         if ( ignoreCodes.join( '-' ).indexOf( countryCode ) < 0 ) {
261                                                 // countryCode is not found from ignoreCodes.
262                                                 // Make countryCode to uppercase.
263                                                 lang = [ lang.substr( 0, countryCodeIdx ), countryCode.toUpperCase( ) ].join( '-' );
264                                         }
265                                 }
266                                 // NOTE: 'en' to 'en-US', because globalize has no 'en' culture file.
267                                 lang = lang == 'en' ? 'en-US' : lang;
268                                 return lang;
269                         }
270
271                         function getNeutralLang ( lang ) {
272                                 var neutralLangIdx = lang.lastIndexOf( '-' ),
273                                         neutralLang;
274                                 if ( neutralLangIdx != -1 ) {
275                                         neutralLang = lang.substr( 0, neutralLangIdx );
276                                 }
277                                 return neutralLang;
278                         }
279
280                         function getCultureFilePath ( lang, cFDic ) {
281                                 var cFPath = null;      // error value
282
283                                 if ( "string" != typeof lang ) {
284                                         return null;
285                                 }
286                                 if ( cFDic && cFDic[lang] ) {
287                                         cFPath = cFDic[lang];
288                                 } else {
289                                         // Default Globalize culture file path
290                                         cFPath = [
291                                                 self.frameworkData.rootDir,
292                                                 self.frameworkData.version,
293                                                 'js',
294                                                 'cultures',
295                                                 ['globalize.culture.', lang, '.js'].join( '' )
296                                         ].join( '/' );
297                                 }
298                                 return cFPath;
299                         }
300
301                         function printLoadError( cFPath, jqXHR ) {
302                                 tizen.log.error( "Error " + jqXHR.status + ": " + jqXHR.statusText
303                                                 + "::Culture file (" + cFPath + ") is failed to load.");
304                         }
305
306                         function loadCultureFile ( cFPath, errCB ) {
307                                 function _successCB ( ) {
308                                         tizen.log.debug( "Culture file (" + cFPath + ") is loaded successfully." );
309                                 }
310                                 function _errCB ( jqXHR, textStatus, err ) {
311                                         if ( errCB ) {
312                                                 errCB( jqXHR, textStatus, err );
313                                         } else {
314                                                 printLoadError( cFPath, jqXHR );
315                                         }
316                                 }
317
318                                 if ( ! cFPath ) {       // Invalid cFPath -> Regard it as '404 Not Found' error.
319                                         mockJSXHR = {
320                                                 status: 404,
321                                                 statusText: "Not Found"
322                                         };
323                                         _errCB( mockJSXHR, null, null );
324                                 } else {
325                                         $.ajax( {
326                                                 url: cFPath,
327                                                 dataType: 'script',
328                                                 cache: true,
329                                                 async: false,
330                                                 success: _successCB,
331                                                 error: _errCB
332                                         } );
333                                 }
334                         }
335
336                         lang = getLang( language );
337                         cFPath = getCultureFilePath( lang, cultureDic );
338                         loadCultureFile( cFPath,
339                                 function ( jqXHR, textStatus, err ) {
340                                         if ( jqXHR.status == 404 ) {
341                                                 // If culture file is not found, try once more with neutral lang.
342                                                 var nLang = getNeutralLang( lang ),
343                                                         ncFPath = getCultureFilePath( nLang, cultureDic );
344                                                 loadCultureFile( ncFPath, null );
345                                         } else {
346                                                 printLoadError( cFPath, jqXHR );
347                                         }
348                                 } );
349
350                         return lang;
351                 },
352                 setGlobalize: function ( ) {
353                         var lang = this.loadGlobalizeCulture( );
354
355                         // Set culture
356                         // NOTE: It is not needed to set with neutral lang.
357                         //       Globalize automatically deals with it.
358                         Globalize.culture( lang );
359                 },
360                 /**
361                  * Load custom globalize culture file
362                  * Find current system language, and load appropriate culture file from given colture file list.
363                  *
364                  * @param[in]   cultureDic      collection of 'language':'culture file path' key-val pair.
365                  * @example
366                  * var myCultures = {
367                  *      "en"    : "culture/en.js",
368                  *      "fr"    : "culture/fr.js",
369                  *      "ko-KR" : "culture/ko-KR.js"
370                  * };
371                  * loadCultomGlobalizeCulture( myCultures );
372                  *
373                  * ex) culture/fr.js
374                  * -------------------------------
375                  * Globalize.addCultureInfo( "fr", {
376                  *   messages: {
377                  *     "hello" : "bonjour",
378                  *     "translate" : "traduire"
379                  *   }
380                  * } );
381                  * -------------------------------
382                  */
383                 loadCustomGlobalizeCulture: function ( cultureDic ) {
384                         tizen.loadGlobalizeCulture( null, cultureDic );
385                 },
386
387                 /** Set viewport meta tag for mobile devices.
388                  *
389                  * @param[in]   viewportWidth   viewport width. "device-width" is OK.
390                  */
391                 setViewport: function ( viewportWidth ) {
392                         var meta = null,
393                                 head,
394                                 content;
395
396                         // Do nothing if viewport setting code is already in the code.
397                         $( "meta[name=viewport]" ).each( function ( ) {
398                                 meta = this;
399                                 return;
400                         });
401                         if ( meta ) {   // Found custom viewport!
402                                 content = $( meta ).prop( "content" );
403                                 viewportWidth = content.replace( /.*width=(device-width|\d+)\s*,?.*$/gi, "$1" );
404                                 tizen.log.warn( "Viewport is set to '" + viewportWidth + "' in a meta tag. Framework skips viewport setting." );
405                         } else {
406                                 // Create a meta tag
407                                 meta = document.createElement( "meta" );
408                                 if ( meta ) {
409                                         meta.name = "viewport";
410                                         content = "width=" + viewportWidth + ", user-scalable=no";
411                                         if ( ! isNaN( viewportWidth ) ) {
412                                                 // Fix scale to 1.0, if viewport width is set to fixed value.
413                                                 // NOTE: Works wrong in Tizen browser!
414                                                 //content = [ content, ", initial-scale=1.0, maximum-scale=1.0" ].join( "" );
415                                         }
416                                         meta.content = content;
417                                         tizen.log.debug( content );
418                                         head = document.getElementsByTagName( 'head' ).item( 0 );
419                                         head.insertBefore( meta, head.firstChild );
420                                 }
421                         }
422                         return viewportWidth;
423                 },
424
425                 /**     Read body's font-size, scale it, and reset it.
426                  *  param[in]   desired font-size / base font-size.
427                  */
428                 scaleBaseFontSize: function ( themeDefaultFontSize, ratio ) {
429                         tizen.log.debug( "themedefaultfont size: " + themeDefaultFontSize + ", ratio: " + ratio );
430                         var scaledFontSize = Math.max( Math.floor( themeDefaultFontSize * ratio ), 4 );
431
432                         $( 'html' ).css( { 'font-size': scaledFontSize + "px" } );
433                         tizen.log.debug( 'html:font size is set to ' + scaledFontSize );
434                         $( document ).ready( function ( ) {
435                                 $( '.ui-mobile' ).children( 'body' ).css( { 'font-size': scaledFontSize + "px" } );
436                         } );
437                 },
438
439                 setScaling: function ( ) {
440                         var viewportWidth = this.frameworkData.viewportWidth,
441                                 themeDefaultFontSize = this.frameworkData.defaultFontSize, // comes from theme.js
442                                 ratio = 1;
443
444                         // Keep original font size
445                         $( 'body' ).attr( 'data-tizen-theme-default-font-size', themeDefaultFontSize );
446
447                         if ( !tizen.util.isMobileBrowser() ) {
448                                 return;
449                         }
450
451                         // Legacy support: tizen.frameworkData.viewportScale
452                         if ( this.frameworkData.viewportScale == true ) {
453                                 viewportWidth = "screen-width";
454                         }
455
456                         // screen-width support
457                         if ( "screen-width" == viewportWidth ) {
458                                 if ( window.self == window.top ) {
459                                         // Top frame: for target. Use window.outerWidth.
460                                         viewportWidth = window.outerWidth;
461                                 } else {
462                                         // iframe: for web simulator. Use clientWidth.
463                                         viewportWidth = document.documentElement.clientWidth;
464                                 }
465                         }
466
467                         // set viewport meta tag
468                         viewportWidth = this.setViewport( viewportWidth );      // If custom viewport setting exists, get viewport width
469
470                         if ( viewportWidth == "device-width" ) {
471                                 // Do nothing!
472                         } else {        // fixed width!
473                                 ratio = parseFloat( viewportWidth / this.frameworkData.defaultViewportWidth );
474                                 this.scaleBaseFontSize( themeDefaultFontSize, ratio );
475                         }
476                 }
477         };
478
479         function export2TizenNS ( $, tizen ) {
480                 if ( !$.tizen ) {
481                         $.tizen = { };
482                 }
483
484                 $.tizen.frameworkData = tizen.frameworkData;
485                 $.tizen.loadCustomGlobalizeCulture = tizen.loadCustomGlobalizeCulture;
486                 $.tizen.loadTheme = tizen.loadTheme;
487
488                 $.tizen.__tizen__ = tizen;      // for unit-test
489         }
490
491         export2TizenNS( $, tizen );
492
493         tizen.getParams( );
494         tizen.loadTheme( );
495         tizen.setScaling( );    // Run after loadTheme(), for the default font size.
496         tizen.setGlobalize( );
497         // Turn off JQM's auto initialization option.
498         // NOTE: This job must be done before domready.
499         $.mobile.autoInitializePage = false;
500
501         $(document).ready( function ( ) {
502                 $.mobile.initializePage( );
503         });
504
505 } ( jQuery, window.Globalize, window ) );
506
507 //>>excludeStart("jqmBuildExclude", pragmas.jqmBuildExclude);
508 } );
509 //>>excludeEnd("jqmBuildExclude");