Lors de mes développements de ces derniers jours, je suis tombé sur un besoin qui me semblait relativement simple. Je voulais être capable de trier rapidement une liste d’éléments. Et comme, malgré tout, j’ai un peu galéré à trouver des tutoriaux à jour, je vous livre ici le fruit de mes travaux.
J’utiliserai pour ce faire la librairie jquery UI, elle même basée sur Jquery.
Attention, hein, c’est samedi matin, je suis mal réveillé, − edit : tellement mal réveillé que j’ai publié l’article avant de l’avoir terminé. fail… − alors je raccourcirai au maximum. Si je ne suis pas clair, laissez moi des commentaires.
Comme d’hab, commençons par le modèle
Je voulais trouver un outil capable de gérer un backlog de produit à la scrum, histoire de communiquer avec mes clients à distance. Cependant, malgré l’existence d’excellents outils dédiés à cette sympathique méthodologie agile, je n’ai pas réussi à trouver quelque chose de vraiment simple et répondant à mes besoins. Alors, en bon geek, j’ai choisi de coder le mien.
Project:
actAs:
Sluggable:
fields: [ title ]
unique: true
columns:
id: { type: integer, primary: true, autoincrement: true }
title: { type: string(255), notnull: true }
Story:
actAs:
Timestampable: ~
columns:
id: { type: integer, primary: true, autoincrement: true }
project_id: { type: integer, notnull: true }
description: { type: clob, notnull: true }
priority: { type: integer(2), default: 0 }
effort: { type: integer(2), default: 0 }
relations:
Project:
foreignAlias: Stories
local: project_id
foreign: id
type: one
foreignType: manyPour les paresseux, je vous colle aussi les fixtures :
Project:
demo:
title: 'Demo project'
Story:
s1:
description: 'En tant qu''anonyme, je peux m''inscrire sur le site pour devenir membre'
priority: 1
effort: 5
Project: demo
s2:
description: 'En tant que membre, je dispose d''une page d''accueil pour éditer mon profil et voir l''activité de mon réseau'
priority: 3
effort: 13
Project: demo
s3:
description: 'En tant que rédacteur, je peux écrire des articles de type magazine et les publier dans des catégories'
priority: 2
effort: 25
Project: demoVoilà pour le modèle. Je vous laisse construire tout ça, vous connaissez le topo. Créez ensuite un module « project », avec une action « show » qui affichera une liste de users stories. Je vous passe le code de l’action qui n’a rien de spécial. Dans le template « showSuccess.php » du module project, ajoutez :
// bla bla bla <div id="stories"> <?php include_partial('story/list', array('stories' => $project->getStories())) ?> </div>
Vous aurez bien entendu surchargé la fonction « getStories » pour trier les histoires par priorité.
Créez ensuite un module « story », et ajoutez-y un template « _list.php » …
<table class="stories"> <tbody> <?php foreach($stories as $story): ?> <tr class="story" id="story_<?php echo $story->getId() ?>"> <?php include_partial('story/detail', array('story' => $story)) ?> </tr> <?php endforeach ?> </tbody> <thead> <tr> <th>#id</th> <th>Description</th> <th>Effort</th> </tr> </thead> </table>
Ainsi qu’un autre template « _detail.php »
<td>
<a href="#" class="sort-button fg-button fg-button-icon-left ui-state-default ui-corner-all">
<span class="ui-icon ui-icon-arrowthick-2-n-s"></span>
<?php echo $story->getId() ?>
<a>
</td>
<td><?php echo $story->getDescription() ?></td>
<td><?php echo $story->getEffort() ?></td>Vous voilà donc avec une belle liste de users stories, affichées par ordre de priorité, et que vous souhaiteriez pouvoir réordonner par drag’n'drop.
Des p’tits tris, des p’tits tris, encore des p’tits tris…
Dans le répertoire web, ajoutez dans votre fichier js maison (créez le s’il n’existe pas) le code suivant.
$(document).ready(function() { $("#stories table tbody").sortable({ // limitons les déplacements sur l'axe des ordonnées, ce sera plus propre axis: 'y', // Il faut cliquer sur cet élément pour pouvoir initier le drag'n'drop handle: '.sort-button', // Créons un joli trou stylé lors des déplacements placeholder: 'ui-state-highlight', forcePlaceholderSize: true, // Cette fonction permet à notre ligne de conserver son formatage lors du déplacement // Pas vraiment utile, mais plus agréable à l'œil helper: function(e, tr) { var $originals = tr.children(); var $helper = tr.clone(); $helper.children().each(function(index) { // Set helper cell sizes to match the original sizes $(this).width($originals.eq(index).width()) }); return $helper; }, // La fonction appelée quand un élément change de position // C'est le code vraiment utile, en fait update: function(event, ui){ // Construit un tableau des ids des stories serial = $(this).sortable('serialize'); // Appelle une action en ajax $.ajax({ url: updateorderurl, // set in layout.php type: "post", data: serial, error: function(){ alert("Error ! Order not updated"); } }) } }); });
Remarquez que la variable « updateorderurl » contient l’url de l’action qui va réaliser la réaffectation des priorités. Comme cette url est générée par Symfony, elle est définie dans le contrôleur, puis affectée à une variable javascript dans la layout grâce à un slot. Ça vaut ce que ça vaut.
Normalement, vous devriez maintenant être capable de changer l’ordre des stories côté frontend. Bien entendu, le code métier chargé de gérer le réordonnancement n’existe pas encore.
Au cœur du métier
Créons donc une nouvelle action dans le module « story ».
public function executeUpdateOrder(sfWebRequest $request) { // Il nous faut un moyen de récupérer le projet en question $project = Doctrine::getTable('project')->find($request->getParameter('project_id')); $this->forward404Unless($project); // Correspond à la variable 'serial' dans le js, vous vous souvenez ? // C'est un simple tableau d'ids $order = $request->getParameter('story'); $project->updateStoriesOrder($order); return sfView::HEADER_ONLY; }
Nous revoilà repartis dans le modèle. Éditons notre classe Project.
// lib/model/doctrine/Project.class.php // … /** * Update the stories order * * @param array $order An array with the stories ids, sorted by priority **/ public function updateStoriesOrder(array $order) { foreach($order as $priority => $storyId) { $story = Doctrine::getTable('story') ->find($storyId); if(!$story || $story->getProjectId() != $this->getId()) throw new Exception('moo'); $story->setPriority($priority); $story->save(); } }
Tadaaaaaam ! Ça devrait fonctionner. Voilà, c’est tout. Tiens, au moment où je finis d’écrire ces lignes, je m’aperçois qu’un plugin censé faire exactement la même chose vient de sortir. Frustration. Bon, tant pis, bon week-end quand même.

3 Commentaires
Sympa l’article. J’ai expliqué le principe de sorting dans mon chapitre sur l’héritage de table Doctrine dans le livre More With Symfony :
http://www.symfony-project.org/more-with-symfony/1_4/en/09-Doctrine-Form-Inheritance
J’utilise le plugin csDoctrineActAsSortablePlugin et jQuery.
Hugo.
Merci pour cet article,
tu peux développer la partie :
« Remarquez que la variable « updateorderurl » contient l’url de l’action qui va réaliser la réaffectation des priorités. Comme cette url est générée par Symfony, elle est définie dans le contrôleur, puis affectée à une variable javascript dans la layout grâce à un slot. Ça vaut ce que ça vaut. »
C’est vrai qu’en général, utiliser les routes en js dans symfony est « alambiqué », tu vois comme quoi comme soluce pour ça ?
hola je veux réaliser une template avec Jquery mais je sais pas d’où commencer si vous pouviez me livrer plus de détails sur cela j’en serais trop reconnaissante:):)
One Trackback
[...] : http://www.miximum.fr/tutos/435-creer-une-liste-triable-avec-symfony-et-jquery-ui Cette entrée a été publiée dans AJAX & jQuery, Symfony. Vous pouvez la mettre en favoris [...]