vZ10
vZ10

Reputation: 2676

Using buffered store + infinite grid with dynamic data

The goal is to use buffered store for the dynamic data set. The workflow is below:

  1. Some data is already present on server.
  2. Clients uses buffered store & infinite grid to handle the data.
  3. When the application runs the store is loading and 'load' event scrolls the grid to the last message.
  4. Some records are added to server.
  5. Client gets a push notification and runs store reload. topic.store.load({addRecords: true});
  6. The load event runs and tries to scroll to the last message again but failes:

TypeError: offsetsTo is null e = Ext.fly(offsetsTo.el || offsetsTo, '_internal').getXY();

Seems that the grid view doesn't refreshes and doesn't show the added records, only the white spaces on their places. Any ideas how can I make the grid view refresh correctly?

The store initialization:

Ext.define('orm.data.Store', {
  extend: 'Ext.data.Store',
  requires: ['orm.data.writer.Writer'],
  constructor: function (config) {
    Ext.apply(this, config);
    this.proxy = Ext.merge(this.proxy, {
      type: 'rest',
      batchActions: true,
      reader: {
        type: 'json',
        root: 'rows'
      },
      writer: {
        type: 'orm'
      }
    });
    this.callParent(arguments);
  }
});

Ext.define('akma.chat.model.ChatMessage', {
  extend:'Ext.data.Model',
  fields:[
    { name:'id', type:'int', defaultValue : undefined },
    { name:'createDate', type:'date', dateFormat:'Y-m-d\\TH:i:s', defaultValue : undefined },
    { name:'creator', type:'User', isManyToOne : true, defaultValue : undefined },
    { name:'message', type:'string', defaultValue : undefined },
    { name:'nameFrom', type:'string', defaultValue : undefined },
    { name:'topic', type:'Topic', isManyToOne : true, defaultValue : undefined }
  ],
  idProperty: 'id'
});



Ext.define('akma.chat.store.ChatMessages', {
   extend: 'orm.data.Store',
   requires: ['orm.data.Store'],
   alias: 'store.akma.chat.store.ChatMessages',
   storeId: 'ChatMessages',
   model: 'akma.chat.model.ChatMessage',
   proxy: {
   url:  'http://localhost:8080/chat/services/entities/chatmessage'
  }
});

var store = Ext.create('akma.chat.store.ChatMessages', {
  buffered: true,
  pageSize: 10,
  trailingBufferZone: 5,
  leadingBufferZone: 5,
  purgePageCount: 0,
  scrollToLoadBuffer: 10,
  autoLoad: false,
  sorters: [
    {
      property: 'id',
      direction: 'ASC'
    }
  ]
});

Grid initialization:

Ext.define('akma.chat.view.TopicGrid', {
  alias: 'widget.akma.chat.view.TopicGrid',
  extend: 'akma.chat.view.grid.DefaultChatMessageGrid',
  requires: ['akma.chat.Chat', 'akma.UIUtils', 'Ext.grid.plugin.BufferedRenderer'],
  features: [],
  hasPagingBar: false,
  height: 500,
  loadedMsg: 0,
  currentPage: 0,
  oldId: undefined,
  forceFit: true,
  itemId: 'topicGrid',
  selModel: {
    pruneRemoved: false
  },
  multiSelect: true,
  viewConfig: {
    trackOver: false
  },
  plugins: [{
    ptype: 'bufferedrenderer',
    pluginId: 'bufferedrenderer',
    variableRowHeight: true,
    trailingBufferZone: 5,
    leadingBufferZone: 5,
    scrollToLoadBuffer: 10
  }],
  tbar: [{
    text: 'unmask',
    handler: function(){
      this.up('#topicGrid').getView().loadMask.hide();
    }
  }],

  constructor: function (config) {

    this.topicId = config.topicId;
    this.store = akma.chat.Chat.getMessageStoreInstance(this.topicId);
    this.topic = akma.chat.Chat.getTopic(this.topicId);

    var topicPanel = this;

    this.store.on('load', function (store, records) {
      var loadedMsg = store.getTotalCount();
      var pageSize = store.pageSize;
      store.currentPage = Math.ceil(loadedMsg/pageSize);
        if (records && records.length > 0) {
          var newId = records[0].data.id;
          if (topicPanel.oldId) {
            var element;
            for (var i = topicPanel.oldId; i < newId; i++) {
              element = Ext.get(i + '');
              topicPanel.blinkMessage(element);
            }
          }
          topicPanel.oldId = records[records.length-1].data.id;
          var view = topicPanel.getView();
          view.refresh();
          topicPanel.getPlugin('bufferedrenderer').scrollTo(store.getTotalCount()-1);
        }
    });

    this.callParent(arguments);
    this.on('afterrender', function (grid) {
      grid.getStore().load();
    });
    var me = this;
    akma.UIUtils.onPasteArray.push(function (e, it) {
      if(e.clipboardData){
        var items = e.clipboardData.items;
        for (var i = 0; i < items.length; ++i) {
          if (items[i].kind == 'file' && items[i].type.indexOf('image/') !== -1) {
            var blob = items[i].getAsFile();
            akma.chat.Chat.upload(blob, function (event) {
              var response = Ext.JSON.decode(event.target.responseText);
              var fileId = response.rows[0].id;
              me.sendMessage('<img src="/chat/services/file?id=' + fileId + '" />');
            })
          }
        }
      }
    });
    akma.UIUtils.addOnPasteListener();
  },
  sendMessage: function(message){
    if(message){
      var topicGrid = this;
      Ext.Ajax.request({
        method: 'POST',
        url: topicGrid.store.proxy.url,
        params:{
          rows: Ext.encode([{"message":message,"topic":{"id":topicGrid.topicId}}])
        }
      });
    }
  },
  blinkMessage: function (messageElement) {
    if (messageElement) {
      var blinking = setInterval(function () {
        messageElement.removeCls('red');
        messageElement.addCls('yellow');
        setTimeout(function () {
          messageElement.addCls('red');
          messageElement.removeCls('yellow');
        }, 250)

      }, 500);
      setTimeout(function () {
        clearInterval(blinking);
        messageElement.addCls('red');
        messageElement.removeCls('yellow');
      }, this.showInterval ? this.showInterval : 3000)
    }
  },
  columns: [        {
      dataIndex: 'message',
      text: 'Message',
      renderer: function (value, p, record) {
        var firstSpan = "<span id='" + record.data.id + "'>";
        var creator = record.data.creator;
        return Ext.String.format('<div style="white-space:normal !important;">{3}{1} : {0}{4}</div>',
            value,
            creator ? '<span style="color: #' + creator.chatColor + ';">' +     creator.username + '</span>' : 'N/A',
            record.data.id,
            firstSpan,
            '</span>'
        );
      }
    }
  ]
});

upd: Seems that the problem is not in View. The bufferedrenderer plugin ties to scroll to the record. It runs a callback function:

       callback: function(range, start, end) {

            me.renderRange(start, end, true);

            targetRec = store.data.getRange(recordIdx, recordIdx)[0];

.....

 store.data.getRange(recordIdx, recordIdx)[0] 

tries to get the last record in the store. ....

 Ext.Array.push(result, Ext.Array.slice(me.getPage(pageNumber), sliceBegin, sliceEnd));

getPage returns all records of the given page, but the last record is missing i.e. the store was not updated perfectly.

Any ideas how to fix?

Upvotes: 0

Views: 8491

Answers (2)

Eugene Sodin
Eugene Sodin

Reputation: 186

The problem is that store.load() doesn't fill up store PageMap with the new data. The simplest fix is using store.reload() instead.

Upvotes: 4

Christoph
Christoph

Reputation: 1023

Maybe you are to early when listening to the load event. I am doing roughly the same in my application (not scrolling to the end, but to some arbitrary record after load). I do the view-refresh and bufferedrender-scrollTo in the callback of the store.load().

Given your code this would look like:

this.on('afterrender', function (grid) {
      var store = grid.getStore();
      store.load({
          callback: function {
              // snip
              var view = topicPanel.getView();
              view.refresh();
              topicPanel.getPlugin('bufferedrenderer').scrollTo(store.getTotalCount()-1);
           }
      });
});

Upvotes: 0

Related Questions