За последние 24 часа нас посетили 34100 программистов и 1314 роботов. Сейчас ищут 992 программиста ...

Список в списке

Тема в разделе "PHP для новичков", создана пользователем bikerlex, 14 мар 2016.

  1. bikerlex

    bikerlex Активный пользователь

    С нами с:
    2 дек 2014
    Сообщения:
    344
    Симпатии:
    40
    Есть массив с пунктами меню такого вида:
    Код (PHP):
    1. <?php
    2. $items = array(
    3.     array('id' => 1, 'title' => 'Курсы дизайнеров', 'parent' => 0),
    4.     array('id' => 2, 'title' => 'Курсы ландшафтного дизайна', 'parent' => 1),
    5.     array('id' => 3, 'title' => 'Курсы дизайнеров интерьера', 'parent' => 1),
    6.     array('id' => 4, 'title' => 'Курсы компьютерной графики', 'parent' => 0),
    7.     array('id' => 5, 'title' => 'Школа фотографии', 'parent' => 0),
    8. );
    9. ?>
    Нужно сделать из этого меню, список в списке вида:
    Код (PHP):
    1. <ul>
    2.     <li>
    3.         Курсы дизайнеров
    4.     <ul>
    5.         <li>Курсы ландшафтного дизайна</li>
    6.         <li>Курсы дизайнеров интерьера</li>
    7.     </ul>
    8.     </li>
    9.     <li>Курсы компьютерной графики</li>
    10.     <li>Школа фотографии</li>
    11. </ul>
    12.  
    Самый простой вариант сделать так:
    Код (PHP):
    1. <ul class="parent">
    2.     <?php foreach ($items as $cat): ?>
    3.         <?php if ($cat['parent'] == 0): ?>
    4.             <li>
    5.                 <?php echo $cat['title']; ?>
    6.                 <ul class="children">
    7.                     <?php foreach ($items as $subcat): ?>
    8.                         <?php if($subcat['parent'] == $cat['id']): ?>
    9.                             <li><?php echo $subcat['title']; ?></li>
    10.                         <?php endif; ?>
    11.                     <?php endforeach; ?>
    12.                 </ul>
    13.             </li>
    14.         <?php endif; ?>
    15.     <?php endforeach; ?>
    16. </ul>
    Но тогда у пунктов без подпунктов будет пустой ul. В общем надо проверять есть ли дочерний пункт меню.

    Тогда я сделал так:
    Код (PHP):
    1. <ul class="parent">
    2.     <?php foreach ($items as $cat): ?>
    3.         <?php if ($cat['parent'] == 0): ?>
    4.             <li>
    5.                 <?php echo $cat['title']; ?>
    6.                 <?php $ul_open = true; $ul_close = false; ?>
    7.                     <?php foreach ($items as $subcat): ?>
    8.                         <?php if($subcat['parent'] == $cat['id']): ?>
    9.                             <?php if ($ul_open) : $ul_open = false; $ul_close = true; ?>
    10.                             <ul class="children">
    11.                             <?php endif; ?>
    12.                             <li><?php echo $subcat['title']; ?></li>
    13.                         <?php endif; ?>
    14.                     <?php endforeach; ?>
    15.                 <?php if ($ul_close): ?>
    16.                 </ul>
    17.                 <?php endif; ?>
    18.             </li>
    19.         <?php endif; ?>
    20.     <?php endforeach; ?>
    21. </ul>
    И это выдает списки так как надо. Но не покидает ощущение, что это тот ещё говнокод и можно сделать проще. Да к тому же я не могу быть уверен, что вложенность будет только одна и мне потом не придется допиливать ещё третий и четвертый цикл.
    Куда покапать? Может как-то отфильтровать массив, разбить по другому, использовать рекурсию? Почитал в мануале по функциям работы с массивами, есть и рекурсивные и с применением callback-функций. http://php.ru/manual/ref.array.html Но, скорее всего тупо из-за нехватки опыта, не увидел возможного решения.
     
  2. igordata

    igordata Суперстар
    Команда форума Модератор

    С нами с:
    18 мар 2010
    Сообщения:
    32.408
    Симпатии:
    1.768
    а ты не рисуй сразу подпункты, сначала собери в массив и если он не пустой, то рисуй. Вообще можно заранее распихать за один раз ещё до начала отрисовки.
     
  3. artoodetoo

    artoodetoo Суперстар
    Команда форума Модератор

    С нами с:
    11 июн 2010
    Сообщения:
    11.128
    Симпатии:
    1.248
    Адрес:
    там-сям
    Ты хочешь чтобы мы отняли у тебя радость новых открытий?
    может, почему бы и нет
    хорошая мысль. а можно рекурсию заменить на итерацию и стек. попробуй всё.
    и если тебе дать готовое решение, ты и дальше не будешь видеть. так устроен мозг: если его не бодрить работой, он ленится.
     
  4. bikerlex

    bikerlex Активный пользователь

    С нами с:
    2 дек 2014
    Сообщения:
    344
    Симпатии:
    40
    Всё гениальное просто, была такая мысль, но почему-то проигнорировал. Так и сделаю.

    О нет, этого я не хочу. Итератор прикольная штука, надо будет поэсперементировать с ним, но пока хочу обойтись рекурсией.

    Запилил такую штуку, что бы дочерние пункты меню были в подмассивах:
    Код (PHP):
    1. function test($items, $parent = 0) {
    2.     $new_items = array();
    3.     foreach($items as $id => $item) {
    4.         if($parent == $item['parent']){
    5.             $new_items[$id] =  $item;
    6.             $new_items[$id][] = test($items, $item['id']);
    7.         }
    8.     }
    9.  
    10.     return $new_items;
    11. }
    12.  
    13. $arr = test($items);
    14. echo '<pre>'.print_r($arr,1).'</pre>';
    15.  
    Но, тут косяк, если нет дочерних пунктов всё равно создается пустой подмассив, не могу придумать как решить эту проблему.
     
  5. artoodetoo

    artoodetoo Суперстар
    Команда форума Модератор

    С нами с:
    11 июн 2010
    Сообщения:
    11.128
    Симпатии:
    1.248
    Адрес:
    там-сям
    ок, смотри такой вариант уже формирующий вложенные <ul>. слова level и space здесь только для красоты оформления отступов, на функциональность не влияют:

    Код (PHP):
    1. // Print whole hierarchy
    2. makeList($items);
    3.  
    4. function makeList(array $adjacencyList, $parentId = 0, $level = 0)
    5. {
    6.     $brothers = array_filter(
    7.         $adjacencyList, 
    8.         function($x) use($parentId) { return $x['parent'] == $parentId; }
    9.     );
    10.     if (count($brothers)) {
    11.         $space = str_repeat('  ', $level);
    12.         echo "<ul>\n";
    13.         foreach ($brothers as $x) {
    14.             echo "{$space}  <li>{$x['title']}";
    15.             makeList($adjacencyList, $x['id'], $level + 1);
    16.             echo "</li>\n";
    17.         }
    18.         echo "{$space}</ul>";
    19.     }
    20. }
    на входе массив как в твоём первом посте
     
  6. bikerlex

    bikerlex Активный пользователь

    С нами с:
    2 дек 2014
    Сообщения:
    344
    Симпатии:
    40
    Круто, прям то что было нужно. Правда не всё понял. С замыканием (вроде это оно) ещё не знаком. Почитаю мануал, поэсперементирую, вардампом попользуюсь, разберусь. Благодарю.
     
  7. bikerlex

    bikerlex Активный пользователь

    С нами с:
    2 дек 2014
    Сообщения:
    344
    Симпатии:
    40
    Дальше надо выделить родительскую рубрику.
    Эмуляция перехода в подменю. $thisCat - текущая категория.
    Код (PHP):
    1. function makeList(array $adjacencyList, $parentId = 0, $level = 0)
    2. {
    3.     $thisCat = 2;
    4.  
    5.     $brothers = array_filter(
    6.         $adjacencyList,
    7.         function ($x) use ($parentId) {
    8.             return $x['parent'] == $parentId;
    9.         }
    10.     );
    11.     if (count($brothers)) {
    12.         $space = str_repeat('  ', $level);
    13.         echo "<ul>\n";
    14.         foreach ($brothers as $x) {
    15.             if($thisCat == $x['id']):
    16.                 echo "{$space}  <li><a style='background: silver' href='#'>{$x['title']}</a>";
    17.             else:
    18.                 echo "{$space}  <li><a href='#'>{$x['title']}</a>";
    19.             endif;
    20.             makeList($adjacencyList, $x['id'], $level + 1);
    21.             echo "</li>\n";
    22.         }
    23.         echo "{$space}</ul>";
    24.     }
    25. }
    Выделить текущую категорию не проблема, а вот как быть с родительской?