Changements génériques sur des éléments chargés en AJAX

Sans spécialisation, ou alors clairement découplée

Le problème exposé dans ce sujet a été résolu.

Bonjour à tous !

Je cherche aujourd'hui à avoir un script aussi général que possible pour appliquer des changements JavaScript sur des éléments (ajout de WYSIWYG, de sélecteurs de date jQuery UI, etc.).

La partie en AJAX est chargée au clic sur des liens, jusque là, rien de méchant : une classe particulière pour cibler les bons liens, un attribut data-* pour y mettre un sélecteur qui dit où doit être inséré le retour de l'appel AJAX.
Maintenant, dans ce retour AJAX, j'ai un formulaire, sur lequel j'aimerais ajouter un WYSIWYG sur un textarea et des sélecteurs de date. Evidemment, comme ces éléments ne sont pas directement dans le DOM, $(document).ready(…) ne permet pas de régler le souci.

Est-ce qu'il existe un équivalent à .on(), mais qui permette d'écouter les changements du DOM et d'appliquer par exemple .datepicker() à des éléments qui seraient trouvés ? L'idée serait d'avoir quelque chose pour traiter ce que $(document).ready( function() { $('.datepicker').datepicker() }) fait pour le premier chargement de la page, mais ici pour des éléments chargés en AJAX.

J'aimerais aussi éviter autant que possible de devoir dupliquer la liste des choses qui sont effectuées tant dès que le DOM est prêt que quand il a été modifié.

J'ai bien pensé à faire une liste de fonctions et d'appeler quelque chose comme

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
$(document).ready( function() {
    $.each(list, function(i, element) {
        element.method.call(element.arguments);
    });
});

$.ajax().then( function(data) {
    // …
    $.each(list, function(i, element) {
        element.method.call(element.arguments);
    });
});

Mais ça me parait un peu bricolé.

Est-ce que quelqu'un aurait une solution plus élégante à me proposer ? Même si ça passe par une bibliothèque supplémentaire, je pense que c'est imaginable.

Merci d'avance  :)

+0 -0

level 1

tu peux déclencher programmatiquement un événement, donc pour appeler "l'ensemble des choses qui sont faites lorsque le document est prêt", il suffit de faire $(document).ready().

Si je ne considère ça que comme le niveau 1 c'est parce qu'il y a beaucoup de choses qui sont faites lorsque le document est prêt et que tu ne veux pas faire pour l'instant.

level 2

mais on peut garder l'idée et utiliser un événement personnalisé, que tu définis toi même puis que tu appelles toi même. https://learn.jquery.com/events/introduction-to-custom-events/

du coup tu n'as juste qu'à définir cet événement sur $(document) et dire :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
$(document).on("data_downloaded", function(){
$.each(list, function(i, element) {
        element.method.call(element.arguments);
    });
});

$(document).ready(function(){this.trigger("data_downloaded");});
$.ajax().then( function(data) {
    // …
    $(document).trigger("data_downloaded");
});

Punaise, je n'ai tellement pas l'habitude de générer des événements, et pourtant, c'est exactement ce qu'il me faudrait  >_<

Merci artragis !

Edit

Mieux que ça, je peux utiliser .ajaxComplete() dans mon cas. Je me suis soudainement souvenu qu'il devait y avoir un événement, et ça a l'air d'être du pur jQuery, mais disponible depuis la 1.0 et pas enlevé. C'est bien assez l'équivalent Ajax pour $(document).ready(…)  ^^

Edit 2

:-°  J'ai d'autres éléments qui sont chargés en AJAX, et pour le coup, ça me multiplie les trucs. Je vais rester aux événements "persos", je pense

Edit 3

Je ne m'en sors pas.

