He escrito muy poco sobre este proyecto. ¿Acaso ya me libré de él? ¡JA JA JA JA JA! No.
Aunque afortunadamente sí que me han ido asignando otros proyectos más emocionantes y de mi gusto, y la balanza se está equilibrando a mi favor. Después de una agradable pausa, con alguna incidencia leve por el camino, el compañero que más sabía de este proyecto se marcha de la empresa. ¿A quién le toca ahora cuidar de él? ¡JA JA JA JA JA! A mí.
Sin embargo el project manager sabe lo caótico que es este proyecto. Así que, no solo no me deja solo ante el peligro, sino que me ha asignado a dos "ayudantes", que llevan algo más de tiempo que yo.
Muy pronto empezaremos otra fase, bastante revolucionaria (para bien de todos), y me ha encargado que vaya instruyendo y dirigiendo a estos ayudantes mientras trato de poner orden en el núcleo. De alguna manera, he subido de categoría. (Y de sueldo, afortunadamente, aunque no tuviera ninguna relación)
El presupuesto no permite cambiar radicalmente todo el código para que, al menos, sea inteligible. Pero haré lo que pueda allá por donde paso, para evitar que las futuras generaciones sufran tanto como yo.
Pero no se preocupen; en algún momento volveré para seguirme desahogando. ¡JA JA JA JA JA! O eso creo.
lunes, 12 de febrero de 2018
martes, 6 de febrero de 2018
Pesadilla en Laravel Street, capítulo 2
Seguimos con mis pequeños y potencialmente terroríficos desahogos.
Terminé el capítulo anterior contando sobre la "autogeneración" de campos en create y edit. Utilizando algo así de relativamente genérico, ¿qué se hace con los datos recibidos a la hora de guardarlos?
Las funciones store y update siguen ahí, por supuesto. En muchos casos debería bastar con llamar al modelo, y usar Eloquent para un create o un update de los de toda la vida. Como mucho, meter un práctico validate() nada más empezar.
¿Para qué? Eso no es lo bastante genérico. Para cada controlar vamos a inventar dos tipos propios de Request. La mayoría de estos Requests no van a contener nada, pero vamos a crearlos, por si acaso
Cada uno de estos Requests contendrá al menos una mención a las reglas de validación. Estas se encuentran como una propiedad estática del modelo correspondiente.
Pero bueno, así tenemos no estamos repitiendo la validaciones en varias funciones. Algo es algo. Ahora vamos a guardar usando las funciones de Eloquent.
¿Para qué? Eloquent es muy mainstream. Me voy a inventar un Repositorio para cada modelo. Este contendrá, entre otras cosas, una función de búsqueda. Esta coge la tabla, cuyo nombre se especifica manualmente (???), y genera un listado de todas sus columnas (???). También controlamos excepciones en caso de que no encuentre tal registro, devolviendo la conocida excepción HTTP nº 1001 (???).
Suficiente por hoy. No quiero que me venga otra jaqueca hoy.
Terminé el capítulo anterior contando sobre la "autogeneración" de campos en create y edit. Utilizando algo así de relativamente genérico, ¿qué se hace con los datos recibidos a la hora de guardarlos?
Las funciones store y update siguen ahí, por supuesto. En muchos casos debería bastar con llamar al modelo, y usar Eloquent para un create o un update de los de toda la vida. Como mucho, meter un práctico validate() nada más empezar.
¿Para qué? Eso no es lo bastante genérico. Para cada controlar vamos a inventar dos tipos propios de Request. La mayoría de estos Requests no van a contener nada, pero vamos a crearlos, por si acaso
Cada uno de estos Requests contendrá al menos una mención a las reglas de validación. Estas se encuentran como una propiedad estática del modelo correspondiente.
Pero bueno, así tenemos no estamos repitiendo la validaciones en varias funciones. Algo es algo. Ahora vamos a guardar usando las funciones de Eloquent.
¿Para qué? Eloquent es muy mainstream. Me voy a inventar un Repositorio para cada modelo. Este contendrá, entre otras cosas, una función de búsqueda. Esta coge la tabla, cuyo nombre se especifica manualmente (???), y genera un listado de todas sus columnas (???). También controlamos excepciones en caso de que no encuentre tal registro, devolviendo la conocida excepción HTTP nº 1001 (???).
Suficiente por hoy. No quiero que me venga otra jaqueca hoy.
Laravel DataTables 5
Hablemos de filtros y buscadores, características estrella en DataTables.
Como ya habremos observado nada más aplicar DataTables por primera vez en jQuery, en la esquina superior derecha de la tabla se puede ver un campo de búsqueda. Al escribir en él, inmediatamente filtra la tabla, mostrando solo las filas que contengan esos caracteres en cualquier columna.
En el modo server-side la búsqueda también la realiza mediante llamadas al servidor. El propio Laravel DataTables se encarga de recoger esos parámetros y aplicar los WHEREs necesarios. En estos WHEREs, como ya expliqué en el capítulo anterior, utilizará el campo indicado en Javascript como name.
Todo esto va bien para la mayoría de casos. Pero la programación sería muy aburrida si no hubieran casos especiales, ¿verdad?
Para estos casos especiales, en los que la información visible en la tabla no es exactamente igual a cómo está guardada en la base de datos, en Laravel DataTables tenemos el método filterColumn.
El ejemplo más típico serían las fechas. En la web mostramos 30/01/2018, pero en la base de datos puede estar guardado como 2018-01-30, o como 1517310949. Para esto... la verdad es que es complicado. De momento he conseguido con un plugin para jQuery DataTables, pero ha requerido unos cuantos trucos en ambos lados para buscar por rango de fechas. Esto me lo reservo para otro capítulo.
Otro ejemplo habitual serían las columnas de Nombre y de Apellidos. Es muy normal guardar estos datos por separado, pero mostrarlos como si fueran una columna. Puede ser uniéndolos directamente en la query (CONCAT(`nombre`, " ", `apellidos`) AS `nombre_completo`) y metiendo el nombre de este alias como name en Javascript. En tal caso filterColumn deja de ser necesario.
Otra manera, la que vemos en la documentación oficial, es inventando el mismo alias dentro de filterColumn.
Es posible que me haya explicado fatal. Lo siento; es la primera vez que escribo documentación sobre algo así. ^_^u
En el próximo capítulo voy a improvisar un ejemplo, y exponerlo todo de manera más práctica.
Como ya habremos observado nada más aplicar DataTables por primera vez en jQuery, en la esquina superior derecha de la tabla se puede ver un campo de búsqueda. Al escribir en él, inmediatamente filtra la tabla, mostrando solo las filas que contengan esos caracteres en cualquier columna.
En el modo server-side la búsqueda también la realiza mediante llamadas al servidor. El propio Laravel DataTables se encarga de recoger esos parámetros y aplicar los WHEREs necesarios. En estos WHEREs, como ya expliqué en el capítulo anterior, utilizará el campo indicado en Javascript como name.
Todo esto va bien para la mayoría de casos. Pero la programación sería muy aburrida si no hubieran casos especiales, ¿verdad?
Para estos casos especiales, en los que la información visible en la tabla no es exactamente igual a cómo está guardada en la base de datos, en Laravel DataTables tenemos el método filterColumn.
El ejemplo más típico serían las fechas. En la web mostramos 30/01/2018, pero en la base de datos puede estar guardado como 2018-01-30, o como 1517310949. Para esto... la verdad es que es complicado. De momento he conseguido con un plugin para jQuery DataTables, pero ha requerido unos cuantos trucos en ambos lados para buscar por rango de fechas. Esto me lo reservo para otro capítulo.
Otro ejemplo habitual serían las columnas de Nombre y de Apellidos. Es muy normal guardar estos datos por separado, pero mostrarlos como si fueran una columna. Puede ser uniéndolos directamente en la query (CONCAT(`nombre`, " ", `apellidos`) AS `nombre_completo`) y metiendo el nombre de este alias como name en Javascript. En tal caso filterColumn deja de ser necesario.
Otra manera, la que vemos en la documentación oficial, es inventando el mismo alias dentro de filterColumn.
->filterColumn('nombre_completo', function($query, $clave) {
$sql = "CONCAT(`nombre`, " ", apellidos) LIKE ?";
$query->whereRaw($sql, ["%{$clave}%"]);
})
Es posible que me haya explicado fatal. Lo siento; es la primera vez que escribo documentación sobre algo así. ^_^u
En el próximo capítulo voy a improvisar un ejemplo, y exponerlo todo de manera más práctica.
martes, 30 de enero de 2018
Laravel DataTables 4
Los ejemplos básicos de Laravel DataTables (incluyendo los míos) solo tratan con tablas individuales. Es más raro encontrar qué hacer con las columnas de otras tablas tomadas por relaciones, algo la mar de normal en bases de datos.
No hay mucha complicación aquí. El nombre utilizado es el mismo que el nombre de la columna en sí, tanto en Laravel como en Javascript. De modo que si utilizamos el método Query Builder:
Podemos mostrar las columnas: 'tabla1.id', 'tabla1.nombre' y 'tabla2.origen'.
Nada nos impide emplear alias.
Sin embargo esto tiene sus limitaciones a la hora de aplicar filtros (sobre los que hablaré en el próximo capítulo). Para esto habrá que engrosar un poco la información en Javascript. El resultado sería algo así:
En cada columna, el data es lo que se muestra en la tabla resultante, y el name es lo que utilizará para las búsquedas o filtros. O sea, lo que meterá en sus WHEREs. No es necesario indicar el nombre completo en columnas de la tabla principal (la del FROM), pero nunca está de más.
Por supuesto, esta distinción es importante si dos tablas tienen columnas con el mismo nombre.
Pos eso. En el próximo capítulo empiezo con los filtros y búsquedas.
No hay mucha complicación aquí. El nombre utilizado es el mismo que el nombre de la columna en sí, tanto en Laravel como en Javascript. De modo que si utilizamos el método Query Builder:
$datos = DB::table('tabla')
->select([
'tabla1.id',
'tabla1.nombre',
'tabla2.origen'
])
->join('tabla2', 'tabla1.origen_id', 'tabla2.id');
Podemos mostrar las columnas: 'tabla1.id', 'tabla1.nombre' y 'tabla2.origen'.
Nada nos impide emplear alias.
$datos = DB::table('tabla')
->select([
'tabla1.id AS id',
'tabla1.nombre AS nombre',
'tabla2.origen AS 'origen'
])
->join('tabla2', 'tabla1.origen_id', 'tabla2.id');
Sin embargo esto tiene sus limitaciones a la hora de aplicar filtros (sobre los que hablaré en el próximo capítulo). Para esto habrá que engrosar un poco la información en Javascript. El resultado sería algo así:
$(function() {
$('#list-table').DataTable({
processing: true,
serverSide: true,
columns: [
{ data: 'id', name: 'tabla1.id' },
{ data: 'nombre', name: 'tabla1.nombre' },
{ data: 'origen', name: 'tabla2.origen' },
],
ajax: 'http://sitio.net/tabla/data'
}
});
});
En cada columna, el data es lo que se muestra en la tabla resultante, y el name es lo que utilizará para las búsquedas o filtros. O sea, lo que meterá en sus WHEREs. No es necesario indicar el nombre completo en columnas de la tabla principal (la del FROM), pero nunca está de más.
Por supuesto, esta distinción es importante si dos tablas tienen columnas con el mismo nombre.
Pos eso. En el próximo capítulo empiezo con los filtros y búsquedas.
lunes, 22 de enero de 2018
Laravel DataTables 3
En la entrada anterior he explicado el uso básico, y espero que haya quedado lo bastante claro, al menos para desarrolladores con algo de experiencia en Laravel. A partir de aquí podemos pasar a temas más concretos, y sobre los que costará más encontrar documentación y ejemplos.
Creo que lo primero que se necesitaría meter en una de estas tablas, al menos dentro de un panel de gestión, serían las acciones por fila. Los típicos botones o enlaces de modificar, eliminar, deshabilitar, seleccionar, etc. cada entrada.
Esta no es una información que forme parte de la base de datos. En PHP se suele meter el HTML necesario en cada bucle de la tabla, inyectándole el ID de cada entrada. ¿Laravel DataTables facilita este aspecto? ¡Y tanto!
Durante la creación del objeto DataTables podemos crear columnas al vuelo, con el método addColumn. Se le especifica un nombre de columna (uno cualquiera) y un contenido. Este puede ser HTML escrito a mano, o puede ser una vista Blade.
Dentro de la vista se pueden utilizar, como variables, las columnas de la fila actual. Sin más.
Un detalle bastante importante en Javascript sería excluir esta columna de las búsquedas y reordenamiento.
En el próximo capítulo: qué hacer con tablas relacionadas. No se preocupe: saldrá muy pronto.
Creo que lo primero que se necesitaría meter en una de estas tablas, al menos dentro de un panel de gestión, serían las acciones por fila. Los típicos botones o enlaces de modificar, eliminar, deshabilitar, seleccionar, etc. cada entrada.
Esta no es una información que forme parte de la base de datos. En PHP se suele meter el HTML necesario en cada bucle de la tabla, inyectándole el ID de cada entrada. ¿Laravel DataTables facilita este aspecto? ¡Y tanto!
Durante la creación del objeto DataTables podemos crear columnas al vuelo, con el método addColumn. Se le especifica un nombre de columna (uno cualquiera) y un contenido. Este puede ser HTML escrito a mano, o puede ser una vista Blade.
DataTables::of($tabla)->addColumn('acciones', 'tablas.tabla-acciones');
Dentro de la vista se pueden utilizar, como variables, las columnas de la fila actual. Sin más.
<a href="editar.php?id={{ $id }}">Editar {{ $nombre }}</a>
<a href="{{ route("tabla.informe", ["id" => $id]) }}">Informe</a>
Un detalle bastante importante en Javascript sería excluir esta columna de las búsquedas y reordenamiento.
{ data: 'acciones', orderable: false, searchable: false }
En el próximo capítulo: qué hacer con tablas relacionadas. No se preocupe: saldrá muy pronto.
martes, 16 de enero de 2018
Laravel DataTables 2
Como indicaba en el capítulo anterior, el primer proyecto donde quería probar Laravel DataTables no estaba basado en Laravel. Pero sí había un proyecto relacionado, mucho más reciente, que utilizaba la misma base de datos y que sí tenía Laravel. Lo que hace jQuery DataTables en modo server-side no son más que llamadas AJAX. Así que el lado de Laravel es básicamente una API lista para servir.
Después de instalar Laravel DataTables (como cualquier otro paquete mediante Composer), creé un controlador exclusivamente para esto, con la función data. Como explica la demo oficial, puede bastar con retornar DataTables::of(Modelo::all())->make(true).
Desgraciadamente como este era un proyecto antiguo pre-Laravel, el acceso por modelo no me sirve. O al menos no de manera directa y fácil.
Afortunadamente Laravel DataTables puede recolectar datos en base a tres tipos de objetos: Eloquent, Collection y Query Builder.
Como este proyecto tenía unas relaciones un tanto complicadas, opté por Query Builder. No es lo ideal, pero así pude copiar las consultas que se estaban utilizando previamente y basarme en ellas. Venía a ser: return DataTables::of(DB::table('tabla')->select(['columna1', 'columna2']))->make(true);
Listos por el lado del controlador. ... ¿Que cómo se gestionan las búsquedas, paginación, etc.? La función of se encarga absolutamente de toda la interactividad, en base a parámetros auto-generados que envía el cliente con cada clic. No hay que programar nada para estos casos. Ni siquiera hace falta extender el controlador.
Lo único que falta es la ruta:
Route::name('tabla.data')->get('tabla/data', 'TablaController@data');
Como verá usted, la ruta no tiene nada de especial. El controlador se encarga de todo.
Por supuesto hay diferencias con mi proyecto: lo paso por ruta POST, en lugar de GET (preferencia personal), y el controlador lo extiendo de ApiGuard (sí, un poco de seguridad, ya que es una API entre dos proyectos, y no quiero que pueda acceder cualquiera).
En el HTML del front-end escribo la tabla solo con sus cabeceras, una por columna, y añado el script necesario:
$(function() {
$('#list-table').DataTable({
processing: true,
serverSide: true,
columns: [
{ data: 'columna1' },
{ data: 'columna2', orderable: false, searchable: false }
],
ajax: 'http://sitio.net/tabla/data'
}
});
});
Y con eso ya tenemos lo básico. Creo que por ahora no necesita más explicación, aparte de que esas dos primeras propiedades siempre deben ser true (hasta donde yo sé).
A partir del próximo capítulo serán principalmente detalles y casos más específicos. Condiciones para las búsquedas, cómo tratar columnas de otras tablas, acciones para cada fila, filtro individual por columna, etc.
Después de instalar Laravel DataTables (como cualquier otro paquete mediante Composer), creé un controlador exclusivamente para esto, con la función data. Como explica la demo oficial, puede bastar con retornar DataTables::of(Modelo::all())->make(true).
Desgraciadamente como este era un proyecto antiguo pre-Laravel, el acceso por modelo no me sirve. O al menos no de manera directa y fácil.
Afortunadamente Laravel DataTables puede recolectar datos en base a tres tipos de objetos: Eloquent, Collection y Query Builder.
- Eloquent sería trabajar con los Modelos, tal cual. Si están bien definidos, con sus relaciones y todo, es lo más laraveliano. Lo ideal en muchos casos.
- La Colección debe estar completamente formada antes de llamar a DataTables. Es útil si los datos necesitan ser tratados en PHP, o cualquier historia "chunga". Esto implica más trabajo todavía para PHP, y menor flexibilidad a la hora de aplicar filtros.
- Query Builder... ya sabe usted, suele ser el paso previo en Laravel a get(), all(), o cualquier función que implique ejecutar la consulta / query. Puede ser tanto formada con métodos de modelos, de DB, o escribiendo manualmente el SQL.
Como este proyecto tenía unas relaciones un tanto complicadas, opté por Query Builder. No es lo ideal, pero así pude copiar las consultas que se estaban utilizando previamente y basarme en ellas. Venía a ser: return DataTables::of(DB::table('tabla')->select(['columna1', 'columna2']))->make(true);
Listos por el lado del controlador. ... ¿Que cómo se gestionan las búsquedas, paginación, etc.? La función of se encarga absolutamente de toda la interactividad, en base a parámetros auto-generados que envía el cliente con cada clic. No hay que programar nada para estos casos. Ni siquiera hace falta extender el controlador.
Lo único que falta es la ruta:
Route::name('tabla.data')->get('tabla/data', 'TablaController@data');
Como verá usted, la ruta no tiene nada de especial. El controlador se encarga de todo.
Por supuesto hay diferencias con mi proyecto: lo paso por ruta POST, en lugar de GET (preferencia personal), y el controlador lo extiendo de ApiGuard (sí, un poco de seguridad, ya que es una API entre dos proyectos, y no quiero que pueda acceder cualquiera).
En el HTML del front-end escribo la tabla solo con sus cabeceras, una por columna, y añado el script necesario:
$(function() {
$('#list-table').DataTable({
processing: true,
serverSide: true,
columns: [
{ data: 'columna1' },
{ data: 'columna2', orderable: false, searchable: false }
],
ajax: 'http://sitio.net/tabla/data'
}
});
});
Y con eso ya tenemos lo básico. Creo que por ahora no necesita más explicación, aparte de que esas dos primeras propiedades siempre deben ser true (hasta donde yo sé).
A partir del próximo capítulo serán principalmente detalles y casos más específicos. Condiciones para las búsquedas, cómo tratar columnas de otras tablas, acciones para cada fila, filtro individual por columna, etc.
martes, 2 de enero de 2018
Laravel DataTables 1
En caso de que usted no lo hubiera leído ya, Laravel DataTables básicamente consiste en un componente para enviar desde Laravel datos en JSON, destinados a un jQuery DataTables en modo server-side.
Si usted había utilizado DataTables, es posible que se hubiese conformado con el modo fácil, basado en DOM: generar desde el backend una tabla HTML con absolutamente toda la información, y ejecutar DataTables encima con jQuery. De esta manera el navegador se encarga del paginado, orden, filtros, y todas las bondades que DataTables lleva años ofreciendo.
Esas bondades dejan de ser tan bonitas cuando la tabla contiene miles de entradas. El servidor tardará en procesar todo ese HTML. El cliente estará utilizando demasiada memoria. Cada interacción puede tardar más de lo debido. El usuario se impacientará cuando "solo" quiere buscar los artículos vendidos tal día por tal empleado con tal método de pago.
Ahí es cuando entra en acción el modo server-side processing. Cada vez que se pasa de página, o se utiliza un filtro, DataTables llamará por AJAX al servidor para pedirle información; solo la necesaria para rellenar la vista actual, y no toda de golpe. La diferencia de rendimiento y estabilidad empieza a ser evidente, ¿verdad?
Laravel DataTables se encarga de que el lado de servidor sea fácil. Puede bastar con crear una ruta, coger un modelo y aplicarle una función. Luego en el front-end se inicializa DataTables con unas pocas opciones, y ya está el funcionamiento básico en marcha y en toda su gloria.
Con varios de mis proyectos me he topado exactamente con el problema descrito antes. Para esto Laravel DataTables me ha parecido un rayo de esperanza (o de luz verde). Y lo he puesto en práctica hace poco.
Por supuesto no iba a ser tan fácil: este proyecto era un poco viejo, y no estaba basado en Laravel. Tuve que hacer algunos trucos para aprovechar Laravel igualmente. Y he tenido que invertir bastantes horas para implementar en el front-end exactamente los mismos filtros que tenían antes. Finalmente he conseguido que apenas se note la diferencia, excepto en que ahora funciona fantásticamente, en lugar de tardar medio minuto en abrir la tabla.
A partir del siguiente capítulo contaré los detalles de esta pequeña aventura.
Si usted había utilizado DataTables, es posible que se hubiese conformado con el modo fácil, basado en DOM: generar desde el backend una tabla HTML con absolutamente toda la información, y ejecutar DataTables encima con jQuery. De esta manera el navegador se encarga del paginado, orden, filtros, y todas las bondades que DataTables lleva años ofreciendo.
Esas bondades dejan de ser tan bonitas cuando la tabla contiene miles de entradas. El servidor tardará en procesar todo ese HTML. El cliente estará utilizando demasiada memoria. Cada interacción puede tardar más de lo debido. El usuario se impacientará cuando "solo" quiere buscar los artículos vendidos tal día por tal empleado con tal método de pago.
Ahí es cuando entra en acción el modo server-side processing. Cada vez que se pasa de página, o se utiliza un filtro, DataTables llamará por AJAX al servidor para pedirle información; solo la necesaria para rellenar la vista actual, y no toda de golpe. La diferencia de rendimiento y estabilidad empieza a ser evidente, ¿verdad?
Laravel DataTables se encarga de que el lado de servidor sea fácil. Puede bastar con crear una ruta, coger un modelo y aplicarle una función. Luego en el front-end se inicializa DataTables con unas pocas opciones, y ya está el funcionamiento básico en marcha y en toda su gloria.
Con varios de mis proyectos me he topado exactamente con el problema descrito antes. Para esto Laravel DataTables me ha parecido un rayo de esperanza (o de luz verde). Y lo he puesto en práctica hace poco.
Por supuesto no iba a ser tan fácil: este proyecto era un poco viejo, y no estaba basado en Laravel. Tuve que hacer algunos trucos para aprovechar Laravel igualmente. Y he tenido que invertir bastantes horas para implementar en el front-end exactamente los mismos filtros que tenían antes. Finalmente he conseguido que apenas se note la diferencia, excepto en que ahora funciona fantásticamente, en lugar de tardar medio minuto en abrir la tabla.
A partir del siguiente capítulo contaré los detalles de esta pequeña aventura.
Suscribirse a:
Entradas (Atom)