Listas en cascada con jQuery, Kendo UI, XML y PHP

El problema es de lo más básico, sobre todo si se considera desde el punto de vista del usuario.

Desde el punto de vista del programador se trata de enlazar dos listas de selección, llenar las listas con los nodos de un archivo XML creado al vuelo a través de una consulta a una base de datos y filtrar la información dependiendo de la selección en la primera lista.
En forma visual se trata de lo siguiente.

Se tienen dos listas, la segunda lista está deshabilitada y se activa hasta que se haga una selección en la primera lista:

Con un XML (que se puede construir fácilmente con PHP) se llena la primera lista:

Cuando se elije una opción; se activa la segunda lista:

En la segunda lista, se filtran los datos del archivo XML para colocar únicamente los nodos que tengan correspondencia con la primera selección:

Ahora bien, para lograr esto, no se necesita reinventar la rueda pero si hay que poner algunas líneas de programación y comprender el proceso como se muestra a continuación:

1) Incluir las librerías de jQuery,  las hojas de estilo y los widgets de Kendo UI web, (una gran herramienta para el desarrollo web):

<head>
 <link href="css/kendo/kendo.common.min.css" rel="stylesheet">
 <link href="css/kendo/kendo.default.min.css" rel="stylesheet">
 <script src="js/kendo/jquery.min.js"></script>
 <script src="js/kendo/kendo.web.min.js"></script>
</head>

2) Crear las dos listas de selección:

<body>
 <label for="nationality">Nacionalidad</label>
 <input name="nationality" id="nationality" required="required"><br />
 <label for="region">Regi&oacute;n</label>
 <input name="region" id="region" required="required"><br />
</body>

3) Crear y configurar la lista de selección Padre:

<body>
 <script>
 $("#nationality").kendoDropDownList({
  optionLabel: "Selecciona una...",
  // Valor que regresa el campo al seleccionar el item (se obtiene del XML)
  // este valor se empleara para filtrar la segunda lista:
  dataValueField: "idNacionalidad",
  // Texto que aparece en la lista de items (se obtiene del XML):
  dataTextField: "nombreNacionalidad",
  dataSource: {
   type: "xml",
   // Mas adelante se detalla el script de PHP
   transport:{ read: "makeXML.php?type=nationality" },
   schema: {
    type: "xml",
    // Estructura del árbol, nodos e hijos del archivo XML 
    data: "content/nacionalidades/nacionalidad",
    model: {
     fields: {
      idNacionalidad: "idNacionalidad/text()",
      nombreNacionalidad: "nombreNacionalidad/text()"
     }
    }
   }
  }
 });
 </script>
</body>

4) Crear y configurar la lista de selección Hija:

<body>
 <script>
  $("#region").kendoDropDownList({
   // Habilitar el elemento, únicamente si en la lista padre hay una selección
   cascadeFrom: "nationality",
   optionLabel: "Selecciona una...",
   // Valor que regresa el campo seleccionado
   dataValueField: "idRegion",
   // Texto que aparece en la lista de items
   dataTextField: "nombreRegion",
   dataSource: {
    type: "xml",
     // Nodo en común en las dos estructuras del archivo XML, permite filtrar los resultados
     filter: [{ field: "idNacionalidad", operator: "eq", value:"" }],
     // Detalles del script de PHP más adelente
     transport: { read: "makeXML.php?type=region" },
     autoBind:false,
     schema: {
      type: "xml",
      // Estructura del árbol/ramas/nodos en el archivo XML
      data: "content/regiones/region",
      model: {
       fields: {
        // Campos de cada nodo que permiten llenar la lista de selección
        idNacionalidad:"idNacionalidad/text()",
        idRegion:"idRegion/text()",
        nombreRegion: "nombreRegion/text()",
       }
      }
     }
    }
   }).data("kendoDropDownList");
 </script>
</body>

5) Tablas de la base de datos en MySQL

-- Tabla con los nombres de los paises del mundo en español
CREATE TABLE IF NOT EXISTS `paises` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `numeroISO` int(3) NOT NULL,
  `iso2` varchar(2) NOT NULL,
  `iso3` varchar(3) NOT NULL,
  `nombre` varchar(255) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8;

Descarga: la lista con los nombres de los países del mundo en SQL

-- Tabla con los nombres de los Estados de la República Mexicana
CREATE TABLE IF NOT EXISTS `estados` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `clave` varchar(2) NOT NULL,
  `nombre` varchar(45) NOT NULL,
  `abrev` varchar(16) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8;

Descarga: la lista con los nombres de los Estados de la República Mexicana en SQL

6) Crear el stream XML a través de un script en PHP (makeXML.php)

<?php
/**
 * @author valar ainur
 * @copyleft 2012
 * @Facultad de Medicina BUAP
 * @Filename: makeXML.php
 */

session_start();
// Esta clase está disponible más adelante para su descarga:
require_once("class.querySimple.php");
// Se informa al navegador que la salida del script debe interpretarse como XML:
header('Content-Type: text/xml');

//
// En la configuración de cada lista, se emplea el metodo GET
// para acceder a los casos: { ?type= nationality | region }
//
switch ($_GET['type']){
 case "nationality":
  print("<content type=\"application/xml\">
   <nacionalidades>
    <nacionalidad>
     <idNacionalidad>1</idNacionalidad>
     <nombreNacionalidad>Mexicana</nombreNacionalidad>
    </nacionalidad>
    <nacionalidad>
     <idNacionalidad>2</idNacionalidad>    
     <nombreNacionalidad>Extranjera</nombreNacionalidad>
    </nacionalidad>    
   </nacionalidades>
  </content>");
 break;

 case "region":
  $DBhost = 'localhost';
  $DBuser = 'root';
  $DBpassword = '!mySuperUltra53cr37pwd!ItsFake';
  $DBname = 'myDataBase';
  // Crear el objeto para realizar las consultas a la BD
  $myQuery = new queryMySQL;
  $myQuery->doConnection($DBhost, $DBuser, $DBpassword, $DBname);
  // Seleccionar las regiones (Estados) de México:
  $sql = "SELECT `nombre` FROM $DBname.`estados` WHERE 1";
  $myQuery->query($sql);
  for($i = 0; $i < $myQuery->countRegisters(); $i++){
   $regionsOfMexico = $myQuery->results();
   //{ idNacionalidad: 1 = Mexicana }
   $regionsOfMexicoXML .= "
    <region>
     <idNacionalidad>1</idNacionalidad>
     <nombreRegion>".$regionsOfMexico['nombre']."</nombreRegion>
    </region>
   ";
  }
  // Seleccionar los paises del mundo desde la BD:
  $sql = "SELECT `nombre` FROM $DBname.`paises` WHERE 1";
  $myQuery->query($sql);
  for($i = 0; $i < $myQuery->countRegisters(); $i++){
   $regionsOfTheWorld = $myQuery->results();
   //{ idNacionalidad: 2 = Extranjera }
   $regionsOfTheWorldXML .= "
    <region>
     <idNacionalidad>2</idNacionalidad>
     <nombreRegion>".$regionsOfTheWorld['nombre']."</nombreRegion>
    </region>
   ";
  }
 // Construir la estructura ordenada del archivo XML  
 print("
  <content type=\"application/xml\">
   <regiones>
    ".$regionsOfMexicoXML."
    ".$regionsOfTheWorldXML."
   </regiones>
  </content>");
 break;
}
?>

7) La clase en PHP para la consulta a la BD:

<?php
class queryMySQL{
 // Variables para la conexion a la BD
 var $DBhost;
 var $DBuser;
 var $DBpasswd;
 var $DBname;
 // Variables de identificacion para cada conexion-consulta
 var $idConection = 0;
 var $idQuery = 0;
 var $db_selected;
 // Variables para el manejo de errores
 var $Errno = 0;
 var $Error = "";

 function doConnection($DBhost, $DBuser, $DBpasswd, $DBname){
  if($DBhost   != "" ) $this->DBhost   = $DBhost;
  if($DBuser   != "" ) $this->DBuser   = $DBuser;
  if($DBpasswd != "" ) $this->DBpasswd = $DBpasswd;
  if($DBname   != "" ) $this->DBname   = $DBname;
  // Conexion
  $this->idConection = mysql_connect($this->DBhost, $this->DBuser, $this->DBpasswd);
  // Error en la conexion?
  if (!$this->idConection){
   $this->Error = mysql_error();
   return 0;
  }
  // Seleccionar la BD
  $this->db_selected = mysql_select_db($this->DBname, $this->idConection);
  if (!$this->db_selected) {
   $this->Error = mysql_error();
   return 0;
  }
  return $this->idConection;
 }

 // Funcion que ejecuta una consulta en SQL
 function query($sql = ""){
  if($sql == ""){
    $this->Error = "Consulta SQL vacía";
    return 0;
  }
  else{
   $this->idQuery = mysql_query($sql, $this->idConection);
   if(!$this->idQuery){
     $this->Errno = mysql_errno();
     $this->Error = mysql_error();
   }
   else{
    return $this->idQuery;
    return $this->Error;
   }
  }
 }

 // Funcion que determina el numero del ultimo error (0 = No hay error)
 function errno(){
  return mysql_errno($this->idConection);
 }

 // Funcion que determina el error en la ejecucion de la consulta.
 function error(){
  return mysql_error($this->idConection);
 }

 // Funcion que cuenta los campos (valores verticales de una tabla)
 function countFields(){
  return @mysql_num_fields($this->idQuery);
 }

 // Funcion que cuenta los registros (valores horizontales) devueltos en un SELECT
 function countRegisters(){
  return @mysql_num_rows($this->idQuery);
 }

 // Funcion que extrae el nombre de un campo de acuerdo a un indice
 function nameField($indexField){
  return @mysql_field_name($this->idQuery, $indexField);
 }

 // Devuelve el número de filas afectadas de la ultima operacion MySQL INSERT, UPDATE, DELETE
 function affectedRows(){
  return @mysql_affected_rows($this->idConection);
 }

 // Devuelve el ultimo ID asignado por mysql
 function getLastID(){
  return @mysql_insert_id();
 }

 // Funcion que devuelve un arreglo con los registros con el par [indice1] => valor1, [llave1] => valor1
 function results(){
  while($row = @mysql_fetch_array($this->idQuery)){
   return $row;
  }
 }

 // Funcion que devuelve un arreglo con los registros con el par [indice1] => valor1, [indice2] => valor2
 function resultsByIndex(){
  while($row = @mysql_fetch_row($this->idQuery)){
   return $row;
  }
 }

 // Funcion que devuelve solo el primer valor de [indice1] => valor1 de cada fila de resultados
 function resultsByFstIndex(){
  while($row = @mysql_fetch_row($this->idQuery)){
   return $row[0];
  }
 }
}
?>

Al final, se pueden enlazar tantas listas se requiera, solo deben tener un campo en común dentro de la estructura del XML para que permita la referenciación y el filtrado de la información.

Y eso es todo… ojalá que me haya dado a entender y que le sea últil a alguien más.

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s