Dynamic input using javascript and symfony embedded forms

This article has moved to this location.


One of great features from symfony 1.2 system form is auto-save objects from embedded forms, which means that when you save a Propel form, symfony will automatically save the main object and all the objects related to the embedded forms.
In this tutorial, i’ll discuss about adding embedded forms dynamically using javascript (without ajax) in symfony 1.2 or greater.
I’ll take polling system as an example. The schema below is taken from sfPropelPollsPlugin. for automatic slug creation, sfPropelActAsSluggableBehaviorPlugin is used.

propel:
  sf_poll:
    id:            ~
    title:         { type: varchar, size: 255, required: true }
    description:   longvarchar
    is_published:  { type: boolean, required: true, default: false }
    is_active:     { type: boolean, required: true, default: false }
    slug:          { type: varchar, size: 255, required: true }
    created_at:
    updated_at:

  sf_polls_answers:
    id:            ~
    poll_id:       { type: integer, foreignTable: sf_poll, foreignReference: id, onDelete: cascade }
    name:          { type: varchar, size: 255, required: true }
    votes:         { type: integer, required: true, default: 0 }
    created_at:

Configure your database connection, then run this task:

php symfony propel:build-all

First, open SfPollForm.class.php, and remove unneeded fields.

public function configure()
{
   unset($this['slug'], $this['created_at'], $this['updated_at']);    
}

Then, open SfPoll.class.php in the model directory, add sluggable behaviour to this class.

<?php
class SfPoll extends BaseSfPoll {
  public function __construct()
  {
     parent::__construct();
  }
  
  public function __toString()
  {
    return $this->getName();
  }
} // SfPoll

//sluggable behaviour
$columns_map = array('from' => sfPollPeer::TITLE,  'to' => sfPollPeer::SLUG);
sfPropelBehavior::add('SfPoll', array('sfPropelActAsSluggableBehavior' => array('columns' => $columns_map, 'separator' => '-', 'permanent' => false)));

In your propel.ini, enable builder behaviour, then rebuild your models.

propel.builder.addBehaviors = true

