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