1
votes

I have created a Sencha Touch 2 app and built a production mode version. However, I have encountered a big issue with the production build and it running in Phone/Tablet modes.

The current profile implementation of ST2 seems flawed as even if you have a specific profile activated, all views are still loaded in. In my application I want to be able to specify views using the xtype alias in the view config, and have the correct view for phone or tablet profile loaded in without any special coding. If all views from profiles are loaded in then this can't work (one view will always override another).

The only way I could achieve this was to dynamically add the profile at bootup stage (within app.js) like so:

Ext.application({
    name: 'MyTestApp',

    var activeProfile = Ext.os.is.Phone ? ['Phone'] : ['Tablet'];

    requires: [ ... ],

    profiles: activeProfile

});

This has worked fine. It means I can then load the correct view and still just use the xtype alias within the config of another view and/or ref in a controller. However, I noticed that when I generate a production build and load up a console window, both of the following are defined:

MyTestApp.views.phone.Login
MyTestApp.views.tablet.Login

Normally the tablet or phone version would be undefined depending on the profile. I'm assuming this is the case because the production mode build has parsed ALL dependencies and then included all views regardless of the profile.

So in my start-up controller I have a button handler which then creates a login view from the xtype.

Controller:

refs: {                
            loginView: {
                selector: 'loginview',
                xtype: 'loginview',
                autoCreate: true
            }
 }

Handler:

var loginView = this.getLoginView();

In development mode, the loginView variable will either be MyTestApp.views.tablet.Login or MyTestApp.views.phone.Login depending on the profile.

How do I ensure that the loginview instantiated here gets the correct version depending on the profile when in production mode?

3
Might want to change the title, it's YOUR code that's flawed. Profiles is an array of profiles that the app goes through and selects the first one that returns true from isActive() method based on profile classes that you need to define. It's spelled out very clearly in the docs: docs.sencha.com/touch/2-2/#!/api/Ext.app.ProfileDawesi
Sorry, my intention isn't to criticize the framework, its great. However, I feel that I've hit a brick wall with this particular feature. Please see the following about this issue: sencha.com/forum/… If my code is flawed, please feel free to tell me HOW it is flawed.jaffa

3 Answers

2
votes

