JavaScript, Инструменты, ТехнологииNested Grids с помощью ExtJS 3.0

Введение

Nested Grids Example

Суть проблемы, рассматриваемой в данной статье заключается в том, что Grid объекты библиотеки ExtJS не предназначены для использования в контексте вложенности. В общем случае, такая задача редко становится перед разработчиком. И все же, иногда, как, например, в моем случае, с ней приходится сталкиваться. Ниже я попытаюсь поделиться накопленным опытом, и, возможно, окажу тем самым кому-нибудь неоценимую помощь, на что искренне надеюсь :). Итак, в добрый путь...

Вложенные Grids или ColumnTree

Многие могут заметить, что большинство задач можно попросту решить методом использования ColumnTree, вместо того, чтобы пытаться реализовать вложенные «сетки». Да, если вас полностью устраивает такое решение — используйте именно его. Именно для этого и предназначены ColumnTrees. Но в ряде случаев довольно тяжело смириться с фактом, что вы лишаетесь всех тех полезных функций, которые нам предоставляют Grids, а именно: сортировки, Drag&Drop колонок, фильтры и т.д. и т.п. Если они действительно необходимы, то приходится задуматься о реализации возможности вкладывать одну сетку в другую. Ниже о том, как это сделать.

Проблемы использования RowExpander

На первый взгляд, решить проблему призван плагин RowExpander из библиотеки ux. Однако, не все так просто. Данный плагин спроектирован для возможности отобразить/скрыть произвольный HTML код в строке «сетки». В частности же, при попытке встроить в строку другой грид, сталкиваемся с проблемами в работе данного плагина. Выхода, по сути 2: написать свой плагин или несколько модифицировать существующий.

Реализацию своих плагинов я оставлю на совесть тех, у кого свободного времени много, я же выбрал путь модификации. В конце-концов, никто не запрещает назвать модифицированный плагин другим именем и использовать его как нечто новое и свое. На здоровье! :)

Основная непрятность связана с неверным отображением иконок и некоторым, связанным с этим, неверным поведением плагина RowExpander, который находится внутри «сетки», также использующей этот плагин. Суть идеи решения проблемы заключается в том, что каждая «сетка» вложенная в другую «сетку» должна использовать свои имена стилей для определения открытости/закрытости строки. Вооружившись данной идеей довольно просто реализовать динамическое создание идентичных стилей с разными именами, напрямую зависящих от идентификатора сетки. Сделать это довольно просто. Изменим немного конструктор плагина, добавив следующий код:

?View Code JAVASCRIPT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
if (!config.id) {
    config.id = Ext.id();
}
 
Ext.apply( this, config);
 
var css =
    '.x-' + this.id + '-grid3-row-collapsed .x-grid3-row-expander { background-position:0 0; }' +
    '.x-' + this.id + '-grid3-row-expanded .x-grid3-row-expander { background-position:-25px 0; }' +
    '.x-' + this.id + '-grid3-row-collapsed .x-grid3-row-body { display:none !important; }' +
    '.x-' + this.id + '-grid3-row-expanded .x-grid3-row-body { display:block !important; }'
;
 
Ext.util.CSS.createStyleSheet( css, Ext.id());
 
this.expanderClass     = 'x-grid3-row-expander';
this.rowExpandedClass  = 'x-' + this.id + '-grid3-row-expanded';
this.rowCollapsedClass = 'x-' + this.id + '-grid3-row-collapsed';

Необходимость определения свойств expanderClass, rowExpandedClass и rowCollapsedClass связана с тем, что теперь нам будет довольно легко оперировать их значениями внутри плагина, а нам еще предстоит внести некоторые изменения в его код.

Теперь у нас есть отдельные классы стилей для каждой уникальной сетки на нашей странице. Изменим методы, связанные с определением поведения плагина, чтобы они могли использовать предопределенные нами имена стилей:

Изменим метод render:

?View Code JAVASCRIPT
1
    return '<div class="' + this.expanderClass + '"> </div>';

Также изменим методы toggleRow:

?View Code JAVASCRIPT
1
    this[Ext.fly(row).hasClass( this.rowCollapsedClass) ? 'expandRow' : 'collapseRow'](row);

expandRow:

?View Code JAVASCRIPT
1
    Ext.fly( row).replaceClass( this.rowCollapsedClass, this.rowExpandedClass);

и collapseRow:

?View Code JAVASCRIPT
1
    Ext.fly( row).replaceClass( this.rowExpandedClass, this.rowCollapsedClass);

Все, на этом лечение описанной выше болезни плагина можно считать законченным.

Однако полное решение всех проблем с этим не приходит.

Ветки и листья дерева

Во первых, как и любое другое дерево, наше должно уметь правильным образом отражать «ветки» и «листья». Под этими понятиями я подразумеваю, что «ветка» может быть раскрыта (у нее есть потомки), а у листьев потомков нет. К сожалению, RowExpander в текущем состоянии не умеет отличать «листья» от «веток» и добавляет элементы управления открытием/закрытием во все строки «сетки». Для полноценной реализации дерева нам придется продолжить его менять.

Благо, решение не такое уж и сложное. Давайте остановимся на тезисе, что наш набор данных должен содержать признак того, является ли запись конечной («лист») или может содержать потомков («ветка»). Для этого достаточно определить в записи поле, например, с названием «is_leaf», принимающие логическое значение true или false (is_leaf = true — «лист», is_leaf = false — «ветка»).

В то же время мы не должны препятствовать плагину работать так, как он это делал и ранее. Т.е., мы будем сами определять, как и когда плагин должен работать в режиме дерева, по-умолчанию же, он будет вести себя так, как и раньше.

Для реализации подобного механизма определим публичные конфигурационные свойства actAsTree = false и treeLeafProperty = 'is_leaf'. Таким образом, при инициализации плагина можно будет указывать, должен ли плагин вести себя как дерево (фактически проверять являются ли строки «сетки» листьями дерева), а также самостоятельно задавать имя признака «листа» в записи.

В первую очередь, нам необходимо определить еще один стиль для отображения листа (по-сути, он должен просто скрыть элемент открытия/закрытия на элементах с типом is_leaf = true):

Добавим, в уже созданное определение стилей:

?View Code JAVASCRIPT
1
2
3
4
var css =
    ...
    + '.x-grid-expander-leaf .x-grid3-row-expander { background: none; }'
;

и определим свойство, которое бы мы могли использовать внутри плагина для данного имени класса стилей:

?View Code JAVASCRIPT
1
this.leafClass = 'x-grid-expander-leaf';

Осталось дело за немногим, нужно проставить данный класс соответствующим строкам и запретить действия по открытию/закрытию строк, помеченных как листья.

Для этого изменим метод getRowClass (инициализация строк):

?View Code JAVASCRIPT
1
2
3
4
5
var cssClass = this.state[record.id] ? this.rowExpandedClass : this.rowCollapsedClass;
if (this.actAsTree && record.get( this.treeLeafProperty)) {
    cssClass = this.leafClass;
}
return cssClass;

и добавим проверку в методы toggleRow, expandRow, collapseRow (запрещаем действия на листьях):

?View Code JAVASCRIPT
1
2
3
if (Ext.fly(row).hasClass( this.leafClass)) {
    return ;
}

Проблема обработки событий

Даже после всего этого, проблемы остаются. Поведение вложенных друг в друга «сеток» оказывается довольно-таки неадекватным. Вы встретите глюки при выборе строк, сортировке и т.п. Действия на строках потомков будут проецироваться на родительские «сетки». Это все довольно неприятно, но очень легко решаемо!

На каждом вновь создаваемом потомке просто нужно отключить всплывающие события. Для этого, внесем очередное изменение в RowExpander в метод onRender, который в свою очередь выполняется в момент рендеринга самой «сетки».

?View Code JAVASCRIPT
1
2
3
if (this.actAsTree) {
    grid.getEl().swallowEvent([ 'mouseover', 'mouseout', 'mousedown', 'click', 'dblclick' ]);
});

Проблема с утечками памяти

И еще не все проблемы решены :). Нам еще необходимо позаботиться об удалении всех компонентов связанных со схлопнутыми сетками. Нету смысла описывать детальные изменения, тем более, что в этой области все еще может измениться не один раз. Просто смотрите в код:

Полный код измененного плагина RowExpander: RowExpander.js
Живой пример вложенных сеток: ExtJS Nested Grids Example

Заключение

Невзирая на первичные трудности с построением вложенных сеток, и, казалось бы, столкнувшись с неприспособленностью «сеток» к вложенности, довольно несложным образом удалось добиться вполне приемлемого результата, что в очередной раз свидетельствует о гибкости и расширяемости библиотеки ExtJS.

Мне же лишь остается надеяться, что данная статья поможет кому-либо побороть трудности.

Удачи в девелопменте!

Хорошая статьяПлохая статья +8
   |    Опубликовано: Декабрь, 11 2009г.    |    Автор: Михаил Стадник

Комментарии (24) к статье “Nested Grids с помощью ExtJS 3.0”

Оставить комментарий