Tutoriel: Drag and Drop JQuery, exemple avec une liste des tâches

Publié par Gui dans jQuery, Tutoriels

Facile

jQuery, css

Après un tutoriel pour créer un diaporama simple avec JQuery, qui d’après vos commentaires vous a assez intéressé, voici un nouveau tutoriel basé sur le Drag and Drop.

L’exemple que nous allons voir est une liste des tâche simple, que je vous présente sous la forme d’une liste de courses.

Le principe est simple, on ajoute des éléments, on les renomme, on modifie leur ordre et on les supprime.

On va voir dans cet article comment utiliser de manière très simple la bibliothèque d’animations et d’interactions JQuery UI.

Celle ci permet d’avoir « nativement » des interfaces pour pouvoir déplacer des éléments et contrôler où on les dépose (drag & drop) ainsi que pour les trier.

Ces interfaces sont les suivantes :

DEMO DOWNLOAD

Mise en page et intégration

Partie HTML

Nous allons créer une page xHTML simple, composée des éléments suivants :

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="fr" lang="fr">
	<head>
		<title>Shopping List JQuery</title>
		<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />

		<!-- Cascading Style Sheets -->
		<link href="page.css" rel="stylesheet" type="text/css" />
		<link href="style.css" rel="stylesheet" type="text/css" />

		<!-- Javascript -->
		<script type="text/javascript" src="js/jquery-1.4.2.min.js"></script>
		<script type="text/javascript" src="js/jquery-ui-1.8.custom.min.js"></script>
		<script type="text/javascript" src="js/jquery.shoppingList.js"></script>
		<script type="text/javascript" src="js/script.js"></script>
	</head>

	<body>

		<h1>My Shopping List</h1>

		<div class="shoppingList">
			<ul>
				<li>Milk</li>
				<li>Red wine</li>
				<li>Sugar</li>
				<li>Ice Cream</li>
			</ul>
		</div>

	</body>

</html>

Nous avons besoin dans ce tutoriel de deux fichiers CSS :

  1. Pour la mise en page de la page elle même
  2. Pour la mise en page de la liste des courses (shopping list)

Ainsi que de quatre fichiers Javascripts :

  1. La librairie JQuery 1.4.2
  2. Notre librairie JQuery UI personnalisée (avec nos trois composants)
  3. Notre plugin shoppingList
  4. Un fichier de script d’initialisation

Partie CSS

En ce qui concerne la partie CSS :

.shoppingList{
	margin:0;
	background:url(img/notebook.gif) no-repeat;
	font-style:normal;
	padding:60px 0 0 40px;
	width:460px;
	height:448px;
	position:relative;
}

.shoppingList ul{
	margin:0;
	padding:0;
	list-style:none;
	position:relative;
	padding-top:17px;
}

.shoppingList li{
	font-size:1.3em;
	position:relative;
	text-shadow:none;
	clear:both;
}

.shoppingList li .count{
	float:right;
	padding-right:20px;
}

.shoppingList li .item{
	cursor:pointer;
}

.shoppingList li .check{
	width:16px;
	height:25px;
	line-height:25px;
	float:left;
	margin-right:50px;
	cursor:pointer;
}

.shoppingList li .unchecked{
	background:url(img/item_unchecked.png) center center no-repeat;
}

.shoppingList li .checked{
	background:url(img/tick_circle.png) center center no-repeat;
}

.shoppingList li.bought .item{
	text-decoration:line-through;
	color:#888;
	font-style:italic
}

.shoppingList .trash{
	position:absolute;
	bottom:70px;
	height:60px;
	background:#ddd;
	-moz-box-shadow:0 0 0.5em #666;
	width:330px;
	opacity:0.6;
	filter : alpha(opacity=60);
    -moz-opacity : 0.6;
	line-height:60px;
	font-style:italic;
	text-shadow:0 1px 0 #ccc;
	font-size:1.5em;
	background:#eee url(img/trash.png) 2.5em center no-repeat;
	padding-left:100px
}

