Tutoriel - Créer un Widget avec Yii 2.0

Auteur : Jonathan KALFA
Date : 06/01/2015 | Mis à jour : 06/01/2015
Mots clés : yii2, widget, ajax
Article vu 30050 fois
Aujourd'hui, nous allons voir comment créer un Widget avec Yii2 sur base d'un exemple concret et je l'espère suffisamment complet : un petit module de création de commentaires.


Qu'est-ce qu'un Widget

Un widget c'est une portion de code réutilisable exploitée au niveau des vues (le V dans MVC ^^). Il y a des dizaines et des dizaines de Widgets packagés dans Yii par défaut mais, ce qui nous intéresse ici, c'est de créer notre propre module. Et tant qu'à faire, je vais vous montrer comment utiliser les Widgets avec de l'Ajax et un Controller dédié (oui ça parait évident, mais les exemples du guide n'en parle pas ou peu, et les tutoriels sur internet ne sont souvent pas à jour).

1. Base de travail : le SQL et le modèle associé

Pour notre exemple, j'utilise un modèle simplifié par rapport à celui que j'utilise réellement pour ce site. L'intérêt étant de ne pas surcharger ce tutoriel qui n'en a pas besoin.
MySQL :
CREATE TABLE IF NOT EXISTS `text_comment`( 
          comment_id int(11) unsigned NOT NULL AUTO_INCREMENT, 
          text_id  int(11) unsigned NOT NULL, 
          username varchar(255) DEFAULT NULL, 
          email varchar(255) DEFAULT NULL, 
          website varchar(255) DEFAULT NULL, 
          createtime timestamp NOT NULL DEFAULT 0, 
          comment text NOT NULL, 
          status_id tinyint(1) unsigned NOT NULL, 
          PRIMARY KEY (`comment_id`) 
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

Je génère le modèle avec Gii et j'y ajoute quelques règles dans rules et une fonction pour gérer les dates d'insertion (beforeSave) :
namespace common\models; 
use Yii; 
 
class TextComment extends \yii\db\ActiveRecord { 
    const STATUS_COMMENT_INIT = 0; 
    const STATUS_COMMENT_VALIDATED = 1; 
    const STATUS_COMMENT_SPAM = 2; 
       
    /** 
    * @inheritdoc 
    */ 
    public static function tableName() 
    { 
    	return '{{%text_comment}}'; 
    } 
     
    /** 
    * @inheritdoc 
    */ 
    public function rules() 
    { 
    	return [ 
    	[['text_id', 'comment', 'username', 'email'], 'required'], 
    	[['text_id', 'status_id'], 'integer'], 
    	[['createtime'], 'safe'], 
    	[['comment'], 'string'], 
    	[['username', 'website', 'email'], 'string', 'max' => 255], 
    	[['username', 'website', 'email'], 'trim'], 
    	[['email'], 'email'], 
    	[['website'], 'url', 'defaultScheme' => 'http'], 
    	]; 
    } 
     
    public function beforeSave($insert) { 
    	if ($insert) {  
    		$this->createtime = date("Y-m-d H:i:s"); 
    		$this->status_id = TextComment::STATUS_COMMENT_INIT; 
    	} 
    	return parent::beforeSave($insert); 
    } 
}

Note : j'ai supprimé le code généré par Gii non nécessaire au tuto, donc ne vous étonnez pas d'obtenir des fonctions supplémentaires de votre côté.
Ces règles (rules), sont un aspect vraiment puissant et agréable de Yii. Elles permettent (entre autres) de :
  • Valider les champs de formulaire, si le formulaire s'appuie directement sur le modèle.
  • Ajouter des valeurs ou appliquer des fonctions par défaut (ex : trim)
  • Valider des types de champ avant insertion en base de données
Dans l'exemple, j'ajoute simplement des indications sur la nature des champs (type, taille, validateur), et ce que je veux en obligatoire dans mon formulaire. Je ne vais pas aller plus loin à ce propos, ce n'est pas le sujet ^o^.

2. Création du Widget

2.1. Répertoire de création

Celui-ci va dépendre de votre template ou de votre manière de faire. Vous pouvez créer (s'il n'existe pas) un répertoire components à votre racine d'application.
Le chemin d'accès du widget (namespace également) sera alors @app/components. De mon côté, mes widgets "Front" sont plutôt dans frontent/widgets.

2.2. Modélisation

Pour créer un widget, on utilise une classe qui étend yii\base\Widget et en surchargeant deux méthodes :
init() : Initialisation du widget et de ses variables.
run() : Exécution et rendu
Je ne vais pas reprendre l'exemple bateau du guide et tout de suite y mettre mon code :
namespace frontend\widgets;        
use yii\base\Widget; 
use yii\helpers\Html; 
use common\models\TextComment; 
 
class CommentWidget extends Widget { 
    public $content_id; 
    public $comment; 
    public $listcomment; 
     
    public function init(){ 
    	parent::init(); 
    	if($this->content_id===null){ 
    		throw new yii\web\BadRequestHttpException; 
    	} 
    	else{ 
    		$this->comment = new TextComment(); 
    		// Lien Article / Commentaire 
    		$this->comment->text_id = $this->content_id; 
     
    		// Chargement de la liste des commentaires validés 
    		$this->listcomment = TextComment::find() 
    		->where(['text_id' => $this->content_id, 'status_id' => TextComment::STATUS_COMMENT_VALIDATED ]) 
    		->orderBy('comment_id') 
    		->asArray() 
    		->all(); 
    	} 
    } 
     
    public function run(){ 
    	return $this->render('commentview',  
    	['comment' => $this->comment, 'listcomment' => $this->listcomment]); 
    }
Dans init(), je récupère les variables d'initialisation passées à mon wigdet et charge ma liste de commentaires.
Dans run(), je restitue visuellement mon bloc de commentaires (formulaire de saisi + liste des commentaires)

2.3. Les vues associées au Widget

Par défaut, les vues rendues par un widget sont à créer dans [votre/path/widget/views]. Pour notre exemple, j'ai créé 3 vues.
widget/views/commentview.php :
  
<div class="text-comment"> 
  <h1>Commentaires</h1> 
    <?= $this->render('_formcomment', [ 
      'comment' => $comment, 
    ]) ?> 
  <hr/> 
  <div id="listcomment"> 
    <?= $this->render('_listcomment', [ 
      'listcomment' => $listcomment, 
    ]) ?> 
  </div> 
</div>

widget/views/_formcomment.php :
<?php 
use yii\helpers\Html; 
use yii\widgets\ActiveForm; 
 
/* @var $this yii\web\View */ 
/* @var $model common\models\TextComment */ 
/* @var $form yii\widgets\ActiveForm */ 
?> 
<h4>Ajouter un commentaire :</h4> 
<div class="text-comment-form"> 
 
<?php $form = ActiveForm::begin([ 
	'id' => $comment->formName(), 
	'action' => ['comment/savecomment'], 
	'enableClientValidation' => true 
]); ?> 
 
<?= Html::activeHiddenInput($comment, 'text_id'); ?> 
<?= $form->field($comment, 'username')->textInput(['maxlength' => 255]) ?> 
<?= $form->field($comment, 'email')->textInput(['maxlength' => 255]) ?> 
<?= $form->field($comment, 'website')->textInput(['maxlength' => 255]) ?> 
<?= $form->field($comment, 'comment')->textarea(['rows' => 6]) ?> 
 
<div class="form-group"> 
<?= Html::submitButton($comment->isNewRecord ? 'Envoyer' : 'Update', ['class' => $comment->isNewRecord ? 'btn btn-success btn-mine' : 'btn btn-primary']) ?> 
</div> 
 
<?php ActiveForm::end(); ?> 
</div>

widget/views/_listcomment.php :
foreach(Yii::$app->session->getAllFlashes() as $key => $message) { 
   echo '<div class="alert alert-' . $key . '">' . $message . '</div>'; 
   } ?> 
   <h4>Liste des commentaires :</h4> 
   <?php  
   if(!isset($listcomment) || empty($listcomment)) { 
   		echo "Pas de commentaires pour le moment :'(<br/>"; 
   } 
   else { 
   		print_r($listcomment);  
   }
L'Art déco étant laissé à la joie et à la discrétion des lecteurs, j'ai réduit ici l'affichage au strict minimum ^o^.

2.4. Utilisation d'Ajax

Pourquoi parler d'un sujet qui pourrait être traité "à côté" ou trouvé en ligne n'importe où ? Et bien, à cause de deux changements majeurs depuis Yii 1.1 et même depuis la bêta de Yii2 :
  • AjaxButton, AjaxSubmitButton n'existent plus (permettaient de créer des appels Ajax directement depuis les formulaires Yii)
  • beforeSubmit (lieu préférentiel pour placer l'Ajax) a été retiré de la classe ActiveForm
Ce qui met à mal pas mal de tutoriels trouvés sur internet... Aussi pour utiliser Ajax, on revient à du classique qui parlera - je pense - à tous les utilisateurs de JQuery (>v1.8, pas d'utilisation de success) en ajoutant à la vue générant le formulaire (en bas du fichier widget/views/_formcomment.php) :
<?php 
$myAjaxJs = <<<JS 
// get the form id and set the event 
$('form#{$comment->formName()}') 
.on('beforeSubmit',  
	function(e) { 
		var \$form = $(this); 
		if(\$form.find('.has-error').length) { 
			return false; 
		} 
 
		$.ajax({ 
			url: \$form.attr('action'), 
			type: 'post', 
			data: \$form.serialize(), 
			dataType : 'html' 
		}) 
		.done(function(data) { 
			$('#listcomment').html(data); 
			\$form[0].reset(); 
		}); 
	return false; 
	} 
) 
.on('submit',  
	function(e){ 
		e.preventDefault(); 
} 
); 
JS; 
$this->registerJs($myAjaxJs); 
?> 
    

En quelques mots :
  • On accroche au formulaire par son id un event Js sur le beforeSubmit
  • Cet event effectue l'appel Ajax vers le controller (url: comment/savecomment) en post avec les datas serialisées.
  • On empêche le submit final

2.5. Controller dédié

J'ajoute à la liste de mes controllers frontend (répertoire dépendant de votre template) - frontend/controllers/CommentController :
<?php
namespace frontend\controllers;
use common\models\TextComment; 
use Yii; 
 
class CommentController extends \yii\web\Controller 
{ 
 
	public function actionSavecomment(){ 
		$model = new TextComment(); 
		if (Yii::$app->request->isAjax && $model->load(Yii::$app->request->post()) && $model->save()) { 
			Yii::$app->session->setFlash('success', 'Commentaire ajouté !'); 
		}  
		else { 
			Yii::$app->session->setFlash('danger', 'Problème lors de l\'ajout du commentaire... Désolé'); 
			// Ecriture dans les logs applicatifs 
			Yii::error($model->getErrors()); 
		}  
		 
		// Réinit widget 
		$listcomment = $this->listcomment($model->text_id); 
		 
		// Refresh widget 
		return $this->renderAjax('@frontend/widgets/views/_listcomment',  
		['listcomment' => $listcomment]); 
	} 
	 
	 
	private function listcomment($text_id) { 
		return TextComment::find() 
		->where(['text_id' => $this->content_id, 'status_id' => TextComment::STATUS_COMMENT_VALIDATED]) 
		->orderBy('comment_id') 
		->asArray() 
		->all();  
	} 
} 
    
Une seule action est présente dans ce controller : actionSavecomment. Comme d'habitude en Yii l'accès se fait via comment/savecomment.
Dans ce controller, on test si on atterrit bien ici via un appel Ajax, on charge les datas formulaires dans un objet commentaire, et on tente le save().
load() et save() sont des méthodes gérées par Yii via l'héritage à ActiveRecord de votre modèle TextComment. Le save() va appeler tous les validateurs et fonctions associés dans les rules de votre modèle et... voilà !

On notera ici l'utilisation d'un renderAjax, qui fait globalement la même chose qu'un renderPartial, soit le rendu d'une portion de code sans application d'un layout (c'est donc bien ce qu'on veut ici puisqu'on rafraîchit un bout de page). La différence c'est que le renderAjax injecte le résultat "avec" les JS/CSS et fichiers enregistrés dans le vue (registerJs, etc).

Note : les plus avisés d'entre vous auront remarqué que ma gestion d'erreur est très "lite" mais encore une fois ce n'est pas l'objet du tutoriel.

Le mot de la fin

Normalement avec Yii 2.0.1, l'exemple doit être à peu prèt fonctionnel (j'ai beaucoup épuré donc à voir ^o^). Vous pouvez bien sûr enrichir à foison avec gestion d'avatars, captcha, users enregistrés, etc...

Ah oui, et si vous souhaitez comme moi une gestion des arbres de réponses tout en étant sous MySQL (pas de fonctions récursives) : je vous recommande de regarder du côté de la gestion des arborescences par représentation intervallaire.

Je ne pense pas avoir couvert tout le scope des widgets (l'univers des possibilités Yii est vaste) mais j'espère que ce tutoriel vous donnera les bases pour faire à peu près tout ce que vous voulez.

Jonathan KALFA
[ @Jokabox | @Jokamax ]

Vous avez aimé ? Partager !

Commentaires

Ajouter un commentaire :


Liste des commentaires :

Pas de commentaires pour le moment :'(
Vous aimez ? Aidez-nous !

Vous aimez nos outils et nos tutoriels ? N'hésitez pas à nous le faire ressentir par un petit geste de temps en temps. :-)

Social Feeling
Publicités
Derniers Commentaires
Pas de commentaires pour le moment :'(