xulfr.org

8.7 Exemple Drag and Drop

Écrit par Neil Deakin. Traduit par Laurent Jouanneau (11/11/2004).
Page originale : http://www.xulplanet.com/tutorials/xultu/dragex.html xulplanet.com

Un exemple de l'implémentation du glisser-déposer est montré dans cette section.

Glisser-déposer des éléments

Ici, nous créérons un simple panneau où des items peuvent y être glisser-déposer à partir d'une palette. L'utilisateur peut cliquer sur l'un des nombreux éléments XUL de la palette et les glisser au dessus d'un élément stack pour créer un élément d'un type particulier.

Tout d'abord, nous ajouterons les scripts du conteneur javascript :

<script src="chrome://global/content/nsDragAndDrop.js"/>
<script src="chrome://global/content/nsTransferable.js"/>

<script src="dragboard.js"/>

Un script supplémentaire dragboard.js est inclus et contiendra le code que nous allons écrire nous-même.

Le panneau sera créé en utilisant un élément stack. Nous utiliserons quelques propriétés de style pour spécifier la largeur et la hauteur de la pile. Une taille maximum est aussi spécifiée, ainsi elle ne sera pas redimensionnée lorsque de nouveaux éléments seront déposés dessus.

Le panneau devra répondre à l'évènement dragdrop, en créant un élément lorsque l'utilisateur en déposera un dessus.

<stack id="board"
               style="width:300px; height: 300px; max-width: 300px; max-height: 300px"
  ondragover="nsDragAndDrop.dragOver(event,boardObserver)"
  ondragdrop="nsDragAndDrop.drop(event,boardObserver)">
</stack>

Le panneau a juste besoin de répondre aux évènements dragdrop et dragover. Nous ajouterons un observateur boardObserver dans le fichier dragboard.js dans un moment.

Ensuite, une palette sera ajoutée sur le coté droit de la fenêtre. Elle contiendra trois boutons, un pour créer des nouveaux boutons, un pour créer des cases à cocher, et un autre pour créer des champs de saisie. Ces boutons répondront à l'évènement draggesture et débuteront un glisser-déposer.

<vbox>

<button label="Button"
        elem="button" ondraggesture="nsDragAndDrop.startDrag(event,listObserver)"/>

<button label="Check Box"
        elem="checkbox" ondraggesture="nsDragAndDrop.startDrag(event,listObserver)"/>
<button label="Text Box"
        elem="textbox" ondraggesture="nsDragAndDrop.startDrag(event,listObserver)"/>
</vbox>

L'objet nsDragAndDrop sera appelé pour faire la plupart du travail. Nous créerons un observateur listObserver qui définiera la donnée à transférer. Notez que chaque bouton ici a un attribut supplémentaire elem. C'est un attribut inventé. XUL ne le reconnait pas et l'ignorera, mais nous pourrons toujours le récupérer avec la fonction DOM getAttribute. Nous avons besoin de ça pour savoir quel est le type d'élément à créer lors du glisser-déposer.

Ensuite nous définirons deux observateurs. Premièrement, listObserver qui a besoin d'une fonction pour gérer le démarrage du glisser.

var listObserver = {
  onDragStart: function (evt,transferData,action){
    var txt=evt.target.getAttribute("elem");
    transferData.data=new TransferData();
    transferData.data.addDataForFlavour("text/unicode",txt);
  }
};

Une seule fonction a été définie, onDragStart, et elle sera appelée par l'objet nsDragAndDrop quand cela sera nécéssaire. La fonction ajoute la donnée à transférer, à l'objet transferData. L'attribut elem est récupéré à partir de la cible de l'évènement du glisser-déposer. La cible sera l'élèment sur lequel le glisser-déposer a commencé. Nous utiliserons la valeur de cet attribut comme donnée pour le glisser.

L'objet boardObserver aura besoin de trois fonctions, getSupportedFlavours, onDragOver, et onDrop. La fonction onDrop récupèrera la donnée à partir de la session du glisser-déposer et créera un nouvel élement du type approprié.

