build: Module build implementation
[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                         themePath = [
224                                 tizen.frameworkData.rootDir,
225                                 tizen.frameworkData.version,
226                                 'themes',
227                                 theme
228                         ].join( '/' );
229
230                         jsPath = [ themePath, 'theme.js' ].join( '/' );
231
232                         if ( tizen.frameworkData.minified ) {
233                                 cssPath = [themePath, 'tizen-web-ui-fw-theme.min.css'].join( '/' );
234                         } else {
235                                 cssPath = [themePath, 'tizen-web-ui-fw-theme.css'].join( '/' );
236                         }
237                         tizen.css.load( cssPath );
238                         tizen.util.loadScriptSync( jsPath );
239                 },
240
241                 /** Load Globalize culture file, and set default culture.
242                  *  @param[in]  language  (optional) Language code. ex) en-US, en, ko-KR, ko
243                  *                        If language is not given, read language from html 'lang' attribute, 
244                  *                        or from system setting.
245                  *  @param[in]  cultureDic (optional) Dictionary having language code->
246                  */
247                 loadGlobalizeCulture: function ( language, cultureDic ) {
248                         var self = this,
249                                 cFPath,
250                                 lang,
251                                 mockJSXHR;
252
253                         function getLang ( language ) {
254                                 var lang = language
255                                                 || $( 'html' ).attr( 'lang' )
256                                                 || window.navigator.language.split( '.' )[0]    // Webkit, Safari + workaround for Tizen
257                                                 || window.navigator.userLanguage        // IE
258                                                 || 'en',
259                                         countryCode = null,
260                                         countryCodeIdx = lang.lastIndexOf('-'),
261                                         ignoreCodes = ['Cyrl', 'Latn', 'Mong']; // Not country code!
262                                 if ( countryCodeIdx != -1 ) {   // Found country code!
263                                         countryCode = lang.substr( countryCodeIdx + 1 );
264                                         if ( ignoreCodes.join( '-' ).indexOf( countryCode ) < 0 ) {
265                                                 // countryCode is not found from ignoreCodes.
266                                                 // Make countryCode to uppercase.
267                                                 lang = [ lang.substr( 0, countryCodeIdx ), countryCode.toUpperCase( ) ].join( '-' );
268                                         }
269                                 }
270                                 // NOTE: 'en' to 'en-US', because globalize has no 'en' culture file.
271                                 lang = lang == 'en' ? 'en-US' : lang;
272                                 return lang;
273                         }
274
275                         function getNeutralLang ( lang ) {
276                                 var neutralLangIdx = lang.lastIndexOf( '-' ),
277                                         neutralLang;
278                                 if ( neutralLangIdx != -1 ) {
279                                         neutralLang = lang.substr( 0, neutralLangIdx );
280                                 }
281                                 return neutralLang;
282                         }
283
284                         function getCultureFilePath ( lang, cFDic ) {
285                                 var cFPath = null;      // error value
286
287                                 if ( "string" != typeof lang ) {
288                                         return null;
289                                 }
290                                 if ( cFDic && cFDic[lang] ) {
291                                         cFPath = cFDic[lang];
292                                 } else {
293                                         // Default Globalize culture file path
294                                         cFPath = [
295                                                 self.frameworkData.rootDir,
296                                                 self.frameworkData.version,
297                                                 'js',
298                                                 'cultures',
299                                                 ['globalize.culture.', lang, '.js'].join( '' ),
300                                         ].join( '/' );
301                                 }
302                                 return cFPath;
303                         }
304
305                         function printLoadError( cFPath, jqXHR ) {
306                                 tizen.log.error( "Error " + jqXHR.status + ": " + jqXHR.statusText
307                                                 + "::Culture file (" + cFPath + ") is failed to load.");
308                         }
309
310                         function loadCultureFile ( cFPath, errCB ) {
311                                 function _successCB ( ) {
312                                         tizen.log.debug( "Culture file (" + cFPath + ") is loaded successfully." );
313                                 }
314                                 function _errCB ( jqXHR, textStatus, err ) {
315                                         if ( errCB ) {
316                                                 errCB( jqXHR, textStatus, err );
317                                         } else {
318                                                 printLoadError( cFPath, jqXHR );
319                                         }
320                                 }
321
322                                 if ( ! cFPath ) {       // Invalid cFPath -> Regard it as '404 Not Found' error.
323                                         mockJSXHR = {
324                                                 status: 404,
325                                                 statusText: "Not Found"
326                                         };
327                                         _errCB( mockJSXHR, null, null );
328                                 } else {
329                                         $.ajax( {
330                                                 url: cFPath,
331                                                 dataType: 'script',
332                                                 cache: true,
333                                                 async: false,
334                                                 success: _successCB,
335                                                 error: _errCB
336                                         } );
337                                 }
338                         }
339
340                         lang = getLang( language );
341                         cFPath = getCultureFilePath( lang, cultureDic );
342                         loadCultureFile( cFPath,
343                                 function ( jqXHR, textStatus, err ) {
344                                         if ( jqXHR.status == 404 ) {
345                                                 // If culture file is not found, try once more with neutral lang.
346                                                 var nLang = getNeutralLang( lang ),
347                                                         ncFPath = getCultureFilePath( nLang, cultureDic );
348                                                 loadCultureFile( ncFPath, null );
349                                         } else {
350                                                 printLoadError( cFPath, jqXHR );
351                                         }
352                                 } );
353
354                         return lang;
355                 },
356                 setGlobalize: function ( ) {
357                         var lang = this.loadGlobalizeCulture( );
358
359                         // Set culture
360                         // NOTE: It is not needed to set with neutral lang.
361                         //       Globalize automatically deals with it.
362                         Globalize.culture( lang );
363                 },
364                 /**
365                  * Load custom globalize culture file
366                  * Find current system language, and load appropriate culture file from given colture file list.
367                  *
368                  * @param[in]   cultureDic      collection of 'language':'culture file path' key-val pair.
369                  * @example
370                  * var myCultures = {
371                  *      "en"    : "culture/en.js",
372                  *      "fr"    : "culture/fr.js",
373                  *      "ko-KR" : "culture/ko-KR.js"
374                  * };
375                  * loadCultomGlobalizeCulture( myCultures );
376                  *
377                  * ex) culture/fr.js
378                  * -------------------------------
379                  * Globalize.addCultureInfo( "fr", {
380                  *   messages: {
381                  *     "hello" : "bonjour",
382                  *     "translate" : "traduire"
383                  *   }
384                  * } );
385                  * -------------------------------
386                  */
387                 loadCustomGlobalizeCulture: function ( cultureDic ) {
388                         tizen.loadGlobalizeCulture( null, cultureDic );
389                 },
390
391                 /** Set viewport meta tag for mobile devices.
392                  *
393                  * @param[in]   viewportWidth   viewport width. "device-width" is OK.
394                  */
395                 setViewport: function ( viewportWidth ) {
396                         var meta = null,
397                                 head,
398                                 content;
399
400                         // Do nothing if viewport setting code is already in the code.
401                         $( "meta[name=viewport]" ).each( function ( ) {
402                                 meta = this;
403                                 return;
404                         });
405                         if ( meta ) {   // Found custom viewport!
406                                 content = $( meta ).prop( "content" );
407                                 viewportWidth = content.replace( /.*width=(device-width|\d+)\s*,?.*$/gi, "$1" );
408                                 tizen.log.warn( "Viewport is set to '" + viewportWidth + "' in a meta tag. Framework skips viewport setting." );
409                         } else {
410                                 // Create a meta tag
411                                 meta = document.createElement( "meta" );
412                                 if ( meta ) {
413                                         meta.name = "viewport";
414                                         content = [ "width=", viewportWidth, ", user-scalable=no" ].join( "" );
415                                         if ( ! isNaN( viewportWidth ) ) {
416                                                 // Fix scale to 1.0, if viewport width is set to fixed value.
417                                                 // NOTE: Works wrong in Tizen browser!
418                                                 //content = [ content, ", initial-scale=1.0, maximum-scale=1.0" ].join( "" );
419                                         }
420                                         meta.content = content;
421                                         tizen.log.debug( content );
422                                         head = document.getElementsByTagName( 'head' ).item( 0 );
423                                         head.insertBefore( meta, head.firstChild );
424                                 }
425                         }
426                         return viewportWidth;
427                 },
428
429                 /**     Read body's font-size, scale it, and reset it.
430                  *  param[in]   desired font-size / base font-size.
431                  */
432                 scaleBaseFontSize: function ( themeDefaultFontSize, ratio ) {
433                         tizen.log.debug( "themedefaultfont size: " + themeDefaultFontSize + ", ratio: " + ratio );
434                         var scaledFontSize = Math.round( themeDefaultFontSize * ratio );
435
436                         $( 'html.ui-mobile' ).css( { 'font-size': scaledFontSize + "px" } );
437                         tizen.log.debug( 'html:font size is set to ' + scaledFontSize );
438                         $( document ).ready( function ( ) {
439                                 $( '.ui-mobile' ).children( 'body' ).css( { 'font-size': scaledFontSize + "px" } );
440                         } );
441                 },
442
443                 setScaling: function ( ) {
444                         var viewportWidth = this.frameworkData.viewportWidth,
445                                 themeDefaultFontSize = this.frameworkData.defaultFontSize, // comes from theme.js
446                                 ratio = 1;
447
448                         // Keep original font size
449                         $( 'body' ).attr( 'data-tizen-theme-default-font-size', themeDefaultFontSize );
450
451                         // Legacy support: tizen.frameworkData.viewportScale
452                         if ( this.frameworkData.viewportScale == true ) {
453                                 viewportWidth = "screen-width";
454                         }
455
456                         if ( "screen-width" == viewportWidth ) {
457                                 viewportWidth = document.documentElement.clientWidth;
458                         }
459
460                         viewportWidth = this.setViewport( viewportWidth );      // If custom viewport setting exists, get viewport width
461                         if ( ! isNaN( viewportWidth ) ) {       // fixed width!
462                                 ratio = parseFloat( viewportWidth / this.frameworkData.defaultViewportWidth );
463                         }
464                         this.scaleBaseFontSize( themeDefaultFontSize, ratio );
465                 }
466         };
467
468         function export2TizenNS ( $, tizen ) {
469                 if ( !$.tizen ) {
470                         $.tizen = { };
471                 }
472
473                 $.tizen.frameworkData = tizen.frameworkData;
474                 $.tizen.loadCustomGlobalizeCulture = tizen.loadCustomGlobalizeCulture;
475                 $.tizen.loadTheme = tizen.loadTheme;
476
477                 $.tizen.__tizen__ = tizen;      // for unit-test
478         }
479
480         export2TizenNS( $, tizen );
481
482         tizen.getParams( );
483         tizen.loadTheme( );
484         tizen.setScaling( );    // Run after loadTheme(), for the default font size.
485         tizen.setGlobalize( );
486
487         // Turn off JQM's auto initialization option.
488         // NOTE: This job must be done before domready.
489         $.mobile.autoInitializePage = false;
490
491         $(document).ready( function ( ) {
492                 $.mobile.initializePage( );
493         });
494
495 } ( jQuery, window.Globalize, window ) );
496
497 //>>excludeStart("jqmBuildExclude", pragmas.jqmBuildExclude);
498 } );
499 //>>excludeEnd("jqmBuildExclude");