.shoppingList .hover{
	opacity:0.7;
	filter : alpha(opacity=70);
    -moz-opacity : 0.7;
	color:#fff;
	background-color:#cd6a76
}

.shoppingList .deleted{
	background-color:#70cc57;
	opacity:0.7;
	filter : alpha(opacity=70);
    -moz-opacity : 0.7;
	color:#fff;
}

.shoppingList .add{
	position:absolute;
	bottom:20px;
}

.shoppingList .add .addValue{
	width:350px;
	margin-right:5px;
	background-color:#f5f2d5;
	-moz-box-shadow:0 0 0.5em #666;
	padding:5px;
	border:1px solid #ccc;
	font-family:Georgia,"Times New Roman","Bitstream Charter",Times,serif;
	color:#093E56;
	font-size:1.3em;
}

.shoppingList .add .addBtn{
	background-color:#093E56;
	-moz-box-shadow:0 0 0.5em #666;
	padding:2px;
	border:1px solid #ccc;
	color:#fff;
	-moz-border-radius:0.2em;
	padding:5px 8px;
	*padding:0;
	cursor:pointer;
	font-size:1.3em;
	font-family:Georgia,"Times New Roman","Bitstream Charter",Times,serif;
}

/*** DRAG ***/

.shoppingList li.ui-sortable-helper{
	cursor:-moz-grabbing;
}

.shoppingList li.ui-sortable-helper .check, .shoppingList li.ui-sortable-helper .count{
	visibility:hidden
}

A ce stade, nous devrions avoir une page qui ressemble à ça :

Rentrons maintenant dans le coeur du sujet, la partie Javascript.

Développement du plugin

Pour plus d’informations sur comment créer un plugin en JQuery, je vous invite à vous référer au tutoriel de création de diaporama.

Voici donc notre script de base pour notre shopping list :

(function($){
	$.fn.shoppingList = function(options) {

		// Options par defaut
		var defaults = {};

		var options = $.extend(defaults, options);

		this.each(function(){

			var obj = $(this);

			// Traitement

		});
		// On continue le chainage JQuery
		return this;
	};
})(jQuery);

Ordonner les éléments

Nous allons maintenant rendre notre liste d’éléments sortable en utilisant la librairie JQuery UI :

// Initialisation du composant "sortable"
$(obj).sortable({
	axis: "y", // Le sortable ne s'applique que sur l'axe vertical
	containment: ".shoppingList", // Le drag ne peut sortir de l'élément qui contient la liste
	handle: ".item", // Le drag ne peut se faire que sur l'élément .item (le texte)
	distance: 10, // Le drag ne commence qu'à partir de 10px de distance de l'élément
	// Evenement appelé lorsque l'élément est relaché
	stop: function(event, ui){
		// Pour chaque item de liste
		$(obj).find("li").each(function(){
			// On actualise sa position
			index = parseInt($(this).index()+1);
			// On la met à jour dans la page
			$(this).find(".count").text(index);
		});
	}
});

Explications :

On applique la méthode Sortable à notre liste <ul> (obj), ce qui rendra chacun de ses éléments fils (<li>) déplaçables (draggable).

Nous pourrons donc les trier selon les paramètres que nous avons définis, à savoir :

  1. Déplacement vertical seulement (axis: « y »)
  2. Déplacement autorisé uniquement au sein de l’élément parent (containment: « .shoppingList »)
  3. Déplacement en cliquant sur le texte (handle: « .item »)

Ensuite, la méthode « stop » va définir les actions à effectuer lorsque l’on arrêtera de déplacer un élément, à savoir simplement actualiser sa position au sein de la liste :

index = parseInt($(this).index()+1);

On récupère sa nouvelle position (méthode index()) que l’on incrémente car celle ci commence de 0 (comme un tableau) (La position est expliquée plus bas dans l’article).

Ajouter / Supprimer des éléments

