Введение
В предыдущих статьях, мы рассмотрели теорию хранения и управления древовидных структур данных, а так же реализовали Perl модуль для облегчения управления ими. Теперь напишем небольшой скрипт упраления (администрирования). Идея скрипта проста - требуется легко и непринужденно, с помощью скрипта, управлять деревом каталогов.
Возьмем стандартную таблицу в которой будет хранится наше дерево каталогов, в ней мы будем хранить только одно дерево, реализацию принципа работы, при котором количество деревьев неограничено, я рассмотрю в следующих статьях. Итак, наша таблица:
CREATE TABLE my_tree ( id INT(11) NOT NULL AUTO_INCREMENT, left_key INT(11) NOT NULL DEFAULT 0, right_key INT(11) NOT NULL DEFAULT 0, level INT(11) NOT NULL DEFAULT 1, name VARCHAR(150) NOT NULL, PRIMARY KEY id (id), INDEX left_key (left_key, right_key, level) );
Конечно, количество дополнительных полей (одно из них - name) может быть неограничено.
Концепция
Для начала определим, какие функции должен выполнять наш скрипт:
- создание узла;
- редактирование узла (с возможностью изменения подчиненности);
- удаление узла;
- перемещение узла на уровень вверх;
- перемещение узла на уровень вниз;
- перемещение узла на порядок вверх (в пределах подчиненности);
- перемещение узла на порядок вниз (в пределах подчиеннности);
Ограничение доступа - вообще не учитывал, то есть авторизация скрипта, проверка доступа - просто отсутсвуют. Оставлю это на Вашей совести. А вообще, проще всего, просто запаролировать директирию скрипта .htaccess и все...
HTML шаблон*, я все-таки вынес из скрипта - терпеть не могу править HTML в скрипте, и Вам того не советую. Шаблон состоит из трех частей:
- верхняя часть (header.html) - заголовки и прочая до момента вывода списка категорий (начало таблицы);
- строка списка категорий (row.html) - одна строка таблицы списка категорий;
- нижняя часть (footer.html) - конец таблицы вывода списка категорий, форма создания, редактирования категории;
* Эта структура шаблонов была придумана "на ходу", поэтому не будем заострять внимание на её правильности, не это важно.
HTML-код шаблонов:
header.html
<html><head> <title>Скрипт управления деревом каталогов</title> <meta http-equiv="Content-Type" content="text/html; charset=windows-1251"> <script language="javascript"> function EditCat (IdCat, NameCat, ParentCat) { document.getElementById('TitleForm').innerHTML = 'ИЗМЕНИТЬ КАТЕГОРИЮ'; document.getElementById('buttonForm').value = 'Изменить'; document.FormCategory.id.value = IdCat; document.FormCategory.name.value = NameCat; document.FormCategory.parent.options[ParentCat].selected = true; document.FormCategory.doing.value = 'edit'; } function ClearFormEdit () { document.getElementById('TitleForm').innerHTML = 'ДОБАВИТЬ КАТЕГОРИЮ'; document.getElementById('buttonForm').value = 'Добавить'; document.FormCategory.id.value = 'xx'; document.FormCategory.name.value = 'Новая категория'; document.FormCategory.doing.value = 'new'; } </script> </head> <body> <h1>Скрипт управления деревом каталогов</h1> <h2>Список категорий фирм</h2> <table width="100%" border="0" cellpadding="0" cellspacing="0">
row.html
<tr> <td>[$prefix$] [$name$]</td> <td width="80"> <a href="#form" onClick="javascript: EditCat ('[$id$]','[$name$]','[$par$]');"> изменить </a> </td> <td width="80"><a href="?ac=[$ac$]&doing=delete&id=[$id$]">удалить</a></td> <td width="80"><a href="?ac=[$ac$]&doing=level_up&id=[$id$]">влево</a></td> <td width="80"><a href="?ac=[$ac$]&doing=level_down&id=[$id$]">вправо</a></td> <td width="80"><a href="?ac=[$ac$]&doing=order_up&id=[$id$]">вверх</a></td> <td width="80"><a href="?ac=[$ac$]&doing=order_down&id=[$id$]">вниз</a></td> </tr>
footer.html
</table> <a name="form"></a> <table align="center" width="95%" border="0" cellpadding="0" cellspacing="0"> <form action="?" method="post" name="FormCategory"> <tr><td colspan="2"><h1 id="TitleForm">ДОБАВИТЬ КАТЕГОРИЮ</h1></td></tr> <tr> <td>Название категории</td> <td><input type="text" value="Новая категория" name="name" size="60"></td> </tr> <tr> <td>Подчинение категории</td> <td><select name="parent"> <option value="root">-- Без подчинения --</option> [$list_select$] </select></td> </tr> <tr> <td colspan="2"> <input type="hidden" name="id" value="xx"> <input type="hidden" name="doing" value="new"> <input type="hidden" name="ac" value="[$ac$]"> </td> </tr> <tr> <td colspan="2" align="center"> <input type="submit" name="buttonForm" id="buttonForm" value="Добавить"> <input type="reset" value="Вернуть" onClick="javascript: ClearFormEdit ();"> </td> </tr> </form> </table> </body></html>
Вот так, если эти три файла сложить в один, как есть, то получится одна страница с таблицей в центре состоящей из одной строки.
Теперь поясню отдельные моменты:
- В первом файле включен JavaSript, так как форма редактирования и создания одна, то при нажатии на ссылку "изменить" нужно внести соответствующие данные в форму. По кнопке "Вернуть" формы, требуется очистить форму от данных редактируемого узла;
- текст заключенный в квадратные скобки и знак доллара ([$текст$]), то что будет динамически заменяться нашим скриптом, где:
- [$id$] - идентификатор узла;
- [$name$] - поле name узла, или имя узла;
- [$prefix$] - отступ на который смещается имя узла (зависит от уровня узла);
- [$par$] - порядковый номер родительского узла в select формы редактирования (не путать с id родительского узла!);
- [$ac$] - случайный набор символов, я буду использовать текущее время (против кеширования страниц);
Код скрипта
Для начала определим где какие файлы у нас будут лежать:
- cgi-bin/admin_tree/
- lib/
- template/
- header.html
- row.html
- footer.html
- admin.pl
Что за файл NestedSets.pm, я думаю, объяснять не нужно (это модуль описанный в предыдущих статьях), с .html файлами - тоже понятно, остался только один файл - admin.pl, его мы как раз и опишем. Итак, код скрипта:
#!/usr/bin/perl # Подключение основных модулей use strict; use CGI; use DBI; use vars '$query', '$dbh', # объект подключения в базе данных '$nested', # объект работы с деревои NestedSets '%user_vars'; # Глобальные пользовательские переменные
# Подключаем модуль для работы с деревои NestedSets use lib 'lib/'; use Global::NestedSets; # Указываем переменные пользовательские переменные $user_vars{'table'} = 'my_tree'; # Имя таблицы БД # Коннект к базе $dbh = 'DBI'->connect('DBI:mysql:database=mybase:host=localhost:port=3306', 'user', 'password') || die $DBI::errstr; # Выбираем переданные данные $query = new CGI; $user_vars{'id'} = $query->param('id') || undef; # Идентификатор узла $user_vars{'doing'} = $query->param('doing') || undef; # Производимое действие # Определяем объект Global::NestedSets $nested = new Global::NestedSets {DBI=>$dbh, table=>$user_vars{'table'}};
# Если производится какое-либо действие if ($user_vars{'doing'}) { # Действие - поднять узел на уровень вверх if ($user_vars{'doing'} eq 'level_up') { $nested->set_unit_level(unit=>$user_vars{'id'}, move=>'up'); # Действие - опустить узел на уровень вниз } elsif ($user_vars{'doing'} eq 'level_down') { $nested->set_unit_level(unit=>$user_vars{'id'}, move=>'down'); # Действие - поднять узел на порядок вверх } elsif ($user_vars{'doing'} eq 'order_up') { $nested->set_unit_order(unit=>$user_vars{'id'}, move=>'up'); # Действие - опустить узел на порядок вниз } elsif ($user_vars{'doing'} eq 'order_down') { $nested->set_unit_order(unit=>$user_vars{'id'}, move=>'down'); # Действие - удалить узел } elsif ($user_vars{'doing'} eq 'delete') { $nested->delete_unit(unit=>$user_vars{'id'}); # Действие - создать узел } elsif ($user_vars{'doing'} eq 'new') { # Выбираем данные формы $user_vars{'name'} = $query->param('name') || 'Новая'; $user_vars{'parent'} = $query->param('parent') || 'root'; # Создаем узел в дереве и получаем его ID $user_vars{'id'} = $nested->insert_unit(under=>$user_vars{'parent'}); # Обновляем дополнительные поля узла $dbh->do('UPDATE '.$user_vars{'table'}. ' SET name = \''.$user_vars{'name'}.'\' WHERE id = '.$user_vars{'id'}) || die $DBI::errstr; # Действие - отредактировать } elsif ($user_vars{'doing'} eq 'edit') { # Выбираем данные формы $user_vars{'name'} = $query->param('name') || 'Новая'; $user_vars{'parent'} = $query->param('parent') || 'root'; # Выбираем ID родителя редактируемого узла my $check = ($nested->get_parent_id(unit=>$user_vars{'id'}))->[0]; # Если меняется родительский узел, то производим перемещение if ($check ne $user_vars{'parent'}) { $nested->set_unit_under(unit => $user_vars{'id'}, under => $user_vars{'parent'}) } # Обновляем дополнительные поля узла $dbh->do('UPDATE '.$user_vars{'table'}. ' SET name = \''.$user_vars{'name'}.'\' WHERE id = '.$user_vars{'id'}) || die $DBI::errstr; } }
# Выдаем заголовок браузеру print "Content-type: text/html; charset=windows-1251\n\n"; # Открываем шаблон верхней части страницы и выводим его на экран open (HTML, './template/header.html') || die 'Can not open file header.html!'; print <HTML>; close HTML;
# Открываем шаблон строки списка и заносим его в переменную open (HTML, './template/row.html') || die 'Can not open file row.html!'; my $line = join('', <HTML>); close HTML;
# Выбираем полностью все дерево и сортируем по левому ключу my $sql = 'SELECT id, name, level FROM '.$user_vars{'table'}.' ORDER BY left_key'; my $sth = $dbh->prepare($sql); $sth->execute() || die $DBI::errstr; # Объявляем хеш и переменную (счетчик) с помощью которого будем определять # порядок родительского узла в списке select формы my %par = (0 => '0', root => '0'); my $i = 1; # Объявляем переменную для формирования списка select формы my $list_select; while (my $row = $sth->fetchrow_hashref()) { # Копируем шаблон строки во временную переменную my $temp_line = $line; # Формируем переменную для "антикеша" $$row{'ac'} = time; # Формируем отступ перед названием узла $$row{'prefix'} = ' ' x ($$row{'level'} - 1); # Определяем порядок родительского узла в списке select формы $$row{'par'} = $par{($nested->get_parent_id(unit=>$$row{'id'}))->[0]}; $par{$$row{'id'}} = $i; $i++; # Обрабатываем строку заменяя соотвествующие $temp_line =~s /\[\$(\w+)\$\]/$$row{$1}/g; print $temp_line; $list_select .= '<option value="'.$$row{'id'}.'">'. $$row{'prefix'}.$$row{'name'}.'</option>'; } $sth->finish();
# Открываем шаблон нижней части страницы и записываем его в переменную open (HTML, './template/footer.html') || die 'Can not open file footer.html!'; my $footer = join('', <HTML>); close HTML; # Вносиим в шаблон список select формы $footer =~s /\[\$list_select\$\]/$list_select/g; # ... и выводим на экран print $footer; # Все... exit; 1;
Вот собственно и все. Как видно, никаких сложностей нет, сам скрипт размером менее чем сто строк благодаря использованию модуля. Конечно, нужно еще проверить данные формы, может быть, некоторые операции...
|