User Tools

Site Tools


php:onetomanyjquery

Implement one-to-many relation with jQuery in forms

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]        

NEW CASE

First, let's take a quick look at the final result:

 New Person/Address

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 template

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>

The Javascript

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;
}

Routing

In routing.yml we implement the URL:

person_add_address:
    url:   /person_add_new_address
    param: { module: readers, action: personAddNewAddress }

Action File

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));
    }
    ...
}

The Partial Template

This will be readers/templates/__addNewAddress:

<tr>
    <td colspan="2">
        <?php echo $form['newAddress'] ?>
    </td>
</tr>

Form Modifications

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);
    } 

EDIT CASE

First a quick look at the final result:

 Editing person-address

Form Modifications

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);
    }
}       

More references here and here .

php/onetomanyjquery.txt · Last modified: 2013/03/16 17:40 (external edit)