Aprendiendo Ext JS 3

Ordenar los nodos de un TreePanel Más videos

Descripción del tema

En el tutorial de hoy veremos cómo podemos ordenar los elementos del TreePanel y además los guardaremos en una base de datos MySQL, este es el primer tutorial donde mostraré un poco más allá del uso de ExtJS y hablare de cómo generar el árbol a partir de una tabla en la base de datos. Supongamos que tenemos una tienda en línea, los productos que se venderán estarán categorizados para que el usuario final pueda navegar fácilmente por la tienda, una categoría puede estar contenida dentro de otra categoría principal, por ejemplo podríamos tener la categoría “Música” y tener alguna subcategoría como “Rock”, el número de niveles será definido por el administrador de la tienda, así que no pondremos límite de niveles.

La demostración

El día de hoy solamente veremos cómo podemos ordenar las categorías existentes, en el siguiente tutorial prometo mostrar cómo podemos agregar o eliminar categorías. Te recomiendo visitar el ejemplo que he preparado.
Ordenar un TreePanel

Ejemplo del tutorial

Material de apoyo

Antes de continuar descarguemos el material de apoyo para este tutorial. Lo descomprimimos y copiamos los archivos a nuestro servidor Web que instalamos al inicio del curso, esto es necesario porque usaremos Ajax, si es necesario cambia las rutas a la librería ExtJS dentro del archivo “tree-order.html”.

Creación de la base de datos

Lo primero que haremos será crear la tabla que usaremos para almacenar las categorías, vamos a crear una tabla que se llame “categories” y que contenga los campos “id, id_parent, category, order_number y description”.
Ordenar un TreePanel

Descripción de la tabla "categories"

Para los que solo les gusta copiar/pegar aquí les dejo el SQL necesario para hacer lo que describí anteriormente.
CREATE TABLE  categories (
	id INT NOT NULL AUTO_INCREMENT PRIMARY KEY ,
	id_parent INT NULL ,
	category VARCHAR( 255 ) NOT NULL ,
	order_number INT NULL,
	description TEXT NOT NULL
) ENGINE = INNODB;
Ahora vamos a crear algunas categorías para que podemos probar el funcionamiento del tutorial, aquí dejo unos cuantos “inserts” a la tabla “categories”:
INSERT INTO categories (id, id_parent, category, description) 
 VALUES (NULL, NULL, 'Music', 'The music category'), 	
		(NULL, NULL, 'Movies', 'All about movies and stuff'),	
		(NULL, NULL, 'Games', 'Are you a gamer?'),	
		(NULL, 1, 'Classic', 'For those who love a quiet moment'),	
		(NULL, 1, 'Rock', 'Let\'s rock and roll!'),	
		(NULL, 1, 'Pop', 'Do you like pop music?'),	
		(NULL, 2, 'Action', 'Actions movies for everyone'),	
		(NULL, 2, 'Romantic', 'Watch this with your girlfriend'),
		(NULL, 3, 'Sports', 'Want to play a game?'),
		(NULL, 3, 'First person shooter', 'Let\'s kill some fools'),
		(NULL, 3, 'Racing', 'For those who like to drive!'),
		(NULL, 9, 'Soccer', 'All about foot ball soccer'),
		(NULL, 9, 'Hockey', 'Ready for the ice?');
Aquí lo único interesante es el campo “id_parent”, este campo tendrá el “id” de la categoría “padre”, de esta manera podremos construir una estructura de árbol con “n” cantidad de niveles, los otros campos creo que son muy descriptivos por lo tanto no hablaré de ellos.
Ordenar un TreePanel

Información de prueba

Creación de la estructura de árbol

El siguiente paso es crear una estructura de árbol con los registros que tenemos en nuestra base de datos, he creado una clase en PHP que nos hará la vida mucho más sencilla.
/**
 * This class creates a Tree structure of information for the TreePanel component
 * of the ExtJS library.
 *
 * @author Crysfel Villa
 * @date 12/18/2009
 *
 */
class TreeExtJS{
	private $tree = array();
	private $index = array();
	private $cont = 0;
	
