Mise en pratique

Parent Previous Next


Mise en pratique



Nous allons faire une petite mise en pratique avant de terminer ce chapitre. Notre but ici est de créer une page Web avec deux zones de drop et quelques éléments que l'on peut déplacer d'une zone à l'autre.


Afin de vous éviter de perdre du temps pour pas grand-chose, voici le code HTML à utiliser et le CSS associé :

<div class="dropper">

 

    <div class="draggable">#1</div>

    <div class="draggable">#2</div>

     

</div>

 

<div class="dropper">

     

    <div class="draggable">#3</div>

    <div class="draggable">#4</div>

     

</div>

.dropper {

    margin: 50px 10px 10px 50px;

    width: 400px;

    height: 250px;

    background-color: #555;

    border: 1px solid #111;

     

    -moz-border-radius: 10px;

    border-radius: 10px;

     

    -moz-transition: all 200ms linear;

    -webkit-transition: all 200ms linear;

    -o-transition: all 200ms linear;

    transition: all 200ms linear;

}

 

.drop_hover {

    -moz-box-shadow: 0 0 30px rgba(0, 0, 0, 0.8) inset;

    box-shadow: 0 0 30px rgba(0, 0, 0, 0.8) inset;

}

 

.draggable {

    display: inline-block;

    margin: 20px 10px 10px 20px;

    padding-top: 20px;

    width: 80px;

    height: 60px;

    color: #3D110F;

    background-color: #822520;

    border: 4px solid #3D110F;

    text-align: center;

    font-size: 2em;

    cursor: move;

     

    -moz-transition: all 200ms linear;

    -webkit-transition: all 200ms linear;

    -o-transition: all 200ms linear;

    transition: all 200ms linear;

     

    -moz-user-select: none;

    -khtml-user-select: none;

    -webkit-user-select: none;

    user-select: none;

}


Rien de bien compliqué, le code HTML est extrêmement simple et la seule chose à comprendre au niveau du CSS est que la classe .drop_hover sera appliquée à une zone de drop lorsque celle-ci sera survolée par un élément HTML déplaçable.


Alors, par où commencer ? Il nous faut, avant toute chose, une structure pour notre code. Nous avons décidé de partir sur un code basé sur cette forme :

(function() {

 

    var dndHandler = {

 

        // Cet objet est conçu pour être un namespace et va contenir les méthodes que nous allons créer pour notre système de drag & drop

 

    };

 

    // Ici se trouvera le code qui utilisera les méthodes de notre namespace « dndHandler »

 

})();


Pour commencer à exploiter notre structure, il nous faut une méthode capable de donner la possibilité aux éléments concernés d'être déplacés. Les éléments concernés sont ceux qui possèdent une classe.draggable. Afin de les paramétrer, nous allons créer une méthode applyDragEvents() dans notre objetdndHandler :

var dndHandler = {

 

    applyDragEvents: function(element) {

         

        element.draggable = true;

         

    }

 

};


Ici, notre méthode s'occupe de rendre déplaçables tous les objets qui lui seront passés en paramètres. Cependant, cela ne suffit pas pour deux raisons :


Ces deux ajouts ne sont pas bien compliqués à mettre en place :

var dndHandler = {

 

    draggedElement: null, // Propriété pointant vers l'élément en cours de déplacement

 

    applyDragEvents: function(element) {

         

        element.draggable = true;

 

        var dndHandler = this; // Cette variable est nécessaire pour que l'événement « dragstart » accède facilement au namespace « dndHandler »

 

        element.addEventListener('dragstart', function(e) {

            dndHandler.draggedElement = e.target; // On sauvegarde l'élément en cours de déplacement

            e.dataTransfer.setData('text/plain', ''); // Nécessaire pour Firefox

        }, false);

         

    }

 

};


Ainsi, nos zones de drop n'auront qu'à lire la propriété draggedElement pour savoir quel est l'élément qui a été déposé.


Passons maintenant à la création de la méthode applyDropEvents() qui, comme son nom l'indique, va se charger de gérer les événements des deux zones de drop. Nous allons commencer par gérer les deux événements les plus simples : dragover et dragleave.

var dndHandler = {

 

    // […]

 

    applyDropEvents: function(dropper) {

         

        dropper.addEventListener('dragover', function(e) {

            e.preventDefault(); // On autorise le drop d'éléments

            this.className = 'dropper drop_hover'; // Et on applique le style adéquat à notre zone de drop quand un élément la survole

        }, false);

         

        dropper.addEventListener('dragleave', function() {

            this.className = 'dropper'; // On revient au style de base lorsque l'élément quitte la zone de drop

        });

         

    }

 

};


Notre but maintenant est de gérer le drop d'éléments. Notre système doit fonctionner de la manière suivante :


En soi, ce système n'est pas bien compliqué à réaliser, voici ce que nous vous proposons comme solution :

dropper.addEventListener('drop', function(e) {

 

    var target = e.target,

        draggedElement = dndHandler.draggedElement, // Récupération de l'élément concerné

        clonedElement = draggedElement.cloneNode(true); // On créé immédiatement le clone de cet élément

     

    target.className = 'dropper'; // Application du style par défaut

     

    clonedElement = target.appendChild(clonedElement); // Ajout de l'élément cloné à la zone de drop actuelle

    dndHandler.applyDragEvents(clonedElement); // Nouvelle application des événements qui ont été perdus lors du cloneNode()

     

    draggedElement.parentNode.removeChild(draggedElement); // Suppression de l'élément d'origine

     

});


Nos deux méthodes sont maintenant terminées, il ne nous reste plus qu'à les appliquer aux éléments concernés :

(function() {

 

    var dndHandler = {

 

        // […]

 

    };

 

    var elements = document.querySelectorAll('.draggable'),

        elementsLen = elements.length;

 

    for(var i = 0 ; i < elementsLen ; i++) {

        dndHandler.applyDragEvents(elements[i]); // Application des paramètres nécessaires aux éléments déplaçables

    }

 

    var droppers = document.querySelectorAll('.dropper'),

        droppersLen = droppers.length;

 

    for(var i = 0 ; i < droppersLen ; i++) {

        dndHandler.applyDropEvents(droppers[i]); // Application des événements nécessaires aux zones de drop

    }

 

})();


Notre code est terminé, cependant il a un bug majeur que vous avez sûrement pu constater si vous avez essayé de déplacer un élément directement sur un autre élément plutôt que sur une zone de drop. Essayez par exemple de déplacer l'élément #1 sur l'élément #4, vous devriez alors voir quelque chose qui ressemble à l'image suivante.



Le code possède un bug majeur



Cela s'explique par le simple fait que l'événement drop est hérité par les éléments enfants, ce qui signifie que les éléments possédant la classe .draggable se comportent alors comme des zones de drop !


Une solution serait d'appliquer un événement drop aux éléments déplaçables refusant tout élément HTML déposé, mais cela obligerait alors l'utilisateur à déposer son élément en faisant bien attention à ne pas se retrouver au-dessus d'un élément déplaçable. Essayez donc pour voir, vous allez rapidement constater que cela peut être vraiment pénible :

applyDragEvents: function(element) {

     

    // […]

 

    element.addEventListener('drop', function(e) {

        e.stopPropagation(); // On stoppe la propagation de l'événement pour empêcher la zone de drop d'agir

    }, false);

     

},


Nous n'avions pas encore étudié la méthode stopPropagation(), car celle-ci nécessite un cas concret d'utilisation, et nous en avons justement un ici !


Cette méthode sert à stopper la propagation des événements. Souvenez-vous des phases de capture et de bouillonnement étudiées dans le chapitre sur les événements ! Dans une phase de bouillonnement, si un élément enfant possède un événement du même type qu'un de ses éléments parents, alors son événement se déclenchera en premier, puis viendra celui de l'élément parent. La méthode stopPropagation() sert à brider ce fonctionnement.


Dans le cadre d'une phase de bouillonnement, en utilisant cette méthode dans l'événement de l'élément enfant, vous empêcherez alors l'élément parent d'exécuter son événement. Dans le cadre d'une phase de capture, en utilisant cette méthode sur l'élément parent, vous empêcherez alors l'élément enfant de déclencher son événement.


La solution la plus pratique pour l'utilisateur serait donc de faire en sorte de « remonter » les éléments parents (avec parentNode) jusqu'à tomber sur une zone de drop. Cela est très simple et se fait en trois lignes de code (lignes 7 à 9) :

dropper.addEventListener('drop', function(e) {

 

    var target = e.target,

        draggedElement = dndHandler.draggedElement, // Récupération de l'élément concerné

        clonedElement = draggedElement.cloneNode(true); // On crée immédiatement le clone de cet élément

     

    while(target.className.indexOf('dropper') == -1) { // Cette boucle permet de remonter jusqu'à la zone de drop parente

        target = target.parentNode;

    }

 

    target.className = 'dropper'; // Application du style par défaut

     

    clonedElement = target.appendChild(clonedElement); // Ajout de l'élément cloné à la zone de drop actuelle

    dndHandler.applyDragEvents(clonedElement); // Nouvelle application des événements qui ont été perdus lors du cloneNode()

     

    draggedElement.parentNode.removeChild(draggedElement); // Suppression de l'élément d'origine

     

});


Si target (qui représente l'élément ayant reçu un élément déplaçable) ne possède pas la classe .dropper, alors la boucle va passer à l'élément parent et va continuer comme cela jusqu'à tomber sur une zone de drop. Vous pouvez d'ailleurs constater que cela fonctionne à merveille !


Voilà qui clôt notre mise en pratique du Drag & Drop, nous espérons qu'elle vous aura satisfait. Voici le code Javascript complet dans le cas où vous seriez un peu perdus :

(function() {

     

    var dndHandler = {

         

        draggedElement: null, // Propriété pointant vers l'élément en cours de déplacement

         

        applyDragEvents: function(element) {

             

            element.draggable = true;

 

            var dndHandler = this; // Cette variable est nécessaire pour que l'événement « dragstart » ci-dessous accède facilement au namespace « dndHandler »

             

            element.addEventListener('dragstart', function(e) {

                dndHandler.draggedElement = e.target; // On sauvegarde l'élément en cours de déplacement

                e.dataTransfer.setData('text/plain', ''); // Nécessaire pour Firefox

            }, false);

             

        },

  

        applyDropEvents: function(dropper) {

             

            dropper.addEventListener('dragover', function(e) {

                e.preventDefault(); // On autorise le drop d'éléments

                this.className = 'dropper drop_hover'; // Et on applique le style adéquat à notre zone de drop quand un élément la survole

            }, false);

             

            dropper.addEventListener('dragleave', function() {

                this.className = 'dropper'; // On revient au style de base lorsque l'élément quitte la zone de drop

            });

             

            var dndHandler = this; // Cette variable est nécessaire pour que l'événement « drop » ci-dessous accède facilement au namespace « dndHandler »

 

            dropper.addEventListener('drop', function(e) {

 

                var target = e.target,

                    draggedElement = dndHandler.draggedElement, // Récupération de l'élément concerné

                    clonedElement = draggedElement.cloneNode(true); // On créé immédiatement le clone de cet élément

                 

                while(target.className.indexOf('dropper') == -1) { // Cette boucle permet de remonter jusqu'à la zone de drop parente

                    target = target.parentNode;

                }

 

                target.className = 'dropper'; // Application du style par défaut

                 

                clonedElement = target.appendChild(clonedElement); // Ajout de l'élément cloné à la zone de drop actuelle

                dndHandler.applyDragEvents(clonedElement); // Nouvelle application des événements qui ont été perdus lors du cloneNode()

                 

                draggedElement.parentNode.removeChild(draggedElement); // Suppression de l'élément d'origine

                 

            });

             

        }

  

    };

     

    var elements = document.querySelectorAll('.draggable'),

        elementsLen = elements.length;

     

    for(var i = 0 ; i < elementsLen ; i++) {

        dndHandler.applyDragEvents(elements[i]); // Application des paramètres nécessaires aux éléments déplaçables

    }

     

    var droppers = document.querySelectorAll('.dropper'),

        droppersLen = droppers.length;

     

    for(var i = 0 ; i < droppersLen ; i++) {

        dndHandler.applyDropEvents(droppers[i]); // Application des événements nécessaires aux zones de drop

    }

 

})();


En résumé


Créé avec HelpNDoc Personal Edition: Créer des documentations web iPhone

Site à deux balles