Liste d’objets ordonnable via AJAX partie 1

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

    Partagez cet article