EDITAR CUALQUIERA DE TODOS LOS ITEMS DE UNA TABLA CLICANDO LA CELDA (POR AJAX)
OPCIÓN 1: doble clic edito y clic afuera guardo o cancelo (si no cambié nada).
CARGA DE LA TABLA
Antes que nada después de conectar mysqli_set_charset($conn, "utf8mb4"); asegura el charset.
La tabla de clientes la cargamos a través de un while ($registro = mysqli_fetch_array
($sqldevolucion, MYSQLI_ASSOC)) como siempre pero en este caso todo por echo()por
lo complejo de la carga de registros.
El id de cada <tr> lo ponemos al campo id de la tabla asi vamos a poder diferenciar sobre que celda
clicamos la clase .entrada va con el trigger dblclick y cada <td> que sea editable
debe llevar como clase el nombre del campo que contiene por ejemplo la celda que contenga el valor del campo
"nombre" será
"<td class='nombre'> <div class='entrada'>" . $nombre . "</div></td>.
Si un campo está en blanco cargo un espacio htmlchar. para que no quede una div vacía que no
detecta eventos con:
if(strlen($registro["nombre-campo"])==0){
$nombre-campo=" ";
}else{
$nombre-campo=$registro["nombre-campo"];
}
Por supuesto que la solución ideal para codificar menos (el if en la carga) es que todos los valores de tipo
varchar y text tengan como default, fechas 0000-00-00 Esto es lo que vamos a hacer en
la opción 2).
SCRIPT PHP PARA EDITAR POR AJAX
Paso por POST desde AJAX el id, el valor del campo que voy a editar y su nombre (para eso puse en
el <td> la clase con el nombre del campo).
Aseguro la codificación con mysqli_set_charset($conn, "utf8mb4");
Y si borré un campo cargo un   para que la div no quede vacía y detecte los eventos con:
if(strlen($valor)==0){
$valor=" ";
}
Luego con un "UPDATE clientes SET " . $campo . "='" . $valor . "' WHERE id=" . $id; edito al nuevo valor
y para asegurar que se haya modificado hago un "SELECT " . $campo . " FROM clientes WHERE id=" . $id;
que me devuelve el valor de la base de datos, este es el que devuelvo en el JSON con
$resultado = array("newvalor" => $registro[$campo]);
SCRIPT JQUERY
Doble click sobre una celda editable
1) $('body').on('dblclick','.entrada', function(e){ si no burbujeamos desde body después de
una edición ya no responde la div recargada por script, porque no está cargada en la DOM inicial.
2) id= $(this).parents('tr').attr('id'); En este caso $(this) es la div .entrada,
parents('tr') busca el primer tr que encuentre hacia arriba y .attr('id')
el id de esa fila (que es el de la dirección obviamente).
3) contenido= $(this).html(); copia en la var contenido(global por declararla fuera de la función, la
vamos a necesitar luego en blur) el valor de la dirección contenida en la div clicada (this). Si el contenido
es un deja el valor del INPUT limpio, si no con el valor de contenido con:
if(contenido==" "){ var entrada = "<input class='cambio' type='text' value='' />";
}else{var entrada = "<input class='cambio' type='text' value='" + contenido + "' />";
si queda un dentro del input falla la igualdad 8) y siempre va al AJAX.
4) temp = $(this).parent().html(); En temp (global) se copia TODO el html porque parent es la etiqueta
superior a this o sea <td class='volver'> así en temp queda todo el html de la div con la dirección
original, la guardamos por si no hay edición para reponerla sin más.
5) campo = $(this).parent().attr('class'); En campo me queda el campo del item que cliqué,
porque el atributo class de la etiqueta parent de la div es el <td> y le ponemos por valor el campo
correspondiente al $registro["xxx"] que va en el html de la div.
6) "<input class='cambio' type='text' value='" + contenido + "' />"; a la var entrada le
doy el valor de un input clase .cambio para manejarla y de val = contenido (o sea la dirección actual). luego
$(this).parent().html(entrada); convierto la div en input con contenido igual al valor de
la <div> clicada. Recordar que this.parent es el <td class='volver'>.
6') Finalmente le doy foco con $('.cambio').focus();
Click sobre cualquier parte excepto la div doble clicada
7) $('body').on('blur','.cambio', function(e){ Cuando abandono el input .cambio (click fuera
de él). (Igual tema de burbujear de body).
8) if ($(this).val()==contenido){ si no cambie el valor (el original que guardé en contenido
es igual al valor del input $(this)).
9) $('#'+id).find('.' + campo).html(temp); Esta linea funciona así: $('#'+id) va
al <tr> de la fila en que estamos (porque así lo cargamos por php en la tabla, ver arriba)
find('.' + campo) busca dentro de esa <tr> la etiqueta que tenga el id '#campo' cuyo contenido
es el input que NO editamos y por eso .html(temp) lo sobreescribe con la div original que guardamos
en temp (pero como esta div aparece en el DOM después de la carga inicial los eventos directos sobre ella no funcionan,
por esto debemos burbujear los eventos desde body "punto 1)").
10) Si cambió el valor var nuevovalor = $(this).val(); guarda el valor editado
declaramos var espera como la div pero con un gif animado de espera.
11) $('.'+id).find('#' + campo).html(espera) lo mismo que en 9) pero lo que pongo es la div
con el gif de espera
FUNCIÓN ejecutar()
La var ejecutar = function(nuevo){ es la que por AJAX cambia el valor del campo editado.
12) Mando por AJAX el par id y nuevovalor con data: 'valor=' + nuevovalor + '&id=' + id + '&campo=' + campo,
(ej: valor=Cruz 566&id=2&campo=direccion ) a procesar, (ver código PHP más abajo, primero un UPDATE, luego un SELECT para
asegurarme que se cambió con éxito y devuelvo el valor nuevo por JSON ej:{"newvalor":"Cruz 705"}).
13) Con $('#'+id).find('.' + campo).html("<div class='entrada'>" + data.newvalor + "</div>");
sustituyo ahora el esperar por una div como la original pero con el valor devuelto por JSON,
que es el actual en la base de datos (data.newvalor).
14) Si el AJAX devuelve error entonces se ejecuta $('#'+id).find('.' + campo).html(temp);
que repone la div inicial
<!--************************HTML CARGAR LA TABLA******************************-->
<head>
<?php
include("conexion.php"); // abre la conexión a la base de datos
global $conn; //lo usamos en conexion, lo declaramos acá para no llamar siempre a $GLOBALS[]
?>
</head>
<body>
<table id="tabla">
<tr>
<td>ID</td>
<td>NOMBRE</td>
<td>DIRECCION</td>
<td>MAIL</td>
<td>NACIMIENTO</td>
</tr>
<?php
if(conectar()){
mysqli_set_charset($conn, "utf8mb4");
$sqltxt = "SELECT * FROM clientes LIMIT 0,15";
$sqldevolucion = mysqli_query($conn,$sqltxt);
if (!mysqli_num_rows($sqldevolucion)) {
echo("Error, no se devuelven registros");
} else {
while ($registro = mysqli_fetch_array($sqldevolucion, MYSQLI_ASSOC)){
if(strlen($registro["nombre"])==0){
$nombre=" ";
}else{
$nombre=$registro["nombre"];
}
if(strlen($registro["direccion"])==0){
$direccion=" ";
}else{
$direccion=$registro["direccion"];
}
if(strlen($registro["mail"])==0){
$mail=" ";
}else{
$mail=$registro["mail"];
}
if(strlen($registro["fecha_nac"])==0){
$fecha_nac="0000-00-00";
}else{
$fecha_nac=$registro["fecha_nac"];
}
echo("<tr id='" . $registro["id"] . "'>");
echo("<td class='filaid'>" . $registro["id"] . "</td>" );
echo("<td class='nombre'> <div class='entrada'>" . $nombre . "</div></td>" );
echo("<td class='direccion'> <div class='entrada'>" . $direccion . "</div></td>" );
echo("<td class='mail'> <div class='entrada'>" . $mail . "</div></td>" );
echo("<td class='fecha_nac' style=' text-align:center;'> <div class='entrada'>" . $fecha_nac . "</div></td>" );
echo("</tr>");
}
mysqli_free_result($sqldevolucion); //devuelvo recurso a memoria
mysqli_close($conn); // cierro conexion
}
}else{
echo(mysqli_error($conn));
}
?>
</table>
</body>
<!--*********************SCRIPT JQUERY (AJAX)*******************************-->
<head>
$(document).ready(function(){
var id=0; //declaro acá para que sean globales
var contenido;
var temp;
var campo;
var nuevovalor;
$('body').on('dblclick','.entrada', function(e){ //1-usamos esta y no la de abajo porque si no en la segunda
// $('.entrada').on('dblclick', function(e){ dblclick no va a responder porque .entrada no se cargo en el DOM inicial
id= $(this).parents('tr').attr('id'); //2-obtiene el id del <tr> o sea de la fila del clicado
contenido= $(this).html(); //3-obtiene el contenido de la <div> clicada
if(contenido==" "){ // si el contenido es   deja el input limpio si no pone su valor
var entrada = "<input class='cambio' type='text' value='' />"; // porque si no la database lo lasa a unicode
}else{ // y luego falla la igualdad de 8
var entrada = "<input class='cambio' type='text' value='" + contenido + "' />";
}
temp = $(this).parent().html(); //4-guarda el html completo de la celda por si no hay cambios
campo = $(this).parent().attr('class'); //5-obtiene la clase = nombre del campo que contiene el <td class=>
var entrada = "<input class='cambio' type='text' value='" + contenido + "' />";
$(this).parent().html(entrada); // 6-convierto la div en input con contenido igual al valor de la <div> clicada
$('.cambio').focus(); //le da foco al input
});
$('body').on('blur','.cambio', function(e){ //7-igual que dblclick pero al salir del input
nuevovalor = $(this).val(); // 10-guardo el valor editado
if (nuevovalor==contenido){ //8- si no cambie el valor
$('#'+id).find('.' + campo).html(temp); //9-vuelve al contenido original si no se cambió nada (en campo=)
}else{
ejecutar(nuevovalor); // va al AJAX para cambiar la base de datos
}
});
var ejecutar = function(nuevo){
var espera = "<div class='entrada' style='text-align:center;'><img src='esperando.gif' width='16' height='16'/></div>";
$('#'+id).find('.' + campo).html(espera);// 11-pongo el gif de esperar
$.ajax({
type: "POST",
url: "tec-php-editar-item-tabla-procesar02.php",
data: 'valor=' + nuevo + '&id=' + id + '&campo=' + campo, //12-POSTeo los pares valor/id/campo
dataType:'json',
success: function(data) {
$('#'+id).find('.' + campo).html("<div class='entrada'>" + data.newvalor + "</div>");
}, // 13-sustituyo el input por una div con el valor que devuelve procesar
error: function(request,error) {
$("#msg").html("ha ocurrido un error al editar la dirección");
$('#'+id).find('.' + campo).html(temp); //14- si no se pudo editar repongo div con el valor antiguo
}
}); //fin ajax
};
});
</script>
<style>
</style>
</head>
<!--********SCRIPT PROCESAR.PHP RECIBE ID Y DEVUELVE ARRAY CON EL NUEVO VALOR*********-->
<?php
include("conexion.php"); // abre la conexión a la base de datos
global $conn; //lo usamos en conexion, lo declaramos acá para no llamar siempre a $GLOBALS[]
$valor=$_POST["valor"]; // se recupera el valor pasado de ajax por post
$id=$_POST["id"]; // se recupera el id de la fila que edito
$campo = $_POST["campo"]; // se recupera el nombre del campo que estoy editando
if(conectar()){
mysqli_set_charset($conn, "utf8mb4");
if(strlen($valor)==0){
$valor=" ";
}
$sqltxt="UPDATE clientes SET " . $campo . "='" . $valor . "' WHERE id=" . $id;
$sqldevolucion = mysqli_query($conn,$sqltxt);
$sqltxt="SELECT " . $campo . " FROM clientes WHERE id=" . $id; //chequeo que se haya modificado
$sqldevolucion = mysqli_query($conn,$sqltxt);
$registro = mysqli_fetch_array($sqldevolucion, MYSQLI_ASSOC);
$resultado = array("newvalor" => $registro[$campo]);
mysqli_close($conn); // cierro conexion
echo(json_encode($resultado));
}
//echo($sqltxt);
?>
OPCIÓN 2: doble clic edito y clic afuera o Enter, ESC cancela, tabla con por default
CARGA DE LA TABLA
Antes que nada después de conectar
mysqli_set_charset($conn, "utf8mb4"); asegura el charset.
La tabla de clientes la cargamos a través de un while
($registro = mysqli_fetch_array
($sqldevolucion, MYSQLI_ASSOC)) como siempre pero en este caso todo por
echo()por
lo complejo de la carga de registros.
El id de cada <tr> lo ponemos al campo id de la tabla asi vamos a poder diferenciar sobre que celda
clicamos la clase
.entrada va con el trigger dblclick y cada <td> que sea editable
debe llevar como clase el nombre del campo que contiene por ejemplo la celda que contenga el valor del campo
"nombre" será
"<td class='nombre'> <div class='entrada'>" . $nombre . "</div></td>.
Si en la tabla todos los valores de tipo varchar y text tengan como default y las fechas
0000-00-00 directamente obviamos los if de la opción 1), esto es lo ideal.
SCRIPT PHP PARA EDITAR POR AJAX
El script es idéntico a la opción 1)
Doble click sobre la dirección
0) a la
Opción 1 agrego la variable
editando=0 que es una flag 0: indica no estoy editando 1:
estoy editando. es necesario para la función on click, que debe poder saber si se está saliendo de un INPUT que edita o
no (si no el siguiente doble click va a dar comportamientos erráticos)
1)
$('body').on('dblclick','.entrada', function(e){ si no burbujeamos desde body después de
una edición ya no responde la div recargada por script, porque no está cargada en la DOM inicial.
2)
id= $(this).parents('tr').attr('id'); En este caso
$(this) es la div .entrada,
parents('tr') busca el primer tr que encuentre
hacia arriba y
.attr('id') el id de esa fila (que es el de la dirección obviamente).
3)
contenido= $(this).html(); copia en la var contenido(global por declararla fuera de la función, la
vamos a necesitar luego en blur) el valor de la dirección contenida en la div clicada (this). Si el contenido
es un deja el valor del INPUT limpio, si no con el valor de contenido con:
if(contenido==" "){ var entrada = "<input class='cambio' type='text' value='' />";
}else{var entrada = "<input class='cambio' type='text' value='" + contenido + "' />";
si queda un
dentro del input falla la igualdad 8) y siempre va al AJAX.
4)
temp = $(this).parent().html(); En temp (global) se copia TODO el html porque parent es la etiqueta
superior a this o sea
<td class='volver'> así en temp queda todo el html de la div con la dirección
original, la guardamos por si no hay edición para reponerla sin más.
5)
campo = $(this).parent().attr('class'); En campo me queda el campo del item que cliqué,
porque el atributo class de la etiqueta parent de la div es el <td> y le ponemos por valor el campo
correspondiente al $registro["xxx"] que va en el html de la div.
6)
"<input class='cambio' type='text' value='" + contenido + "' />"; a la var entrada le
doy el valor de un input clase .cambio para manejarla y de val = contenido (o sea la dirección actual). luego
$(this).parent().html(entrada); convierto la div en input con contenido igual al valor de
la <div> clicada. Recordar que this.parent es el <td class='volver'>.
7) Finalmente le doy foco con
$('.cambio').focus();
7') Con
editando=1; levanto la flag para indicar que estoy en modo edición.
Al soltar una tecla
8)
$('body').on('keyup','.cambio', function(e){ al soltar una tecla en el INPUT (clase .cambio)
acá igual que antes tengo que burbujear desde body hasta .cambio, si no los INPUT no activan eventos
9)
e.keyCode == 27 detecta si soltamos ESC
10)
$('#'+id).find('.' + campo).html(temp); '#'+id es el <tr> (fila) que estamos editando
y
find('.' + campo) busca el <td> (celda) que tiene el campo que estabamos editando y
html(temp)
repone la <div> completa con el valor no cambiado que guardamos en
temp en la linea jquery 4)
11)
editando=0; indica que la edición terminó
12)
e.keyCode == 13 detecta si soltamos INTRO.
13) Con
nuevovalor = $(this).val(); guardamos el valor actual en el INPUT.
14-15) Si
nuevovalor==contenido entonces repetimos la linea 10) que repone la <div> inicial.
16) Si cambió va a la función ejecutar que tiene el AJAX para cambiar la base de datos
}else{ejecutar(nuevovalor);}
17) En ambos casos
editando=0; indica que la edición terminó
Al clicar en cualquier parte del documento
18)
$('body').on('blur','.cambio', function(e){ Cuando pierde foco el INPUT.
19) Con
nuevovalor = $(this).val(); guardo el valor actual del INPUT, ya que si clico fuera
de él ya pierdo ese valor, así lo conservo en la global
nuevovalor.
20) Acá hay un cambio,
$('body').on('click', function(e){ detecta un clic en cualquier parte del
body, asi que hay que separar los clic de dblclic, los dentro del INPUT y solo tener en cuenta los clicados
fuera del INPUT al terminar una edición.
21)
var quien= e.target.nodeName; .target.nodenamenos da el nombre de la etiqueta sobre la
cual se produce el evento, en este caso clic.
22) Primero verificamos que se clicó fuera de un INPUT con
quien != 'INPUT' y que estaba editando
editando==1
flag que levante en el dblclic que introdujo el INPUT. Con el if:
if (quien != 'INPUT' && editando==1 ){
23-24) Tengo que ver si no se cambió el valor del INPUT estas dos lineas son idénticas a 14-15), pero por eso en
el blur guardamos el valor actual del INPUT, aquí ya lo perdemos, no se sabe donde se clicó fuera del INPUT.
25-26) Repiten 16-17), o sea si se cambió el INPUT voy a la función AJAX y pongo
editando=0;
La función ejecutar() es la misma que en la opción 1
<!--************************HTML CARGAR LA TABLA******************************-->
Igual a la opción 1 si las tablas no tienen por default en text y varchar y 0000-00-00 en date.
si no simplemente será:
<?php
if(conectar()){
mysqli_set_charset($conn, "utf8mb4");
$sqltxt = "SELECT * FROM clientes LIMIT 0,15";
$sqldevolucion = mysqli_query($conn,$sqltxt);
if (!mysqli_num_rows($sqldevolucion)) {
echo("Error, no se devuelven registros");
} else {
while ($registro = mysqli_fetch_array($sqldevolucion, MYSQLI_ASSOC)){
echo("<tr id='" . $registro["id"] . "'>");
echo("<td class='filaid'>" . $registro["id"] . "</td>" );
echo("<td class='nombre'> <div class='entrada'>" . $registro["nombre"] . "</div></td>" );
echo("<td class='direccion'> <div class='entrada'>" . $registro["direccion"] . "</div></td>" );
echo("<td class='mail'> <div class='entrada'>" . $registro["mail"] . "</div></td>" );
echo("<td class='fecha_nac' style=' text-align:center;'> <div class='entrada'>" . $registro["echa_nac"]f . "</div></td>" );
echo("</tr>");
}
mysqli_free_result($sqldevolucion); //devuelvo recurso a memoria
mysqli_close($conn); // cierro conexion
}
}else{
echo(mysqli_error($conn));
}
?>
<!--*********************SCRIPT JQUERY (AJAX)*******************************-->
<script>
$(document).ready(function(){
var id=0; //declaro acá para que sean globales
var contenido; //guarda el contenido de un campo por si no se cambia
var temp; //guarda toda la div para reponerla si no se cambia
var campo; //guarda el nombre del campo que vamos a editar
var nuevovalor; //guarda el valor del input si editamos o no.
var editando=0; //flag para saber en el click si salimos de editar
$('body').on('dblclick','.entrada', function(e){//1-usamos esta y no la de abajo porque si no en la segunda
// $('.entrada').on('dblclick', function(e){ dblclick no va a responder porque .entrada no se cargo en el DOM inicial
id= $(this).parents('tr').attr('id'); //2-obtiene el id del <tr> o sea de la fila del clicado
contenido= $(this).html(); //3-obtiene el contenido de la <div> clicada
if(contenido==" "){ // si el contenido es   deja el input limpio si no pone su valor
var entrada = "<input class='cambio' type='text' value='' />"; // porque si no la database lo lasa a unicode
}else{ // y luego falla la igualdad de 8
var entrada = "<input class='cambio' type='text' value='" + contenido + "' />";
}
temp = $(this).parent().html(); //4-guarda el html completo de la celda por si no hay cambios
campo = $(this).parent().attr('class'); //5-obtiene la clase = nombre del campo que contiene el <td class=>
$(this).parent().html(entrada); //6-convierto la div en input con contenido igual al valor de la <div> clicada
$('.cambio').focus(); //7-le da foco al input.
editando=1; //7'-levanto la flag para indicar que estoy en modo edición.
});
$('body').on('keyup','.cambio', function(e){ //8-igual que dblclick pero al oprimir una tecla
if (e.keyCode == 27 ) { //9-si la tecla es ESC (keycode=27)
$('#'+id).find('.' + campo).html(temp); //10-vuelve al contenido original.
editando=0; //11-indico que terminó la edición
}else if (e.keyCode == 13 ) { //12-si la tecla es INTRO (keycode=13)
nuevovalor = $(this).val(); //13-guardo el valor actual del input
if (nuevovalor==contenido){ //14 si no cambie el valor
$('#'+id).find('.' + campo).html(temp); //15-vuelve al contenido original.
}else{
ejecutar(nuevovalor); //16-si cambió va al AJAX para cambiar la base de datos
}
editando=0; //17-en ambos casos indico fin de edición
}
});
$('body').on('blur','.cambio', function(e){ //18-igual que dblclick pero al perder foco el input
nuevovalor = $(this).val(); //19-guardo el valor actual del input (para el click)
});
$('body').on('click', function(e){ //20-igual que dblclick pero al click
var quien= e.target.nodeName; //21-detecto en que htlm cliqué
if (quien != 'INPUT' && editando==1 ){ //22-si cliqué fuera del input y estoy editando
if (nuevovalor==contenido){ //23- si no cambie el valor
$('#'+id).find('.' + campo).html(temp); //24-vuelve al contenido original si no se cambió nada (en campo=)
}else{
ejecutar(nuevovalor); //25-si cambió va al AJAX para cambiar la base de datos
}
editando=0; //26-en ambos casos indico fin de edición
}
});
var ejecutar = function(nuevo){ //Función de EDICIÓN
var espera = "<div class='entrada' style='text-align:center;'><img src='esperando.gif' width='16' height='16'/></div>";
$('#'+id).find('.' + campo).html(espera); //26-pongo el gif de esperar
$.ajax({
type: "POST",
url: "tec-php-editar-item-tabla-procesar02.php",
data: 'valor=' + nuevo + '&id=' + id + '&campo=' + campo, //27-POSTeo los pares valor/id/campo
dataType:'json',
success: function(data) {
$('#'+id).find('.' + campo).html("<div class='entrada'>" + data.newvalor + "</div>");
}, //28-sustituyo el input por una div con el valor que devuelve procesar
error: function(request,error) {
$("#msg").html("ha ocurrido un error al editar la dirección");
$('#'+id).find('.' + campo).html(temp); //29-si no se pudo editar repongo div con el valor antiguo
}
}); //fin ajax
};
});
</script>
<!--********SCRIPT PROCESAR.PHP RECIBE ID Y DEVUELVE ARRAY CON EL NUEVO VALOR*********-->
Igual a la opción 1