Nous allons à présent ajouter de quoi rajouter ou supprimer des éléments de notre liste :

// On ajoute l'élément Poubelle à notre liste
$(obj).after("<div class='trash'>Trash</div>");
// On ajoute un petit formulaire pour ajouter des items
$(obj).after("<div class='add'><input class='addValue' /> <input type='button' value='Add' class='addBtn' /></div>");

Nous ajoutons donc simplement deux <div> après notre liste d’éléments.

Ensuite nous allons définir des actions/évènements pour ces <div>.

Action d’ajout

// Bouton ajouter
$(".addBtn").click(function(){
	// Si le texte n'est pas vide
	if($(".addValue").val() != "")
	{
		// On ajoute un nouvel item à notre liste
		$(obj).append('<li>'+$(".addValue").val()+'</li>');
		// On réinitialise le champ de texte pour l'ajout
		$(".addValue").val("");
		// On ajoute les contrôles à notre nouvel item
		addControls($(obj).find("li:last-child"));
	}
})
// On autorise également la validation de la saisie d'un nouvel item par pression de la touche entrée
$(".addValue").live("keyup", function(e) {
	if(e.keyCode == 13) {
		// On lance l'évènement click associé au bouton d'ajout d'item
		$(".addBtn").trigger("click");
	}
});

Au clic sur l’élément « .addBtn » (notre bouton), si le champ de saisie est rempli, nous récupérons sa valeur que nous ajoutons à la liste d’éléments (méthode append()). Nous ajoutons au dernier élément de la liste (donc celui que l’on vient d’ajouter) des contrôles via la méthode addControls(), que je vais détailler plus loin.

Nous ajoutons aussi un évènement « keyup« , qui nous permet d’ajouter un nouvel élément en validant par la touche <entrée>.

Note :

Pour réitérer la même action que lors du clic, nous pouvons déclencher un évènement (en l’occurence un « click ») via la méthode « trigger()« , et ça c’est bien pratique !

Action de suppression

Pour la partie suppression, nous allons être un peu original ! Pour changer de la très classique croix que l’on clique pour supprimer un élément, nous allons ici profiter de notre sortable pour dragger (déplacer) un élément vers la poubelle.

Pour ce faire nous allons donc appliquer à notre <div> Poubelle la méthode droppable().

Le principe étant de déclencher une action (la suppression de l’élément) lorsque l’on relâche un élément au dessus de la poubelle.

$(".trash").droppable({
	// Lorsque l'on passe un élément au dessus de la poubelle
	over: function(event, ui){
		// On ajoute la classe "hover" au div .trash
		$(this).addClass("hover");
		// On cache l'élément déplacé
		ui.draggable.hide();
		// On indique via un petit message si l'on veut bien supprimer cet élément
		$(this).text("Remove "+ui.draggable.find(".item").text());
		// On change le curseur
		$(this).css("cursor", "pointer");
	},
	// Lorsque l'on quitte la poubelle
	out: function(event, ui){
		// On retire la classe "hover" au div .trash
		$(this).removeClass("hover");
		// On réaffiche l'élément déplacé
		ui.draggable.show();
		// On remet le texte par défaut
		$(this).text("Trash");
		// Ainsi que le curseur par défaut
		$(this).css("cursor", "normal");
	},
	// Lorsque l'on relache un élément sur la poubelle
	drop: function(event, ui){
		// On retire la classe "hover" associée au div .trash
		$(this).removeClass("hover");
		// On ajoute la classe "deleted" au div .trash pour signifier que l'élément a bien été supprimé
		$(this).addClass("deleted");
		// On affiche un petit message "Cet élément a été supprimé" en récupérant la valeur textuelle de l'élément relaché
		$(this).text(ui.draggable.find(".item").text()+" removed !");
		// On supprimer l'élément de la page, le setTimeout est un fix pour IE (http://dev.jqueryui.com/ticket/4088)
		setTimeout(function() { ui.draggable.remove(); }, 1);

		// On retourne à l'état originel de la poubelle après 2000 ms soit 2 secondes
		elt = $(this);
		setTimeout(function(){ elt.removeClass("deleted"); elt.text("Trash"); }, 2000);
	}
})

Nous utilisons trois évènements dans cette interface :

  1. over: au survol de la zone de la poubelle pour indiquer que l’on va supprimer cet élément
  2. out: pour revenir à l’état original (quand on quitte la zone de la poubelle)
  3. drop: la méthode qui va nous permettre de définir une action au moment de relâcher un élément

Méthode Over

Hormis le fait de cacher le texte de l’élément et de modifier l’apparence de l’élément poubelle, la principale subtilité de cette méthode est ce bout de code :

$(this).text("Remove "+ui.draggable.find(".item").text());

On récupère ici le texte de l’élément déplacé via le paramètre ui, qui est un object JQuery que l’on peut donc exploiter via la méthode text() et on l’affiche dans le <div> Poubelle.

Méthode Out

Cette méthode permet de revenir à l’état original (enlever les classes, réafficher l’élément en cours de déplacement et réinitialiser le texte du <div> Poubelle)

Méthode Drop

Enfin la méthode Drop est celle qui va nous permettre de déclencher la suppression de notre élément. On va changer la classe du <div> Poubelle pour indiquer que l’élément a été supprimé l’espace de 2 secondes avant de revenir à la normale.

Nous devrions maintenant avoir une interface assez fonctionnelle qui ressemble à cela :

Confirmation visuelle pour la suppression de l'élément Ice Cream

L'élément a bien été supprimé

Ajout de fonctionnalités

Comme dit plus haut, nous allons maintenant enrichir notre liste avec quelques fonctionnalités bien pratiques comme :

  1. La possibilité de renommer un élément
  2. Afficher sa position dans la liste
  3. Ajouter un bouton pour dire que la tâche est accomplie

Voici donc la fonction :

function addControls(elt)
{
	// 1. On ajoute en premier l'élément textuel
	$(elt).html("<span class='item'>"+$(elt).text()+"</item>");
	// Puis l'élément de position
	$(elt).prepend('<span class="count">'+parseInt($(elt).index()+1)+'</span>');
	// Puis l'élément d'action (élément acheté)
	$(elt).prepend('<span class="check unchecked"></span>');

	// Au clic sur cet élément
	$(elt).find(".check").click(function(){
		// On alterne la classe de l'item (le <li>), le CSS associé fera que l'élément sera barré
		$(this).parent().toggleClass("bought");

		// Si cet élément est acheté
		if($(this).parent().hasClass("bought"))
			// On modifie la classe en ajoutant la classe "checked"
			$(this).removeClass("unchecked").addClass("checked");
		// Le cas contraire
		else
			// On modifie la classe en retirant la classe "checked"
			$(this).removeClass("checked").addClass("unchecked");
	})

	// Au double clic sur le texte
	$(elt).find(".item").dblclick(function(){
		// On récupère sa valeur
		txt = $(this).text();
		// On ajoute un champ de saisie avec la valeur
		$(this).html("<input value='"+txt+"' />");
		// On la sélectionne par défaut
		$(this).find("input").select();
	})

	// Lorsque l'on quitte la zone de saisie du texte
	$(elt).find(".item input").live("blur", function(){
		// On récupère la valeur du champ de saisie
		txt = $(this).val();
		// On insère dans le <li> la nouvelle valeur textuelle
		$(this).parent().html(txt);
	})

	// On autorise la même action lorsque l'on valide par la touche entrée
	$(elt).find(".item input").live("keyup", function(e) {
		if(e.keyCode == 13) {
			$(this).trigger("blur");
		}
	});
}

Explications :

Ajout d’éléments HTML

On ajoute le « markup » nécessaire pour nos actions :

  1. On englobe le texte de l’élément avec un <span>
  2. On ajoute juste après un <span> pour afficher la position
  3. Et enfin un dernier <span> pour notre bouton d’action « check« 

Accomplissement d’une tâche

Au clic sur le <span> « check« , on alterne la classe du <li> grâce à la méthode toggleClass().

Ensuite, en fonction de la tâche est accomplie (i.e si la classe « bought » est présente), on définit la bonne classe pour l’élément (« checked » correspond à une tâche finie (barrée), et « unchecked » l’inverse).

Achèvement d'une tâche

Renommer l’élément

Lorsque l’on double clique sur le texte de l’élément, on récupère sa valeur, on la remplace par un champ de saisie avec comme par valeur par défaut le texte de l’élément, sélectionné automatiquement.

Renommer un élément

Ensuite, lorsque l’on quitte la zone du champ de saisie (méthode « blur()« ),  ou lorsque l’on valide par la touche <entrée>, on récupère la nouvelle valeur que l’on attribue à notre élément.

Interopérabilité

L’application a été testée sous Firefox 3.6, Chrome (PC), Safari, IE8.

A noter sous IE7, un léger problème de disparition des deux <span> Position et Bouton lors du déplacement d’un élément. Si par hasard vous arrivez à le corriger, n’hésitez pas à laisser un message 😉

Interaction avec une base de données

Pour aller plus loin et sauvegarder les données triées dans une base de données, vous pouvez suivre le très bon tutoriel de Sébastien Angot, qui se base sur cet article pour ajouter cette fonctionnalité de sauvegarde via Ajax. C’est par ici: Tutoriel: Drag and drop jQuery / AJAX.

Conclusion

Et voilà notre liste de course est terminée ! Facile non ?

Pour toute question, laissez un commentaire juste en dessous là et sinon ça se passe ici pour la Demo et là pour les Sources.

65 commentaires

1 2 3 4
  1. tazkeox

    En plein développement sur un projet en base javascript et à la découverte de jQuery je tombe sur ce post au moment même où je cherchais un système de drag & drop vers la corbeille, awesome !

    Par contre je me demandais si cela marchait pour des balises « div » et non « li » et avec les axes x + y ?

    Merci beaucoup et félicitation pour l’article 😉

  2. Guillaume

    Ravi que ce tutoriel puisse t’aider !

    Pour répondre à ta question, j’ai utilisé une liste, car dans mon exemple c’était pertinent, notamment pour trier les éléments, et puis parce qu’une liste de courses, ben c’est une liste ! 🙂

    Si tu ne veux qu’utiliser du drag n drop d’éléments, tu n’es pas obligé d’utiliser le composant sortable (qui ne s’utilise qu’avec des listes a priori), mais simplement les composants draggable et droppable sur des div.

    Bon développement !

  3. lastico31

    merci pour ce Tutoriel magnifique, je viens juste de prendre en main le drag and drop. J’ai une légère question qui te semblera stupide peut être, mais est-ce que l’on peut interagir avec une bdd mysql, pour stoker les listes de course ?

  4. Michel

    joli script !

    petite erreur en fin de première ligne de la fonction addControls : remplacer : par :

  5. Michel

    oups !
    le code n’apparait pas (of course)

    il faut lire :

    « remplacer : par  » (sans les espaces)

  6. Michel

    bon, j’essaie encore :

    remplacer item par span

  7. Pitchounette

    J’ai repris cette démo mais j’ai remplacé le texte par des images et mes images ne s’affichent pas. Je ne comprend pas pourquoi :s

  8. Nostalgeek

    Bonjours, je viens de voir ton tuto qui est plutôt bien foutu 😉

    Voyant qu’il ne date pas de trop longtemps je me permet de te poser une question.

    Comment initialise tu ton draggable après l’append des ?
    Serai-ce par ce qu’ils sont en child des ? si Oui aurais-tu une solution à me proposer ? 🙂

    Bien cordialement.

    La geekologie est une science 😀

  9. Nostalgeek

    L’append des li*
    child des ul*

    Usage de balises dans un commentaire fatale pour l’affichage 🙂

  10. Gui
  11. Nostalgeek

    D’accord mon probleme n’est pas liée au sortable.

    Je n’utilise que le draggable(faire un effet de fenetre),
    je génère donc mes div avec un append et du contenu en ajax.

    Lors de l’ajout de ma div avec class draggable rien ne ce pass,
    mais lors d’un append d’une div déjà existante avec la classe draggable cela marche a la perfection si ce n’ai que je suis limité a une div 🙁

    Merci de m’avoir répondu aussi rapidement.

    Cordialement NostalGeek.

  12. Gui
  13. Nostalgeek

    Je te met un petit bout du projet en ligne

    http://adthev.fr/virtual_PC/index2.html

  14. bronson

    Bonjour je découvre à la fois jQuery & ce tuto remarquablement bien documenté et très pratique comme base.

    J’ai une question :
    Je veux ajouter au li du sortable un input text que je veux pouvoir modifier comme le texte du li, je cherche depuis un bout de temps mais je patauge;
    en fait je voudrais que tous les éléments du sortable soit Bouteille de vin rouge
    J’ai bien compris pour modifier le texte du li mais pour l’input comment faire ?

    D’avance merci

  15. DeMesmaeker

    Non, je ne signerai pas vos contrats : le drag&drop ne tourne pas sur IE6 🙂
    Oui, c’est peut-être dépassé mais c’est présent dans le contrat.

    Flûte, v’là Gaston, j’me sauve….

  16. Gui
  17. DeMesmaeker

    Oui, Oui, je suis d’accord, rien n’était indiqué pour IE6. Mais si des lecteurs passent sur votre blog et cherchent cette information, ils sauront le résultat avant d’avoir testé (gain de temps).

    De plus, bravo pour votre source donné en téléchargement libre (par libre, j’entends surtout un accès disponible par simple clic). D’ailleurs, libre de droits ? (licence spécifiée pour JQuery et son plugin JQuery-UI mais la partie ShoppingList ?)

    Et sauf que… ça serait super que dans la page index.html, il y ait un petit pied-de-page avec un lien vers ce billet de votre blog. Juste histoire que cela vous fasse un peu de pub et que pour les lecteurs, on puisse montrer ce source à d’autres gens qui viendront lire vos recommandations sur ce coin-ci de la Toile (juste en cliquant sur un lien pour venir ici).

    Vous allez me dire que c’est « peanut » et vous auriez raison (on peut le faire). Après, j’dis ça, j’dis rien.

    Et pour vos soucis au boulot, engagez un Gaston: vous allez voir que le IE6 ne restera pas longtemps en sa compagnie. D’ailleurs, vous ne voulez pas signer un contrat avec moi ? J’achète vos inventions, Javascript ou pas, 1 centime pièce ^^ Bon, d’accord, je vais retourner tenter auprès de Fantasio.

    Merci pour vos réponses. Bonne continuation.

  18. partir-en-vtt

    Bonjour,

    je suis tombé sur votre site par hasard. Tout d’abord excusez moi pour ce petit déterrage mais cette démo fait exactement ce que je souhaiterais avoir. En effet, je souhaite pouvoir gérer des catégories (et plus tard des sous catégories) dynamiquement. Le seul souci réside dans le fait de savoir s’il est possible d’aller modifier une base mysql une fois les groupe ajoutés et positionnés et/ou supprimés.

    Par avance merci.

  19. seb

    Pas encore testé mais ça va bien m’aider, l’exemple est exactement ce que je voulais. En fait j’ai pas la partie drag n’ drop mais j’ai fais le reste (ajouter/editer/supprimer/cocher + le stockage avec le stockage persistant du navigateur).

    Good job 🙂

  20. AdelanteWeb

    Excellent article pour se familiariser avec le drag & drop !! J’apprécie particulièrement la soin apporté à l’exemple, au code comme au commentaires et au design!!

    Félicitations!

1 2 3 4

Laisser une réponse


Post shadow