Ajax

индекс
86,27

Не гони коней!

Для построения дерева DOM браузеру при загрузке страницы требуется время. Это нужно учитывать при подгрузке контента на страницу с помощью Ajax.
Собственно к чему это я ...
При разработке последнего проекта я взял на себя задачу организовать подгрузку контента на страницу с помощью Ajax. XMLHTTP-соединение успешно устанавливалось, я получал необходимый контент и с помощью innerHTML вставлял его в скрытый блок на странице, чтобы уже оттуда выдергивать необходимые части. И тут я наткнулся на грабли: в ответ на мои запросы getElementById или getElementsByTagName браузер выдавал undefined вместо необходимых мне подгруженных элементов.

Как оказалось, грабли таились в том, что я слишком торопился: с момента вставки полученного контента на страницу и до момента обращения к его элементам браузер не успевал добавлять новые элементы в DOM-дерево страницы. Решение: задать таймаут, тем самым дать браузеру время для построения дерева DOM. А если мы поместим в конец загружаемого контента узел-метку (например, пустой div с id='flag'), то сможем определить закончил браузер свою работу, или нам нужно еще немного подождать.
var hiddenDiv; // скрытый контейнер на странице
var oXmlHttp = createXmlHttp();
oXmlHttp.open(«get», url, true);
oXmlHttp.onreadystatechange = function ()
{
if (oXmlHttp.readyState == 4)
{
if (oXmlHttp.status == 200)
{
hiddenDiv.innerHTML = oXmlHttp.responseText;
var t = setTimeout(«handle()», 100);
}
}
}
oXmlHttp.send(null);

function handle()
{
if (document.getElementById('flag') != undefined)
{
// Дерево DOM готово, можем разбирать полученный контент по частям
}
else
{
var t = setTimeout(«handle()», 100);
}
}

В зависимости от размера получаемого контента следует установить нужный таймаут.

Оригинал статьи
+14
10 сентября 2008, 18:38
11

комментарии (52)

+5
lam0x86 #
Подозрительное решение… Получается, таймаут надо выставлять в зависимости от производительности системы? Странно как-то…
0
afitiskin #
т.к. не во всех браузерах существуют события, которые сообщат нам о том что дерево DOM готово (токое как, например, onload при загрузке страницы), то придется использовать таймауты, и уж вам выбирать — только лишь для IE (спасибо за подсказку комментариев ниже) или для всех браузеров
0
neformal #
У вас ошибки в коде, пожалуйста протестите такой код, и увидите, что всьо работает нормально…

Text
+1
neformal #
<div id='mainDiv'>Text</div>
<script>
var str='<div>какойто мега контент</div>'+'<div id=«testDiv»>Всьо работает нормально</div>';
document.getElementById('mainDiv').innerHTML=str;
alert(document.getElementById('testDiv').innerHTML);
</script>
0
IlVin #
onload — не удачный пример…

document.addEventListener( «DOMContentLoaded»,…, false );
document.documentElement.doScroll(«left»); // javascript.nwbox.com/IEContentLoaded/
document.readyState != «loaded» || document.readyState != «complete»

0
blo #
ajax возвращал не текст, а часть HTML? и потом шло getElementById к тому что было вставлено с помощью innerHTML?
и что происходит при повторном запросе? флаг то уже существует.
0
afitiskin #
hiddenDiv.innerHTML = oXmlHttp.responseText; alert(document.getElementById('flag').tagName);
получим undefined, если контент будет сложной древовидной структуры, именно в этом случае необходимо использовать таймаут
НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь
0
blo #
понимаю что глупо, но где-то видел такой хак

hiddenDiv.innerHTML = oXmlHttp.responseText;
setTimeout(«_alert()», 10);

function _alert(){
alert(document.getElementById('flag').tagName);
}

т.е. смысл — разнести в разные функции
НЛО прилетело и опубликовало эту надпись здесь
+2
Pirilisik #
НЛО прилетело и опубликовало эту надпись здесь
+1
gro #
В каких браузерах наблюдается? На каких объёмах вставляемого кода?
0
afitiskin #
получаемый контент содержал пору десятков DIV в каждом из которых еще по 3-4 DIV с разметкой (span, a, strong, etc...)
0
afitiskin #
тестировал в FF2
0
XaocCPS #
может стоит переделать ваше дерево элементов раз оно локально строится браузером дольше, чем происходит ajax-запрос на внешний ресурс?
+5
miros #
В мозилле, опере и вебките есть событие DOMContentLoaded

А вообще во всех фреймворках есть событие, происходящие после загрузки DOM. Можете в исходном коде того же jQuery посмотреть. Правда для IE там тоже проверка через таймауты. Вот такой вот трюк:
javascript.nwbox.com/IEContentLoaded/
0
name #
Я думаю, что проще будет добавить необходимые элементы в дерево, после того как их подгрузили. Посмотрите статью на сайте Sun (http://java.sun.com/developer/technicalArticles/J2EE/AJAX/), а конкретно — 6 и 7 разделы статьи. Я полагаю, вам должно это помочь.
0
Pirilisik #
Можно попробовать Mutation Events. DOMNodeInserted

А вообще ситуация странная. Даже если полученный html-код был очень громоздким, то браузер должен был «подвиснуть» на вот этой строке:

hiddenDiv.innerHTML = oXmlHttp.responseText;

И ваш дальнейший код получил бы управление только после того, как парсинг завершился.
Может, ошибка кроется в чём-то другом?

P.S. Жаль, что регистрация закрыта.
0
afitiskin #
дело в том, что я при написание кода думал именно так, но на практике получилось, что задержки в выполнении js-кода не возникало, то есть у элемента hiddenDiv в момент выполнения следующей строки кода не было ниодного дочернего узла, хотя можно было проследить с помощью breakpoint'ов в firebug что в параметре innerHTML находится весь подгруженный контент.
Столкнувшись с этим я начал выяснять в чем же дело, и нашел решение с помощью timeout
0
Pirilisik #
А как устроена функция createXmlHttp? Там, случайно, не используется один и тот же экземпляр XMLHttpRequest?
0
afitiskin #
простите, не понял что вы имеете ввиду
0
Pirilisik #
Я имею ввиду ошибку в Firefox и IE, возникающую при вызове метода abort. А именно, возникает событие readystatechange и свойство readyState при этом будет равно 4. После этого readyState снова устанавливается в ноль.
0
afitiskin #
хм, не понимаю, каким образом это связано с проблемой описанной в топе.
повторюсь, что контент приходит в полном объеме, но он на тот момент является текстом. Конечно, можно с помощью регулярных выражений без всяких задержек распилить его на повторюсь текстовые блоки и вставить их куда нужно. Но удобнее конечно вылавливать нужные блоки по id, а для этого контент должен быть не текстом, а DOM элементом, и именно преобразование текста в элемент посредством innerHTML занимает время, то есть не происходит мгновенно, на что я и хотел обратить внимание в своем топике, дабы, наткнувшись на грабли потратил довольно много времени на повторную проверку всего кода, на предмет косяков…
0
no_smoking #
Не пойму почему нельзя использовать oXmlHttp.responseXML.getElementById('id тогочто нужно'); вместо этих огородов. Вупор не пойму может я глупый обьясните :)
0
no_smoking #
Ну если у вас не получается получит responseXML то можно попробывать так

function toXml(s) {
var doc = null;
if (window.ActiveXObject) {
doc = new ActiveXObject('Microsoft.XMLDOM');
doc.async = 'false';
doc.loadXML(s);
}
else
doc = (new DOMParser()).parseFromString(s, 'text/xml');
return (doc && doc.documentElement && doc.documentElement.tagName != 'parsererror')? doc: null;
};

var doc = toXml(oXmlHttp.responseText);
doc.getElementById('id того что нужно');
0
weirdan #
Поправьте меня, если я не прав, но разве для это не нужно, чтобы ответ был валидным xml документом? А судя по описанию, ответ представляет собой кусок html'я (например последовательность нескольких тегов без root-нода).
0
no_smoking #
ну а кто мешает добввить root нод и использовать второй вариант, да и вобще я за чистату xhtml кода :)
0
taliban #
может я не так понял статью, но я делаю так:

