12.10.2010
This example is built on:
# config/doctrine/schema.yml
SbPerson:
columns:
peid: { type: integer, primary: true, autoincrement: true, unsigned:true }
firstname: string(100)
lastname: string(100)
CNP: string(13)
identity_docs: string(100)
DOB: date
phone: string(30)
email: string(30)
relations:
SbAddress: { type: many, class: SbAddress, local: peid, foreign: person_id }
SbAddress:
columns:
person_id: { type: integer, unsigned: true }
street: string(80)
number: string(10)
addition: string(10)
city: string(50)
county: string(50)
postal_code: string(8)
indexes:
streetIdx:
fields: [street]
First, let's take a quick look at the final result:
The + sign will add one at the time Address form. For example, if you want 3 Addresses, you'll press 3 times in a row (you should do this operation *before* actually putting data there). The - sign of course, will remove a form at the time.
The code inside the form template looks something as:
<table class="ui-widget ui-widget-content genTable" id="tblNewPerson"> <?php echo $form ?> <tr> <td colspan="2"> <a id="person_add_address" href="#"><img src="/images/plus-icon.png" alt="Add Address"/></a> <a id="person_delete_address" href="#"><img src="/images/minus-icon.png" alt="Remove Address"/></a> </td> </tr> <tr><td colspan="2" id="tblAddress"></td></tr> </table>
Implement the actions for + and - buttons.
$().ready(function() { $('#person_add_address').click(function(){ new_address_count = new_address_count + 1; $('#tblNewPerson #tblAddress').html(person_add_new_address(new_address_count)); }); $('#person_delete_address').click(function() { new_address_count = new_address_count - 1; if (new_address_count >= 0) { $('#tblNewPerson #tblAddress').html(person_add_new_address(new_address_count)); } }); });
Function person_add_new_address is as follow:
var new_address_count = 0; function person_add_new_address(num){ return $.ajax({ type: 'GET', url: '/person_add_new_address?howmany='+num, async: false }).responseText; }
In routing.yml we implement the URL:
person_add_address:
url: /person_add_new_address
param: { module: readers, action: personAddNewAddress }
We continue with personAddNewAddress action inside readers module (the file will be readers/actions/actions.class.php):
class readersActions extends sfActions { ... public function executePersonAddNewAddress(sfWebRequest $request) { $this->forward404unless($request->isXmlHttpRequest()); $howmany = intval($request->getParameter("howmany")); $this->form = new SbPersonForm(); $this->form->addNewAddress($howmany); return $this->renderPartial('addNewAddress',array('form' => $this->form, 'howmany' => $howmany)); } ... }
This will be readers/templates/__addNewAddress:
<tr> <td colspan="2"> <?php echo $form['newAddress'] ?> </td> </tr>
SbPersonForm will get a new method (the one what actually embed the Address subforms)
class SbPersonForm extends BaseSbPersonForm { public function configure() { ... // EMBEDDED RELATION $this->embedRelation('SbAddress'); } .... public function addNewAddress($howmany){ $new_addresses = new BaseForm(); for ($i=1; $i <= $howmany; $i++){ $addressObj = new SbAddress(); $addressObj->setPersonId($this->getObject()); $new_addresses->embedForm($i, new SbAddressForm($addressObj)); } $this->embedForm('newAddress', $new_addresses); } ....
Also, overwrite the bind method inside SbPersonForm.
/** * * recreates the structure of the request data in the created form before the form binding */ public function bind(array $taintedValues = null, array $taintedFiles = null) { $new_addresses = new BaseForm(); if (array_key_exists('newAddress', $taintedValues)) { foreach($taintedValues['newAddress'] as $key => $value){ $addressObj = new SbAddress(); $addressObj->setPersonId($this->getObject()); $new_addresses->embedForm($key,new SbAddressForm($addressObj)); } } $this->embedForm('newAddress', $new_addresses); parent::bind($taintedValues, $taintedFiles); }
In order to be able to delete addresses in edit more, we'll implement a “Delete” field in SbAddressForm:
class SbAddressForm extends BaseSbAddressForm { public function configure() { unset($this['person_id']); // insert only if is NOT new (edit mode) if ($this->object->exists()) { $this->widgetSchema['delete'] = new sfWidgetFormInputCheckbox(); $this->validatorSchema['delete'] = new sfValidatorPass(); } } }
SbPersonFrom will be modified as follow:
class SbPersonForm extends BaseSbPersonForm { protected $address_tobe_deleted = array(); // add this /** * UPDATED BIND HERE! * recreates the structure of the request data in the created form before the form binding */ public function bind(array $taintedValues = null, array $taintedFiles = null) { $new_addresses = new BaseForm(); if (array_key_exists('newAddress', $taintedValues)) { foreach($taintedValues['newAddress'] as $key => $value){ $addressObj = new SbAddress(); $addressObj->setPersonId($this->getObject()); $new_addresses->embedForm($key,new SbAddressForm($addressObj)); } } $this->embedForm('newAddress', $new_addresses); if (array_key_exists('SbAddress', $taintedValues)) { foreach ($taintedValues['SbAddress'] as $key => $value) { if (isset($value['delete']) && $value['id']) { $this->address_tobe_deleted[$key] = $value['id']; } } } parent::bind($taintedValues, $taintedFiles); } // this will do the delete part protected function doUpdateObject($values) { if ($this->address_tobe_deleted) { foreach ($this->address_tobe_deleted as $key => $value) { unset($values['SbAddress'][$key]); unset($this->object['SbAddress'][$key]); Doctrine::getTable('SbAddress')->findOneById($value)->delete(); } } $this->getObject()->fromArray($values); } }