1. #1
    Sencha Premium Member
    Join Date
    Sep 2014
    Posts
    13
    Vote Rating
    0
    Van_vco is on a distinguished road

      0  

    Default Unanswered: listening for changes to models with setData()

    Environment: Touch 2.4.1, CMD 5.1.2.52

    Problems:
    1. Not receiving change listener for fields on an existing model instance when new data is applied to the model using setData().
    2. Not sure how to get register change listeners on the fields of a HasOne assocation
    Description:

    We have a set of models that represent the current state of our UI (logged in user, preferences, etc). I've simplified the model to User and Department for this post. The server periodically pushes out updates for this model via websockets, which is why we want to use model.setData() instead of just letting the Proxy update the data in the model.

    Since we're using this in-memory object to track UI state, and not just binding it to a form or list, when updates come in, we want to know which field's values are changing -- not only on the base model but also on fields within the HasOne, HasMany associated models. Here are some specifics, and a Fiddle:

    We receive JSON like this:

    Code:
    {
        "username": "dvader",
        "dept": {
            "name": "Dark Side Liquidations,Acquisitions and Mergers",
            "code": "DSLAM"
        }
    }
    Here are the models:

    Code:
    Ext.define('Fiddle.model.User', {
        extend : 'Ext.data.Model',
        
        fields : [
            'username'
        ],
        
        hasOne: [
            {
                associationKey: 'dept',
                model: 'Fiddle.model.Department'
            }
        ],
    
    
        afterEdit: function(a, b, c) {
            this.callParent(arguments);
            this.fireEvent('fieldChange',a);
        }
    });
    
    
    Ext.define('Fiddle.model.Department', {
        extend : 'Ext.data.Model',
        fields : ['name','code'],
        afterEdit: function(a, b, c) {
            this.callParent(arguments);
            this.fireEvent('fieldChange',a);
        }
    });
    We're overriding model.afterEdit() in order to dispatch an event every time a field changes, which we catch in the controller. If we set a single property on the model, that listener fires as expected:

    Code:
    var user = Ext.create('Fiddle.model.User');
    user.set('username','Luke');
    // (event listener fires)
    But what we really want to do is apply the whole JSON string to that model instance in a single call, and receive events only for the fields that are changing.

    Code:
    user.setData(jsonFromServer);
    // problem:  no events fire
    Equally importantly is that when model.setData() changes the values in the nested models ("dept" in this example), I can't figure out how to register a listener for the fieldChange event that model is firing.

  2. #2
    Sencha - Sr Software Engineer mitchellsimoens's Avatar
    Join Date
    Mar 2007
    Location
    Gainesville, FL
    Posts
    38,518
    Vote Rating
    1113
    Answers
    3693
    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

    If you look at the code, setData does not call afterEdit. You would have to also hook into setData to handle your event firing.
    Mitchell Simoens @SenchaMitch
    Sencha Inc, Senior Software Engineer
    ________________
    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
    Join Date
    May 2011
    Location
    Gainesville, FL
    Posts
    217
    Vote Rating
    22
    Answers
    29
    fischer1121 will become famous soon enough

      0  

    Default

    Another option is to simply do:
    Code:
    Ext.create('Fiddle.model.User', jsonFromServer);
    The advantage is you can update any record within your app without needing to maintain a reference to it. Touch checks the in-memory cache of every record, finds the existing record for you, and updates it along with its associations. #endEdit will get called if there are modifications.

    There's a slight difference too. With #set & #setData, any local changes the user has made without persisting (or calling #commit()) will be lost when the server pushes the new record. With this approach, if a field is modified by the user but not yet persisted, the server value will *not* be applied - it will pass over the field - allowing the user to persist the changes made. So depending on the scenario, you may want to choose the first or second approach.

  4. #4
    Sencha Premium Member
    Join Date
    Sep 2014
    Posts
    13
    Vote Rating
    0
    Van_vco is on a distinguished road

      0  

    Default StoreManager?

    Quote Originally Posted by fischer1121 View Post
    Another option is to simply do:
    Code:
    Ext.create('Fiddle.model.User', jsonFromServer);
    The advantage is you can update any record within your app without needing to maintain a reference to it. Touch checks the in-memory cache of every record, finds the existing record for you, and updates it along with its associations. #endEdit will get called if there are modifications.
    That sounds like what I'm looking for, but I'm not quite sure how to implement it.
    • When you say "checks the in-memory cache" are you referring to the Store Manager? If so, do I need to add a store to my example to use this approach, perhaps with a memory proxy?
    • From another controller, how do I get a reference to that model instance? If I'm using a store, then I guess the answer would be "Ext.data.StoreManager.lookup('storeName').getById(0)". But you aren't recommending me to add a store to my code, then I'm not sure how to do this.
    How should I modify my example to use your suggestion?

  5. #5
    Sencha User
    Join Date
    May 2011
    Location
    Gainesville, FL
    Posts
    217
    Vote Rating
    22
    Answers
    29
    fischer1121 will become famous soon enough

      0  

    Default

    The cache I'm referring to is Ext.data.Model.cache. If you type that in the console, you'll see that Touch maintains a reference to every record you create - independent of stores. It uses this when you call Ext.create to update the existing record if present in memory. The relevant code is in Ext.data.Model#constructor. By plugging into this directly, you can make the app real-time with almost no knowledge of the app's architecture. No need to know where records are, no need to kluge around with stores, etc. Here's an example of a fully real-time Touch app:

    Code:
    // server sends: { event_name: 'Update', record_type: 'User', record: {...} }
    function yourWebsocketChannelCallback(data){
      var record = Ext.create('MyApp.model.' + data.record_type, data.record);
    
      if(data.event_name === 'Create'){
        var store = Ext.getStore(Ext.util.Inflector.plural(data.record_type));
        if(store){
          store.add(record);
        }
      }
      else if(data.event_name === 'Update'){
        // relax. the record has already been updated by Ext.create!
      }
      else if(data.event_name === 'Delete'){
        record.destroy();
      }
    }
    If you need to maintain a reference to a model across controllers, and it's acting like a singleton that doesn't really belong in a store, one way is to add it to app.js. So:
    Code:
    Ext.application({
      name: 'MyApp',
    
      _user: null,
    
      getUser: function(){
        return this._user;
      },
    
      setUser: function(user){
        this._user = user;
      }
    });
    
    Ext.define('MyApp.controller.Main', {
      // ...
      fnThatSetsTheRecordInitially: function(){
        MyApp.app.setUser(userRecord);
      }
    });
    
    Ext.define('MyApp.controller.AnotherController', {
      // ...
      fnThatNeedsTheRecord: function(){
        var user = MyApp.app.getUser();
      }
    });

    Also, an alternative to overriding things in your model is to simply bind the model to a fn as if it were bound to a component/store. Example:

    Code:
    Ext.define('Ext.mux.data.Model', {
      extend: 'Ext.data.Model',
    
        /**   * An easy way to say "when this record is changed, call this function". Also, calls the function once when bound.
       */
      bindWith: function(callback){
        var me = this;
        var boundFns = {
          afterEdit: function(){
            callback(me);
          },
          afterErase: function(){
            callback(me);
          }
        };
    
    
        this.join(boundFns);
    
    
        callback(me);
    
    
        return function(){
          me.unjoin(boundFns);
        };
      }
    });
    
    
    Ext.define('MyApp.model.User', {
      extend: 'Ext.mux.data.Model',
      // ..
    });
    
    Ext.define('MyApp.controller.User', {
      extend: 'Ext.app.Controller',
    
      fnThatCreatesTheUserInitially: function(){
        var user = Ext.create('MyApp.model.User', userData);
    
        user.bindWith(function(user){
          Ext.Viewport.down('mainheader #userName').setHtml(user.get('name'));
        });
      }
    });
    Something like that would keep a header in sync with the user record.

    Sorry if this is scatterbrained! Just throwing out ideas from what I'm doing - so use what you can.

Thread Participants: 2

Tags for this Thread