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

186
11 мая 2018, 10:24

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

$("*[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) {
          applyPlugins($("#some_block").html(data));
        }
    });

    Это может быть вызов .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
Добавить класс и удалить при повторном клике

Добавить класс и удалить при повторном клике

Нужно, чтобы при клике на checkbox добавлялся класс checkbox_active, при этом, если я кликну следующий checkbox нужно чтобы с предыдущего не удалился класс,...

201
Управление курсором в С#

Управление курсором в С#

Может кто подскажет как управлять положением курсора Например в Combobox введено слово Как программно установить курсор после 3 буквы?

170
Преобразование строки в массив чисел

Преобразование строки в массив чисел

Есть строка, в которой символы и числаЭту строку нужно преобразовать в целочисленный массив

122
Не правильно отображается текст в Visual Studio

Не правильно отображается текст в Visual Studio

Даже не знаю как задать вопрос, открываю проект и вижу это

169