Поиск по этому блогу

понедельник, 12 сентября 2011 г.

ExtJs 3.1.0. Динамическое создание колонок

Фактически для динамического создания колонок мы должны передать метаданные и построить по ним модель колонок. Метаданные передаются JsonReader'у вместе с данными и начинаются со слова "metaData", они содержат параметры для конфигурации reader'а, полей и могут содержать произвольные данные.
При чтении наш JsonReader должен выделить метаданные, сконфигурировать себя, заполнить модель колонок и массив полей.
Массив полей он заполнит и без нашего участия, а вот модель колонок придется создавать ручками.


Согласно документации метаданные это объект в формате JSON, который содержит информацию о пакете с данными: 
  •         "root" - корень, с какого параметра начинать читать сами данные
  •         "totalProperty" - где описано количество записей в наборе
  •         "successProperty" - параметр, по которому reader поймет, что все OK
  •         "fields" - объект, который содержит описание полей
Кроме того, данные могут содержать произвольные свойства, объекты.
Полное описание метаданных в help'e, а выглядят метаданные примерно следующим образом:
{
// описываем метаданные
    metaData: {
        "idProperty": "id",
        "root": "rows",
        "totalProperty": "total"
        "successProperty": "success",
        "fields": [
            {"name": "name"},
            {"name": "job", "mapping": "occupation"}
        ],      
        // произвольные объекты, например, сюда мы можем отправить модель колонок
        "columns": [ модель колонок ]
    },
// эти свойства относятся к данным       
    "success": true, // Reader's configured successProperty   
    "results": 2000, // Reader's configured totalProperty   
    // сами данные
    "rows": [ // *Note: this must be an Array
        { "id": 1, "name": "Bill", "occupation": "Gardener" },
        { "id": 2, "name":  "Ben", "occupation": "Horticulturalist" }
    ]
}

Вопрос номер один - где хранить данные о колонках? Вариантов, как минимум, два. Во-первых, мы можем хранить данные о колонках в отдельном произвольном объекте "columns": [ модель колонок ] или отправлять всю информацию в том же объекте fields. По некоторому размышлению, я пришла к выводу, что для меня удобнее второй вариант.

