Обновление одной записи при редактировании ячеек Grid ExtJS 4.1

В целях оптимизации мне хотелось, чтобы при редактировании какой-нибудь ячейки грида (используя плагин CellEditing) данные с сервера получались не полностью, а только для одной редактируемой записи. Это снизило бы как количество передаваемых данных, так и время обработки запроса на сервере — получить данные для одной строки по идентификатору бывает на порядок быстрее, чем для всей страницы из 25 или 50 записей.

Справка на сенче показала мне, что эта проблема не реализована полностью в ExtJS, а гугл — что она мучает не только меня. Перепробовав все предложенные варианты, я понял, что в 4.1.3 не один из них не работает. Пришлось лезть в исходники и чуточку доделать.

Вообще вся идея основана на использовании метода store.load() с параметрами. Оказывается, можно передать объект с набором полей например так:

store.load({id: 123, addRecords: true, refreshOne:true});

Кстати, это наиболее популярное решение, использующее баг ExtJS и, насколько я понял, в версии 4.1.3 этот баг уже исправлен. При передаче параметра addRecords стор понимал, что не нужно трогать весь грид, а нужно только добавить новые данные. Но я на сервере получал переданный id и возвращал одну единственную запись. Это приводило к тому, что после выполнения метода в моем гриде редактируемая строчка оставалась неизменной, а вот в конец таблицы добавлялась запись, пришедшая с сервера.

Пришлось расширить мой store переопределенным методом, в котором я добавил обработку параметра refreshOne=true (для пущего контроля), а потом обновляющего все данные записи из ответа сервера. Так как свойства устанавливаются через метод set, необходим вызов после этого record.commit(), чтобы убрать пометки о «грязных» (не синхронизированных) данных. (Серым цветом отмечен неизменный код основного метода ExtJS)

store.loadRecords = function(records, options) {
    var me= this,i= 0,length = records.length,start = (options = options || {}).start,snapshot = me.snapshot;
    if (!options.addRecords) {delete me.snapshot;me.clearData(true);} 
    else if (snapshot) {snapshot.addAll(records);}
    /* Если задан recordIndex, стор должен обновить только одну запись */
    if (options.refreshOne && records.length == 1) {
        var editrecord = me.getById(parseInt(options.id));
        editrecord.set(records[0].data);
        //for (var fname in records[0].raw)
        //    editrecord.raw[fname] = records[0].raw[fname];
        editrecord.commit();
    } 
    else me.data.addAll(records);
 
    if (typeof start != 'undefined') {for (; i < length; i++) {records[i].index = start + i;records[i].join(me);}} 
    else {for (; i < length; i++) {records[i].join(me);}}
    me.suspendEvents();
    if (me.filterOnLoad && !me.remoteFilter) {me.filter();}
    if (me.sortOnLoad && !me.remoteSort) {me.sort();}
    me.resumeEvents();
    me.fireEvent('datachanged', me);
    me.fireEvent('refresh', me);
};

После этого вызывается метод refreshNode у представления грида, но если включена синхронизация, все работает и без этого.

Вы могли заметить проход по всем свойствам в record.raw, это сделано из-за того, что далеко не все данные у нас описаны как поля store. Довольно существенная их часть, используемая в рендерерах напрямую, не нужна в сторе и была удалена оттуда в целях оптимизации. Это действительно сократило на пару процентов время парсинга записей. Можно сделать то же самое, пробежав по всем свойствам record.data в цикле (и тогад метод commit не потребуется), но с точки зрения ООП мне это кажется не очень правильным.

Еще одна проблема, которая возникает при генерации ответа хэндлера, когда мы возвращаем всего одну запись: что же нам писать в rowsCount (свойсто totalCount у грида)? Мы же получаем из базы всего одну запись в целяз оптимизации и понятия не имеем сколько их там хранится на интерфейсе в гриде. Самое безболезненное решение, которое я нашел — в параметры запроса к хэндлеру я добавляю число записей, которое в данный момент имеет store:

storeResources.proxy.extraParams.totalCount = this.totalCount;

, а на сервере (только если мы запрашиваем всего одну запись по параметру id) просто возвращаю ее же с надеждой, что у нас больше ничего не поменялось:

int rowsCount;
int resourceID;
List<TResource_withResAttr> resources;
if (int.TryParse(context.Request["id"], out resourceID))
{
    resources = new List<TResource_withResAttr> { resourceBll.GetResourceByID(resourceID) };
    int.TryParse(context.Request["totalCount"], out rowsCount);
}
...

Если есть идеи получше, буду рад услышать.

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *