it-swarm.com.ru

Выполнение файла js, возвращенного в ответе ajax

У меня есть приложение HTML5, которое использует jquery 3.2.1.

В части приложения - функция поиска - я делаю запрос ajax. Ответом на запрос ajax является HTML, и он включает тег <script>, который ссылается на файл js, который размещен на том же сервере, что и приложение.

Таким образом, код ajax выглядит следующим образом - для выполнения запроса ajax и записи ответа в div с идентификатором #ajaxContent:

$.ajax({
    url: $('#searchRegulations').attr('action'),
    type: 'post',
    cache: false,
    data: $('#searchRegulations').serialize()
 }).done(function (response, status, xhr) {
        if (response) {
            $('main .content').hide();
            $('#ajaxContent').html(response).show();
            return false;
        }
    }
});

Если я проверю #ajaxContent, то увижу, что тег <script> включен в ответ ajax:

 enter image description here

Я также проверил свою вкладку «Сеть», чтобы убедиться, что /js/search_regulations.js загружается правильно и дает ответ 200:

Внутри search_regulations.js есть некоторый jquery, который переключает некоторые фильтры, присутствующие в #ajaxContent

Проблема в том, что этот код работает только на 50% время. Когда он работает, он переключает состояние некоторого фильтра кнопки, добавляя/удаляя класс .active для элементов внутри .browse-ctp__filters-data, а затем записать их в скрытую форму с идентификатором #tmpFilters.

Чтобы убедиться, что сценарий «запускается», я поместил в строку console.log('search_regulations.js firing'); и, конечно же, это отображается в консоли каждый раз, независимо от того, работает сценарий или нет.

Более странным является то, что если я вырезал/вставил код в свою консоль после того, как ответ ajax был записан на страницу, он всегда работает как положено.

Это какая-то проблема с тем, как скрипт переносится на страницу?

Я вставил скрипт ниже, но я не думаю, что это проблема с кодом в нем, а скорее с обработкой ответа браузера/ajax:

$(function() {  

console.log('search_regulations.js firing');

/* toggle the active (applied) state on browse by filters */
/* @link https://stackoverflow.com/questions/48662677/switch-active-class-between-groups-of-include-exclude-buttons */
$(document).on('click', '.browse-ctp__filters-data .include, .exclude', function(){

    var $this = $(this);

    // Split name into array (e.g. "find_355" == ["find", "355"]) 
    var arr = $this.attr('name').split('_');


    // Toggle active class
    $this.toggleClass("active");
    if ($this.siblings().hasClass("active")) {
      $this.siblings().removeClass("active")
    }

    // Remove any existing instances of the filter from hidden form
    $('#tmpFilters input[value="exclude_'+arr[1]+'"]').remove();
    $('#tmpFilters input[value="find_'+arr[1]+'"]').remove();

    // If a filter has been applied then add it to hidden form
    if ($this.hasClass('active')) {
        $('#tmpFilters').append('<input type="hidden" name="tmpFilter[]" value="'+$this.attr('name')+'">');
    }
});    
}); 

Примечания о предлагаемой Баунти:

Я предложил вознаграждение, потому что это не тривиальная проблема, которую можно решить - это доказано тем фактом, что никто не дал действенного ответа. Я ожидаю правильного ответа на:

  1. Быть доказуемым с помощью jsfiddle или аналогичного.
  2. Объясните, как/почему это работает.
  3. Поймите, что ответом ajax является HTML и js. JS действует на элементы HTML в ответе. Поэтому в ответ необходимо включить both HTML и js - вместо того, чтобы сказать «просто добавьте js в глобальный файл» (я не хочу, чтобы js был глобальным, поскольку он специфичен для HTML ответ дан и может варьироваться в разных частях приложения).
  4. Не следует использовать время ожидания (setTimeout или что-то еще). Если пользователь взаимодействует с элементами пользовательского интерфейса - такими как кнопки - возвращенными в ответе HTML before timeout, и, следовательно, запускается js ..., это просто приводит к той же проблеме, что и сейчас. Так что это не является правильным решением, насколько я понимаю.
  5. Если эту проблему невозможно решить в HTML5/jquery, объясните, почему и предложите альтернативные способы ее решения.

jsfiddle показывает тег HTML и скрипт, возвращенный через ajax:

Несколько человек попросили скрипку или демо. Никто из двух людей не получил бы один и тот же результат - что является очень важным вопросом - поэтому я не сделал этого изначально. Возвращенный HTML-код, который записывается в #ajaxContent, показан здесь: http://jsfiddle.net/v4t9j32g/1/ - это то, что инструменты dev в браузере показывают после ответа ajax. Обратите внимание, что длина возвращаемого содержимого может варьироваться, так как это ответ на функцию поиска по ключевым словам, которая возвращает множество кнопок фильтра. Также обратите внимание, что этот ответ HTML содержит строку <script src="/js/search_regulations.js"></script>. Вот где находится проблемный js и полное содержание которого показано выше в этом вопросе - бит, который включает в себя console.log('search_regulations.js firing')

15
Andy

Одна из проблем, которую я вижу, заключается в том, что вы связываете onclick с одинаковыми элементами несколько раз ...

Поскольку js можно загружать многократно с помощью запросов ajax, важно сначала отсоединить, а затем снова присоединять события.

Другая проблема заключается в том, что вы запускаете код в событии $(document).ready (то есть, когда HTML-документ загружен и DOM готов), однако вам, вероятно, лучше запустить код в событии $(window).load (которое выполняет немного позже, когда полная страница полностью загружена, включая все кадры, объекты и изображения)

Пример :

