Yii Framework. Рекурсивное меню категорий.

Yii Framework. Рекурсивное меню категорий.
Рекурсивное меню.Создание горизонтального меню с неопределенным уровнем вложенности на платформе yii framework.

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

Для выборки рекурсивного меню будет использовать лишь один запрос к базе данных.Если у вас еще нет таблицы в БД то возможно вам будет полезно ознакомится с моей статьей посвященной  одному из способов хранения деревьев данных, применимому к базе данных SQLite. Эту статью я писал как раз таки на примере хранения структуры категорий.

Для улучшения производительности мы будем производить выборку данных одним запросом, а для построения рекурсивного дерева нам помогут вспомогательные функции:

function prepareTree($categories)
{  
	$arr = array();
	foreach($categories as $category)
	{
		if (!$category->parent_id)
			$category->parent_id = 0;
		if(empty($arr[$category->parent_id]))
			$arr[$category->parent_id] = array();
		$arr[$category->parent_id][] = $category;
	}
	return $arr;	
}

эта функция формирует массив, ключами которого будут id родительской категории. Затем, на основе полученного вспомогательного массива мы строим само дерево используя следующую рекурсивную функцию: 

function buildTree($arr,$parent_id = 0,&$str = '') 
{		
	//Условия выхода из рекурсии
	if(empty($arr[$parent_id])) 
		return;
	if ($parent_id == 0)
		$str.= '<ul id="cat_menu">';
	else
		$str.= '<ul>';
	//перебираем в цикле массив формируем строку
	for($i = 0; $i < count($arr[$parent_id]);$i++) {
		$str.= '<li>'.
		CHtml::link($arr[$parent_id][$i]->name, array(
			'category/view',
			'id' => $arr[$parent_id][$i]->id
		));
		//рекурсия - проверяем нет ли дочерних категорий
		buildTree($arr,$arr[$parent_id][$i]->id,$str);
		$str.= '</li>';
	}
	$str.= '</ul>';
}

и наконец функция которая будет обращаться к этим функциям и формировать html код нашего меню:

function getMenu($categories)
{
	$string = ''; 
	$cat = prepareTree($categories);
	buildTree($cat,0,$string);
	return $string;
}	

Эти функции вы можете использовать непосредственно в макете, однако более правильным будет вынести их логику за пределы представления. Я для этих целей создал вспомогательный компонент в приложении и сделал их методами класса этого компонента. 

Теперь давайте посмотрим код который мы сформировали:

$categories = Category::model()->findAll();
echo getMenu($categories);

 выведет нам что то вроде этого: 

Таким образом мы сформировали древовидный список, элементами которого являются ссылки на наши категории и при этом использовали лишь один запрос к базе данных. Теперь неплохо было бы прикрутить наше меню к виджету cmenu для отображения горизонтального меню. Делается это очень просто, меняем макет main.php следующим образом:

<head>
...
</head>
<?php
//Регистрируем jquery ui для меню категорий
Yii::app()->getClientScript()->registerCoreScript( 'jquery.ui' );
Yii::app()->clientScript->registerCssFile(
    Yii::app()->clientScript->getCoreScriptUrl().
    '/jui/css/base/jquery-ui.css'
);
//Подключаем jquery ui к пунктам меню 
Yii::app()->clientScript->registerScript('menu', '     
	$(function() {
		$( "#cat_menu" ).menu();
	});	
	
	$("#cat_menu").css("display","none");
	
	$(".category").mouseover(function() {
		$("#cat_menu").show();
	});
	$(".category").mouseout(function() {
		$("#cat_menu").hide();
	});	
');
// генерим рекурсивное меню категорий, для интеграции с виджетом CMenu
$categories = Category::model()->findAll();
if (!empty($categories))
	$menuTemplate = '{menu}'.getMenu($categories);
else
	$menuTemplate = '{menu}';
<body>
...
<?php $this->widget('zii.widgets.CMenu',array(			
			'items'=>array(	
                        ...
                            array(
                                'label'=>'Категории',  
                                'url'=>array('category/index'),
                                'itemOptions'=>array('class'=>'category'),
                                'template'=>$menuTemplate
                            ),
                        ... 

Теперь немного пояснений. Сначала мы регистрируем и подключаем библиотеку jquery ui , которая нам понадобится для оживления нашего меню.  Мы использовали  идентификатор #cat_menu которое сформировала наша функция buildtree, и класс элементов .category, для того чтобы регистрировать события(наведения курсора мыши) для нашего списка. Затем мы присваиваем переменной $menutemplate - html код нашего списка,  и присваиваем ее переменной template нужного нам элемента в виджете cmenu, после  чего наш код будет помещен в элемент <li class="category"> виджета.

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

Комментарии

    Коментарий от: Andrew

    Дата: 20.03.2014, 14:31:38
  • Блин, все получилось) Общаюсь тут сам с собой))

    Вот эту функцию вызываю рекурсивно и получаю то, чот хотел:
    protected function buildTree($arr, $parent_id = 0)
         {            
            //Условия выхода из рекурсии
            if(empty($arr[$parent_id]))
               return false;

            $rootArray = array();
            
            //перебираем в цикле массив формируем строку
            for($i = 0; $i < count($arr[$parent_id]);$i++) {
               $element = array(
                 "id"=>$arr[$parent_id][$i]->id,
                 "text"=>$arr[$parent_id][$i]->name,
               );
              
               if($childArray = $this->buildTree($arr,$arr[$parent_id][$i]->id))
                    $element["children"] = $childArray;
              
               $rootArray[] = $element;          
            }
            
            return $rootArray;
         }
    Ответить
  • Коментарий от: Andrew

    Дата: 20.03.2014, 13:15:45
  • Добрый день! Наконец нашел, то что нужно. Спасибо заранее.

    Столкнулся со следующей проблемой. Мне бы хотело этот вывод категорий сформировать для вывода в CTreeView, который имеет несколько другой формат (вложенные массивы). У вас случайно не было практики формирования нечто подобного? Не могу понять логику прохождения рекурсии, и добавления в массив по ссылке подмассива..
    Ответить
Добавить комментарий