Lista arrastrable con jQuery, jQuery UI, AJAX, PHP y MySQL

¡Hola! En esta ocasión voy a mostrar como crea una lista que pueda ser ordenada siendo arrastrada. Esto se realiza gracias a la función Sortable de jQuery UI. El orden será enviado a través de la función AJAX de jQuery, procesado a través de PHP y almacenado en MySQL.

El resultado es este:

Notas iniciales:

Para este ejemplo estoy usando una versión custom de jQuery UI (v1.11.4) que únicamente incluye UI Core e Interactions (ver en la web oficial), pero se puede usar la versión completa sin problema. La versión de jQuery es la 2.1.1 minificada.

¿Cómo crear una lista arrastrable?

Como siempre digo, seguiré desarrollando en local, utilizando XAMPP (Apache y MySQL con PHPMyAdmin) en Windows.

1 de 2: MySQL

Lo primero que debemos hacer es estructurar nuestra base de datos.
Continúo trabajando sobre una base de datos llamada mmv con cotejamiento utf8_spanish_ci. Dentro tengo una tabla llamada mmv007 que tiene tres columnas. El motor de almacenamiento es InnoDB.

  1. id -> INT(5) -> A_I -> primaria
  2. contenido -> varchar(300) -> utf8_spanish_ci
  3. orden -> INT(10)

2 de 2: HTML, PHP, jQuery y AJAX

Voy a comenzar creando el formulario para enviar el contenido.

Primero debemos crear un archivo PHP con la estructura de HTML5 estándar. Para este ejemplo, este será el index.php.

<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Lista arrastrable</title>
</head>

<body>
</body>
</html>

A continuación, vamos a añadir las librerías de jQuery. Estas se agregarán justo antes del cierre de la etiqueta body:

<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Lista arrastrable</title>
</head>

<body>

<script src="js/jquery-2.1.1.min.js"></script>
<script src="js/jquery-ui-1.11.4-custom.min.js"></script>
</body>
</html>

Ahora creamos el contenedor que mostrará los mensajes correspondientes, junto a un formulario con método POST, id nuevoContenido, action vacío y que acepte UTF-8. El único campo será contenido.

<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Lista arrastrable</title>
</head>

<body>

<div id="mensaje"></div>

<form method="POST" id="nuevoContenido" accept-charset="utf-8">
	<label for="contenido"><p>Agregar nuevo contenido:</p></label>
	<input type="text" name="contenido" id="contenido" placeholder="" value="" autocomplete="off" maxlength="60" required>

	<input type="submit" name="nuevoContenido" value="Enviar">
</form>

<script src="js/jquery-2.1.1.min.js"></script>
<script src="js/jquery-ui-1.11.4-custom.min.js"></script>
</body>
</html>

Una vez hecho esto, creamos el script de AJAX que permite enviar el contenido a PHP sin tener que recargar la página.

<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Lista arrastrable</title>
</head>

<body>

<div id="mensaje"></div>

<form method="POST" id="nuevoContenido" accept-charset="utf-8">
	<label for="contenido"><p>Agregar nuevo contenido:</p></label>
	<input type="text" name="contenido" id="contenido" placeholder="" value="" autocomplete="off" maxlength="60" required>

	<input type="submit" name="nuevoContenido" value="Enviar">
</form>

<script src="js/jquery-2.1.1.min.js"></script>
<script src="js/jquery-ui-1.11.4-custom.min.js"></script>

<script>
$(document).ready(function(){
	var mensaje = $("#mensaje");
	mensaje.hide();

	$("#nuevoContenido").on("submit", function(e){
		e.preventDefault();
		var formData = new FormData(document.getElementById("nuevoContenido"));

		$.ajax({
			url: "recursos/nuevoContenido.php",
			type: "POST",
			dataType: "html",
			data: formData,
			cache: false,
			contentType: false,
			processData: false
		}).done(function(echo){
			mensaje.html(echo);
			mensaje.slideDown(500);
		});
	});
});
</script>
</body>
</html>

Como se puede ver en la url de AJAX, estamos enviando este contenido a un archivo llamado nuevoContenido.php (dentro de mi carpeta recursos), así que vamos a crearlo.