	/**
	 * 	This method inserts a node to the Tree, the child param may contain an
	 * 	"id" property that will be use as a "key", if the child param doesn't contains
	 *	an "id" property a generated "key" is given to the node.
	 *
	 *	@child the node to insert
	 *	@parentKey(optional) The parent key where the node will be inserted, if null 
	 *	the node is inserted in the root of the Tree
	 */
	public function addChild($child,$parentKey = null){
		$key = isset($child["id"])?$child["id"]:'item_'.$this->cont;
		$child["leaf"] = true;
		if($this->containsKey($parentKey)){
			//added to the existing node
			$this->index[$key] =& $child;
			$parent =& $this->index[$parentKey];
			if(isset($parent["children"])){
				$parent["children"][] =& $child;
			}else{
				$parent["leaf"] = false;
				$parent["children"] = array();
				$parent["children"][] =& $child;
			}
		}else{
			//added to the root
			$this->index[$key] =& $child;
			$this->tree[] =& $child;
		}
		$this->cont++;
	}
	
	/**
	 *	Return a node by the given key
	 *	@key
	 */
	public function getNode($key){
		return $this->index[key];
	}
	
	/**
	 *	@TODO Remove the node from the Tree
	 *	@key
	 */
	public function removeNode($key){
		//unset($this->index[key]);
	}
	
	/**
	 *	Check if exist a node with the given key
	 */
	public function containsKey($key){
		return isset($this->index[$key]);
	}
	
	/**
	 *	Return a representation of the Tree structure in JSON format
	 */
	public function toJson(){
		return json_encode($this->tree);
	}
}
He documentado los métodos que contiene esta clase, te recomiendo copiar/pegar el código en un archivo que se llame “TreeExtJs.class.php” (ya viene en el material de apoyo), la idea de esta clase es ir insertando nodos dentro de algún nodo existente en el árbol o bien a la raíz, una vez construida la estructura podremos generar una representación de esta en formato JSON para que se lo enviemos al componente TreePanel y este pueda desplegarla correctamente. Es importante mencionar que es la primera versión de esta clase, y que en el futuro planeo agregarle más funcionalidades y mejoras, pero por ahora hace lo necesario para este tutorial.

Generar el JSON a desplegar

Ahora tenemos que conectarnos a la base de datos y sacar la información que necesitamos desplegar, vamos a escribir el siguiente código dentro el archivo “tree-order.php”:
// Include the Tree class to generate the TreePanel JSON easily
include("TreeExtJS.class.php"); 

// Make a MySQL Connection and select the database
$conn = mysql_connect("localhost", "root", "") or die(mysql_error());
mysql_select_db("testing") or die(mysql_error());

// Retrieve all the data from the "categories" table
$result = mysql_query("SELECT * FROM categories order by id_parent,order_number asc") or die(mysql_error());
Notemos que estamos haciendo uso de “order by” para que las categorías sean regresadas ordenadas por el “id_parent” y “order_number”, esto es de suma importancia ya que de esta manera nos será más sencillo crear la estructura de árbol. Lo siguiente es crear un “array” con la información que hemos sacado de la base de datos, esto lo hacemos de la siguiente manera:
// Create an array of the data
$data = array();
while($row = mysql_fetch_array( $result )){
	array_push($data,array(
		"id" => $row["id"],
		"idParent" => $row["id_parent"],
		"text" => $row["category"],
		"orderNumber" => $row["order_number"],
		"description" => $row["description"]
	));
}
Aquí no hay nada complicado, solamente estamos “populando” la colección “$data” con los registros de la base de datos. Por último necesitamos crear la estructura de árbol haciendo uso de la clase “TreeExtJs”, esto será muy sencillo.
// Creating the Tree
$tree = new TreeExtJS();
for($i=0;$i<count($data);$i++){
	$category = $data[$i];
	$tree->addChild($category,$category["idParent"]);
}

echo $tree->toJson();
Mediante un ciclo vamos recorriendo la información que sacamos de la base de datos y vamos agregando nuevos nodos al árbol, aquí es donde es de suma importancia que vengan ordenados los elementos por el campo “id_parent” ya debemos insertar primero los nodos padre para que al insertar los hijos no se ocasione ningún error. No olvidemos cerrar la conexión a la base de datos.
mysql_close($conn);

Hemos terminado la parte más complicada de este tutorial, ahora vamos a escribir el JavaScript necesario para desplegar correctamente la información que creamos en el servidor.

Creación del TreePanel

Editamos el archivo “tree-order.js” y dentro de la función “init” creamos el componente que necesitamos.
var tree = new Ext.tree.TreePanel({  	
	border: false,
	autoScroll:true,
	dataUrl:'tree-order.php',
	enableDD: true,			// Step 1
	root: new Ext.tree.AsyncTreeNode({
		text: 'Categories',
		draggable: false		//Step 2
	})
});

