Embauchez-moi

Je suis freelance ! Besoin d'un professionnel du développement web ? Pourquoi ne pas me passer un coup de fil ?

Plus d'infos sur… http://thibault.jouannic.fr

mots-cles : Ingénieur web freelance Symfony eZ Publish Solr

Créer une liste triable avec Symfony et jquery ui

Symfony backlog
Creative Commons License

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: many

Pour 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: demo

Voilà 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

  1. Posté le 23/01/2010 à 18:04 | Permalien

    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.

  2. Cyril
    Posté le 05/03/2010 à 11:59 | Permalien

    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 ?

  3. kamilia
    Posté le 10/04/2010 à 23:06 | Permalien

    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

  1. [...] : 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 [...]