Actuellement, j'ai ce code :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
$(document).ready( function() {
    $('a.ajaxify').click(function(e) {
        $(this.dataset.loadTarget).load(this.href, null, function() {
            $(this.dataset.loadTarget).trigger('ajaxLoaded', {bubbling: true});
        });
        e.preventDefault();
    });
    $('form.ajaxify').submit( function(e) {
        var postData = $(this).serializeArray();
        if (!this.dataset.loadTarget) {
            $.post(this.action, postData);
        } else {
            $(this.dataset.loadTarget).load(
                this.action,
                postData,
                function(data) {
                    $(this.dataset.loadTarget).html(data);
                    $(this.dataset.loadTarget).trigger('ajaxLoaded', {bubbling: true});
                }
            );
        }
        e.preventDefault();
    });
    $(document).on('ajaxLoaded', function(e) {
        console.log('Yuk', e.type, e.namespace);
//      if (e.namespace == 'mine') {
            console.log('Yukity');
            $('.wysiwyg').wysihtml5();
            $('.datepicker').datepicker({
                autoclose : true,
                todayHighlight : true
            });
//      }
    });
});

Seulement, il semble que mon événement ne soit pas déclenché. Je peux déplacer de quelques lignes pour mettre après e.preventDefault(), mais là, il est déclenché avant que les éléments aient été chargés.

Je ne vois évidemment pas ce qui m'échappe…

Edit 4

Si je fais $(document).trigger(…), on est bon. Il me semblait qu'il y avait moyen de faire "bouillonner" un événement ? C'est pas avec {bubbling: true}, apparemment…

+0 -0

Question bête, mais pourquoi tu n'utilises pas tout bêtement la délégation d'événement pour être sûr que tous les nouveaux éléments en profitent ?

1
2
3
$(document).on('event', 'selector', function () {
    // La fonction sera appelée pour tous les élément correspondant au sélecteur, y compris s'ils ont été créés plus tard
});

L'idéal étant évidemment d'avoir un contexte plus précis que document dans l'idéal, pour limiter les traitements inutiles…

Dans les faits tu appliques cet écouteur sur un élément fixe dont le contenu peut changer : ainsi tu ne fais ça qu'une fois, mais ça fonctionne toujours peu importe le contenu de ce contexte.

Utiliser $().on('event', 'selector', function() {…}) me permettrait uniquement d'écouter les clics, hovers, etc. Mais pour placer un WYSIWYG, ça ne doit pas demander un événement particulier, mais on doit reprendre le nouveau DOM.
Quant aux datepicker, il n'est pas non-plus prévu pour être mis comme écouteur au clic : le plug-in place lui-même un écouteur.

Je dois donc écouter d'une manière ou d'une autre les modifications du DOM pour relancer $('.wysiwyg').wysiwyg(). Pas d'événement habituel dans ce cas précis, et ces plugins ne sont pas non-plus faits pour réanalyser le DOM en cas de changement. Donc je cherchais un palliatif.

L'idéal étant évidemment d'avoir un contexte plus précis que document dans l'idéal, pour limiter les traitements inutiles…

viki53

Oui, mais encore faut-il que l'événement "bouillonne" ou puisse être lancé sur l'élément choisi.

+0 -0

Du coup pas trop le choix : faut faire le travail à la main après chaque requête Ajax.

viki53

Justement, c'est ce que je cherche à automatiser, plutôt que d'utiliser les callbacks de succès ou de complétion à tour de bras.  ;)

Au pire tu peux appliquer une class pour les éléments déjà transformés, histoire de les éviter après à coup de :not(.ajax-ready)

viki53

Heureusement, ces plug-ins gèrent les instances (il me semble), donc je ne peux pas remettre un WYSIWYG sur un champ qui n'a pas été rechargé et qui a déjà été modifié.

+0 -0

Tiens, je l'avais oublié celui-là, depuis le temps…

J'ai fini par utiliser ce que je considérerais comme "la version évoluée des DOMEvents", soit MutationObserver. En ne faisant attention qu'aux éléments ajoutés, je peux du coup instancier ces plug-ins sans souci.

+0 -0
Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte