Navigation

Related Articles

Back to Latest Articles

Liste d’objets ordonnable via AJAX partie 1 bis


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

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.

Show Comments (2)

Comments

  • Éric Rogé

    J’avais relevé ces bugs lors de ma précédente utilisation du projet, malheureusement les développeurs de centre{source} ont refusé mon aide pour corriger les bugs.
    Comme j’en avais un usage très basique du plugin sur mon précédent projet, j’ai réussi à passer outre.

    Merci pour tes corrections, elles devraient m’économiser pas mal de temps de débuggage sur mon nouveau projet. Par contre, au vue du manque de suivi du plugin, je pense qu’il vaux mieux dupliquer le plugin et le corriger que le laisser se mettre à jour.

    • Article Author
  • Lionel

    Super bon article, chapeau bas !

    • Article Author

Recevez nos articles