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 &nbsp; htmlchar. para que no quede una div vacía que no detecta eventos con:
if(strlen($registro["nombre-campo"])==0){
        $nombre-campo="&nbsp;";
}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 &nbsp; 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 &nbsp para que la div no quede vacía y detecte los eventos con:
if(strlen($valor)==0){
        $valor="&nbsp;";
}
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 &nbsp; deja el valor del INPUT limpio, si no con el valor de contenido con:
if(contenido=="&nbsp;"){ var entrada = "<input class='cambio' type='text' value='' />";
}else{var entrada = "<input class='cambio' type='text' value='" + contenido + "' />";
si queda un &nbsp; 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=="&nbsp;"){					// 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="&nbsp;";
		}
		$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 &nbsp; 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 &nbsp; 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 &nbsp; deja el valor del INPUT limpio, si no con el valor de contenido con:
if(contenido=="&nbsp;"){ var entrada = "<input class='cambio' type='text' value='' />";
}else{var entrada = "<input class='cambio' type='text' value='" + contenido + "' />";
si queda un &nbsp; 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 &nbsp; 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=="&nbsp;"){					// 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
						
					
© IQSystems 2023