Как использовать плагины jquery с динамическим контентом или почему после AJAX отваливается javascript

393
16 августа 2017, 20:31

Подключил на страницу несколько суперплагинов:

$("*[data-foo]").foo();
$(".bar").bar();
$("p a b i").baz();

Но после загрузки через AJAX код перестает работать! Как это исправить?!

Answer 1

Говорить, что "код перестает работать" - некорректно. Потому что код работать и не начинал. Когда вы пишите $(селектор).метод() - это означает однократный вызов метода. Этот метод применяется ко всем элементам, соответствующим переданному селектору, которые в этот момент были на странице.

Обычно говорят, что чтобы исправить ошибку, надо после обновления страницы через ajax выполнить код еще раз. Но это не всегда верно.

Повторное выполнение вызова вида $(".bar").bar(); вызовет плагин bar не только на новых элементах - но и на старых. В лучшем случае будет бесполезно потраченное время. Но в худшем случае из-за повторного вызова что-нибудь поломается (а если вы подписывались на события - то что-нибудь поломается гарантировано!). Поэтому надо применять плагины только к обновленному контейнеру.

Также бывают грабли с тем, как вызывать эти плагины после обновления. Обычно это пытаются делать через инлайн script в ответе сервера - но в таком случае этот скрипт не имеет доступа к обновляемому элементу. Надо искать в коде где происходит само обновление - и править там.

Выше был общий принцип. Ниже я напишу способ, которым можно попытаться заставить заработать код в простом случае.

  1. Собираем все "улучшения" страницы в одном месте:

    applyPlugins();
    // ...
    function applyPlugins() {
      $("*[data-foo]").foo();
      $(".bar").bar();
      $("p a b i").baz();
    }
    
  2. Изменяем получившуюся функцию так, чтобы она действовала только внутри переданного ей контейнера:

    applyPlugins($(document));
    function applyPlugins($cnt) {
      $cnt.find("*[data-foo]").foo();
      $cnt.find(".bar").bar();
      $cnt.find("p a b i").baz();
    }
    

    Здесь мы воспользовались методом find, который ищет дочерние элементы.

    Важно! Функция applyPlugins не должна обращаться к элементам за пределами переданного ей контейнера $cnt!

  3. Находим в коде те места, где выполняется динамическое обновление содержимого или создание элементов.

    Это может быть вызов .html(...):

    $.ajax({
        // ...
        success: function (data) {
          $("#some_block").html(data);
        }
    });
    

    В таком случае надо после вызова html добавить вызов applyPlugins:

    $.ajax({
        // ...
        success: function (data) {
          var $cnt = $("#some_block");
          $cnt.html(data);
          applyPlugins($cnt);
        }
    });
    

    Это может быть вызов .load():

    $("#some_block").load("http://some/url");
    

    В таком случае надо добавить туда параметр с функцией обратного вызова:

    $("#some_block").load("http://some/url", function () {
      applyPlugins($(this));
    });
    

    Это может быть и что-то другое. Но все же надеюсь что вы это место найдете - это же ваш код, а не чей-то еще :)

Способ выше не будет работать если используемый для плагина селектор проходит через динамический контейнер:

$(".foo .bar .baz").somePlugin();
$(".bar").load(...);

В таком случае попытка прямого переписывания в applyPlugins в виде $cnt.find(".foo .bar .baz").somePlugin() будет неудачной, поскольку ни .foo, ни .bar не являются дочерними элементами для обновляемого контейнера.

В таком случае вам, наверное, стоит более внимательно отнестись к тому что и как вы обновляете или загружаете вместо слепого применения трюка с applyPlugins.

Или же можно попытаться сделать как-то так, чтобы предусмотреть все случаи - но этот код для понимания и для отладки будет довольно тяжелым:

$cnt.find(".foo .bar .baz").somePlugin();
$cnt.filter(".foo, .foo *").find(".bar .baz").somePlugin();
$cnt.filter(".foo .bar, .foo .bar *").find(".baz").somePlugin();

(Здесь проверяется, является ли текущий контейнер элементом .foo или его дочерним элементом, и если является - в пути .foo .bar .baz пропускается первый элемент. Этот код предполагает, что один .foo не может быть вложен в другой. То же самое для .bar.)

PS хотя я говорил только про плагины, ответ можно применять и для подписки на события. Но это будет не самый лучший способ - потому что есть способ проще. Большинство событий является всплывающими - а потому их можно отлавливать в корне документа. И JQuery имеет встроенные механизмы для фильтрации всплывших событий. Пример:

$(document).on("click", "a[data-href]", function (e) {
    // ...
})

Этот обработчик будет слушать нажатия на любые ссылки с установленным атрибутом data-href независимо от того, как и когда они появились на странице.

READ ALSO
вернуть input type='file' в первоначальное состояние

вернуть input type='file' в первоначальное состояние

Имеется форма для загрузки файла на сервер в которой множество полей

326
Как кастомизировать компоненты React Bootstrap?

Как кастомизировать компоненты React Bootstrap?

Создатели React перенесли Bootstrap под свою гребенкуПри работе с данной библиотекой React-Bootstrap, возник вопрос, как кастомизировать стили компонентов?...

242
Убрать пробелы вначале в инпуте?

Убрать пробелы вначале в инпуте?

У меня есть функция onChange, где в стейт записываю то, что ввели в инпут

219