Code snippets for symfony 1.x

Navigation

Double List for ManyToMany relationships (deprecated)

I needed the object_admin_double_list helper of the admin generator for my regular projects (without using admin generator). So I ported the admin_double_list helper a bit and now I want to publish this for you.

The admin_double_list helper is for selecting multiple items from a pool of items using ManyToMany relationships. For example to associate a user to multiple groups.

The helper itself has two parts: - two helper functions - two javascript functions

After that I have an example how the three controller, view and model parts handle this helper.

The snippet itself

Helper file

/**
 * two multiline select tags with associated and unassociated items
 *
 * @return string
 * @param object object
 * @param string method of object
 * @param array options
 * @param array html options of select tags
 **/
function double_list($object, $method, $options = array(), $html_options = array())
{
  $options = _parse_attributes($options);
 
  // get the lists of objects
  list($all_objects, $objects_associated, $associated_ids) = _get_object_list($object, $method, _get_option($options, 'through_class'));
 
  // options
  $html_options['multiple'] = _get_option($html_options, 'multiple', true);
  $html_options['size'] = _get_option($html_options, 'size', 10);
  $html_options['class'] = 'double_list';
 
  $label_assoc = _get_option($options, 'associated_label', 'Zugehörige Gruppen');
  $label_all   = _get_option($options, 'unassociated_label', 'Gruppenliste');
  $name1 = _get_option($options, 'associated', 'associated');
  $name2 = _get_option($options, 'unassociated', 'unassociated');
  $form = _get_option($options, 'form_id', 'editForm');
 
  // unassociated objects
  $objects_unassociated = array();
  foreach ($all_objects as $object)
  {
    if (!in_array($object->getPrimaryKey(), $associated_ids))
      $objects_unassociated[] = $object;
  }
 
  // select tags
  $select1 = select_tag($name1, options_for_select(_get_options_from_objects($objects_associated), '', $options), $html_options);
  unset($html_options['class']);
  $select2 = select_tag($name2, options_for_select(_get_options_from_objects($objects_unassociated), '', $options), $html_options);
 
  // output skeloton
  $html =
'<div style="float:left; padding-right: 20px;">
  <label for="%s">%s</label>
  %s
</div>
<div class="float:left; padding-right: 20px; padding-top: 20px">%s<br />%s</div>
<div class="float:left;">
  <label for="%s">%s</label>
  %s
</div>
<div style="clear:both"></div>';
 
  // include js library
  $response = sfContext::getInstance()->getResponse();
  $response->addJavascript('/js/double_list.js', 'last');
 
  return sprintf($html,
      $name1, $label_assoc, $select1,
      link_to_function(image_tag('resultset_previous'), "double_list_move(\$('{$name2}'), \$('{$name1}'))"),
      link_to_function(image_tag('resultset_next'), "double_list_move(\$('{$name1}'), \$('{$name2}'))", 'style=display:block'),
      $name2, $label_all, $select2,
      $form
    );
}
 
/**
 * retrieve object list via propel
 *
 * @return array
 * @param object root object
 * @param string retrieving method
 * @param string name of satellite class
 **/
function _get_object_list($object, $method, $middleClass)
{
  // get object
  $object = $object instanceof sfOutputEscaper ? $object->getRawValue() : $object;
 
  // get all objects
  $objects = sfPropelManyToMany::getAllObjects($object, $middleClass);
  // get related objects
  $objects_associated = sfPropelManyToMany::getRelatedObjects($object, $middleClass);
  // get ids
  $ids = array_map(create_function('$o', 'return $o->getPrimaryKey();'), $objects_associated);
 
  return array($objects, $objects_associated, $ids);
}
 

You probably want to modify the look of the list using css (like I do). So you can change the $js variable like you want f.e. adding class names. And you perhaps also want to change the image paths (I used two icons of the famfamfam icon library).

The javascript

Put the code below in a file called double_list.js in your js directory. (For individual path and file name, modify the double_list helper, search for $response)

function double_list_move(src, dest)
{
  for (var i = 0; i < src.options.length; i++)
  {
    if (src.options[i].selected)
    {
      dest.options[dest.length] = new Option(src.options[i].text, src.options[i].value);
      src.options[i] = null;
      --i;
    }
  }
}
 
function double_list_submit()
{
  // get all selects with double list class
    selects = $$('select.double_list');
 
    selects.each(function(element){
        for (var i = 0; i < element.options.length; i++)
            element.options[i].selected = true;
    });
 
    return true;
}
 

Example

I would like to show an example how to handle this helper in the three patterns.

The example is easy. Assigning an user to many groups.

Model layer

We have to build a table which handles the ManyToMany relationship.

user_group:
  _attributes:
    phpName:    UserGroup
  group_id:
    type:       integer
    primaryKey: true
    foreignTable:groups
    foreignReference:id
    onDelete:   cascade
  user_id:
    type:       integer
    primaryKey: true
    foreignTable:users
    foreignReference:id
    onDelete:   cascade
 

Don't forget to rebuild all db stuff and to clear the cache.

Presentation layer

Now we display the double_list:

<?php echo double_list($user, 'getUserGroups', 'through_class=UserGroup associated=groups unassociated=not_groups associated_label=Associated Groups unassociated_label=Group list') ?>
 

The first parameter is the user object, than the method of the object retrieving the UserGroup records. I think the options are clear.

Controller layer

At last we have to save the selection of the user. Before doing this we have to delete all UserGroup objects of the user, because we would assign it twice, if the item was selected before.

// clear group data to save it again
$c = new Criteria();
$c->add(UserGroupPeer::USER_ID, $user->getId());
UserGroupPeer::doDelete($c);
 
// save groups
$groups = $this->getRequestParameter('groups');
if ($groups)
{
  foreach ($groups as $id)
  {
    $group = new UserGroup();
    $group->setGroupId($id);
    $group->setUserId($user->getId());
    $group->save();
  }
}
 

It was a bit too long. As I said, this is a port of the original object_admin_double_list helper, which can be practically only used with the Admin Generator.

Please check the snippet, because I use this in a little bit more customized version.

by Halil Köklü on 2007-07-04, tagged admin  form  propel 

Comments on this snippet

gravatar icon
#1 KRavEN on 2007-07-12 at 02:39

If you want to use the helper as a standalone custom helper in your application's lib/helper/ directory you need to copy the function link_to_function from symfony/helper/JavascriptHelper.php into your customer helper.

gravatar icon
#2 Halil Köklü on 2007-07-21 at 08:55

I've tested the official ObjectAdmin helper (select list etc.) on my current project. It functions too. So i think this is deprecated. Use the official one instead.