Правила     Закладки     Карма    Календарь    Журналы    Помощь    Поиск    PDA    Чат   
     
 

Все статьи:


1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87

Обработка документа в браузере


и работа с ним через javascript


    vasa_c 15.03.2007 - 17:48
Понятно, что здесь в основном php-шники, но все-таки.
Уровень: для самых маленьких.

Обработка документа в браузере
  • куда девались теги?
  • что у меня с кодировкой?
  • почему не добавляются html-сущности?
  • с document.write() постоянно какие-то траблы
  • почему не могу записать текст в элемент?
  • да почему у меня опять ничего не работает?!

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

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

Т.е. имеем:
html-файл на диске --> разбор файла при загрузке в браузер --> работа с получившимся деревом

Что такое (XML-)дерево, надеюсь, подробно рассказывать не нужно. Набор элементов (узлов), каждый из которых может иметь вложенные элементы. В корне дерева элемент «HTML», в него вложены «HEAD» и «BODY», в них вложено еще что-то. В самом конце иерархии либо пустые элементы (IMG), либо текстовые узлы. Каждый элемент так же может иметь набор атрибутов. Атрибут характеризуется именем (уникальным в рамках элемента) и значением (строкой). Вот и все. В смысле, еще есть комментарии, DOCTYPE и еще много чего, но это к делу не относится.

Основная разница между html-деревом и html-файлом: дерево, это структура с которой работает браузер, а файл, это описание этой структуры, удобное для хранения на диске и прочтения человеком.


Теги, кавычки, html-сущности

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

Правила эти просты и общеизвестны:
  1. Элементы задаются с помощью тегов (открывающего и закрывающего). Открывающий тег содержит имя элемента (и, возможно, описание его атрибутов), заключенное в знаки ”<” и ”>”. Закрывающий тег имеет формат </имя_тега>. Все, что между ними, считается содержимым элемента.
  2. Значения атрибутов, желательно, заключать в кавычки. Там же, где значения содержат пробелы, без кавычек не обойтись, так как разборщик просто не поймет, что это - все еще значение атрибута, или же новый атрибут.
  3. Как текст на странице, так и значения атрибутов могут иметь абсолютно любое значение, т.е. быть последовательностью абсолютно любых символов, абсолютно любой длины (в теории). Но так, как некоторые символы могут быть поняты разборщиком неправильно, вместо них следует ставить т.н. html-сущности, которые символизируют эти символы (< & и др)

И так далее, и тому подобное. Не буду пересказывать основы html.

Так вот, главное, что нужно понимать, это то, что все это имеет место быть, только для исходного html-кода. К дереву с которым работает браузер (и с которым может работать js-программист) оно не имеет отношения.

В разобранном html-документе нет никаких открывающих и закрывающих тегов и нет никаких ”<” и ”>”. Они нужны были только для описания структуры в тексте и теперь больше не нужны. DOM-свойство tagName возвращает имя элемента без символов больше-меньше. При создании элемента, через document.createElement() не нужно указывать ”<>” (хотя иногда можно, а в некоторых случаях и полезно).

У атрибутов нет никаких кавычек. Они нужны были только для того, чтобы отделить значение от окружающего текста в исходном коде.

И нету никаких html-сущностей. Все они преобразованы в нужные символы на этапе разбора.

HTML
  1. <div id="test">Текст &laquo;в кавычках&raquo;</div>
  2. <script type="text/javascript">
  3. var dt = document.getElementById( "test" );
  4. alert( dt.firstChild.nodeValue );
  5. dt.firstChild.nodeValue += "еще &" + "laquo; кавычка";
  6. </script>


alert() выводит кавычки в виде символов, а не сущностей. Динамическое добавление последовательности &laquo; к тексту не приводит к преобразованию ее в символ кавычки.

Некоторых смущает такое свойство DOM-элементов, как innerHTML, которое создает иллюзию привязки к элементу какого-то HTML-кода. На самом деле, это просто ловкость рук и никакого мошенничества. Когда программист читает из этого свойства, происходит вызов функции, которая формирует из нужной части дерева html-код. Когда записывает, этот код вновь разбирается в часть дерево, которое цепляется в нужном месте. Даже если присмотреться, то innerHTML возвращает не всегда дословно то, что было в исходном коде. Разные браузеры по разному могут менять регистр имен тегов, порядок атрибутов, кавычек и т.п.


