Skip to content Skip to sidebar Skip to footer

Load App.js Before Rest Of Application

I'm trying to figure out how I can load app.js before allowing the user to get the actual application. What I'm attempting to do is load a user's configuration file before all of m

Solution 1:

I call this "splitting the build", because it removes the Ext.container.Viewport class's dependency tree from the Ext.app.Application class. All Ext JS applications have a viewport that is set as the main view. By moving all requires declarations of the core of the application to the viewport class, an application can load the viewport explicitly from the application class, and the production build can be configured to output two separate files, app.js and viewport.js. Then any number of operations can occur before the core of the application is loaded.

// The app.js file defines the application class and loads the viewport// file.Ext.define('MyApp.Application', {
   extend: 'Ext.app.Application',
   requires: [
      // Ext JS'Ext.Loader'
   ],
   appProperty: 'application',
   name: 'MyApp',

   launch: function() {
      // Perform additional operations before loading the viewport// and its dependencies.Ext.Ajax.request({
         url: 'myapp/config',
         method: 'GET',
         success: this.myAppRequestSuccessCallback
      });
   },

   myAppRequestSuccessCallback: function(options, success, response) {
      // Save response of the request and load the viewport without// declaring a dependency on it.Ext.Loader.loadScript('classic/viewport.js');
   }
});

-

// The clasic/viewport.js file requires the viewport class which in turn// requires the rest of the application.    Ext.require('MyApp.container.Viewport', function() {
   // The viewport requires all additional classes of the application.MyApp.application.setMainView('MyApp.container.Viewport');
});

When building in production, the viewport and its dependencies will not be included in app.js, because it is not declared in the requires statement. Add the following to the application's build.xml file to compile the viewport and all of its dependencies into viewport.js. Conveniently, the development and production file structures remain the same.

<targetname="-after-js"><!-- The following is derived from the compile-js target in
        .sencha/app/js-impl.xml. Compile the viewport and all of its
        dependencies into viewport.js. Include in the framework
        dependencies in the framework file. --><x-compilerefid="${compiler.ref.id}">
        <![CDATA[
            union
              -r
              -class=${app.name}.container.Viewport
            and
            save
              viewport
            and
            intersect
              -set=viewport,allframework
            and
            include
              -set=frameworkdeps
            and
            save
              frameworkdeps
            and
            include
              -tag=Ext.cmd.derive
            and
            concat
              -remove-text-references=${build.remove.references}
              -optimize-string-references=${build.optimize.string.references}
              -remove-requirement-nodes=${build.remove.requirement.nodes}
              ${build.compression}
              -out=${build.framework.file}
              ${build.concat.options}
            and
            restore
              viewport
            and
            exclude
              -set=frameworkdeps
            and
            exclude
              -set=page
            and
            exclude
              -tag=Ext.cmd.derive,derive
            and
            concat
              -remove-text-references=${build.remove.references}
              -optimize-string-references=${build.optimize.string.references}
              -remove-requirement-nodes=${build.remove.requirement.nodes}
              ${build.compression}
              -out=${build.out.base.path}/${build.id}/viewport.js
              ${build.concat.options}
            ]]>
    </x-compile><!-- Concatenate the file that sets the main view. --><concatdestfile="${build.out.base.path}/${build.id}/viewport.js"append="true"><filesetfile="classic/viewport.js" /></concat></target><targetname="-before-sass"><!-- The viewport is not explicitly required by the application,
         however, its SCSS dependencies need to be included. Unfortunately,
         the property required to filter the output, sass.name.filter, is
         declared as local and cannot be overridden. Use the development
         configuration instead. --><propertyname="build.include.all.scss"value="true"/></target>

This particular implementation saves the framework dependencies in their own file, framework.js. This is configured as part of the output declaration in the app.json file.

"output": {
   ...
   "framework": {
      // Split the framework from the application."enable": true
   }
}

https://docs.sencha.com/extjs/6.2.0/classic/Ext.app.Application.html#cfg-mainViewhttps://docs.sencha.com/extjs/6.2.0/classic/Ext.container.Viewport.htmlhttps://docs.sencha.com/cmd/guides/advanced_cmd/cmd_build.html#advanced_cmd-_-cmd_build_-_introduction

Solution 2:

As far as I know, this is not possible with Sencha Cmd, because while Sencha Cmd can load framework and application separately, it is not possible to tell the production microloader to wait with the second file until the code from the first file has done something (presumably loaded something from the server?).

So the only approach would be to get the options outside ExtJS, before loading ExtJS.

You would have to write your own javascript that loads the configuration into a global variable using a bare, synchronous XmlHttpRequest, and include that into the index.html before the ExtJS script. That way, the script is executed before ExtJS is loaded at all, and you have completely consistent behaviour across development, testing and production builds without modifying any framework file that may be overwritten during framework upgrades.

I guess this is what you are searching for.

So how I did it: In index.html, I added a custom script that fills some global variables:

<metaname="viewport"content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"><scripttype="text/javascript">APIURI = '../api/', // <- also used in ExtJS.var xhr = newXMLHttpRequest();
    xhr.open('GET', APIURI+'GetOptions', false);
    xhr.setRequestHeader('Accept','application/json');
    xhr.send(null);
    try {
        var configdata = eval("(" + xhr.responseText + ")");
    } catch(e) {
         // snip: custom code for the cases where responseText was invalid JSON because of a shitty backend
    }
    if(configdata.options!=undefined) Settings = configdata.options;
    elseSettings = {};
    if(configdata.translations!=undefined) Translations = configdata.translations;
    elseTranslations = {};
    Translations.get=function(str) {
        if(typeofTranslations[str]=="string") returnTranslations[str];
        return"Translation string "+str+" missing.";
    };
 </script><linktype="image/vnd.microsoft."href="../fav.ico"><title>Application</title><scriptid="microloader"data-app="1a7a9de2-a3b2-2a57-b5af-df428680b72b"type="text/javascript"src="bootstrap.js"></script>

Then I could use in Ext.define() e.g. title: Translations.get('TEST') or hidden: Settings.HideSomeButton or url: APIURI + 'GetUserData'.

However, this has major drawbacks you should consider before proceeding.

After a short period of time, new feature requests emerged and settings I had considered fixed should change at runtime, and I realized that always reloading the application when a setting changes is not good user experience. A while later, I also found that Chrome has deprecated synchronous XmlHttpRequests, and that this approach delays application startup time.

So, the decision was made that in the long run, the only sane approach is to be able to react to changes of any configuration value at runtime, without a full reload of the application. That way, settings could be applied after loading the application, and the requirement could be dropped to wait for settings load before proceeding with the application.

For this, I had to completely work out everything needed for full localization support, so the user can switch between languages without reload of the application, and also any other setting can change at runtime and is automatically applied to the application.

Short-term, this is quite some work, which didn't really matter to me because I was scheduled to rework the whole application layout, but long-term, this will save quite some time and headache, especially when someone decides we should start polling for changes to the settings from the server, or that we should use an ExtJS form for login instead of good old Basic authentication (which was by then already asked for multiple times, but we couldn't deliver because of said shitty ExtJS app architecture).

Solution 3:

We actually do use a Sencha CMD approach. As @Alexander mentioned, we also use a global variable for keeping the application's configuration. This approach also implies that the server returns the actual declaration of the global config variable.

If you dig into the app.json file, and find the js config key, you will see that in the description it says

List of all JavaScript assets in the right execution order.

So, we add the configuration's endpoint before the app.js asset

"js": [
    {
        "path": "data/config",
        "remote": true
    },
    {
        "path": "${framework.dir}/build/ext-all-debug.js"
    },
    {
        "path": "app.js",
        "bundle": true
    }
]

also specifying remote: true.

// Specify as true if this file is remote and should not be copied into the build folder

The "data/config" endpoint returns something like:

varCONFIG= {
    user: {
        id:1,
        name:'User'
    },
    app: {
        language:'en'
    }
}

And now we can have a reference to the CONFIG variable anywhere in our classes.

Post a Comment for "Load App.js Before Rest Of Application"