Ajax – динамически загружаемый javascript

Written by elwood

Хочу поделиться маленьким велосипедом. Частенько нам нужно загрузить динамически кусок html-разметки (например, какой-нибудь div) и вставить его в DOM страницы. Иногда этот кусок разметки содержит javascript в виде некоторых обработчиков событий. Но если загружать его в ajax запросе, то javascript выполнен не будет (и функции, которые вы определили, тоже не будут зарегистрированы). Для того, чтобы заставить это работать, нужно либо вынести весь js-код в отдельный js-файл и указать его на главной странице заблаговременно, либо ограничивать себя использованием только inline-javascript (это когда мы пишем <input type=”button” onclick=”someJsCode()”/>. В моем случае оба варианта были неудобны, а самым удобным был именно обычный javascript в загружаемых кусочках разметки (потому что IDE при редактировании этих частей лучше понимает контекст – не возникает варнингов о “неизвестных” функциях, айдишниках элементов, переменных итд”. Поэтому я сочинил функцию, которая принимает в качестве аргумента загруженную разметку, выдирает оттуда скрипты (содержимое тегов <script>), склеивает их в один элемент <script> и добавляет в document.body. После этого javascript-функции, определенные в загруженных скриптах регистрируются в пространстве имен, и нормально работают. Причем, если при следующем ajax-запросе необходимо будет поменять реализацию некоторых функций, ничего дополнительно делать не нужно – js-интерпретатор затрет старые функции (по имени) новыми.

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

Код функции:

/**
 * Concatenates all javascript code in html into one big script and
 * creates appends script element with this code to document body.
 * @param html Markup with embedded scripts retrieved using ajax.
 *
 * TODO : mb additional work required for scripts with specified src
 */
function processEmbeddedScripts(html) {
    var tempDiv = document.createElement('div');
    tempDiv.innerHTML = html;
    var scriptNodesList = tempDiv.getElementsByTagName('script');
    if (scriptNodesList.length > 0) {
        var wholeScript = '';
        for (var i = 0; i < scriptNodesList.length; i++) {
            var scriptCode = scriptNodesList.item(i);
            wholeScript = wholeScript + scriptCode.innerHTML + ' ';
        }
        var scriptElement = document.createElement('script');
        scriptElement.type = 'text/javascript';
        scriptElement.text = wholeScript;
        document.body.appendChild(scriptElement);
    }
}
 
/**
 * Removes all embedded scripts from specified piece of html markup
 * and returns the shrinked markup.
 */
function excludeEmbeddedScripts(html) {
    return html.replace(/<script(.|\s)*?\/script>/g, '');
}

Использование:

// получаем html, очищенный от скриптов
var htmlWithoutScripts = excludeEmbeddedScripts(rawHtml);
// добавляем этот html в DOM
document.getElementById('mydiv').innerHTML = htmlWithoutScripts;
// и после того, как мы имеем уже полностью собранный DOM, достаем скрипты и регистрируем их
// в документе. при этом скрипты будут выполнены браузером.
processEmbeddedScripts(rawHtml);

Возможно (см TODO), еще придется доработать этот велосипед для скриптов с указанным src (их нужно пропихивать в document.body без text, но с указанным src).

PS: При повторных выполнениях этого кода старые функции затираются новым кодом (это корректное поведение).

PS-2: В некоторых браузерах подобных подход тяжело отлаживать, поскольку добавленные таким образом скрипты браузер в инструментах разработчика либо не отображает полностью, либо не отображает до тех пор, пока не случится брейпоинт в этих кусочках кода. Поэтому приходится этот метод использовать редко, предпочитая классически выносить весь js-код в отдельные файлы.