Кодировка

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

Кодировка документа, которая указывается в теге META либо в заголовке Content-Type, не указывает браузеру, в какой кодировке ему следует работать. А указывает разборщику, в какой кодировке пришел документ, с тем, чтобы он перекодировал его в UTF.

Например, создаем html-файл в котором задаем кодировку:
HTML
  1. <meta http-equiv="content-type" content="text/html; charset=windows-1251" />

так же в нем подключаем сценарий в другой кодировке
HTML
  1. <script type="text/javascript" src="./test.js" charset="koi8-r"></script>

а на странице делаем iframe в который загружаем другой документ, который использует кодировку ISO.

Что означают эти манипуляции? Они не означают то, что будут загружены два документа и один сценарий в трех разных кодировках и, в случае организации взаимодействия между ними, придется с помощью js производить перекодировку. Они означают то, что все три файла при загрузке первым делом будут перекодированы, каждый из своей кодировки, в UTF. И в итоге они будут работать и взаимодействовать в одной и той же кодировке.

Вопрос 1

У меня есть документ, который находится в кодировке windows-1251, в нем есть код:
JavaScript
  1. if ( str.charCodeAt( 0 ) == 224 ) alert( "Yes!" );

я точно знаю, что строка начинается с символа ”а”, а его код в win1251 – 224. Но ничего не работает. Для проверки я сделал так:
JavaScript
  1. var str = "абвгд";
  2. if ( str.charCodeAt( 0 ) == 224 ) alert( "Yes!" );

и даже так:
JavaScript
  1. if ( "абвгд".charCodeAt( 0 ) == 224 ) alert( "Yes!" );

Но все равно ничего не работает. Я бьюсь над этим весь день и скоро выброшусь из окна. В чем проблема?!

Проблема в том, что в кодировке windows-1251 находится не документ, а файл на диске. А в браузере он уже в UTF. Все строки, которые были заданы в коде, перекодированы в UTF, все строки, которые вводятся в поля формы, кодируются в UTF. А в UTF код русского символа ”а” отнюдь не такой.

Вопрос 2

У меня есть страница:

HTML
  1. <?xml version="1.0" encoding="windows-1251"?>
  2. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
  3. <html xmlns="http://www.w3.org/1999/xhtml">
  4. <title>Тест</title>
  5. <meta http-equiv="content-type" content="text/html; charset=windows-1251" />
  6. <script type="text/javascript">
  7. function f() {
  8.  
  9. if ( window.XMLHttpRequest ) {
  10. var req = new XMLHttpRequest;
  11. } else {
  12. try {
  13. var req = new ActiveXObject( "Msxml2.XMLHTTP" );
  14. } catch( e ) {}
  15. }
  16. if ( !req )
  17. return false;
  18.  
  19. req.onreadystatechange = ( function() {
  20. if ( ( req.readyState != 4 ) || ( req.status != 200 ) || ( !req.responseXML ) || ( !req.responseXML.documentElement ) )
  21. return false;
  22. var dE = req.responseXML.documentElement;
  23. document.getElementById( "test" ).firstChild.nodeValue =
  24. dE.getElementsByTagName( "word" ).item( 0 ).firstChild.nodeValue + ", " +
  25. dE.getElementsByTagName( "len" ).item( 0 ).firstChild.nodeValue;
  26. return true;
  27. } );
  28.  
  29. req.open( "GET", "./test.php?var=" + encodeURIComponent( document.getElementById( "inp" ).value ) );
  30. req.send( "" );
  31.  
  32. return true;
  33.  
  34. }
  35. </script>
  36. </head>
  37. <input type="text" id="inp" value="" />
  38. <input type="submit" value="Ok" onclick="f()" />
  39. <div id="test">Loading...</div>
  40. </body>
  41. </html>
  42.  


Я ввожу какой-то текст в поле, нажимаю кнопку, и значение поля отправляется в запросе в файл test.php:

PHP
  1. <?php
  2. header( 'content-type: text/xml; charset=windows-1251' );
  3. print '<?xml version="1.0" encoding="windows-1251"?>';
  4. ?>
  5. <response>
  6. <word><?php print htmlSpecialChars( $_GET[ 'var' ] ); ?></word>
  7. <len><?php print strLen( $_GET[ 'var' ] ); ?></len>
  8. </response>


Сценарий возвращает значение поля и его длину, все это выводится на странице в отведенном под это месте. Когда я пишу на английском - все нормально. А когда на русском, вместо слова выводится невесть что, а длина содержит вообще непонятное число. Почему?

Ответ – все потому же. Значение поля в запросе отправляется уже в UTF. Возвращайте в заголовках ответа кодировку xml – utf, а для вычисления длины используйте mbstring. А еще лучше используйте везде на страницах и в сценариях UTF. Геморроя будет куда меньше.

JS-код в атрибутах

Некоторые атрибуты элементов могут содержать в качестве значения js-код. Это, например, обработчики событий (onclick, onmouseover и др.).

Здесь не нужно забывать тот момент, что эти атрибуты в первую очередь именно html-атрибуты, а уже потом js-код. Т.е. они обрабатываются, как и все другие атрибуты и должны соответствовать нужному формату. Например, неоднозначные символы желательно заменять на их html-сущности. Можно и не заменять. Браузеры, конечно, нам все простят, но иногда могут быть проблемы. Кстати, здесь есть и положительные места. Например, кошмар всех php-шников:

HTML
  1. <form method="post" onsubmit="return cofirm('вы уверены, что хотите отослать форму')">


Данный тег нужно вывести через print. Для ограничения значения атрибута onsubmit используются двойные кавычки, а для строки внутри него – одинарные. Для вывода через print придется применять кавычки еще раз, а внутри устраивать частокол из слешев. А можно иначе:

PHP
  1. print '<form method="post" onsubmit="return cofirm(&quot;вы уверены, что хотите отослать форуму&quot;)">';


И все работает. Более того, именно так в теории и правильно.

Кстати, по идее, содержимое элемента <script> так же должно обрабатываться перед выполнением, либо во избежание этого заключаться в секцию <[CDATA[]]>. Однако, услужливые браузеры содержимое данного тега не обрабатывают, позволяя нам не париться. Так что alert( 5 &gt; 4 ) пройдет в атрибуте, но не в элементе сценария.

Атрибуты типа href

Есть некоторые атрибуты, которые содержат URL. В частности, это href у элемента <A>. Спрашивается, что нужно применять к ним: html-кодирование или url-кодирование. Ответ – как любой URL, значение атрибута href должно быть представлено в заданном для url формате (со всякими %). Т.е., например в PHP, все значения параметров должны быть пропущены через rawUrlEncode(). А вот являясь html-атрибутом, в исходном тексте html оно должно соответствовать формату html (htmlSpecialChars()). Т.е. например ссылка с двумя параметрами:
http://site.com/page.php?x=Вася&y=Петя по-правильном должна выглядеть, как
http://site.com/page.php?x=%D0%92%D0%B0%D1...%B5%D1%82%D1%8F

А html-ссылка так:
<a href=”http://site.com/page.php?x=%D0%92%D0%B0%D1%81%D1%8F&amp;y=%D0%9F%D0%B5%D1%82%D1”>

Ага, именно &amp; должен разделять параметры, а не &.


Загрузка документа

document.write()

Очень многие любят заявлять о том, что document.write() устарел и вместо его следует использовать DOM. На самом деле это совершенно различные вещи, хотя документрайтом злоупотреблять все равно не следует.

Рассмотрим этап загрузки страницы. Именно здесь происходит разбор исходного кода. Вернее разбор потока. Разборщик получает на вход поток html-кода, который постепенно разбирает и отрисовывает. Подвал страницы еще может формироваться на сервере, а пользователь уже наслаждается анимированными баннерами в шапке сайта.

Один из важных моментов здесь, это то, что, получив очередным элементом, элемент <script>, браузер исполняет его. А одной из возможностей javascript на данном этапе является возможность писать в поток кода. Что и делает document.write().

HTML
  1. <div style="border: 1px solid #000000">
  2. One
  3. <script type="text/javascript">
  4. if ( confirm( "Закрыть нам этот несчастный див прямо сейчас?" ) ) {
  5. document.write( "</div><div>" );
  6. }
  7. </script>
  8. Two
  9. </div>