I had been struggling with this, when I would move either of the solutions to the devices, I would be stuck with the fact that all views are referenced and would get some xtype collision always giving me the phone view. ( i had to move to aliases eventually - not sure why :( ). I finally managed to crack this for my use case, just sharing for future reference.

I am running touch 2.3.1 and cordova 3.3.1 with the latest cmd 4.0.2.67

I use the solution from Christopher except I had to change the source code in the sencha touch source directory rather than keep it in the app.js [truthfully I don't know why it hangs when I leave it as an override]

In addition I have had to configure the views the following way in order for:

  • define a base class for the view with an alias so the controller to understand the ref as it loads first
  • dynamically assign the alias to the view instantiated by the profile
  • strip out (using Christopher code)

Base class for the views

    Ext.define('MyApp.view.CatalogView', {
        extend: 'Ext.Container',
        alias: 'widget.catalogview'
    });        

Assign an alias to the profile specific view

    Ext.define('MyApp.profile.Phone', {
        extend: 'Ext.app.Profile',

        config: {
            name: 'Phone',
            views: ['CatalogView'],
        },

       isActive: function() {
           return Ext.os.is('Phone');
       },

       launch: function() {
           Ext.ClassManager.setAlias('MyApp.view.phone.CatalogView', 'widget.catalogview');
       }
    });

Repeat for the tablet view

1
votes

For all who want to know how I resolved this, I'm now left bald after pulling all my hair out;)

All my profile views where I want to have the xtype names remain the same even though they might belong in the phone or tablet profiles, I have to remove the alias/xtype config on the class. I then have a profile base class defined like so with a shared helper function:

Ext.define('MyApp.profile.Base', {
    extend: 'Ext.app.Profile',

    config: {

    },

    mapViewAliases: function () {
        var self = this;

        var views = this.getDependencies().view;
        var newAliasMap = null;

        Ext.each(views, function (view) {
            Ext.Array.some(self.getViewsToAliasMap(), function (map) {
                if (map[view]) {
                    if (!newAliasMap) {
                        newAliasMap = {};
                    }

                    newAliasMap[view] = [map[view]];                    
                    return true;
                }
            });
        });

        if (newAliasMap) {
            console.log('view aliases being mapped for: ' + this.$className);
            Ext.ClassManager.addNameAliasMappings(newAliasMap)
        }
    }
});

Then I have the profile class inherit from the base class (this is repeated with the tablet profile except the viewsToAliasMap holds classes belonging to the tablet profile instead of the phone profile):

Ext.define('MyApp.profile.Phone', {
    extend: 'MyApp.profile.Base',

    config: {
        name: 'Phone',
        views: ['Login', 'Home', 'Welcome' ],

        viewsToAliasMap: [
             { 'MyApp.view.phone.Login': 'widget.loginview' },
             { 'MyApp.view.phone.Home': 'widget.homeview' },
             { 'MyApp.view.phone.Welcome': 'widget.welcomeview' }
        ]
    },

    isActive: function () {
        return Ext.os.is.Phone;
    },

    launch: function () {
        console.log("Phone profile launched");

        this.mapViewAliases();

    }
});

So basically, the profile calls the function mapViewAliases() on the base class in the launch function. The mapViewAliases() registers the view class names with the aliases defined in the profile with the class manager. So effectively the xtype names are resolved at run-time.

I'm sure this code can be improved and/or a better way to do this. Please feel free to let me know.

0
votes

I am using a pretty naive implementation... I'm sure it could be made more robust, but I've been hacking at this for 5 hours or so now.

Ext.define('MyApp.override.Application', {
    override : 'Ext.app.Application',

    onProfilesLoaded: function() {
        var profiles  = this.getProfiles(),
            length    = profiles.length,
            instances = [],
            requires  = this.gatherDependencies(),
            current, i, profileDeps;

        for (i = 0; i < length; i++) {
            var instance = Ext.create(profiles[i], {
                application: this
            });

            /*
             * Note that we actually require all of the dependencies for all Profiles - this is so that we can produce
             * a single build file that will work on all defined Profiles. Although the other classes will be loaded,
             * the correct Profile will still be identified and the other classes ignored. While this feels somewhat
             * inefficient, the majority of the bulk of an application is likely to be the framework itself. The bigger
             * the app though, the bigger the effect of this inefficiency so ideally we will create a way to create and
             * load Profile-specific builds in a future release.
             *
             CMK - PSHAW!
             */

            if (instance.isActive() && !current) {
                console.log('Profile active: ' + instance.getName());
                current = instance;

                profileDeps = instance.getDependencies();
                requires = requires.concat(profileDeps.all);
                var ns = instance.getNamespace();

                this.setCurrentProfile(current);

                // Merge Controllers, Models, Stores, and Views
                this.setControllers(this.getControllers().concat(profileDeps.controller));
                this.setModels(this.getModels().concat(profileDeps.model));
                this.setStores(this.getStores().concat(profileDeps.store));
                this.setViews(this.getViews().concat(profileDeps.view));

                // Remove the view ref and requires for default views, when a profile specific one exists
                Ext.each(profileDeps.view, function(className) {
                    if (className.indexOf('view.' + ns + '.') !== -1) {

                        // Requires
                        var index = requires.indexOf(className.replace('view.' + ns, 'view'));
                        if (index !== -1) {
                            requires.splice(index, 1);
                        }
                        // Views
                        index = this.getViews().indexOf(className.replace('view.' + ns, 'view'));
                        if (index !== -1) {
                            this.getViews().splice(index, 1);
                        }
                    }
                }, this);

                instances[0] = instance;
                break;
            }
        }

        this.setProfileInstances(instances);
        Ext.require(requires, this.loadControllerDependencies, this);
    }
});

Put this before your Ext.application, and it replaces the profile loader... This one strips out default views with the same name as one in the active profile namespace.

It requires that you define an xtype for the views that match, then even your refs in controllers will work...

I need to continue testing with this, but it looks promising so far.