Utiliser plusieurs CGridView sur une même page

Classé dans Yii

Lors du développement d'une application avec Yii, il est souvent nécessaire d'utiliser les widgets mis à disposition par le framework. CGridView en fait partie et j'avoue que son utilisation n'est pas des plus intuitives. Même en lisant la doc plusieurs fois, il n'est pas si évident de faire ce que l'on veut. J'ai été confronté au problème d'utiliser plusieurs fois le widget sur une même page, en utilisant le même modèle mais en voulant afficher des résultats différents. Et voici comment j'ai procédé.

La structure de l'application

Yii se base sur la structure MVC (Modèle Vue Controlleur). Nous aurons donc le modèle Articles dans le fichier "Articles.php", le controlleur dans le fichier "ArticlesController.php" et la vue associée, "view.php".

Le widget CGridView pouvant fonctionner avec un module de recherche avancé, nous aurons besoin d'une vue annexe qui sera incluse dans la vue principale. Par convention, les vues intégrées sont précédées d'un underscore. Nous nommerons donc le fichier _search.php

La base de données

On va utiliser une base théorique pour l'exemple qui se compose de la table "articles" :

CREATE TABLE `articles` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `nom` tinytext,
  `titre` text,
  `date_parution` date DEFAULT NULL,
  `etat` varchar(60) NOT NULL,
  `contact_id` int(11) unsigned NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;

et de la table "contacts" :

CREATE TABLE `contacts` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `nom` varchar(100) DEFAULT NULL,
  `prenom` varchar(100) DEFAULT NULL,
  `tel_fixe` tinytext,
  `tel_portable` tinytext,
  `fax` tinytext,
  `email` tinytext,
  `adresse` text,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;

Les deux tables sont liées entre elles par le champ "contact_id" de la table "articles" et l'index "id" de la table "contacts". Au niveau du modèle "Articles", cela se traduit par la relation :

  /**
  * @return array relational rules.
  */
 public function relations()
 {
  // NOTE: you may need to adjust the relation name and the related
  // class name for the relations automatically generated below.
  return array( 
    'contact' => array(self::BELONGS_TO, 'Contacts', 'contact_id'),
        );
 }

Les vues

view.php

Nous souhaitons trier les articles en fonction de leur état possible ('ouvert','ferme','attente'). Chaque résultat sera positionné par état dans un tableau généré par CGridView.

Voici le code commenté :

<?php 
//Le fil d'ariane
 $this->breadcrumbs=array(
  Yii::t('app','Administration')=>array('gestion/index'),
  Yii::t('app','Liste des articles '),
 );

 //On défini ici le menu que l'on complétera en fonction de l'état de l'article
 $this->menu=array();

 //Les différents états possibles pour un article
 $states = array('ouvert','ferme','attente');
 
 //On fait une boucle afin d'afficher les résultats en fonction de l'état des articles
 foreach ($states as $key => $value) :
  switch ($value) {
      case "ouvert":
    //$etat permettra de controller entre autre le widget
          $etat = 'ouvert';
    //$statut permettra de compléter les légendes
          $statut = Yii::t('app','ouvertes');
          break;
      case "ferme":
          $etat = 'ferme';
          $statut = Yii::t('app','fermées');
          break;
   case "attente":
          $etat = 'attente';
          $statut = Yii::t('app','en attente');
          break;
   default :
    $etat = 'ouvert';
    $statut = Yii::t('app','ouvertes');
  }

  //On complète le menu
  $this->menu[] = array('label'=>Yii::t('app','Liste des articles ').$statut, 'url'=> '#'.$etat);

  //On paramètre le nombre de pages de résultats
  $pageSize=Yii::app()->user->getState('pageSize',Yii::app()->params['defaultPageSize']);
  
  //On lance une obfuscation afin que le widget puisse générer sans erreur les différents tableaux de résultats.
 ob_start();

  //Par défaut, les champs de recherche avancée sont masqués par une fonction javascript qui permet aussi de les afficher
  //Chaque état devant être indépendant, on génère une fonction pour chacun d'entre eux.
  Yii::app()->clientScript
   ->registerScript('search'.$etat, "
   $('#search-button_$etat').click(function(){
    $('#search_$etat').toggle();
    return false;
   });
   $('.search-form form#form_$etat').submit(function(){
    $.fn.yiiGridView.update('$etat', {
     data: $(this).serialize()
    });    
    $('#search_$etat').toggle();
    return false;
   }); 
   ",
   CClientScript::POS_READY);
 //On ouvre une division afin d'envelopper chaque état pour y accéder plus facilement par le menu
 //Chaque division ayant un index unique correspondant à l'état des articles
 ?>

 <div id="<?php echo $etat;?>" class="contenu"><!-- articles <?php echo strtoupper($statut);?>-->

 <?php
 //On inclue le formulaire de recherche avancée
 //On donne un index unique au lien permettant l'ouverture/la fermeture du formulaire afin que le javascript fonctionne
 echo CHtml::link(Yii::t('app','Recherche avancée'),'#',array('id'=>'search-button_'.$etat));

 //On appelle la vue _search.php en lui passant les variables définies dans le modèle (voir ci-après)
 $this->renderPartial('_search',array(
    'model'=>$model,
    'contacts'=>$contacts,
    'etat'=>$etat
      ));

 //On définie une variable afin de pouvoir traduire le message de suppression qui sera utilisé par CGridView
 //Si on le fait directement dans le widget, la trduction n'est pas prise en compte
 $del = Yii::t('app','Êtes vous sûr de vouloir supprimer l’article "');

 //Enfin on définie le widget
 $this->widget('zii.widgets.grid.CGridView', array(
   //L'id unique permet l'indépendance entre chaque widget
   'id'=>'tableau_'.$etat,
   //Le dataProvider permet de modifier l'affichage du widget en fonction des résultats de recherche
   //La recherche se fait ici par $_POST par la méthode search définie dans le modèle Articles
   //search prend en argument l'état de l'article
   'dataProvider'=> $model->search($etat),
   //On peut afficher un formulaire de recherche simplifié. La recherche se fera par $_GET 
   //sur le modèle principal
   'filter'=> $model,
   //L'affichage peut être généré ou non en ajax
   'ajaxUpdate'=>true, 
   //On peut décider de l'endroit où sera affiché le formulaire de recherche simplifiée
   'filterPosition' => 'header',
   //Si la pagination est activée, on peut personnaliser son affichage
   //en précisant le texte qui sera utilisé
   'summaryText'=>Yii::t('app','articles ').'{start}'.Yii::t('app',' à ').'{end}'.Yii::t('app',' sur ').'{count}'.Yii::t('app',' au total'),
   //en définisant le template de pagination
   'template'=>"{pager}\n{summary}\n{items}\n{pager}",
   //et les liens qui seront affichés (ainsi que leur css associé)
   'pager'=>array( 
    'header'=>false,
    'maxButtonCount'=> 4,
    'firstPageLabel'=>'&lt;&lt;',
    'lastPageLabel'=>'&gt;&gt;',
    'nextPageLabel'=>'&gt;',
    'prevPageLabel'=>'&lt;',
    'cssFile'=>Yii::app()->theme->baseUrl.'/css/pagination.css'
   ), 
   //Une fois toutes les options de présentation définies
   //on affiche les données
   'columns'=>array( 
    //En première colonne, on peut afficher un bouton permettant la suppression des articles
    array( 
     //header est l'entête qui sera affichée en première ligne dans le tableau de résultats
     'header'=>Yii::t('app','Action'),
     //On définit un bouton associé au widget
     'class'=>'CButtonColumn',
     //On définit sa fonction
     //Par défaut, Yii prévoit 3 tickets de fonction différents
     //delete, update et view
     //D'autres tickets de fonction peuvent être définis
     'template'=>'{delete}',
     //On définit le message de confirmation
     //Le contenu étant généré par javascript, il faut avoir au préalable traduit le message
     'deleteConfirmation'=> "js:'".$del."'+$(this).parents('tr').children(':nth-child(2)').text()+'\" ?'"
    ),
    //En deuxième colonne, on affiche le nom de l'article
    array(
      'header'=>Yii::t('app','Nom'),
      //On définit le nom qui sera donné au champ de recherche
      //Comme le widget est utilisé plusieurs fois
      //on crée un tableau en fonction de l'état des articles afin de bien différencier les résultats
      'name' => "[$etat]nom", 
      //On choisi d'afficher un lien vers la partie administration afin de modifier l'article
      //le champ doit donc être de type html
      'type' => 'html',
      //On affiche enfin la valeur de chaque cellule
      //Lorsque l'on affiche les résultats, on n'utilise pas directement le modèle $model
      //Le widget fait une boucle afin d'afficher les résultats
      //qui sont stockés au préalable dans l'objet $data.
      //Le code doit être placé entre guillemets
      'value' => 'CHtml::link($data->nom,array("articles/update","id" => $data->id, "article" => CHtml::encode($data->nom)))',
    ),
    array(
     //Même principe
     'header'=>Yii::t('app','Titre'),
     'name'=>"[$etat]titre",
     'value'=>'$data->titre'
    ),
    array( 
     //On définit de nouveaux boutons d'actions
     //On les sépare du boutons de suppression afin d'éviter toute erreur
     //de manipulation de la part de l'utilisateur
     //Même principe que précédemment
     'header'=>Yii::t('app','Actions'),
     'class'=>'CButtonColumn',
     'template'=>'{update} {view}',
     //On précise les url et les options de chaque bouton généré
     'buttons'=>array(
      'update'=>array(  
       'url'=> 'Yii::app()->controller->createAbsoluteUrl("articles/update", array("id"=>$data->id,"nom"=>$data->nom))'
       ), 
      'view'=>array(  
       'url'=> 'Yii::app()->controller->createAbsoluteUrl("articles/view", array("id"=>$data->id,"nom"=>$data->nom))',
       'options'=> array(
        'title'=>Yii::t('app','Aperçu de l\'article'))
       )           
     )
    ),        
   ),
  ));
 ?>

  </div><!-- FIN articles <?php echo strtoupper($statut);?> -->

 <?php
 //On stocke le contenu obfusqué dans une variable.
 //Le contenu est concaténé avec le contenu précédent
 $articles .= ob_get_contents();
 //On ferme l'obfuscation
 ob_end_clean();
 //On ferme la boucle
 endforeach;
 //Enfin on affiche les résultats
 ?>

 <h1><?php echo Yii::t('app','Gestion des articles ');?></h1> 

 <?php
 //On affiche le menu sous forme de liste non ordonnée
 $this->widget('zii.widgets.CMenu', array(
  'items'=>$this->menu,
  'htmlOptions'=>array('class'=>'css-tabs history'),
 ));
 //et les résultats
 ?>

 <div class="panes"><!-- PANES -->

  <?php echo $articles;?>

 </div><!-- FIN PANES -->

_search.php

{C}<?php 

 if (empty($model->date_parution)) {
  $model->date_parution = null;
 }
 //On lance une obfuscation afin que le widget puisse générer 
 //sans erreur le formulaire pour chaque CGridView
 $form = $this->beginWidget('CActiveForm', array(
  'method'=>'post',
  //On donne l'id défini dans la fonction javascript de la page view.php
  'id'=>'form_'.$etat
 )); 

 //On affiche le formulaire
 //Pour un gain de temps, j'ai volontairement fais une mise en page à l'aide d'un tableau
 //Oui, je sais c'est pas bien....
 echo '

  <div class="search-form" style="display:none;" id="search_'.$etat.'">

 <table summary="form" class="table-form"> 
  <tr>
   <td>'.$form->label($model,Yii::t('app','nom'),array('for'=>'nom_'.$etat)).'</td>'
   
   //On définit le nom qui sera donné au champ de recherche
     //Comme le widget est utilisé plusieurs fois
     //on crée un tableau en fonction de l'état des articles afin de bien différencier les résultats
   //On donne également un id unique à chaque champ
   '<td>'.$form->textField($model,"[$etat]nom",array('size'=>60,'maxlength'=>255,'id'=>'nom_'.$etat)).'</td>
  </tr>
  <tr>
   <td>'.$form->labelEx($model,Yii::t('app','date de parution <span>*</span>'),array('for'=>'date_parution_'.$etat,'id'=>'label_ouverture_'.$etat)).'</td>

   <td>'.$form->textField($model,"[$etat]date_parution",array('class'=>'date','id'=>'date_parution_'.$etat)).'</td>
   
  </tr>'
   //dropDownList fonctionne avec un tableau (ici $contacts défini dans le controlleur)
   '<td>'.$form->label($model,Yii::t('app','Contact'),array('for'=>'contact_'.$etat)).'</td>
   <td>'.$form->dropDownList($model,"[$etat]contact_id",$contacts,array('id'=>'contact_'.$etat)).'</td>
  </tr> 


  <tr class="buttons">
   <td colspan="2" class="centrer">'.CHtml::submitButton(Yii::t('app','Rechercher')).'</td>
  </tr> 
 </table>


 ';

 $this->endWidget(); ?>
 <?php $return = ob_get_contents();
 ob_end_clean();?>

     <?php echo $return;?>

  </div><!-- search-form -->

{C}

Le controlleur

Je ne détaillerai pas l'ensemble des méthodes du controlleur. Uniquement celle qui nous intéresse pour l'exemple.

<?php
 /**
  * Manages all articles.
  */
 public function actionArticles()
 {   
  //On appelle la méthode search en créant l'objet model
  $model = new Articles('search');
  $model->unsetAttributes();  // On supprime toutes les valeurs par défaut
  
  //On s'assure que s'il une requête est postée, elle le soit depuis le site
  if (isset($_POST['YII_CSRF_TOKEN']) && $_POST['YII_CSRF_TOKEN'] == Yii::app()->request->csrfToken) {
   //Si c'est le cas, on passe tous les arguments de POST à GET
   //car se sont les arguments de GET qui seront passés comme attributs à l'objet
   $_GET = $_POST;
  }
  
  //Si le tableau $_GET existe
  if(isset($_GET['Articles'])){
   //On passe les arguments de $_GET comme attributs de l'objet
   //en fonction de l'état d'origine de l'article.
   //Cela permet une recherche que sur un état en particulier
   if (isset($_GET['Articles']['ouvert'])){
    $model->attributes = $_GET['Articles']['ouvert'];
   }
   elseif (isset($_GET['Articles']['ferme'])){
    $model->attributes = $_GET['Articles']['ferme'];
   }
   elseif (isset($_GET['Articles']['attente'])){
    $model->attributes = $_GET['Articles']['attente'];
   }
  }
        
  //On recherche tous les contacts
  $c = Contacts::model()->findAll(array('select' => 'nom, prenom, id')); 
  //On ajoute un champ nul pour éviter que la recherche de ce champ soit obligatoire
  $contacts = array(0=>null) + $c;

  //On affiche la vue en passant toutes les variables nécessaires
  $this->render('admin',array(
   'model'=>$model,
   'contacts'=>$contacts
  ));
 }
 ?>

La méthode search() du modèle Articles

C'est le coeur de l'affichage des résultats. C'est elle qui permet le tri des résultats en fonction de l'état des articles.

<?php
 /**
  * Retrieves a list of models based on the current search/filter conditions.
  * @return CActiveDataProvider the data provider that can return the models based on the search/filter conditions.
  * @param string $etat, state of the article (opened, closed, pending) 
 */
 //On passe en argument l'état de l'article
 //Par défaut, il est nul, ce qui permet de tous les afficher
 public function search($etat = null)
 {
   //On crée un nouvel objet de type CDbCriteria qui permet de rechercher dans la base de données
   $criteria=new CDbCriteria;
   
         $criteria->together=true;
      
   //Si la recherche vient d'un état en particulier,
   //On compare les arguments de $_GET à la base de données
   if (isset($_GET['Articles'][$etat])){
    //Le premier argument est le champ de la base de données
    //Le deuxième argument est la valeur de l'attributs donné par $_GET
    //Le troisième permet de faire une recherche stricte (paramètre à false)
    //ou approximative (paramètre à true). Lors de la requête sql générée
    //l'argument est alors entouré de deux %
    $criteria->compare('t.nom',$this->nom,true);
    $criteria->compare('t.titre',$this->titre,true); 
    $criteria->compare('t.date_parution',$this->date_parution,true);
   }
   //On fait une recherche stricte en fonction de l'état des articles 
   //afin d'afficher un tableau de résultats différent par état
      $criteria->compare('t.etat',$etat,false);
   
   //On retourne les résultats au widget CGridView qui se charge de les afficher
   return new CActiveDataProvider(get_class($this), array(
    'criteria'=>$criteria,
    'sort'=>array(
              'defaultOrder'=>'t.nom ASC',
          ),
          'pagination'=>array(
              'pageSize'=>10
          )
   ));
   
         
 }
 ?>

Conclusion

Yii fournit un ensemble de widgets qui méritent d'être étudiés en profondeur car ils permettent des fonctionnalités vraiment poussées. Même si la documentation n'est pas assez exhaustive à mon goût, le forum officiel est rempli d'exemples et d'âmes charitables qui pourront vous renseigner avec plus de précision.

Yii c'est bon ! Mangez-en.

1 commentaire

  1. j'ai tt géneré avec le gii les mvc de ma base de Données.j'ai ts mes modèles vue et controlleur.A présent je veux modifier mon menu pour ajouter mes vues et autres formulaires mais je suis bloqué et je ne sais plus quel chemin emprunté. de l'aide svp

Écrire un commentaire

*


*

 Se rappeler de moi sur ce site

*
Quelle est la quatrième lettre du mot fgcznk ? :