$(window).load(function() {  

    console.log('search_regulations.js firing');

    //off: detach the events
    //on: attach the events
    $('.browse-ctp__filters-data .include, .exclude').off('click').on('click', function(){
        ...
    }
}); 
4
bastos.sergio

НОТА:

  • @ bastos.sergios уже ответил на ваш вопрос - смотрите его первый пункт.
  • Следует также отдать должное @ yts за "усиление" ответа Бастоса.

Поэтому следующее должно исправить проблему с вашим скриптом search_regulations.js:

$(document)
  // Detach the event handler to prevent multiple/repeat triggers on the target elements.
  .off('click.toggleActive', '.browse-ctp__filters-data .include, .exclude')
  // Attach/re-attach the handler.
  .on('click.toggleActive', '.browse-ctp__filters-data .include, .exclude', function(){
    ... put your code here ...
  });

Но мое основное намерение написать этот ответ - позволить вам увидеть проблему с вашим скриптом .

И если вам интересно, вот сценарий, который я использовал с этой демонстрацией, которая немного изменилась:

https://codepen.io/anon/pen/RBjPjw.js

1
Sally CJ

В дополнение к тому, что все говорили в комментариях, я постараюсь поместить это в ответ. 

Событие управляемое

JS управляется событиями, это не значит, что вам нужно загружать скрипты на лету, чтобы они выполняли функции при возникновении события. Лучшим подходом было бы загрузить все, что вам нужно, при загрузке страницы и добавить прослушиватели событий, это обеспечит гораздо лучший пользовательский опыт (меньше запросов http) и намного лучшую совместимость кода.

TL; DR

В общем, я хотел бы сделать что-то подобное в вашем HTML-файле:

<script src="/js/main.js">  
<script src="/js/search_regulations.js" async>  <!-- bonus tip! google "async script tag" -->

Это загрузит все необходимые вам js. 

Имейте в виду, что search_regulations.js должен добавить нужный прослушиватель событий с помощью метода jQuery on, но поскольку HTML-код не существовал, когда сценарий добавил прослушиватель событий, вы можете проверить this .

Почему это лучше?

  • Теперь у вас был довольно простой случай, когда один файл загружает другой. В будущем вы можете захотеть добавить больше функций и более сложный код. Это будет больно отлаживать, когда у вас есть цепочки скриптов, которые загружают друг друга.
  • Пользователь должен ждать, пока загрузится запрос файла search_regulations.js, если он находится в мобильной сети/размер файла увеличится, что может ухудшить восприятие пользователем.
  • Вы по-прежнему можете использовать свой код поверх приложения с помощью search_regulations.js 
1
Or Duan

О требованиях

Решение проблемы доступно с использованием либо HTML5/jQuery, либо комбинации предыдущей версии HTML и нативного Javascript. Единственная причина использования библиотеки, такой как jQuery, заключается в том, чтобы привести более старый браузер к тому же уровню поддержки или исправить ошибки и иметь стандартную структуру, разделяемую между группами разработчиков.

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

О проблеме

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

Каждый раз, когда происходит Ajax-запрос, скрипт в ответ добавляет новых прослушивателей событий. Они будут непрерывно суммироваться до тех пор, пока браузер не выйдет из строя по какой-либо причине (недостаточно памяти, исчерпан стек событий или условия состязания событий щелчка).

О решении

Таким образом, решение вашей проблемы состояло бы в том, чтобы избежать добавления событий каждый раз, когда происходит запрос/ответ Ajax. Это задача, которая может быть легко решена с помощью простого «делегирования события фазы захвата» (к сожалению, не часто используется).

Прослушиватель событий добавляется только один раз на один из верхних узлов, которым может быть сам узел «документ», элемент «html» или элемент «тело», то есть на один элемент, который всегда присутствует в DOM после начальной страницы. нагрузки.

Этот элемент будет отвечать за захват всех кликов на странице, в настоящее время присутствующих или загруженных позднее (по вашим запросам Ajax), и этот элемент будет действовать таким образом в течение всего срока службы страницы, не нужно настраивать/уничтожить события, как я вижу, некоторые предложили. Хорошо, что это, вероятно, будет работать, но будет генерировать большую нагрузку на движок браузера, потреблять больше памяти, требовать больше кода и в целом будет медленнее и излишне сложно, если не сказать больше.

Образцы кода

Я настроил два примера CodePen для вас:

Делегация 1 имеет код обработки событий в разделе HEAD, это мой предпочтительный способ решения подобных проблем, если не существует ограничений или других неизвестных причуд. Преимущества этой версии - более короткий/быстрый и более понятный код.

Делегация1

Delegation2 имеет код обработки событий в ответе Ajax и должен отвечать требованиям, предъявляемым к загрузке HTML/JS непрерывно. Есть проверка, чтобы установить слушателя только один раз и избежать множественных событий, перекрывающих друг друга.

Делегация2

Код Javascript в примерах содержит соответствующие комментарии, и его должно быть достаточно, чтобы объяснить реализованную стратегию, чтобы сделать ее максимально простой и многократно используемой в других аналогичных случаях.

Эти примеры были написаны с учетом старых браузеров, более новые браузеры предоставляют более мощные API, такие как element.matches(selector), очень полезные для делегирования событий. Я намеренно избегал использовать его для более широкой поддержки.

1
Diego Perini

Чтобы избежать возможности условия гонки без реализации плоского таймера, вам нужно будет вернуть ответ ajax, который отделяет скрипт от html, такой как объект json, который возвращает два элемента (скрипт или массив скриптов для поддержки случаев). где вам нужно несколько, и второй элемент, который содержит строковую разметку html).

Пример ответа JSON от AJAX:

{
    "script": "/yourscript.js",
    "html": "<p>Your markup...</p>"
}

Этот (разобранный) json для краткости будет называться xhrResponse ниже.

Перед применением к dom добавьте тег preload для ваших скриптов перед загрузкой элемента dom, чтобы они начали разрешаться в фоновом режиме без загрузки, что должно минимизировать общую временную шкалу загрузки, и помочь облегчить большую часть возможных условий гонки:

document.getElementsByTagName('head')[0].appendChild( '<link rel="preload" href="' + xhrResponse.script + '" as="script">' );

