Success! Looks like we've fixed this one. According to our records the fix was applied for TOUCH-1554 in a recent build.
  1. #1
    Sencha Premium Member
    Join Date
    Jan 2012
    Posts
    5
    Vote Rating
    0
    webexplorer123 is on a distinguished road

      0  

    Default PR4 - Custom folder structure for MVC no longer supported?

    PR4 - Custom folder structure for MVC no longer supported?


    Hi,

    I just upgraded to PR4 and encountered the following issue:

    In PR3, I could have custom folder structure for my MVC classes as follows:
    Code:
    Ext.application({
                                    name  : 'MyApp',
                                    controllers: [
                                        'MyApp.mobile.abc.controller.Controller1'
                                    ]
                                });
    together with
    Code:
    Ext.Loader.setConfig({
       paths: {'MyApp.mobile.abc' : 'resources/scripts/myapp/mobile/abc'}
    });
    It seems like this no longer works in PR4. In PR4, I got the error saying that the class 'MyApp.controller.MyApp.mobile.abc.controller.Controller1' was required. I looked at Ext.app.Application.js, and noticed that the MVC classes are now expected to be in their standard locations. Is there a way around this limitation or custom folder structure is no longer supported going forward?

    Thanks.

    --chen

  2. #2
    jay@moduscreate.com's Avatar
    Join Date
    Mar 2007
    Location
    Frederick MD, NYC, DC
    Posts
    16,360
    Vote Rating
    81
    jay@moduscreate.com is a name known to all jay@moduscreate.com is a name known to all jay@moduscreate.com is a name known to all jay@moduscreate.com is a name known to all jay@moduscreate.com is a name known to all jay@moduscreate.com is a name known to all

      0  

    Default


    Controller1.js (a non descriptive name!) should be in resources/scripts/myapp/mobile/abc/controller

  3. #3
    Sencha Premium Member
    Join Date
    Jan 2012
    Posts
    5
    Vote Rating
    0
    webexplorer123 is on a distinguished road

      0  

    Default


    Thanks for the reply. I verified that the controller was in the right folder location and it worked fine in PR3. I believe the real cause of the problem is that in PR4, the class names of model, view, controller, profiles, etc of Ext.app.Application are determined from predefined formats. As an example, take a look at the gatherDependencies() method:
    Code:
    /**
         * @private
         * Computes all of the class names for this Application's dependencies
         */
        gatherDependencies: function() {
            var name = this.getName(),
                models = this.getModels(),
                views = this.getViews(),
                controllers = this.getControllers(),
                stores = this.getStores(),
    
                classes = [],
                format  = Ext.String.format;
    
            Ext.each(models, function(modelName) {
                classes.push(format('{0}.model.{1}', name, modelName));
            }, this);
    
            Ext.each(views, function(viewName) {
                classes.push(format('{0}.view.{1}', name, viewName));
            }, this);
    
            Ext.each(controllers, function(controllerName) {
                classes.push(format('{0}.controller.{1}', name, controllerName));
            }, this);
    
            Ext.each(stores, function(storeName) {
                if (Ext.isString(storeName)) {
                    classes.push(format('{0}.store.{1}', name, storeName));
                }
            }, this);
    
            return classes;
        },
    The returned classes from the above method will be used in the subsequent call to Ext.require, which gave me the error when they were not found.

    Another example using predefined format for class name when the controllers are instantiated:
    Code:
    /**
         * @private
         */
        instantiateControllers: function() {
            var controllerNames = this.getControllers(),
                instances = [],
                length = controllerNames.length,
                appName = this.getName(),
                name, i;
    
            for (i = 0; i < length; i++) {
                name = controllerNames[i];
    
                instances[name] = Ext.create(appName + '.controller.' + name, {
                    application: this
                });
    
                instances[name].init();
            }
    
            this.setControllerInstances(instances);
        }
    In PR3, the class names were determined using the getModuleClassName() method, which depended on Ext.Loader to figure out the class names. :
    Code:
    getModuleClassName: function(name, type) {
            var namespace = Ext.Loader.getPrefix(name);
    
            if (namespace.length > 0 && namespace !== name) {
                return name;
            }
    
            return this.name + '.' + type + '.' + name;
        },
    I must say that I am fairly new to Sencha Touch, and may have missed something here.

  4. #4
    Sencha - Community Support Team edspencer's Avatar
    Join Date
    Jan 2009
    Location
    Palo Alto, California
    Posts
    1,939
    Vote Rating
    9
    edspencer is a jewel in the rough edspencer is a jewel in the rough edspencer is a jewel in the rough

      0  

    Default


    Ok, I've pushed this into the bug tracker for analysis - it's quite possible it's a bug in the framework
    Ext JS Senior Software Architect
    Personal Blog: http://edspencer.net
    Twitter: http://twitter.com/edspencer
    Github: http://github.com/edspencer

  5. #5
    Sencha Premium Member
    Join Date
    Jan 2012
    Posts
    5
    Vote Rating
    0
    webexplorer123 is on a distinguished road

      0  

    Default


    Ok. Thanks for the prompt response. I will look for a work around for the time being. Thanks.

  6. #6
    Sencha User
    Join Date
    Jan 2012
    Posts
    16
    Vote Rating
    0
    widged is on a distinguished road

      0  

    Default


    Initially posted in the pr3 to pr4 gotchas before I bumped into this post. The exact same issue and report as webexplorer123.

    In pr3, in Ext.app.Application, a controller path was extrapoloated with:

    Code:
    this.getModuleClassName(controllers[i], 'controller').
    In pr4 this has apparently become:

    Code:
    format('{0}.controller.{1}', name, controllerName), name, controllerName);
    Here used in function gatherDependencies of Ext.app.Application. However, format('{0}.targetType.{1}', appName, targetName) is used all over the place in pr4 when the old this.getModuleClassName, would give a lot more flexibility (though I assume this was done to improve performance).

    An argument could be made for enforcing the use of the word controller in the path (though some could argue that App.SectionController could be used instead). It is less clear how best practices could dictate that the application name must be the first and only item in the path before the controller folder. Big applications benefit from splitting the code into separate sections, each with its mvc separation.
    Last edited by widged; 25 Jan 2012 at 5:10 AM. Reason: better connection with existing content

  7. #7
    Sencha Premium Member
    Join Date
    Jan 2012
    Posts
    5
    Vote Rating
    0
    webexplorer123 is on a distinguished road

      0  

    Default


    I agree. When working with MVC, I always feel that the best practice dictates too much on how my code should be structured. In a large application, code could be in separate modules with predefined package names that are not conforming to the standard MVC structure. Many times they are reusable code that are application agnostic, so the 'App.xx.xx' structure doesn't work too well for these classes.

    Just like the rest of ST, I think the MVC framework should be flexible enough to let us name/structure our code however we like.

    Just my two cents.

  8. #8
    Sencha User
    Join Date
    Mar 2010
    Location
    Seattle, WA
    Posts
    137
    Vote Rating
    1
    wprater is on a distinguished road

      0  

    Default


    Im having a similar issue migrating from PR3 -> PR4. We don't put our code in an app folder, rather a AppName folder and have setup are loader config as such.

    This was working in PR3.. any ideas for a quick fix would be appreciated.

    Let me know if you need more info.

  9. #9
    jay@moduscreate.com's Avatar
    Join Date
    Mar 2007
    Location
    Frederick MD, NYC, DC
    Posts
    16,360
    Vote Rating
    81
    jay@moduscreate.com is a name known to all jay@moduscreate.com is a name known to all jay@moduscreate.com is a name known to all jay@moduscreate.com is a name known to all jay@moduscreate.com is a name known to all jay@moduscreate.com is a name known to all

      0  

    Default


    Great to see that this was classified as a bug.

  10. #10
    Sencha - Community Support Team edspencer's Avatar
    Join Date
    Jan 2009
    Location
    Palo Alto, California
    Posts
    1,939
    Vote Rating
    9
    edspencer is a jewel in the rough edspencer is a jewel in the rough edspencer is a jewel in the rough

      0  

    Default


    Ok I spent the morning working through this one and think I have it in a good place now. Instead of building the class names on the fly in gatherDependencies we compute the full name at configuration time. This means you can specify any class in any of the models, views, controllers, stores and profiles configurations in your Application and it will load correctly.

    The basic rule is if the string you specify contains a period (".") we will treat it as a fully qualified class name and just load it as normal. If it doesn't contain a period we'll add the app namespace for you automatically as before.

    It's probably easiest to just show the test case I created to confirm the behavior. This now executes without errors:

    Code:
    Ext.application({
        name: 'MyApp',
        
        controllers: [
            'LocallyQualified',
            'MyApp.controller.FullyQualified',
            'MyApp.controller.deeply.Nested',
            'customNS.controller.Custom'
        ],
        
        stores: [
            'LocallyQualified',
            'MyApp.store.FullyQualified',
            'MyApp.store.deeply.Nested',
            'customNS.store.Custom'
        ],
        
        models: [
            'LocallyQualified',
            'MyApp.model.FullyQualified',
            'MyApp.model.deeply.Nested',
            'customNS.model.Custom'
        ],
        
        views: [
            'LocallyQualified',
            'MyApp.view.FullyQualified',
            'MyApp.view.deeply.Nested',
            'customNS.view.Custom'
        ],
        
        profiles: [
            'Phone',
            'MyApp.profile.Tablet',
            'customNS.profile.Custom'
        ],
        
        launch: function() {
            var controllers = this.getControllerInstances(),
                stores = this.getStores(),
                errors = [];
            
            var expectedControllers = [
                'MyApp.controller.FullyQualified',
                'MyApp.controller.LocallyQualified',
                'MyApp.controller.deeply.Nested',
                'customNS.controller.Custom'
            ];
            
            var expectedStores = [
                'MyApp.store.LocallyQualified',
                'MyApp.store.FullyQualified',
                'MyApp.store.deeply.Nested',
                'customNS.store.Custom'
            ];
            
            var expectedModels = [
                'MyApp.model.LocallyQualified',
                'MyApp.model.FullyQualified',
                'MyApp.model.deeply.Nested',
                'customNS.model.Custom'
            ];
            
            var expectedViews = [
                'MyApp.view.LocallyQualified',
                'MyApp.view.FullyQualified',
                'MyApp.view.deeply.Nested',
                'customNS.view.Custom'
            ];
            
            var expectedProfiles = [
                'MyApp.profile.Phone',
                'MyApp.profile.Tablet',
                'customNS.profile.Custom'
            ];
            
            Ext.each(expectedControllers, function(name) {
                if (!controllers[name]) {
                    errors.push(name + " should have been loaded but was not");
                }
            }, this);
            
            Ext.each(expectedStores, function(name, index) {
                if (!(stores[index] instanceof Ext.ClassManager.classes[name])) {
                    errors.push("Expected " + name + " to be ready");
                }
            }, this);
            
            Ext.each(expectedModels, function(name) {
                if (!Ext.ClassManager.classes[name]) {
                    errors.push("Expected Model " + name + " to be defined");
                }
            }, this);
            
            Ext.each(expectedViews, function(name) {
                if (!Ext.ClassManager.classes[name]) {
                    errors.push("Expected View " + name + " to be defined");
                }
            }, this);
            
            Ext.each(expectedProfiles, function(name) {
                if (!Ext.ClassManager.classes[name]) {
                    errors.push("Expected Profile " + name + " to be defined");
                }
            }, this);
            
            if (errors.length) {
                Ext.each(errors, function(e) {
                    console.error(e);
                });
            } else {
                console.log('Everything seemed to load ok');
            }
        }
    });
    Ext JS Senior Software Architect
    Personal Blog: http://edspencer.net
    Twitter: http://twitter.com/edspencer
    Github: http://github.com/edspencer