El archivo debe estar totalmente vacío. Las etiquetas HTML (doctype, html, head, body, etc.) que puedan generar algunos editores de texto (como Dreamweaver) deben ser eliminadas.

Entonces le decimos al documento que vamos a escribir PHP y requerimos nuestro archivo de conexión a la BBDD. Después evitamos que PHP nos muestre los E_NOTICE, y pasamos a obtener los datos del input contenido. Esos datos los filtramos con las funciones de PHP htmlspecialchars() y mysqli_real_escape_string().

Después usamos la función trim() para eliminar espacio en blanco del inicio y el final del contenido.

<?php
require('conexion.php');
error_reporting(E_ALL ^ E_NOTICE);

$contenido = $_POST['contenido'];
$contenido = htmlspecialchars(mysqli_real_escape_string($conexion, $contenido));
$contenido = trim($contenido);

?>

Ahora debemos comprobar el último número de orden en la base de datos para sumarle un uno al ingresarlo. Si fuese el primer contenido, lógicamente será 1.

<?php
require('conexion.php');
error_reporting(E_ALL ^ E_NOTICE);

$contenido = $_POST['contenido'];
$contenido = htmlspecialchars(mysqli_real_escape_string($conexion, $contenido));
$contenido = trim($contenido);

//////////////////

$consultaOrden = mysqli_query($conexion, "SELECT `orden` FROM `mmv007` ORDER BY `orden` DESC LIMIT 1");
while($datosOrden = mysqli_fetch_array($consultaOrden)) {
	$ultimoOrden = $datosOrden['orden'];
};
$nuevoOrden = $ultimoOrden + 1;

?>

Y por último insertamos los datos a MySQL y mostramos un mensaje de éxito en el contenedor #mensaje.

<?php
require('../../conexion.php');
error_reporting(E_ALL ^ E_NOTICE);

$contenido = $_POST['contenido'];
$contenido = htmlspecialchars(mysqli_real_escape_string($conexion, $contenido));
$contenido = trim($contenido);

//////////////////

$consultaOrden = mysqli_query($conexion, "SELECT `orden` FROM `mmv007` ORDER BY `orden` DESC LIMIT 1");
while($datosOrden = mysqli_fetch_array($consultaOrden)) {
	$ultimoOrden = $datosOrden['orden'];
};
$nuevoOrden = $ultimoOrden + 1;

//////////////////

$consultaInsert = "INSERT INTO `mmv007` (`contenido`, `orden`) VALUES ('$contenido', '$nuevoOrden')";

if(mysqli_query($conexion, $consultaInsert)) {
	die('Contenido insertado');
};

?>

Es hora de mostrar los datos en la página. Para esto, en el archivo index.php debemos requerir el archivo de conexión. Creamos una consulta a MySQL y mostramos los datos. Es importante obtener el ID, después veremos por qué.

<?php require('../conexion.php'); ?>
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Lista arrastrable</title>
</head>

<body>

<div id="mensaje"></div>

<form method="POST" id="nuevoContenido" accept-charset="utf-8">
	<label for="contenido"><p>Agregar nuevo contenido:</p></label>
	<input type="text" name="contenido" id="contenido" placeholder="" value="" autocomplete="off" maxlength="60" required>

	<input type="submit" name="nuevoContenido" value="Enviar">
</form>

<hr>

<h2>Contenido:</h2>

<div class="contenedorContenido">
<?php
$consultaObtencion = mysqli_query($conexion, "SELECT * FROM `mmv007` ORDER BY `orden`");
if(mysqli_num_rows($consultaObtencion) > 0) {
	while($contenidoBD = mysqli_fetch_array($consultaObtencion)) {
		$numid = $contenidoBD['id'];
		$contenido = $contenidoBD['contenido'];
?>
    <div class="mi_contenido" id="contenido-<?php echo $numid; ?>">
    	<p><?php echo $numid; ?> - <?php echo $contenido; ?></p>
    </div>
<?php
	};//Fin while
} else {
	?>
    <p>No hay contenido para mostrar.</p>
<?php
};//Fin num_rows
?>
</div>

<script src="js/jquery-2.1.1.min.js"></script>
<script src="js/jquery-ui-1.11.4-custom.min.js"></script>

<script>
$(document).ready(function(){
	var mensaje = $("#mensaje");
	mensaje.hide();

	$("#nuevoContenido").on("submit", function(e){
		e.preventDefault();
		var formData = new FormData(document.getElementById("nuevoContenido"));

		$.ajax({
			url: "recursos/nuevoContenido.php",
			type: "POST",
			dataType: "html",
			data: formData,
			cache: false,
			contentType: false,
			processData: false
		}).done(function(echo){
			mensaje.html(echo);
			mensaje.slideDown(500);
		});
	});
});
</script>
</body>
</html>

¿Por qué hay un div con clase contenedorContenido? Este es el contenedor padre que contiene los elementos que serán arrastrables.

¿Por qué obtenemos y mostramos el ID del contenido? Necesitamos usar la función uniqueId para comprobar el identificador único de cada contenedor (.mi_contenido) y hacerlo arrastrable.

<?php require('../conexion.php'); ?>
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Lista arrastrable</title>
</head>

<body>

<div id="mensaje"></div>

<form method="POST" id="nuevoContenido" accept-charset="utf-8">
	<label for="contenido"><p>Agregar nuevo contenido:</p></label>
	<input type="text" name="contenido" id="contenido" placeholder="" value="" autocomplete="off" maxlength="60" required>

	<input type="submit" name="nuevoContenido" value="Enviar">
</form>

<hr>

<h2>Contenido:</h2>

<div class="contenedorContenido">
<?php
$consultaObtencion = mysqli_query($conexion, "SELECT * FROM `mmv007` ORDER BY `orden`");
if(mysqli_num_rows($consultaObtencion) > 0) {
	while($contenidoBD = mysqli_fetch_array($consultaObtencion)) {
		$numid = $contenidoBD['id'];
		$contenido = $contenidoBD['contenido'];
?>
    <div class="mi_contenido" id="contenido-<?php echo $numid; ?>">
    	<p><?php echo $numid; ?> - <?php echo $contenido; ?></p>
    </div>
<?php
	};//Fin while
} else {
	?>
    <p>No hay contenido para mostrar.</p>
<?php
};//Fin num_rows
?>
</div>

<script src="js/jquery-2.1.1.min.js"></script>
<script src="js/jquery-ui-1.11.4-custom.min.js"></script>

<script>
$(document).ready(function(){
	var mensaje = $("#mensaje");
	mensaje.hide();

	$("#nuevoContenido").on("submit", function(e){
		e.preventDefault();
		var formData = new FormData(document.getElementById("nuevoContenido"));

		$.ajax({
			url: "recursos/nuevoContenido.php",
			type: "POST",
			dataType: "html",
			data: formData,
			cache: false,
			contentType: false,
			processData: false
		}).done(function(echo){
			mensaje.html(echo);
			mensaje.slideDown(500);
		});
	});

	$(".contenedorContenido").children().uniqueId().end().sortable({
	opacity: 0.8, 
	cursor: 'move',
    update: function (event, ui) {
        var data = $(this).sortable('serialize');
        $.ajax({
            data: data,
            type: 'POST',
            url: 'recursos/nuevoOrden.php'
        });
    }
	});

});
</script>
</body>
</html>

Como se puede observar, el contenedor que arrastremos pasará a tener 80% de opacidad y se mostrará el cursor de “mover elementos”. Obviamente, estos valores pueden ser editados.

Después usamos AJAX para enviar los datos del nuevo orden serializados a nuevoOrden.php, este archivo simplemente enviará esos datos a la base de datos.

<?php 
require('conexion.php');
$array = $_POST['contenido'];

array_unshift($array,"");
unset($array[0]);

$count = 1;
foreach ($array as $idval) {
	$query = "UPDATE `mmv007` SET `orden` = " . $count . " WHERE `id` = " . $idval;
	mysqli_query($conexion, $query);
	$count ++;	
}
?>

Y listo, ya tenemos la lista arrastrable completada.

Conclusión:

Es la primera vez que utilizo jQuery UI, y me costó bastante averiguar cómo obtener el contenedor que vamos a arrastrar, pero finalmente lo conseguí, y este es el resultado.

Espero que el tutorial haya sido de utilidad. Nos leemos, ¡saludos!

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s