ExtJS CookBook: Código base
03/12/2008 por Javier Caride
Voy a iniciar una serie de artículos a los que voy a denominar ‘ExtJS Cookbook’, en los que iré explicando pequeñas acciones o trucos de ExtJS pero que por ser muy comunes merecen un poco de atención
La primera parte de esta serie de artículos se van a basar en el código de una miniaplicación para gestión de un rudimentario libro de cuentas y que tenéis disponible para evaluar
Este ejemplo muestra cómo hacer varias cosas: cargar datos desde PHP en Extjs, cargar los datos de un FormPanel desde un GridPanel, filtrar un GridPanel desde un formulario externo, cómo obtener los datos de los campos de un FormPanel, etc.
Espero que los artículos que publique estos días os sean de utilidad
Fichero 1: Código Javascript de la miniaplicación
/*
* Definimos nuestros propios validadores de datos
* para los formularios
*/
Ext.apply(Ext.form.VTypes, {
daterange : function(val, field) {
var date = field.parseDate(val);
if(!date){
return;
}
if (field.startDateField && (!this.dateRangeMax || (date.getTime() != this.dateRangeMax.getTime()))) {
var start = Ext.getCmp(field.startDateField);
start.setMaxValue(date);
start.validate();
this.dateRangeMax = date;
}
else if (field.endDateField && (!this.dateRangeMin || (date.getTime() != this.dateRangeMin.getTime()))) {
var end = Ext.getCmp(field.endDateField);
end.setMinValue(date);
end.validate();
this.dateRangeMin = date;
}
return true;
},
/**
* Definimos un nuevo validador: exclusionNotZero
*
* Valida un par de campos numéricos para que no puedan tener valor positivo los dos ni
* valor cero los dos.
*
**/
exclusionNotZero: function(val, field) {
if (field.exclusionPair) {
var pair = Ext.getCmp(field.exclusionPair);
var pairValue = pair.getValue();
if ((pairValue > 0) && (val > 0)) {
field.markInvalid('Un movimiento no puede tener a la vez haber y deber');
return false;
} else if ((pairValue == 0) && (val == 0)){
field.markInvalid('Un movimiento no puede tener haber y deber a cero');
return false;
} else {
field.clearInvalid();
pair.clearInvalid();
return true;
}
}
return true;
}
});
Ext.onReady(function() {
/*
* Creamos un espacio de nombres
*/
Ext.namespace('GestorCuentas');
/*
* Definimos el registro para un movimiento
*/
GestorCuentas.movimientoRecord = new Ext.data.Record.create([
{name: 'mov_id', type: 'int'},
{name: 'mov_concepto', type: 'string'},
{name: 'mov_fecha', type: 'date', dateFormat: 'Y-m-d'},
{name: 'mov_deber', type: 'float'},
{name: 'mov_haber', type: 'float'}
]);
/*
* Creamos el reader para el Grid de movimientos
*/
GestorCuentas.movimientosGridReader = new Ext.data.JsonReader({
root: 'data',
totalProperty: 'total',
id: 'mov_id'},
GestorCuentas.movimientoRecord
);
/*
* Creamos el DataProxy para carga remota de los datos
*/
GestorCuentas.movimientosDataProxy = new Ext.data.HttpProxy({
url: 'cargaMovimientos.php',
method: 'POST'
});
GestorCuentas.movimientosDataStore = new Ext.data.Store({
id: 'movimientosDS',
proxy: GestorCuentas.movimientosDataProxy,
reader: GestorCuentas.movimientosGridReader
});
/*
* Creamos el modelo de columnas para el grid de movimientos de la cuenta
*/
GestorCuentas.movimientosColumnMode = new Ext.grid.ColumnModel(
[{
header: 'Fecha',
dataIndex: 'mov_fecha',
width: 80,
renderer: Ext.util.Format.dateRenderer('d/m/Y')
},{
header: 'Concepto',
dataIndex: 'mov_concepto',
width: 200
},{
header: 'Haber',
dataIndex: 'mov_haber',
width: 90
},{
header: 'Deber',
dataIndex: 'mov_deber',
width: 90
}]
);
/*
* Creamos el reader para el formulario de alta/modificación de movimientos
*/
GestorCuentas.movimientosFormReader = new Ext.data.JsonReader({
root : 'data',
successProperty : 'success',
totalProperty: 'total',
id: 'mov_id'
},GestorCuentas.movimientoRecord
);
/*
* Creamos el grid de movimientos
*/
GestorCuentas.movimientosGrid = new Ext.grid.GridPanel({
id : 'mov-movimientos-grid',
store : GestorCuentas.movimientosDataStore,
cm : GestorCuentas.movimientosColumnMode,
enableColLock : false,
width : 750,
height : 550,
bbar : new Ext.PagingToolbar({
pageSize: 20,
store: GestorCuentas.movimientosDataStore,
displayInfo: true
}),
selModel : new Ext.grid.RowSelectionModel({singleSelect:false})
});
/*
* Creamos el formulario para filtrar el grid
*/
GestorCuentas.filterForm = new Ext.FormPanel({
id: 'form-filtro',
region: 'north',
split: false,
frame: true,
height: 100,
width: 800,
items: [{
xtype: 'datefield',
width: 200,
fieldLabel: 'Desde',
name: 'startdt',
id: 'startdt',
vtype: 'daterange',
endDateField: 'enddt' // id of the end date field
},{
xtype: 'datefield',
width: 200,
fieldLabel: 'Hasta',
name: 'enddt',
id: 'enddt',
vtype: 'daterange',
startDateField: 'startdt' // id of the start date field
}]
});
/*
* Añadimos el botón para borrar el filtro
*/
GestorCuentas.filterForm.addButton({
text : 'Borrar filtro',
disabled : false,
handler : function() {
GestorCuentas.filterForm.getForm().reset();
GestorCuentas.movimientosDataStore.baseParams = {
dateStart: '',
dateEnd: ''
};
GestorCuentas.movimientosDataStore.load({params: {start:0,limit:20}});
}
});
/*
* Añadimos el botón para filtrar
*/
GestorCuentas.filterForm.addButton({
text : 'Filtrar',
disabled : false,
handler : function() {
var startDate = GestorCuentas.filterForm.findById('startdt').getValue();
var endDate = GestorCuentas.filterForm.findById('enddt').getValue();
GestorCuentas.movimientosDataStore.baseParams = {
dateStart: startDate.dateFormat('Y-m-d'),
dateEnd: endDate.dateFormat('Y-m-d')
};
GestorCuentas.movimientosDataStore.load({params: {start:0,limit:20}});
}
});
/*
* Creamos el formulario de alta/modificación de movimientos
*/
GestorCuentas.movForm = new Ext.FormPanel({
id: 'form-movimientos',
region: 'west',
split: false,
collapsible: true,
frame: true,
width: 300,
minWidth: 300,
height: 500,
waitMsgTarget: true,
reader: GestorCuentas.movimientosFormReader,
items: [{
fieldLabel : 'Haber',
xtype: 'numberfield',
id: 'frm_mov_haber',
name : 'mov_haber',
exclusionPair: 'frm_mov_deber',
vtype: 'exclusionNotZero',
allowBlank:false,
width : 175
},{
fieldLabel : 'Deber',
xtype: 'numberfield',
id: 'frm_mov_deber',
name : 'mov_deber',
exclusionPair: 'frm_mov_haber',
vtype: 'exclusionNotZero',
allowBlank:false,
width : 175
}, {
fieldLabel : 'Concepto',
id: 'frm_mov_concepto',
name : 'mov_concepto',
allowBlank:false,
xtype: 'textarea',
width : 175,
height: 225
}, {
fieldLabel : 'Fecha',
id: 'frm_mov_fecha',
name : 'mov_fecha',
allowBlank:false,
xtype: 'datefield',
renderer: Ext.util.Format.dateRenderer('d/m/Y'),
width : 175
}, {
id: 'frm_mov_id',
name : 'mov_id',
xtype: 'hidden'
}]
});
/*
* Añadimos el botón para borrar el formulario
*/
GestorCuentas.movForm.addButton({
text : 'Borrar formulario',
disabled : false,
handler : function() {
GestorCuentas.movForm.getForm().reset();
}
});
/*
* Añadimos el botón para guardar los datos del formulario
*/
GestorCuentas.movForm.addButton({
text : 'Guardar',
disabled : false,
handler : function() {
GestorCuentas.movForm.getForm().submit({
url : 'salvaMovimientos.php',
waitMsg : 'Salvando datos...',
failure: function (form, action) {
Ext.MessageBox.show({
title: 'Error al salvar los datos',
msg: 'Error al salvar los datos.',
buttons: Ext.MessageBox.OK,
icon: Ext.MessageBox.ERROR
});
},
success: function (form, request) {
Ext.MessageBox.show({
title: 'Datos salvados correctamente',
msg: 'Datos salvados correctamente',
buttons: Ext.MessageBox.OK,
icon: Ext.MessageBox.INFO
});
responseData = Ext.util.JSON.decode(request.response.responseText);
GestorCuentas.movForm.getForm().reset();
GestorCuentas.movimientosDataStore.load({params: {start:0,limit:20}});
}
});
}
});
/*
* Añadimos el evento doble click en una fila para editar el registro correspondiente
*/
GestorCuentas.movimientosGrid.on('rowdblclick',function( grid, row, evt) {
var movRecord = GestorCuentas.movimientosDataStore.getAt(row);
GestorCuentas.movForm.getForm().load({
url : 'cargaMovimiento.php',
method: 'POST',
params: {
mov_id: movRecord.data.mov_id
},
waitMsg : 'Espere por favor'
});
});
/*
* Creamos la ventana
*/
GestorCuentas.contentWindow = new Ext.Window({
id: 'libro-cuentas',
title:'Gestor Libro de Cuentas',
width:860,
height:600,
iconCls: 'icon-grid',
shim:false,
animCollapse:false,
constrainHeader:true,
layout: 'border',
renderTo: document.body,
items:[GestorCuentas.filterForm, GestorCuentas.movForm, {
id: 'col-grid-movimientos',
xtype: 'panel',
region: 'center',
width: 560,
layout: 'fit',
items:[GestorCuentas.movimientosGrid]
}
]
});
/*
* Mostramos ventana, la centramos y cargamos los datos iniciales en el grid
*/
GestorCuentas.contentWindow.show();
GestorCuentas.contentWindow.center();
GestorCuentas.movimientosDataStore.load({params: {start:0,limit:20}});
});
Fichero 2: PHP de carga de datos del grid
require_once 'Zend/Loader.php';
Zend_Loader::registerAutoload();
require_once 'Config/ConfigCuentas.php';
$db->setFetchMode(Zend_Db::FETCH_OBJ);
if (!empty($_POST['dateStart']) && !empty($_POST['dateEnd'])) {
$filterClause = "WHERE (mov_fecha >= '".$_POST['dateStart']."' AND mov_fecha <= '".$_POST['dateEnd']."')";
} else {
$filterClause = ' ';
}
if (!empty($_POST['limit']) && !empty($_POST['start'])) {
$limit = $_POST['limit'];
$start = $_POST['start'];
$limitClause = "LIMIT $start,$limit";
} else {
$limitClause = ' ';
}
$query = "SELECT * FROM mov_movimientos $filterClause ORDER BY mov_fecha ASC $limitClause";
$result = $db->fetchAssoc($query);
$resultados = array();
foreach ($result as $record) {
$resultados[] = $record;
}
$retValue = array (
'total' => count($result),
'data' => $resultados
);
echo json_encode($retValue);
Fichero 3: PHP de carga de datos del formulario
require_once 'Zend/Loader.php';
Zend_Loader::registerAutoload();
require_once 'Config/ConfigCuentas.php';
$query = "SELECT * FROM mov_movimientos WHERE mov_id=" . $_POST['mov_id'];
$db->setFetchMode(Zend_Db::FETCH_OBJ);
$result = $db->fetchAssoc($query);
$record = $result[$_POST['mov_id']];
$resultados = array();
$retValue = array (
'total' => count($result),
'success'=>true,
'data' => array (
array(
'mov_id' => $_POST['mov_id'],
'mov_concepto' => $record['mov_concepto'],
'mov_haber' => $record['mov_haber'],
'mov_deber' => $record['mov_deber'],
'mov_fecha' => $record['mov_fecha']
)
)
);
echo json_encode($retValue);
Fichero 4: PHP de guardado de datos desde el formulario
require_once 'Zend/Loader.php';
Zend_Loader::registerAutoload();
require_once 'Config/ConfigCuentas.php';
if (is_null($_POST['mov_id']) || empty($_POST['mov_id'])) {
$query = "INSERT INTO mov_movimientos(mov_fecha,mov_concepto,mov_haber,mov_deber) "
."VALUES(STR_TO_DATE('". $_POST['mov_fecha'] ."', '%d/%m/%Y'),'". $_POST['mov_concepto'] ."',".$_POST['mov_haber'].",".$_POST['mov_deber'].")";
} else {
$query = "UPDATE mov_movimientos SET "
."mov_fecha = STR_TO_DATE('". $_POST['mov_fecha'] ."', '%d/%m/%Y'),"
."mov_concepto = '" . $_POST['mov_concepto'] ."',"
."mov_haber = " . $_POST['mov_haber'] . ","
."mov_deber = " . $_POST['mov_deber'] . " "
."WHERE mov_id = " . $_POST['mov_id'];
}
$db->setFetchMode(Zend_Db::FETCH_OBJ);
$result = $db->query($query);
$lastInsert = $db->lastInsertId();
$resultados = array();
$retValue = array (
'total' => count($result),
'success'=>true,
'data' => array (
array(
'mov_id' => $mov_id
)
)
);
echo json_encode($retValue);
muy buen ejemplo, sobre todo la explicacion..es un poco dificil encontrar “codigo explicado” para Extjs generalmente solo se encuentran ejemplo, claro que sirven pero las librerias de Ext son tan amplias que empezando uno realmente se pierde dentro de tanto codigo…
Gracias Nando,
Perdona por responder tan tarde, pero es que el filtro anti-spam de mi blog te clasificó los comentarios como spam. Espero que ahora que te aprobé los comentarios no vuelva a pasar.
A ver si poco a poco voy ampliando los ejemplos, ahora que sé que alguien me lee y sigue me da más animo
Un saludo y gracias de nuevo
Javier muy buenos los articulos, segui que es una muy buena ayuda para los que comenzamos en esto.
Hola! hubiera estado perfecto un zip del proyecto para utilizarlo en casa, pero aun asi, gracias que soy nuevo en ext y no doy con bola…
hola javier, estoy comenzando a leer sobre ext y aun estoy super nuevo, quiera apreder algo mas de esto sobre todo diseñar pag web si tienes algunos ejemplos te agragderia la ayuda
un abrazo
Bueno, creo que una buena opcion, seria dejar el ZIP! de este ejemplo, por lo menos sin el LIB del ZEND…
No trae los CSS y es un cochinero
Buenas:
La explicación es perfectísima sobre todo para los que estamos empezando, pero el ejemplo para verlo funcionar no está disponible.
Un saludo.
Buenas a todos,
Antes de nada, pediros disculpas por no haber aparecido vuestros comentarios hasta hoy, pero en la última actualización del wordpress introduje mal la dirección de aviso de comentarios nuevos y no los moderé hasta hoy. De verdad lo siento mucho.
A partir de ahora haré una moderación a posteriori para que vuestros comentarios vayan entrando. Sobre lo del código disponible en .zip, es una buena idea así que tomo nota y en los próximos días lo haré.
Gracias por los mensajes de apoyo… me animan a que siga con esta serie de mini-tutoriales sobre ExtJS. He estado un tiempo bastante liado y sin poder atender el blog, pero prometo que a partir de hoy seguiré con esta serie.
Un saludo
Hola Javier, muchas gracias por este minitutorial, estoy comenzado con ExtJS y me hago bolas, al parecer los archivos no están disponibles… muchas gracias.
Lo primero es agradecerte ya que aprendiendo ExtJS uno se va primero al english y son casi nada los ejemplos como este en espanol, de verdad gracias y felicidades.
Comentando podrias poner un screen de el ejemplo (ayuda a interesarse y a ver el posible resultado) y poner los nombres de los archivos (ficheros dicen los espanoles), de zend ni te digo nada porque no lo tengo y pues creo que por cultura tengo que estudiar.
Saludos y no se me desanime nunca que la red esta llena de lo que ni te imaginas y en el horario que menos piensas.
Mira hay uno bueno en english que la verdad creo que puede ser optimizado y de alguna manera buscar la manera de hacerlo mas parametrizable para que pueda ser aplicado a cualquier tabla.
Me dices que te parece la idea e igual y colaboro en la tarea.
http://extjs.com/learn/Tutorial:Ext20_Grid_Editor_PHP_MySQL
Muy buen tutorial, pero el enlace a la miniaplicacion no funciona. Tengo una duda con respecto a las columnas de un grid. me explico : ¿Como hago para combinar 2 columnas en una sola, por ejm. si en mi tabla mysql, tengo nombres y apellidos, y quiero mostrar ambos campos concatenados en el grid en una sola columna “Nombre Completo”? me entienden, como se hace ?
necesito que por favor me envien a bladimirbalbin@walla.com la aplicacion porque en el link que estaba no aparece.
Muchas gracias
Por favor coloca el enlace bueno, para poder ejecutar todo el ejemplo.
Gracias y un abrazo.
hola, quisiera pedirte un favor no se como cargar una pagina php en un tabPanel de extjs, si me podrias ayudar seria de gran ayuda.
gracias.
Hola!
Me gustaría probar la aplicación pero el link me da error 404. Donde lo puedo conseguir?
Gracias
Pedro desde Uruguay