Скрипт управления деревом

В предыдущих статьях, мы рассмотрели теорию хранения и управления древовидных структур данных, а так же реализовали 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">&nbsp;
            <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="Добавить">
            &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
            <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/
      • MP/
        • NestedSets.pm
    • 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-1251nn";
# Открываем шаблон верхней части страницы и выводим его на экран
    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'} = '&nbsp;&nbsp;&nbsp;&nbsp;' 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;

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

Автор: Сергей Томулевич (aka Phoinix)

Источник: http://phoinix.ucoz.ru



Опубликовал admin
11 Дек, Понедельник 2006г.



Программирование для чайников.