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
-~----------~----~----~----~------~----~------~--~---