Как из дерева сделать ul-li структуру?

Рубрика: MaxSite CMS -> PHP
Суббота, 5 апреля 2008 г.
Просмотров: 2205
Подписаться на комментарии по RSS
]]>
]]>

Получилось, что я немного меньше стал публиковать записей в блоге, но на это есть довольно веские причины. Помимо работы я постоянно занимаюсь MaxSite CMS.

Столкнулся с одной проблемой, может кто поможет её решить. (Самому уже не хвататет уже ни терпения, ни ума).

Задача связана с выводом рубрик. Есть таблица рубрик, где есть поля:

  • category_id (номер рубрики)
  • category_id_parent (номер родителя)

Теоретически этого хватает, чтобы организовать древовидную структуру (массив).

Я её реализовал классическим способом: через рекурсию. То есть для поиска «детей» выполняется еще один SQL-запрос. Соответственно при большом количестве рубрик и её «ветвистости» получается довольно много SQL-запросов.

Частично эту проблему можно решить за счет кэширования: получается где-то раза в два меньше.

Погуглив я нашел еще один способ создания деревьев: в нем указывается отступ слева (level). Например так («Код 1»):

  1.  001 level = 0
  2.   002 level = 1
  3.   003 level = 1
  4.   004 level = 2
  5.   005 level = 1

Путем различных манипуляций с массивом я смог сделать аналогичную структуру рубрик. При этом понадобился всего один SQL-запрос. Получилась примерно так:

  1.  [1] => Array
  2.          (
  3.              [category_id] => 1
  4.              [category_id_parent] => 0
  5.              [category_name] => Новости
  6.              [category_menu_order] => 4
  7.              [parents] => 0
  8.              [childs] =>
  9.              [level] => 0
  10.          )
  11.      [3] => Array
  12.          (
  13.              [category_id] => 3
  14.              [category_id_parent] => 0
  15.              [category_name] => CodeIgniter
  16.              [category_menu_order] => 1
  17.              [parents] => 0
  18.              [childs] => 6 5
  19.              [level] => 0
  20.          )
  21.      [6] => Array
  22.          (
  23.              [category_id] => 6
  24.              [category_id_parent] => 3
  25.              [category_name] => Еще
  26.              [category_menu_order] => 0
  27.              [parents] => 3
  28.              [childs] =>
  29.              [level] => 1
  30.          )
  31.  ...

Это «одномерная» структура, где сразу указываются и level (отступ), и все «дети» (childs), и родитель (parents). Всей этой информации по идее должно хватить на то, чтобы выстраивать и получать любые рубрики.

Теперь, чтобы выстоить рубрики по уровню, вполне достаточно одного level. И в принципе сделать это с помощью str_pad - две строчки кода (результат будет выглядеть как «код 1»).

Но дальше - ступор... Потратил несколько дней, но так и не смог придумать, каким образом все это конвертировать в ul-li. Сложность в том, что простыми заменами кажется не обойтись из-за вложенности тэгов. Чтобы было совсем понятно, нужно конвертировать «Код 1» (или сам массив) вот в это:

  • 001

    • 002
    • 003

      • 004
    • 005

HTML-код:

  1.  <ul>
  2.   <li>001
  3.   <ul>
  4.   <li>002</li>
  5.   <li>003
  6.   <ul>
  7.   <li>004</li>
  8.   </ul>
  9.   </li>
  10.   <li>005</li>
  11.   </ul>
  12.   </li>
  13.  </ul>

Кто-то знает как решить эту задачку?

[upd] Вот мой вариант решения, основанный только на level. Передполагается, что массив ($r) уже отсортирован в нужной последовательности.

  1.  function mso_tree_print($r)
  2.  {
  3.   $out = "\n<ul>";
  4.   $open  = "</li>\n<li>";
  5.   $close = "</li>\n</ul>";
  6.   $left  = "\n<ul>\n<li>";
  7.   $old_level = 0;
  8.   $open_li = 0;
  9.   foreach ($r as $key=>$val)
  10.   {
  11.   $level = $val['level'];
  12.   if ($level == $old_level)
  13.   {
  14.   $out .= $open . $val['category_name'];
  15.   }
  16.   elseif ($level > $old_level)
  17.   {
  18.   $open_li = $open_li + $level - $old_level;
  19.   $out .= $left . $val['category_name'];
  20.   }
  21.   else // $level < $old_level
  22.   {
  23.   if (( $open_li) > 0)
  24.   $out .= str_repeat($close, $open_li);
  25.   $out .= $open . $val['category_name'];
  26.   $open_li = 0;
  27.   }
  28.   $old_level = $level;
  29.   }
  30.   $out .= $close;
  31.   $out = str_replace("<ul></li>", "<ul>", $out);
  32.   return $out;
  33.  }

