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

Ordered list of items

  • getName() ?>

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 :

  • getName() ?>

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

  • Previous ArticleNext Article

    This post has 3 Comments

    3
    1. 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

    2. 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.

    3. 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.

    Laisser un commentaire

    Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *