Ajaxify symfony embedded form

This article has moved to this location.


In my previous post, i’ve explained how to embedding form dynamically using javascript without ajax. In this post i’ll try to reimplement my previous post using ajax and jQuery based on this post Dynamic embedded forms in symfony.
Here is the screenshot before and after we added answer 4

.
Below are the modified step by step from my previous post.

  1. open SfPollsAnswersForm.class.php, remove unused fields

      public function configure()
      {
        unset($this['poll_id'], $this['votes'], $this['created_at']);
      }
    
  2. open SfPollForm.class.php, and edit the ‘configure’ function
      public function configure()
      {
        unset($this['slug'], $this['created_at'], $this['updated_at']);
        $this->embedSfPollsAnswers();
      }
      
      protected function embedSfPollsAnswers()
      {
        $polls_answers_forms = new sfForm();
        
        //we only need the form container for embedding form via ajax,
        if (false === sfContext::getInstance()->getRequest()->isXmlHttpRequest())
        {
        	$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_answers->setSfPoll($this->getObject());
            	$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.
    <?php use_stylesheets_for_form($form) ?>
    <?php use_javascripts_for_form($form) ?>
    <?php use_javascript('/js/jquery.js', 'first') ?>
    
    <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): ?>
                  	<?php echo include_partial('sf_poll/addAnswerForm', array('field' => $field, 'num' => ++$counter)) ?>
                  <?php endforeach; ?>
                </tbody>
              </table>
            </td>
          </tr>
        </tbody>
        <tfoot>
          <tr>
            <td>&nbsp;</td>
            <td>
              <button id="add_answer" type="button"><?php echo "Add Answer" ?></button>
            </td>
          </tr>
        </tfoot>
      </table>
    </form>
    <script type="text/javascript">
    var cnt = <?php echo $counter ?>;
    function addAnswer(num) 
    {
      var r = jQuery.ajax({
        type: 'GET',
        url: '<?php echo url_for('sf_poll/addAnswerForm')?>'+'<?php echo ($form->getObject()->isNew()?'':'?id='.$form->getObject()->getId()).($form->getObject()->isNew()?'?num=':'&num=')?>'+num,
        async: false
      }).responseText;
      return r;
    };
    function delAnswer(num) 
    {
    document.getElementById('answer_container').removeChild(document.getElementById('answer_'+num));
      cnt = cnt - 1;
    };
    jQuery().ready(function() 
    {
      jQuery('button#add_answer').click(function() {
        jQuery("#answer_container").append(addAnswer(cnt));
        cnt = cnt + 1;
      });
    });
    </script>
    
  4. When user click ‘Add Answer’, symfony will execute AddAnswerForm function, and the action will call SfPollForm to create the answer form and render the created form to the template.
    First, create the _addAnswerForm.php partial template

    <tr id="answer_<?php echo $num ?>">
      <td><?php echo $field->renderLabel('Answer '.$num) ?></td>
      <td>
         <?php echo $field['name']->renderError() ?>
         <?php echo $field['name'] ?>
      </td>
      <?php echo $field['id'] ?>
    	<?php if ($sf_request->isXmlHttpRequest()): ?>
    	<td>
    		<a href="#" onclick="delAnswer(<?php echo $num ?>);return false;"><?php echo image_tag('delete.png', array('style'=>"border: medium none ; vertical-align: middle;")) ?></a>
    	</td>
    	<?php endif; ?>
    </tr>
    

    Open actions.class.php and add this function.

    //sf_pollActions
    public function executeAddAnswerForm(sfWebRequest $request)
    {
    	$this->forward404Unless($request->isXmlHttpRequest());
    	if ($sf_poll = SfPollPeer::retrieveByPk($request->getParameter('id')))
    	{
    		$form = new SfPollForm($sf_poll);		
    	}
    	else
    	{
    		$form = new SfPollForm();
    	}
    	$number = $request->getParameter('num')+1;
    	$key = 'sf_poll_answer'.$number;
      	$form->addPollAnswerForm($key);
      	return $this->renderPartial('addAnswerForm',array('field' => $form['sf_polls_answers'][$key], 'num' => $number));
    }
    

    then in SfPollForm.class.php, add the function below

     //this function will be called from action to add form dynamically via ajax request	
      public function addPollAnswerForm($key)
      {
          $polls_answers = new sfPollsAnswers();
          $polls_answers->setSfPoll($this->getObject());
          $this->embeddedForms['sf_polls_answers']->embedForm($key, new SfPollsAnswersForm($polls_answers));
          $this->embedForm('sf_polls_answers', $this->embeddedForms['sf_polls_answers']);
      }
    
  5. The final and important step is to override bind function of SfPollForm.class.php. Open SfPollForm.class.php, and add this function.
    public function bind(array $taintedValues = null, array $taintedFiles = null)
    {
        foreach($taintedValues['sf_polls_answers'] as $key=>$form)
        {
           if (false === $this->embeddedForms['sf_polls_answers']->offsetExists($key))
           {
        	   $this->addPollAnswerForm($key);
           }
        }
        parent::bind($taintedValues, $taintedFiles);
    }
    

Thanks you for visiting my blog.. and happy coding.. cheer 😉

Advertisements

12 responses to “Ajaxify symfony embedded form

  1. Pingback: Dynamic embedded forms in symfony | Nacho Martín

  2. Andreas Stephan April 22, 2010 at 10:30 pm

    Sometimes the form framework just drives me insane 😉
    Thanks a lot for this article! It saved me a great deal of time. Highly appreciated!

    Cheers from Hamburg, Germany

    Andy

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

  4. Marvin Ochieng' January 20, 2011 at 5:49 pm

    Hi,

    I followed your tutorial to the letter and everything works fine except for the saving part where the form just tells me ‘Records could not be saved because of some errors’. Here’s my code:
    //occurenceform.class.php
    public function configure()
    {
    $this->embedActionTaken();
    }

    protected function embedActionTaken()
    {
    $actions_taken_forms = new sfForm();

    //we only need the form container for embedding form via ajax,
    if (false === sfContext::getInstance()->getRequest()->isXmlHttpRequest())
    {
    $actions_takens = $this->getObject()->getActionTaken();
    if (count($actions_takens) == 0) //if still empty, create 3 answers by default
    {
    for($i=0; $isetOccurence($this->getObject());
    $actions_takens[] = $actions_taken;
    }
    }
    foreach ($actions_takens as $key=>$v)
    {
    $ActionsTakenForm = new ActionTakenForm($v);
    $actions_taken_forms->embedForm(‘action_taken’.($key+1), $ActionsTakenForm);
    $actions_taken_forms->widgetSchema[‘action_taken’.($key+1)]->setLabel(‘Action ‘.($key+1));
    }
    }
    $this->embedForm(‘actions_taken’, $actions_taken_forms);
    $this->widgetSchema[‘actions_taken’]->setLabel(‘Actions Taken’);
    }
    //this function will be called from action to add form dynamically via ajax request
    public function addActionTakenForm($key)
    {
    $actions_taken = new ActionTaken();
    $actions_taken->setOccurence($this->getObject());
    $this->embeddedForms[‘actions_taken’]->embedForm($key, new ActionTakenForm($actions_taken));
    $this->embedForm(‘actions_taken’, $this->embeddedForms[‘actions_taken’]);
    }
    public function bind(array $taintedValues = null, array $taintedFiles = null)
    {
    foreach($taintedValues[‘actions_taken’] as $key=>$form)
    {
    if (false === $this->embeddedForms[‘actions_taken’]->offsetExists($key))
    {
    $this->addActionTakenForm($key);
    }
    }
    parent::bind($taintedValues, $taintedFiles);
    }
    //action.class.php
    ublic function executeAddActionTakenForm(sfWebRequest $request)
    {
    $this->forward404Unless($request->isXmlHttpRequest());
    if ($occurence = Doctrine_Core::getTable(‘Occurence’)->find($request->getParameter(‘id’)))
    {
    $form = new OccurenceForm($occurence);
    }
    else
    {
    $form = new OccurenceForm();
    }
    $number = $request->getParameter(‘num’)+1;
    $key = ‘action_taken’.$number;
    $form->addActionTakenForm($key);
    return $this->renderPartial(‘addActionTakenForm’,array(‘field’ => $form[‘actions_taken’][$key], ‘num’ => $number));
    }

  5. Marvin Ochieng' January 20, 2011 at 6:59 pm

    Which form could be having the unset fields, main one or embedded? thanks for your quick response by the way

  6. Marvin Ochieng' January 20, 2011 at 7:13 pm

    p.s. I’m a noob so pardon me if i ask, how do i save in non-ajax mode?

  7. Marvin Ochieng' January 21, 2011 at 10:51 am

    i’ve sent you all the files on email

  8. Marvin Ochieng' February 14, 2011 at 3:28 pm

    Hi, me again. Trying to implement your solution with Diem and running into problems. I get an error telling me that “Widget “vendor_accounts” does not exist.” vendor_accounts being my embedded form

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: