Navigation

Related Articles

Back to Latest Articles

Liste d’objets ordonnable via AJAX partie 1


Samuel Breton
Liste d’objets ordonnable via AJAX...

Aujourd’hui, voyons les fonctions sortables permettant de changer la position d’objet, de les ordonner et de sauvegarder l’ordre. C’est toujours pratique pour organiser une galerie photo, ou des éléments dans votre site internet.

Toutes les informations sur l’objet que nous souhaitons ordonner et/ou classer seront stockées en base :
schema.yml

Item:
  columns:
    name:
      type: string(255)
    rank:
      type: integer
      notnull: true
      unique: true


L’attribut ‘rank’ étant notnull, il faut forcément qu’il soit définit lors du save(), et étant unique on ne peut pas le définir par défaut.
Il faut donc surcharger la méthode save() :
Item.class.php

public function save(Doctrine_Connection $con = null)
{
   // les nouveaux enregistrements seront ajoutés à la suite, donc avec un rang élévé de 1 par rapport au rang max
   if($this->isNew())
   $this->setRank($this->getTable()->getMaxRank()+1);
 
   return parent::save($con);
}

Il faut aussi surchager la méthode delete() pour redescendre tous les éléments de rang supérieur lors de la suppression :
Item.class.php

public function delete(Doctrine_Connection $con = null)
{
   //tous les enregistrements au dessus descendent d'une place
  $items = Doctrine_Query->create()->from('Item')->where('rank > ?', $this->getRank())->execute();
 
   // delete the item
   $ret = parent::delete();
 
  foreach($items as $item)
  {
    $item->setRank($item->getRank()-1);
    $item->save();
  }
  return $ret;
}

Nous avons donc besoin d’une méthode permettant de récupérer le rang max :
ItemTable.class.php

//permet de récupérer le rang le plus haut
public function getMaxRank()
{
  $query = Doctrine_Query::create()
        ->select('MAX(rank)')
        ->from('Item i');
 
    $tmp = $query->execute(array(),Doctrine::HYDRATE_NONE);
 
    return $tmp[0][0];
}

Ici on passe en paramètre Doctrine::HYDRATE_NONE pour que la méthode ne retourne pas une collection. Le retour est un tableau à 2 entrées, mais la requête ne retournant qu’un résultat, il est disponible en [0][0].

Passons maintenant à la vue.
Il s’agit d’afficher la liste des items, tout simplement.
Mais pour les afficher, il faut les charger, voyons donc d’abord l’action :
actions.class.php

public function executeIndex()
{
  $this->items = Doctrine::getTable('Item')->getAllOrderedByRank();
}

Un coup d’oeil tout de suite sur la méthode getAllOrderedByRank() :
ItemTable.class.php

public function getAllOrderedByRank()
  {
    $query = Doctrine_Query::create()
        ->from('Item')
        ->orderBy('rank ASC');
 
    return $query->execute();
  }

Maintenant voyons la vue :
indexSuccess.php

<h1>Ordered list of items</h1>
<ul>
  <?php foreach($items as $item): ?>
  <li id="<?php echo $item->getId() ?>">
      <?php echo $item->getName() ?>
  </li>
  <?php endforeach ?>
</ul>

Passons maintenant aux choses sérieuses.
Le framework Javascript JQuery comporte des fonctions permettant de créer des liste dont les éléments sont « drag n’ droppables », ici c’est celles ci que l’on va utiliser.
Pour cela, il faut modifier la vue, afin de rajouter le script jquery, et de spécifier une id à la liste, id que le script jquery utilisera pour cibler la liste à ordonner :

<ul id="order">
  <?php foreach($items as $item): ?>
  <li id="<?php echo $item->getId() ?>">
      <?php echo $item->getName() ?>
  </li>
  <?php endforeach ?>
</ul>
 
