Pull to refresh

Nested Grids с помощью ExtJS 3.0

Reading time 6 min
Views 3.1K

Введение



image

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



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



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

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



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

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

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

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';


* This source code was highlighted with Source Code Highlighter.


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

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

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

return '<div class="' + this.expanderClass + '"> </div>';

* This source code was highlighted with Source Code Highlighter.


Также изменим методы toggleRow:
this[Ext.fly(row).hasClass( this.rowCollapsedClass) ? 'expandRow' : 'collapseRow'](row);

* This source code was highlighted with Source Code Highlighter.


expandRow:
Ext.fly( row).replaceClass( this.rowCollapsedClass, this.rowExpandedClass);

* This source code was highlighted with Source Code Highlighter.


и collapseRow:
Ext.fly( row).replaceClass( this.rowExpandedClass, this.rowCollapsedClass);

* This source code was highlighted with Source Code Highlighter.


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

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

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



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

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

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

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

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

Добавим, в уже созданное определение стилей:
var css =
  ...
  + '.x-grid-expander-leaf .x-grid3-row-expander { background: none; }'
;


* This source code was highlighted with Source Code Highlighter.


и определим свойство, которое бы мы могли использовать внутри плагина для данного имени класса стилей:
this.leafClass = 'x-grid-expander-leaf';

* This source code was highlighted with Source Code Highlighter.


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

Для этого изменим метод getRowClass (инициализация строк):
var cssClass = this.state[record.id] ? this.rowExpandedClass : this.rowCollapsedClass;
if (this.actAsTree && record.get( this.treeLeafProperty)) {
  cssClass = this.leafClass;
}
return cssClass;


* This source code was highlighted with Source Code Highlighter.


и добавим проверку в методы toggleRow, expandRow, collapseRow (запрещаем действия на листьях):
if (Ext.fly(row).hasClass( this.leafClass)) {
  return ;
}


* This source code was highlighted with Source Code Highlighter.


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



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

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

if (this.actAsTree) {
  grid.getEl().swallowEvent([ 'mouseover', 'mouseout', 'mousedown', 'click', 'dblclick' ]);
});


* This source code was highlighted with Source Code Highlighter.


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



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

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

Заключение



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

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

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

P.S. Это кросс-постинг оригинальной статьи "Nested Grids с помощью ExtJS 3.0", размещенной на моем личном блоге.
Tags:
Hubs:
+8
Comments 23
Comments Comments 23

Articles