After finding this absolute gem of a post on how to do dynamic columns I've been a bit stuck on getting paging working. So I'm hoping somoene can shed some light on what I'm doing wrong.

I send the total records via the normal method in the JSON. Here's my code - note that much of it is identical to that quoted in the above post. I'm hoping the someone maybe able to help?

Code:
Ext.QuickTips.init();
Ext.data.DynamicJsonReader = function(config){
  Ext.data.DynamicJsonReader.superclass.constructor.call(this, config, []);
};
Ext.extend(Ext.data.DynamicJsonReader, Ext.data.JsonReader, {
  getRecordType : function(data) {
    var i = 0, arr = [];
      for (var name in data[0]) { arr[i++] = name; } // is there a built-in to do this?
    this.recordType = Ext.data.Record.create(arr);
    return this.recordType;
  },
  readRecords : function(o){ // this is just the same as base class, with call to getRecordType injected
    this.jsonData = o;
    var s = this.meta;
      var sid = s.id;
           
      var totalRecords = o.total;
      if(s.totalProperty){
      var v = parseInt(eval("o." + s.totalProperty), 30);
      if(!isNaN(v)){
        totalRecords = v;
      }
    }
      var root = s.root ? eval("o." + s.root) : o;
      
      var recordType = this.getRecordType(root);
      var fields = recordType.prototype.fields;
      
    var records = [];
    for(var i = 0; i < root.length; i++){
        var n = root[i];
      var values = {};
      var id = (n[sid] !== undefined && n[sid] !== "" ? n[sid] : null);
      for(var j = 0, jlen = fields.length; j < jlen; j++){
        var f = fields.items[j];
        var map = f.mapping || f.name;
        var v = n[map] !== undefined ? n[map] : f.defaultValue;
        v = f.convert(v);
        values[f.name] = v;
      }
      var record = new recordType(values, id);
      record.json = n;
      records[records.length] = record;
    }
    
    return {
      records : records,
      totalRecords : totalRecords,
      totalProperty : totalRecords
    };
  }
});

Ext.grid.DynamicColumnModel = function(store){
  var cols = [];
  var recordType = store.recordType;
  var fields = recordType.prototype.fields;
     var fm = Ext.form, Ed = Ext.grid.GridEditor;
  
  for (var i = 0; i < fields.keys.length; i++)
  {
    var fieldName = fields.keys[i];
    var field = recordType.getField(fieldName);
    
    if(i == 0 || i == 3){
        cols[i] = {
                header: field.name, 
                dataIndex: field.name,
                tooltip: fieldName, 
                hidden:true};   
    }else if(i > 0 && i < 4){
        cols[i] = {header: field.name, tooltip: fieldName, dataIndex: field.name};
    }else{
        cols[i] = {header: field.name, dataIndex: field.name, tooltip: fieldName, editor: new Ed(new fm.TextField({allowBlank: false}))};
    }
  }
  Ext.grid.DynamicColumnModel.superclass.constructor.call(this, cols);
};
Ext.extend(Ext.grid.DynamicColumnModel, Ext.grid.ColumnModel, {});

function showGrid(container, url, productType)
{
    dsProductTypes = new Ext.data.Store({
        proxy: new Ext.data.HttpProxy({url: 'data/product_types.php?a=r'}),
        reader: new Ext.data.JsonReader({
            root: 'product_types',
            id: 'product_type_id'
            },[
                {name:'product_type_id', mapping:'product_type_id'},
                {name:'product_type_name', mapping:'product_type_name'}
            ]),
        remoteSort: false
    });

    dsProductTypes.load();
    
    var comboProductTypes = new Ext.form.ComboBox({
    store: dsProductTypes,
    displayField:'product_type_name',
    valueField:'product_type_id',
    typeAhead: true,
    mode: 'local',
    triggerAction: 'all',
    emptyText:'Select Product Type...',
    selectOnFocus:true
    });
        
    comboProductTypes.on('change', function(field, newVal, oldVal){
        showGrid(container, url, newVal);
    });
    
  // create the Data Store
  var ds = new Ext.data.Store({
      proxy: new Ext.data.HttpProxy({url: url}),
      reader: new Ext.data.DynamicJsonReader({root: 'attribute_values', totalProperty:'total'}),
      remoteSort: true
  });
  
  ds.on('load', function() {
    // Reset the Store's recordType
    ds.recordType = ds.reader.recordType;
    ds.fields = ds.recordType.prototype.fields;
    
    cm = new Ext.grid.DynamicColumnModel(ds);
      cm.defaultSortable = true;

    // Create the grid
    var grid = new Ext.grid.EditorGrid(container, {
       ds: ds,
       cm: cm,
       selModel: new Ext.grid.CellSelectionModel(),
       enableColLock:true,
       autoSizeColumns: true,
       autoSizeHeaders: false
    });
       
    // render it
    grid.render();
    var gridHead = grid.getView().getHeaderPanel(true);
    
        /**
         * Defining toolbar buttons.  In this case we have the add record and efresh table buttons.
         */
      var tb = new Ext.Toolbar(gridHead);
        
        filterButton = new Ext.Toolbar.MenuButton({
          icon: 'images/list-items.gif',
          cls: 'x-btn-text-icon',
            text: 'Filter',
            tooltip: 'Select filter by',
            menu: {items: [
                new Ext.menu.CheckItem({ text: 'Product Name', value: 'attribute_name', checked: false, group: 'filter', checkHandler: onItemCheck }),
                new Ext.menu.CheckItem({ text: 'Company', value: 'attribute_title', checked: false, group: 'filter', checkHandler: onItemCheck })
            ]},
            minWidth: 40
        });
        
        tb.add(comboProductTypes);
        tb.add(filterButton);
        
        // Create the filter field
        var filter = Ext.get(tb.addDom({ // add a DomHelper config to the toolbar and return a reference to it
            tag: 'input', 
            type: 'text', 
            size: '30', 
            value: '', 
            style: 'background: #F0F0F9;'
        }).el);
        
        filter.on('keypress', function(e){ //setup an onkeypress event handler
        if(e.getKey() == e.ENTER && this.getValue().length > 0){ //Only interested in the enter key
          ds.load({params:{start:0, limit:30}});
        }
      });
      
      filter.on('keyup', function(e) { //setup an onkeyup event handler
        if(e.getKey() == e.BACKSPACE && this.getValue().length === 0) { //listen for the BACKSPACE key and the field being empty
            ds.load({params:{start:0, limit:30}});
        }
      });
      
        function onItemCheck(item, checked){
            if(checked){
                filterButton.setText(item.text + ':');
            }
        }
        
     /************************************************************
      * create footer panel 
      *    actions and paging
      ************************************************************/ 
      var gridFoot = grid.getView().getFooterPanel(true);
    
      // add a paging toolbar to the grid's footer
      var paging = new Ext.PagingToolbar(gridFoot, ds, {
          pageSize: 30,
          displayInfo: true,
          displayMsg: '{2} results found. Currently viewing {0} - {1}',
          emptyMsg: "no result to display"
      });
      // --- end -- create foot panel
      
      ds.on('beforeload', function(){
          ds.baseParams = { // modify the baseParams setting for this request
            filterValue: filter.getValue(),// retrieve the value of the filter input and assign it to a property named filter 
            filterTxt: filterButton.getText(),
            productType: comboProductTypes.value
          }
        });      
  });
  
  ds.load({params:{offset:0, limit:30, type:productType}});
}

Ext.onReady(function(){
    url = 'data/attribute_values.php';
    container = 'edit-grid';
    showGrid(container, url);
});