Symfony World blog is not maintained anymore. Check new sys.exit() programming blog.

Ajax/symfony easy & user friendly example

Scene from "The Wizard of Oz" by Victor Fleming (1939)

This time I would like to present a piece of code making use of symfony built-in AJAX functionalities. We're going to build a switch for activating/deactivating users in the listing of the sfDoctrineGuardPlugin (should be the same with Propel).

Dependencies

We're gonna use two major symfony plugins: sfDoctrineGuardPlugin and sfJqueryReloadedPlugin. I'm not going to show how to install plugins here.

Problem description

The moderator of a website needs to have a quick access to the is_active attribute of all users. This means it takes too much time for him to enter the edit form of each users, change the is_active property of a user, save changes, go back to the list, then choose another one and so on. Too many times the page has to be reloaded. We need to make a simpler access to this kind of information.

Another smart use of this technique may be enabing/disabling posts/comments in a blog, guestbook, forum entries and many, many more...

Solution

First step is to add activate and deactivate to object_actions section in the sfGuardUser/config/generator.yml:

generator:
  param:
    config:
      list:
        object_actions:
          _edit: ~
          _delete: ~
          activate: ~
          deactivate: ~

Now, the templates/_list_td_actions.php has two lines added, one for activating and the other for deactivating the user. But activating an active user is senseless, such as deactivating a non-active user. So we need to display either activate or deactivate user link depending on it's is_active value:

<span id="user_is_active_action_<?php echo $sf_guard_user->getId() ?>">
  <?php if ($sf_guard_user->getIsActive()): ?>
    <?php include_partial('sfGuardUser/ajax_deactivate', array('sf_guard_user' => $sf_guard_user)) ?>
  <?php else: ?>
    <?php include_partial('sfGuardUser/ajax_activate', array('sf_guard_user' => $sf_guard_user)) ?>
  <?php endif; ?>
</span>
Now, we put above code into the _ajax_main_active.php partial. _list_td_actions template should look like this now:
<td>
  <ul class="sf_admin_td_actions">
    <?php echo $helper->linkToEdit($sf_guard_user, array(  'params' =>   array(  ),  'class_suffix' => 'edit',  'label' => 'Edit',)) ?>
    <?php echo $helper->linkToDelete($sf_guard_user, array(  'params' =>   array(  ),  'confirm' => 'Are you sure?',  'class_suffix' => 'delete',  'label' => 'Delete',)) ?>
    <?php include_partial('ajax_main_active', array('sf_guard_user' => $sf_guard_user)) ?>
  </ul>
</td>
And now, create those two templates, sfGuardUser/templates/_ajax_deactivate.php:
<li class="sf_admin_action_deactivate_user" id="ajax_deactivate_<?php echo $sf_guard_user->getId() ?>">
<?php use_helper('jQuery'); ?>
  <?php echo jq_link_to_remote(__('Deactivate', array(), 'sf_admin'), array(
    'update'   => 'user_is_active_action_'.$sf_guard_user->getId(),
    'url'      => '@ajax_sf_guard_user_deactivate?id='.$sf_guard_user->getId(),
    'script' => true,
    'complete' => jq_remote_function( array(
      'update' => 'user_is_active_column_'.$sf_guard_user->getId(),
      'url'    => 'graphics/empty',
      'script' => true
    )),
  )) ?>
</li>
sfGuardUser/templates/_activate.php is very similar:
<li class="sf_admin_action_activate_user" id="ajax_activate_<?php echo $sf_guard_user->getId() ?>">
<?php use_helper('jQuery'); ?>
  <?php echo jq_link_to_remote(__('Activate', array(), 'sf_admin'), array(
    'update'   => 'user_is_active_action_'.$sf_guard_user->getId(),
    'url'      => '@ajax_sf_guard_user_activate?id='.$sf_guard_user->getId(),
    'script' => true,
    'complete' => jq_remote_function( array(
      'update' => 'user_is_active_column_'.$sf_guard_user->getId(),
      'url'    => 'graphics/tick',
      'script' => true
    )),
  )) ?>
</li>

Seems quite a lot of templates, uh? This part is the most difficult one, since all parameters have to be set correctly. Software, such as firebug are extremely helpful, making it easy to spot any errors, mistakes, typos, etc.

The update parameter defines the id of the tag that content is going to be changed (when the AJAX action is performed). url option defines the url of the actions that is going to be performed. complete option defines what is going to be executed after the main action is completed.

Aditionally, graphics module is added inside the project (but this can be done in many many different ways). Graphics/tick action shows a tick icon and nothing more. The action code:

  /**
  * Displays tick image.
  *
  * @param sfRequest $request A request object
  */
  public function executeTick(sfWebRequest $request)
  {
  }
and the template code:
<?php echo image_tag('/sfDoctrinePlugin/images/tick.png') ?>

In the admin generators, I miss a possibility to set a custom object action as a partial (e.g. _object_action_one.php) - the only way to edit them is to override the _td_actions.php file from cache and replace custom object actions with your own code.

The main actions the are going to perform activating and deactivating users shall be defined in the routing.yml:

ajax_sf_guard_user_activate:
  url:   /ajax_user_activate/:id
  param: { module: sfGuardUser, action: activate }
  requirements:
    id: \d+

ajax_sf_guard_user_deactivate:
  url:   /ajax_user_deactivate/:id
  param: { module: sfGuardUser, action: deactivate }
  requirements:
    id: \d+

... and in the sfGuardUser/actions/actions.class.php file:

  /**
   * Activates a user from admin generator list using AJAX.
   *
   * @param sfWebRequest $request
   * @return Partial - generated partial enabling user deactivating (switch).
   */
  public function executeActivate(sfWebRequest $request)
  {
    $user = sfGuardUserTable::getUserByIdQuery($request->getParameter('id'))->fetchOne();
    $user->activate();
 
    return $this->renderPartial('sfGuardUser/ajax_deactivate', array('sf_guard_user' => $user));
  }
 
  /**
   * Deactivates a user from admin generator list using AJAX.
   *
   * @param sfWebRequest $request
   * @return Partial - generated partial enabling user activating (switch).
   */
  public function executeDeactivate(sfWebRequest $request)
  {
    $user = sfGuardUserTable::getUserByIdQuery($request->getParameter('id'))->fetchOne();
    $user->deactivate();
 
    return $this->renderPartial('sfGuardUser/ajax_activate', array('sf_guard_user' => $user));
  }

In the code above, activate and deactivate methods are called on the sfGuardUser objects, therefore we define them inside the sfGuardUser.class.php:

  /**
   * Activates user (and saves itself afterwards).
   */
  public function activate()
  {
    $this->setIsActive(true);
    $this->save();
  }
 
  /**
   * Deactivates user (and saves itself afterwards).
   */
  public function deactivate()
  {
    $this->setIsActive(false);
    $this->save();
  }

Screenshots

Let's take a look how does our mechanism work:
Above is the admin generated sfGuardUser module, users are listed.
On the right hand side, there's a Actions column, where you can find either activate or deactivate links (depending on user's is_active value).
Clicking on activate/dactivate link executes three actions:

  • the user is activated/deactivated in the database
  • the activate/deactivate button is switched to the opposite one
  • the active column's tick icon is either shown or hidden

What's next

Of course, this is just the simplest possible example. With symfony, one can develop much more complex AJAX modules. I strongly encourage to do so, since modules auto-generated with symfony are, in general, just a simple interface to the data in the database. Many modifications are needed. Last, but not leaset, AJAX can be very user-friendly. In case of any questions, don't hesitate to write them here. If you have any feedback or want to post your ideas, please, do so.

Plugin references

Some of my plugins are provided with this symfony/AJAX technique. These are:

Install them and start using symfony AJAX.

5 comments:

  1. great, thx for this nice post !

    ReplyDelete
  2. In ASP.Net 2.0,you can use a JavaScript to change GridView row color by using events Onmousemove and Onmouseout.

    ReplyDelete
  3. Recent update of this article includes links to plugins using described symfony/AJAX technique.

    ReplyDelete
  4. Oh my goodness! Impressive article dude!

    ReplyDelete