Présentation statique d’une arborescence

Après avoir vu comment gérer une arborescence avec CakePHP, intéressons-nous maintenant à la présentation de cette arborescence en tant que moyen de navigation principal dans les pages du site. Nous entendons par « statique » le fait que seules les branches de premier niveau soient affichées à l’arrivée sur le site, puis que la branche cliquée fasse apparaitre ses filles, et ainsi de suite.

1. Passage de l’arborescence au layout

Comme pour l’administration de l’arbre, nous allons utiliser dans les Vues le Helper Tree créé par AD7six, mais cette fois nous allons nous en servir pour afficher l’arborescence sur toute la partie publique du site. C’est pourquoi nous devons changer l’appel au Helper, nous le passons du Contrôleur Categories au Contrôleur général.

C’est également ce Contrôleur général qui va se charger de passer l’arborescence au layout public. Le meilleur endroit pour effectuer cette opération est sans doute la méthode beforeRender qui sera appelée avant la génération des Vues.

// {app}/app_controller.php
class AppController extends Controller
{
	var $helpers = array('Html', 'Form', 'Tree');
	var $uses = array('Category');
 
	function beforeRender()
	{
		$this->Category->recursive = -1;
		$layout_categories = $this->Category->children(false);
		$this->set('layout_categories', $layout_categories);
	}
}

Rappelons que la méthode children(), ajoutée par le Comportement Tree, renvoie l’arborescence complète quand elle est appelée avec le seul paramètre false.

2. Le layout public

Affichons maintenant notre arborescence dans le layout principal :

// /views/layout/default.ctp
echo $tree->generate(
	$layout_categories,
	array(
		'model'   => 'Category',
		'element' => 'category_layout'
	)
);

N’oublions pas ici de préciser quel Modèle est concerné, sinon le Helper ne pourra pas afficher l’arbre correctement.

3. L’élément d’affichage d’une catégorie

Le Helper permet d’appeler un élément pour chaque branche, découvrons-le :

// /views/elements/category_layout.ctp
echo $html->link(
	$data['Category']['libelle'],
	array(
		'controller' => 'categories',
		'action'     => 'view',
		$data['Category']['id']
	)
);

Nous avançons, mais toutes les catégories sont affichées, quel que soit leur niveau. Or nous ne voulons afficher que les catégories de premier niveau, plus les enfants d’une catégorie, si l’une d’elle a été cliquée.

Nous devons commencer par savoir si une catégorie a été cliquée, et si oui, laquelle. Cette logique s’effectue dans le Contrôleur Categories, dans l’action view qui est la destination de tous les liens de notre menu. Mais attention, la catégorie cliquée peut ne pas être de premier niveau, mais se trouver profondément dans l’arbre, nous devons donc pour une catégorie donnée remonter toute l’arborescence pour identifier ses parents :

// {app}/controllers/categories_controller.php
function view($id = null)
{
	$category = $this->Category->read(null, $id);
 
	$path_ids = $this->Category->get_path_ids($id);
 
	$this->set(compact('category', 'path_ids'));
}

Voyons la méthode getpathids, que nous plaçons dans le Modèle Category :

// {app}/models/category.php
function get_path_ids($id = null)
{
	$path = $this->getpath($id);
 
	return Set::extract('/Category/id', $path);
}

La méthode getpath a été ajoutée automatiquement par le Comportement Tree au Modèle Category, et renvoie un tableau des parents d’un élément de l’arbre (ainsi que l’élément lui-même, en dernier). Seuls les id des catégories nous intéressent, nous les extrayons donc avec la méthode statique extract de la class Set. Grâce à cette liste d’id de catégories, nous pouvons boucler sur les éléments de l’arbre et voir si l’id de catégorie en cours appartient à cette liste, auquel cas nous devons l’afficher ainsi que ses filles éventuelles.

4. Affichage de l’arborescence

Nous avons ici deux possibilités pour parvenir au même résultat visuel : soit nous continuons d’afficher toutes les branches dans le source HTML et cachons les catégories qui doivent l’être avec une propriété CSS, soit nous effectuons un traitement préalable du tableau des catégories avant le boucler dessus.

4.1. Méthode 1 : Arborescence complète et camouflage CSS

Commençons par la méthode la plus simple : reprenons simplement l’élément d’affichage d’une catégorie, appelé par le Helper Tree, et ajoutons un petit test :

// /views/elements/category_layout.ctp
if(!isset($path_ids)) $path_ids = array();
 
if($hasChildren && !in_array($data['Category']['id'], $path_ids))
{
	$tree->addTypeAttribute('style', 'display', 'none');
}
 
echo $html->link(
	$data['Category']['libelle'],
	array(
		'controller' => 'categories',
		'action'     => 'view',
		$data['Category']['id']
	)
);

Le test est assez limpide : si la catégorie en train d’être analysée a des enfants (la variable $hasChildren nous est automatiquement fournie par le Helper Tree) et que l’id de la catégorie n’est pas dans la liste $path_ids (la catégorie cliquée et ses parents), alors nous ajoutons un style CSS grâce à la méthode addTypeAttribute pour cacher les enfants. Nous obtenons bien le résultat attendu, mais ce n’est que visuel, les catégories cachées sont toujours dans le code source HTML.

4.2. Méthode 2 : Suppression des catégories non voulues dans l’arbre à postériori

Pour cette deuxième méthode, revenons en arrière et reprenons l’élément tel qu’il l’était au début.

// /views/elements/category_layout.ctp
echo $html->link(
	$data['Category']['libelle'],
	array(
		'controller' => 'categories',
		'action'     => 'view',
		$data['Category']['id']
	)
);

La logique va intervenir avant de passer l’arborescence au layout, dans le Contrôleur général.

// {app}/app_controller.php
class AppController extends Controller
{
	var $helpers = array('Html', 'Form', 'Tree');
	var $uses = array('Category');
 
	var $path_ids = array();
 
	function beforeRender()
	{
		$this->Category->recursive = -1;
		$layout_categories = $this->Category->children(false);
 
		foreach($layout_categories as $i => $cat)
		{
			if(!empty($cat['Category']['parent_id']) && !in_array($cat['Category']['id'], $this->path_ids) && !in_array($cat['Category']['parent_id'], $this->path_ids))
			{
				unset($layout_categories[$i]);
			}
		}
 
		$this->set('layout_categories', $layout_categories);
	}
}

Notons l’ajout de la variable de classe $pathids, un tableau vide par défaut. Le raisonnement est le même : pour chaque catégorie de l’arbre, si le parent n’est pas nul (catégorie qui n’est pas de premier niveau), que l’id du parent n’est pas dans la liste $pathids et que l’id de la catégorie n’est pas dans la liste $path_ids, alors nous enlevons la catégorie du tableau.

Il ne nous reste qu’à changer légèrement le comportement de l’action view dans le Contrôleur Categories :

// {app}/controllers/categories_controller.php
function view($id = null)
{
	$category = $this->Category->read(null, $id);
 
	$this->path_ids = $this->Category->get_path_ids($id);
 
	$this->set(compact('category'));
}

Cette fois nous n’avons plus besoin de passer la liste des id des parents à la Vue, mais nous la passons à la variable de classe pour que le Contrôleur général puisse y accéder.

Notre arborescence est terminée. Nous verrons dans un prochain sujet comment faire une présentation sous forme d’arbre interactif avec jQuery.

Pierre-Emmanuel Fringant

Commentaires

Bonsoir, Je rencontre un problème que je n’arrive pas à résoudre.

L’affichage se passe correctement pour la racine. Par contre, dès que je clique sur un noeuds, il m’indique : Undefined variable: html [APP\views\elements\category_layout.ctp, line 12]

Alors que le helper fonctionne très bien pour afficher lors de la première fois (afficher juste la racine), la il n’affiche plus rien (du moins, plus moyen de mettre de lien via le $html) dès que j’ai cliqué sur un lien.

J’ai un peu tout testé, demandé à d’autres personnes (amis dev sous cakephp), mais rien.

ps : j’ai la tte dernière version de cake.

Merci.

As-tu inclu le Helper Html dans le AppController ?

Oui, biensur.

J’ai refait tout le tuto plusieurs fois, cela revient au même (je l’ai encore refait hier soir sur un cakePHP que je venais de dl, tout propre donc).

Mais ce qui est bizarre, c’est qu’il ne me met pas d’erreur lors du chargement de la page d’accueil, donc avec le menu statique en haut. C’est dès que je clique sur un lien qu’il met l’erreur : Undefined variable: html [APP\views\elements\categorylayout.ctp, line 2] Fatal error: Call to a member function link() on a non-object in C:\wamp\www\CakePHP\app\views\elements\categorylayout.ctp on line 2

Merci.

Personne n’a d’idée pour résoudre le problème ?

to Lenouvdu44 :

Undefined variable: html [APP\views\elements\category_layout.ctp, line 2]

Tu as du oublier de déclarer ton HtmlHelper dans ton contrôleur …

Merci Hermant.

Le problème est résolu (hum hum), j’ai oublié de tenir informer cet endroit. Cela ne vient pas de l’HTMLHelper, comme on l’a déjà dit au dessus.

On peut trouver la solution et les aides apportées ici : http://forum.cakephp-fr.org/viewtopic.php?id=585 Pour résumer, il faut créer le fichier view.ctp dans la vue concernée (category dans le cas présent).

Merci, @bientôt.

Participez

Pour insérer une portion de code, utilisez <pre lang="php">...</pre>