This tutorial shows how to make an AJAX script that dynamically changes some selects with Symfony.
For example, let’s suppose that a user wants to create a new product, inserting the name, the category and one or more subcategories.
The Schema and Data
The database is structured with Product, Category, Subcategory e SubcategoryPerProduct tables.
Product:
columns:
name: { type: string(255), notnull: true }
category_id: { type: integer, notnull: true }
relations:
Category: { local: category_id, foreignAlias: Products, onDelete: cascade}
Category:
columns:
name: { type: string(255), notnull: true }
Subcategory:
columns:
name: { type: string(255), notnull: true }
description: { type: string(255) }
category_id: { type: integer }
relations:
Category: { local: category_id, foreignAlias: Subcategories, onDelete: cascade}
SubcategoryPerProduct:
columns:
subcategory_id: { type: integer, notnull: true }
product_id: { type: integer, notnull: true }
relations:
Subcategory: { local: subcategory_id, foreignAlias: SubcategoriesPerProduct, onDelete: CASCADE }
Product: { local: product_id, foreignAlias: SubcategoriesPerProduct, onDelete: CASCADE }
To bring the project to life, insert the following test data.
Category:
restaurant:
id: 1
name: Restaurant
clothing:
id: 2
name: Clothing
Subcategory:
condition_1:
Category: clothing
name: Shoes
description: Shoes donec sollicitudin, tortor at porttitor sollicitudin.
condition_2:
Category: clothing
name: Clothes
description: Clothes morbi eu turpis ac sapien auctor consequat.
condition_3:
Category: clothing
name: trousers
description: Trousers cras purus nulla, venenatis vel sodales vel, viverra nec diam.
condition_4:
Category: restaurant
name: pizzeria
description: Pizzeria phasellus vel ligula quis odio.
condition_5:
Category: restaurant
name: Sandwich shop
description: Sandwich shop morbi eu turpis ac sapien auctor consequat.
ProductForm
Analyzing the Product form, you will notice that subcategory selection checkboxes do not appear. To do this, we need to create a sfWidgetFormDoctrineChoice widget.
class ProductForm extends BaseProductForm
{
public function configure()
{
$this->widgetSchema['subcategory'] = new sfWidgetFormDoctrineChoice(array(
'model' => 'Subcategory',
'add_empty' => false,
'expanded' => true,
'multiple' => true,
'query' => SubcategoryTable::getInstance()->createQuery()
));
$this->validatorSchema['subcategory'] = new sfValidatorDoctrineChoice(array(
'required' => true,
'model' => 'Subcategory',
'multiple' => true,
'query' => SubcategoryTable::getInstance()->createQuery(),
));
}
}
With this new widget, all subcategories are shown. To show only those relative to the selected category, let’s add a query in SubcategoryTable and modify the Product form.
public function findByCategoryQuery(Category $category)
{
$q = $this->createQuery()
->where('category_id = ?', $category->getId());
return $q;
}
# lib/form/doctrine/ProductForm.class.php
class ProductForm extends BaseProductForm
{
public function configure()
{
// Verify if category exists
$category = $this->getObject()->getCategory();
if (!$category->exists()) {
$request = sfContext::getInstance()->getRequest();
$params = $request->getParameter($this->getName());
if ($params['category_id'])
$category = CategoryTable::getInstance()->findOneById($params['category_id']);
else
$category = CategoryTable::getInstance()->findAll()->getFirst();
}
$this->widgetSchema['subcategory'] = new sfWidgetFormDoctrineChoice(array(
'model' => 'Subcategory',
'add_empty' => false,
'expanded' => true,
'multiple' => true,
'query' => SubcategoryTable::getInstance()->findByCategoryQuery($category)
));
$this->validatorSchema['subcategory'] = new sfValidatorDoctrineChoice(array(
'required' => true,
'model' => 'Subcategory',
'multiple' => true,
'query' => SubcategoryTable::getInstance()->findByCategoryQuery($category),
));
}
// This method save the subcategories
protected function doUpdateObject($values)
{
$subcategories = $values['subcategory'];
unset($values['subcategory']);
parent::doUpdateObject($values);
$product = $this->getObject();
foreach ($subcategories as $key => $subcategoryId) {
$subcategoryPerProduct = new SubcategoryPerProduct();
$subcategoryPerProduct->setSubcategoryId($subcategoryId);
$product->SubcategoriesPerProduct[$key] = $subcategoryPerProduct;
}
}
}
To complete the Product form, we can assign a class attribute to the category widget, and an id to the subcategories widget. We will need this for the jQuery script.
class ProductForm extends BaseProductForm
{
public function configure()
{
// ...
$this->getWidget('category_id')->setAttribute('class', 'category');
$this->widgetSchema['subcategory'] = new sfWidgetFormDoctrineChoice(array(
'model' => 'Subcategory',
'add_empty' => false,
'expanded' => true,
'multiple' => true,
'query' => SubcategoryTable::getInstance()->findByCategoryQuery($category),
'renderer_class' => 'myWidgetFormSelectCheckbox'
));
// ...
}
}
To assign the id to the subcategories widget, let’s create a class that inherits from sfWidgetFormSelectCheckbox, implementing the formatter method.
class myWidgetFormSelectCheckbox extends sfWidgetFormSelectCheckbox
{
public function formatter($widget, $inputs)
{
$rows = array();
foreach ($inputs as $input) {
$rows[] = $this->renderContentTag('li', $input['input'] . $this->getOption('label_separator') . $input['label']);
}
return !$rows ? '' : $this->renderContentTag('ul', implode($this->getOption('separator'), $rows), array('id' => 'subcategory', 'class' => $this->getOption('class')));
}
}
Creating a “subcategory” module
This module defines an action to recover the list of subcategories, filtered by category, and that returns a JSON template. The JSON answer will be read by the AJAX script, in order to update the subcategory checkboxes.
list_by_category:
url: /category/:id/subcategory.:sf_format
param: { module: subcategory, action: listByCategory }
requirements:
sf_format: (?:json)
class subcategoryActions extends sfActions
{
public function executeListByCategory(sfWebRequest $request)
{
$this->subcategories = SubcategoryTable::getInstance()->findByCategoryId($request->getParameter('id'));
}
}
{
<?php $count = $subcategories->count(); $i = 0; ?>
<?php foreach($subcategories as $subcategory) : $i++ ?>
"<?php echo $subcategory->getId()?>": { "title": "<?php echo $subcategory->getName()?>" }<?php echo $i == $count ? '' : ','?>
<?php endforeach ?>
}
Retrieve subcategories with AJAX and jQuery
To complete our tutorial, we need to add a jQuery script that, when a category is selected, updates the subcategory checkboxes using AJAX.
// ...
<script type="text/javascript">
$(function() {
$('.category').change(function() {
var chosenoption = this.options[this.selectedIndex];
var conditionUrl = "/category/"+ chosenoption.value +"/subcategory.json";
$.ajax({
url: conditionUrl,
dataType: "json",
success: function(choices) {
if(choices) {
$("#subcategory").hide();
$("#subcategory").html('');
var $subcategories = '';
$.each(choices, function(index) {
$subcategories = $subcategories +
"<li>\n\
<input id=\"" + chosenoption.value + "_" + index + "\" type=\"checkbox\" multiple=\"multiple\" value=\"" + index + "\" name=\"product[subcategory][]\">\n\
<label for=\"product_subcategory_" + index + "\">" + this.title + "</label>\n\
</li>";
});
$("#subcategory").html($subcategories);
$("#subcategory").show();
}
}
});
});
});
</script>





