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. saeed

    est tres bien explique merci

  2. ziba

    I am very glad to comment on this site with the excellent content of your site

  3. nexvan

    Good article thanks for sharing

1 2 3 4

Laisser une réponse à ziba


Post shadow