var win = new Ext.Window({		
	title:'Reorder the nodes',
	layout:'fit',
	width:300,
	height:400,
	items: tree
});
win.show();	
Anteriormente hemos visto como crear un TreePanel, en el código anterior solamente hay dos diferencias con respecto a los tutoriales pasados. En el paso uno habilitamos que los nodos puedan ser arrastrados y soltados (Drag and Drop), esto nos permitirá que el usuario pueda reacomodar las categorías a su gusto. En el paso dos le indicamos al nodo “root” que no se pueda arrastrar, esto es importante para que el usuario no pueda mover el nodo principal.
Ordenar un TreePanel

Creación de un TreePanel

Si colapsamos el nodo principal podremos ver la información que tenemos en nuestra base de datos.
Ordenar un TreePanel

Información cargada de la base de datos MySQL

También podemos arrastrar los nodos de un lugar a otro con el mouse.
Ordenar un TreePanel

Drag and Drop a los elementos

El mismo componente se encarga de monitorear donde se soltará el nodo que se está arrastrando. Si es un lugar incorrecto simplemente no permitirá que sea soltado avisando al usuario con un ícono en color rojo.
Ordenar un TreePanel

Restricciones del componente

Guardando el orden

Hasta ahora podemos ordenar los elementos en el TreePanel, pero si actualizamos la página vemos que el orden es el mismo que teníamos al principio, para que los cambios puedan persistir debemos guardar los cambios efectuados en nuestra base de datos. Debemos buscar el evento adecuado para guardar los cambios, podríamos crear un botón “guardar” para que el usuario guarde los cambios cuando crea que es necesario, o bien podríamos hacerlo más transparente al usuario y guardar los cambios automáticamente cuando suceda cualquier movimiento en los nodos. Para este tutorial haremos la segunda opción, para eso debemos utilizar el evento “movenode” que se disparará cuando un nodo cambió de lugar.
tree.on('movenode',function(tree,node,oldParent,newParent,index ){
	//save the new order
});
Podemos ver en los parámetros que uno lleva por nombre “newParent”, necesitamos utilizar este parámetro para iterar por todos sus hijos de primera generación, es decir aquellos que sean descendientes directos de él, luego sacamos el “ID” de cada uno de ellos para enviarlos en el orden correcto al servidor y que éste los guarde en la base de datos.
var nodes = [];
newParent.eachChild(function(n){
	nodes.push(n.attributes.id);		//Step 1
});
tree.el.mask('Saving…', 'x-mask-loading');	//Step 2
Ext.Ajax.request({
	url: 'tree-order.php',			//Step 3
	params:{				//Step 4
		updateOrder: true,
		parent: newParent.attributes.id,
		nodes: nodes.join(',')
	},
	success: function(){
		tree.el.unmask();		//Step 5
	},
	failure: function(){
		tree.el.unmask();		//Step 6
		Ext.Msg.alert('Error','Error saving the changes');
	}
});
En el paso uno recolectamos los “ID” de cada nodo, esto es necesario para generar el orden que guardaremos en la base de datos. En el paso dos enmascaramos el TreePanel y mostramos un mensaje de “Saving…” esto es importante para que el usuario se dé cuenta que se están guardando sus cambios. En el paso tres indicamos la “url” que procesará la petición Ajax, en este caso usaremos el mismo PHP, pero si estuviésemos usando algún Framework como: CodeIgniter, Ruby on Rails, Groovy and Grails, etc., pondríamos un “controller” diferente. En el paso cuatro definimos los parámetros que enviaremos al servidor cuando se realice la petición Ajax, estos parámetros son “updateOrder” con el cual decidiremos si vamos hacer un “update” o un “select” a la tabla en la base de datos, el parámetro “parent” es importante porque permitiremos que el usuario cambie los nodos inclusive a otras “ramas”, por lo tanto necesitamos saber el padre de los nodos que enviamos en el parámetro “nodos”. El paso cinco se ejecutará si se realizó satisfactoriamente la actualización, aquí solamente quitamos la máscara que pusimos al inicio de la petición Ajax. El paso seis se ejecutará si ha sucedido algún error en el servidor o en la petición, aquí solamente quitamos la máscara y enviamos un mensaje de error al usuario para informarle que los cambios no se guardaron.
Ordenar un TreePanel

Guardando los cambios den la base de datos

Por el momento es todo lo que escribiremos en JavaScript ahora es el momento de hacer la lógica necesaria en el servidor para que guardemos los cambios efectuados. En el archivo “tree-order.php” escribiremos el siguiente código después de abrir la conexión a la base de datos:
// Include the Tree class to generate the TreePanel JSON easily
include("TreeExtJS.class.php"); 

