I implemented exactly this before Christmas.  I actually did two
versions, one with a sortable list and a box standard select based one.

I meant to package it up properly but haven't yet found the time, so
I'll embed it inline here (yuck, sorry!)

SortableObjectAdminHelper.php:

<?php

$request = sfContext::getInstance()->getResponse();
$request->addJavascript('admin_double_sortable_list');


function object_admin_double_sortable_list($object, $method, $options =
array ()) {

        if ($options['rich']==true) {
                return _object_admin_double_sortable_list_ajax($object, $method,
$options);
        } else {
                return _object_admin_double_sortable_list_normal($object, 
$method,
$options);
        }


}


function _object_admin_double_sortable_list_normal($object, $method,
$options = array(), $callback = null)
{
 $options = _parse_attributes($options);

 $options['multiple'] = true;
 $options['class'] = 'sf_admin_multiple';
 if (!isset($options['size']))
 {
   $options['size'] = 10;
 }
 $label_all   = isset($options['unassociated_label']) ?
$options['unassociated_label'] : 'Unassociated';
 $label_assoc = isset($options['associated_label'])   ?
$options['associated_label']   : 'Associated';

        if ($callback==null) $callback = '_get_propel_object_list_sorted';

 // get the lists of objects
 list($all_objects, $objects_associated, $associated_ids) =
_get_object_list($object, $method, $options, $callback);

 $objects_unassociated = array();
 foreach ($all_objects as $object)
 {
   if (!in_array($object->getPrimaryKey(), $associated_ids))
   {
     $objects_unassociated[] = $object;
   }
 }

 // override field name
 unset($options['control_name']);
 $name  = _convert_method_to_name($method, $options);
 $name1 = 'unassociated_'.$name;
 $name2 = 'associated_'.$name;
 $select1 = select_tag($name1,
options_for_select(_get_options_from_objects($objects_unassociated),
'', $options), $options);
 $options['class'] = 'sf_admin_multiple-selected';
 $select2 = select_tag($name2,
options_for_select(_get_options_from_objects($objects_associated), '',
$options), $options);

 $html =
'<div>
 <div style="float: left">
   <div style="font-weight: bold; padding-bottom: 0.5em">%s</div>
   %s
 </div>
 <div style="float: left">
   %s<br />
   %s
 </div>
 <div style="float: left">
   <div style="font-weight: bold; padding-bottom: 0.5em">%s</div>
   %s
 </div>
 <div style="float: left">
   %s<br />
   %s
 </div>
 <br style="clear: both" />
</div>
';
$updown =
'<img src="/images/moveup.gif" alt="Move Up"
onclick="moveOptionsUp(\''.$name2.'\')" /><br />
<img src="/images/movedown.gif" alt="Move Down"
onclick="moveOptionsDown(\''.$name2.'\')" />';

 return sprintf($html,
   $label_all,
   $select1,

submit_image_tag(sfConfig::get('sf_admin_web_dir').'/images/next.png',
"style=\"border: 0\" onclick=\"double_list_move(\$('{$name1}'),
\$('{$name2}')); return false;\""),

submit_image_tag(sfConfig::get('sf_admin_web_dir').'/images/previous.png',
"style=\"border: 0\" onclick=\"double_list_move(\$('{$name2}'),
\$('{$name1}')); return false;\""),
   $label_assoc,
   $select2,
   submit_image_tag('/images/up.png', "style=\"border: 0\"
onclick=\"moveOptionsUp('{$name2}'); return false;\""),
   submit_image_tag('/images/down.png', "style=\"border: 0\"
onclick=\"moveOptionsDown('{$name2}'); return false;\"")
 );
}



function _object_admin_double_sortable_list_ajax($object, $method,
$options = array ()) {

        $options = _parse_attributes($options);

        $options['multiple'] = true;
        $options['class'] = 'sf_admin_multiple';
        if (!isset ($options['size'])) {
                $options['size'] = 10;
        }
        $label_all = isset ($options['unassociated_label']) ?
$options['unassociated_label'] : 'Unassociated';
        $label_assoc = isset ($options['associated_label']) ?
$options['associated_label'] : 'Associated';

        // get the lists of objects
        $through_class = _get_option($options, 'through_class');
        $sort = _get_option($options, 'sort');
        $c = new Criteria();
        if ($sort) $c->addAscendingOrderByColumn(constant($sort));
        $objects_associated = sfPropelManyToMany :: getRelatedObjects($object,
$through_class, $c);
        $all_objects = sfPropelManyToMany :: getAllObjects($object,
$through_class);
        $objects_unassociated = array ();
        $associated_ids = array_map(create_function('$o', 'return
$o->getPrimaryKey();'), $objects_associated);
        foreach ($all_objects as $object) {
                if (!in_array($object->getPrimaryKey(), $associated_ids)) {
                        $objects_unassociated[] = $object;
                }
        }

        // override field name
        unset ($options['control_name']);
        $name = _convert_method_to_name($method, $options);
        $name1 = 'unassociated_' . $name;
        $name2 = 'associated_' . $name;

        $sort1  = '<div id="'.$name1.'_sort'.'" style="float: left; height:
300px; width: 250px; border: 1px solid #DDDDDD;">';
        foreach ($objects_unassociated AS $ob):
        $sort1 .= '<div
id="name_'.$ob->getId().'">'.$ob->__toString().'</div>';
        endforeach;
        $sort1 .= '</div>';

        $js     = sortable_element($name1.'_sort', array(
//              'overlap' => 'vertical',
                'containment' => array($name2.'_sort', $name1.'_sort'),
                'constraint' => '',
                'dropOnEmpty' => 'true',
                'tag' => 'div',
                'onUpdate' =>
                        "function() {
                        }
                        ",
                ));

        $sort2  = '<div id="'.$name2.'_sort'.'" style="float: left; height:
300px; width: 250px; border: 1px solid #DDDDDD;">';
        foreach ($objects_associated AS $ob):
        $sort2 .= '<div
id="name_'.$ob->getId().'">'.$ob->__toString().'</div>';
        endforeach;
        $sort2 .= '</div>';

        $js     .= sortable_element($name2.'_sort', array(
//              'overlap' => 'vertical',
                'containment' => array($name1.'_sort', $name2.'_sort'),
                'constraint' => '',
                'dropOnEmpty' => 'true',
                'tag' => 'div',
                'onUpdate' =>
                        "function() {
                        var items = Sortable.sequence('{$name2}_sort');
                        for (var i = 0; i < $(\"{$name2}\").options.length; 
i++) {
                                $(\"{$name2}\").options[i] = null; i--;
                        }
                        for (var i=0; i<items.length;i++) {
                                $(\"{$name2}\").options[$(\"{$name2}\").length] 
= new
Option(items[i], items[i]);
                        }

                        }
                        ",
                ));

//      $select1 = select_tag($name1,
options_for_select(_get_options_from_objects($objects_unassociated),
'', $options), $options);
        $options['class'] = 'sf_admin_multiple-selected';
        $options['style'] = 'display: none;';
        $select2 = select_tag($name2,
options_for_select(_get_options_from_objects($objects_associated), '',
$options), $options);

        $html = '<div>
          <div style="">
            <div style="font-weight: bold; padding-bottom: 0.5em; width:
200px;">%s</div>
            %s
          </div>
          <div style="">
            <div style="font-weight: bold; padding-bottom: 0.5em; width:
200px;">%s</div>
            %s
          </div>
          <br style="clear: both" />
        </div>
        ';

        $html = $sort1.$sort2.$js.$select2.'<br style="clear: both" />';
//      return print_r(($objects_associated), true);
        return sprintf($html, $label_all, $select1, $label_assoc, $select2);
}

function _get_propel_object_list_sorted($object, $method, $options)
{
 // get the lists of objects
 $through_class = _get_option($options, 'through_class');
        $sort = _get_option($options, 'sort');
        $c = new Criteria();
        if ($sort) $c->addAscendingOrderByColumn(constant($sort));

 $objects = sfPropelManyToMany::getAllObjects($object,
$through_class);
 $objects_associated = sfPropelManyToMany::getRelatedObjects($object,
$through_class, $c);
 $ids = array_map(create_function('$o', 'return
$o->getPrimaryKey();'), $objects_associated);
 return array($objects, $objects_associated, $ids);
}


?>


generator.yml:

generator:
 class:              sfPropelAdminGenerator
 param:
   model_class:      HbHandbook
   theme:            default
   edit:
     display:    [ title, slug, created_at, description, pages ]
     fields:
       pages:
         type: admin_double_sortable_list
         params:
           through_class: HbHandbookPage
           sort: HbHandbookPagePeer::RANK


Then modify your actions.php to actually do something with the rank
that's returned:

 protected function saveHbHandbook($hb_handbook)
 {
   $hb_handbook->save();

     // Update many-to-many for "pages"
     $c = new Criteria();
     $c->add(HbHandbookPagePeer::HB_HANDBOOK_ID,
$hb_handbook->getPrimaryKey());
     HbHandbookPagePeer::doDelete($c);

     $ids = $this->getRequestParameter('associated_pages');
     if (is_array($ids))
     {
        $rank = 0;
       foreach ($ids as $id)
       {
         $HbHandbookPage = new HbHandbookPage();

$HbHandbookPage->setHbHandbookId($hb_handbook->getPrimaryKey());
         $HbHandbookPage->setHbPageId($id);
         $HbHandbookPage->setRank($rank++);
         $HbHandbookPage->save();
       }
     }

 }


The only other thing used (that I can remember right now!) is up and
down images which are simply rotated versions of the left/right
buttons.

HTH,

Mike


On Jan 4, 2:36 pm, "Michel D'HOOGE" <[EMAIL PROTECTED]> wrote:
Hello,

I have a many-to-many relation that I (want to) display with the admin
generator. So I use the admin_double_list type which works perfectly
as expected. Unfortunately, that M2M relation is ordered (there is an
additional 'rank' field in the intermediate table) and the default
_get_propel_object_list() can't handle this, of course. Note that it
doesn't sort them in alphabetical order either - but that's another
story...

So I dug into the generator and found that
- object_admin_double_list has a 4th param that can be used to choose
a callback to create the SELECT list
- sfPropelCrudGenerator only handles 3 params when generating an
object_%s() call

So AFAIK, I am stuck with the fully automated admin generation :-(
My question is then what is the best move to duplicate as few code as
possible from the _edit_form file generated in the cache?

Thanks for your help
--
Michel


--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups "symfony 
developers" group.
To post to this group, send email to [email protected]
To unsubscribe from this group, send email to [EMAIL PROTECTED]
For more options, visit this group at 
http://groups.google.com/group/symfony-devs?hl=en
-~----------~----~----~----~------~----~------~--~---

Reply via email to