Creating new relations by using jQuery Autocompleter widget in Symfony
This post describes how to create new relation by using jQuery Autocompleter widget in Symfony project.
Recently, I’ve had a situation where I had to create a form for entering product details, with a dropdown or autocomplete input for selecting product’s brand. If a user entered a non-existing brand, it had to be automatically created and assigned to the product that the user was editing.
Creating an autocomplete input is done by inserting this in a form class:
// lib/form/doctrine/your_form.class.php
$this->widgetSchema['brand_id'] = new sfWidgetFormDoctrineJQueryAutocompleter(array(
'url' => '/autocomplete_brand',
'model' => 'Brand',
'value_callback' => 'findOneById',
'config' => "{max: 20}"
));
$this->setValidator('brand_id', new sfValidatorPass());
It is important to add a sfValidatorPass validator for the brand_id field, so that Symfony won’t return an error when entering a name of a non-existent brand in the database.
For URL /autocomplete_brand create an action that handles jQuery queries:
// apps/frontend/modules/autocomplete/actions.class.php
function executeAutocompletebrand(sfWebRequest $request)
{
$limit = $request->getParameter('limit', 20);
$result = BrandTable::findBrandByName($request['q'], $limit);
$output = array();
foreach($result as $r)
$output[$r['id']] = $r['name'];
return $this->renderText(json_encode($output));
}
After searching for items that match the entered input, check if there are results to display. If there are none, insert a result that will tell your application to create a new Brand relation:
// lib/model/doctrine/BrandTable.class.php
public static function findBrandByName($name, $limit = 20)
{
$results = self::getInstance()->createQuery('b')
->where("b.name LIKE '%".$name."%'")
->limit($limit)
->execute(array(), Doctrine_Core::HYDRATE_ARRAY);
if(count($results) == 0)
return array(array('id'=>'new--'.$name, 'name'=>''.$name)); // If no brands are found, return this as a new Brand.
else
return $results;
}
Finally, create a post validator in which you’ll create the new Brand relation if field brand_id has been filled with a prefix new–. Your form class should now look something like this:
// lib/form/doctrine/your_form.class.php
public function configure()
{
...
$this->widgetSchema['brand_id'] = new sfWidgetFormDoctrineJQueryAutocompleter(array(
'url' => '/autocomplete_brand',
'model' => 'Brand',
'value_callback' => 'findOneById',
'config' => "{max: 20}"
));
$this->setValidator('brand_id', new sfValidatorPass());
$this->validatorSchema->setPostValidator(
new sfValidatorCallback(array('callback' => array($this, 'productPostValidate')))
);
...
}
/** Helper function for determining if a new brand has been entered **/
private function has_new_brand($brand_id)
{
return strpos($brand_id, 'ew--') == 1;
}
/** Helper function for extracting the new brand name from brand_id field value **/
private function get_new_brand_name($brand_id)
{
$tmp = explode('new--', $brand_id);
return(end($tmp));
}
function productPostValidate($validator, $values)
{
$tainted_vals = $this->getTaintedValues();
$brand_id = $tainted_vals['brand_id'];
if($this->has_new_brand($tainted_vals['brand_id']))
{
// Create new brand
$brand = new Brand();
$brand->setName($this->get_new_brand_name($tainted_vals['brand_id']));
$brand->save();
// Set brand id
$values['brand_id'] = $brand->getId();
}
...
}
This should enable you to automatically create a new brand entry if an entered name does not exist in your database.
📄 View source (.md)