С учетом сказанного, json данные должны выглядеть примерно так:
{
"metaData": {
"totalProperty": "total",
"successProperty": "success",
"root": "tablerow",
"idProperty": "ID",
"fields": [
// у поля ID определяем заголовок и ширину
{"name": "ID","type": "string", "mapping": "ID", "header": "PK", "width" : "80"}
// у поля tdoc - заголовок, ширину и параметры сортировки
{"name": "tdoc", "type": "string", "mapping": "TDOC", "header": "tdoc", "width" : "400", "sortable": "0"}
// у поля nametdoc не определяем ничего
{"name": "nametdoc", "type": "string", "mapping": "NAMETDOC"}
, "success": true,"total": 9,
"tablerow":[
  {"ID":1,"TDOC":1,"NAMETDOC":"name tdoc 1"}
, {"ID":2,"TDOC":2,"NAMETDOC":"name 2"}]
}

JsonReader прочитает наши метаданные, настроится, создаст массив полей. Все это действо происходит внутри класса:
Ext.extend(Ext.data.JsonReader, Ext.data.DataReader,
    ...
    readRecords: function(a){
        this.jsonData = a;
        if (a.metaData)...
    buildExtractors: function(){...

Каждый раз, когда приходит пакет с сервера, JsonReader пытается выделить метаданные (metaData), это означает, что изменить метаданные мы можем в любой момент, "на лету". Если в пакете обнаруживаются метаданные, то происходит переконфигурация хранилища (store), связанного с нашим reader'ом, и возбуждается событие metachange, при этом в качестве входных параметров передаются метаданные.

    metachange : ( Store this, Object meta )

!!! Событие metachange возбуждается только в JsonReader'e, другие типы хранилищ таким преимуществом не обладают.
!!! Переконфигурация хранилища (store) может привести к ошибкам, это связанно с тем, что некоторые поля, объявленные в reader'е могут отсутствовать, поэтому help от extjs настоятельно рекомендует объявлять JsonReader следующим образом:
   
 
var ReaderData= new Ext.data.JsonReader();

Теперь необходимо отловить событие metachange. Ставим слушателя (listeners) для нашего хранилища
var dataStore = new Ext.data.Store ({
     // ** конфигурация хранилища store
      ...
      //** PROXY *************
     url: 'model-tdoc.php',
     reader: ReaderData, // наш JsonReader
      writer: Writer,
      listeners: {         
        load : function(obj, records) {
            ...   
        } // eo load
        , metachange: function(obj, meta) {
            console.info('Пришли метаданные');       
            var m = {};
            // можем использовать объект meta, к-й передается в функцию
            m = meta;   
            // или обратиться к метаданным т.о.
            m = this.reader.meta;
            // смотрим что получили
            console.info(Ext.util.JSON.encode(m));       
            // можно вывести значение объекта totalProperty ...   
            console.info(m.totalProperty);           
            // цикл по массиву fields
            for (var i = 0; i < m.fields.length; i++ ) {               
                console.info('поле '+ m.fields[i].name
                + ' заголовок ' +  m.fields[i].header
                );               
            } // eo for
        } // eo metachange   
      } // eo listener
    }); // eo store
Пока мы написали небольшой экзерсис, дабы потренироваться в получении и обработке метаданных.

Следующий шаг - построить модель колонок ColumnModel и переконфигурировать наш grid.

Чтобы применить к гриду новую модель колонок необходимо воспользоваться методом
    reconfigure( Ext.data.Store store, Ext.grid.ColumnModel colModel ) : void
Этот метод позволяет "на лету" изменить свойства гриды для использования другого хранилища (store) и (или) другой модели колонок. Метод возбуждает событие  'reconfigure', связывает новые параметры с гридом и обновляет его.
В нашем случае, код слушателя (listener) события  metachange измениться следующим образом:
..., metachange: function(obj, meta) {   
            console.info('получили метаданные');                                       
            // функция построения модели колонок по метаданным
            cm = SetColModel(meta);       
            var grid = Ext.getCmp('grid');  // получить ссылку на grid
            // применить новые колонки к гриду
            grid.reconfigure(this, cm);
            } ...
Для того, чтобы это заработало необходимо также определить "пустую" модель колонок ...
var def_cm = new Ext.grid.ColumnModel({
        id: 'cm',       
        // свойства по умолчанию
        defaults: {
            ,filterable: false  // отключаем фильтрацию, иначе ругается              
        },
        columns: [] // пустой массив !!!
    });
... и связать grid с этой моделью колонок
var grid = new Ext.grid.EditorGridPanel({
        id: 'grid',       
        store: dataStore,  // хранилище   
        cm: def_cm, // пустая модель колонок
        ...      
    }); 
Примечание. В принципе, создание пустой модели колонок вовсе не обязательно, просто один из плагинов, который я подключила очень недоволен тем фактом, что колонок нет.
Теперь собственно функция, которая будет брать метаданные и строить по ним новую модель колонок
function SetColModel(obj, meta) {
// значения по умолчанию   
var defs = {           
              filterable: true
            , width: 120
        }   
 var cols = [];
// заполняем массив колонок
 Ext.each(meta.fields, function(fld, idx){

        cols[idx] = {
        // если заголовок не указан, будем использовать имя поля
        header: fld.header ? fld.header : fld.name
        , dataIndex: fld.name
        // если sortable=0, то false, в остальных случаях true 
        , sortable: parseInt(fld.sortable) == 0 ? false : true
        // если ширина не указана, берем из defs
        , width: parseInt(fld.width) ? parseInt(fld.width) : defs.width   
        ...
                }                       
            }); // eo each
   
    var cm = new Ext.grid.ColumnModel({
       ...     
        }); // eo cm   

    cm.setConfig(cols); // устанавливаем конфигурацию колонок   
    //console.info(cm);
    return cm;     
};

Тестовая функция php, которая отправляет данные и метаданные
<?php

$out = getRecordsWithMetadata();
//********************* отправляем РЕЗУЛЬТАТ ************************************************
echo $out;   

function getRecordsWithMetadata(){
// это метаданные !!! не забываем, никаких "левых" символов быть не должно, ака перенос строки и прочее
    $meta = '{'
. '"metaData": {totalProperty: "total", "successProperty": "success", "root": "tablerow","idProperty": "ID",'
.'"fields":[ '
.'{"name": "ID","type": "string", "mapping": "ID", "header": "PK", "width" : "80"} '
.',{"name": "tdoc", "type": "string", "mapping": "TDOC", "header": "tdoc", "width" : "400", "sortable": "0"} '
.',{"name": "nametdoc", "type": "string", "mapping": "NAMETDOC"} '
.'] '
.' }';
// а это данные
    $out =', "success": true,"total": 9, "tablerow":[ {"ID":1,"TDOC":1,"NAMETDOC":"name tdoc 1"} , {"ID":2,"TDOC":2,"NAMETDOC":"name 2"}]}';
// возвращаем результат
    return $meta . $out;
   
} // eo function getRecordsWithMetadata   
?>

Пишем новое расширение.
Задача. Создать новое расширение. Родителем выберем Ext.grid.GridPanel.
Напишем инициализацию, для того, чтобы иметь возможность переопределять в наследниках url и др.   
Перекроем метод onRender, где будем вызывать загрузку хранилища (store) и функцию динамического построения колонок.

// ***** динамический грид *******************************//
DynamicGrid = Ext.extend(Ext.grid.GridPanel, {   
    // применяем конфигурацию
    initComponent: function(){
        var config = {
            viewConfig: {
                forceFit: false
            },
            enableColLock: false,
            loadMask: true,
            border: true,
            // ... и другие
            ds: new Ext.data.Store({
                url: this.storeUrl, // url, который будет передан наследникам этого класса
                reader: new Ext.data.JsonReader() // пустой reader
            }),
            // модель колонок по умолчанию
            cm: new Ext.grid.ColumnModel({
                defaults: {
                    sortable: true //
                    , filterable: true
                , width: 100
            },
            columns: [] // пустые колонки
            })           
        };
    // применяем конфигурацию   
        Ext.apply(this, config);
        Ext.apply(this.initialConfig, config);
        DynamicGrid.superclass.initComponent.apply(this, arguments);
    },
    onRender: function(ct, position){
        // вызываем метод предка
        DynamicGrid.superclass.onRender.call(this, ct, position);              
       
        // обрабатываем событие on load для store
        this.store.on('load', function(){
            console.info('грузим store');           
            // !!! Здесь получаем новую модель колонок
            this.reconfigure(this.store, DynamicColumnModel(this.store));           
           
        }, this);
        // грузим данные в store
        this.store.load({ params: { start: 0, limit: 10} });               
    }
}); // eo dynamic grid

Ссылки по теме: