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.

57 commentaires

1 2 3
  1. Jeremy

    Super tuto… très utile mais j’ai un petit soucis avec la selection de texte sur Chrome.

    Dans le js il y a :
    // Empêcher la sélection des éléments à la sourirs (meilleure gestion du drag & drop)
    var _preventDefault = function(evt) { evt.preventDefault(); };
    $(« li »).bind(« dragstart », _preventDefault).bind(« selectstart », _preventDefault);

    Fonctionne très bien sur Firefox mais pas sur Chrome.

    Une idée?

    Merci :-)

    • Gui
  2. Micka

    Bonjour, merci beaucoup pour ce tuto.
    Je voudrais savoir si il serait possible de remplacer l’ajout d’un mot par une liste déroulante ?

    Merci d’avance

  3. Macbernie

    Merci Gui, la partie « Ordonner les éléments » c’est exactement ce que je cherchais :]

  4. Henrion

    Bonjour,

    Tout d’abord merci pour ce tutoriel il me sert très bien.

    j’ai cependant un petit soucis quand je tente de recupérer le texy à l’intérieur de mon span (exemple ici: le texte milk ou sugar), je me retrouve bien avec mon texte mais avec d’énorme blanc entre mon texte pure, après recherche le problème viendrais du jquery-ui-1.8.custom.min.js quelqu’un aurait t’il une sugestion

  5. tybo

    Jettez un coup d’oeil à cette manip :
    1) Tout cocher
    2) Jeter le « Sugar »
    3) Observer les numéros sur la droite
    4) Ajouter « Kiwi »
    5) Observer les numéros sur la droite
    6) Remonter « Kiwi » en 2nd place
    7) Observer les numéros sur la droite
    8) C’est tout

  6. nico

    je souhaite faire marche votre scripte sur tablette numerique mais le deplacement de mots ne marche pas avec le doigt

  7. Laura-Lie

    Salut!

    Tuto vraiment tip-top, j’ai même réussi à le combiner à une base de donnée (héhé :p)

    Par contre, là où je coince un peu, c’est avec les listes imbriquées…
    En fait quand on a cette ligne

    $(elt).html(«  »+$(elt).text()+ » »);

    Cela reprend le texte entre les balises mais aussi la sous liste associée… ça ne fait plus qu’une ligne…
    J’ai eu beau retourner le code je n’ai pas réussi!

    Comment dissocier une sous-liste de sa liste parente?

    (Désolée, je sais que le sujet date un peu…)

  8. Simon

    Bonjours, j’aimerais savoir comment on peut faire en sorte pour que ça soit coché par défaut ?

  9. Bast

    Pas mal mais c’est bugé.
    Dès que tu insères une ligne :
    Si tu as double cliques sur la ligne et que tu ressors du focus : OK
    Si tu déplaces la ligne puis que tu double cliques puis que tu ressors du focus : KO, l’input reste affiché.

  10. 銆愭婧栧彇浠樸偟銉笺儞銈广€慔ITACHI(鏃ョ珛)銆愭礂婵Ы鑷嫊銇婃巸闄ゆ鑳姐兓銉掋兗銈裤兗涔剧嚗姗熻兘浠樸€?娲楁刊涔剧嚗姗?銆屻儞銉笺儓銈︺偐銉冦偡銉ャ€嶏紙娲楁刊10.0kg/

    リーダー、優れたのもの、私のフィードグーグルニュースにブログ私はちょうどこれを追加しました。 することはできません十分に得る!

  11. 銈枫儯銉笺儣 AQUOS 銈偗銈偣LC-32R5W [32V鍨?鍦颁笂?BS?110搴S銉囥偢銈裤儷銉忋偆銉撱偢銉с兂娑叉櫠銉嗐儸銉?LED AQUOS 銈偗銈偣 銉栥儷銉笺儸銈ゃ儸銈炽兗銉€銉煎唴钄?銉涖儻銈ゃ儓绯

    そこヘイヘイ|あなたは使用して作業しているどのブログのプラットフォームには、心共有する旨でしょうか?私は今すぐに、近い将来にが、私はハード抱えている私自身のブログを開始するために BlogEngine間に選択を時間を/ WordPressの/ b2evolutionのとDrupalの。異なる、最もブログをようで、私が何かを探しています完全にユニークあなた ので、私が尋ねる理由がある。 PS謝罪というのオフトピックが、私は聞いていた!

1 2 3

Laisser une réponse


Post shadow