<input type="button" value="Enregistrer" onClick="submitOrder()"/>
<script type="text/javascript">
  $(function() {
    //on spécifie la liste
    $("#order").sortable();
    $("#order").disableSelection();
  });
 
  function submitOrder()
  {
    //initialisation de la chaine contenant les id des items
    var elements = "";
 
    //parcours de la liste d'items
    $('ul#order li').each(function()
    {
      //pour chaque item, on concatene l'id dans la chaine
      elements += $(this).attr('id')+",";
    })
  //on execute l'action, a laquelle on envoie en param la liste des id
  $.post('<?php echo url_for('@order') ?>', { elements: elements.substr(0, elements.length-1) });
  }
</script>

Vous aurez noté l’ajout du bouton submit. C’est en cliquant sur celui-ci que l’on déclenche l’action qui sauvegarde le nouvel ordre des items.
Pour enregistrer l’ordre des items, chaque

  • a pour id l’id de l’item en question.
    Lors du click on exécute un script qui récupère toutes les id dans l’ordre et qui les concatène dans une chaîne de caractères en les séparant par des « , ». Cette chaîne est envoyée en paramètre à l’action.
    Routing :
    routing.yml

    order:
      url:      /order-item
      params:  { module: item, action: order }

    L’action :
    actions.class.php

    public function executeOrder(sfWebRequest $request)
      {
        //on récupère les id, que l'on place dans un tableau pour un parcours plus aisé
        $this->elements = explode(",",$request->getParameter('elements'));
     
        //on parcours les id, on récupère l'item correspondant, et on met son rang à jour
        foreach($this->elements as $key=>$element_id)
        {
          $item = Doctrine::getTable('Item')->find($element_id);
          $item->setRank($key);
          $item->save();
        }
     
        $this->items = Doctrine::getTable('Item')->getAllByRank();
     
        //on affiche à nouveau la liste des items, mis à jour
        $this->setTemplate('index');
      }

    Voilà, vous aurez noté que les nouveaux rangs sont tout simplement la position dans la liste, partant de 0.

    Dans la prochaine partie, nous verront comment implanter cette fonctionnalité dans votre backend avec l’admin generator

  • Show Comments (3)

    Comments

    • Éric Rogé

      Le plugin csDoctrineActAsSortablePlugin marche déjà pas mal du tout, tu devrais y jeter un coup d’oeil.

      http://www.symfony-project.org/plugins/csDoctrineActAsSortablePlugin

      Pour la réorganisation d’éléments, il est toujours plus agréable de faire ça en drag & drop quand la liste est suffisamment courte.
      Fabien avait rédiger un tutoriel sur la question, il est basé sur Prototype, mais les changements pour l’adapter à jQuery UI et autres sont minimes : http://www.symfony-project.org/cookbook/1_2/en/sortable

      • Article Author
    • Nikaw

      J’ai déjà lu le tuto de Fabien sur le CookBook, je me suis même basé dessus, mais il utilise une méthode du helper javascript, et sachant que les helpers javascript et form seront retirés de symfony sur la version 1.4 il fallait faire sans.

      En revanche, j’avais pas pensé à chercher dans les plugins, et c’est vrai que celui dont tu donnes le lien à l’air puissant, et surtout ultra-pratique ! Merci.

      • Article Author
    • Nikaw

      Pour ceux que ça intéresse, j’ai testé ce plugin.
      Il est effectivement très bien.
      En réalité, c’est le contraction de tout ce qui est expliqué dans ce post dans un behavior, ce qui est excessivement pratique.

      En revanche, il comporte des erreurs, en gros il n’a pas l’air fini.

      J’en ai corrigé une ou deux, il fonctionne sur cette implémentation succinte, mais impossible de savoir s’il subsiste d’autres erreurs (des variables non définies dans les classes, des lettres manquantes : « beginTransation » au lieu de « beginTransaction »).

      Je ferais probablement un petit post dessus prochainement.

      • Article Author

    Recevez nos articles