var boardObserver = {
  getSupportedFlavours : function () {
    var flavours = new FlavourSet();
    flavours.appendFlavour("text/unicode");
    return flavours;
  },
  onDragOver: function (evt,flavour,session){},
  onDrop: function (evt,dropdata,session){
    if (dropdata.data!=""){
      var elem=document.createElement(dropdata.data);
      evt.target.appendChild(elem);
      elem.setAttribute("left",""+evt.pageX);
      elem.setAttribute("top",""+evt.pageY);
      elem.setAttribute("label",dropdata.data);
    }
  }
};

La fonction getSupportedFlavours a seulement besoin de retourner une liste de type que la pile peut accepter lors de la dépose. Dans notre cas, elle accepte juste du texte. Nous n'avons pas besoin de faire quelque chose de spécial pour la fonction onDragOver, ainsi aucun code ne sera ajouté dans son contenu.

Le gestionnaire onDrop utilise tout d'abord la fonction createElement pour créer un nouvel élément du type stocké dans la session. Ensuite, appendChild est appelée pour ajouter un nouvel élément à la pile, qui est la cible de l'évènement. Enfin, nous ajoutons quelques attributs à ce nouvel élément.

La position des éléments dans la pile est déterminée par les attributs left et top. Les valeurs des propriétés pageX et pageY contiennent les coordonnées du pointeur de la souris sur la fenêtre lorsque la dépose a lieu. Cela nous permet de placer le nouvel élément à la même position que la souris quand le bouton a été relaché. Ce n'est pas tout a fait le bon moyen de faire cela puisque nous devons en fait calculer les coordonnées de l'évènement relativement à la pile. Mais cela fonctionne ici parce que le panneau est dans le coin en haut à gauche de la fenêtre.

L'attribut label est défini avec la donnée issue du glisser-déposer, ainsi le bouton a un libellé par défaut.

Cet exemple est assez simple. Un changement possible est d'utiliser un type personnalisé pour les données plutôt que du texte. Le problème avec l'utilisation du texte est que si le texte provenant d'un glisser-déposer externe est le mot button, un bouton sera créé sur le panneau. Un type personnalisé signifie que le panneau acceptera uniquement les glisser-déposer en provenance de la palette.

Le code final est montré en dessous :

Exemple 8.7.1 : Source

<window title="Composant à déplacer" id="test-window"
  orient="horizontal"
  xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">

<script src="chrome://global/content/nsDragAndDrop.js"/>
<script src="chrome://global/content/nsTransferable.js"/>
<script src="dragboard.js"/>

<stack id="board"
       style="width:300px; height: 300px; max-width: 300px; max-height: 300px"
  ondragover="nsDragAndDrop.dragOver(event,boardObserver)"
  ondragdrop="nsDragAndDrop.drop(event,boardObserver)">
</stack>

<vbox>

<button label="Bouton"
        elem="button" ondraggesture="nsDragAndDrop.startDrag(event,listObserver)"/>
<button label="Case à cocher"
        elem="checkbox" ondraggesture="nsDragAndDrop.startDrag(event,listObserver)"/>
<button label="Zone de saisie"
        elem="textbox" ondraggesture="nsDragAndDrop.startDrag(event,listObserver)"/>
</vbox>

</window>

Exemple 8.7.2 : Source

var listObserver = {
  onDragStart: function (evt,transferData,action){
    var txt=evt.target.getAttribute("elem");
    transferData.data=new TransferData();
    transferData.data.addDataForFlavour("text/unicode",txt);
  }
};

var boardObserver = {
  getSupportedFlavours : function () {
    var flavours = new FlavourSet();
    flavours.appendFlavour("text/unicode");
    return flavours;
  },
  onDragOver: function (evt,flavour,session){},
  onDrop: function (evt,dropdata,session){
    if (dropdata.data!=""){
      var elem=document.createElement(dropdata.data);
      evt.target.appendChild(elem);
      elem.setAttribute("left",""+evt.pageX);
      elem.setAttribute("top",""+evt.pageY);
      elem.setAttribute("label",dropdata.data);
    }
  }
};

Dans la section suivante, nous verrons l'utilisation des feuilles de styles avec les fichiers XUL.