Now, the question is how to embed the SfPollsAnswersForm to SfPollForm, and enable dynamic input via javascript?.
Here is step by step you have to do:

  1. open SfPollForm.class.php, and edit the ‘configure’ function
  2. public function configure()
    {
        unset($this['slug'], $this['created_at'], $this['updated_at']);
        $this->embedSfPollsAnswers();
    }
      
    protected function embedSfPollsAnswers()
    {
       $polls_answers_forms = new sfForm();
       $polls_answerss = $this->getObject()->getSfPollsAnswerss();
       if (count($polls_answerss) == 0)  //if still empty, create 3 answers by default
       {
         for($i=0; $i<3; $i++)
         {
           $polls_answers = new sfPollsAnswers();
           $polls_answerss[] = $polls_answers;
         }
       }
       foreach ($polls_answerss as $key=>$v)
       {
         $sfPollsAnswersForm = new SfPollsAnswersForm($v);
         $polls_answers_forms->embedForm('sf_poll_answer'.($key+1), $sfPollsAnswersForm);
         $polls_answers_forms->widgetSchema['sf_poll_answer'.($key+1)]->setLabel('Answer '.($key+1));
       }
       $this->embedForm('sf_polls_answers', $polls_answers_forms);
       $this->widgetSchema['sf_polls_answers']->setLabel('Possible Answers'); 
    } 
    
  3. Open and edit _form.php in the sf_poll templates folder. Below is the _form.php after modified.
  4. <?php use_stylesheets_for_form($form) ?>
    <?php use_javascripts_for_form($form) ?>
    
    <form action="<?php echo url_for('sf_poll/'.($form->getObject()->isNew() ? 'create' : 'update').(!$form->getObject()->isNew() ? '?id='.$form->getObject()->getId() : '')) ?>" method="post" <?php $form->isMultipart() and print 'enctype="multipart/form-data" ' ?>>
    <?php if (!$form->getObject()->isNew()): ?>
    <input type="hidden" name="sf_method" value="put" />
    <?php endif; ?>
    
    <?php $counter = 0 ?>
    <?php $name = 'sf_polls_answers' ?>
    
      <table>
        <tfoot>
          <tr>
            <td colspan="2">
              <?php echo $form->renderHiddenFields(false) ?>
              &nbsp;<a href="<?php echo url_for('sf_poll/index') ?>">Back to list</a>
              <?php if (!$form->getObject()->isNew()): ?>
                &nbsp;<?php echo link_to('Delete', 'sf_poll/delete?id='.$form->getObject()->getId(), array('method' => 'delete', 'confirm' => 'Are you sure?')) ?>
              <?php endif; ?>
              <input type="submit" value="Save" />
            </td>
          </tr>
        </tfoot>
        <tbody>
          <?php echo $form->renderGlobalErrors() ?>
          <tr>
            <th><?php echo $form['title']->renderLabel() ?></th>
            <td>
              <?php echo $form['title']->renderError() ?>
              <?php echo $form['title'] ?>
            </td>
          </tr>
          <tr>
            <th><?php echo $form['description']->renderLabel() ?></th>
            <td>
              <?php echo $form['description']->renderError() ?>
              <?php echo $form['description'] ?>
            </td>
          </tr>
          <tr>
            <th><?php echo $form['is_published']->renderLabel() ?></th>
            <td>
              <?php echo $form['is_published']->renderError() ?>
              <?php echo $form['is_published'] ?>
            </td>
          </tr>
          <tr>
            <th><?php echo $form['is_active']->renderLabel() ?></th>
            <td>
              <?php echo $form['is_active']->renderError() ?>
              <?php echo $form['is_active'] ?>
            </td>
          </tr>
          <tr>
            <th><?php echo $form['sf_polls_answers']->renderLabel() ?></th>
            <td>
              <table>
                <tbody id="answer_container">
                  <?php foreach($form[$name] as $key=>$field): ?>
                    <tr id="answer_<?php echo ++$counter ?>">
                      <td><?php echo $field->renderLabel() ?></td>
                      <td>
                          <?php echo $field['name']->renderError() ?>
                          <?php echo $field['name'] ?>
                      </td>
                      <?php echo $field['id'] ?>
                    </tr>
                  <?php endforeach; ?>
                </tbody>
              </table>
              <?php //echo $form['sf_polls_answers']->renderError() ?>
              <?php //echo $form['sf_polls_answers'] ?>
            </td>
          </tr>
          <input type="hidden" id="answer_attachment_counter" value="<?php echo $counter ?>" />
        </tbody>
        <tfoot>
          <tr>
            <td>&nbsp;</td>
            <td>
              <a href="#" onclick="addAnswer();return false;"><img src="/sfPropelPlugin/images/new.png" style="border: medium none ; vertical-align: middle;"/>&nbsp;Add Answer</a>
            </td>
          </tr>
        </tfoot>
      </table>
    </form>
    <?php use_helper('JavascriptBase') ?>
    <?php echo javascript_tag("
    function addAnswer() {
      var container = document.getElementById('answer_container');
      var counter = document.getElementById('answer_attachment_counter').value;
      counter++;
      document.getElementById('answer_attachment_counter').value = counter;
      var attachment_tr = document.createElement('tr');
      attachment_tr.setAttribute('id', 'answer_'+counter);
      
      //Start : create <td><label for=''>Answer x</label></td>
      var attachment_td = document.createElement('td');
      var attachment_label = document.createElement('label');
      attachment_label.setAttribute('for', 'sf_poll_sf_polls_answers_sf_poll_answer'+counter);
      attachment_label.innerHTML = 'Answer '+counter;
      attachment_td.appendChild(attachment_label);
      //End --
      
      //attach to tr
      attachment_tr.appendChild(attachment_td);
    
      //input text (name)
      var attachment_td1 = document.createElement('td');
      var attachment_name = document.createElement('input');
      attachment_name.setAttribute('type', 'text');
      attachment_name.setAttribute('name', 'sf_poll[sf_polls_answers][sf_poll_answer'+counter+'][name]');
      attachment_name.setAttribute('id', 'sf_poll_sf_polls_answers_sf_poll_answer'+counter+'_name');
      attachment_td1.appendChild(attachment_name);
    
      var attachment_del = document.createElement('a');
      attachment_del.setAttribute('href', 'javascript:delAnswer('+counter+');');
      attachment_del.innerHTML = '[delete]';
      attachment_td1.appendChild(attachment_del);
    
      attachment_tr.appendChild(attachment_td1);
    
      //input hidden (id)
      var attachment_id = document.createElement('input');
      attachment_id.setAttribute('type', 'hidden');
      attachment_id.setAttribute('id', 'sf_poll_sf_polls_answers_sf_poll_answer'+counter+'_id');
      attachment_id.setAttribute('name', 'sf_poll[sf_polls_answers][sf_poll_answer'+counter+'][id]');
      attachment_tr.appendChild(attachment_id);
    
      container.appendChild(attachment_tr);
    };
    
    function delAnswer(counter) 
    {
    document.getElementById('answer_container').removeChild(document.getElementById('answer_'+counter));
    document.getElementById('answer_attachment_counter').value--;
    };
    ");
    ?>
    

    And here is the screenshoot with embedded forms:
    Screenshot1.

  5. The final and important step is to override bind function of SfPollForm.class.php. Open SfPollForm.class.php, and add this function.
  6. public function bind(array $taintedValues = null, array $taintedFiles = null)
    {
        $childForm = 'sf_polls_answers';
        $datas = $taintedValues[$childForm];
        $extra_fields = array(); //holding extra widget that was added dynamically by javascript
    
        foreach ($datas as $key=>$v)
        {
          if (!$this->embeddedForms[$childForm]->offsetExists($key))
          {
            //won't work ( i don't know why, so i have to unset and reembed the child form. See code below )
            //$this->embeddedForms[$childForm]->embedForm($key, new SfPollsAnswersForm());
    
            $extra_fields[] = $key;
          }      
        }
    
        if (count($extra_fields) > 0)
        {
          //clone childForm
          $cloneForm = clone $this->embeddedForms[$childForm];
    
          //create new form and embed it 
          foreach($extra_fields as $key)
          {
            $cloneForm->embedForm($key, new SfPollsAnswersForm());
          }
          
          //remove childForm n embed cloneForm
          $this->offsetUnset($childForm);
          $this->embedForm($childForm, $cloneForm);
    
          unset($extra_fields);
        }
    
        $retval = parent::bind($taintedValues, $taintedFiles);
        {
        	foreach($this->embeddedForms[$childForm]->getEmbeddedForms() as $f)
        	{
              $f->getObject()->setSfPoll($this->getObject());
        	}
        }
        unset($datas); 
        return $retval;
    }
    

I know, this is not the best solution, if you have ideas to improve this code.. please leave a comment.
Btw.. i hope it help!!!, thanks you for visiting my blog 🙂

Advertisements

4 responses to “Dynamic input using javascript and symfony embedded forms

  1. Awin November 7, 2009 at 6:44 pm

    mantaff yeuh…hahaha

  2. Pingback: Ajaxify symfony embedded form « Just Blog no more…

  3. Pingback: Embedding forms using sfPropel15Plugin « Just Blog, No More…

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: