![]() |
|
sfDependentSelectPlugin - 0.1.5Widgets for dependents selects and action for automatize AJAX calls. |
|
![]() |
40
users
Sign-in
to change your status |
Widgets for dependents selects and action for automatize AJAX calls. |
A linking selects plugin, in a simple and elegant way
| Name | Status | |
|---|---|---|
|
|
lead | moc.liamg <<ta>> abcres |
Copyright (c) 2010 Sergio Flores
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
| Version | License | API | Released |
|---|---|---|---|
| 0.1.6alpha | MIT license | 0.1.0alpha | 12/03/2011 |
| 0.1.5alpha | MIT license | 0.1.0alpha | 09/08/2010 |
| 0.1.4alpha | MIT license | 0.1.0alpha | 01/08/2010 |
| 0.1.3alpha | MIT license | 0.1.0alpha | 22/07/2010 |
| 0.1.2alpha | MIT license | 0.1.0alpha | 19/07/2010 |
| 0.1.1alpha | MIT license | 0.1.0alpha | 18/07/2010 |
| 0.1.0alpha | MIT license | 0.1.0alpha | 17/07/2010 |
| Version | License | API | Released |
|---|---|---|---|
| 0.1.6alpha | MIT license | 0.1.0alpha | 12/03/2011 |
| 0.1.5alpha | MIT license | 0.1.0alpha | 09/08/2010 |
| 0.1.4alpha | MIT license | 0.1.0alpha | 01/08/2010 |
| 0.1.3alpha | MIT license | 0.1.0alpha | 22/07/2010 |
| 0.1.2alpha | MIT license | 0.1.0alpha | 19/07/2010 |
| 0.1.1alpha | MIT license | 0.1.0alpha | 18/07/2010 |
| 0.1.0alpha | MIT license | 0.1.0alpha | 17/07/2010 |
Un plugin para enlazar selects de una forma simple y elegante
$this->widgetSchema['pais_id'] = new sfWidgetFormDoctrineChoice(array(
'model' => 'Pais'
));
$this->widgetSchema['provincia_id'] = new sfWidgetFormDoctrineDependentSelect(array(
'model' => 'Provincia',
'depends' => 'Pais'
));
Nota: Documentación original en español disponible en doc/README_es.
Modo estático y modo AJAX con módulo automático (respetando add_empty, {peer|table}_method, order_by, etc),
Permite enlazar listas de array con listas de modelos o entre modelos (niveles ilimitados),
Soporte para Doctrine y Propel,
Fácil de personalizar y extender.
Instalar el plugin
$ php symfony plugin:install sfDependentSelectPlugin
Publicar javascript
$ php symfony plugin:publish-assets
Limpiar la cache
$ php symfony cache:clear
Habilitar el plugin en la configuración del proyecto (config/ProjectConfiguration.class.php)
$this->enablePlugins(..., 'sfDependentSelectPlugin');
Habilitar el módulo sfDependentSelectAuto si deseas utilizar ajax automatizado (apps/{tuapp}/config/settings.yml)
all:
.settings:
...
enabled_modules: [sfDependentSelectAuto]
Los ejemplos son para Doctrine, pero puedes hacerlo de igual forma para propel utilizando la clase sfWidgetFormPropelDependentSelect.
TÃpico caso en que se tiene paises, provincias y ciudades relacionadas. Al momento de crear una dependencia a la ciudad desde otra entidad, se crea un gran problema a la hora de mostrar el formulario, ya que aparecen todas las ciudades de todas las provincias de todos los paises mezclados.
A continuación, la solución. Se hará con una persona que vive en una ciudad.
config/doctrine/schema.yml:
Pais:
columns:
nombre: string(45)
Provincia:
columns:
pais_id: integer
nombre: string(45)
relations:
Pais:
foreignAlias: Provincias
Ciudad:
columns:
provincia_id: integer
nombre: string(45)
relations:
Provincia:
foreignAlias: Ciudades
Persona:
columns:
ciudad_id: integer
nombre: string(45)
relations:
Ciudad:
foreignAlias: Personas
lib/form/doctrine/PersonaForm.class.php:
class PersonaForm extends BasePersonaForm
{
public function configure()
{
// widgets
$this->widgetSchema['pais_id'] = new sfWidgetFormDoctrineChoice(array(
'model' => 'Pais',
'add_empty' => 'Seleccione paÃs',
));
$this->widgetSchema['provincia_id'] = new sfWidgetFormDoctrineDependentSelect(array(
'model' => 'Provincia',
'depends' => 'Pais',
'add_empty' => 'Seleccione provincia',
));
$this->widgetSchema['ciudad_id'] = new sfWidgetFormDoctrineDependentSelect(array(
'model' => 'Ciudad',
'depends' => 'Provincia',
'add_empty' => 'Seleccione ciudad',
));
// siempre el orden de los selects tienen que ser según la dependencia.
// es decir, primero pais > provincia > ciudad
$this->widgetSchema->moveField('ciudad_id', 'after', 'provincia_id');
// validadores
$this->validatorSchema['pais_id'] = new sfValidatorDoctrineChoice(array(
'model' => 'Pais',
));
$this->validatorSchema['provincia_id'] = new sfValidatorDoctrineChoice(array(
'model' => 'Provincia',
));
// el validador de 'ciudad_id' está en BasePersonaForm
}
}
Listo, con eso ya deberÃa funcionar. Se mostrará el select "independiente" para paises, y de acuerdo al valor de éste serán las provincias mostradas. De igual forma para con las ciudades según la provincia.
Cabe destacar que el ejemplo anterior no hace llamadas remotas (AJAX), simplemente carga los selects con los diferentes grupos de datos y los va mostrando de acuerdo a los valores seleccionados en el select del cual depende. Por eso notarás la rapidez en el funcionamiento, ideal para pequeños/medianos conjuntos de datos.
Cuando los niveles son profundos y con grandes cantidades de opciones, seguramente querrás utilizar AJAX. A continuación veremos cómo modificar el ejemplo anterior para añadir esta caracterÃstica facilmente.
Importante: Recuerda cargar jQuery sino no se harán las llamadas remotas.
lib/form/doctrine/PersonaForm.class.php:
class PersonaForm extends BasePersonaForm
{
public function configure()
{
// widgets
$this->widgetSchema['pais_id'] = new sfWidgetFormDoctrineDependentSelect(array(
'model' => 'Pais',
'add_empty' => 'Seleccione paÃs',
'ajax' => true,
));
// si hubieses querido cargarlo sin ajax (ya que son pocos datos):
#$this->widgetSchema['pais_id'] = new sfWidgetFormDoctrineChoice(array(
# 'model' => 'Pais',
# 'add_empty' => 'Seleccione pais',
#));
$this->widgetSchema['provincia_id'] = new sfWidgetFormDoctrineDependentSelect(array(
'model' => 'Provincia',
'depends' => 'Pais',
'add_empty' => 'Seleccione provincia',
'ajax' => true,
// aprovechamos para mostrar otras opciones
#'ref_method' => 'getPaisId',
#'url' => sfContext::getInstance()->getController()->genUrl('sfDependentSelectAuto/_ajax'),
#'cache' => true,
// están disponibles las mismas que sfWidgetForm{Doctrine|Propel}Choice
'order_by' => array('nombre', 'asc'),
#'method' => '__toString',
#'key_method' => 'getId',
#'table_method' => 'getPaisesEuropeos',
));
$this->widgetSchema['ciudad_id'] = new sfWidgetFormDoctrineDependentSelect(array(
'model' => 'Ciudad',
'depends' => 'Provincia',
'add_empty' => 'Seleccione ciudad',
'ajax' => true,
'order_by' => array('nombre', 'asc'),
));
...
}
}
Eso es todo. Las llamadas remotas son atendidas por un módulo especial llamado sfDependentSelectAuto que devuelve los valores respetando todas las opciones establecidas como si de forma local se tratase.
Más adelante se explican detalladamente las opciones disponibles y cómo funciona el módulo automático para que lo puedas extender y devolver tus propios datos.
Antes de eso, otro ejemplo.
Se utilizará como ejemplo un caso de ventas de productos y servicios que extienden a un Ãtem. Al momento de cargar el detalle de la factura, se selecciona el tipo (producto o servicio) y luego se despliegan los mismos en otro select.
Este ejemplo muestra cómo mezclar arrays con modelos.
config/doctrine/schema.yml:
Item:
columns:
nombre: string(45)
precio: float
tipo: string(45)
Producto:
inheritance:
extends: Item
type: column_aggregation
keyField: tipo
keyValue: producto
Servicio:
inheritance:
extends: Item
type: column_aggregation
keyField: tipo
keyValue: servicio
Factura:
columns:
numero: integer
fecha: date
FacturaConcepto:
columns:
factura_id: integer
item_id: integer
cantidad: float
relations:
Factura:
foreignAlias: Conceptos
Item:
foreignAlias: Conceptos
lib/model/doctrine/Item.class.php
class Item extends BaseItem
{
private static $tipos = array(
'producto' => 'Productos',
'servicio' => 'Servicios',
);
public static function getTipos()
{
return self::$tipos;
}
}
lib/form/doctrine/FacturaConceptoForm.class.php
class FacturaConceptoForm extends BaseFacturaConceptoForm
{
public function configure()
{
// widgets
$this->widgetSchema['tipo'] = new sfWidgetFormChoice(array(
'choices' => Item::getTipos(),
));
// si quieres cargar los tipos con ajax, podrÃas haber utilizado
// la clase sfWidgetFormArrayDependentSelect que se explica luego
$this->widgetSchema['item_id'] = new sfWidgetFormDoctrineDependentSelect(array(
'model' => 'Item',
'depends' => 'tipo',
'ajax' => true,
), array(
'size' => 5,
));
// validadores
$this->validatorSchema['tipo'] = new sfValidatorChoice(array(
'choices' => Item::getTipos(),
));
}
}
Aparecerá un select en donde seleccionas el tipo, luego de seleccionarlo se carga la lista de items de ese tipo.
Puedes combinar arrays y modelos libremente.
Veamos el ejemplo de paises y provincias pero esta vez utilizando un array como fuente de datos. Para ello se utilizará el widget sfWidgetFormArrayDependentSelect.
A los datos los pondremos en una clase contenedora.
lib/MisDatos.class.php:
abstract class MisDatos
{
private static $paises = array(
'ar' => 'Argentina',
'br' => 'Brasil',
);
private static $provincias = array(
'ar' => array(
'ar_bue' => 'Buenos Aires',
'ar_cba' => 'Córdoba',
),
'br' => array(
'br_sp' => 'São Paulo',
'br_mg' => 'Minas Gerais',
),
);
public static function getPaises()
{
return self::$paises;
}
public static function getProvincias()
{
return self::$provincias;
}
}
lib/form/doctrine/PersonaForm.class.php:
class PersonaForm extends BasePersonaForm
{
public function configure()
{
// widgets
$this->widgetSchema['pais'] = new sfWidgetFormArrayDependentSelect(array(
'callable' => array('MisDatos', 'getPaises'),
));
$this->widgetSchema['provincia'] = new sfWidgetFormArrayDependentSelect(array(
'callable' => array('MisDatos', 'getProvincias'),
'depends' => 'pais',
));
// validadores
$this->validatorSchema['pais'] = new sfValidatorChoice(array(
'choices' => MisDatos::getPaises(),
));
$this->validatorSchema['provincia'] = new sfValidatorChoice(array(
'choices' => MisDatos::getProvincias(),
));
}
}
También funciona hablitando ajax.
Por widget, en negritas están las requeridas. El sÃmbolo ">" significa que ese widget extiende al otro.
depends = null: ¿De qué otro select se depende? Puede ser el nombre del campo o bien del modelo. El widget se encargará de determinar los detalles de acuerdo a este valor,
add_empty = true: Especifica si agrega una opción en blanco. Puede ser true, false o el texto que quieras que aperzca,
ajax = false: Especifica si se utilizan llamadas remotas para rellenar los valores del select,
cache = true: Especifica si se utilizará la caché de valores de un select una vez que ya fué cargado remotamente (para evitar peticiones extras),
url = sfDependentSelectAuto/_ajax: URL dónde se harás las llamadas remotas para cargar los selects. El valor no debe ser del tipo "modulo/accion" ni nombre de ruta (puede utilizar el método genUrl de sfWebController para genrarla),
params = array(): Parámetros adicionales que desea enviar a la llamada remota.
source_class = null: La clase que se utilizará como orÃgen de datos. (Ver más adelante en personalizando).
source_params = array(): Parámetros enviados al orÃgen de datos.
callable = null: Función o método que se llamará para solicitar el array. En caso de ser un método estático, indicar: array('Clase', 'metodo'). No utilices instancias de clases como $this, ya que no funcionará en las llamadas remotas.
heredadas: depends, add_empty, ajax, cache, url, params
model = null: Modelo que se listará en este select
method = __toString: Método para el texto cada opción
key_method = getPrimaryKey: Método para el valor de cada opción
ref_method = null: Método de la relación con 'depends'
order_by = null: Órden del lista, del tipo array('columna', {'asc'|'desc'})
heredadas: depends, add_empty, ajax, cache, url, params
table_method = null: Método que se llamará en la clase Table del objeto para devolver los valores a mostrar.
heredadas: depends, add_empty, ajax, cache, url, params, model, method, key_method, ref_method, order_by
peer_method = null: Método que se llamará en la clase PEER del objeto para devolver los valores a mostrar.
heredadas: depends, add_empty, ajax, cache, url, params, model, method, key_method, ref_method, order_by
En el caso que desees tener el control de los datos en las llamadas tanto locales como remotas puedes hacerlo de varias formas.
Si trabajas con un ORM, lo más fácil será especificar la opción '{table|peer}_method' en el widget y luego implementarla en la clase correspondiente.
Veamos un ejemplo para devolver sólo aquellas provincias que sean productoras.
lib/form/doctrine/PersonaForm.class.php:
class PersonaForm extends BasePersonaForm
{
public function configure()
{
...
$this->widgetSchema['provincia'] = new sfWidgetFormDoctrineDependentSelect(array(
'model' => 'Provincia',
'depends' => 'Pais',
'table_method' => 'getProductorasQuery',
));
...
}
}
lib/model/doctrine/ProvinciaTable.class:
class ProvinciaTable extends Doctrine_Table
{
public function getProductorasQuery($refValue)
{
return $this->createQuery('p')->where('p.es_productora = true and p.pais_id = ?', $refValue);
}
}
Es importante que sepas que cuando la acción llama al método {table|peer}, se le pasa como parámetro el valor de referencia, para que puedas filtrar tus datos de acuerdo al valor del select del cual depende.
En el caso que quieras utilizar otra fuente de datos (por si no usas un ORM o simplemente un canal RSS), puedes crear facilmente uno personalizado. De esta forma podrás aprovochar la automatización de las llamadas tanto locales como remotas y enlazarlas con otros selects dependientes que utilicen otros orÃgenes.
Veamos cómo hacer para crear tu propio orgien de datos para provincias productoras.
lib/ProvinciaProductoraSource.class.php:
class ProvinciaProductoraSource extends sfDependentSelectObjectSource
{
// como vamos a trabajar con objetos extendemos a sfDependentSelectObjectSource
// en tu caso puedes extender directamente a sfDependentSelectSource
public function getObjects($fk = null)
{
return ProvinciaTable::getInstance()->createQuery('p')
->where('p.es_productora = true and p.pais_id = ?', $fk)
->execute();
}
public function getObject($pk)
{
// este método se utiliza para la reconstrucción de la cadena de
// selects como se explica más adelante. de momento sólo preocuparnos
// en devolver el objeto que corresponde con el parametro $pk
return ProvinciaTable::getInstance()->find($pk);
}
}
lib/form/doctrine/PersonaForm.class.php:
class PersonaForm extends BasePersonaForm
{
public function configure()
{
...
// nota que cuando utilizas tu propia fuente de datos tienes que
// utilizar el widget sfWidgetFormDependentSelect
$this->widgetSchema['provincia'] = new sfWidgetFormDependentSelect(array(
'depends' => 'Pais',
'source_class' => 'ProvinciaProductoraSource',
// tambien puedes enviarle parametros al source
#'source_params'=> array('excepto' => 'Santa Fé'),
));
...
}
}
En este ejemplo hemos extendido a sfDependentSelectObjectSource que facilita el trabajo con objetos, pero si por ejemplo quieres crear una fuente RSS puedes basarte en cómo lo hace sfWidgetFormArrayDependentSelect.
También puedes crear tu propio widget para que utilice siempre este orÃgen o bien añadirle otra funcionalidad, revisa las clases sfDependentSelectSource y sfWidgetFormDependentSelect para conocer como crear las tuyas.
Otra forma es extender tu módulo a sfActionsDependentSelect y definir métodos de la forma getValuesFor{name} y getRefValueFor{name}. No olvides especificar tu acción en la opción 'url' del widget.
Veamos en el ejemplo de paises, si quisiéramos devolver las provincias de esta forma.
apps/tuapp/modules/tumodulo/actions/actions.class.php:
class tuModuloActions extends sfActionsDependentSelect
{
protected function getValuesForPersonaProvincia($request, $source)
{
// en este caso trabajamos con objetos, pero puedes hacerlo de cualquier
// forma siempre y cuando devuelvas un array con los id como clave
// y los textos como valores del array.
$valores = array();
$provincias = ProvinciaTable::getInstance()->createQuery('p')
->where('p.pais_id = ? and p.es_productora = true')
->execute($request->getParameter('_ds_ref'));
// el parámetro _ds_ref contiene el valor del select del cual depende
// en este caso serÃa el valor de pais.id
foreach ($provincias as $provincia) {
$valores[$provincia->getId()] = $provincia->getNombre();
}
return $valores;
}
protected function getRefValueForPersonaProvincia($request, $source)
{
// este método se utiliza para "reconstruir" la cadena de selects
// dependientes desde el orden inverso. es decir, si sólo se tiene
// el valor de provincia, tenemos que saber de qué pais es para
// seleccionarlo en el select de paises.
// en este caso el valor de _ds_ref es el valor actual del select
// de provincias (provincia.id) y debemos devolver a qué paÃs
// corresponde (provincia.pais_id)
$provincia = ProvinciaTable::getInstance()->find($request->getParameter('_ds_ref'));
return $provincia->getPaisId();
}
}
Nota que cada método también recibe como parámetro la fuente de datos por si te es necesario.
Puedes revisar la clase sfActionsDependentSelect para conocer más sobre cómo extender la funcionalidad en tu módulo.
Si no puedes extender tu módulo a la clase antes mencionada y definitivamente ninguna de las formas ofrecidas satisfacen tus necesidades, deberás implementar una acción en tu módulo que contenga toda la funcionalidad como la acción '_ajax' lo hace en la clase sfActionsDependentSelect. Con Firebug puedes ver los parámetros enviados vÃa POST.
Por defecto se incluye en el plugin dos versiones del script, normal y minimizado. Siempre se cargará el minimizado a menos que cambies tu configuración:
apps/tuapp/config/app.yml:
dev:
app:
...
sfDependentSelectPlugin:
js: normal
También puedes establecer 'false' para que no se cargue el script. El valor para mostrarlo minimizado es 'minimized'.
Desgraciadamente no hablo inglés y he elaborado la mayor parte del código tratando de usar terminologÃa en ese idioma con ayuda de traductores automáticos. Pido disculpas por incoherencias encontradas y agradecerÃa me informen para poder corregirlas.
Me pueden escribir a mi correo personal por cualquier sugerencia o duda. Con mucho gusto estaré dispuesto a mejorar los aspectos que me indiquen.
A [TRADUCTORES] por ofrecerse tan amablemente a corregir y traducir la documentación y a todos los miembros de la lista de symfony-es por formar esta gran comunidad.

