Draw tree

Материал из Eludia
Перейти к: навигация, поиск

Содержание

После таблиц (draw_table) и форм (draw_form), третьим основным элементом построения интерфейсов в Eludia является дерево (draw_tree). Здесь описывается функциональность, доступная при использовании $_SKIN'ов Classic, TurboMilk и IsUp. И ещё уточним, что в настоящей статье речь идёт именно о дереве как корневом элементе навигации (draw_tree), а не об элементе ввода типа tree, доступном на формах ввода draw_form.

Общие положения

Если таблицы и формы можно комбинировать на странице в произвольном порядке, то дерево занимает экран целиком и, более того, диктует его двухфреймовую структуру (по мотивам Windows Explorer):

  • собственно дерево в левом фрейме (_tree_iframe);
  • правый фрейм (_content_iframe) как цель для ссылок с элементов дерева.

Таким образом, определение типа экрана, использующее draw_tree, задаёт только "обвязку", а содержимое _content_iframe (обычно занимающее большую часть экрана), вообще говоря, зависит от данных, точнее, от ссылок с дерева. Правда, в большинстве случаев все ссылки ведут на экраны одинакового типа.

При описании дерева обязательно указывается опция selected_node: id записи, ссылку с которой необходимо активизировать при первой отрисовке. Обеспечить наличие записи с таким id в предоставленной выборке — ответственность программиста. Итак, первое обращение к экрану с draw_tree порождает минимум 2 HTTP-запроса к серверу.

Простой пример

Draw tree.gif

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

id
как всегда, уникальный номер записи;
parent
ссылка на родительскую запись или 0 для корневых записей;
label
наименование подразделения.

Допустим, приложение гарантирует наличие записи с id=1. Особо ограничительным это условие не является, так как, скажем "Руководство" имеется практически всегда.

Опишем экран с типом deps, отображающий дерево организационно-штатной структуры. Извлечение данных можно оформить лаконично:

sub select_deps {
				
	sql ({}, deps => []);

}

Теперь — презентационная часть:

sub draw_deps {

	my ($data) = @_;

	draw_tree (
		
		sub {

			draw_node ({
				id     => $i -> {id},
				parent => $i -> {parent},
				label  => $i -> {label},
				href   => "/?type=deps&id=$i->{id}",
			});
		
		},
		
		$data -> {deps},
	
		{
			selected_node => 1,
		},

	);

}

Как легко видеть, API полностью аналогичен draw_table. Как и там, можно определять контекстные меню для каждого узла. Это может пригодиться, например, для быстрого перехода к карточкам разного типа, связанных с одним подразделением:

			draw_node ({
				id     => $i -> {id},
				parent => $i -> {parent},
				label  => $i -> {label},
				href   => "/?type=deps&id=$i->{id}",
			}, [
				{
					icon  => 'edit',
					label => 'Структура',
					href   => "/?type=deps&id=$i->{id}",
				},
				{
					icon  => 'edit',
					label => 'Сотрудники',
					href   => "/?type=deps_hr_orders&id=$i->{id}",
				},
				{
					icon  => 'edit',
					label => 'Лимиты',
					href   => "/?type=deps_with_dmnd_arts&id=$i->{id}",
				},
			]);

Сортировка записей

В нашем примере записи выбираются в порядке по умолчанию: по возрастанию label (см. sql). Этот порядок наверняка отличается от того, в котором должны располагаться строки на экране, поскольку не учитывает parent. Поэтому draw_tree необходимо пересортировать предоставленные данные в иерархическом порядке (для этого служит функция tree_sort).

Однако может случиться так, что ваша выборка уже учитывает иерархию. Например, если в таблице deps есть поле ord со значениями вида "01.15.17" (и, разумеется, ни одно подразделение не имеет более 99 дочерних), а данные достаются так:

	sql ({}, deps => [
		[ORDER => 'ord'],
	]);

В этом случае вызов tree_sort будет излишним. Его следует блокировать, указав опцию in_order:

		{
			selected_node => 1,
			in_order      => 1,
		},

Подгрузка поддеревьев

В вышеописанном примере при каждом обращении к странице с сервера на клиент передаются данные и генерируется соответствующий HTML-код для всех подразделений. При этом в большинстве случаев интерес представляют лишь одна-две ветви, остальная часть данных может не использоваться никак. С ростом количества уровней и узлов объём трафика и время отрисовки в браузере ощутимо растёт, а потребности пользователя остаются на одном уровне, так что эффективность заметно падает. Грубо говоря, при десятках записей такое дерево вполне применимо, но для сотен уже не подходит.

Решением описанной проблемы является активное (динамическое) дерево, которое обрабатывает данные не для всей иерархии сразу, а отдельными этажами. При первом показе передаются и отрисовываются вершины, находящиеся рядом с selected_node, а также те, у которых parent = 0. В дальнейшем подгрузка данных осуществляется при первом раскрытии каждой вершины (по клику на +). Чтобы сделать дерево активным, достаточно указать одну опцию:

		{
			selected_node => 1,
			active        => 1,
			...
		},

С использованием $_SKIN'а Classic данный механизм, увы, в настоящее время не работает.

Оптимизация на стороне сервера

Вышеописанная операция ускоряет передачу и обработку данных на клиенте, однако серверу жизни, конечно, облегчить не может. Ведь, поскольку мы не переписали select_deps, данные по-прежнему извлекаются полностью (а дальше автоматически фильтруются и пересчитываются). Такой вариант можно рассматривать в основном для быстрого решения проблем на клиенте с сохранением совместимости.

Если же мы хотим оптимизировать приложение в целом, необходимо сделать 2 вещи:

  • научиться учитывать контекст текущего поддерева (параметр $_REQUEST {__parent});
  • сообщать количество дочерних вершин для выбранного подмножества (поле cnt_children), чтобы процедура могла разобраться, где отображать +, а где — нет.

Учёт контекста вершины

Итак, id раскрываемой вершины доступно в качестве значения $_REQUEST {__parent}, пустое значение соответствует первому показу. К сожалению, фильтр parent = 0 в этом случае не подходит, так как корневые узлы не имеют знаков "+" для раскрытия, так что приходится доставать и 0-й, и 1-й этаж дерева:

	my @filter = ();
	
	unless ($_REQUEST {__parent}) {		
		my @ids = sql_select_col ('SELECT id FROM deps WHERE parent = 0');
		push @filter, ['parent IN' => [-1, 0, @ids]];
	}
	
	my $data = sql ({}, deps => [
		[parent => $_REQUEST {__parent}],
		@filter,
	]);

Подсчёт дочерних узлов

Предлагаемый рецепт обеспечит получение необходимых данных с минимальной нагрузкой на сервер БД (если, конечно, таблица deps проиндексирована по полю parent):

	my ($ids, $idx) = ids ($data -> {deps});
	
	sql_select_loop (
	
		"SELECT parent, COUNT(*) AS cnt FROM deps WHERE parent IN ($ids) GROUP BY 1",
		
		sub {$idx -> {$i -> {parent}} -> {cnt_children} = $i -> {cnt}}
	
	);
	
	return $data;

Настройка

Осуществив все эти модификации, необходимо добиться того, чтобы draw_tree учитывала их при формировании DHTML. Для этого необходимо указать

		{
			selected_node => 1,
			active        => 2,
			...
		},
Персональные инструменты
Пространства имён

Варианты
Действия
Навигация
Разработчику
Администратору
Инструменты