Ext JS: TabPanel слишком быстро отображает бесконечную сетку

Допустим, у меня есть TabPanel, к которому добавлены два компонента. Каждый компонент содержит бесконечную сетку. Каждая бесконечная сеть загружает свои данные из вызова службы, и каждый набор данных содержит 2000 записей. После того, как компоненты добавлены в TabPanel, мы устанавливаем каждый из них активной вкладкой, используя setActiveTab. Сначала мы устанавливаем вторую вкладку в качестве активной, а затем устанавливаем первую вкладку. Когда страница загружается, выбирается первая вкладка, как мы и ожидали.

При взгляде на первую вкладку все выглядит нормально... мы можем бесконечно прокручивать, сортировать, скрывать столбцы и т.д. Однако, если мы переключаемся на вторую вкладку, мы видим, что она частично загружена, и мы не можем прокручивать дальше x записи, которые были загружены, скрывают столбцы, сортируют и т. д. Это похоже на то, как если бы использование setActiveTab было немного преждевременным с рендерингом сетки... как если бы хранилище не было полностью загружено, но вкладка все равно была визуализирована. (это то, что я предполагаю, что проблема)

У меня есть код, но для его воспроизведения требуется небольшая работа с вашей стороны (потому что вам нужно обратиться в службу поддержки). Я использую CompoundJS в приложении Node.js, поэтому мне было очень легко создать тестовый пример. Если у вас есть доступ к базе данных и вы можете быстро позвонить в службу поддержки, просто измените мой код Ext JS, но если вы хотите использовать Node.js, вы можете попробовать следующее:

Ext JS 4.2.1

Ext.onReady(function() {
  var tabPanel = Ext.create('Ext.tab.Panel', {
    width: 400,
    height: 400,
    renderTo: Ext.getBody()
  });

  Ext.define('myGrid', {
    extend: 'Ext.grid.Panel',
    constructor: function(config) {
      this.columns = config.columns;
      this.store = Ext.create('Ext.data.Store', {
        fields: config.fields,
        buffered: true,
        leadingBufferZone: 20,
        pageSize: 50,
        proxy: {
          type: 'ajax',
          url: '/getData?id=' + config.id,
          reader: {
            totalProperty: 'totalCount',
            type: 'json',
            root: 'root'
          }
        },
        autoLoad: true
      });
      this.id = config.id;
      this.callParent();
    }
  });

  var grid1 = Ext.create('myGrid', {
    id: 'blah',
    columns: [{
      text: 'one',
      dataIndex: 'one'
    }, {
      text: 'two',
      dataIndex: 'two'
    }, {
      text: 'three',
      dataIndex: 'three'
    }],
    fields: ['one', 'two', 'three'],
    title: 'grid1'
  });

  var grid2 = Ext.create('myGrid', {
    id: 'bleh',
    columns: [{
      text: 'one',
      dataIndex: 'one'
    }, {
      text: 'two',
      dataIndex: 'two'
    }, {
      text: 'three',
      dataIndex: 'three'
    }],
    fields: ['one', 'two', 'three'],
    title: 'grid2'
  });

  var c1 = [];
  c1.items = [grid1];
  c1.title = "BLAH";
  c1.layout = 'fit';
  var c2 = [];
  c2.items = [grid2];
  c2.title = "BLEH";
  c2.layout = "fit";
  tabPanel.add([c1, c2]);
  tabPanel.setActiveTab(1);
  tabPanel.setActiveTab(0);
});

Код Node.js

compound init test && cd test
npm install
compound g s testcontroller

Замените app/controllers/testcontrollers_controller.js на:

load('application');

action('getData', function(data) {
  var query = data.req.query;
  var id = query.id;
  var page = query.page;
  var pushObj;
  if (id === 'blah') {
    pushObj = {
      one: 'bye',
      two: 'goodbye',
      three: 'auf wiedersehen'
    };
  }
  else {
    pushObj = {
      one: 'hi',
      two: 'hello',
      three: 'guten tag'
    };
  }
  var obj = [];
  for (var i = 0; i < 50; i++) {
    obj.push(pushObj);
  }
  send({
    totalCount: 2000,
    root: obj,
    page: page
  });
});

В config/routes.js удалите строку map.resources testcontroller и добавьте следующее:

map.get('getData', 'testcontrollers#getData');

В public/index.html сделайте его общим HTML-файлом и добавьте свои ссылки на Ext JS и мой приведенный выше код Ext JS.

Теперь, когда вы все это сделали, вы сможете воспроизвести мою проблему. Первая вкладка откроется и будет работать нормально, но вторая вкладка загружает только первое количество записей x и не работает должным образом. Я считаю, что это связано с тем, что хранилище не загружается полностью при запуске setActiveTab, что делает загрузку рендеринга беспристрастным хранилищем.

Я хочу знать, как мне заставить это работать правильно? Я пытался дождаться загрузки магазина, а затем добавить его на вкладку, но это все еще дает мне противоречивые результаты, а также пытался дождаться, пока сетка прекратит рендеринг, но все же я получаю противоречивые результаты... противоречиво это означает, что иногда сетка загружается полностью, и вкладка в порядке, но в других случаях я получаю невозможно прочитать свойство 'length' неопределенного в ext-all-dev.js:135,786.. ., из-за чего кажется, что хранилище не полностью загружено, так как эта строка содержит ссылку на records.length.

Если у кого-то есть идеи, буду рад их услышать! Кросс-пост с сайта форумы Sencha.

РЕДАКТИРОВАТЬ: благодаря @rixo я смог воспроизвести проблему в этом примере. Если вы включите Firebug, вы увидите ошибку о длине свойства undefined, как я сказал выше.


person incutonez    schedule 17.12.2013    source источник
comment
Пробовали ли вы вручную обновлять сетку после того, как все будет отрисовано, чтобы увидеть, загружаются ли данные полностью? Вы можете легко добавить кнопку на панель инструментов в сетке, которая перезагружает данные сетки при нажатии на нее, чтобы проверить это.   -  person forgivenson    schedule 17.12.2013
comment
Хорошая идея. Я добавил кнопку, которая захватывает сетку и выполняет doLayout, а также пытался обновить контейнер сетки, но без кубиков.   -  person incutonez    schedule 17.12.2013
comment
Собственная скрипка Sencha теперь довольно мощная, с ней проще имитировать удаленные вызовы, чем на стандартной скрипке. Я воспроизвел вашу проблему здесь. К сожалению, iframe очень затрудняет отладку такой тонкой ошибки...   -  person rixo    schedule 17.12.2013
comment
Ваш сервер учитывает параметры start и limit?   -  person rixo    schedule 17.12.2013
comment
@rixo: этого нет в моем фиктивном коде выше, но да, мой сервер учитывает start и limit. И вау, спасибо, что указали на скрипку Сенчи... Я не знал, что она существует. Я определенно вижу свою проблему, когда это загружается! На второй вкладке нет всех данных...   -  person incutonez    schedule 17.12.2013
comment
Хорошо, обновил сообщение с примером, который не работает и дает мне ошибку records.length. Возможно, вам придется запустить его пару раз, чтобы увидеть проблему.   -  person incutonez    schedule 17.12.2013


Ответы (1)


Я отследил проблему до плагина, кэширующего неправильные значения размера, когда он скрыт.

Вот переопределение, которое исправит это поведение:

/**
 * Prevents BufferedRenderer plugin to break when buffered views are
 * rendered or refreshed while hidden, like in a card layout.
 *
 * Tested with Ext 4.2.1
 */
Ext.define('Ext.ux.Ext.grid.plugin.BufferedRenderer.HiddenRenderingSupport', {
    override: 'Ext.grid.plugin.BufferedRenderer'

    /**
     * Refreshes the view and row size caches if they have a value of 0
     * (meaning they have probably been cached when the view was not visible).
     */
    ,onViewResize: function() {
        if (this.rowHeight === 0) {
            if (this.view.body.getHeight() > 0) {
                this.view.refresh();
            }
        } else {
            this.callParent(arguments);
        }
    }
});

При этом я советую не использовать это и излишне раздувать вашу кодовую базу.

