Utiliser plusieurs versions de jQuery simultanément

Bonjour,

Aujourd’hui encore, beaucoup de projets utilisent jQuery.

Malheureusement, jQuery ne garde pas une compatibilité ascendente, on peut donc avoir des projets qui ne marchent qu’avec une version précise.

Bien sûr, si j’en parle, c’est que j’en ai fais les frais.

Eh bien, j’ai du utiliser 2 composants qui dépendaient de 2 versions incompatibles de jQuery.

Après quelques recherches, j’ai trouvé la solution :

On peut renommer sur pour une des 2 la variable ‘$’.

var $j = jQuery.noConflict(true);
$j(function() {
   $j('.datepicker').datepicker({
      onSelect: function() {
         update(this);
      }
   });
});

La commande noConflict, ordonne à jQuery de ne plus utiliser l’alias $.

C’est la dernière instance chargée qui est effacée. On récupère l’instance dans une variable que l’on peut utiliser en place de ‘$’ ou ‘jQuery’.

La première instance est celle qui peut utiliser ‘$’ et ‘jQuery’.

Avec cette méthode, on peut utiliser autant de versions de jQuery que l’on veut. Il faut juste faire attention à l’ordre de chargement des scripts.

Faire un watcher en javascript

Introduction

Bonjour,

Récemment, j’ai vu un tutoriel sur vue.js.
Il y a un concept qui m’a plu, c’est celui du binding.

En gros, on attache une variable à un contenu puis le contenu se met a jour automatiquement quand la variable change. Ce n’est pas a double sens, par exemple si le contenu est un input, la variable ne change pas quand on modifie le champ.

Je voulais recréer ce comportement en javascript et j’ai décidé d’appeler ça un watcher.

Implémentation

La syntaxe doit être simple et élégante. Voici comment j’aimerais utiliser le watcher :

<!DOCTYPE html>
<html>
 <head>
 <title>watcher</title>
 </head>
 <body>
 Exemple d'un watcher sur une variable.<br>
Tout ce qui est écrit dans ce champ sera répercuté immédiatement dans les instances dessous.<br>
 <input oninput="toto.valeur=this.value;"><br>
 Voici une instance : <span bind="toto"></span><br>
 Encore une : <span bind="toto"></span><br>
 Et une autre : <span bind="toto"></span><br>
 Allez, une dernière : <span bind="toto"></span>
 </body>
</html>

L’input sert à modifier la variable toto. un attribut bind= »toto » attachera l’élement à la variable toto.

Pour que tout se passe automatiquement, il faut utiliser get et set. Malheureusement, cela ne marche qu’a l’intérieur des objets. Il faudra donc que toto soit un objet.

Voici le script pour effectuer la magie :
On peut copier directement ce code avant </body>

 <script>
 var toto = {
 watcher : [],
 _valeur : "",
 get valeur () {
 return this._valeur;
 },
 set valeur(argValeur) {
 this._valeur = argValeur;
 for (i=0 ; i < this.watcher.length ; i++) {
 this.watcher[i].innerHTML = argValeur;
 this.watcher[i].value = argValeur;
 }
 },
 watch : function (argElement) {
 toto.watcher.push(argElement);
 }
 };
 elements = document.querySelectorAll('[bind=toto]');
 for (i=0 ; i < elements.length ; i++) {
 toto.watch(elements[i]);
 }
 </script>

Sans utiliser de champ input, on peut modifier la variable directement en javascript de la façon suivante :

toto.valeur="coucou";

Attention !

La variable toto.valeur n’est pas sanitisée, il ne faut donc pas l’utiliser directement comme ci-dessus.

Conclusion

C’était juste pour reproduire un comportement qui paraît « magique » au premier abord et d’en comprendre la mécanique.

 

Sanitiser une chaine en javascript

Bonjour,

Je veux aujourd’hui partager une soluton simple à un problème qui se posait à moi assez souvent.

Comment afficher une chaine en javascript dans un élément html ?

C’est simple, vous me direz :

<div id="cible">
</div>
<script>
   String maChaine='Salut tout le monde !';
   cible = document.getElementById('cible');
   cible.innerHTML = maChaine;
</script>

Ca marche dans certains cas, mais si j’ai :

<div id="cible">
</div>
<script>
   String maChaine='Salut <i>tout</i> le monde !';
   cible = document.getElementById('cible');
   cible.innerHTML = maChaine;
</script>

On a :

« Salut tout le monde ! »

Et ce n’est pas une fonctionnalité trop cool.

J’aimerais avoir :

« Salut <i>tout</i> le monde ! »

Et oui, il faut échapper la chaine pour afficher toute la chaine sans rien interpréter. C’est ce que les « vrais » veulent.

Les vrais quoi ? Les vrais programmeurs, les pros quoi ! Adhérez à cette idée si vous ne voulez pas passer pour un amateur.

Alors, comment on fait ?

On peut installer une librairie qui fait ça, et on va ajouter du bloat à une application qui n’en a vraiement pas besoin.

On peut aussi le faire tout seul, ça prend 5min et ça durera pour toujours, ou jusqu’à ce que javascript propose un équivalent.

On va ajouter une méthode à String qu’on appellera toText()

String.prototype.toText = function (){return this.replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;').replace('"', '&quot;');}

On utilisera toText() à chaque fois qu’on veut écrire notre chaine.

cible.innerHTML = maChaine.toText();

Le tour est joué.

J’ai échappé le strict minimum, soit 4 caractères. Libre à chacun d’en ajouter.

Drag and drop en html et javascript

Bonjour,

Aujourd’hui j’ai du implémenter un outil pour réorganiser une arborescence.

Il y a donc une imbrication de <ul> (et les <li> qui vont avec) et l’utilisateur pourra bouger n’importe quelle partie de l’arborescence.

Mise en place basique

Voici un exemple d’arborescence en html :

<!DOCTYPE html>
<html>
    <head>
        <title>Drag and drop</title>
    </head>
    <body>
        <ul>
            <li>Element 1</li>
            <li>Element 2</li>
            <li>Element 3
                <ul>
                    <li>Element 4</li>
                </ul>
            </li>
        </ul>
    </body>
</html>

Il faut rendre les <li> draggable avec l’attribut draggable= »true »

Ensuite faire une balise <script>, de préférence en fin de page, avant </body>.

On y mettra les fonctions pour le drag and drop (drag pour ondragstart et drop pour ondrop)

function drag(ev) {
}
function drop(ev) {
    ev.preventDefault();
}
function dragOver(ev) {
    ev.preventDefault();
}

Ceci ne suffit pas, il faut affecter ces fonctions aux évènements des éléments concernés. Mais on va faire ça bien, c’est à dire dans une fonction init qui se lancera dès que le dom est prêt.

function init() {
    // attache drop
    elements = document.querySelectorAll('li');
    for (i = 0 ; i < elements.length ; i++) {
        elements[i].addEventListener('drop', drop);
        elements[i].addEventListener('dragover', dragOver);
    }
    // attache drag
    elements = document.querySelectorAll('li[draggable]');
    for (i = 0 ; i < elements.length ; i++) {
        elements[i].addEventListener('dragstart', drag);
    }
}
document.addEventListener('DOMContentLoaded', init);

J’attache drop avant drag pour du chipotage, je donnerais la réponse en commentaire si ça intéresse quelqu’un. En plus, j’attache drag seulement aux éléments draggable et drop à tous les li, juste pour montrer que c’est pas forcément en double sens.

On y est presque, il faut maintenant détacher l’élément source et l’attacher dans l’élément cible. Pour cela, il faut générer un id dans les éléments draggable si ils en ont pas déjà un.

// attache drag
elements = document.querySelectorAll('li[draggable]');
for (i = 0 ; i < elements.length ; i++) {
    id = elements[i].getAttribute('id');
    if (id == null || id == '') {
        elements[i].setAttribute('id', 'draggable'+i);
    }
    elements[i].addEventListener('dragstart', drag);
}

Maintenant que tout est en place, on peut donner les actions de déplacement dans le dom.

function drag(ev) {
    element = ev.target;
    ev.dataTransfer.setData('text', element.getAttribute('id'));
}
function drop(ev) {
    ev.preventDefault();
    data = ev.dataTransfer.getData('text');
    element = document.getElementById(data);
}

Ca marche mais une fois ou deux, ce code, il y a plein de bogue !

Amélioration et hierarchie

On va voir ce qui se passe et corriger. On met directement un <li> dans un autre <li>, il faudrait construire un <ul> et mettre notre <li> dedans, comme ça on aura corrigé le problème et on peut avoir une hiérarchie.

function drop(ev) {
    ev.preventDefault();
    data = ev.dataTransfer.getData('text');
    element = document.createElement('ul');
        element2 = document.getElementById(data);
        parent = element2.parentElement;
        element.appendChild(element2);
    ev.target.appendChild(element);
}

Ca marche bien maintenant. Mais quand je lâche un élément sur lui même, il disparaît.

Empêcher la source d’être la cible

Ce petit problème est en fait la raison d’être de cet article.

Je vais d’abord exposer la méthode qui m’est naturellement venue à l’esprit :

Dans drag, on annule les évènements drop pour l’élément choisi. Puis dans drop, on remet les évènements pour tout le monde.

function drag(ev) {
    element = ev.target;
    ev.dataTransfer.setData('text', element.getAttribute('id'));
    // annule drop pour la cible et ses enfants
    element = ev.target;
    elements = element.querySelectorAll('li');
    element.removeEventListener('drop', drop);
    element.removeEventListener('dragover', dragover);
    for (i = 0 ; i < elements.length ; i++) {
        elements[i].removeEventListener('drop', drop);
        elements[i].removeEventListener('dragover', dragover);
    }
}
function drop(ev) {
    ev.preventDefault();
    data = ev.dataTransfer.getData('text');
    element = document.createElement('ul');
    element2 = document.getElementById(data);
    // remet drop pour la cible et ses enfants
    elements = element2.querySelectorAll('li');
    element2.addEventListener('drop', drop);
    element2.addEventListener('dragover', dragover);
    for (i = 0 ; i < elements.length ; i++) {
        elements[i].addEventListener('drop', drop);
        elements[i].addEventListener('dragover', dragover);
    }
    parent = element2.parentElement;
    element.appendChild(element2);
    ev.target.appendChild(element);
}

Ca marche quand on se trompe pas, mais des fois on drop là ou ça ne lance pas drop.

Il faut donc tout faire dans drag. Dès qu’on drag (ouais, je vais me passer de faire une blague ici), on reset les drop (on met les drop à tout le monde) et on empêche l’élément et ses descendants de drop.

function drag(ev) {
    element = ev.target;
    ev.dataTransfer.setData('text', element.getAttribute('id'));
    // remet drop pour tout le monde
    elements = document.querySelectorAll('li');
    for (i = 0 ; i < elements.length ; i++) {
        elements[i].addEventListener('drop', drop);
        elements[i].addEventListener('dragover', dragover);
    }
    // annule drop pour la cible et ses enfants
    element = ev.target;
    elements = element.querySelectorAll('li');
    element.removeEventListener('drop', drop);
    element.removeEventListener('dragover', dragover);
    for (i = 0 ; i < elements.length ; i++) {
        elements[i].removeEventListener('drop', drop);
        elements[i].removeEventListener('dragover', dragover);
    }
}
function drop(ev) {
    ev.preventDefault();
    data = ev.dataTransfer.getData('text');
    element = document.createElement('ul');
    element2 = document.getElementById(data);
    parent = element2.parentElement;
    element.appendChild(element2);
    ev.target.appendChild(element);
}

C’est déjà mieux, mais quand on joue trop longtemps, il y a des fois où on peut faire un drag and drop sur l’élément ou son descendant, et ça fait planter.

J’ai essayé de me dépatouiller avec les events, mais en vain. J’ai donc décidé de mettre un drapeau sur les éléments qui ne doivent pas accepter le drop.

function drag(ev) {
    element = ev.target;
    ev.dataTransfer.setData('text', element.getAttribute('id'));
    // remet drop pour tout le monde
    elements = document.querySelectorAll('li');
    for (i = 0 ; i < elements.length ; i++) {
        elements[i].removeAttribute('data-notarget');
    }
    // annule drop pour la cible et ses enfants
    element = ev.target;
    elements = element.querySelectorAll('li');
    element.setAttribute('data-notarget', 'true');
    for (i = 0 ; i < elements.length ; i++) {
        elements[i].setAttribute('data-notarget', 'true');
    }
}
function drop(ev) {
    isTarget = ev.target.getAttribute('data-notarget');
    if (isTarget == null || isTarget == '') {
        ev.preventDefault();
        data = ev.dataTransfer.getData('text');
        element = document.createElement('ul');
        element2 = document.getElementById(data);
        parent = element2.parentElement;
        element.appendChild(element2);
        ev.target.appendChild(element);
    }
}
function dragover(ev) {
    isTarget = ev.target.getAttribute('data-notarget');
    if (isTarget == null || isTarget == '') {
        ev.preventDefault();
    }
}

On a une solution plus robuste, mais il y a quand même quelque plantages.

Après une multitude de tests, j’ai réussit à trouver la cause : l’élément <ul> est aussi droppable et execute drop quand on vise bien dessus (sur la puce par exemple).

Je considère que c’est un bug et pas une feature, car si j’aurais voulu que <ul> soit droppable, je lui aurais affecté manuellement cet évènement.

Donc, il faut inhiber le drop sur <ul>

function drop(ev) {
    isTarget = ev.target.getAttribute('data-notarget');
    if (isTarget == null || isTarget == '') {
        ev.preventDefault();
        data = ev.dataTransfer.getData('text');
        element = document.createElement('ul');
        element.setAttribute('data-notarget', 'true');
        element2 = document.getElementById(data);
        parent = element2.parentElement;
        element.appendChild(element2);
        ev.target.appendChild(element);
    }
}
function init() {
    // exclusion de ul
    elements = document.querySelectorAll('ul');
    for (i = 0 ; i < elements.length ; i++) {
        elements[i].setAttribute('data-notarget', 'true');
    }
    // attache drop
    elements = document.querySelectorAll('li');
    for (i = 0 ; i < elements.length ; i++) {
        elements[i].addEventListener('drop', drop);
        elements[i].addEventListener('dragover', dragover);
    }
    // attache drag
    elements = document.querySelectorAll('li[draggable]');
    for (i = 0 ; i < elements.length ; i++) {
        id = elements[i].getAttribute('id');
        if (id == null || id == '') {
            elements[i].setAttribute('id', 'draggable'+i);
        }
        elements[i].addEventListener('dragstart', drag);
    }
}

Maintenant, c’est du code robuste, qui ne plante plus. Mais si je change <ul> par <ol> de partout, j’ai que des 1 qui se suivent. Pourquoi ?

C’est parcequ’on ne se pose pas la question de savoir si il existe un <ul> (ou <ol> quand on a fait le changement). On fabrique <ul>, on remplit, puis on ajoute au dom. On ne vérifie pas non plus si le <ul> qu’on laisse derrière est vide. Il faut donc nettoyer tout ça.

Nettoyage du dom et amélioration de la hiérarchie

En plus du nettoyage, j’ai un petit souci avec la hierarchie. Les éléments ne peuvent pas passer en premier ordre. La solution que j’ai trouvé, c’est de faire un <ul> qui permettra de déposer n’importe quel élément, mais qui n’est pas draggable.

Pour le nettoyage du dom, tout est dit un peu plus haut.

<ol>
    <li>D&eacute;poser ici les &eacute;l&eacute;ments de plus haut niveau
        <ol>
            <li draggable="true">Element 1</li>
            <li draggable="true">Element 2</li>
            <li draggable="true">Element 3
                <ol>
                    <li draggable="true">Element 4</li>
                </ol>
            </li>
        </ol>
    </li>
</ol>
function drop(ev) {
    isTarget = ev.target.getAttribute('data-notarget');
    if (isTarget == null || isTarget == '') {
        ev.preventDefault();
        data = ev.dataTransfer.getData('text');
        elements = ev.target.getElementsByTagName('ol');
        element = null;
        if (elements.length != 0) {
            element = elements[0];
        } else {
            element = document.createElement('ol');
            element.setAttribute('data-notarget', 'true');
        }
        element2 = document.getElementById(data);
        parent = element2.parentElement;
        element.appendChild(element2);
        ev.target.appendChild(element);
        if (parent.children.length == 0) {
            parent.remove();
        }
    }
}

Voilà, on a un drag and drop qui est fonctionnel et qui est propre.

Peaufinage

Comment on peut faire un effet d’inversion vidéo sur les éléments droppables ?

C’est facile, il suffit de savoir qu’il y a un évènement supplémentaire qui est dragleave (et pas dragout ça aurait été trop simple).

Je ne vais pas donner d’exemple qui fonctionne de partout car il faut gérer les bug de firefox sur dragleave qui donne pas le <li> mais le texte dedans alors que dragover marche bien !

<!DOCTYPE html>
<html>
 <head>
 <title>Drag and drop</title>
 </head>
 <body>
 <ol>
 <li>D&eacute;poser ici les &eacute;l&eacute;ments de plus haut niveau
 <ol>
 <li draggable="true">Element 1</li>
 <li draggable="true">Element 2</li>
 <li draggable="true">Element 3
 <ol>
 <li draggable="true">Element 4</li>
 </ol>
 </li>
 </ol>
 </li>
 </ol>
 <script>
 function drag(ev) {
 element = ev.target;
 ev.dataTransfer.setData('text', element.getAttribute('id'));
 // remet drop pour tout le monde
 elements = document.querySelectorAll('li');
 for (i = 0 ; i < elements.length ; i++) {
 elements[i].removeAttribute('data-notarget');
 }
 // annule drop pour la cible et ses enfants
 element = ev.target;
 elements = element.querySelectorAll('li');
 element.setAttribute('data-notarget', 'true');
 for (i = 0 ; i < elements.length ; i++) {
 elements[i].setAttribute('data-notarget', 'true');
 }
 ev.stopPropagation();
 }
 function drop(ev) {
 target = ev.target;
 isTarget = target.getAttribute('data-notarget');
 if (isTarget == null || isTarget == '') {
 ev.preventDefault();
 ev.target.style.backgroundColor = '#fff';
 data = ev.dataTransfer.getData('text');
 elements = ev.target.getElementsByTagName('ol');
 element.setAttribute('data-notarget', 'true');
 element = null;
 if (elements.length != 0) {
 element = elements[0];
 } else {
 element = document.createElement('ol');
 }
 element2 = document.getElementById(data);
 parent = element2.parentElement;
 element.appendChild(element2);
 ev.target.appendChild(element);
 if (parent.children.length == 0) {
 parent.remove();
 }
 }
 ev.stopPropagation();
 }
 function dragover(ev) {
 target = ev.target;
 isTarget = target.getAttribute('data-notarget');
 if (isTarget == null || isTarget == '') {
 ev.preventDefault();
 ev.target.style.backgroundColor = '#fcc';
 }
 ev.stopPropagation();
 }
 function dragleave(ev) {
 target = ev.target;
 isTarget = target.getAttribute('data-notarget');
 if (isTarget == null || isTarget == '') {
 ev.preventDefault();
 ev.target.style.backgroundColor = '#fff';
 }
 ev.stopPropagation();
 }
 function init() {
 // exclusion de ol
 elements = document.querySelectorAll('ol');
 for (i = 0 ; i < elements.length ; i++) {
 elements[i].setAttribute('data-notarget', 'true');
 }
 // drop related events
 elements = document.querySelectorAll('li');
 for (i = 0 ; i < elements.length ; i++) {
 elements[i].addEventListener('drop', drop);
 elements[i].addEventListener('dragover', dragover);
 elements[i].addEventListener('dragleave', dragleave);
 }
 // drag related events
 elements = document.querySelectorAll('li[draggable]');
 for (i = 0 ; i < elements.length ; i++) {
 id = elements[i].getAttribute('id');
 if (id == null || id == '') {
 elements[i].setAttribute('id', 'draggable'+i);
 }
 elements[i].addEventListener('dragstart', drag);
 }
 }
 document.addEventListener('DOMContentLoaded', init);
 </script>
 </body>
</html>

Ce drag and drop marche bien sur chrome, mais pas sur firefox 45.1.1 a cause de dragleave. Vu que c’est clairement un bug, je ne vais pas investiguer.