extjsextjs-storesextjs7

PullRefresh plugin of a List doesn't handle ChainedStore possibility


Using Ext JS 7.1 Modern, I have prepared an example to show the problem.

When I have remote filters on my main store, binding the dataview.List to a ChainedStore correctly handles my local filtering. However, when I also add a PullRefresh plugin to the list, I get an error during pull refresh. I see from the source code that the plugin doesn't consider the possibility that a list's store can be a ChainedStore.

I have tried to explain the problem with a Sencha Fiddle and also attached the code below.

I have temporarily solved the problem by overriding the fetchLatest and onLatestFetched methods of Ext.dataview.pullrefresh.PullRefresh plugin, to use the source store if the list's store is a ChainedStore. But I believe the source code must be updated to handle this case.

app.js

Ext.define('App.model.Test', {
    extend: 'Ext.data.Model',

    fields: ['id', 'firstName', 'lastName']
});

Ext.define('App.store.Test', {
    extend: 'Ext.data.Store',
    alias: 'store.teststore',

    model: 'App.model.Test'
});

Ext.define('App.viewmodel.Test', {
    extend: 'Ext.app.ViewModel',
    alias: 'viewmodel.test',
    data: {
        query: ''
    },
    stores: {
        test: {
            type: 'teststore',
            autoLoad: true,
            proxy: {
                type: 'ajax',
                url: 'names.json',
                reader: {
                    type: 'json',
                    rootProperty: 'data'
                }
            },

            remoteFilter: true,
            filters: {
                property: 'id',
                value: 1
            }
        },
        chained: {
            type: 'chained',

            autoLoad: true,
            source: '{test}'
        }
    }
});

Ext.define('App.controller.TestController', {
    extend: 'Ext.app.ViewController',
    alias: 'controller.testcontroller',

    doSearch: function (field) {
        var list = this.lookup('list'),
            store = list.getStore(),
            value = field.getValue();

        if (Ext.isEmpty(value)) {
            store.removeFilter('firstName')
        } else {
            store.filter([{
                property: 'firstName',
                value: value,
                operator: 'like'
            }])
        }
    }
});

Ext.define('App.dataview.TestList', {
    extend: 'Ext.dataview.List',
    xtype: 'testlist',

    viewModel: {
        type: 'test'
    },

    plugins: [{
        type: 'pullrefresh',
        mergeData: false
    }],

    emptyText: 'Name not found',
    bind: {
        store: '{chained}'
    },
    itemTpl: '<div class="contact">{id} <b>{firstName} {lastName}</b></div>'
});

Ext.define('App.MainView', {
    extend: 'Ext.Panel',
    controller: 'testcontroller',
    fullscreen: true,

    viewModel: {
        type: 'test'
    },

    items: [{
        xtype: 'searchfield',
        ui: 'solo',
        placeholder: 'Search names',
        listeners: {
            buffer: 500,
            change: 'doSearch'
        },
        bind: {
            value: '{query}'
        }
    }, {
        reference: 'list',
        xtype: 'testlist'
    }]

})

Ext.application({
    name: 'App',

    mainView: 'App.MainView'
});

names.json

var data = [{
    id: 1,
    firstName: 'Peter',
    lastName: 'Venkman'
}, {
    id: 2,
    firstName: 'Raymond',
    lastName: 'Stantz'
}, {
    id: 3,
    firstName: 'Egon',
    lastName: 'Spengler'
}, {
    id: 4,
    firstName: 'Winston',
    lastName: 'Zeddemore'
}]

var results = data.filter(function(record) {
    if (params.filter) {
        return record.id > params.filter[0].value
    }
})

return {
    "success": true,
    "data": results
}

App.override.dataview.pullrefresh.PullRefresh:

Ext.define('App.override.dataview.pullrefresh.PullRefresh', {
  override: 'Ext.dataview.pullrefresh.PullRefresh',

  privates: {
    fetchLatest: function() {
      const store = this.getStore().isChainedStore ? this.getStore().getSource() : this.getStore()
      store.fetch({
          page: 1,
          start: 0,
          callback: this.onLatestFetched,
          scope: this
      });
    },

    onLatestFetched: function(newRecords, operation, success) {
      var me = this,
          list = me.getList(),
          store = list.getStore().isChainedStore ? list.getStore().getSource() : list.getStore(),
          length, toInsert,
          oldRecords, newRecord, oldRecord, i;

      if (success) {
          if (me.getMergeData()) {
              oldRecords = store.getData();
              toInsert = [];
              length = newRecords.length;

              for (i = 0; i < length; i++) {
                  newRecord = newRecords[i];
                  oldRecord = oldRecords.getByKey(newRecord.getId());

                  if (oldRecord) {
                      oldRecord.set(newRecord.getData());
                  }
                  else {
                      toInsert.push(newRecord);
                  }
              }

              store.insert(0, toInsert);
          }
          else {
              store.loadRecords(newRecords);
          }

          me.setLastUpdated(new Date());
      }

      me.setState('loaded');
      list.fireEvent('latestfetched', me, toInsert || newRecords);

      if (me.getAutoSnapBack()) {
          me.snapBack(true);
      }
    }
  }
})

Thanks in advance


Solution

  • Since this post, instead of being a question, was a bug report with a possible solution, it has been posted to Ext JS 7.x Community Forums\Ext JS 7.x Bugs.

    The above solution, that overwrites the plugin where source store is needed, works if anyone comes across the same issue.