Tabla con contenido editable en AngularJS 1.x
En ocasiones deseamos hacer nuestras páginas web más accesibles, facilitándole a nuestros usuarios el uso de la misma: a través de atajos, controles dinámicos, controles más visibles, reducir las llamadas al servidor, etc.
En esta ocasión quiero presentar, como opción, una directiva (tipo atributo) que con solo agregarla a una celda de una tabla hará que el usuario pueda modificar dicha celda sólo con darle click encima (sin utilizar ningún botón o menú adicional).
El resultado final de este post se encuentra en codepen, puedes ver todo el código completo y probarlo en línea.
Aunque la directiva está en AngularJS (para poder recuperar fácilmente los cambios que el usuario haga en el contenido de la tabla) se vale del atributo contenteditable de HTML5, el cuál (en diciembre de 2015) ya es soportado por la mayoría de navegadores web.
Haz click aquí para ver la tabla de soporte actualizada.
Además cada cambio que el usuario haga dentro de la tabla se verá reflejado en una variable dentro de nuestro $scope (para este ejemplo la variable se llama lista), así podemos fácilmente manipular toda la información que sea ingresada o modificada en nuestra tabla, ¿para postearla a nuestro servidor talvez?.
Para empezar entonces hay que utilizar una tabla para mostrar todos los registros que contiene la variable $scope.lista (a través de un ng-repeat). La tabla sería mas o menos así:
<table class="table"> <thead> <tr> <th>#</th> <th>Nombres</th> <th>Apellidos</th> <th>Corre Electrónico</th> </tr> </thead> <tbody> <tr ng-repeat="item in lista"> <td style="cursor: not-allowed;">{{$index + 1}}</td> <td>{{item.nombres}}</td> <td>{{item.apellidos}}</td> <td>{{item.email}}</td> </tr> </tbody> </table>
Luego, dentro de la directiva haremos 3 cosas:
- Agregamos el atributo contenteditable = “true” (de HTML5).
- Agregamos una función en los eventos onblur, onkeyup y onchange de nuestro td, esta función tomará los cambios que el usuario haga y los guardara en la variable $scope.lista. Para saber que fila y columna es la que el usuario está modificando utilizará los atributos row y field que también debemos agregar a nuestro td (más adelante indicaré como se deben agregar).
- Agregamos el evento onclick a nuestro td y desde este evento mandamos a seleccionar todo el texto que contenga (para que al usuario se le haga más fácil proceder a modificarlo).
El código de la directiva es el siguiente (estoy colocando en negrita las líneas que hacen relación a cada uno de los puntos que anteriormente describi):
app.directive('editableTd', [function() { return { restrict: 'A', link: function(scope, element, attrs) { element.css("cursor", "pointer"); element.attr('contenteditable', 'true'); // Referencia: Inciso 1 element.bind('blur keyup change', function() { // Referencia: Inciso 2 scope.lista[attrs.row][attrs.field] = element.text(); }); element.bind('click', function() { // Referencia: Inciso 3 document.execCommand('selectAll', false, null) }); } }; }]);
Por último, para hacer referencia a la directiva desde nuestro td, es necesario incluir los atributos row y field (como anteriormente mencione):
<td editable-td row="{{$index}}" field="nombres"> {{item.nombres}} </td>
En donde:
- row: hace referencia al elemento que el usuario está modificando, para decirlo más práctico, es nuestro $index.
- field: hace referencia al campo que se está mostrando dentro del td (por el ng-repeat)
EXTRA
Podemos hacer también una opción de eliminar y otra de agregar para que el usuario pueda interactuar con ellas.
En el caso de la función eliminar se pide una confirmación al usuario y, al tener una respuesta afirmativa, utilizamos la función splice sobre la variable $scope.lista. La función recibe como parámetro row, que es el número de fila que deseamos eliminar.
$scope.eliminar = function(row) { if (confirm("¿Seguro que desea eliminar?")) { $scope.lista.splice(row, 1); } };
La función agregar es más sencila, solamente se hace un push a la variable $scope.lista, en donde agregamos un nuevo registro con sus respectivos campos vacíos (o puedes asignarle un valor predefinido).
$scope.agregar = function() { $scope.lista.push({ nombres: '', apellidos: '', email: '' });
Eso es todo, si te gustó el post recuerda darle a compartir. También puedes suscribirte si deseas recibir más post como éste en tu correo electrónico.
EXTRA 2
Con solamente una pequeña variación se pueden agregar columnas calculadas, mira en CODEPEN el ejemplo.
EXTRA 3
Se puede detectar el evento keypress en cada columna que sea numérica para no permitir que el usuario ingrese letras o cualquier otro caracter inválido, mira en CODEPEN el ejemplo.
Hola amigo excelente manual, se puede hacer esto para calcular campos como por ejemplo la cantidad * precio = total y que se muestre en una celda aparte, si tienes alguna documentación te agradecería mucho
No tengo documentación, pero con una pequeña variación al ejemplo te puedo mostrar que si se puede hacer: Tabla con contenido editable V2
Con este cambio lo más importante es hacer uso del
scope.$digest();
para que la línea se actualice cuando modificas uno de los dos valores utilizados para realizar el cálculo.Hola muy bueno el manual, como puedo hacer que cuando pulse el botón “Recuperar Valores” obtenga solo los valores que fueron afectados?
Gracias….
Hola, muy bien el post, pero cuando ordeno la tabla con orderBy: ‘email’:false no pinchan los calculos, es como si se mariara con los rows. me pudieras ayudar con esto???? muchas graciass
Hola amigo se puede hacer que solo se acepten números o que la opccion sea la de un combo ?
Claro, dentro de la directiva editableTd podes detectar el evento KeyPress del elemento y validar que solamente se ingresen números, la validación sería así:
Podés ver el cambio corriendo aquí: CODEPEN
Muchas gracias amigo, me sirvio de mucho, pero encontre un pequeño problema que no puedo solucionarlo, en Google Chrome si quiero escribir 253, escribe 352 porque le pone al cursor siempre primero, en firefox funciona muy bien, me podrias ayudar con alguna solucion?? muchas gracias de antemano
La línea scope.$digest (que está dentro de la directiva editableTd); es la que ocasiona que eso pase, dicha línea se usa para las columnas calculadas, si no necesitas columnas calculadas podes quitarla sin problemas
Hola, muchas gracias por el aporte, solo una duda, lo estoy implementando en ASP.NET con C#, no logro que al hacer el post, del lado del servidor lea las variables o el JSON que me genera “RECUPERAR VALORES” ([{“nombres”:”Juan”,”apellidos”:”Pérez”,”email”:”juanperez@gmail.com”,”pass”:”123juan”,”$$hashKey”:”004″},{“nombres”:”Julio”,”apellidos”:”López”,”email”:”jlopez@gmail.com”,”pass”:”passjulio”,”$$hashKey”:”005″}]), guardando en una varialbe $scope.lista, luego pasandolos a un FormData, llegan pero como valores independientes algo asi como Lista[0][nombres]…..Lista[1][nombres], en vez de algo como “Lista” y dentro el resto del json, como hago para almenos dar un formato o tomar los valores individuales de la tabla?
Hola que tal me gusto mucho la explicación que diste, mas sin embargo yo no estoy utilizando JQery no se i de casualidad has hecho este ejemplo pero con angular 6, si es asi te agradecería mucho si me lo puedes proporcionar , gracias !!
con que hiciste el aspecto visual? bootstrap?
Es correcto, eso es bootstrap.
Gracias amigo, ya agregé el CDN de bootstrap pero por alguna extraña razón me queda distinto el diseño y aparte, los iconos de trash y plus no me salen en los botones, alguna idea del porqué?
Consulta:
al crear una nueva fila y escribir sobre ella se multiplican los caracteres que se escriben,
hay alguna forma de solucionar esto?