En este artículo vamos a realizar un ejercicio realmente muy interesante. Se trata de recopilar tablas que están relacionadas entre sí, incluyendo en los formularios de creación y edición campos de tipo select (los clásicos combos desplegables). Este ejercicio es especialmente laborioso y requiere mucha atención para sacarle todo el valor didáctico que posee. A cambio de este pequeño esfuerzo adicional, nos encontraremos con una forma de uso muy habitual en situaciones reales de desarrollo. Veremos que, a pesar de lo aparentemente extenso del código, siempre es más compacto y optimizado (es decir, más robusto, seguro y eficiente) que si programásemos a mano los formularios. Además, es más rápido y fácil de programar.
Al tratarse de un ejercicio basado en un desarrollo real, seguramente los códigos que te ofrezco en este artículo te resulten útiles en tu trabajo, con pocas adaptaciones. Venga, vamos al lío.
EL ESCENARIO
Para este desarrollo hemos empleado, una vez más, una tabla de personal de una compañía, con los datos extraidos de los ejemplos de la propia página de DataTables (https://datatables.net/). El modelado de datos responde a un esquema que ya hemos empleado anteriormente, en otros artículos. Tenemos tres tablas:
- La de personal.
- La de ciudades.
- La de cargos.
Las tablas de ciudades y cargos están relacionadas con la de personal, de modo que, cada elemento de la tabla personal
estará relacionado con un elemento de la tabla ciudades
y con uno de la tabla cargos
, a través del id de cada uno de estos elementos. El id de la ciudad, por tanto, es clave primaria en la tabla ciudades
y clave foránea en la tabla personal
. Otro tanto ocurre con el id del cargo. Una vez más, para facilitarte las cosas, te dejo la base de datos en este enlace, para que la descargues y te la importes con PHPMyAdmin (o tu herramienta favorita).
Lo que pretendemos en este ejercicio es que, cuando vamos a editar un registro, la ciudad y el cargo de la persona cuya ficha estamos editando aparezcan en sendos combos desplegables, mostrando todas las opciones de las respectivas tablas. Por defecto, en cada combo estará seleccionada la opción correspondiente al valor actual del registro. Cuando vayamos a crear un campo nuevo, podremos elegir cualquier opción. Por defecto, nos aparecerá seleccionada la primera de cada lista. De este modo, limitamos un poco la operativa al usuario, forzándole a que siempre haya un valor seleccionado en los combos.
Cabe añadir que, en este ejercicio, no empleamos combos de selección múltiple (eso lo dejaremos para un artículo posterior). La razón de ello es doble: por una parte, el escenario que hemos dibujado no lo requiere. Por otro lado, para este artículo, bastante tenemos con esto, creeme.
EL SCRIPT PRIMARIO
Vamos a empezar por ver el funcionamiento del script primario, ya que nos aportará interesantes conocimientos sobre el uso de combos en los formularios. Se llama articulo_editor_04.php
, y su listado es el siguiente:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 |
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Uso de DataTables</title> <!-- El CSS de jQuery UI --> <link rel="stylesheet" type="text/css" href="http://code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css"> <!-- El CSS de DataTables --> <link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/v/dt/jszip-2.5.0/pdfmake-0.1.18/dt-1.10.12/af-2.1.2/b-1.2.2/b-colvis-1.2.2/b-flash-1.2.2/b-html5-1.2.2/b-print-1.2.2/cr-1.3.2/fc-3.2.2/fh-3.1.2/kt-2.1.3/r-2.1.0/rr-1.1.2/sc-1.4.2/se-1.2.0/datatables.min.css"/> <!-- El CSS de Bootstrap --> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"> <!-- El CSS del editor --> <link rel="stylesheet" type="text/css" href="editor/css/editor.dataTables.css"> </head> <body> <br> <div class="container"> <div class="row"> <div class="col-sm-12"> <table id="tabla_de_personal" class="table display table-striped table-bordered"> <thead> <tr> <th>NOMBRE</th> <th>APELLIDO</th> <th>CARGO</th> <th>CIUDAD</th> <th>INGRESO</th> <th id="cabeceraDeSalario">SALARIO</th> </tr> </thead> <tbody> </tbody> </table> </div> </div> </div> <!-- jQuery --> <script language="javascript" src="https://code.jquery.com/jquery-3.1.1.min.js"></script> <!-- jQuery UI --> <script language="javascript" src="http://code.jquery.com/ui/1.12.1/jquery-ui.min.js"></script> <!-- El JavaScript de DataTables --> <script language="javascript" type="text/javascript" src="https://cdn.datatables.net/v/dt/jszip-2.5.0/pdfmake-0.1.18/dt-1.10.12/af-2.1.2/b-1.2.2/b-colvis-1.2.2/b-flash-1.2.2/b-html5-1.2.2/b-print-1.2.2/cr-1.3.2/fc-3.2.2/fh-3.1.2/kt-2.1.3/r-2.1.0/rr-1.1.2/sc-1.4.2/se-1.2.0/datatables.min.js"></script> <!-- El JavaScript de BootStrap --> <script language="javascript" src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script> <!-- El JavaScript del editor --> <script type="text/javascript" src="editor/js/dataTables.editor.js"></script> <!-- La librería moment.js para fechas --> <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.13.0/moment.min.js"></script> <!-- El código JavaScript --> <script language="javascript"> /* Definimos dos variables en las que se almacenarán las listas de ciudades y cargos, para construir los select del formulario de nuevo o edición. Estas variables se recrearán por Ajax cada vez que se invoque una operación de creación o edición de un registro. De este modo, siempre tendrán las listas actualizadas, si, por ejemplo, otro usuario modificase las tablas de cargos o ciudades. */ var listaDeCiudades = ''; var listaDeCargos = ''; /* CREAMOS EL OBJETO EDITOR, PARA LAS FUNCIONES DE NUEVO, EDICIÓN Y ELIMINACIÓN DE REGISTROS. */ var objetoEditor = new $.fn.dataTable.Editor({ ajax: 'crud_editor_04.php', // El script que crea, actualiza o borra registros. table: '#tabla_de_personal', // La tabla sobre la que se actúa. idSrc: 'id', // El nombre del campo clave primaria de la tabla sobre la que se actúa. /* Las traducciones para los botones, los valores múltiples, el campo de fecha y la confirmación de eliminación. */ i18n: { create: { button: "Nuevo", title: "Crear nuevo registro", submit: "Grabar" }, edit: { button: "Editar", title: "Editar registro", submit: "Actualizar" }, remove: { button: "Borrar", title: "Borrar registro", submit: "Borrar", confirm: { _: "¿Estás seguro de eliminar estos %d registros?", 1: "¿Estás seguro de eliminar este registro?" } }, multi: { title: "Múltiples valores", info: "Los registros seleccionados contienen diversos valores para este campo. Para editar este campo con el mismo valor en los registros seleccionados, pulsa aquí. En caso conterario, los registros mantendrán sus valores individuales en este campo.", restore: "Restaurar los valores múltiples", noMulti: "Esta entrada puede ser modificada individualmente, pero no como parte de un grupo." }, datetime: { previous: 'Anterior', next: 'Siguiente', months: ['Enero', 'Febrero', 'Marzo', 'Abril', 'Mayo', 'Junio', 'Julio', 'Agosto', 'Septiembre', 'Octubre', 'Noviembre', 'Diciembre'], weekdays: ['Dom', 'Lun', 'Mar', 'Mié', 'Jue', 'Vie', 'Sáb'] } }, /* La difinición de los campos del formulario. */ fields: [ /* ATENCIÓN. Observa que en los campos se incluye, en el atributo name, el nombre de la tabla, un punto y el nombre del campo. El motivo de esto es porque trabajamos con tres tablas y los atributos name deben coincidir con los nombres que tienen los campos en las column del datatables. A su vez, estos, vienen determinados por la respuesta JSON del script que lee los datos secundarios para el datatables. Si no coinciden los nombres, los formularios de edición y creación no funcionarán. */ /* Los campos de tipo text se pueden definir sin especificar el tipo. El valor text es el tipo de campo por defecto. */ {label: 'Nombre:', name: 'personal.nombre', attr: {class:'form-control'}}, {label: 'Apellido:', name: 'personal.apellido', attr: {class:'form-control'}}, /* Para los campos de tipo select (los combos), es necesario establecer el atributo type, con el valor 'select'. Además, en general, se debería definir aquí el atributo options, con las opciones. Sin embargo, en este ejemplo, no se definen estas, porque se definirán de forma dinámica, ya que otro usuario podría modificar las listas de ciudades o cargos. */ { label: 'Cargo:', name: 'personal.cargo', attr: {class:'form-control'}, type: 'select', fieldInfo: 'Selecciona el cargo' }, { label: 'Ciudad:', name: 'personal.ciudad', attr: {class:'form-control'}, type: 'select', fieldInfo: 'Selecciona la ciudad' }, /* El campo de fecha con el datepicker jQueryUI, por funcionalidad. */ { label: 'F. Ingreso:', name: 'personal.fecha_de_ingreso', type: 'date', def: function(){return new Date();}, dateFormat: 'dd-mm-yy', attr: {readonly:true, class:'form-control', style:'display:inline'}, opts:{ buttonImage:'editor/images/calendar.png', buttonImageOnly: true, buttonText: 'Elegir fecha', dayNames: ['Domingo', 'Lunes', 'Martes', 'Miércoles', 'Jueves', 'Viernes', 'Sábado'], dayNamesMin: ['Do', 'Lu', 'Ma', 'Mi', 'Ju', 'Vi', 'Sá'], dayNamesShort: ['Dom', 'Lun', 'Mar', 'Mié', 'Jue', 'Vie', 'Sáb'], firstDay: 1, monthNames: ['Enero', 'Febrero', 'Marzo', 'Abril', 'Mayo', 'Junio', 'Julio', 'Agosto', 'Septiembre', 'Octubre', 'Noviembre', 'Diciembre'], monthNamesShort: ['Ene', 'Feb', 'Mar', 'Abr', 'May', 'Jun', 'Jul', 'Ago', 'Sep', 'Oct', 'Nov', 'Dic'], changeMonth: true, changeYear: true, prevText: "Anterior", nextText: "Siguiente" } }, { label: 'Salario anual:', name: 'personal.salario_bruto_anual', attr: {class:'form-control'}, fieldInfo: 'El salario debe ir como valor numérico, con dos decimales (incluso si son 00), separados por un punto.' }, ] }); /* FINAL DE LA DEFINICIÓN DE UN OBJETO EDITOR. */ /* Cuando se pulsa un botón de creación, edición o eliminación, y antes de que el editor abra el correspondiente formulario, se dispara el evento preOpen. Aquí lo usamos para recuperar, mediante un ajax síncrono, las listas de ciudades y cargos actualizadas, incorporándolas a los correspondientes campos de tipo select. Observa que esto sólo se hace si la operación es una edición, o una creación. Si se trata de una eliminación, no es necesario, ya que no se muestran los campos en el formulario. */ objetoEditor.on('preOpen', function(e, mode, action){ if (action == "remove") return; $.ajax({ url:"leer_ciudades_editor_04.php", async:false, dataType: "JSON", complete:function(datosRecibidos) { listaDeCiudades = datosRecibidos.responseText; } }); objetoEditor.field('personal.ciudad').update(JSON.parse(listaDeCiudades)); $.ajax({ url:"leer_cargos_editor_04.php", async:false, dataType: "JSON", complete:function(datosRecibidos) { listaDeCargos = datosRecibidos.responseText; } }); objetoEditor.field('personal.cargo').update(JSON.parse(listaDeCargos)); }); /* Cuando se pulsa el botón de enviar un formulario, y antes de que este sea, efectivamente, enviado (al script definido en el atributo ajax del objeto editor), se dispara el evento preSubmit. Nosotros lo utilizamos para comprobar los campos de texto del formulario, donde, a priori, el usuario puede teclear cualquier cosa, o ninguna. Si algún campo de texto está vacío, le mostramos un mensaje en rojo debajo de dicho campo, mediante el uso de la propiedad error (nativa de los campos de formulario) y retornamos un valor false, lo que aborta el envío. Como ves, si la acción pedida es la eliminación de un registro, se abandona la prevalizadción, ya que no es necesaria. */ objetoEditor.on('preSubmit', function(e, data, action){ if (action == 'remove') return true; // Se comprueba que el nombre tenga contenido, si no es multiedición. var dato_nombre = objetoEditor.field('personal.nombre').val(); if (!objetoEditor.field('personal.nombre').isMultiValue()) dato_nombre = dato_nombre.trim(); if (dato_nombre == "" && !objetoEditor.field('personal.nombre').isMultiValue()){ objetoEditor.field('personal.nombre').error('Debes teclear el nombre.'); objetoEditor.field('personal.nombre').focus(); return false; } else { objetoEditor.field('personal.nombre').error(''); } // Se comprueba que el apellido tenga contenido, si no es multiedición. var dato_apellido = objetoEditor.field('personal.apellido').val(); if (!objetoEditor.field('personal.apellido').isMultiValue()) dato_apellido = dato_apellido.trim(); if (dato_apellido == "" && !objetoEditor.field('personal.apellido').isMultiValue()){ objetoEditor.field('personal.apellido').error('Debes teclear el apellido.'); objetoEditor.field('personal.apellido').focus(); return false; } else { objetoEditor.field('personal.apellido').error(''); } // Se comprueba que el nombre tenga contenido, si no es multiedición. Además, se comprueba que se ajuste a formato. var dato_salario_bruto_anual = objetoEditor.field('personal.salario_bruto_anual').val(); if (!objetoEditor.field('personal.salario_bruto_anual').isMultiValue()) dato_salario_bruto_anual = dato_salario_bruto_anual.trim(); if (dato_salario_bruto_anual == "" && !objetoEditor.field('personal.salario_bruto_anual').isMultiValue()){ objetoEditor.field('personal.salario_bruto_anual').error('Debes teclear el salario bruto anual.'); objetoEditor.field('personal.salario_bruto_anual').focus(); return false; } else { objetoEditor.field('personal.salario_bruto_anual').error(''); } if (dato_salario_bruto_anual != parseFloat(dato_salario_bruto_anual).toFixed(2) && !objetoEditor.field('personal.salario_bruto_anual').isMultiValue()){ objetoEditor.field('personal.salario_bruto_anual').error('El salario bruto anual no tiene el formato correcto.'); objetoEditor.field('personal.salario_bruto_anual').focus(); return false; } else { objetoEditor.field('personal.salario_bruto_anual').error(''); } return true; // Si todo ha ido bien, se retorna true para que se complete el envío. }); /* SE DEFINE EL OBJETO DATATABLES. SU USO ES, PRÁCTICAMENTE, EL MISMO QUE SIEMPRE, PARA RECUPERAR UN DATASET Y RENDERIZARLO EN LA PÁGINA. */ var objetoDataTables_personal = $('#tabla_de_personal').DataTable({ "language": { select: { rows: { _: "%d registros seleccionados", 0: "No se han seleccionado registros", 1: "1 registro seleccionado" } }, "emptyTable": "No hay datos disponibles en la tabla.", "info": "Del _START_ al _END_ de _TOTAL_ ", "infoEmpty": "Mostrando 0 registros de un total de 0.", "infoFiltered": "(filtrados de un total de _MAX_ registros)", "infoPostFix": "(actualizados)", "lengthMenu": "Mostrar _MENU_ registros", "loadingRecords": "Cargando...", "processing": "Procesando...", "search": "Buscar:", "searchPlaceholder": "Dato para buscar", "zeroRecords": "No se han encontrado coincidencias.", "paginate": { "first": "Primera", "last": "Última", "next": "Siguiente", "previous": "Anterior" }, "aria": { "sortAscending": "Ordenación ascendente", "sortDescending": "Ordenación descendente" } }, "lengthMenu": [[5, 10, 20, 25, 50, -1], [5, 10, 20, 25, 50, "Todos"]], "iDisplayLength": 10, "bServerSide": true, "sAjaxSource": "datos_externos_editor_04.php", // OJO. Ver el código para comprender. /* Observa que los datos para las columnas se definen precediéndolos del nombre de la tabla de la que proceden, ya que estamos trabajando con tres tablas, y el JSON retornado tiene datos de las tres. Observa que el campo ciudad se toma de la tabla de ciudades, ya que, en la tabla de personal, lo que hay es el campo id_ciudad, que contiene una clave foránea. Sin embargo, en el objeto editor se referencia el campo como personal.ciudad, porque se toma el literal de ciudad, pero se está trabajando con la tabla de personal. Con los cargos se aplica la misma operativa. */ "columns" : [ {"data": 'personal.nombre'}, {"data": 'personal.apellido'}, {"data": 'cargos.cargo'}, {"data": 'ciudades.ciudad'}, {"data": 'personal.fecha_de_ingreso'}, {"data": 'personal.salario_bruto_anual', className: "text-right"} ], "columnDefs": [ { "targets": 5, "render": function (data) { return salaryFormat(data); } } ], select: true, dom: 'Bfrtipl', buttons: [ {extend: 'create', editor: objetoEditor}, {extend: 'edit', editor: objetoEditor}, {extend: 'remove', editor: objetoEditor} ] }); $('label').addClass('form-inline'); $('select, input[type="search"]').addClass('form-control input-sm'); $('#cabeceraDeSalario').removeClass("text-right"); function salaryFormat(valorRecibido) { var separado = valorRecibido.split("."); var parteEntera = separado[0]; var unidades = new Array(); var fragmento; while (parteEntera > ""){ fragmento = parteEntera.substr(-3, 3); parteEntera = parteEntera.substr(0, parteEntera.length - 3); unidades.unshift(fragmento); } var valorFinal = unidades.join("."); if (separado.length > 1) valorFinal += ("," + separado[1]); valorFinal += " €"; return valorFinal; } </script> </body> </html> |
El código nos es, en su mayoría, familiar. Las partes más interesantes están profusamente comentadas, para que no te cueste esfuerzo interpretarlo. A pesar de ello, lo verás más claro globalmente cuando hablemos de los scripts secundarios, que también presentan novedades que debemos conocer.
LOS CAMPOS DE TIPO select
Antes de entrar en harina con los scripts secundarios y sus novedades, vamos a conocer algunos detalles importantes sobre la forma en que se crean y emplean los campos de tipo select
en un objeto editor.
A la hora de definir un campo de tipo select
, debemos, cómo se menciona en el código, establecer las opciones (options
). Nosotros no lo hemos hecho en este código (las cargamos posteriormente) debido a que estas pueden variar, y queremos tener la lista actualizada. Si fueran fijas, las definiríamos en el propio campo, como vemos a continuación:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
{ label: 'Ciudad:', name: 'personal.ciudad', attr: {class:'form-control'}, type: 'select', fieldInfo: 'Selecciona la ciudad', options: [ {label: 'Edinburgh', value: '1'}, {label: 'London', value: '2'}, {label: 'New York', value: '3'}, {label: 'San Francisco', value: '4'}, {label: 'Sidney', value: '5'}, {label: 'Singapore', value: '6'}, {label: 'Tokyo', value: '7'} ], def: '4' }, |
Cómo ves, las opciones se definen como objetos formados por un label
(lo que el usuario ve escrito en la lista) y un value
(el valor que tiene cada opción para pasarlo con el campo, cuando el formulario sea enviado).
La propiedad def
establece cual será el valor seleccionado por defecto, cuando se quiere crear un registro nuevo.
Si la lista de opciones no viene con las claves label
y value
, sino con otras distintas (por ejemplo, por proceder de un JSON que emplea otras claves), contamos con la propiedad optionsPair
, que nos permite indicar cual es el label
y cual el value
. Observa el siguiente ejemplo.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
{ label: 'Ciudad:', name: 'personal.ciudad', attr: {class:'form-control'}, type: 'select', fieldInfo: 'Selecciona la ciudad', options: [ {ciudad: 'Edinburgh', id_ciudad: '1'}, {ciudad: 'London', id_ciudad: '2'}, {ciudad: 'New York', id_ciudad: '3'}, {ciudad: 'San Francisco', id_ciudad: '4'}, {ciudad: 'Sidney', id_ciudad: '5'}, {ciudad: 'Singapore', id_ciudad: '6'}, {ciudad: 'Tokyo', id_ciudad: '7'} ], optionsPair: { label: 'ciudad', value: 'id_ciudad' }, def: '4' }, |
Aunque menos relevante, también está bien saber que hay otros modos de definir las options
de un select
. Así mismo, tenemos algunas posibilidades secundarias, como establecer un placeholder. Si no te asusta un poco de inglés, echa un vistazo a https://editor.datatables.net/reference/field/select. No obstante, las opciones más necesarias en el día a día las iremos desvelando en estos artículos.
Hay una opción que, en muchos casos, es imprescindible: se trata de multiple
, para crear combos múltiples. Como ya hemos dicho, de esta hablaremos en un artículo posterior, para no meter un exceso de materia en este.
OBTENER EL DATASET
Bien. Aquí vamos a ver uno de los scripts secundarios. Se trata del que recupera el dataset para el objeto DataTables. Esta es una tecnología con la que ya estamos familiarizados de los artículos de DataTables pero, en este caso, hay algunos detalles un poco diferentes, que me gustaría comentar contigo. El script se llama datos_externos_editor_04.php
y responde al siguiente listado:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 |
<?php // Establecemos la codificacion para las llamadas y respuestas HTTP mb_internal_encoding ('UTF-8'); /* CREAMOS LA CONEXION A LA BASE DE DATOS, O BIEN LA IMPORTAMOS DESDE UN ARCHIVO EXTERNO DE CONFIGURACION. */ include ('conexion_bd_editor.php'); /* RECUPERAMOS TODOS LOS PARAMETROS DE $_GET. LOS QUE NO APAREZCAN EN LA CONSULTA RECIBIRAN UN VALOR PREDETERMINADO. ESTOS DATOS SON LOS QUE SE RECIBEN CADA VEZ QUE EL PLUGIN DATATABLES LLAMA A ESTE SCRIPT. */ $datosDeLlamada = $_GET; /* SE INDICA(N) LA(S) TABLA(S) QUE SE VA(N) A USAR EN LA CONSULTA. */ $tablasDeBBDD = array( 'personal', 'ciudades', 'cargos' ); /* SE DEFINE LA LISTA DE COLUMNAS QUE SE DEVOLVERON PARA SER MOSTRADAS EN LA TABLA HTML. LOS NOMBRES DE ESTAS COLUMNAS DEBEN COINCIDIR CON LOS DE LAS COLUMNAS DE LA(S) TABLA(S) AFECTADA(S) POR LA CONSULTA. */ /* ATENCIÓN. SE DEFINEN LAS COLUMNAS EN EL MISMO ORDEN EN QUE APARECEN EN LA TABLA HTML, AÑADIENDO, AL FINAL, AQUELLAS QUE PODRÍAMOS LLAMAR "AUXILIARES" (A FALTA DE UN NOMBRE MEJOR), QUE SON LAS QUE NO APARECEN EN LA TABLA HTML. */ $columnasParaRetorno = array( $tablasDeBBDD[0].'.nombre', $tablasDeBBDD[0].'.apellido', $tablasDeBBDD[2].'.cargo', $tablasDeBBDD[1].'.ciudad', $tablasDeBBDD[0].'.fecha_de_ingreso', $tablasDeBBDD[0].'.salario_bruto_anual', $tablasDeBBDD[0].'.id_cargo', $tablasDeBBDD[0].'.id_ciudad', $tablasDeBBDD[0].'.id' ); $numeroDeColumnas = count($columnasParaRetorno); //////////////////////////////////////////////// REGLAS DE FILTRADO //////////////////////////// /* PREPARAMOS EL FILTRADO POR COLUMNAS PARA LA CAJA DE BUSQUEDA */ $reglasDeFiltradoDeUsuario = array (); if (isset($datosDeLlamada['sSearch']) && $datosDeLlamada['sSearch'] != "") { for($i = 0; $i < $numeroDeColumnas; $i++) { if (isset ($datosDeLlamada['bSearchable_'.$i]) && $datosDeLlamada['bSearchable_'.$i] == 'true') { $reglasDeFiltradoDeUsuario[] = $columnasParaRetorno[$i]." LIKE '%".addslashes($datosDeLlamada['sSearch'])."%'"; } } } if (!empty($reglasDeFiltradoDeUsuario)){ $reglasDeFiltradoDeUsuario = ' ('.implode(" OR ", $reglasDeFiltradoDeUsuario).') '; } else { $reglasDeFiltradoDeUsuario = ''; } /* PREPARAMOS LAS REGLAS DE FILTRADO PARA LAS BUSQUEDAS PROGRAMADAS POR METODO .search() (BÚSQUEDAS PERSONALIDAS DEL USUARIO). */ $reglasDeBusquedaPorProgramacion = array(); for($i = 0; $i < $numeroDeColumnas; $i++) { if (!isset($datosDeLlamada['sSearch_'.$i])) continue; if ($datosDeLlamada['sSearch_'.$i] !== ""){ $reglasDeBusquedaPorProgramacion[] = $columnasParaRetorno[$i].$datosDeLlamada['sSearch_'.$i]; } } if (!empty($reglasDeBusquedaPorProgramacion)){ $reglasDeBusquedaPorProgramacion = ' ('.implode(" AND ", $reglasDeBusquedaPorProgramacion).') '; } else { $reglasDeBusquedaPorProgramacion = ''; } /* PREPARAMOS LAS REGLAS DE FILTRADO DE RELACIONES ENTRE TABLAS. ESTAS SE PROGRAMAN AQUI A MANO, PORQUE PUEDEN EXISTIR O NO, DEPENDIENDO DE QUE SE USE UNA TABLA O MAS DE UNA. */ $reglasDeFiltradoDeRelaciones = ''; $reglasDeFiltradoDeRelaciones .= " (".$tablasDeBBDD[1].".id = ".$tablasDeBBDD[0].".id_ciudad "; $reglasDeFiltradoDeRelaciones .= "AND ".$tablasDeBBDD[2].".id = ".$tablasDeBBDD[0].".id_cargo) "; /* SE COMPONE TODA LA REGLA DE FILTRADO. EN ESTE CASO INCLUYE LAS CLAÚSULAS DE BÚSQUEDA, Y LAS RELACIONES ENTRE TABLAS. SIGUE SIENDO UN EJEMPLO SIMPLE, PERO MÁS ELABORADO QUE EL ANTERIOR. MÁS ADELANTE VEREMOS OTROS USOS. LO IMPORTANTE AQUI ES QUE, ADEMÁS DE LAS CLAUSULAS DE BÚSQUEDA (VARIABLE $reglasDeFiltradoDeUsuario, QUE PUEDEN EXISTIR O NO) TAMBIÉN HAY UNA CLAÚSULA DE RELACIONES ENTRE LAS TABLAS. SI HAY MÁS DE UNA TABLA SIEMPRE HABRÁ UNA CLAÚSULA DE RELACIONES ($reglasDeFiltradoDeRelaciones). LAS IMPLEMENTAMOS COMO UNA MATRIZ PARA PODER COMPROBAR LAS QUE EXISTEN Y LAS QUE NO, Y LUEGO LAS UNIMOS CON EL OPERADOR AND, SI HAY MÁS DE UNA CLAÚSULA DE FILTRADO. */ $reglasDeFiltrado = array(); if ($reglasDeFiltradoDeUsuario > '') $reglasDeFiltrado[] = $reglasDeFiltradoDeUsuario; if ($reglasDeFiltradoDeRelaciones > '') $reglasDeFiltrado[] = $reglasDeFiltradoDeRelaciones; if ($reglasDeBusquedaPorProgramacion > '') $reglasDeFiltrado[] = $reglasDeBusquedaPorProgramacion; $reglasDeFiltrado = implode(" AND ", $reglasDeFiltrado); //////////////////////////////////////////// FIN DE REGLAS DE FILTRADO /////////////////////////// /////////////////////////// REGLAS DE ORDENACION //////////////////////// /* SE COMPONE LA REGLA DE ORDENACION. */ $reglasDeOrdenacion = array (); if (isset($datosDeLlamada['iSortCol_0'] )) { $columnasDeOrdenacion = intval($datosDeLlamada['iSortingCols']); for($i = 0; $i < $columnasDeOrdenacion; $i ++) { if ($datosDeLlamada['bSortable_'.intval($datosDeLlamada['iSortCol_'.$i])] == 'true') { $reglasDeOrdenacion[] = $columnasParaRetorno[intval($datosDeLlamada['iSortCol_'.$i])].($datosDeLlamada['sSortDir_'.$i] === 'asc'?' asc':' desc'); } } } if (!empty($reglasDeOrdenacion)) { $reglasDeOrdenacion = " ORDER BY ".implode(", ", $reglasDeOrdenacion); } else { $reglasDeOrdenacion = ""; } /* SE COMPONE LA REGLA DE LIMITACION DE REGISTROS. */ $reglaDeLimitacion = ($datosDeLlamada['iDisplayLength'] == '-1')?';':' LIMIT '.$datosDeLlamada['iDisplayStart'].', '.$datosDeLlamada['iDisplayLength'].';'; /////////////////////////////////////// FIN DE REGLAS DE ORDENACION //////////////////// /* SE PREPARA LA CONSULTA DE RECUPERACION DEL DATASET SOLICITADO. */ $consulta = "SELECT ".implode(', ', $columnasParaRetorno)." "; $consulta .= "FROM ".implode(', ', $tablasDeBBDD)." "; $consulta .= "WHERE 1 "; if ($reglasDeFiltrado > "") $consulta .= "AND (".$reglasDeFiltrado.") "; $consulta .= $reglasDeOrdenacion; $consulta .= $reglaDeLimitacion; $hacerConsulta = $conexion->prepare($consulta); $hacerConsulta->execute(); $DataSetPrevio = $hacerConsulta->fetchAll(PDO::FETCH_ASSOC); $hacerConsulta->closeCursor(); /* Se procesa el dataset previo, obtenido de la consulta, para obtener el dataset definitivo, con los datos separados en submatrices, dependiendo de la tabla de la que proceden. */ $DataSet = array(); foreach ($DataSetPrevio as $item){ $empleado = array( 'id'=>$item['id'], 'personal'=>array( 'nombre'=>$item['nombre'], 'apellido'=>$item['apellido'], 'cargo'=>$item['id_cargo'], 'ciudad'=>$item['id_ciudad'], 'fecha_de_ingreso'=>date("d-m-Y", strtotime($item['fecha_de_ingreso'])), 'salario_bruto_anual'=>$item['salario_bruto_anual'] ), 'ciudades'=>array( 'ciudad'=>$item["ciudad"] ), 'cargos'=>array( 'cargo'=>$item['cargo'] ) ); $DataSet[] = $empleado; unset($empleado); } unset($DataSetPrevio); /* SI ES NECESARIO ADAPTAR ALGUN DATO PARA PRESENTACION, SE ADAPTA AQUI. AQUÍ NO NECESITAMOS ESTA FUNCIONALIDAD, PORQUE EL ÚNICO DATO A ADAPTAR, QUE ES EL FORMATO DE LA FECHA DE INGRESO, YA LO HEMOS ADAPTADO AL CREAR EL DATASET DEFINITIVO. POR ESTA RAZÓN DEJAMOS EL BUCLE VACÍO Y ANULADO, SOLO A EFECTOS DOCUMENTALES.*/ // foreach ($DataSet as $keyDL=>$DL){ // } /* CALCULO DE DATOS INFORMATIVOS DE REGISTROS. */ $numeroDeRegistrosDelDataSet = count($DataSet); /* CALCULO DEL TOTAL DE REGISTROS QUE CUMPLEN LAS REGLAS DE FILTRADO SIN ORDENACION NI LIMITACION. */ $consulta = "SELECT COUNT(".$columnasParaRetorno[0].") "; $consulta .= "FROM ".implode(', ', $tablasDeBBDD)." "; $consulta .= "WHERE 1 "; if ($reglasDeFiltrado > "") $consulta .= "AND (".$reglasDeFiltrado.") "; $hacerConsulta = $conexion->prepare($consulta); $hacerConsulta->execute(); $totalDeRegistrosConFiltrado = $hacerConsulta->fetch(PDO::FETCH_NUM)[0]; $hacerConsulta->closeCursor(); /* TOTAL DE REGISTROS DE LA TABLA PRIMARIA (O UNICA, SI SOLO HAY UNA). */ $consulta = "SELECT COUNT(".$columnasParaRetorno[0].") "; $consulta .= "FROM ".$tablasDeBBDD[0].";"; $hacerConsulta = $conexion->prepare($consulta); $hacerConsulta->execute(); $totalDeRegistrosEnBruto = $hacerConsulta->fetch(PDO::FETCH_NUM)[0]; $hacerConsulta->closeCursor(); // SE PREPARA UNA MATRIZ CON TODOS LOS DATOS NECESARIOS PARA LA SALIDA. $matrizDeSalida = array( "sEcho" => intval($datosDeLlamada['sEcho']), "iTotalRecords" => strval($totalDeRegistrosEnBruto), "iTotalDisplayRecords" => strval($totalDeRegistrosConFiltrado), "data" => array() ); foreach ($DataSet as $keyItem=>$item) $matrizDeSalida['data'][] = $item; $salidaDeDataSet = json_encode ($matrizDeSalida, JSON_HEX_QUOT); /* SE DEVUELVE LA SALIDA */ echo $salidaDeDataSet; ?> |
Veamos los puntos de interés. Empieza por fijarte en el primer bloque resaltado, entre las líneas 28
y 38
. Lo que hacemos es definir las columnas que leeremos de las tablas, respetando el orden en que se definen en la tabla HTML en el script principal. Colocamos al final aquellas columnas que vamos a necesitar leer pero que no aparecen en la tabla HTML. Esto es importante porque, si no lo hacemos así, al hacer ordenaciones no se harán de la forma correcta.
Fíjate, también, en la línea 128
. Cuando leemos el dataset, no vamos a enviarlo tal cual. Esto se debe a que debemos preparar el JSON con los datos colocados según la tabla a la que pertenecen (en seguida veremos el JSON de respuesta, para que entiendas esto). Por esta razón, el dataset lo leemos en una matriz provisional.
En las líneas de la 135
a la 157
preprocesamos el dataset previo que hemos leido, para obtener el dataset definitivo que convertiremos en el JSON final.
El JSON que se devuelve tiene, cómo es lógico, una estructura algo más compleja que cuando leíamos una sola tabla, así:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 |
{ "sEcho":1, "iTotalRecords":"57", "iTotalDisplayRecords":"57", "data":[{ "id":"5", "personal":{ "nombre":"Airi", "apellido":"Satou", "cargo":"1", "ciudad":"7", "fecha_de_ingreso":"28-11-2008", "salario_bruto_anual":"162700.00" }, "ciudades":{"ciudad":"Tokyo"}, "cargos":{"cargo":"Accountant"} },{ "id":"25", "personal":{ "nombre":"Angelica", "apellido":"York", "cargo":"2", "ciudad":"2", "fecha_de_ingreso":"09-10-2009", "salario_bruto_anual":"1200000.00" }, "ciudades":{"ciudad":"London"}, "cargos":{"cargo":"Chief Executive Officer (CEO)"} },{ "id":"3", "personal":{ "nombre":"Ashton", "apellido":"Cox", "cargo":"15", "ciudad":"4", "fecha_de_ingreso":"12-01-2009", "salario_bruto_anual":"86000.00" }, "ciudades":{"ciudad":"San Francisco"}, "cargos":{"cargo":"Junior Technical Author"} },{ "id":"19", "personal":{ "nombre":"Bradley", "apellido":"Greer", "cargo":"27", "ciudad":"2", "fecha_de_ingreso":"13-10-2012", "salario_bruto_anual":"132000.00" }, "ciudades":{"ciudad":"London"}, "cargos":{"cargo":"Software Engineer"} },{ "id":"28", "personal":{ "nombre":"Brenden", "apellido":"Wagner", "cargo":"27", "ciudad":"4", "fecha_de_ingreso":"07-06-2011", "salario_bruto_anual":"206850.00" }, "ciudades":{"ciudad":"San Francisco"}, "cargos":{"cargo":"Software Engineer"} },{ "id":"6", "personal":{ "nombre":"Brielle", "apellido":"Williamson", "cargo":"12", "ciudad":"3", "fecha_de_ingreso":"02-12-2012", "salario_bruto_anual":"372000.00" }, "ciudades":{"ciudad":"New York"}, "cargos":{"cargo":"Integration Specialist"} },{ "id":"43", "personal":{ "nombre":"Bruno", "apellido":"Nash", "cargo":"27", "ciudad":"2", "fecha_de_ingreso":"03-05-2011", "salario_bruto_anual":"163500.00" }, "ciudades":{"ciudad":"London"}, "cargos":{"cargo":"Software Engineer"} },{ "id":"23", "personal":{ "nombre":"Caesar", "apellido":"Vance", "cargo":"20", "ciudad":"3", "fecha_de_ingreso":"12-12-2011", "salario_bruto_anual":"106450.00" }, "ciudades":{"ciudad":"New York"}, "cargos":{"cargo":"Pre-Sales Support"} },{ "id":"51", "personal":{ "nombre":"Cara", "apellido":"Stevens", "cargo":"23", "ciudad":"3", "fecha_de_ingreso":"06-12-2011", "salario_bruto_anual":"145600.00" }, "ciudades":{"ciudad":"New York"}, "cargos":{"cargo":"Sales Assistant"} },{ "id":"4", "personal":{ "nombre":"Cedric", "apellido":"Kelly", "cargo":"25", "ciudad":"1", "fecha_de_ingreso":"29-03-2012", "salario_bruto_anual":"433000.00" }, "ciudades":{"ciudad":"Edinburgh"}, "cargos":{"cargo":"Senior Javascript Developer"} } ] } |
Cómo ves, cada elemento de data
tiene los datos de las tres tablas. Y observa, y esto es MUY importante, que en la parte que corresponde a los datos propios de la tabla personal
lo que se ha almacenado bajo la clave ciudad
es el id de la ciudad, que es lo que ha salido de la tabla personal
. Lo que se ha almacenado bajo la clave cargo
es el id del cargo, que es lo que hemos obtenido de la tabla personal
. Y estos nombres (personal.ciudad
y personal.cargo
) coinciden con los correspondientes atributos name
de los campos select
en la definición del objeto editor del script principal. Si no hubiera esa coincidencia de nombres, los campos select
no podrían identificar el valor de la ciudad o el cargo leidos, y no funcionarían.
Fijate que, en cada elemento de data
tenemos dos campos con la clave ciudad
: uno englobado en personal
y otro en ciudades
. Con los cargos ocurre otro tanto. En este sentido, los nombres de las tablas están actuando como un espacio de nombres para poder distinguir el campo que contiene el id de la ciudad o el cargo del que contiene su nombre real.
Sé que todo esto suena un poco rebuscado al principio. Sin embargo, si lo piensas, tiene toda la lógica del mundo. Observa la matriz del dataset que ha dado lugar al elemento data
del JSON:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 |
array(10) { [0]=>array(4) { ["id"]=>string(1) "5" ["personal"]=>array(6) { ["nombre"]=>string(4) "Airi" ["apellido"]=>string(5) "Satou" ["cargo"]=>string(1) "1" ["ciudad"]=>string(1) "7" ["fecha_de_ingreso"]=>string(10) "28-11-2008" ["salario_bruto_anual"]=>string(9) "162700.00" } ["ciudades"]=>array(1) { ["ciudad"]=>string(5) "Tokyo" } ["cargos"]=>array(1) { ["cargo"]=>string(10) "Accountant" } } [1]=>array(4) { ["id"]=>string(2) "25" ["personal"]=>array(6) { ["nombre"]=>string(8) "Angelica" ["apellido"]=>string(4) "York" ["cargo"]=>string(1) "2" ["ciudad"]=>string(1) "2" ["fecha_de_ingreso"]=>string(10) "09-10-2009" ["salario_bruto_anual"]=>string(10) "1200000.00" } ["ciudades"]=>array(1) { ["ciudad"]=>string(6) "London" } ["cargos"]=>array(1) { ["cargo"]=>string(29) "Chief Executive Officer (CEO)" } } [2]=>array(4) { ["id"]=>string(1) "3" ["personal"]=>array(6) { ["nombre"]=>string(6) "Ashton" ["apellido"]=>string(3) "Cox" ["cargo"]=>string(2) "15" ["ciudad"]=>string(1) "4" ["fecha_de_ingreso"]=>string(10) "12-01-2009" ["salario_bruto_anual"]=>string(8) "86000.00" } ["ciudades"]=>array(1) { ["ciudad"]=>string(13) "San Francisco" } ["cargos"]=>array(1) { ["cargo"]=>string(23) "Junior Technical Author" } } [3]=>array(4) { ["id"]=>string(2) "19" ["personal"]=>array(6) { ["nombre"]=>string(7) "Bradley" ["apellido"]=>string(5) "Greer" ["cargo"]=>string(2) "27" ["ciudad"]=>string(1) "2" ["fecha_de_ingreso"]=>string(10) "13-10-2012" ["salario_bruto_anual"]=>string(9) "132000.00" } ["ciudades"]=>array(1) { ["ciudad"]=>string(6) "London" } ["cargos"]=> array(1) { ["cargo"]=>string(17) "Software Engineer" } } [4]=>array(4) { ["id"]=>string(2) "28" ["personal"]=>array(6) { ["nombre"]=>string(7) "Brenden" ["apellido"]=>string(6) "Wagner" ["cargo"]=>string(2) "27" ["ciudad"]=>string(1) "4" ["fecha_de_ingreso"]=>string(10) "07-06-2011" ["salario_bruto_anual"]=>string(9) "206850.00" } ["ciudades"]=>array(1) { ["ciudad"]=>string(13) "San Francisco" } ["cargos"]=>array(1) { ["cargo"]=>string(17) "Software Engineer" } } [5]=>array(4) { ["id"]=>string(1) "6" ["personal"]=>array(6) { ["nombre"]=>string(7) "Brielle" ["apellido"]=>string(10) "Williamson" ["cargo"]=>string(2) "12" ["ciudad"]=>string(1) "3" ["fecha_de_ingreso"]=>string(10) "02-12-2012" ["salario_bruto_anual"]=>string(9) "372000.00" } ["ciudades"]=>array(1) { ["ciudad"]=>string(8) "New York" } ["cargos"]=>array(1) { ["cargo"]=>string(22) "Integration Specialist" } } [6]=>array(4) { ["id"]=>string(2) "43" ["personal"]=>array(6) { ["nombre"]=>string(5) "Bruno" ["apellido"]=>string(4) "Nash" ["cargo"]=>string(2) "27" ["ciudad"]=>string(1) "2" ["fecha_de_ingreso"]=>string(10) "03-05-2011" ["salario_bruto_anual"]=>string(9) "163500.00" } ["ciudades"]=>array(1) { ["ciudad"]=>string(6) "London" } ["cargos"]=>array(1) { ["cargo"]=>string(17) "Software Engineer" } } [7]=>array(4) { ["id"]=>string(2) "23" ["personal"]=>array(6) { ["nombre"]=>string(6) "Caesar" ["apellido"]=>string(5) "Vance" ["cargo"]=>string(2) "20" ["ciudad"]=>string(1) "3" ["fecha_de_ingreso"]=>string(10) "12-12-2011" ["salario_bruto_anual"]=>string(9) "106450.00" } ["ciudades"]=>array(1) { ["ciudad"]=>string(8) "New York" } ["cargos"]=>array(1) { ["cargo"]=>string(17) "Pre-Sales Support" } } [8]=>array(4) { ["id"]=>string(2) "51" ["personal"]=>array(6) { ["nombre"]=>string(4) "Cara" ["apellido"]=>string(7) "Stevens" ["cargo"]=>string(2) "23" ["ciudad"]=>string(1) "3" ["fecha_de_ingreso"]=>string(10) "06-12-2011" ["salario_bruto_anual"]=>string(9) "145600.00" } ["ciudades"]=>array(1) { ["ciudad"]=>string(8) "New York" } ["cargos"]=>array(1) { ["cargo"]=>string(15) "Sales Assistant" } } [9]=>array(4) { ["id"]=>string(1) "4" ["personal"]=>array(6) { ["nombre"]=>string(6) "Cedric" ["apellido"]=>string(5) "Kelly" ["cargo"]=>string(2) "25" ["ciudad"]=>string(1) "1" ["fecha_de_ingreso"]=>string(10) "29-03-2012" ["salario_bruto_anual"]=>string(9) "433000.00" } ["ciudades"]=>array(1) { ["ciudad"]=>string(9) "Edinburgh" } ["cargos"]=>array(1) { ["cargo"]=>string(27) "Senior Javascript Developer" } } } |
Te he puesto la matriz que se genera para que puedas compararla con el JSON. A mi me facilitó la comprensión de esto en su momento y espero que a ti también te sirva de ayuda es estos primeros pasos con el editor.
LEYENDO LAS LISTAS
Si volvemos al script principal, vemos que usamos, cómo hemos dicho anteriormente, el evento preOpen
de los formularios del objeto editor para obtener, en tiempo real, las listas de ciudades y de cargos, para los campos select. Esto lo hacemos por si, por ejemplo, mientras un usuario trabaja con esta página, otro usuario añade una nueva ciudad a la lista. Así, cada vez que pulsemos el botón de edición, la nueva ciudad estará inmediatamente disponible.
En realidad, lo que hacemos es leer por ajax la lista de ciudades y de cargos. Aquí vemos el script leer_ciudades_editor_04.php
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
<?php // Establecemos la codificacion para las llamadas y respuestas HTTP mb_internal_encoding ('UTF-8'); /* CREAMOS LA CONEXION A LA BASE DE DATOS, O BIEN LA IMPORTAMOS DESDE UN ARCHIVO EXTERNO DE CONFIGURACION. */ include ('conexion_bd_editor.php'); /* Se obtiene la lista de las ciudades a partir de la correspondiente tabla. */ $consulta = "SELECT "; $consulta .= "id AS value, "; $consulta .= "ciudad AS label "; $consulta .= "FROM ciudades "; $consulta .= "ORDER BY ciudad;"; $hacerConsulta = $conexion->query($consulta); $matrizDeCiudades = $hacerConsulta->fetchAll(PDO::FETCH_ASSOC); $hacerConsulta->closeCursor(); $listaDeCiudades = json_encode ($matrizDeCiudades, JSON_HEX_QUOT); echo $listaDeCiudades; ?> |
Y, a continuación, su contraparte para los cargos, llamado leer_cargos_editor_04.php
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
<?php // Establecemos la codificacion para las llamadas y respuestas HTTP mb_internal_encoding ('UTF-8'); /* CREAMOS LA CONEXION A LA BASE DE DATOS, O BIEN LA IMPORTAMOS DESDE UN ARCHIVO EXTERNO DE CONFIGURACION. */ include ('conexion_bd_editor.php'); /* Se obtiene la lista de las ciudades a partir de la correspondiente tabla. */ $consulta = "SELECT "; $consulta .= "id AS value, "; $consulta .= "cargo AS label "; $consulta .= "FROM cargos "; $consulta .= "ORDER BY cargo;"; $hacerConsulta = $conexion->query($consulta); $matrizDeCargos = $hacerConsulta->fetchAll(PDO::FETCH_ASSOC); $hacerConsulta->closeCursor(); $listaDeCargos = json_encode ($matrizDeCargos, JSON_HEX_QUOT); echo $listaDeCargos; ?> |
Como ves, no tienen nada de particular. Solo recuperan las listas de ciudades y cargos y las devuelven en JSON, que es como se intercambian siempre datos en DataTables (y en cualquier API que se precie).
PROCESAR LOS FORMULARIOS
Cuando se ha pedido la edición o el borrado de uno o más registros, o la creación de uno nuevo, es necesario, al pulsar el botón de envío del formulario, que se procese este adecuadamente. Esto ya aprendimos a hacerlo en el artículo anterior. Sin embargo, nos encontramos con lo mismo que hemos tenido presente en todo este artículo: al trabajar simultáneamente con más de una tabla, este proceso también resulta modificado. El listado se llama crud_editor_04.php
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 |
<?php // Establecemos la codificacion para las llamadas y respuestas HTTP mb_internal_encoding ('UTF-8'); /* CREAMOS LA CONEXION A LA BASE DE DATOS, O BIEN LA IMPORTAMOS DESDE UN ARCHIVO EXTERNO DE CONFIGURACION. */ include ('conexion_bd_editor.php'); /* RECUPERAMOS TODOS LOS PARAMETROS DE $_POST. LOS QUE NO APAREZCAN EN LA CONSULTA RECIBIRAN UN VALOR PREDETERMINADO. ESTOS DATOS SON LOS QUE SE RECIBEN CADA VEZ QUE EL PLUGIN DATATABLES LLAMA A ESTE SCRIPT. */ $datosDeLlamada = $_POST; /* SE INDICA(N) LA(S) TABLA(S) QUE SE VA(N) A USAR EN LA CONSULTA. */ $tablasDeBBDD = array( 'personal' ); /* SE DEFINE LA LISTA DE COLUMNAS QUE SE DEVOLVERON PARA SER MOSTRADAS EN LA TABLA HTML. LOS NOMBRES DE ESTAS COLUMNAS DEBEN COINCIDIR CON LOS DE LAS COLUMNAS DE LA(S) TABLA(S) AFECTADA(S) POR LA CONSULTA. */ $columnasDeDatos = array( $tablasDeBBDD[0].'.nombre', $tablasDeBBDD[0].'.apellido', $tablasDeBBDD[0].'.id_cargo', $tablasDeBBDD[0].'.id_ciudad', $tablasDeBBDD[0].'.fecha_de_ingreso', $tablasDeBBDD[0].'.salario_bruto_anual', $tablasDeBBDD[0].'.id' ); /* Se determina qué es lo que se va a hacer. Esto viene por POST en la variable "action". Puede ser remove, edit o create. En cada caso se llevara a cabo la acción elegida, sobre los registros que estuvieran seleccionados. Además, se prepara la respuesta adecuada para devolver al script primario. */ $data = array(); switch ($datosDeLlamada["action"]){ case "remove": foreach ($datosDeLlamada["data"] as $keyReg=>$registro){ $consulta = "DELETE FROM ".$tablasDeBBDD[0]." "; $consulta .= "WHERE ".$columnasDeDatos[6]."='".$keyReg."';"; $conexion->query($consulta); } break; case "edit": foreach ($datosDeLlamada["data"] as $keyReg=>$registro){ $consulta = "UPDATE ".$tablasDeBBDD[0]." SET "; $consulta .= $columnasDeDatos[0]."='".$registro["personal"]["nombre"]."', "; $consulta .= $columnasDeDatos[1]."='".$registro["personal"]["apellido"]."', "; $consulta .= $columnasDeDatos[2]."='".$registro["personal"]["cargo"]."', "; $consulta .= $columnasDeDatos[3]."='".$registro["personal"]["ciudad"]."', "; $consulta .= $columnasDeDatos[4]."='".date("Y-m-d", strtotime($registro["personal"]["fecha_de_ingreso"]))."', "; $consulta .= $columnasDeDatos[5]."='".$registro["personal"]["salario_bruto_anual"]."' "; $consulta .= "WHERE ".$columnasDeDatos[6]."='".$keyReg."';"; $conexion->query($consulta); $elemento = array("id"=>$keyReg); foreach ($registro["personal"] as $keyItem=>$item) $elemento[$keyItem] = $item; $data[] = $elemento; } break; case "create": $consulta = "INSERT INTO ".$tablasDeBBDD[0]." ("; $consulta .= $columnasDeDatos[0].", "; // nombre $consulta .= $columnasDeDatos[1].", "; // apellido $consulta .= $columnasDeDatos[2].", "; // id_cargo $consulta .= $columnasDeDatos[3].", "; // id_ciudad $consulta .= $columnasDeDatos[4].", "; // fecha_de_ingreso $consulta .= $columnasDeDatos[5].""; // salario_bruto_anual $consulta .= ") VALUES ("; $consulta .= "'".$datosDeLlamada["data"][0]["personal"]["nombre"]."', "; // nombre $consulta .= "'".$datosDeLlamada["data"][0]["personal"]["apellido"]."', "; // apellido $consulta .= "'".$datosDeLlamada["data"][0]["personal"]["cargo"]."', "; // id_cargo $consulta .= "'".$datosDeLlamada["data"][0]["personal"]["ciudad"]."', "; // id_ciudad $consulta .= "'".date("Y-m-d", strtotime($datosDeLlamada["data"][0]["personal"]["fecha_de_ingreso"]))."', "; // fecha_de_ingreso $consulta .= "'".$datosDeLlamada["data"][0]["personal"]["salario_bruto_anual"]."'"; // salario_bruto_anual $consulta .= ");"; $conexion->query($consulta); $elemento = array("id"=>$conexion->lastInsertId()); foreach ($datosDeLlamada["data"][0]["personal"] as $keyItem=>$item) $elemento[$keyItem] = $item; $data[] = $elemento; break; } $datosParaDevolver = array("data"=>$data); $resultado = json_encode($datosParaDevolver); echo $resultado; ?> |
Presta especial atención a las líneas resaltadas, y observa la diferencia con las equivalentes del artículo anterior. Cuando nos referimos a los elementos de $datosDeLlamada
(es decir, los que han sido pasados por post), hemos añadido el índice ["personal"]
. Esto es necesario ya que ahora un campo no se llama, por ejemplo, nombre
, sino personal.nombre
. Al actuar el nombre de la tabla como espacio de nombres, tal cómo te comenté más arriba, al generar la matriz este espacio de nombres forma una agrupación indexada que debemos referenciar.
LA CONEXIÓN A BD
Seguro que ya te has fijado, pero te lo digo igualmente. Dado que la conexión a base de datos ya no se hace, como veníamos haciendo hasta ahora, en uno o en dos scripts, sino que, en este ejercicio se hace en cuatro, lo que hemos hecho es crearla en un único archivo externo y llamarla dónde hace falta. A partir de ahora, usaremos este sistema, más cómodo y mantenible. El script, conexion_bd_editor.php
, no puede ser más simple.
1 2 3 4 5 |
<?php /* Definimos la conexion a base de datos para usarla en los scripts que necesitemos. */ $conexion = new PDO('mysql:host=localhost;dbname=datatables;charset=UTF8', 'root', ''); $conexion->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); ?> |
Si la conexión a bases de datos por PDO te ofrece dudas consulta este artículo.
Y, cómo tampoco es cuestión de que andes copiando y pegando todos los códigos, si has llegado leyendo hasta aquí, tienes premio. Te los puedes descargar todos en este enlace. De nada. 😉
Hola,
Estoy intentando añadir una funcionalidad a una datatable, que consiste en un buscador por columna independiente.
A partir del ejemplo:
https://datatables.net/examples/api/multi_filter.html
he añadido los imput correspondientes, pero el problema está en la función
$( ‘input’, this. header() ).on( ‘keyup change’, function () {
if ( that.search() !== this.value ) {
that
.search( this.value )
.draw();
}
} );
Al llenar cualquiera de las cajas de texto para hacer una búsqueda, se genera el error.
Con la herramienta de desarrollo web de Firefox: red, he comprobado que el error consiste en que, al escribir un carácter en la caja de texto, añade ese carácter al nombre de la columna y lógicamente no encuentra la columna.
Ej: escribo tx en la columna: ‘tb_asignatura.asignatura
Fatal error: Uncaught exception ‘PDOException’ with message ‘SQLSTATE[42S22]: Column not found: 1054 Unknown column ‘tb_asignatura.asignaturatx
Agradecería mucho cualquier sugerencia para solucionarlo.
Un saludo