Liste d’objets ordonnable via AJAX partie 1 bis

Ce post a pour but de reprendre la première version de la liste, en utilisant le plugin csDoctrineActAsSortablePlugin.
Ce plugin gère exactement la situation décrite dans la partie 1 en utilisant simplement un behavior dans le schema.

Voilà le lien du plugin : csDoctrineActAsSortablePlugin

Le readme est assez bien fait, cependant pour plus de clarté reprenons l’exemple précédent.

Dans un premier temps, révisons le schema.yml :
schema.yml

Item:
  actAs:
    Sortable: ~
  columns:
    name:
      type: string(255)

Le champs « rank » est remplacé par le behavior actAs: Sortable: ~

Il gère les ajout et les suppression, les surcharges des méthodes save() et delete() dans la classe Item.class.php ne sont plus utiles.

Il ajoute aussi des méthodes de tri, de classement, de positionnement etc. les méthodes déclarées dans la class ItemTable.class.php ne sont donc plus utiles non plus.

Nous allons voir maintenant à quel point le listing et les déplacements sont facilités grâce à ce plugin.
Commençons par les actions :
actions.class.php

public function executeIndex(sfWebRequest $request)
  {
    $this->items = Doctrine::getTable('Item')->findAllSorted('ASCENDING');
  }

La méthode findAllSorted() est fournie par le plugin et est associée à tout objet comportant le behavior actAs: Sortable dans le schéma.

La vue indexSuccess.php ne change pas.

En revanche, l’action déclenchée par le bouton oui :
actions.class.php:

public function executeOrder(sfWebRequest $request)
  {
    $this->elements = explode(",",$request->getParameter('elements'));
 
    Doctrine::getTable('Item')->sort($this->elements);
 
    $this->items = Doctrine::getTable('Item')->findAllSorted('ASCENDING');
    $this->setTemplate('index');
  }

Les changements sont Doctrine::getTable(‘Item’)->sort($this->elements);
Cette méthode est également liée au behavior actAs: Sortable, mais le plugin tel quel contient des erreurs.

Corrections des erreurs de la méthode sort() :
/plugins/csDoctrineActAsSortablePlugin/lib/template/Sortable.php, ligne 221

$connection->beginTransaction();

Là il manque juste la lettre « c ».

A la ligne 225 une variable $class est utilisée, mais elle n’est pas définie, ce qui génère une deuxième erreur.
Pour la corriger, il faut donc définir cette $class, qui est en fait une variable contenant la classe de l’objet à trier.
Ici il s’agirait de la classe « Item », mais soyons théorique : il faut se débrouiller pour récupérer la classe d’un objet quelconque.
Voici ma solution :
/plugins/csDoctrineActAsSortablePlugin/lib/template/Sortable.php, fonction sortTableProxy()

/**
   * Send an array from the sortable_element tag (symfony+prototype)and it will
   * update the sort order to match
   *
   * @param string $order
   * @return void
   * @author Travis Black
   */
  public function sortTableProxy($order)
  {
    /*
      TODO
        - Make this a transaction.
        - Add proper error messages.
    */
    //on récupère l'objet
    $object = $this->getInvoker();
    //on récupère la connection
    $connection = $object->getTable()->getConnection();
 
    $connection->beginTransaction();
 
    foreach ($order as $position => $id)
    {
      $newObject = $object->getTable()->findOneById($id);
 
      if ($newObject->get($this->_options['name']) != $position + 1)
      {
        $newObject->moveToPosition($position + 1);
      }
    }
 
    // Commit Transaction
    $connection->commit();
  }

Et voilà, la classe est corrigée, et l’action fonctionne à présent.

Les méthodes principales pour reclasser des éléments Sortable sont (comme indiquées dans le readme) :
pour les Record :
promote()
demote()
moveToFirst()
moveToLast()
moveToPosition($newPosition)

pour les Table :
sort($order)
findAllSorted($sortMode)
findAllSortedWithParent(…)

Voyons comment utiliser ces méthodes.
Mais d’abord, corrigeons encore quelques erreurs :
tout d’abord, le return de la méthode getFinalPosition() :

return (integer) $finalPosition;

Il faut faire un cast car cette méthode entraine une erreur de type.
Ensuite, dans la méthode moveToPosition() :

public function moveToPosition($newPosition)
  {
    if (!is_int($newPosition))
    {
      throw new Doctrine_Exception('moveToPosition requires an Integer as the new position. Entered ' . $newPosition);
    }
 
    $object = $this->getInvoker();
    $position = $object->get($this->_options['name']);
    $connection = $object->getTable()->getConnection();
 
    //begin Transaction
    $connection->beginTransaction();
 
    // Position is required to be unique. Blanks it out before it moves others up/down.
    $object->set($this->_options['name'], null);
    $object->save();
 
 
    if ($position > $newPosition)
    {
      $q = $object->getTable()->createQuery()
          ->update(get_class($object))
          ->set($this->_options['name'], $this->_options['name'].' + 1')
          ->where($this->_options['name'].' < ?',$position)
          ->andWhere($this->_options['name'].' >= ?',$newPosition)
          ->orderBy($this->_options['name'].' DESC');
 
      foreach ($this->_options['uniqueBy'] as $field)
      {
        $q->addWhere($field.' = '.$object[$field]);
      }
 
      $q->execute();
    }
    elseif ($position < $newPosition)
    {
      $q = $object->getTable()->createQuery()
          ->update(get_class($object))
          ->set($this->_options['name'], $this->_options['name'].' - 1')
          ->where($this->_options['name'].' > ?',$position)
          ->andWhere($this->_options['name'].' <= ?',$newPosition)
          ->orderBy($this->_options['name'].' ASC');
 
      foreach($this->_options['uniqueBy'] as $field)
      {
        $q->addWhere($field . ' = ' . $object[$field]);
      }
 
      $q->execute();
    }
 
    $object->set($this->_options['name'], $newPosition);
    $object->save();
 
    // Commit Transaction
    $connection->commit();
  }

Il manquait tout simplement le orderBy ASC dans le elseif, ce qui engendrait une erreur a cause du unique.

Maintenant que tout est juste, il ne reste plus qu’à créer une route et une action pour chaque méthode :
routing.yml

promote:
  url:        /promote/:id
  class:      sfDoctrineRoute
  params:     { module: item, action: promote }
  options:    { model: Item, type: object }
 
demote:
  url:        /demote/:id
  class:      sfDoctrineRoute
  params:     { module: item, action: demote }
  options:    { model: Item, type: object }
 
movetofirst:
  url:        /movetofirst/:id
  class:      sfDoctrineRoute
  params:     { module: item, action: moveToFirst }
  options:    { model: Item, type: object }
 
movetolast:
  url:        /movetolast/:id
  class:      sfDoctrineRoute
  params:     { module: item, action: moveToLast }
  options:    { model: Item, type: object }

actions.class.php

public function executePromote(sfWebRequest $request)
  {
    $this->item = $this->getRoute()->getObject();
    $this->item->promote();
    $this->items = $this->item->getTable()->findAllSorted('ASCENDING');
    $this->redirect('@homepage');
  }
 
  public function executeDemote(sfWebRequest $request)
  {
    $this->item = $this->getRoute()->getObject();
    $this->item->demote();
    $this->items = $this->item->getTable()->findAllSorted('ASCENDING');
    $this->redirect('@homepage');
  }
 
  public function executeMoveToFirst(sfWebRequest $request)
  {
    $this->item = $this->getRoute()->getObject();
    $this->item->moveToFirst();
    $this->items = $this->item->getTable()->findAllSorted('ASCENDING');
    $this->redirect('@homepage');
  }
 
  public function executeMoveToLast(sfWebRequest $request)
  {
    $this->item = $this->getRoute()->getObject();
    $this->item->moveToLast();
    $this->items = $this->item->getTable()->findAllSorted('ASCENDING');
    $this->redirect('@homepage');
  }
 
  public function executeOrder(sfWebRequest $request)
  {
    $this->elements = explode(",",$request->getParameter('elements'));
 
    Doctrine::getTable('Item')->sort($this->elements);
 
    $this->items = Doctrine::getTable('Item')->findAllSorted('ASCENDING');
    $this->redirect('@homepage');
  }

Et la vue qui correspond, avec les liens pour tester chaque méthode :
indexSuccess.php

<h1>Ordered list of items - AJAX enabled</h1>
<ul id="order">
  <?php foreach($items as $item): ?>
  <li id="<?php echo $item->getId() ?>">
      <?php echo $item->getName() ?> |
      <a href="<?php echo url_for('promote',$item) ?>">Promote</a> |
      <a href="<?php echo url_for('demote',$item) ?>">Demote</a> | 
      <a href="<?php echo url_for('movetofirst',$item) ?>">Move to first</a> |
      <a href="<?php echo url_for('movetolast',$item) ?>">Move to last</a> |
  </li>
  <?php endforeach ?>
</ul>
 
<input type="button" value="Enregistrer" onclick="submitOrder()"/>
<script type="text/javascript">
  $(function() {
    $("#order").sortable();
    $("#order").disableSelection();
  });
 
  function submitOrder()
  {
    var elements = "";
    var i=0;
    $('ul#order li').each(function()
    {
      elements += $(this).attr('id')+",";
      i++;
    })
//alert(elements.substr(0, elements.length-1));
$.post('<?php echo url_for('@order') ?>', { elements: elements.substr(0, elements.length-1) });
  }
</script>

Voilà donc tout ce qu’il faut pour utiliser basiquement ce plugin.

Partagez cet article