// Make a MySQL Connection and select the database
$conn = mysql_connect("localhost", "root", "") or die(mysql_error());
mysql_select_db("testing") or die(mysql_error());

if(isset($_POST["updateOrder"])){	// Step 1
	//Aquí vamos a crear el UPDATE a la tabla categories
}else{
	//aquí dejamos el código necesario para generar el JSON del…
// TreePanel que escribimos anteriormente
}

mysql_close($conn);
En el paso uno solamente estamos tomando la decisión de hacer el UPDATE o hacer el SELECT, esto es definido por el parámetro “updateOrder”, si el parámetro existe en el request haremos un UPDATE a la tabla, si no existe entonces se hace todo lo que ya escribimos anteriormente. Para guardar los cambios lo hacemos de la siguiente manera:
$nodes = $_POST["nodes"];		//Step 1
$ids = explode(",",$nodes);
$idParent = (int)$_POST["parent"];	//Step 2
for($i=0;$i<count($ids);$i++){
       $id = (int)$ids[$i];
       $query = sprintf("UPDATE categories SET order_number = %d,id_parent = %d WHERE id = %d",
				$i,$idParent,$id);	//Step 3
	mysql_query($query);		//Step 4
}
Primero tomamos los nodos que editaremos y creamos un arreglo con cada “ID”. El segundo paso es tomar el parámetro “parent” y convertirlo a entero. En el tercer paso creamos el query para actualizar el campo “order_number” y “id_parent”, lo hacemos de esta manera para asegurarnos que no hagan un SQL Injection, los parámetros “id” e “idParent” deben ser enteros. En el paso cuatro ejecutamos el query para que se guarden los cambios correspondientes. Ahora solo queda probar la aplicación, debería ser capaz de mostrar y guardar los cambios efectuados inclusive si la ejecutamos de diferentes computadoras, esto es posible porque la información esta contenida en la base de datos y no solamente en la memoria de nuestra computadora u ordenador.
Ordenar un TreePanel

El orden persiste

Conclusiones

Hemos visto como es muy sencillo ordenar los nodos de un TreePanel, la parte más complicada en mi opinión es crear la estructura de arbol, esto lo había comentado en tutoriales anteriores pero ahora hemos visto como crearla. Recuerda seguirnos en Twitter para estar actualizado de los nuevos tutoriales, tenemos algunas sorpresas para el próximo año que seguramente les gustarán, sigan pendientes y nos vemos en la segunda parte de este tutorial.

Te gustaría recibir más tutoriales como este en tu correo?

Este tutorial pertenece al curso Aprendiendo Ext JS 3, te recomiendo revises el resto de los tutoriales ya que están en secuencia de menor a mayor complejidad.

Si deseas recibir más tutoriales como este en tu correo te recomiendo registrarte al curso, si ya eres miembro solo identifícate y registrate al curso, si no eres miembro te puedes registrar gratuitamente!

Si no gustas registrarte en este momento no es necesario! Aún así puedes recibir los nuevos tutoriales en tu correo! Jamás te enviaremos Spam y puedes cancelar tu suscripción en cualquier momento.

¿Olvidaste tu contraseña?