Это начнет загружать ваш скрипт в фоновом режиме, не выполняя его, поэтому он может быть применен немедленно, когда вы позже примените тег script к dom.

Затем вы захотите добавить следующие элементы dom, а затем применить сценарии после разрешения нового содержимого dom. Вы можете поочередно передавать разметку непосредственно в ответе json или поочередно возвращать ссылку на файл шаблона и использовать ее вместе с вышеупомянутым методом предварительной загрузки для самого шаблона. Если вас больше волнует возможность избежать проблем с контентом, сделайте последнее. Если вас больше беспокоит пропускная способность и задержка, сделайте первое. Есть несколько незначительных предостережений к обоим, в зависимости от вашего варианта использования.

document.getElementsByTagName('head')[0].appendChild( xhrResponse.html );

Затем вы захотите убедиться, что примененный HTML-контент разрешен в DOM. Произвольный таймер ожидания не очень хороший способ сделать это, так как он имеет высокую вероятность заставить пользователя ждать дольше, чем это необходимо, и случайные узкие места в сети также могут вызвать удушение сценария, если содержимое не разрешается до произвольная длина таймера (больше проблем, если вы обслуживаете шаблон по ссылке вместо строковой разметки html), или если у конечного пользователя недостаточно памяти в настоящее время (старые машины, открытые вкладки bzillion или очень большие полезные нагрузки html, даже когда подается прямо в стринг).

Пожалуйста, смотрите этот ответ для довольно надежного решения, чтобы проверить, правильно ли разрешено динамически добавленное содержимое dom.

Как только это разрешится, затем примените элементы скрипта примерно так:

var script = document.createElement('script');
script.src = xhrResponse.script;
script.async = true; // use false here if you want synchronous loading for some reason
document.head.appendChild(script);

Ранее упомянутый тег preload должен гарантировать, что ваш сценарий уже локализован браузером к этому моменту, или уже почти сделал это. Затем вы можете проверить, завершил ли он прослушивание событий состояния готовности, если он все еще вызывает проблемы:

script.onreadystatechange = function() {
    if (script.readyState === "complete") {
        // debug stuff, or call an init method for your js if you really want to be sure it fired after everything else is done.
    }
}

Следует отметить, что в вышеупомянутом слушателе состояния готовности, если предварительная загрузка разрешается до этой части вашей логики, состояние готовности не изменится (потому что это уже сделано), и эта часть логики не будет выполнена. В зависимости от веса вашего DOM-контента, это может относиться или не распространяться на вас.


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

0
mopsyd

Вам нужно удалить тег <script> из HTML и вручную создать тег script для добавления в DOM. По сути, вы можете конвертировать HTML в JS с помощью:

var $html = $(data.Html)

Затем извлеките все теги сценария следующим образом:

 var scripts = [];
        $html.each(function(i, item) {
            if (item instanceof HTMLScriptElement) {
                scripts.Push(item);
            }
        });

Затем вам нужно удалить все теги <script> из html, прежде чем добавлять его в DOM.

$html.find("script").remove();

$("placeToAddHtml").append($html);

После добавления HTML, лишенного тегов, вы можете вручную добавить каждый сценарий в DOM в конце:

           for (var i = 0; i < scripts.length; i++) {
                var script = document.createElement('script');

                $(scripts[i]).each(function() {
                    $.each(this.attributes, function(j, attrib) {
                        var name = attrib.name;
                        var value = attrib.value;
                        script[name] = value;
                    });
                });

                script.text = scripts[i].innerHTML;
                document.body.appendChild(script);
            }

Надеюсь, это работает так, как вы намереваетесь!

Правка: Вам может понадобиться создать тег сценария таким образом, чтобы добавить все извлеченные сценарии одновременно, если они зависят друг от друга. Таким образом, вы добавляете только один гигантский тег сценария, который имеет все остальные внутренние теги одновременно.

0
Daniel Lorenz