In questo tutorial mostreremo come realizzare, tramite symfony, uno script AJAX per modificare dinamicamente alcune select.
Come esempio, supponiamo che un utente voglia creare un nuovo prodotto, indicando il nome, la categoria e una o più sotto categorie.
Lo schema e i dati
Il database è formato dalle tabelle relative a Product, Category, Subcategory e SubcategoryPerProduct.
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 }
Per dare più spessore al nostro esempio, inseriamo anche alcuni dati di test.
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
Analizzando il form relativo a Product, si noterà che non compaiono i checkbox di selezione delle sotto categorie. Per raggiungere l’obiettivo è quindi necessario creare un widget sfWidgetFormDoctrineChoice.
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(),
));
}
}
In questo modo sono mostrate tutte le sotto categorie. Per mostrare solo quelle relative alla categoria selezionata al momento della creazione del form, aggiungiamo una query in SubcategoryTable e modifichiamo il form di Product.
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()
{
// Verifica se il prodotto ha già una categoria assegnata
$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),
));
}
// Questo metodo serve per salvare le sotto categorie selezionate
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;
}
}
}
Per terminare le modifiche al form di Product, non ci resta che assegnare un attributo class al widget delle categorie e un id al widget delle sotto categorie, che ci serviranno per lo script jQuery che implementeremo a breve.
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'
));
// ...
}
}
Per assegnare l’id al widget delle sotto categorie occorre creare una classe che eredita da sfWidgetFormSelectCheckbox e implementare il metodo formatter.
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')));
}
}
Creazione del modulo “subcategory”
Questo modulo deve definire una action per recuperare la lista delle sotto categorie, filtrate per categoria, e restituire i risultati su un template JSON. La risposta JSON sarà letta dello script AJAX per aggiornare i checkbox delle sotto categorie.
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 ?>
}
Recuperare le sottocategorie con AJAX e jQuery
Siamo arrivati al l’ultimo punto del nostro tutorial. Non ci rimane che aggiungere uno script, che quando viene scelta una categoria, aggiornerà il checkbox delle sotto categorie utilizzando 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>





