DOC.PROTOTYPES.RU

Главная > Базы данных > Деревья SQL > Nested Sets > практика Perl >

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

Введение

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

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

SQL код (1)
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 в скрипте, и Вам того не советую. Шаблон состоит из трех частей:

* Эта структура шаблонов была придумана "на ходу", поэтому не будем заострять внимание на её правильности, не это важно.

HTML-код шаблонов:

header.html

HTML код (1)
<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

HTML код (2)
<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

HTML код (3)
</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>

Вот так, если эти три файла сложить в один, как есть, то получится одна страница с таблицей в центре состоящей из одной строки.

Теперь поясню отдельные моменты:

Код скрипта

Для начала определим где какие файлы у нас будут лежать:

Что за файл NestedSets.pm, я думаю, объяснять не нужно (это модуль описанный в предыдущих статьях), с .html файлами - тоже понятно, остался только один файл - admin.pl, его мы как раз и опишем. Итак, код скрипта:

Perl код (1)
#!/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;

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

Сергей Томулевич aka Phoinix (01.06.2005, ред. 01.05.2010 г.)
Copyright © 2011 Сергей Томулевич