views:

652

answers:

2

I am using Symfony 1.3.2 with Propel ORM on Ubuntu 9.10.

I have developed a form that dynamically populates a select widget with cities in a selected country, using AJAX.

Before the data entered on the form is saved, I validate the form. If validation fails, the form is presented back to the user for correction. However, because the country list is dynamically generated, the form that is presented for correction does not have a valid city selected (it is empty, since the country widget has not changed yet).

This is inconvenient for the user, because it means they have to select ANOTHER country (so the change event is fired), and then change back to the original country they selected, then FINALLY select the city which they had last selected.

All of this is forced on the user because another (possibly unrelated) field did not vaildate.

I tried $form->getValue('widget_name'), called immediately after $form->bind(), but it seems (infact, IIRC, if form fails to validate, all the values are reset to null) - so that does not work.

I am currently trying a nasty hack which involves the use of directly accesing the input (i.e. tainted) data via $_POST, and setting them into a flash variable - but I feel its a very nasty hack)

What I'm trying to do is a common use case scenario - is there a better way to do this, than hacking around with $_POST etc?

+1  A: 

What I do for this exact issue is that I post the form to the same action that generated it, and in that action, I grab any selected countries/regions/cities as POST variables and pass them back to the template (regardless of validation). In the template, I then use JQuery to set the select values to what they were. When validation passes, they get used. When not, they get passed back to template.

If you can tolerate a little PHP in your JQuery, you could do this in the template:

$(document).ready(function()
{
   $("#country-select").val('<?php echo $posted_country; ?>');
});

If you use this approach, don't forget to initialise $this->posted_country in your template the first time around or Jquery will get confused.

I guess you could also use $this->form->setWidget(...)->setDefault(...) or something similar, but I havent found a way around using $_POST as accessing the elements seems to need binding the form otherwise.

UPDATED CODE IN RESPONSE TO COMMENTS BELOW:

if($_POST['profile']['country_id'] != '') 
{
    $this->posted_country = $_POST['profile']['country_id'];
    $q = Doctrine_Query::create()
        ->select('c.city_id, c.city_name')
        ->from('City c')
        ->where('c.country_id = ?', $this->posted_country);     
    $cities = $q->execute(array(), Doctrine_Core::HYDRATE_NONE);
    foreach($cities as $city) $list[$city[0]] = $city[1];
    $this->form->setWidget('city_id', new sfWidgetFormChoice(array('choices' => array('' => 'Please select') + $list)));
}

So... I get the country from the post, I query db with that, get cities, and craft cities back into a dropdown. Then in the template, you can set a default selected city with something like $this->posted_city (which would be a POST variable too, if exists).

Tom
Thanks Tom, you are on the right track (I have voted up your question). What I'm doing is slightly more complicated though. Its the cities that get 'lost'. The previously selected country is still there. The behavior I want is this: when the form fails to validate, the previously selected country remains (this is already working), but also, I want the list of cities for that country to remain available AND the last selected city to be the one selected. So, in otherwords, if the error is coming from somewhere else in the form, then the user does not need to touch the country/city fields again
Stick it to THE MAN
... (unless they want to). I believe I can use jQuery to do this. I am just having to think it through a little more, since I dont "think in" jQuery.
Stick it to THE MAN
You could always post a little jQuery snippet here to get me going ;) - and once I have succesfully imoplemneted the behaviour using/building on your snippet, I will accept your answer as the final one.
Stick it to THE MAN
Tom, please take a look at my edited question
Stick it to THE MAN
Ok, basically, I check if a country was posted. If yes, I re-generate the list of countries for that widget... and then you can use jquery to set a default selected city again, like with country. I've added some code above.
Tom
Hi Tom, I didn't edit the original question afterlall, because as I was explaining what I needed to do, I realized, I had the implementation logic!. One thing though, the val method:$("#country-select").val('<?php echo $posted_country; ?>');does not seem to be working. I will be checking the jQuery docs to find out why
Stick it to THE MAN
Hmm... it should work. I would suggest checking that the "#country-select" refers to the correct element id in your code, and also check that the correct value is passing back to template and matches the select value="" that it points to.
Tom
A: 

Hi Tom,

I am totally new in symfony. You have done the things i need to do now. Can you give me the code of this (like, where i need to put "$(document).ready(function()" and "UPDATED CODE IN RESPONSE")? I could load 2 dropdown list from database, but i need to load second dropdownlist depending on the selected value of 1st dropdown list. I googled a lot but cudnt find the full example. Below is my current code:

//--------------------------------------------------- class BaseDevnetPackageForm extends BaseFormPropel { public function setup() { $this->setWidgets(array( 'pkg_type' => new sfWidgetFormPropelChoice(array('model' => 'DevnetPackageType', 'add_empty' => true)),

'pkg_status' => new sfWidgetFormPropelChoice(array('model' => 'DevnetPackageStatus', 'add_empty' => true)), ));

$this->setValidators(array( 'pkg_type' => new sfValidatorPropelChoice(array('model' => 'DevnetPackageType', 'column' => 'ID', 'required' => false)), 'pkg_status' => new sfValidatorPropelChoice(array('model' => 'DevnetPackageStatus', 'column' => 'ID', 'required' => false)) ));

parent::setup(); } //---------------------------------------------------

I need to load 'pkg_status' depending on the selection of 'pkg_type'. Please help.

Thanks Suborno

Suborno