1. #1
    Sencha User
    Join Date
    Jul 2011
    Posts
    6
    Vote Rating
    0
    jgeiger is on a distinguished road

      0  

    Default Answered: Extend/inherit from a custom class which extends Ext.data.Model?

    Answered: Extend/inherit from a custom class which extends Ext.data.Model?


    I've got a series of models that I'd like to inherit from a common base. The only thing that really changes between them is the proxy url. I've been searching but haven't found a good example of how to get this working in 4.x. I've included the base model and what I've use to try and create the custom model as well.
    When I use this it dies with "Uncaught TypeError: Cannot call method 'substring' of undefined" which is probably because I'm not initializing it correctly. Any help/example would be appreciated. Thank you.

    Code:
    Ext.define('RA.model.SimpleModel', {  extend : 'Ext.data.Model',
    
    
      idProperty : 'id',
    
    
      fields : [{
        name : 'id',
        type : 'int'
      }, {
        name : 'value',
        type : 'string'
      }],
      proxy : {
        simpleSortMode : true,
        type : 'rest',
        url : '',
        reader : {
          type : 'json',
          root : 'results'
        },
        writer : simpleWriter
      }
    
    
    });
    Code:
    Ext.define('RA.model.Institution', {
      extend : 'RA.model.SimpleModel',
    
    
      proxy : {
        url : 'institutions'
      }
    
    
    });

  2. Just a quick feedback to your WriterFactory. Overriding a method in Ext.create will replace the method and not allow to call this.callOverridden() (at least in 4.0.2a). Even in cases where you don't need access to the overriden method I tend to dislike this style.

    Better do this IMHO

    Code:
    Ext.define('MyWriter', {
        extend: 'Ext.data.writer.Writer',
    
        prefix: 'defaultprefix', // optional
    
        write: function(request) {
            var prefix = this.prefix;
            // ...
        }
    });
    
    Ext.define('RA.model.WriterFactory', {
        generate : function(prefix) {
            return Ext.create('MyWriter', {
                prefix: prefix
            });
        }
    });
    And wouldn't it be better to override writeRecords?

  3. #2
    Sencha User mberrie's Avatar
    Join Date
    Feb 2011
    Location
    Bangkok, Thailand
    Posts
    506
    Vote Rating
    14
    Answers
    26
    mberrie will become famous soon enough mberrie will become famous soon enough

      0  

    Default


    Just a guess here: RA.model.Institution overrides the proxy configuration. However, Ext will not auto-magically merge the config object for you with the one from the parent class (RA.model.SimpleModel).

    The result is a proxy config that is incomplete and thanks to Ext's (nearly) incomplete error reporting you will get this awesome error message (btw, make sure you use ext-dev.js during development for better error messages).

    Possible solutions:
    • redefine the proxy config including ALL proxy config properties (code duplication)
    • merge the config from the derived class with the parent class. you could:
      • have your SimpleModule define the proxy in a defaultProxy config property, then in the constructor use Ext.apply(If) to merge the default configuration with the actual configuration.
        Be careful with execution order, Ext.data.Proxy will evaluate the proxy config object in the constructor (I think), so the merging has to happen before that.

        UPDATE: just found that Proxy already has a defaultProxyType property. However, from looking at the source this is just a fallback and doesn't merge configs either.
      • get a bit more fancy and use Ext's onBeforeClassCreated callback. This is not well documented (or not at all?), so it depends if you want to dig into the source. Furthermore, Ext.data.Model is somewhat special in that it has quite a bit of preprocessing code already.

  4. #3
    Sencha User
    Join Date
    Jul 2011
    Posts
    6
    Vote Rating
    0
    jgeiger is on a distinguished road

      0  

    Default


    So I was able to get it working, but it may not be the best way of doing it. I created a couple of factories that will generate the proxy and the writer since I needed a custom writer as well.

    So when I generate the custom model it extends the simple model with the fields and then builds the proxy using the factories. Right now I'm creating the factory instances in the app.js file (below) along with some other configuration.

    Is there a better place to be creating these? (I'm getting an error "[Ext.Loader] Synchronously loading 'RA.model.ProxyFactory'; consider adding Ext.require('RA.model.ProxyFactory') above Ext.onReady")

    Thanks.

    Code:
    Ext.Loader.setConfig({
      enabled : true,
      paths : {
        'Ext' : 'extjs/src',
        'RA' : 'app'
      }
    });
    
     Ext.require(['Ext.*', 'RA.*']);
     
    Ext.application({
      name : 'RA',
       appFolder : 'public/app',
       // Define all the controllers that should initialize at boot up of your
      // application
      controllers : ['LoginController'],
       autoCreateViewport : true
    });
    
    Ext.JSON.encodeDate = function(d) {
      return Ext.Date.format(d, '"Y-m-d"');
    };
    var proxyFactory = Ext.create('RA.model.ProxyFactory');
    var writerFactory = Ext.create('RA.model.WriterFactory');
    Code:
    Ext.define('RA.model.Institution', {
       extend : 'RA.model.SimpleModel',
       proxy : proxyFactory.generate('institutions', 'institution')
     });
    Code:
    Ext.define('RA.model.SimpleModel', {
      extend : 'Ext.data.Model',
       idProperty : 'id',
       fields : [{
        name : 'id',
        type : 'int'
      }, {
        name : 'value',
        type : 'string'
      }]
    });
    Code:
    Ext.define('RA.model.ProxyFactory', {
      generate : function(url, prefix) {
        var proxy = Ext.create('Ext.data.proxy.Rest', {
          simpleSortMode : true,
          type : 'rest',
          url : url,
          reader : {
            type : 'json',
            root : 'results'
          },
          writer : writerFactory.generate(prefix)
        })
        return proxy;
      }
    });
    Code:
    Ext.define('RA.model.WriterFactory', {
       generate : function(prefix) {
        var writer = Ext.create('Ext.data.writer.Writer', {
          write : function(request) {
            var data = request.records[0].data;
            for(key in data) {
              request.params[prefix+"."+key] = data[key];
            }
            return request;
          }
        })
        return writer;
      }
    });

  5. #4
    Sencha User
    Join Date
    Jul 2011
    Posts
    6
    Vote Rating
    0
    jgeiger is on a distinguished road

      0  

    Default


    Also, thanks for the tip about using dev. I was using debug-all and though that was the correct one. dev is a lot more helpful with the error messages.

  6. #5
    Sencha User mberrie's Avatar
    Join Date
    Feb 2011
    Location
    Bangkok, Thailand
    Posts
    506
    Vote Rating
    14
    Answers
    26
    mberrie will become famous soon enough mberrie will become famous soon enough

      0  

    Default


    Just a quick feedback to your WriterFactory. Overriding a method in Ext.create will replace the method and not allow to call this.callOverridden() (at least in 4.0.2a). Even in cases where you don't need access to the overriden method I tend to dislike this style.

    Better do this IMHO

    Code:
    Ext.define('MyWriter', {
        extend: 'Ext.data.writer.Writer',
    
        prefix: 'defaultprefix', // optional
    
        write: function(request) {
            var prefix = this.prefix;
            // ...
        }
    });
    
    Ext.define('RA.model.WriterFactory', {
        generate : function(prefix) {
            return Ext.create('MyWriter', {
                prefix: prefix
            });
        }
    });
    And wouldn't it be better to override writeRecords?

  7. #6
    Sencha User mberrie's Avatar
    Join Date
    Feb 2011
    Location
    Bangkok, Thailand
    Posts
    506
    Vote Rating
    14
    Answers
    26
    mberrie will become famous soon enough mberrie will become famous soon enough

      0  

    Default


    To get rid of the Ext.Loader warning:

    Add your two classes to the 'requires' section of your Ext.application definition, and create the factories inside the 'launch' callback.

    See Ext.app.Application

  8. #7
    Sencha User
    Join Date
    Jul 2011
    Posts
    6
    Vote Rating
    0
    jgeiger is on a distinguished road

      0  

    Default


    I've gotten it mostly working except where to create the factories. If I add them into the launch section inside the app, it fails to create the institution because it's being created before the app is launched. This might be an organization issue on my part, but I'm trying to follow the 4.x MVC idea that you load your controllers in the app.js, and the controllers load their models and views. If I move them outside the Ext.application block everything works, I just get the warning to move it before onReady.

    The updated code is included in case anyone needs to see it. Thanks.

    Code:
    Ext.define('RA.model.SimpleWriter', {  extend : 'Ext.data.writer.Writer',
      prefix : '', // optional
      writeRecords : function(request) {
        var prefix = this.prefix;
        var data = request.records[0].data;
        for(key in data) {
          request.params[prefix + "." + key] = data[key];
        }
        return request;
      }
    });
    Code:
    Ext.define('RA.model.WriterFactory', {
      generate : function(prefix) {
        var writer = Ext.create('RA.model.SimpleWriter', {
          prefix : prefix
        })
        return writer;
      }
    });

  9. #8
    Sencha User mberrie's Avatar
    Join Date
    Feb 2011
    Location
    Bangkok, Thailand
    Posts
    506
    Vote Rating
    14
    Answers
    26
    mberrie will become famous soon enough mberrie will become famous soon enough

      1  

    Default


    Quote Originally Posted by jgeiger View Post
    If I add them into the launch section inside the app, it fails to create the institution because it's being created before the app is launched.
    Yeah, you're right. My bad.

    The problem is the way that you assign the proxy to the 'Ra.model.Inistitution' as part of the class definition (Ext.define). I found that it is vital in understanding the Ext class system to remember that this is still Javascript even though Ext.define makes certain things look very similar to other class- (and not prototype) based languages. Check this out:
    Code:
    Ext.define('RA.model.Institution', {
       extend : 'RA.model.SimpleModel',
       proxy : proxyFactory.generate('institutions', 'institution')
     });
    This means that proxyFactory.generate will be called as soon as Institution.js (the file, not the class!) is loaded.

    Quote Originally Posted by mberrie View Post
    Looking at classic OOP and compiled languages (or languages that have the construct of a 'class'), you have to consider that the code statement that defines the (default) value for a class field is re-evaluated each time an instance of the class is created.

    This is not possible in Javascript and in Ext.define(). The code statement that defines the value for the property will be evaluated exactly once and the *result* will be passed to Ext.define! The class object that Ext creates from the class definition cannot really imitate the behavior described above.
    Check out this discussion: http://www.sencha.com/forum/showthre...riginal-fields
    As a result the proxyFactory is called VERY early in your application bootstrap process.


    In many other cases you could solve this problem by just assigning the value as part of the constructor:

    Code:
    // NOTE: Don't do this - read my commentary below!!
    Ext.define('RA.model.Institution', {
       extend : 'RA.model.SimpleModel',
       requires: ['RA.model.ProxyFactory'],
       constructor: function() {
          this.proxy = proxyFactory.generate('institutions', 'institution');
          this.callParent(arugments);
       }
     });
    However, this will - I think - fail in this particular case. Ext.data.Model has special class preprocessor (onBeforeCreated) that will evaluate the proxy property and instantiate a proxy.

    I just noticed that the other day, and digging into the source more it looks like the configuration process for the proxy on a Model is actually pretty fragile. Ext requires the proxy to have a back reference to the model

    Code:
        setProxy: function(proxy) {
            //make sure we have an Ext.data.proxy.Proxy object
            if (!proxy.isProxy) {
                if (typeof proxy === "string") {
                    proxy = {
                        type: proxy
                    };
                }
                proxy = Ext.createByAlias("proxy." + proxy.type, proxy);
            }
            proxy.setModel(this.self);
            this.proxy = proxy;
    
            return proxy;
        },
    Note that setProxy is called in the onBeforeClassCreated preprocessing step. If you assign the proxy in the constructor, setProxy will already have been called. Fail!

    Not sure if you wanted to dive into the inner workings of Ext though



    So what's the conclusion:

    Solution (1)

    Stick to your current approach and assign the proxy in Ext.define.

    It will be difficult though to get dependencies right in your development setup (with Ext.Loader enabled). An easy solution is to include the factories with static <script src="xx"> tags (early). This will guarantee that they are loaded before your Model class is loaded and Ext.define is executed.

    What happens now is that when you call Ext.create('RA.model.ProxyFactory') the corresponding class file has not been loaded yet, and Ext.Loader will do an 'emergency' load operation and load it *synchronously* (blocking further execution).
    So if you don't mind the small delay ignoring the 'synchronous loading' warning is actually a 'solution' as well during development.

    In production environments you shouldn't use Ext.Loader anyway


    Solution (2)

    Go back to your original approach and use config objects instead of factories. The actual instances will then be created for you by the framework at the correct time.
    Register an alias for your proxy and writer classes which allows you to reference them in object literals. Also, specify dependencies on your custom proxy and writer classes in your Model definition.

    Of course, this will bring back the issues/challenges outlined in my first response to your problem.
    Actually, with the newly insight into the Model class I have to mention that you CANNOT merge the proxy configs in constructor (too late! see discussion above) but have to digg into the onBeforeClassCreated approach.

    However, if you create a custom proxy class which has the writer and everything already pre-configured, the config object would be very basic.

    Code:
       // assumes that your proxy class was defined with   alias:'proxy.raproxy'
    
      // this is inside your model class
        proxy: {
            type: 'raproxy',
            url: 'xxx'
        }
    There should actually be no need to explicitely state the dependency for your Model class on your custom proxy class. Ext.data.Model has special handling for that in its preprocessor.

    Also, you could now make use of the defaultProxyType config property (on your Model base class) mentioned in my first post:

    http://docs.sencha.com/ext-js/4-0/#!...faultProxyType


    Hm. I think Solution (2) could work out nicely!

Thread Participants: 1

Tags for this Thread