Export 0.2.3
[platform/framework/web/web-ui-fw.git] / src / loader / loader.js
index 2bdffc3..96cc6aa 100644 (file)
@@ -1,21 +1,56 @@
 /**
- * loader.js : Loader for web-ui-fw
- * Refactored from bootstrap.js
+ * @class core
+ * loader.js
+ *
+ * Youmin Ha <youmin.ha@samsung.com>
  *
- * By Youmin Ha <youmin.ha@samsung.com>
  *
  */
+/*
+       Web UI scaling concept in Tizen Web UI
+
+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.
+
+
+* Viewport on mobile web browser
+
+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.
+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.
+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.
+Detailed usage of viewport meta tag is found in here: http://www.w3.org/TR/mwabp/#bp-viewport
+
+
+* Viewport setting by application developers
+
+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.
+
+
+* Automatic viewport setting by Tizen Web UI Framework
+
+If developers do not give a viewport meta tag, Tizen Web UI Framework automatically add a viewport meta tag with default viewport setting.
 
+
+* Portrait/landscape mode
+
+
+* Tizen Web UI widgets scaling
+
+
+ */
 ( function ($, Globalize, window, undefined) {
 
-       window.S = {
+        var tizen = {
                libFileName : "tizen-web-ui-fw(.min)?.js",
 
                frameworkData : {
                        rootDir: '/usr/lib/tizen-web-ui-fw',
                        version: '0.1',
-                       theme: "default",
+                       theme: "tizen-white",
+                       viewportWidth: "device-width",
                        viewportScale: false,
+
+                       defaultFontSize: 22,
+                       minified: false
                },
 
                util : {
@@ -24,6 +59,7 @@
                                        url: scriptPath,
                                        dataType: 'script',
                                        async: false,
+                                       crossDomain: false,
                                        success: successCB,
                                        error: function ( jqXHR, textStatus, errorThrown ) {
                                                if ( errorCB ) {
                                        }
                                } );
                        },
-                       getScaleFactor: function ( ) {
-                               var factor = window.scale,
-                                       width = 0,
-                                       defaultWidth = 720;
-
-                               if ( !factor ) {
-                                       width = screen.width < screen.height ? screen.width : screen.height;
-                                       factor = width / defaultWidth;
-                                       if ( factor > 1 ) {
-                                               // NOTE: some targets(e.g iPad) need to set scale equal or less than 1.0
-                                               factor = 1;
-                                       }
-                               }
-                               console.log( "ScaleFactor: " + factor );
-                               return factor;
-                       },
                        isMobileBrowser: function ( ) {
                                var mobileIdx = window.navigator.appVersion.indexOf("Mobile"),
                                        isMobile = -1 < mobileIdx;
                                        '',
                        addElementToHead : function ( elem ) {
                                var head = document.getElementsByTagName( 'head' )[0];
-                               head.appendChild( elem );
-                       },
-                       load: function ( path ) {
-                               this.addElementToHead( this.makeLink( path + this.cacheBust ) );
+                               if( head ) {
+                                       $( head ).prepend( elem );
+                               }
                        },
                        makeLink : function ( href ) {
-                               var customstylesheetLink = document.createElement( 'link' );
-                               customstylesheetLink.setAttribute( 'rel', 'stylesheet' );
-                               customstylesheetLink.setAttribute( 'href', href );
-                               return customstylesheetLink;
+                               var cssLink = document.createElement( 'link' );
+                               cssLink.setAttribute( 'rel', 'stylesheet' );
+                               cssLink.setAttribute( 'href', href );
+                               cssLink.setAttribute( 'name', 'tizen-theme' );
+                               return cssLink;
+                       },
+                       load: function ( path ) {
+                               var head = document.getElementsByTagName( 'head' )[0],
+                                       cssLinks = head.getElementsByTagName( 'link' ),
+                                       idx,
+                                       l = null;
+                               // Find css link element
+                               for ( idx = 0; idx < cssLinks.length; idx++ ) {
+                                       if( cssLinks[idx].getAttribute( 'name' ) == "tizen-theme" ) {
+                                               l = cssLinks[idx];
+                                               break;
+                                       }
+                               }
+                               if ( l ) {      // Found the link element!
+                                       l.setAttribute( 'href', path );
+                               } else {
+                                       this.addElementToHead( this.makeLink( path ) );
+                               }
                        }
                },
 
                getParams: function ( ) {
-                       /* Get data-* params from <script> tag, and set S.frameworkData.* values
+                       /* Get data-* params from <script> tag, and set tizen.frameworkData.* values
                         * Returns true if proper <script> tag is found, or false if not.
                         */
                        // Find current <script> tag element
                                src,
                                tokens,
                                version_idx;
+
+                       function getTizenTheme( ) {
+                               var t = navigator.theme ? navigator.theme.split( ':' )[0] : null;
+                               if ( t ) {
+                                       t = t.replace('-hd', '');
+                                       if( ! t.match( /^tizen-/ ) ) {
+                                               t = 'tizen-' + t;
+                                       }
+                               }
+                               return t;
+                       }
+
                        for ( idx in scriptElems ) {
                                elem = scriptElems[idx];
                                src = elem.src ? elem.getAttribute( 'src' ) : undefined;
                                        // Set framework data, only when they are given.
                                        tokens = src.split(/[\/\\]/);
                                        version_idx = -3;
-                                       this.frameworkData.rootDir = elem.getAttribute( 'data-framework-root' )
+                                       this.frameworkData.rootDir = elem.getAttribute( 'data-framework-root' )
                                                || tokens.slice( 0, tokens.length + version_idx ).join( '/' )
-                                               || this.frameworkData.rootDir;
+                                               || this.frameworkData.rootDir ).replace( /^file:(\/\/)?/, '' );
                                        this.frameworkData.version = elem.getAttribute( 'data-framework-version' )
                                                || tokens[ tokens.length + version_idx ]
                                                || this.frameworkData.version;
                                        this.frameworkData.theme = elem.getAttribute( 'data-framework-theme' )
+                                               || getTizenTheme( )
                                                || this.frameworkData.theme;
-                                       this.frameworkData.viewportScale = "true" === elem.getAttribute( 'data-framework-viewport-scale' ) ? true : this.frameworkData.viewportScale;
+                                       this.frameworkData.viewportWidth = elem.getAttribute( 'data-framework-viewport-width' )
+                                               || this.frameworkData.viewportWidth;
+                                       this.frameworkData.viewportScale =
+                                               "true" === elem.getAttribute( 'data-framework-viewport-scale' ) ? true 
+                                               : this.frameworkData.viewportScale;
+                                       this.frameworkData.minified = src.search(/\.min\.js$/) > -1 ? true : false;
                                        foundScriptTag = true;
                                        break;
                                }
                        return foundScriptTag;
                },
 
-               loadTheme: function ( ) {
-                       var themePath = [
-                                       this.frameworkData.rootDir,
-                                       this.frameworkData.version,
+               loadTheme: function ( theme ) {
+                       var themePath, cssPath, jsPath;
+
+                       if ( ! theme ) {
+                               theme = tizen.frameworkData.theme;
+                       }
+                       themePath = [
+                                       tizen.frameworkData.rootDir,
+                                       tizen.frameworkData.version,
                                        'themes',
-                                       this.frameworkData.theme
+                                       theme
                                ].join( '/' ),
-                               cssPath = [themePath, 'tizen-web-ui-fw-theme.css'].join( '/' ),
-                               jsPath = [themePath, 'theme.js'].join( '/' );
 
-                       this.css.load( cssPath );
-                       this.util.loadScriptSync( jsPath );
+                       jsPath = [themePath, 'theme.js'].join( '/' );
+
+                       if( tizen.frameworkData.minified ) {
+                               cssPath = [themePath, 'tizen-web-ui-fw-theme.min.css'].join( '/' );
+                       } else {
+                               cssPath = [themePath, 'tizen-web-ui-fw-theme.css'].join( '/' );
+                       }
+                       tizen.css.load( cssPath );
+                       tizen.util.loadScriptSync( jsPath );
                },
 
                /** Load Globalize culture file, and set default culture.
-                *  @param[in]  language  Language code. ex) en-US, en, ko-KR, ko
-                *                        If language is not given, read language from html 'lang' attribute, or from system setting.
+                *  @param[in]  language  (optional) Language code. ex) en-US, en, ko-KR, ko
+                *                        If language is not given, read language from html 'lang' attribute, 
+                *                        or from system setting.
+                *  @param[in]  cultureDic (optional) Dictionary having language code->
                 */
-               loadGlobalizeCulture: function ( language ) {
-                       function getGlobalizeCultureFile( lang ) {
-                               return ['globalize.culture.', lang, '.js'].join( '' );
+               loadGlobalizeCulture: function ( language, cultureDic ) {
+                       var self = this,
+                               cFPath,
+                               lang,
+                               mockJSXHR;
+
+                       function getLang ( language ) {
+                               var lang = language
+                                               || $( 'html' ).attr( 'lang' )
+                                               || window.navigator.language.split( '.' )[0]    // Webkit, Safari + workaround for Tizen
+                                               || window.navigator.userLanguage        // IE
+                                               || 'en',
+                                       countryCode = null,
+                                       countryCodeIdx = lang.lastIndexOf('-'),
+                                       ignoreCodes = ['Cyrl', 'Latn', 'Mong']; // Not country code!
+                               if ( countryCodeIdx != -1 ) {   // Found country code!
+                                       countryCode = lang.substr( countryCodeIdx + 1 );
+                                       if ( ignoreCodes.join( '-' ).indexOf( countryCode ) < 0 ) {
+                                               // countryCode is not found from ignoreCodes.
+                                               // Make countryCode to uppercase.
+                                               lang = [ lang.substr( 0, countryCodeIdx ), countryCode.toUpperCase( ) ].join( '-' );
+                                       }
+                               }
+                               // NOTE: 'en' to 'en-US', because globalize has no 'en' culture file.
+                               lang = lang == 'en' ? 'en-US' : lang;
+                               return lang;
                        }
-                       function getGlobalizeCulturePath( self, file ) {
-                               return [
-                                       self.frameworkData.rootDir,
-                                       self.frameworkData.version,
-                                       'js',
-                                       'cultures',
-                                       file,
-                               ].join( '/' );
+
+                       function getNeutralLang ( lang ) {
+                               var neutralLangIdx = lang.lastIndexOf( '-' ),
+                                       neutralLang;
+                               if ( neutralLangIdx != -1 ) {
+                                       neutralLang = lang.substr( 0, neutralLangIdx );
+                               }
+                               return neutralLang;
                        }
 
-                       // Get lang, and change country code to uppercase chars.
-                       var self = this,
-                               lang = language
-                                       || $( 'html' ).attr( 'lang' )
-                                       || window.navigator.language.split( '.' )[0]    /* Webkit, Safari + workaround for Tizen */
-                                       || window.navigator.userLanguage        /* IE */
-                                       || 'en',
-                               countryCode = null,
-                               countryCodeIdx = lang.lastIndexOf('-'),
-                               ignoreCodes = ['Cyrl', 'Latn', 'Mong'], // Not country code!
-                               globalizeCultureFile,
-                               globalizeCulturePath,
-                               neutralLangIndex;
-
-                       if ( countryCodeIdx != -1 ) {   // Found country code!
-                               countryCode = lang.substr( countryCodeIdx + 1 );
-                               if ( ignoreCodes.join( '-' ).indexOf( countryCode ) < 0 ) { // countryCode is not found from ignoreCodes
-                                       // Make countryCode to uppercase
-                                       lang = [ lang.substr( 0, countryCodeIdx ), countryCode.toUpperCase( ) ].join( '-' );
+                       function getCultureFilePath ( lang, cFDic ) {
+                               var cFPath = null;      // error value
+
+                               if ( "string" != typeof lang ) {
+                                       return null;
+                               }
+                               if ( cFDic ) {
+                                       if ( cFDic[lang] ) cFPath = cFDic[lang];
+                               } else {
+                                       // Default Globalize culture file path
+                                       cFPath = [
+                                               self.frameworkData.rootDir,
+                                               self.frameworkData.version,
+                                               'js',
+                                               'cultures',
+                                               ['globalize.culture.', lang, '.js'].join( '' ),
+                                       ].join( '/' );
                                }
+                               return cFPath;
                        }
 
-                       globalizeCultureFile = getGlobalizeCultureFile( lang );
-                       globalizeCulturePath = getGlobalizeCulturePath( self, globalizeCultureFile );
-                       neutralLangIndex = lang.lastIndexOf( '-' );
-
-                       // Run culture script
-                       console.log( 'Run globalize culture: ' + globalizeCulturePath );
-                       this.util.loadScriptSync(
-                               globalizeCulturePath,
-                               null,
-                               function ( jqXHR, textStatus, errorThrown ) {   // Failed to load!
-                                       if ( jqXHR.status == 404 ) {
-                                               // If culture file is not found, run neutral language culture. 
-                                               // (e.g. en-US --> en)
-                                               if ( neutralLangIndex != -1 ) {
-                                                       var neutralLang = lang.substr( 0, neutralLangIndex ),
-                                                               neutralCultureFile = getGlobalizeCultureFile( neutralLang ),
-                                                               neutralCulturePath = getGlobalizeCulturePath( self, neutralCultureFile );
-                                                       console.log( 'Run globalize culture of neutral lang: ' + neutralCulturePath );
-                                                       self.util.loadScriptSync( neutralCulturePath );
-                                               }
-                                       } else {
-                                               window.alert( 'Error while loading ' + globalizeCulturePath + '\n' + jqXHR.status + ':' + jqXHR.statusText );
+                       function printLoadError( cFPath, jqXHR ) {
+                               console.log( "Error " + jqXHR.status + ": " + jqXHR.statusText );
+                               console.log( "::Culture file (" + cFPath + ") is failed to load.");
+                       }
+
+                       function loadCultureFile ( cFPath, errCB ) {
+                               function _successCB ( ) {
+                                       console.log( "Culture file (" + cFPath + ") is loaded successfully.");
+                               }
+                               function _errCB ( jqXHR, textStatus, err ) {
+                                       if( errCB ) {
+                                               errCB( jqXHR, textStatus, err );
+                                       }
+                                       else {
+                                               printLoadError( cFPath, jqXHR );
                                        }
                                }
-                       );
+
+                               if( ! cFPath ) {        // Invalid cFPath -> Regard it as '404 Not Found' error.
+                                       mockJSXHR = {
+                                               status: 404,
+                                               statusText: "Not Found"
+                                       };
+                                       _errCB( mockJSXHR, null, null );
+                               } else {
+                                       $.ajax( {
+                                               url: cFPath,
+                                               dataType: 'script',
+                                               cache: true,
+                                               async: false,
+                                               success: _successCB,
+                                               error: _errCB
+                                       } );
+                               }
+                       }
+
+                       lang = getLang( language );
+                       cFPath = getCultureFilePath( lang, cultureDic );
+                       loadCultureFile( cFPath,
+                               function ( jqXHR, textStatus, err ) {
+                                       if( jqXHR.status == 404 ) {
+                                               // If culture file is not found, try once more with neutral lang.
+                                               var nLang = getNeutralLang( lang ),
+                                                       cFPath = getCultureFilePath( nLang, cultureDic );
+                                               loadCultureFile( cFPath, null );
+                                       } else {
+                                               printLoadError( cFPath, jqXHR );
+                                       }
+                               } );
+
                        return lang;
                },
                setGlobalize: function ( ) {
                        var lang = this.loadGlobalizeCulture( );
 
                        // Set culture
-                       // NOTE: It is not needed to set with neutral lang. 
+                       // NOTE: It is not needed to set with neutral lang.
                        //       Globalize automatically deals with it.
                        Globalize.culture( lang );
                },
+               /**
+                * Load custom globalize culture file
+                * Find current system language, and load appropriate culture file from given colture file list.
+                *
+                * @param[in]   cultureDic      collection of 'language':'culture file path' key-val pair.
+                * @example
+                * var myCultures = {
+                *              "en"    : "culture/en.js",
+                *              "fr"    : "culture/fr.js",
+                *              "ko-KR" : "culture/ko-KR.js"
+                * };
+                * loadCultomGlobalizeCulture( myCultures );
+                *
+                * ex) culture/fr.js
+                * -------------------------------
+                * Globalize.addCultureInfo( "fr", {
+                *   messages: {
+                *     "hello" : "bonjour",
+                *     "translate" : "traduire"
+                *   }
+                * } );
+                * -------------------------------
+                */
+               loadCustomGlobalizeCulture: function ( cultureDic ) {
+                       tizen.loadGlobalizeCulture( null, cultureDic );
+               },
 
                /** Set viewport meta tag for mobile devices.
                 *
-                * @param[in]   viewportWidth   Viewport width. 'device-dpi' is also allowed.
-                * @param[in]   useAutoScale    If true, calculate & use scale factor. otherwise, scale factor is 1.
-                * @param[in]   useDeviceDpi    If true, add 'target-densityDpi=device-dpi' to viewport meta content.
+                * @param[in]   viewportWidth   viewport width. "device-width" is OK.
                 */
-               setViewport: function ( viewportWidth, useAutoScale, useDeviceDpi ) {
-                       var meta,
-                               scale = 1,
-                               head;
+               setViewport: function ( viewportWidth ) {
+                       var meta = null,
+                               head,
+                               content;
+
                        // Do nothing if viewport setting code is already in the code.
-                       $( "meta" ).each( function ( ) {
-                               if ( $( this ).attr( "name" ) === "viewport" ) {
-                                       console.log( "User set viewport... framework viewport will not be applied." );
-                                       meta = this;
-                                       return;
-                               }
+                       $( "meta[name=viewport]" ).each( function ( ) {
+                               meta = this;
+                               return;
                        });
-
-                       // Set meta tag
-                       meta = document.createElement( "meta" );
-                       if ( meta ) {
-                               scale = useAutoScale ? this.util.getScaleFactor( ) : scale;
-                               meta.name = "viewport";
-                               meta.content = "width=" + viewportWidth + ", initial-scale=" + scale + ", maximum-scale=" + scale + ", user-scalable=0";
-                               if ( useDeviceDpi ) {
-                                       meta.content += ", target-densityDpi=device-dpi";
+                       if( meta ) {    // Found custom viewport!
+                               content = $( meta ).prop( "content" );
+                               console.log( "Viewport is already set. Framework skips viewport setting." );
+                               viewportWidth = content.replace( /.*width=(device-width|\d+)\s*,?.*$/gi, "$1" )
+                       } else {
+                               // Create a meta tag
+                               meta = document.createElement( "meta" );
+                               if ( meta ) {
+                                       meta.name = "viewport";
+                                       content = [ "width=", viewportWidth, ", user-scalable=no" ].join( "" );
+                                       if ( ! isNaN( viewportWidth ) ) {
+                                               // Fix scale to 1.0, if viewport width is set to fixed value.
+                                               // NOTE: Works wrong in Tizen browser!
+                                               //content = [ content, ", initial-scale=1.0, maximum-scale=1.0" ].join( "" );
+                                       }
+                                       meta.content = content;
+                                       console.log( content );
+                                       head = document.getElementsByTagName( 'head' ).item( 0 );
+                                       head.insertBefore( meta, head.firstChild );
                                }
-                               console.log( meta.content );
-                               head = document.getElementsByTagName( 'head' ).item( 0 );
-                               head.insertBefore( meta, head.firstChild );
                        }
+                       return viewportWidth;
                },
 
                /**     Read body's font-size, scale it, and reset it.
                 *  param[in]   desired font-size / base font-size.
                 */
                scaleBaseFontSize: function ( themeDefaultFontSize, ratio ) {
+                       console.log( "themedefaultfont size: " + themeDefaultFontSize + ", ratio: " + ratio );
                        var scaledFontSize = Math.round( themeDefaultFontSize * ratio );
-                       $( '.ui-mobile' ).css( { 'font-size': scaledFontSize + "px" } );
-                       $( '.ui-mobile').children( 'body' ).css( { 'font-size': scaledFontSize + "px" } );
+
+                       $( 'html.ui-mobile' ).css( { 'font-size': scaledFontSize + "px" } );
+                       console.log('html:font size is set to ' + scaledFontSize );
+                       $( document ).ready( function ( ) {
+                               $( '.ui-mobile').children( 'body' ).css( { 'font-size': scaledFontSize + "px" } );
+                       } );
                },
 
                setScaling: function ( ) {
-                       var baseWidth = 720,            // NOTE: need to be changed to get the value from theme.
-                               standardWidth = 360,
-                               themeDefaultFontSize = parseInt( $( 'body' ).css( 'font-size' ), 10 );
+                       var viewportWidth = this.frameworkData.viewportWidth,
+                               themeDefaultFontSize = this.frameworkData.defaultFontSize, // comes from theme.js
+                               ratio = 1;
+
+                       // Keep original font size
                        $( 'body' ).attr( 'data-tizen-theme-default-font-size', themeDefaultFontSize );
 
-                       if ( this.frameworkData.viewportScale ) {
-                               // Use viewport scaling with base font-size
-                               // NOTE: No font-size setting is needed.
-                               this.setViewport( baseWidth, true, true );
-                       } else {
-                               // Fixed viewport scale(=1.0) with scaled font size
-                               this.setViewport( "device-dpi", false, undefined );
-                               this.scaleBaseFontSize( themeDefaultFontSize, parseFloat( standardWidth / baseWidth ) );
+                       // Legacy support: tizen.frameworkData.viewportScale
+                       if ( this.frameworkData.viewportScale == true ) {
+                               viewportWidth = "screen-width";
                        }
+
+                       if ( "screen-width" == viewportWidth ) {
+                               viewportWidth = window.outerWidth;
+                               // TODO : Above code will be replaced by below codes. But screen.availWidth has a webkit bug at this moment.
+                               // viewportWidth = screen.availWidth,
+                       }
+
+                       viewportWidth = this.setViewport( viewportWidth );      // If custom viewport setting exists, get viewport width
+                       if ( ! isNaN( viewportWidth ) ) {       // fixed width!
+                               ratio = parseFloat( viewportWidth / this.frameworkData.defaultViewportWidth );
+                       }
+                       this.scaleBaseFontSize( themeDefaultFontSize, ratio );
                }
        };
-} ( jQuery, window.Globalize, window ) );
 
+       function export2TizenNS ( $, tizen ) {
+               if ( undefined == typeof $.tizen ) {
+                       $.tizen = { };
+               }
+
+               $.tizen.frameworkData = tizen.frameworkData;
+               $.tizen.loadCustomGlobalizeCulture = tizen.loadCustomGlobalizeCulture;
+               $.tizen.loadTheme = tizen.loadTheme;
+
+               $.tizen.__tizen__ = tizen;      // for unit-test
+       }
+
+       export2TizenNS( $, tizen );
 
-// Loader's job list
-( function ( S, $, undefined ) {
-       S.getParams( );
-       S.loadTheme( );
-       S.setGlobalize( );
+       tizen.getParams( );
+       tizen.loadTheme( );
+       tizen.setScaling( );    // Run after loadTheme(), for the default font size.
+       tizen.setGlobalize( );
 
        // Turn off JQM's auto initialization option.
        // NOTE: This job must be done before domready.
        $.mobile.autoInitializePage = false;
 
        $(document).ready( function ( ) {
-               S.setScaling( );
                $.mobile.initializePage( );
        });
-} ( window.S, jQuery ) );
+
+} ( jQuery, window.Globalize, window ) );