samedi 25 juin 2011

CGridView : effacement de lignes par lot

Décidément, le composant CGridView est à la mode. Après la mise en forme des colonnes et celle des cellules, voici un nouvel article sur ce composant bien utile fourni en standard par le framework Yii.
Aujourd'hui, nous allons voir comment faire pour effacer les lignes que nous aurons coché, puis comment rafraîchir le CGridView sans recharger l'ensemble de la page.

La première étape consiste à initialiser notre CGridView de façon à ce que la première colonne de chaque ligne soit occupée par par une case à cocher....ainsi nous pourrons sélectionner les lignes à effacer. Par chance Yii permet de mettre en place cette configuration très facilement. Deux points sont à retenir :

  1. selectableRows : indique combien de lignes peuvent être sélectionnées. Par défaut cette valeur est à 0 (aucune ligne sélectionnable). A 1, une seule ligne est sélectionnable, et sinon, plusieurs lignes sont sélectionnables.
  2. CCheckBoxColumn : cette classe permet de définir le contenu d'une colonne comme devant être une case à cocher (juste ce qu'il nous faut)
Dans l'exemple suivant nous déclarons donc notre CGridView avec une première colonne contenant notre belle case à cocher. Ce n'est pas suffisant, mais c'est un bon début.

<?php 
   $this->widget('zii.widgets.grid.CGridView', array(
      
      // cet identifiant est important car c'est grâce à lui que
      // nous allons faire référence au CGridView plus tard, depuis
      // les fonctions javascript getSelection() et update()
     
      'id'=>'data-grid',
      'dataProvider'=>$model->search(),

      // ici, on indique que une ou plusieurs colonnes
      // pourront être sélectionnées. La valeur par défaut est 0
      // ce qui indique qu'aucune ligne n'est sélectionnable   

      'selectableRows'=>2,
      'columns'=>array(
          
          // La première colonne est utilisé pour afficher la case à
          // cocher
          
          array(
            'class'=>'CCheckBoxColumn',
          ),
        
          // enfin, les colonnes suivantes contiendront les attributs
          // de notre modèle
  
          'name',
          'address'
      ),
   )); 
?>

La deuxième étape concerne l'effacement à proprement parlé, ainsi que la mise à jour du CGridView. Bien sûr, tout autre traitement par lot est envisageable, mais pour cet exemple, l'utilisateur pourra :

  1. sélectionner une ou plusieurs colonnes
  2. cliquer sur un hyperlien (ajaxLink) pour effacer les colonnes sélectionnées

Lorsque l'utilisateur cliquera sur le lien d'effacement, nous allons collecter toutes les valeurs des cases cochées (qui sont en fait les id des instances affichées), et les envoyer (ajax - POST) vers une action Yii qui sera chargée de les supprimer de la base de données. Cette action renverra le nombre d'éléments effectivement modifiés et si cette valeur n'est pas nulle, le contenu du CGridView sera mis à jour.


<?php
   echo CHtml::ajaxLink("Delete Selected",
   
      // route vers l'action en charge d'effacer les éléments
      // depuis la base de données (voir plus bas pour le code)
      
       $this->createUrl('deleteBulk'),
       array(
      
          "type" => "post",   // POST donc.
         
         // les identifiants des modèles sélectionnées sont accessibles
         // via la méthode $.fn.yiiGridView.getSelection à laquelle
         // on passe l'identifiant du CGridView visé (ici 'data-grid').
         
          "data" => "js:{ids:$.fn.yiiGridView.getSelection('data-grid')}",
         
         // cette fonction javascript traite la réponse renvoyée par
         // l'action 'deleteBulk'. La variable 'data' contient le nombre
         // d'enregistrement supprimés. 
         // La mise à jour du CGridView se fait via la méthode
         // $.fn.yiiGridView.update (à qui on passe l'identifiant du CGridView)
         
          'success'=> 'js:function(data, textStatus, XMLHttpRequest){'.
          'if(data!=0)$.fn.yiiGridView.update("data-grid")}'
       )
    );
   
   // déclarer le CGridView ici...
?>

Toute la magie ajax est possible grâce aux fonctions javascript mise à disposition par le CGridView : getSelection() et update()
Enfin, dernière étape, l'action deleteBulk, qui est appelée (ajax) pour effacer les lignes dont les ids sont  passées dans la requête (POST). Bon, là c'est du classique, tout (trop?) simple pour les besoins de cet exemple.

public function actionDeleteBulk(){
      $modelDeleted=0;
      
      if( Yii::app()->request->isPostRequest && 
         isset($_POST['ids']) && 
         is_array($_POST['ids']))
      {
         foreach ($_POST['ids'] as $modelId){
            $this->loadModel($modelId)->delete();
         }
      }
      echo $modelDeleted;
   }   

Et voilà. Pour conclure, les liens habituels :


lundi 20 juin 2011

CGridView : formater les cellules

Dans un précédent post, nous avons vu comment il est possible de définir ce qui sera affiché dans les colonnes du widget CGridView. Le principe consistait à décrire sous la forme d'un tableau associatif, ce que Yii allait devoir afficher dans une colonne donnée. La valeur du paramètres 'value' en particulier, est évalué par Yii et c'est le résultat de cette évaluation qui est affiché dans la colonne.
C'est bien....
C'est bien mais ça peut devenir vite rébarbatif à écrire surtout si la valeur de la colonne est le résultat d'une expression complexe.. on s’emmêle vite les pinceaux entre les guillemets simples, doubles ... bref, c'est pas la joie. Dans un tel cas, le CGridView permet au développeur de définir une classe spécifique, dédié à la création du contenu d'une colonne, et ça, c'est plus que bien ! La classe CDataColumn (oui, c'est son nom), est utilisée par le CGridView, pour produire les chaînes de caractères qui viendront peupler ses colonnes. Cette classe accepte différents paramètres comme par exemple :

  • header : le titre de la colonne
  • footer : le pied de colonne
  • name : le nom de l'attribut associé à une colonne
  • type : pilote la façon dont le contenu de la colonne sera créé
  • value : une expression PHP dont l'évaluation sera interprétée comme le contenu de la colonne
  • etc ....
Nous avons déjà rencontré ces paramètres, et dans la plupart des cas, nous nous en accommoderons sans problème, mais pour ceux qui en veulent plus, ils ne sont pas suffisant. La solution consiste alors à définir notre propre classe, héritant de CDataColumn, puis d'indiquer au CGridView que c'est cette classe (la notre) qu'il devra utiliser plutôt que la classe de base CDataColumn.

Dans l'exemple suivant, nous avons donc créé notre propre classe afin de prendre en charge la création du contenu d'une colonne, dont le rôle est d'afficher une petite image d'enveloppe ouverte (pour les messages lus) ou fermée (pour les messages non lus) ... oui, il s'agit d'un exemple de boîte à lettres.

<?php
Yii::import('zii.widgets.grid.CDataColumn');

class MessageStatusColumn extends CDataColumn {

   // nous surchargons cette méthode qui est invoquée par le CGridView
   // chaque fois que le contenu d'une cellule doit être créé
   // L'argument $data représente l'instance affichée sur la ligne
   // correspondante.

   public function renderDataCellContent($row,$data)
   {
      $imgPath=Yii::app()->theme->baseUrl.'/images';

      // Bon, c'est juste un exemple, mais en gros le but est de générer
      // un tag image (IMG) en fonction de la valeur renvoyée par la
      // méthode hasBeenReadByUser() .. disponible au niveau du modèle
      // Message (le type de la variable $data)

      echo CHtml::image(
         $imgPath.'/'.($data->hasBeenReadByUser()?
            'email_read.png':
            'email_unread.png'),
         ($data->hasBeenReadByUser()?'':'*'),
         array(
            'title' => ($data->hasBeenReadByUser()?
               'message read':
               'message not read')
         )
      );
   }    
}
?>

D'autre méthodes de la classe CDataColum pourraient aussi être surchargées, mais pour les besoins de cet exemple, restons simples. Maintenant il ne reste plus qu'à déclarer notre CGridView, en précisant bien que c'est notre propre classe (MessageStatusColumn) qu'il convient d'utiliser pour l'affichage de la première colonne.

<?php 

$this->widget('zii.widgets.grid.CGridView', array(
   'id'=>'message-grid',
   'dataProvider'=>$model->search(),
   'columns'=>array(    
       array(
         
         // Ici on défini la classe à utiliser pour
         // afficher cette colonne
         
          'class'=> 'MessageStatusColumn',
          'name' => 'flags',
       ),
   // etc ...


Et le tour est joué !
Pour aller plus loin, les habituels liens :

lundi 13 juin 2011

formulaire : ajout de paramètre

Il peut arriver qu'un formulaire propose à l'utilisateur plusieurs boutons d'envoi, chacun ayant une signification différente. Par exemple un formulaire permettant pour l'écriture d'un message privé à destination d'un utilisateur, peut disposer d'un bouton 'envoyer' ainsi que d'un bouton 'sauvegarder'. Dans les deux cas, le formulaire pourra être traité par la même action, mais en fonction d'un paramètre additionnel, celle-ci procèdera uniquement à la sauvegarde du message (comme brouillon), ou bien sauvegardera puis enverra le message. La question est alors : comment rajouter ce paramètre ?
La solution proposée ici consiste à utiliser les paramètres de la méthode CHtml::submitButton() pour rajouter un paramètres GET à l'adresse d'envoi du formulaire. Ce paramètre (appellons-le 'envoi') a la valeur 1 ou 0 selon que l'on souhaite envoyer ou seulement sauvegarder (comme brouillon).

<div class="row buttons">
      <?php echo   
         CHtml::submitButton('Envoyer',array(
            // optionnel (pour le style)   
            'class' =>'button',
            // premier bouton : envoyer 
            // On utilise la paramètre 'submit' pour indiquer explicitement
            // l'adresse d'envoi du formulaire, et on en profite pour y
            // rajouter notre paramètre 'envoi=1'    
            'submit'=>$this->createUrl($this->route,array('envoi'=>1)),
         ));
      ?>         
      <?php echo 
         // second bouton : sauvegarder uniquement
         // Cette fois le paramètre 'envoi' est à 0
         CHtml::submitButton('Sauvegarder comme brouillon',array(
            'class' =>'button',
            // second bouton : sauvegarder uniquement
            // Cette fois le paramètre 'envoi' est à 0   
            'submit'=>$this->createUrl($this->route,array('envoi'=>0)),
         ));
      ?>     
   </div>


Côté contrôleur, c'est du classique : on rajoute le paramètres 'envoi' comme argument de l'action visée. Il sera automatiquement lié (bind) à la valeur de notre paramètre 'envoi'.
Bon, ça va pas chercher bien loin, mais ça peut toujours être utile ...
Pour aller plus loin :