1. #1
    Sencha User bluehipy's Avatar
    Join Date
    Mar 2010
    Location
    Romania
    Posts
    612
    Vote Rating
    28
    bluehipy will become famous soon enough bluehipy will become famous soon enough

      0  

    Default ST2 + SDK Tools 2.0.0.beta3, localisation / i18n approach w/o bundle and overrites

    ST2 + SDK Tools 2.0.0.beta3, localisation / i18n approach w/o bundle and overrites


    I have desperately needed localisation in the project I was working on. And the requirement for the i18n came n at a later time, so I didn't have the chance to think about it from the beginning.
    First, because I was fooled about the development microloader, I have obtained an "interuption" inside application launch method, where I was listening for my singleton Locale to notify about its completeness. After that I have added the first view to the Ext.Viewport and everything was going on normal.
    I was using the Locale properties directly in my views similar to this:
    Code:
    Ext.define('MyApp.view.Dashboard',
    {
      extend:'Ext.Container',
      xtype:'appdashboard',
      config:{
         title:Locale.DASHBOARD_TITLE
      }
    );

    and life was beautiful until I have tried to make an production build.
    As soon as I tried that, I realized that all the js definitions are parsed instantly in the compact mode and my Locale will not have the time to form and even if would 've have the time, at the moment when I was referring it, in launch method, the views would 've been loaded and parsed already.


    So I have quit this, my first beautiful and naive idea.
    the next try was with the use of overriding the apply method. in this case, in views I had something like this:


    Code:
    Ext.define('MyApp.view.Dashboard',
    {
      extend:'Ext.Container',
      xtype:'appdashboard',
      config:{
         title:'DASHBOARD_TITLE',
         items:[
                  {
                          xtype:'button',
                         text:'LABEL_LOGOUT',
                         applyText:function(key)
                         {
                               return Locale[key] || key;
                         }
                  }
          ]
      },
      applyTitle:function(key)
      {
          return Locale[key] || key; //if not defined, will output the key so I can notice it's missing
      }
    );

    Not very nice. A lot of extra code to add to many views. Also some properties that look like strings are not really just strings at the apply point ( check the title of the navigation panel and other similar) and or you can't just do what have wrote upper, you have to take care about the context.


    And though it was working in devel mode, yes you guessed right, it still wasn't working in production but partially. I didn't understand why, but for some reasons, again, in production build beside the extra complications of writing all those applies, wasn't working. I know that normally the apply override should have been done in a define section, but still, it was working in dev! ...a nd didn't work on production, after compact+ minify + etc.


    Then I've said to myself that this problem is quite simple conceptual and all I need is to get a little further of the build process and get my Locale created even before the Ext or application files to be loaded.


    Loading and initialize Locale before Ext exists is not necesarily hard but makes things ugly, especially if I was going to use ajax and stores anyway. Also I wanted to keep the devel / production tandem.
    So finally what I have done was to hook in microloaders and modify them :D




    I made use of the following, more or less, "hacks":


    1) The debug tag.
    Everything between //<debug> ...//</debug> is interpreted in devel mode but not parsed in production.
    This means one can implement different behaviours for the two contexts.
    Code:
    isDebug = false;
    //<debug>
    isDebug = true;
    //</debug>
    if(isDebug)
    {
       //start application in the dev mode
    }else{
     //start the application in the production mode
    }



    2) During the production build, all the parameters inside the app.json, are transported to the /build/production/app.json


    That helps because one can set extra parameters to be used in the "hacked" microloaders:
    Code:
     "js": [
            {
                "path": "sdk/sencha-touch-all.js",
                "priority":1,
                "sync":true,
                "callback":false
            },
            {
                "path": "app.js",
                "update": "delta",
                "preloader":{
                    "path":"locale.js",
                    "priority":2,
                    "sync":true,
                    "callback":true
                }
            }
        ],



    3) Determine if current session is dev / production or the special case when sdk tool determines dependencies.
    Code:
    if(!window.location.href.match(/^http:/))
    {
        //during sdk tools build operation will pass here ALSO
    }

    also, because that code will be executed in different other cases so you have to take care of that.




    4) Use of different app.json copies for dev / production by modifying microloaders.


    So, finally, my files are:
    app-dev.json
    Code:
    {
        /**
         * The application's namespace, used by Sencha Command to generate classes
         */
        "name": "MyApp",
    
    
        /**
         * List of all JavaScript assets in the right execution order.
         * Each item is an object with the following format:
         *      {
         *          "path": "path/to/script.js" // Relative path to this app.json file
         *          "update": "delta"           // (Optional)
         *                                      //  - If not specified, this file will only be loaded once, and
         *                                      //    cached inside localStorage until this value is changed.
         *                                      //  - "delta" to enable over-the-air delta update for this file
         *                                      //  - "full" means full update will be made when this file changes
         *
         *      }
         */
        "js": [
            {
                "path": "sdk/sencha-touch-all.js"
            },
            {
                 "path":"locale.js" /*add this before the app.js*/
    
    
            },
            {
                "path": "app.js",
                "update": "delta",
            }
        ],
    
    
        /**
         * List of all CSS assets in the right inclusion order.
         * Each item is an object with the following format:
         *      {
         *          "path": "path/to/item.css" // Relative path to this app.json file
         *          "update": "delta"          // (Optional)
         *                                     //  - If not specified, this file will only be loaded once, and
         *                                     //    cached inside localStorage until this value is changed to either one below
         *                                     //  - "delta" to enable over-the-air delta update for this file
         *                                     //  - "full" means full update will be made when this file changes
         *
         *      }
         */
        "css": [
            {
                "path": "resources/css/app.css",
                "update": "delta"
            }
        ],
    
    
        /**
         * Used to automatically generate cache.manifest (HTML 5 application cache manifest) file when you build
         */
        "appCache": {
            /**
             * List of items in the CACHE MANIFEST section
             */
            "cache": [
                "index.html"
            ],
            /**
             * List of items in the NETWORK section
             */
            "network": [
                "*"
            ],
            /**
             * List of items in the FALLBACK section
             */
            "fallback": []
        },
    
    
        /**
         * Extra resources to be copied along when build
         */
        "extras": [
            "resources/images",
            "resources/icons",
            "resources/loading",
            "resources/css/fonts",
            "resources/locale/en.json"
        ],
    
    
        /**
         * Directory path to store all previous production builds. Note that the content generated inside this directory
         * must be kept intact for proper generation of delta between updates
         */
        "archivePath": "archive",
    
    
        /**
         * Default paths to build this application to for each environment
         */
        "buildPaths": {
            "testing": "build/testing",
            "production": "build/production",
            "package": "build/package",
            "native": "build/native"
        },
    
    
        /**
         * Build options
         */
        "buildOptions": {
            "product": "touch",
            "minVersion": 3,
            "debug": false,
            "logger": "false"
        },
    
    
        /**
         * Uniquely generated id for this application, used as prefix for localStorage keys.
         * Normally you should never change this value.
         */
        "id": "75a277d0-6836-11e1-aa0a-374b25a899d0"
    }





    app.json
    Code:
    {
        /**
         * The application's namespace, used by Sencha Command to generate classes
         */
        "name": "MyApp",
    
    
        /**
         * List of all JavaScript assets in the right execution order.
         * Each item is an object with the following format:
         *      {
         *          "path": "path/to/script.js" // Relative path to this app.json file
         *          "update": "delta"           // (Optional)
         *                                      //  - If not specified, this file will only be loaded once, and
         *                                      //    cached inside localStorage until this value is changed.
         *                                      //  - "delta" to enable over-the-air delta update for this file
         *                                      //  - "full" means full update will be made when this file changes
         *
         *      }
         */
        "js": [
            {
                "path": "sdk/sencha-touch-all.js",
                "priority":1,
                "sync":true,
                "callback":false
            },
            {
                "path": "app.js",
                "update": "delta",
                "preloader":{
                    "path":"locale.js",
                    "priority":2,
                    "sync":true,
                    "callback":true
                }
            }
        ],
    
    
        /**
         * List of all CSS assets in the right inclusion order.
         * Each item is an object with the following format:
         *      {
         *          "path": "path/to/item.css" // Relative path to this app.json file
         *          "update": "delta"          // (Optional)
         *                                     //  - If not specified, this file will only be loaded once, and
         *                                     //    cached inside localStorage until this value is changed to either one below
         *                                     //  - "delta" to enable over-the-air delta update for this file
         *                                     //  - "full" means full update will be made when this file changes
         *
         *      }
         */
        "css": [
            {
                "path": "resources/css/app.css",
                "update": "delta"
            }
        ],
    
    
        /**
         * Used to automatically generate cache.manifest (HTML 5 application cache manifest) file when you build
         */
        "appCache": {
            /**
             * List of items in the CACHE MANIFEST section
             */
            "cache": [
                "index.html"
            ],
            /**
             * List of items in the NETWORK section
             */
            "network": [
                "*"
            ],
            /**
             * List of items in the FALLBACK section
             */
            "fallback": []
        },
    
    
        /**
         * Extra resources to be copied along when build
         */
        "extras": [
            "resources/images",
            "resources/icons",
            "resources/loading",
            "resources/css/fonts",
            "resources/locale/en.json",
            "locale.js" /*add this so the locale.js get copied during build*/
        ],
    
    
        /**
         * Directory path to store all previous production builds. Note that the content generated inside this directory
         * must be kept intact for proper generation of delta between updates
         */
        "archivePath": "archive",
    
    
        /**
         * Default paths to build this application to for each environment
         */
        "buildPaths": {
            "testing": "build/testing",
            "production": "build/production",
            "package": "build/package",
            "native": "build/native"
        },
    
    
        /**
         * Build options
         */
        "buildOptions": {
            "product": "touch",
            "minVersion": 3,
            "debug": false,
            "logger": "false"
        },
    
    
        /**
         * Uniquely generated id for this application, used as prefix for localStorage keys.
         * Normally you should never change this value.
         */
        "id": "75a277d0-6836-11e1-aa0a-374b25a899d0"
    }

    locale.js
    Code:
    //global locale reference
    //we keep it simple
    Locale={};
    window.Locale = Locale;
    if(window.isDebug) //window.isDebug set in microloader
    {
        Ext.Loader.setPath({
        'Ext': 'sdk/src'
        });
    }
    
    
    console.log('debug:',isDebug);
    function onDictionaryReady()
    {
        console.log('Dictionary definition ready...');
        Dictionary.init();
    }
    
    
    var dictionary_cfg = {
        singleton:true,
        constructor:function(config)
        {
            this.initConfig(config);
            return this;
        },
        init:function()
        {
            //reference to the localstorage key => value list
            //where we store current locale key
            this.localSettings = Ext.create('Ext.data.Store',
                                            {
                                                fields:['key','value'],
                                                proxy:{
                                                    type:'localstorage',
                                                    id:'settings'
                                                },
                                                autoLoad:false
                                            });
            this.localSettings.on('load', this.onSettingsLoaded, this, {single:true});
            this.localSettings.load();
        },
        loadStorageLocale:function(locale)
        {
            console.log('loading dictionary from storage',locale);
            this.store = Ext.create('Ext.data.Store',
                                            {
                                                fields:['key','value'],
                                                proxy:{
                                                    type:'localstorage',
                                                    id:'locale-' + locale
                                                },
                                                autoLoad:false
                                            });
            this.store.on('load', this.onDictionaryLoaded, this, {single:true});
            this.store.load();
        },
        loadExternalLocale:function(locale)
        {
            console.log('loading dictionary from json',locale);
            this.store = Ext.create('Ext.data.Store',
                                            {
                                                fields:['key','value'],
                                                proxy:{
                                                    type:'ajax',
                                                    url:'resources/locale/'+locale+'.json', //this can be whatever else you need
                                                                                            //not just local uri
                                                    reader:{
                                                        type:'json',
                                                        rootProperty:'locale'   //I m loading a json that looks like:
                                                                                // {
                                                                                //      "locale":[
                                                                                //                  {"key":"MY_LABEL","value":"My label english string"},
                                                                                //                  {"key":"MY_TEXT","value":"My english text"}
                                                                                //              ]
                                                                                //}
                                                    }
                                                },
                                                autoLoad:false
                                            });
            this.store.on('load', this.onDictionaryLoaded, this, {single:true});
            //this.store.on('load', this.onExternalDictionaryLoaded, this, {single:true});//we wnat the update the local store also
            this.store.load();
        },
        notifyDone:function()
        {
            // console.log('notify!!');
            // var evt = new CustomEvent("AssetLoaded");
            // window.dispatchEvent(evt);
            // I had to quit CustomEvent\// too much trouble on various platforms
    
    
            //check if anybody defined a hanlde for the loaded asset, if yes call it
            if(window.notifyAssetLoaded)
                window.notifyLocaleLoaded();
        },
        //handles
        onSettingsLoaded:function()
        {
            var rec = this.localSettings.findRecord('key','locale');
            if(rec)
            {
                //we should have a locale loaded in local storage
                this.locale = rec.get('value');//en,ro
                this.loadStorageLocale(this.locale);
            }else{
                //we have no locale in local storage we load default one from outside
                this.locale = 'en';
                this.loadExternalLocale(this.locale);
            }
        },
        onDictionaryLoaded:function()
        {
            var rec,key,value,i=0,ln=this.store.getCount();
            for(i=0;i<ln;i++)
            {
                rec     = this.store.getAt(i),
                key     = rec.get('key'),
                value   = rec.get('value');
                Locale[ key ] = value;
                console.log(key,value);
            }
            
            //TODO: add code to update local storage with the loaded locale
    
    
            //now we can laod the application that uses Locale
            console.log('Locale done, continue...');
    
    
            //notify that locale thing is done
            this.notifyDone();
        },
        onLoad:function(){},
        onError:function(){}
    };
    
    
    if(!isDebug)
    {
    Ext.setup(
                {
                  onReady:function()
                  {
                    console.log('Ext setup ready...');
                    Ext.Viewport.on('ready',function()
                        {
                            console.log('Ext Viewport ready...');
                            Ext.define('Dictionary',dictionary_cfg,onDictionaryReady);
                        });
                  }
                }
              );
    }else{
                console.log('Defining Dictionary on debug', isDebug);
                  Ext.define('Dictionary',dictionary_cfg,onDictionaryReady);
    }





    app.js
    Code:
    //<debug>
    Ext.Loader.setPath({
        'Ext': 'sdk/src',
        'MyApp':'app'
    });
    
    
    // window.addEventListener('AssetLoaded', function()
    // {
    //     Ext.application(app_cfg);
    // });
    window.notifyLocaleLoaded = function()
    {
       Ext.application(app_cfg);
    };
    //</debug>
    
        var app_cfg = {
            name: 'MyApp',
            requires: [
            'Ext.MessageBox',
            'Ext.data.Store',
            'Ext.navigation.View'
            ],
            controllers:[
            'Cookie',
            'Locale',
            'Business' //etc
            ],
            views: [/*views here*/]],
            models:[/*models here*/],
            stores:[],
            profiles:['Phone'],
    
    
        launch: function() {
            // Destroy the #appLoadingIndicator element
            Ext.fly('appLoadingIndicator').destroy();
            //...
    
    
    
    
        }    
    };
    
    
    var isDebug=false;
    //<debug>
    isDebug = true;
    //</debug>
    
    
    if(!isDebug )
    {
      //we should be in production mode
      new Ext.app.Application(app_cfg);
    }
    <debug>
    //try to catch the sdk tool session
    if(!window.location.href.match(/^http:/))
    {
        Ext.application(app_cfg);
    }
    </debug>

    sdk/microloader/development.js
    Code:
    /*
    
    
    This file is part of Sencha Touch 2
    
    
    Copyright (c) 2012 Sencha Inc
    
    
    Contact:  http://www.sencha.com/contact
    
    
    Commercial Usage
    Licensees holding valid commercial licenses may use this file in accordance with the Commercial Software License Agreement provided with the Software or, alternatively, in accordance with the terms contained in a written agreement between you and Sencha.
    
    
    If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
    
    
    */
    /**
     * Sencha Blink - Development
     * @author Jacky Nguyen <jacky@sencha.com>
     */
    (function() {
        window.isDebug = true;
        function write(content) {
            document.write(content);
        }
    
    
        function meta(name, content) {
            write('<meta name="' + name + '" content="' + content + '">');
        }
    
    
        var xhr = new XMLHttpRequest();
        xhr.open('GET', 'app-dev.json', false); //here I had to set this to be able to use different config json on dev
        xhr.send(null);
    
    
        var options = eval("(" + xhr.responseText + ")"),
            scripts = options.js || [],
            styleSheets = options.css || [],
            i, ln, path;
    
    
        meta('viewport', 'width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no');
        meta('apple-mobile-web-app-capable', 'yes');
        meta('apple-touch-fullscreen', 'yes');
    
    
        for (i = 0,ln = styleSheets.length; i < ln; i++) {
            path = styleSheets[i];
    
    
            if (typeof path != 'string') {
                path = path.path;
            }
    
    
            write('<link rel="stylesheet" href="'+path+'">');
        }
    
    
        for (i = 0,ln = scripts.length; i < ln; i++) {
            path = scripts[i];
    
    
            if (typeof path != 'string') {
                path = path.path;
            }
    
    
            write('<script src="'+path+'"></'+'script>');
        }
    
    
    })();



    In production I had to alter a lot of code to implement the sync loading of sencha touch framework and prerequisits.
    The logic is:
    - every node in app.json that has a *sync* property will be loaded sunchrone.
    - sync nodes are ordered by their *priority* property
    - every node can have a *preloader* property where other needed resource can be loaded syncrone
    - on loading a *sync* asset if node *callback* property is true then the script will wait until the loaded resource signal that it is ready, other wise on load the script goes to the next asset in line


    Notes:
    - preloaded, sync resources will skip delta check <<< bad
    - on application cache update event the application onUpdate check is called no more though this can be fixed




    sdk/microloader/production.js
    Code:
    /*
    
    
    This file is part of Sencha Touch 2
    
    
    Copyright (c) 2012 Sencha Inc
    
    
    Contact:  http://www.sencha.com/contact
    
    
    Commercial Usage
    Licensees holding valid commercial licenses may use this file in accordance with the Commercial Software License Agreement provided with the Software or, alternatively, in accordance with the terms contained in a written agreement between you and Sencha.
    
    
    If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
    
    
    */
    /*
    ruined or addapted by me
    */
    (function(global)
      {
        global.isDebug=false;
        var emptyFn=function(){},
        callbacks=[],
        doc=global.document,
        head=doc.head,
        addWindowListener=global.addEventListener,
        storage=global.localStorage,
        appCache=global.applicationCache,
        jsonParse=JSON.parse,
        a=doc.createElement("a"),
        documentLocation=doc.location,
        documentUri=documentLocation.origin+documentLocation.pathname+documentLocation.search,
        manifestFile="app.json",
        isRefreshing=false,
        activeManifest;
        function getManifestStorageKey(id)
        {
          return id+"-"+documentUri+manifestFile
        }
        function Manifest(manifest)
        {
          var manifestContent;
          if(typeof manifest=="string")
          {
            manifestContent=manifest;
            manifest=jsonParse(manifestContent)
          }else{
            manifestContent=JSON.stringify(manifest)
          }
          var applicationId=manifest.id,
          key=getManifestStorageKey(applicationId),
          assetMap={};
          
          function processAsset(asset)
          {
            var uri;
            if(typeof asset=="string")
            {
              asset={path:asset}
            }
            if(asset.shared)
            {
              asset.version=asset.shared;
              uri=asset.shared+asset.path
            }else{
              uri=toAbsoluteUri(asset.path)
            }
            asset.uri=uri;
            asset.key=applicationId+"-"+uri;
            assetMap[uri]=asset;
            return asset
          }
    
    
          function processAssets(assets,type)
          {
            console.log('processAssets', assets);
            
            var ln=assets.length,i,k,asset;
            for(i=0;i<ln;i++)
            {
              asset=assets[i];
              assets[i]=asset=processAsset(asset);
              asset.type=type;
              asset.index=i;
              asset.collection=assets;
              asset.ready=false;
              asset.evaluated=false;
            }
            i=0;k=assets.length;
            while(i<assets.length)
            {
              var asset = assets[i];
              if(asset.preloader)
              {
                asset = asset.preloader;
                assets[k]= asset = processAsset(asset);
                k++;
                asset.type=type;
                asset.index=k-1;
                asset.collection=assets;
                asset.ready=false;
                asset.evaluated=false
              }
              i++;
            }
            return assets
          }
    
    
          this.key=key;
          this.css=processAssets(manifest.css,"css");
          this.js=processAssets(manifest.js,"js");
          this.assets=this.css.concat(this.js);
          
          this.getAsset=function(uri){
            return assetMap[uri]
          };
    
    
          this.store=function(){
            store(key,manifestContent)
          }
        }
    
    
        if(typeof Ext==="undefined")
        {
          var Ext=global.Ext={}
        }
    
    
        function toAbsoluteUri(uri)
        {
          a.href=uri;
          return a.href
        }
    
    
        function writeMeta(name,content)
        {
          doc.write('<meta name="'+name+'" content="'+content+'">')
        }
    
    
        function request(uri,isShared,onSuccess,onFailure)
        {
          (isShared?requestIframe:requestXhr)(uri,onSuccess,onFailure)
        }
    
    
        function requestXhr(uri,onSuccess,onFailure)
        {
          var xhr=new XMLHttpRequest(),status;
          onFailure=onFailure||emptyFn;
          try{
            xhr.open("GET",uri,true);
            xhr.onreadystatechange=function(){
              if(xhr.readyState==4)
              {
                status=xhr.status;
                if(status==200||status==304||status==0)
                {
                  onSuccess(xhr.responseText)
                }else{
                  onFailure()
                }
              }
            };
            xhr.send(null)
          }catch(e){
            onFailure()
          }
        }
    
    
        function requestIframe(uri,onSuccess)
        {
          var iframe=doc.createElement("iframe");
          callbacks.push({iframe:iframe,callback:onSuccess});
          iframe.src=uri+".html";
          iframe.style.cssText="width:0;height:0;border:0;position:absolute;z-index:-999;visibility:hidden";
          doc.body.appendChild(iframe)
        }
    
    
        function requestAsset(asset,onSuccess,onFailure)
        {
          var isShared=!!asset.shared;
          if(!isShared)
          {
            var onRequestSuccess=onSuccess,version=asset.version,remoteChecksumBlock;
            onSuccess=function(content)
            {
              if(version)
              {
                  remoteChecksumBlock=content.substring(0,version.length+4);
                  if(remoteChecksumBlock!=="/*"+version+"*/")
                  {
                    if(confirm("Requested: '"+asset.uri+"' with checksum: "+version+" but received: "+remoteChecksumBlock.substring(2,version.length)+"instead. Attemp to refresh the application?"))
                    {
                      refresh()
                    }
                    return
                  }
            }
              onRequestSuccess(content)
            }
          }
    
    
          request(asset.uri,isShared,onSuccess,onFailure)
        }
    
    
        function onMessage(e)
        {
          var data=e.data,
          sourceWindow=e.source.window,i,ln,callback,iframe;
          for(i=0,ln=callbacks.length;i<ln;i++)
          {
            callback=callbacks[i];
            iframe=callback.iframe;
            if(iframe.contentWindow===sourceWindow)
            {
              callback.callback(data);
              doc.body.removeChild(iframe);
              callbacks.splice(i,1);
              return
            }
          }
        }
    
    
        function patch(content,delta)
        {
          var output=[],chunk,i,ln;
          if(delta.length===0)
          {
            return content
          }
          for(i=0,ln=delta.length;i<ln;i++)
          {
            chunk=delta[i];
            if(typeof chunk==="number")
            {
              output.push(content.substring(chunk,chunk+delta[++i]))
            }else{
              output.push(chunk)
            }
          }
          return output.join("")
        }
    
    
        function log(message)
        {
          if(typeof console!="undefined")
          {
            (console.error||console.log).call(console,message)
          }
        }
    
    
        function store(key,value)
        {
          console.log('storing',key,value);
          try{
            storage.setItem(key,value)
          }catch(e){
            if(e.code==e.QUOTA_EXCEEDED_ERR)
            {
              var items=activeManifest.assets.map(
                function(asset)
                {
                  return asset.key
                }
                ),
              i=0,ln=storage.length,cleaned=false,item;
              items.push(activeManifest.key);
              while(i<=ln-1)
              {
                item=storage.key(i);
                if(items.indexOf(item)==-1)
                {
                  storage.removeItem(item);
                  cleaned=true;
                  ln--
                }else{
                  i++
                }
              }
    
    
              if(cleaned)
              {
                store(key,value)
              }
            }
          }
        }
    
    
        function retrieve(key)
        {
          console.log('retrieving',key);
          try{
            return storage.getItem(key)
          }catch(e){
            return null
          }
        }
    
    
        function retrieveAsset(asset)
        {
          return retrieve(asset.key)
        }
    
    
        function storeAsset(asset,content)
        {
          return store(asset.key,content)
        }
    
    
        function refresh()
        {
          console.log('refereshing');
          if(!isRefreshing)
          {
            isRefreshing=true;
            requestXhr(
              manifestFile,
              function(content)
              {
                new Manifest(content).store();
                global.location.reload()
              }
              )
          }
        }
    
    
        function blink(currentManifest)
        {
          console.log('blink');
          var currentAssets=currentManifest.assets,
          assetsCount=currentAssets.length,
          newManifest;
          activeManifest=currentManifest;
          addWindowListener("message",onMessage,false);
    
    
          //>>>
          var syncroneAssets,sycroneAssectsCount;
    
    
          function isSync(element, index, array)
          {
            console.log('is sync?',element,element.sync);
            return element.hasOwnProperty('sync') ? element.sync : false;
          }
    
    
          function byPriority(asset1, asset2)
          {
            console.log('byPriority',asset1,asset2);
            var p1=asset1.hasOwnProperty('priority')?asset1.priority:0,
            p2=asset2.hasOwnProperty('priority')?asset2.priority:0;
            return p2-p1;
          }
          function onAssetNotify()
          {
            console.log("onAssetNotify");
            syncInit();
          }
    
    
          function syncInit()
          {
            sycroneAssectsCount--;
            var asset = syncroneAssets[sycroneAssectsCount];
            log('syncInit',asset);
            if(asset)
            {
              var content= retrieveAsset(asset);
                if(content===null)
                {
                  requestAsset(
                    asset,
                    function(content)
                    {
                      console.log('asset content', content);
                      storeAsset(asset,content);
                      onSyncAssetReady(asset,content)
                    },
                    function(){
                      console.log('asset request fail', asset);
                      onSyncAssetReady(asset,"")
                    }
                    )
                }else{
                 console.log('content not null');
                  onSyncAssetReady(asset,content)
                }
            }else{
              console.log('starting the async part');
              asyncInit();
            }
    
    
          }
          //<<<<
    
    
          function asyncInit()
          {
            console.log('asyncInit');
            currentAssets.forEach(
              function(asset)
              {
                var content=retrieveAsset(asset);
                if(content===null)
                {
                  requestAsset(
                    asset,
                    function(content)
                    {
                       console.log('success request asset');
                      storeAsset(asset,content);
                      onAssetReady(asset,content)
                    },
                    function(){
                       console.log('fail request asset');
                      onAssetReady(asset,"")
                    }
                    )
                }else{
                  onAssetReady(asset,content)
                }
              }
              );
          }
    
    
          //>>>
          function onSyncAssetReady(asset, content)
          {
             console.log('onSyncAssetReady');
            asset.ready=true;
            asset.content=content;
            if(!asset.evaluated)
            {
              evaluateAsset(asset)
            }
            console.log('sync asset done', asset);
            if(!asset.callback)
            {
              console.log('no need to wait for event', asset);
              syncInit();
            }
          }
          //<<<<
    
    
          function onAssetReady(asset,content)
          {
            console.log('onAssetReady',asset);
            var assets=asset.collection,index=asset.index,ln=assets.length,i;
            asset.ready=true;
            asset.content=content;
            for(i=index-1;i>=0;i--)
            {
              asset=assets[i];
              if(!asset.ready||!asset.evaluated)
              {
                return
              }
            }
            for(i=index;i<ln;i++)
            {
              asset=assets[i];
              if(asset.ready)
              {
                if(!asset.evaluated)
                {
                  evaluateAsset(asset)
                }
              }else{return}
            }
          }
    
    
          function evaluateAsset(asset)
          {
            console.log('evaluating asset',asset);
            asset.evaluated=true;
            if(asset.type=="js")
            {
              try{
                eval(asset.content)
              }catch(e){
                log("Error evaluating "+asset.uri+" with message: "+e)
              }
            }else{
              var style=doc.createElement("style"),base;
              style.type="text/css";
              style.textContent=asset.content;
              if("id" in asset)
              {
                style.id=asset.id
              }
              if("disabled" in asset)
              {
                style.disabled=asset.disabled
              }
              base=document.createElement("base");
              base.href=asset.path.replace(/\/[^\/]*$/,"/");
              head.appendChild(base);
              head.appendChild(style);
              head.removeChild(base)
            }
            delete asset.content;
            if(--assetsCount==0)
            {
              onReady()
            }
          }
    
    
          function onReady()
          {
              console.log('onReady');
              var updatingAssets=[],
              appCacheReady=false,
              appCacheIdle=false,
              onAppCacheIdle=function()
              {
                 console.log('onAppCacheIdle');
                appCacheIdle=true
              },
              onAppCacheReady=function()
              {
                console.log('onAppCacheReady');
                appCache.swapCache();
                appCacheReady=true;
                onAppCacheIdle()
              },
              updatingCount;
              global.removeEventListener("message",onMessage,false);
              if(appCache.status==appCache.UPDATEREADY)
              {
                onAppCacheReady()
              }else{
                if(appCache.status==appCache.CHECKING||appCache.status==appCache.DOWNLOADING)
                {
                  appCache.onupdateready=onAppCacheReady;
                  appCache.onnoupdate=appCache.onobsolete=onAppCacheIdle
                }else{
                  onAppCacheIdle()
                }
              }
    
    
              function notifyUpdateIfAppCacheReady()
              {
                console.log('notifyUpdateIfAppCacheReady');
                if(appCacheReady)
                {
                  notifyUpdate()
                }
              }
    
    
              function notifyUpdate()
              {
                console.log('notifyUpdate');
                //>>>>
                var updatedCallback= function()
                {
                    if(confirm( "This application has just successfully been updated to the latest version. Reload now?"))
                    {
                       global.location.reload();
                    }
                }//Ext.onUpdated||emptyFn;
    
    
                //<<<
                if("onSetup" in Ext)
                {
                  Ext.onSetup(updatedCallback)
                }else{
                  updatedCallback()
                }
              }
    
    
              function doUpdate()
              {
                console.log('doUpdate');
                newManifest.store();
                updatingAssets.forEach(
                  function(asset)
                  {
                    console.log('updatingAssets',asset);
                    storeAsset(asset,asset.content)
                  }
                  );
                notifyUpdate()
              }
              function onAssetUpdated(asset,content)
              {
                console.log('onAssetUpdated',asset);
                asset.content=content;
                if(--updatingCount==0)
                {
                  if(appCacheIdle)
                  {
                    doUpdate()
                  }else{
                    onAppCacheIdle=doUpdate
                  }
                }
              }
    
    
              if(navigator.onLine!==false)
              {
                requestXhr(
                  manifestFile,
                  function(manifestContent)
                  {
                    console.log('requestXhr manifestFile',manifestContent);
                    activeManifest=newManifest=new Manifest(manifestContent);
                    var assets=newManifest.assets,currentAsset;
                    assets.forEach(
                      function(asset)
                      {
                         console.log('manifestFile assets ',asset);
                        currentAsset=currentManifest.getAsset(asset.uri);
                        if(!currentAsset||asset.version!==currentAsset.version)
                        {
                          updatingAssets.push(asset)
                        }
                      }
                    );
                    console.log('updatingCount  ',updatingAssets);
                    updatingCount=updatingAssets.length;
                    if(updatingCount==0)
                    {
                      if(appCacheIdle)
                      {
                        notifyUpdateIfAppCacheReady()
                      }else{
                        onAppCacheIdle=notifyUpdateIfAppCacheReady
                      }
                      return
                    }
                    updatingAssets.forEach(
                      function(asset)
                      {
                        var currentAsset=currentManifest.getAsset(asset.uri),
                        path=asset.path,
                        update=asset.update;
    
    
                        function updateFull()
                        {
                          requestAsset(asset,
                            function(content)
                            {
                              onAssetUpdated(asset,content)
                            }
                          )
                        }
                        if(!currentAsset||!update||retrieveAsset(asset)===null||update!="delta")
                        {
                          updateFull()
                        }else{
                          requestXhr(
                            "deltas/"+path+"/"+currentAsset.version+".json",
                            function(content)
                            {
                              try{
                                onAssetUpdated(asset,patch(retrieveAsset(asset),
                                    jsonParse(content)
                                  )
                                )
                              }catch(e){
                                log("Malformed delta content received for "+asset.uri)
                              }
                            },
                            updateFull)
                        }
                      }
                      )
                  }
                )
              }
          }
          
          console.log("currentAssets",currentAssets);
    
    
          if(assetsCount==0)
          {
            onReady();
            return
          }
    
    
          
          syncroneAssets = currentAssets.filter(isSync);
          
          
          console.log('syncroneAssets',syncroneAssets);
    
    
          // while(currentAssets.some(isSync))
          // {
          //  var i=0;ln=currentAssets.length;
          //  for(i=0;i<ln;i++)
          //  {
          //    var asset = currentAssets[i];
          //    if(asset.sync)
          //    {
          //      currentAssets.splice(i,1);
          //      break;
          //    }
          //  }
          // }
    
    
          assetsCount = currentAssets.length;
    
    
          if(syncroneAssets && syncroneAssets.length>0)
          {
            sycroneAssectsCount = syncroneAssets.length;
            syncroneAssets.sort(byPriority);
    //        addWindowListener('AssetLoaded', onAssetNotify, false);
            window.notifyLocaleLoaded = function(){
              onAssetNotify();          
            }
            syncInit();
    
    
          }else{
            asyncInit();
          }
    
    
        }
    
    
        function blinkOnDomReady(manifest)
        {
          if(doc.readyState.match(/interactive|complete|loaded/)!==null)
          {
            blink(manifest)
          }else{
            addWindowListener("DOMContentLoaded",
              function()
              {
                blink(manifest)
              },false)
          }
        }
    
    
        Ext.blink=function(manifest)
        {
          var manifestContent=retrieve(getManifestStorageKey(manifest.id));
          writeMeta("viewport","width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no");
          writeMeta("apple-mobile-web-app-capable","yes");
          writeMeta("apple-touch-fullscreen","yes");
          if(manifestContent)
          {
            manifest=new Manifest(manifestContent);
            blinkOnDomReady(manifest)
          }else{
            requestXhr(manifestFile,
              function(content)
              {
                manifest=new Manifest(content);
                manifest.store();
                blinkOnDomReady(manifest)
              }
              )
          }
        }
      }
    )(this);



    ...and I guess that's it, if anybody have the patience to read so far :))






    Hopefully it will help or inspire somebody to make it better :D If that will happen, please let me know!

  2. #2
    Sencha - Senior Forum Manager mitchellsimoens's Avatar
    Join Date
    Mar 2007
    Location
    Gainesville, FL
    Posts
    37,549
    Vote Rating
    873
    mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute

      0  

    Default


    I would have used a utility class and required it before Ext.application. That will have it created before your app classes are
    Mitchell Simoens @SenchaMitch
    Sencha Inc, Senior Forum Manager
    ________________
    Check out my GitHub, lots of nice things for Ext JS 4 and Sencha Touch 2
    https://github.com/mitchellsimoens

    Think my support is good? Get more personalized support via a support subscription. https://www.sencha.com/store/

    Need more help with your app? Hire Sencha Services services@sencha.com

    Want to learn Sencha Touch 2? Check out Sencha Touch in Action that is in print!

    When posting code, please use BBCode's CODE tags.

  3. #3
    Sencha User bluehipy's Avatar
    Join Date
    Mar 2010
    Location
    Romania
    Posts
    612
    Vote Rating
    28
    bluehipy will become famous soon enough bluehipy will become famous soon enough

      0  

    Default


    yes mitchellsimoens but still the Locale properties would not been there at the Ext.defined moment, so it won't work.

    I have seen your utility related to localisation, and I get inspired in doing things differently.

    I want to
    add locales { prop:'key' } in components config
    in component
    instantiate visit component and set the translated value for the prop, attash a listener on Locale change

    Tried so far to override Ext.Base setConfig but it doesn't look possible so far.
    I have been successful, so far, in overwriting Ext.ClassManager.instanciate to get a entry point to all components.


    I wonder if it is a good idea.

    Code:
        (function(window){
            function cLocale()
            {
                this.objects=[];
                this.locale = {    };
            }
    
    
            cLocale.prototype.get = function(key)
            {
                return this.locale[key] || key+' not defined';
            }
    
    
            cLocale.prototype.load = function(lang)
            {
                if(lang == 'en')
                {
                    this.locale = {
                        'DASHBOARD_TITLE'     : 'Dashboard',
                        'AUTH_TITLE'        :'Login',
                        'EN'                :'za en',
                        'FR'                :'za fr'
                    }
                }else{
                    this.locale = {
                        'DASHBOARD_TITLE'     : 'Le Dashboard',
                        'AUTH_TITLE'        :'Le Login',
                        'EN'                :'le en',
                        'FR'                :'le fr'
                    }
                }
                var self = this;
                //private method
                function pingObjects()
                {
                    for(var o in self.objects)
                    {
                        self.objects[o].onLocaleChange();
                    }
                }
                pingObjects();
            }
            
            cLocale.prototype.visit = function(obj)
            {
                console.log(obj);
                if( obj.locales || obj.getLocales)
                {
                    console.log('visit',obj);
                    var key,value,capitalizedName,locales = obj.locales || obj.getLocales();
                    for(key in locales)
                    {
                        capitalizedName = key.charAt(0).toUpperCase() + key.substr(1);
                        console.log(capitalizedName);
                        value = locales[key];
                        obj['set'+capitalizedName].call(obj,this.get(value));
                    }
                    if(this.objects.indexOf(obj)==-1)
                    {
                        var self = this;
                        obj.onLocaleChange = this.onChange;
                        this.objects.push(obj);
                    }
                }
            }
            cLocale.prototype.onChange = function()
            {
                Locale.visit(this);
    
    
            }
            window.Locale = new cLocale();
    
    
            var fn = Ext.ClassManager.instantiate;
            Ext.ClassManager.instantiate = function()
            {
                console.log('create one');
                var instance = fn.apply(this,arguments);
                Locale.visit(instance);
                return instance;
            }
    
    
    
    
            Ext.define('MyView',{
                extend:'Ext.Container',
                config:{
                    layout:'vbox',
                    items:[
                    {
                        xtype:'titlebar',
                        title:'ceva',
                        locales:{
                            title:'DASHBOARD_TITLE'
                        }
                    },
                    {
                        xtype:'textfield',
                        label:'',
                        placeHolder:'',
                        locales:{
                            label:'DASHBOARD_TITLE',
                            placeHolder:'AUTH_TITLE'
                        }
                    },
                    {
                        xtype:'button',
                        ui:'action',
                        text:'fr',
                        locales:{
                            text:'FR'
                        },
                        handler:function(){
                            Locale.load('fr');
                        }
                    },
                    {
                        xtype:'button',
                        ui:'action',
                        text:'en',
                        locales:{
                            text:'EN'
                        },
                        handler:function(){
                            Locale.load('en');
                        }
                    }]
    
    
                }
            });
    
    
            Ext.application({
                name:'MyApp',
                requires:['Ext.TitleBar','Ext.Button'],
                launch:function(){
                    Locale.load('fr');
                    Ext.Viewport.add(new MyView());
                }
            });
        })(this)



    because it works very well without having to write overrides for container, field, etc

  4. #4
    Sencha - Senior Forum Manager mitchellsimoens's Avatar
    Join Date
    Mar 2007
    Location
    Gainesville, FL
    Posts
    37,549
    Vote Rating
    873
    mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute

      0  

    Default


    Quote Originally Posted by bluehipy View Post
    yes mitchellsimoens[COLOR=#3E3E3E][FONT=Helvetica Neue] but still the Locale properties would not been there at the Ext.defined moment, so it won't work.
    Sure it will. I actually use the same concept for my urls for my stores that are defined in the config. Something like that? The Ext.require will require the Config class before everything.

    Code:
    Ext.define('MyApp.util.Config', {
        singleton : true,
    
        config : {
            baseUrl : 'http://localhost/'
        },
    
        constructor : function(config) {
            this.initConfig(config);
    
            this.callParent([config]);
        }
    });
    
    Ext.define('MyApp.store.Users', {
        ...
    
        config : {
            autoLoad : true,
            proxy : {
                type : 'ajax',
                url : MyApp.util.Config.getBaseUrl() + 'getUsers'
            }
        }
    });
    
    Ext.Loader.setPath({
        MyApp : 'app'
    });
    
    Ext.require([
        'MyApp.util.Config'
    ]);
    
    Ext.application({
        name : 'MyApp',
        stores : ['Users']
    });
    Mitchell Simoens @SenchaMitch
    Sencha Inc, Senior Forum Manager
    ________________
    Check out my GitHub, lots of nice things for Ext JS 4 and Sencha Touch 2
    https://github.com/mitchellsimoens

    Think my support is good? Get more personalized support via a support subscription. https://www.sencha.com/store/

    Need more help with your app? Hire Sencha Services services@sencha.com

    Want to learn Sencha Touch 2? Check out Sencha Touch in Action that is in print!

    When posting code, please use BBCode's CODE tags.

  5. #5
    Sencha User bluehipy's Avatar
    Join Date
    Mar 2010
    Location
    Romania
    Posts
    612
    Vote Rating
    28
    bluehipy will become famous soon enough bluehipy will become famous soon enough

      0  

    Default


    1) Yes, in your case it works because the property of Config are not "dynamic", meaning that baseUrl is hard coded in Config's config

    In my case singleton Locale needs some time to load data from a json / webservice / localStorage and just after that it dynamically creates properties in a loop in load handler, something like this:
    i,ln=store.getCount();
    for(;i<ln;i++)
    {
    var model = store.getAt(i);
    Locale[ model.get('key') ] = model.get('value');
    }

    but this will trigger after application launch and, most pity, after Ext.define-ses.

    So everything in my views, where I have:

    {
    title: Locale.DASHBOARD_TITLE
    }

    will evaluate as undefined.


    2) What do you say about the overwriting of Ext.ClassManager.instanciate combined with the visitor approach I presented above. My tests make me very optimistic on that direction I am asking because you were my source of inspiration on that

    tx!

  6. #6
    Sencha - Senior Forum Manager mitchellsimoens's Avatar
    Join Date
    Mar 2007
    Location
    Gainesville, FL
    Posts
    37,549
    Vote Rating
    873
    mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute

      0  

    Default


    What I do in https://github.com/mitchellsimoens/Ux.locale.Manager is I get a list of components that need locales put on it and then use overrides to set the locale on each component.
    Mitchell Simoens @SenchaMitch
    Sencha Inc, Senior Forum Manager
    ________________
    Check out my GitHub, lots of nice things for Ext JS 4 and Sencha Touch 2
    https://github.com/mitchellsimoens

    Think my support is good? Get more personalized support via a support subscription. https://www.sencha.com/store/

    Need more help with your app? Hire Sencha Services services@sencha.com

    Want to learn Sencha Touch 2? Check out Sencha Touch in Action that is in print!

    When posting code, please use BBCode's CODE tags.

  7. #7
    Sencha User bluehipy's Avatar
    Join Date
    Mar 2010
    Location
    Romania
    Posts
    612
    Vote Rating
    28
    bluehipy will become famous soon enough bluehipy will become famous soon enough

      0  

    Default


    I understood that.
    I was asking your opinion on my approach using overriding of [COLOR=#3E3E3E][FONT=Helvetica Neue]Ext.ClassManager.instanciate to implement locale on all the instances presenting a locales property.

    function cLocale()
    {
    this.objects=[];
    this.locale = {};
    }


    cLocale.prototype.get = function(key)
    {
    return this.locale[key] || key+' not defined';
    }


    cLocale.prototype.load = function(lang)
    {
    if(lang == 'en')
    {
    this.locale = {
    'DASHBOARD_TITLE' : 'Dashboard',
    'AUTH_TITLE' :'Login',
    'EN' :'za en',
    'FR' :'za fr'
    }
    }else{
    this.locale = {
    'DASHBOARD_TITLE' : 'Le Dashboard',
    'AUTH_TITLE' :'Le Login',
    'EN' :'le en',
    'FR' :'le fr'
    }
    }
    var self = this;
    //private method
    function pingObjects()
    {
    for(var o in self.objects)
    {
    self.objects[o].onLocaleChange();
    }
    }
    pingObjects();
    }

    cLocale.prototype.visit = function(obj)
    {
    console.log(obj);
    if( obj.locales || obj.getLocales)
    {
    console.log('visit',obj);
    var key,value,capitalizedName,locales = obj.locales || obj.getLocales();
    for(key in locales)
    {
    capitalizedName = key.charAt(0).toUpperCase() + key.substr(1);
    console.log(capitalizedName);
    value = locales[key];
    obj['set'+capitalizedName].call(obj,this.get(value));
    }
    if(this.objects.indexOf(obj)==-1)
    {
    var self = this;
    obj.onLocaleChange = this.onChange;
    this.objects.push(obj);
    }
    }
    }
    cLocale.prototype.onChange = function()
    {
    Locale.visit(this);


    }
    window.Locale = new cLocale();


    var fn = Ext.ClassManager.instantiate;
    Ext.ClassManager.instantiate = function()
    {
    console.log('create one');
    var instance = fn.apply(this,arguments);
    Locale.visit(instance);
    return instance;
    }



    Do you see any drawbacks out of this?

  8. #8
    Sencha - Senior Forum Manager mitchellsimoens's Avatar
    Join Date
    Mar 2007
    Location
    Gainesville, FL
    Posts
    37,549
    Vote Rating
    873
    mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute mitchellsimoens has a reputation beyond repute

      0  

    Default


    The only drawback I see is maintainability. Overriding something that deep in the framework could be tricky. For instance, layouts are going to get some work done for 2.1.0 so if you created your own layout there is a risk it won't work 100% in 2.1.0. May not happen just something to keep in mind.
    Mitchell Simoens @SenchaMitch
    Sencha Inc, Senior Forum Manager
    ________________
    Check out my GitHub, lots of nice things for Ext JS 4 and Sencha Touch 2
    https://github.com/mitchellsimoens

    Think my support is good? Get more personalized support via a support subscription. https://www.sencha.com/store/

    Need more help with your app? Hire Sencha Services services@sencha.com

    Want to learn Sencha Touch 2? Check out Sencha Touch in Action that is in print!

    When posting code, please use BBCode's CODE tags.

  9. #9
    Sencha User bluehipy's Avatar
    Join Date
    Mar 2010
    Location
    Romania
    Posts
    612
    Vote Rating
    28
    bluehipy will become famous soon enough bluehipy will become famous soon enough

      0  

    Default


    Thank you!

Thread Participants: 1

Tags for this Thread