0
taliban #
черт, хабрахабр питается тэгами :( он сьел мои тэги О.О
вобщем делаю так:
вставляем тег скрипта сразу за дивом в который надо поместить контент, и если скрипт выполнится, то див уже загрузился полюбому (т.к. находится перед скриптом) проблем не возникало…
НЛО прилетело и опубликовало эту надпись здесь
0
neformal #
мдя… чтото даже трудно педставить такой код… контент передается вместе с логикой, думаю ето кошмар для програмиста… и через несколько таких Ajax запросов у вас бутет милион Js обектов…

Так или иначе сложной логики на таком коде не построиш…
0
taliban #
я не пишу весь код js в html а только вызов фунции и все, это не «контент передается вместе с логикой» а исключение из правил
0
IlVin #
А этот прием где-нибудь в стандартах документирован?
А то выпустят процессор с 1000 ядрами и запустят построение DOM на одном процессоре, а выполнение JS на другом и потом гадай что из них быстрее выполнится…
+2
G0rnap5chtickn3R #
А зачем хранить какие-то данные в скрытых дивах? Чем вам обычный яваскриптовский массив не угодил?
–4
IlVin #
А зачем хранить какие-то данные в массиве? Их же надо эскейпить!!!
Проще их в Засунуть…
–4
IlVin #
сорри — контейнер вырезался…
<textarea style=«display: none»></textarea>
0
egorinsk #
Тут все равно надо применять htmlspecialchars()
+1
lahmatiy #
Мне кажется проблемы у вас в том что вы вставляете подгружаемый HTML до полной загрузки HTML страницы. Если это так, то стоит дождаться полной загрузки страницы (события onload) и затем подгружать данные. Попробуйте вставить ваш скрипт в самый конец документа (перед </body>).
В любом случае setTimeout самое плохое решение в данном случае. Всегда можно придумать другое решение по распределению (куда вставлять) подгружаемый контент.
0
sirus #
чегото и мне так кажется
0
afitiskin #
инициализация обновления страницы с помощью ajax стоит на событие onload + timeout (от 10 секунд, до нескольких минут)
0
lahmatiy #
Дело наверное в особенностях работы onload в разных браузерах.
В любом случае, может было бы лучше грузить данные в формате json?
например
{
htmlPart1: '....',
htmlPart2: '....'
}
тогда никаких проблем с выборкой через getElementById не будет
да, и попробуйте не document.getElementById(id), а node.getElementById(id) (тут node узел в который вы вставляете HTML)
–4
enartemy #
Может я чего-то не понимаю, но неужели нельзя все ajax-подгрузки навешивать на body onload, которое срабатывает как раз апосля того, как страница загрузилась?
0
IlVin #
onload срабатывает только после загрузки всего барахла, что подключено к странице (графика, флеш и т.п.), а нам нужно подгружать данные сразу же как только построится DOM.
0
lahmatiy #
Ваше утверждение не совсем верно, для части браузеров это не так, и onload наступает как только загружена сама страница, css и javascript — совсем не обязательно после загрузки всех картинок. В ряде случае событие может наступить и до загрузки всего javascript (например в IE есть для этого специальный атрибут для тега script).
+1
IlVin #
А я давно уже не изобретаю велосипеды. Я использую jQuery.
Документацию по этому очень полезному фреймворку можно посмотреть здесь:
docs.jquery.com/Main_Page

И Вашу задачу (ожидание окончания построения DOM) с помощью jQuery можно решить так:
$(document).ready(function () {
// А здесь можно выполнять Ajax запросы
});

А если покопаться в исходниках фреймворка на предмет функции bindReady(),
то можно насчитать 3 способа определения готовности DOM (Mozilla, IE, Safari).

Так что, прежде чем изобретать велосипед, разбираем по винтикам уже изобретенный и, если появилась свежая мысль, модернизируем старый велосипед до более совршенного уровня…
+1
dfitiskin #
вообще как я понял, проблема к ajax мало чем относится. может спокойно возникнуть и без него:

node.innerHtml = 'много много сложного html кода';
subnode = node.getElementById(id); // легко может быть undefined, даже если он там точно есть

просто в обычной жизни практически нет необходимости пихать через innerHtml много много кода, а вот при использовании ajax — иногда появляется
0
lahmatiy #
Что-то мне ваш сценарий кажется сомнительным. Когда делаем node.innerHtml = 'много много сложного html кода' мы можем адресовать запросы к этому узлу сразу, за исключением, возможно, тех случаев когда сама страница еще не полностью загружена. Честно говоря не проверял, но что-то мне подсказывает что это так.
0
pavlick #
обычно пока он не выполнит комманду по вставке кода, он не двигается на следующий шаг. Поэтому неполная загрузка кажется более правдоподобным объяснением.
–5
bagzilla #
о_О
0
pavlick #
каков объем вставляемого кода? Современное железо вряд ли будет тупить из-за вставки текста. Пробовали ли вы искать другие возможные проблемы?

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