Сорри, при переносе удалились комменты, поэтому здесь, те, которые сохранились.


TedBeer :

6 апреля 2008 г. в 18:51 edit

Вот пример без level и без children(вычисляются по ходу дела) в базе. Порядок неважен, дети в массиве могут быть и до родителей, так что выборку из базы можно делать без сортировки.

  1.  $info = Array(
  2.  1 => Array
  3.  (
  4.  'category_id' => 1,
  5.  'category_id_parent' => 0,
  6.  'category_name' => 'Один'
  7.  ),
  8.  2 => Array
  9.  (
  10.  'category_id' => 2,
  11.  'category_id_parent' => 1,
  12.  'category_name' => 'Два'
  13.  ),
  14.  3 => Array
  15.  (
  16.  'category_id' => 3,
  17.  'category_id_parent' => 1,
  18.  'category_name' => 'Три'
  19.  ),
  20.  4 => Array
  21.  (
  22.  'category_id' => 4,
  23.  'category_id_parent' => 3,
  24.  'category_name' => 'Четыре'
  25.  ),
  26.  5 => Array
  27.  (
  28.  'category_id' => 5,
  29.  'category_id_parent' => 0,
  30.  'category_name' => 'Пять'
  31.  ),
  32.  6 => Array
  33.  (
  34.  'category_id' => 6,
  35.  'category_id_parent' => 4,
  36.  'category_name' => 'Шесть'
  37.  )
  38.  );
  39.  $top = Array();
  40.  foreach($info as &$item){
  41.  $parentId = $item['category_id_parent'];
  42.  $id = $item['category_id'];
  43.  if( $parentId && isset($info[ $parentId])){
  44.  if( !isset($info[ $parentId]['children']))
  45.  $info[ $parentId]['children'] = Array($id);
  46.  else
  47.  $info[ $parentId]['children'][] = $id;
  48.  } else {
  49.  $top[] = $id;
  50.  }
  51.  }
  52.  function glue( $in, &$info){
  53.  $out = Array();
  54.  foreach( $in as $id){
  55.  $out[] = '<li>'. $info[$id]['category_name'];
  56.  if( isset( $info[$id]['children'])){
  57.  $out[] = '<ul>';
  58.  $out[] = glue( $info[$id]['children'], $info);
  59.  $out[] = '</ul>';
  60.  }
  61.  $out[] = '</li>';
  62.  }
  63.  return implode("\n", $out);
  64.  }
  65.  echo '<ul>' . glue( $top, $info) . '</ul>';

Galchenkov :

7 апреля 2008 г. в 11:19 edit

  1.  'Один', 'level' => 0);
  2.  $categories[] = Array('name' => 'Два', 'level' => 1);
  3.  $categories[] = Array('name' => 'Три', 'level' => 1);
  4.  $categories[] = Array('name' => 'Четыре', 'level' => 2);
  5.  $categories[] = Array('name' => 'Пять', 'level' => 3);
  6.  $categories[] = Array('name' => 'Шесть', 'level' => 2);
  7.  $categories[] = Array('name' => 'Семь', 'level' => 0);
  8.  // Инициализируем переменную
  9.  $current_level = -1;
  10.  foreach ($categories as $category)
  11.  {
  12.  // Уровень отображаемого категории (узла, если хотите)
  13.  $category_level = $category['level'];
  14.  if ($current_level <li>'.$category['name'];
  15.  }
  16.  if ($current_level &gt; $category_level)
  17.  {
  18.  // Тут важно закрыть все открытые ранее списки (ведь из третьего
  19.  // уровня можно сразу попасть в первый)
  20.  echo str_repeat('</li></ul>', $current_level - $category_level + 1);
  21.  echo '<ul><li>'.$category['name'];
  22.  }
  23.  if ($current_level == $category_level)
  24.  {
  25.  echo '</li><li>' . $category['name'];
  26.  }
  27.  $current_level = $category_level;
  28.  }
  29.  // Тут тоже закрываем все открытые ранее списки
  30.  echo str_repeat('</li></ul>', $current_level + 1);
  31.  ?>
]]>twitter.com Google Buzz google.com bobrdobr.ru del.icio.us technorati.com linkstore.ru news2.ru rumarkz.ru memori.ru moemesto.ru]]>
РЕКЛАМАТанцевальный центр на Белорусской - обучение танцам. Получи высшее образование.

Комментариев: 2

Вы можете получать новые комментарии к этой записи по RSS или оформить подписку на все комментарии сайта. Или даже на все новые записи сайта. Не знаете, как это сделать?
  1. 2009-07-07 в 23:26:56 | Arreay

    Дааа, можно всё сделать на много проще grin Над такой задачей я бился недели две тож пишу свою ЦМС

  2. 2009-10-26 в 13:54:51 | lorigin

    у меня была таблица [id name owner type]

    owner - ID родителя [0 - корень]

    type (0 если это каталог, 1 если конечный элемент), грубо говоря папка и файл

    нужно было вывести дерево.

    логика примерно такая:

    - берем все где владелец КОРЕНЬ.

    - запоминаем idшники этих записей, а саму выборку сохраняем в results[0] - тоесть хранит все элементы первого уровня

    - теперь выбираем "второй уровень", тоесть у кого owner'ы - те что нашли раньше =)

    - ну и т.д.

    тоесть получаем массив results где каждый элемент массива содержит выборку по определенному уровню... количество запросов сколько уровней.. редко превышает 5

    function WriteTree() {
        global $result; //делаем глобальной
        $in_string = '0'; // параметры запроса, тоесть сначала делаем выборку "WHERE owner in (0)"
        $result = array();
        $level = 0; // уровень дерева
        while ($in_string !='') {
            $query = "SELECT * FROM catalog WHERE owner in (".$in_string.") ORDER BY id ASC";
            $result[$level] = mysql_query($query) or die('ОШИБКА '.mysql_error());
            $in_string = '';
            $in_array = array();
            $num_rows = mysql_num_rows($result[$level]);
            if ($num_rows) { //если хотябы одна запись вернулась
                while ($catalog_row = mysql_fetch_array($result[$level])) {
                    $in_array[] = $catalog_row['id'];  // выбираем ID текущего уровня, чтобы патом определить узлы следуюшей вложености
                }
                $in_string = implode(',',$in_array);
                $level++;
            }
        }
        unset ($result[$level]); // убиваем последнюю выборку, которая пустая
        //echo "уровней = ".$level;
        PrintBranch(0,0);
    }
    

    а тут разворачиваем дерево, заходим в каждую ветку и выводим элементы..

    function PrintBranch($level,$id) {
        global $result;
        $ul_write = false;
        echo "\n<ul>";
        mysql_data_seek($result[$level], 0);
        while ($row = mysql_fetch_assoc($result[$level])) {
            if ($row['owner'] == $id) {
            //if (!$ul_write) echo "\n<ul>";
                echo "<li><span><a href = \"#".$row['id']."\" itemindex = \"".$row['id']."\" type =\"".$row['type']."\">";
                echo "<img src =\"img/type".$row['type'].".png\"/>";
                echo "<b>".GetLangText($row['name'],'ru')."</b><b class=\"hide\">".GetLangText($row['name'],'en')."</b></a></span>";
                $new_lev = $level;
                if (isset($result[$level+1]) )
                    PrintBranch(++$new_lev,$row['id']);
                echo "</li>\n";
            }
        }
        echo "</ul>\n";
    }
    

Оставьте комментарий!

Не регистрировать/аноним

Используйте нормальные имена. Ваш комментарий будет опубликован после проверки.

Если вы уже зарегистрированы как комментатор или хотите зарегистрироваться, укажите пароль и свой действующий email.
(При регистрации на указанный адрес придет письмо с кодом активации и ссылкой на ваш персональный аккаунт, где вы сможете изменить свои данные, включая адрес сайта, ник, описание, контакты и т.д.)



grin LOL cheese smile wink smirk rolleyes confused surprised big surprise tongue laugh tongue rolleye tongue wink raspberry blank stare long face ohh grrr gulp oh oh downer red face sick shut eye hmmm mad angry zipper kiss shock cool smile cool smirk cool grin cool hmm cool mad cool cheese vampire snake excaim question

(обязательно)