13Comentarios

  • Avatar-2 Imfael 22/12/2009

    ¡Genial!

    • Avatar-4 Bladimir Balbin 07/01/2010

      fenomenal, gracias crysfel

      • Avatar-6 Bladimir Balbin 07/01/2010

        crysfel, una pregunta porque al correr el ejemplo no despliega bien los nodos y solo muestra el root "categories". a otra cosa me baje el codigo fuente y como que hace falta algo porque tampoco corre, muchas gracias

        • Avatar-6 Crysfel 07/01/2010

          Eso es porque alguien movió todos los nodos al root, por ahora el ejemplo no permite transformar una hoja en rama, sería bueno hacer un tut al respecto, por otro lado para ejecutar el ejemplo en tu máquina necesitas un servidor web y una base de datos MySQL, luego cambiar el passwd y usuario para hacer la conexión correctamente. saludos

          • Avatar-3 Buildero 12/02/2010

            Que tal Crysfel, Bladimir Balbin se refiere ál demo que tienes como demostración del tutorial ya que no refleja exactamente lo que explicas; como él dice, sólo se muestra el root "categories" con todos los datos en un sólo nivel. En el caso del código fuente que acompaña al tuto, éste no viene con los archivos que en teoría deberían ser, ya que no están los siguientes archivos: - tree-order.js - tree-order.html En lugar de éstos, agregaste dos archivos del tutorial anterior: - tree-show-files.js - tree-show-files.html Debo decir que siguiendo el tuto y creando los archivos no hay ningún problema con el mismo, el detalle estpa sólo en el demo y su código fuente. Saludos!

            • Avatar-10 Galatea 04/03/2010

              me surge una duda en este ejemplo, ya que no se en que parte del js debo insertar el codigo para que guarde los cambios en la base de datos... podrias porfavor ayudarme con el codigo fuente de este java script... muchisimas gracias

              • Avatar-10 softkrates 01/04/2010

                Soy nuevo con extJs y creo que son buenos tutoriales: paso a paso. En este ejemplo tengo un problema y no funciona. Creo que es problema del parametro que envio a la base de datos. No se porque pero con Tamper veo que envia: node = xnode-3 ¿ no deberia de ser nulo o un numero ?

                • Avatar-4 Manuel 13/05/2010

                  Excelente tutorial referente a lo que dice Crysfel de convertir una hoja en rama, si tal hoja tine leaf:false dejara que le agreges hojas y se convertira en rama

                  • Avatar-3 Iza 17/05/2010

                    Si yo Tengo el mismo problema

                    • Avatar-9 Edwin Leonardo 20/05/2010

                      Hola amigo, una pregunta habra una manera de que pueda obtener el codigo entero de este ejemplo, he estado uniendo el codigo pero me salio al final, y he probado con unos cambios pero no me funciona y me urge mucho correr este ejemplo. Muchas gracias.

                      • Avatar-7 Ricardo B. 08/02/2011

                        Hay un error en los scripts que se descargan, para que funcionen correctamente deben reemplazar en: 'tree-show-files' linea 14: dataUrl:'get-files.php', por: linea 14: dataUrl:'tree-order.php',

                        • Avatar-4 Miguel Llerena 22/03/2015

                          Buenas, tengo un problema con un treepanel en el cual tengo un padre y hijo mediante mysql pero quisiera que ese hijo tambien pueda contar con otro hijo adicional y asi los demas. Ojala puedan orientarme aqui pego una parte del codigo en mencion. Gracias. //Building the menu..Creando el Menu function menuMain() { $session_data = $this->session->userdata('logged_in'); $data['username'] = $session_data['username']; $username=$session_data['username']; $this->db->select('permissions.Menu_id as menuId'); $this->db->from('permissions'); $this->db->join('users','permissions.Group_id=users.grupoId'); $this->db->join('menu','permissions.Menu_id = menu.id'); $this->db->where('users.username',$username); $resultdb = $this->db->get(); $folder = array(); if($resultdb->num_rows()!=0){ $in = '('; /* fetch associative array */ foreach($resultdb->result_array() as $user){ $in .= $user['menuId'] . ","; } $in = substr($in, 0, -1).')' ; /* free result set */ $resultdb->free_result(); $resultdb = $this->db->query("SELECT * FROM MENU WHERE parent_id IS NULL AND id in". $in); if ($resultdb->result_array()!=0) { foreach($resultdb->result_array() as $r){ $sqlquery=$this->db->query("SELECT * FROM MENU WHERE parent_id=".$r['id']. " AND id in". $in); // check if have a child node if ($nodes = $sqlquery) { // determine number of rows result set $count = $nodes->num_rows(); if ($count > 0){ // if have a child $r['leaf'] = false; $r['items'] = array(); foreach($nodes->result_array() as $item){ $item['leaf'] = true; $r['items'][] = $item; } } else { // if have no child $r['leaf'] = true; } $folder[] = $r; } } } } echo json_encode(array( "items" => $folder )); } }

                          • Avatar-12 Crysfel Villa 23/03/2015

                            Tu pregunta va orientada a como estructurar tu base de datos para guardar esa información? o como generar el arbol desde php?

                          Instructor del curso

                          Crysfel3

                          Autor: Crysfel Villa

                          Es ingeniero de software con más de 7 años de experiencia en desarrollo web.

                          Descarga Código Fuente Ver Demostración

                          Regístrate a este curso

                          Este tutorial pertenece al curso Aprendiendo Ext JS 3, revisa todos los tutoriales que tenemos en este mismo curso ya que están en secuencia y van de lo más sencillo a lo más complicado.

                          Tendrás acceso a descargar los videos, códigos y material adicional.

                          Podrás resolver los ejercicios incluidos en el curso así como los Quizzes.

                          Llevarás un registro de tu avance.