I’m going to show a way that I’ve found to easily edit N1 relationships with the admin generator of symfony 1.2 Surely it can be polished, and maybe it could be done better another way, if you know how please let me know.
We are going to use the next model schema:
# config/schema.yml
propel:
lista:
id: ~
name: varchar(255)
type: varchar(255)
lista_item:
id: ~
lista_id: {type: integer, foreignTable: lista, foreignReference: id, required: true }
name: varchar(255)
There is a table of lists, and a table of list items related with the lists, so a list can contain n list items. The problem is to achieve the edition of the list properties and related list items on the same form.
I recommend to create the project to follow all the steps. So first of all…
$ mkdir listproject $ cd listproject $ symfony generate:project listproject
To test with some initial data we can add some fixtures:
# data/fixtures/lists.yml
Lista:
list_1:
name: List of fruits
type: fruits
list_2:
name: List of books
type: books
ListaItem:
item_1:
lista_id: list_1
name: apple
item_2:
lista_id: list_1
name: orange
item_3:
lista_id: list_1
name: pear
item_4:
lista_id: list_2
name: Treasure Island
item_5:
lista_id: list_2
name: Moby Dick
Now we can configure database and load with some data:
$ symfony configure:database "mysql:host=localhost;dbname=listproject" root mYsEcret $ mysqladmin -uroot -pmYsEcret create listproject $ symfony propel:build-all-load
The next step is to create the backend application, and generate an admin module for the Lista model:
$ symfony generate:app backend $ symfony propel:generate-admin backend Lista
Now we can view and edit the lists, as you can see on these screenshots:
We are ready for the main subject. The first step is to add an action to the edit template, which will add items to the list.
Edit the generator.yml of the module, and replace with this:
generator:
class: sfPropelGenerator
param:
model_class: Lista
theme: admin
non_verbose_templates: true
with_show: false
singular: ~
plural: ~
route_prefix: lista
with_propel_route: 1
config:
actions: ~
fields: ~
list: ~
filter: ~
form: ~
edit:
actions:
_delete:
_list:
add_item:
_save:
new: ~
This will add a new link at the bottom of the edit template, linking to the following action: ListAddItem. So we add the action to the actions.class.php of the module:
class listaActions extends autoListaActions
{
public function executeListAddItem()
{
if($this->getUser()->hasAttribute('N1added'))
{
$N1added = $this->getUser()->getAttribute('N1added');
$this->getUser()->setAttribute('N1added', $N1added + 1);
}
else
{
$this->getUser()->setAttribute('N1added', 1);
}
$this->forward('lista', 'edit');
}
}
Here is the trick. We add an attribute to the user (N1added) that has the count of the added items to the list. Each time the user clicks on “Add item”, N1added will be increased. This attribute will allow us to embed the needed number of forms into the ListaForm, as we can see below:
class ListaForm extends BaseListaForm
{
public function configure()
{
$n = 0;
foreach($this->object->getListaItems() as $item)
{
$n++;
$this->embedForm('Item '.$n, new ListaItemForm($item));
}
if(sfContext::getInstance()->getUser()->hasAttribute('N1added'))
{
for($i=0; $i < sfContext::getInstance()->getUser()->getAttribute('N1added'); $i++)
{
$it = new ListaItem();
$it->setLista($this->object);
$n++;
$this->embedForm('Item_'.$n, new ListaItemForm($it));
}
}
}
}
Also we have to set the ‘lista_id’ field to an input hidden on ListaItemForm:
class ListaItemForm extends BaseListaItemForm
{
public function configure()
{
$this->widgetSchema['lista_id'] = new sfWidgetFormInputHidden();
}
}
So, after this steps you can view, add and save items to the list as the next screenshot shows:
Now we have to reset the N1added counter in two cases: after save the list, and returning to the list, so we add the next two methods to the actions:
public function executeIndex(sfWebRequest $request)
{
$this->getUser()->setAttribute('N1added', 0);
parent::executeIndex($request);
}
protected function processForm(sfWebRequest $request, sfForm $form)
{
$this->getUser()->setAttribute('N1added', 0);
parent::processForm($request, $form);
}
The last thing to be done is the option to delete items. We are going to modify the _form_field.php template, that you can copy from the cache folder and save in the templates folder of lista’s module. The modified template is as follows (added code is in bold):
<?php if ($field->isPartial()): ?>
<?php include_partial('lista/'.$name, array(
'form' => $form, 'attributes' => $attributes instanceof sfOutputEscaper ?
$attributes->getRawValue() : $attributes)) ?>
<?php elseif ($field->isComponent()): ?>
<?php include_component('lista', $name, array(
'form' => $form, 'attributes' => $attributes instanceof sfOutputEscaper ?
$attributes->getRawValue() : $attributes)) ?>
<?php else: ?>
<div class="<?php echo $class ?><?php $form[$name]->hasError() and print ' errors' ?>">
<?php echo $form[$name]->renderError() ?>
<div>
<?php echo $form[$name]->renderLabel($label) ?>
<?php echo $form[$name]->render($attributes instanceof sfOutputEscaper ?
$attributes->getRawValue() : $attributes) ?>
<?php if($form[$name] instanceof sfFormFieldSchema): ?>
<ul class="sf_admin_actions">
<li class="sf_admin_action_delete">
<?php $value = $form[$name]->getValue();
echo link_to(__('Delete'), 'lista/deleteItem?itemId='.$value['id'],
array('confirm' => 'Are you sure?'));
?>
</li>
</ul>
<?php endif; ?>
<?php if ($help || $help = $form[$name]->renderHelp()): ?>
<div class="help"><?php echo __($help, array(), 'messages') ?></div>
<?php endif; ?>
</div>
</div>
<?php endif; ?>
We have added a delete link for each item. And finally, add the corresponding action in actions.class.php:
public function executeDeleteItem(sfWebRequest $request)
{
$item = ListaItemPeer::retrieveByPk($request->getParameter('itemId'));
$id = $item->getLista()->getId();
$item->delete();
$this->getUser()->setFlash('notice', 'The item was deleted successfully.');
$this->redirect('lista/edit?id='.$id);
}
That’s all. You can now edit, add and delete every item of every list.
I don’t know if this is the best solution for this problem, so if you find a better one or have any suggestions to improve this, please tell me.
11 Comments on “A way to edit N-1 relationships in one form with symfony 1.2”
You can track this conversation through its atom feed.



Great idea. I’m having a problem though.
When I add an item, fill it and then save the form, I receive an error. For examble if I save 2 items a third item appears and gives error: item_id: required.
Also the processForm written like this may not be good if there are validation errors in the embedded forms.
Posted on December 31, 2008 at 11:20 pm.
(in my previous commet I meant list_id instead of item_id)
To be more clear. If I allow list_id in the schema to be empty, when I save a form I always end up with one more lista_item in the database, with list_id empty.
I’m using Doctrine and Symfony 1.2
Any idea what am I doing wrong?
Posted on December 31, 2008 at 11:32 pm.
Good idea! I didn’t think about refreshing a page for each “add item” action. It may not be as smooth using JavaScript code, but I think it’s one of the most clear way to work with symfony.
I have a suggestion to keep edited values for every “add item” request.
1. Call getListaItems only once from ‘edit’ action of ‘lista’ module. Not inside the ListaForm.
2. Move “add action” link into the form as another button beside “save”. I’m not sure whether symfony supports either http://articles.techrepublic.com.com/5100-10878_11-5242116.html or http://www.codeproject.com/KB/scripting/multiaction.aspx.
3. Add $request parameter to executeListAddItem, and call EmbedForm for each $request->getParameter(‘lista’)[Item *].
I’ll try tomorrow, and let you know if it works.
Posted on January 1, 2009 at 7:14 pm.
I didn’t succeed to implement my idea yet, but found an interesting post about the same issue.
http://blog.barros.ws/2009/01/01/using-embedformforeach-in-symfony-part-ii/comment-page-1
Posted on January 2, 2009 at 10:23 am.
@Murena
sorry, but I have no experience with Doctrine.
Posted on January 8, 2009 at 9:13 pm.
Wouldn’t it be easier to embed all ListaItems and one extra?
I’m doing it that way (but without form framework) and works.
Posted on April 1, 2009 at 9:25 am.
@vojto
yes, that is another way, but you can add only one item each time you save.
Posted on April 1, 2009 at 9:49 am.
Hello,
Really a nice tutorial but has anyone succeeded making it work also for the new action?
Posted on April 7, 2009 at 2:27 pm.
Symfony Admin Generated Forms – One to Many (1:M) | Me Like Dev says:
[...] Check it out @ http://dev.markhaus.com [...]
Posted on November 7, 2009 at 1:45 am.
If you are looking for a way to implement a similar system for your own forms, not those generated by the admin generator, I have written a tutorial explaining how to do so. If you are interested check out http://ezzatron.com/2009/12/03/expanding-forms-with-symfony-1-2-and-doctrine/ and let me know your thoughts. Any feedback would be most welcome.
Posted on December 3, 2009 at 7:13 am.
“So we add the action to the actions.”
You can more about this?
Posted on June 1, 2010 at 8:08 pm.