Вы должны организовать свой код таким образом, чтобы избежать этой ситуации. Проблема создается тем фактом, что вы вызываете setActiveTab несколько раз в одном и том же кадре выполнения. Если вы избежите этого, Ext самостоятельно справится с ситуацией.

Как правило, вы должны попытаться сделать свой код Ext более декларативным... Архитектура Ext скорее предполагает, что вы будете создавать большие объекты конфигурации для создания вашего компонента сразу (с возможностью атомарных обновлений DOM и т. д.) и использовать методы, которые будут обновлять состояние только позже, чтобы управлять поведением компонентов.

В вашем конкретном случае вы должны использовать activeTab, чтобы установить начальную вкладку, и вызывать setActiveTab только позже, когда вам действительно нужно изменить вкладку. Это избавит от ненужной обработки при инициализации, а также решит проблему с рендерингом...

Вот как должен выглядеть ваш код:

Ext.create('Ext.tab.Panel', {
    renderTo: Ext.getBody(),
    width: 400,
    height: 400,

    // Use this option to set the initial tab.        
    activeTab: 2,

    // You don't need to overnest your grids into another Panel, you can add
    // them directly as children of the tab panel.
    items: [
        // Ideally, you should give an xtype to your class, and add this to
        // avoid instantiating the object yourself... Thus giving Ext more
        // control on the rendering process, etc.
        Ext.create('myGrid', {
            id: 'blah',
            title: 'Bleh',
            columns: [{
                text: 'one',
                dataIndex: 'one'
            }, {
                text: 'two',
                dataIndex: 'two'
            }, {
                text: 'three',
                dataIndex: 'three'
            }],
            fields: ['one', 'two', 'three'],
            title: 'grid1'
        }),
        Ext.create('myGrid', {
            id: 'bleh',
            title: 'Bleh',
            columns: [{
                text: 'one',
                dataIndex: 'one'
            }, {
                text: 'two',
                dataIndex: 'two'
            }, {
                text: 'three',
                dataIndex: 'three'
            }],
            fields: ['one', 'two', 'three'],
            title: 'grid2'
        })
    ]
});

Как видите, дочерние элементы панели вкладок устанавливаются во время создания, что позволяет избежать бесполезной обработки методом add. Точно так же опция activeTab позволяет избежать инициализации панели в заданном состоянии и сразу изменить ее... Позже (в другом кадре выполнения, например, в обработчике кнопки) вы можете использовать setActiveTab, не вызывая ошибку, продемонстрированную в исходном примере. .

person rixo    schedule 18.12.2013
comment
Что касается того, что вы сказали о моем коде. То, что мы (член моей команды и я) делаем, идет вразрез с классическим программированием на Ext JS. Мы используем шаблон модульного проектирования, что делает наш код очень динамичным. Что делает фактический код, так это проверяет localStorage, чтобы увидеть, есть ли у пользователя какие-либо вкладки, сохраненные как открытые... если они есть, создайте эту вкладку (с каким бы классом она ни была) и повторно визуализируйте ее. Теперь причина, по которой используется setActiveTab, заключается в том, что мы просто повторно используем наш код, когда пользователь нажимает на наше представление навигации, чтобы открыть одну вкладку. - person incutonez; 18.12.2013
comment
Хорошо, у меня были некоторые проблемы с тем, чтобы это работало, но теперь, похоже, оно работает как шарм. Большое спасибо за всю помощь! Вы действительно сделали все возможное, чтобы помочь мне, и я ценю это. Теперь главный вопрос, который у меня есть, это сообщить об этом как об ошибке Sencha? - person incutonez; 18.12.2013
comment
Запустил ветка ошибок на форумах Sencha. - person incutonez; 18.12.2013
comment
Это интересно... для одной из моих вкладок я использую карту, созданную с помощью Google Maps. Теперь, если эта вкладка с картой скрыта с самого начала (аналогично нашей проблеме здесь), только часть карты визуализируется и, следовательно, может использоваться, так что это говорит мне о том, что проблема под рукой не решена и более фундаментальнее, чем сетка ... кажется, проблема с сокрытием вкладки. - person incutonez; 19.12.2013