Т.е. document.write() просто выводит код, который идет, как часть потока и обрабатывается разборщиком. Соответственно наступает момент, когда поток иссякает, конечное дерево сформировано окончательно и поток закрывается.

Далее вопрос. У меня при щелчке по кнопке вызывается document.write(), который выводит некоторый текст. При этом вся страница становится белой и на ней остается только этот текст. Что это?

Встречный вопрос. А куда ты вообще хочешь, чтобы document.write() чего-то выводил? Конечно, к моменту щелчка, разбор, скорее всего, закончен и поток закрыт. Все что остается document.write() это грохнуть текущий документ и открыть новый поток. Что он и делает.

defer

Как уже сказано, натыкаясь в процессе разбора на элемент <script>, браузер выполняет его, после чего продолжает разбирать входной код дальше. <script> же может не непосредственно содержать код, а ссылаться на js-файл. В этом случае браузер запрашивает файл, ждет загрузки, выполняет его, после чего продолжает отрисовывать документ. Обратите внимание, пока файл не будет загружен, ничего больше в браузер выводиться не будет. Если это файл с удаленного сервера (например, баннер), да еще тот сервер намертво лежит (что с ними часто бывает), то браузер может зависнуть очень надолго.

Для решения данной проблемы есть атрибут defer. Ставя его в элементе <script> вы указываете, что в загружаемом сценарии нет работы с потоком кода, поэтому он может загружаться параллельно странице. К сожалению, баннеры пользуют обычно именно document.write().

На вопрос: ”для ускорения загрузки я поставил у загружаемого сценария defer, но при этом перестал работать document.write()” предлагаю читателю ответить самостоятельно.

Доступ к документу до полной загрузки

Ну и напоследок детская, но очень распространенная ошибка:

HTML
  1. <script type=text/javascript>
  2. document.getElementById( ”divid ).innerHTML = ”Текст;
  3. </script>
  4. <div id="divid"></div>


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

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




Спустя 1 час, 41 минута, 26 секунд (15.03.2007 - 18:30) Ghost написал(а):
есть много сайтов, где написано дофига про то какие есть функции в JS и теги HTML,
есть даже много сайтов с примерами HTML-разметки и JS-кодов.
и вообще статей по HTML и JS очень много.

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

Спасибо :)

Спустя 1 час, 23 минуты, 34 секунды (15.03.2007 - 19:53) Timok написал(а):
небольшое дополнение, если не возражаете...

Цитата(vasa_c @ 15.3.2007, 18:48) [snapback]16145[/snapback]

HTML
  1. <div id="test">Текст «в кавычках»</div>
  2. <script type="text/javascript">
  3. var dt = document.getElementById( "test" );
  4. alert( dt.firstChild.nodeValue );
  5. dt.firstChild.nodeValue += "еще &" + "laquo; кавычка";
  6. </script>


alert() выводит кавычки в виде символов, а не сущностей. Динамическое добавление последовательности « к тексту не приводит к преобразованию ее в символ кавычки.


это, в принципе, решаемо:

HTML
  1. <div id="test">Текст «в кавычках»</div>
  2. <script type="text/javascript">
  3. var dt = document.getElementById( "test" );
  4. alert( dt.firstChild.nodeValue );
  5. dt.firstChild.nodeValue += "еще u00AB кавычка";
  6. </script>


т.е. в сценарии можно заменить формат сущностей ISO на юникод.
А узнать коды (как ISO, так и юникод) можно здесь: http://html.manual.ru/book/info/specialchars.php

Спустя 11 минут, 22 секунды (15.03.2007 - 20:05) vasa_c написал(а):
Рашаемо :) Можно даже просто ее ввести (ALT+0171). Просто пример не совсем про кавычки.

Спустя 15 часов, 40 минут, 38 секунд (16.03.2007 - 11:45) Ghost написал(а):
Возникли такие вопросы:

1. теги должны закрываться, но иногда можно и не закрывать. бразеры дополняют хмл до нужной структуры? это как-нить корелирует с тегами которые принципиально не закрываются?
2. в хмл-структуру попадают только те атрибуты тегов, которые могут быть интерпретированы бразером?
3. хмл-структура содержит полное описание элемента, или только то что задано в атрибутах?
4. в 1-м коде ты алерт не рано поставил? и &" + "laquo; кавычка"; или &" + "quot; кавычка"; и дальше идет &laquo - что за сущность?
5. а часто ваще банера инклюдятся ява-скриптом? мне че-то казалось что они еще на этапе формирования документа должны вставляться
6. >>Как уже сказано, натыкаясь в процессе разбора на элемент <script>, браузер выполняет его, после чего продолжает разбирать входной код дальше.
а если скрипт содержит рефреш на другую страницу (window.location...) - имеем дело с новым потоком?

Спустя 4 часа, 32 минуты, 37 секунд (16.03.2007 - 16:18) vasa_c написал(а):
Цитата
1. теги должны закрываться, но иногда можно и не закрывать. бразеры дополняют хмл до нужной структуры? это как-нить корелирует с тегами которые принципиально не закрываются?


В правильном xml- и xhtml-документе он всегда должны закрываться, так чтобы разборщик точно мог понять, что хотел сказать верстальщик. А там, где правила валидного xml не соблюдаются, браузер начинает пытаться по определенным алгоритмам понять, то, что верстальщик имел ввиду. В некоторых моментах браузеры действую достаточно стандартно, в соответстии со спецификациями, например, внутри <P> не может быть <div>, поэтому, если после P встречают div, то P автоматически закрывается. А в некоторых ситуациях предугадать, то что будет творить каждый конкретный браузер, вообще невозможно.

Цитата
2. в хмл-структуру попадают только те атрибуты тегов, которые могут быть интерпретированы бразером?
3. хмл-структура содержит полное описание элемента, или только то что задано в атрибутах?


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

Цитата
4. в 1-м коде ты алерт не рано поставил? и &" + "laquo; кавычка"; или &" + "quot; кавычка"; и дальше идет &laquo - что за сущность?


Там я просто хотел показать, что в html-коде есть сущности, которые к моменту обращения к элементу в дереве (через js) уже преобразованы в конечные символы. А так же, что если записывать эти сущности прямо в узлы дерева, то никакого преобразования уже не будет.

Цитата
5. а часто ваще банера инклюдятся ява-скриптом? мне че-то казалось что они еще на этапе формирования документа должны вставляться


В поиск по запросу "жутко тормозит phpforum.ru" :)

Цитата
6. >>Как уже сказано, натыкаясь в процессе разбора на элемент <script>, браузер выполняет его, после чего продолжает разбирать входной код дальше.
а если скрипт содержит рефреш на другую страницу (window.location...) - имеем дело с новым потоком?


А вот не задумывался :).
В следующем примере срабатывает первый алерт, после чего происходит переход:
HTML
  1. <script type="text/javascript">
  2. window.location.href = "./one.htm";
  3. alert( 1 );
  4. </script>
  5. !!!!!!!!!!!!!
  6. <script type="text/javascript">
  7. alert( 2 );
  8. window.location.href = "./two.htm";
  9. alert( 3 );
  10. </script>


А в следующем срабатывают все алерты и происходит переход на two.htm. Причем судя по снифферам IE запрашивает оба файла, а FF только последний:

HTML
  1. <script type="text/javascript">
  2. window.location.href = "./one.htm";
  3. alert( 1 );
  4. alert( 2 );
  5. window.location.href = "./two.htm";
  6. alert( 3 );
  7. </script>


Откуда можно сделать вывод - отдельный элемент <script> всегда дорабатывает до конца. Точно так же, как, например, и отдельная функция:

HTML
  1. <script type="text/javascript">
  2. function f() {
  3. alert( 1 );
  4. window.location.href="./one.htm";
  5. alert( 2 );
  6. }
  7. </script>
  8. <a href="#" onclick="f()">A</a>


Если же внутри изменяется location.href, то происходит переход на то значение, которое было записано последним. Причем IE, на всякий случай запрашивает и первый документ.

Кто сможет объяснить все это более внятно (или дать полезные ссылки), а так же указать практическую пользу от понимания данных механизмов, заранее считается молодец :)