вторник, Сентябрь 12, 2006

Javascript on XHTML pages

There are lots of rumours about Javascript in XHTML served as different MIME types. I'll try to explain what's really going on. Shortly, innerHTML works everywhere (even for XHTML served as text/xml); document.* collections work identically for application/xhtml+xml and text/html.

XHTML served as text/html

There are two main differences in DOM between valid HTML and XHTML served as text/html — in XHTML all elements are in lowercase and UAs don't create implicit elements such as tbody in XHTML. That's all! You don't need to put inline styles and scripts in CDATA sections. UAs handle these XHTML pages practically the same as they handle HTML ones,xml well-formedness is not checked. And of course, all document.* collections work, innerHTML works either. And yes, document.all works even in Firefox (since version 1.5).

Yes, there's also a problem of marking inline scripts and style as CDATA sections, but with well-coded unobtrusive javascript there's no need for inline scripts, and there's definately no need for inline styles except rare cases (even LJ allows using external CSS files).

XHTML served as application/xhtml+xml

That is the preferred MIME-type for XHTML pages. Commonly it is used with content-negotiation mechanism to serve pages as application/xhtml+xml for browsers that support it and to serve pages as text/html for browser that doesn't support application/xhtml+xml. Some people say that most of neat DOM methods and properties do not work in XHTML document served as application/xhtml+xml. They say:

  • document.write doesn't work
  • innerHTML doesn't work
  • document.applets, document.forms, document.anchors, document.images, document.links, other document.* collections don't work
  • document.all doesn't work
  • document.body and all other properties of document don't work

They are completely wrong.

XHTML document served as application/xhtml+xml has the same DOM as XHMTL served as text/html.

At first Mozilla has been creating XMLDocument object for pages served as application/xhtml+xml, but then there was opened a Bug in December 2001, and the first fix was proposed in March 2003 and in two monthes final fix was accepted. Since that time Mozilla started creating HTMLDocument object for XHTML pages served as application/xhtml+xml and therefore they had to support all that HTML stuff: document.write, document.applets, HTMLElement.innerHTML, document.forms, document.anchors, document.images, document.links, document.cookie etc. The only thing they didn't support is document.all, it's only supported in quirks mode of document served as text/html.
As for Opera: it checks namespace of <html> element to understand if it is xhtml and therefore there should be a HTML DOM or if it is a xml document and there should be XML DOM. And yes, Opera provides HTML DOM for ALL MIME types that XHTML can be served as.

XHTML served as application/xml or text/xml

Mozilla creates XMLDocument for XHTML served as application/xml or text/xml, so it doesn't provide HTML DOM for such documents. But Opera does.

innerHTML

Yes, that may sound strange, but it's a fact — innerHTML is supported everywhere, FOR EVERY XHTML MIME-TYPE.

Conclusion

There's quite little amount of problems concerned XHTML served as application/xhtml+xml, javascript works fine since there's the same HTML DOM as it would have being served as text/html. When you serve XHTML as text/html or application/xhtml+xml, in both case you will have eqaul HTML DOMs. So don't be afraid of all those scary stories of not working innerHTML or document.forms — it's all a lie.

For those who don't believe me.


show cut

понедельник, Сентябрь 11, 2006

September intro

Damn it, August was really hot… Since I'm now in projects.cc, I need to practice my English. So maybe some posts will be still in Russian, but all IT-related stuff will be in English.

done so far:

Alinii.ru Dasha's (my girlfriend) design, my server-side scripting and xhtml/css. IE6/FF/Opera. I wish I had time to fix IE5 issues, but time is money, so when maybe they ask us to upgrade the site or code some fancy stuff, I'll remake the layout. Quite simple project, nothing interesting.

mycherrymobile.com — in progress…


show cut

пятница, Август 11, 2006

Кэширование и сжатие: теория

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

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

Основа кэширования

Кэширование всегда инициируется сервером, и поддерживается клиентом. Самый простой способ кэширования выглядит так:
Клиент запрашивает какой-то ресурс, сервер включает в заголовки ответа заголовок Expires со значением какой-либо даты. Этот заголовок «говорит» клиенту о том, что данный ресурс будет актуальным до даты, указанной в нём. Если указать некоторую дату в будущем, клиент будет вправе до этой даты при запросе пользователя выдавать ресурс из своего кэша. Если указать 0, текущую дату или дату в прошлом, клиент будет при каждом запросе заново скачивать весь ресурс, так как будет полагать, что «срок годности» ресурса истёк и следует получить его свежую версию.

Механизм метода устаревания документа

  • клиент запрашивает какой-либо ресурс.
    GET http://somehost.ru/index.html HTTP/1.0
    
  • Сервер, «знающий», что данный ресурс не устареет до определённой даты, включает её в заголовок Expires.
    HTTP/1.0 200 OK
    Date: Tue, 08 Aug 2006 08:15:21 GMT
    Expires: Wed, 09 Aug 2006 08:15:21 GMT
    Content-Length: 4
    Content-Type: text/html
    
    test
    
    В данном примере ресурс index.html устареет ровно через день.
  • Клиент высчитывает «срок годности» ресурса (просто вычитая Date из Expires) и сохраняет в кэше ресурс вместе со сроком годности.
  • В течение этого «срока годности» клиент не будет запрашивать ресурс с сервера вовсе, а будет отдавать его пользователю из кэша.

В стандарте HTTP/1.1 был добавлен заголовок Cache-control, контролирующий механизм кэширования (о нём я расскажу ниже). Одним из его возможных атрибутов является атрибут max-age, явно указывающий срок годности ресурса. В случае, если клиент поддерживает HTTP/1.1 и видит в заголовке Cache-control, принятом от сервера, атрибут max-age с каким-либо значением, клиент принимает срок годности, указанный в max-age, даже если есть заголок Expires с другим значением. Другими словами, max-age имеет бóльший приоритет, чем Expires, что позволяет, например, предоставлять разные возможности кэширования HTTP/1.0 и HTTP/1.1-клиентам.

Механизм устаревания ресурса удобен в некоторых случаях. Например, очень удобно отдавать различные служебные изображения на сайте с заведомо большим сроком устаревания, например, размером в год. Таким образом можно предотвратить большинство самых частых запросов — клиенты будут скачивать такие изображения очень редко. Недостаток один — если картинка изменилась, нужно как-то заставить клиента заново её скачать. Обычно изображение просто переименовывают, клиент, соответственно, думает, что это новое изображение и скачивает его. Также, если сайт устоялся и на нём не планируется в ближайшее время изменений, можно выдать таблицы стилей и скрипты с таким заголовком.
Необходимо заметить, что большинство серверов для статических файлов автоматически реализуют кэширование, основанное на методе «GET-запроса с условием» (см. ниже), что даёт экономию в трафике, но всё равно заставляет клиента каждый раз выполнять множество запросов, что, в свою очередь, вынуждает сервер проверять все условия и отвечать клиенту либо 304 Not Modified, либо отдавать сам ресурс. Наиболее оптимально совмещать эти два способа, потому я и предлагаю заняться кэшированием вручную.

Резюмируем метод кэширования, основанного на устаревании ресурса:

  • В течение срока, установленного сервером в заголовке Expires (или в значении атрибута max-age заголовка Cache-control), клиент отображает пользователю ресурс из кэша, вообще не запрашивая сервер.
  • Метод хорош для служебных данных — изображений, таблиц стилей, скриптов, — для действительно редко изменяемых данных.
  • Наиболее оптимально сочетать этот метод с методом «GET-запроса с условием».

Так как в большинстве браузеров по нажатию кнопки «Обновить» такие ресурсы запрашиваются заново, поэтому я и предлагаю сочетать этот метод с «GET-запросом с условием».

«GET-запрос с условием» (conditional GET)

Не всегда авторам известна дата устаревания ресурса, потому был создан механизм «GET-запроса с условием». Общий механизм прост — сервер каким-либо образом идентифицирует ресурс, и клиент в последующем запросе добавляет в заголовки присланный сервером идентификатор; сервер сверяет собственный и присланный идентификатор, если они равны, значит, ресурс не изменился, в таком случае сервер говорит клиенту, чтобы тот показывал ресурс из кэша. Если же идентификаторы различаются, сервер заново отдаёт ресурс с новым идентификатором. Образно говоря, клиент просит сервер отдать документ, если он изменился.

«GET-запрос с условием» в HTTP/1.0

При запросе клиентом ресурса, определенного URI, сервер может добавить к стандартным заголовкам ответа заголовок Last-Modified со значением даты последней модификации ресурса. В случае запроса статического файла в качестве времени модификации удобно использовать время его последней модификации в файловой системе.
Клиент получает ответ, и если он умеет работать с кэшем, сохраняет скачанный документ вместе с полученной датой. При следующем запросе ресурса с тем же URI клиент смотрит в свой кэш, и если находит ресурс с сохранённой датой(полученной из заголовка Last-Modified), добавляет к заголовкам запроса заголовок If-Modified-Since, куда подставляет сохранённую дату.
Сервер, получая такой заголовок, сравнивает дату модификации ресурса у себя с датой, полученной в заголовке If-Modified-Since. Если даты совпали (или дата, присланная клиентом, «больше»), сервер отдаёт клиенту заголовок 304 Not Modified, таким образом оповещая клиента о том, что ресурс не изменился и клиент может отобразить его из своего кэша. Если у клиента дата старее, чем дата модификации ресурса, сервер отдаёт клиенту ресурс заново вместе с новой датой модификации.

Механизм работы «GET-запроса с условием» HTTP/1.0:

  1. клиент запрашивает какой-либо ресурс.
    GET http://somehost.ru/index.html HTTP/1.0
    
  2. Сервер, поддерживающий кэширование для данного ресурса, добавляет в ответ заголовок Last-Modified.
    HTTP/1.0 200 OK
    Date: Fri, 30 Dec 2003 13:20:12 GMT
    Last-Modified: Fri, 29 Dec 2003 13:20:12 GMT
    Content-Length: 4
    Content-Type: text/html
    
    test
    
  3. Клиент сохраняет в кэше документ вместе с датой, полученной от сервера в заголовке Last-Modified.
  4. В следующий раз клиент при запросе того же ресурса в заголовок запроса добавит сохранённую дату
    GET http://somehost.ru/index.html HTTP/1.0
    If-Modified-Since: Fri, 29 Dec 2003 13:20:12 GMT
    
  5. Сервер сравнит полученную дату с датой последнего изменения ресурса. Если на сервере находится более «свежая» версия ресурса, он отдаст её вместе с новой датой модификации.
    HTTP/1.0 200 OK
    Date: Sat, 31 Dec 2003 11:20:22 GMT
    Last-Modified: Sat, 31 Dec 2003 10:00:00 GMT
    Content-Length: 7
    Content-Type: text/html
    
    newtest
    
    Если же на сервере документ актуален (т.е. даты равны), то сервер отдаст заголовок HTTP/1.0 304 Not Modified, но уже не отдаст тело ответа
    HTTP/1.0 304 Not Modified
    Date: Sat, 31 Dec 2003 11:20:22 GMT
    
    Клиент же, получив такой ответ, покажет пользователю документ из своего кэша.

Дата, указанная в Last-Modified, должна быть именно в таком виде и в формате GMT. Строго говоря, и HTTP/1.0 и HTTP/1.1 обязывают серверы понимать три формата времени:

Sun, 06 Nov 1994 08:49:37 GMT 
Sunday, 06-Nov-94 08:49:37 GMT 
Sun Nov  6 08:49:37 1994       
Но HTTP/1.0 добавляет, что сами серверы не имеют права использовать последний формат, а HTTP/1.1 требует от серверов использовать только первый формат времени.

Таким образом реализуется замечательная возможность — передавать только те документы, которые действительно изменились. Но этот метод далёк от совершенства — бывает сложно однозначно идентифицировать изменённость документа только по дате модификации файла, а в случае динамически генерируемого содержимого и вовсе не всегда возможно. Потому в спецификации HTTP/1.1 механизм «GET-запроса с условием» был расширен и дополнен.

Резюмируем:

  • метод применим для любых типов ресурсов, если для них возможно определить время модификации
  • метод поддерживается всеми клиентами и всеми прокси-серверами (уж HTTP/1.0 сейчас реализуют все)
  • в сочетании с методом кэширования, основанном на устаревании ресурса (Expires) идеален для кэширования служебных изображений, таблиц стилей, скриптов. Клиент не будет запрашивать сервер до даты, определённой в Expires, а затем, когда запросит, если ресурс не изменился, получит 304 Not Modified с новым значением Expires и снова перестанет запрашивать сервер до даты из нового Expires.

«GET-запрос с условием» в HTTP/1.1

В HTTP/1.1 в механизм «GET-запроса с условием» было добавлено ещё одно «условие» т.н. ETag — сущность, однозначно определяющая содержимое ресурса. Это может быть хэш всего ресурса, его контрольная сумма или другая функция, однозначно идентифицирующая содержимое. В ситуации, когда документ генерируется шаблонной системой и CMS, бывает сложно определить время модификации ресурса, особенно если он «собирается» из нескольких частей. Вот в таких случаях удобнее определить ETag и сравнить его с присланным клиентом. Механизм сравнения похож на реализованный в HTTP/1.0 механизм сравнения даты модификации. Только если в HTTP/1.0 использовалась пара заголовков Last-Modified(сервер)/If-Modified-Since(клиент), то в в HTTP/1.1 используется пара ETag(сервер)/If-None-Match(клиент), использующаяся таким же образом — сервер идентифицирует ресурс и шлёт клиенту в заголовке ETag этот идентификатор. Клиент в следующий раз в запрос того же ресурса добавляет заголовок If-None-Match с сохраненным значением присланного сервером заголовка ETag. Сервер сверяет идентификаторы и либо возвращает 304 Not Modified в случае, если ресурс не изменился, или же возвращает заново весь ресурс с новым значением ETag.

Механизм работы «GET-запроса с условием» HTTP/1.1:

  1. клиент запрашивает какой-либо ресурс.
    GET /index.html HTTP/1.1
    Host: somehost.ru
    
  2. Сервер, поддерживающий кэширование для данного ресурса, добавляет в ответ заголовок ETag.
    HTTP/1.1 200 OK
    Date: Fri, 30 Dec 2003 13:20:12 GMT
    ETag: "328e-1d-1b63fda6"
    Content-Length: 4
    Content-Type: text/html
    
    test
    
  3. Клиент сохраняет в кэше документ вместе со значением заголовка ETag, полученным с сервера.
  4. В следующий раз клиент при запросе того же ресурса в заголовок запроса добавит сохранённое значение ETag.
    GET /index.html HTTP/1.1
    Host: somehost.ru
    If-None-Match: "328e-1d-1b63fda6"
    
  5. Сервер сравнит полученное значение идентификатора ETag с собственным. Если идентификаторы различаются, сервер отдаст документ вместе с новым значением идентификатора ETag.
    HTTP/1.1 200 OK
    Date: Sat, 31 Dec 2003 11:20:22 GMT
    ETag: "435b-4t-jla890l9"
    Content-Length: 7
    Content-Type: text/html
    
    newtest
    
    Если же значение идентификатора ETag, присланное клиентом совпадает со значением серверным, то сервер отдаст заголовок HTTP/1.1 304 Not Modified, но уже не отдаст тело ответа.
    HTTP/1.1 304 Not Modified
    Date: Sat, 31 Dec 2003 11:20:22 GMT
    
    Клиент же, получив такой ответ, покажет пользователю документ из своего кэша.

Резюмируем:

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

Сжатие

Сжатие ответа было представлено в HTTP/1.0. Оно выполняется просто. Если клиент поддерживает какой-то механизм кодирования, он к каждому запросу может приложить заголовок Accept-Encoding с перечислением поддерживающихся механизмов кодирования. Сервер может закодировать ресурс одним из методов, поддерживающихся клиентом. В HTTP/1.0 были введены два типа кодирования — x-gzip и x-compress (но было сказано, что gzip и compress им эквивалентны для совместимости с будущими реализациями протокола). В HTTP/1.1 уже просто используются gzip и compress (и говорится, что программы должны понимать x-gzip и x-compress для совместимости с предыдущими реализациями протокола:)). Также в HTTP/1.1 был введён метод deflate, но из-за кучи проблем с реализацией в разных браузерах этот метод используется гораздо реже. Чаще всего используется gzip. Его поддерживают все браузеры, начиная c IE4, Opera5.12, NN4.06 и ранних версий Firefox. Сжимать можно любой тип данных, но большинство браузеров понимает только text/plain, text/html, text/css, text/javascript. Необходимо заметить, что при сжатии в заголовке Content-Length указывается длина сжатого тела сообщения.

Последовательность работы механизма сжатия:

  • Клиент запрашивает какой-то ресурс, сообщая серверу о поддерживаемых типах кодирования:
    GET /index.html HTTP/1.1
    Host: somehost.ru
    Accept-Encoding: gzip,deflate
  • Сервер выбирает тип кодирования, кодирует ресурс и шлёт пользователю
    HTTP/1.1 200 OK
    Date: Thu, 10 Aug 2006 17:08:26 GMT
    Content-Encoding: gzip
    Content-Length: 59
    
    всякая_пожатая_непроизносимая_ерунда
    
  • Клиент, зная тип кодирования, декодирует ресурс.

Необходимо осветить также заголовок вариантности кэширования.

Заголовок вариантности Vary

Vary: Accept-Encoding нужен для того, чтобы дать понять кэширующему механизму, что ответ сервера зависит от принимаемого клиентом метода кодирования. Предположим ситуацию: есть два клиента, один поддерживает gzip, другой нет. Поддерживающий gzip клиент шлёт в запросе Accept-Encoding: gzip, не поддерживающий не шлёт, соответственно :) Всё замечательно работает — сервер шлёт одному клиенту пожатый контент, другому несжатый. Но если на пути к серверу попадётся прокси-сервер, поддерживающий кэширование, возникает проблема. Если первый клиент, поддерживающий gzip, получает от сервера пожатый контент, прокси эти данные кэширует. При обращении к тому же ресурсу второго клиента(не поддерживающего сжатие), прокси-сервер отдаёт этому клиенту пожатый контент! Клиент ничего не понимает :) Эта проблема решается с помощью посылаемого сервером заголовка Vary. Его значением может быть любое HTTP-поле. Фактически этот заголовок говорит, что ответ на запрос будет разным в зависимости от некоторых заголовков. В результате в кэше будут храниться все варианты ответов. Если скрипт отдаёт разные данные в зависимости от Cookie, нужно отдавать Vary: Cookie, таким образом кэш отдаст закэшированный вариант ответа только для конкретной куки. Например, если два клиента обращались к ресурсу с Cookie: name=Vasya и Cookie: name=Petya, будет закэшированы отдельно ответы для обоих клиентов, соответственно Васе будет отдан его ответ, Пете — его. Ещё раз акцентирую внимание на том, что все условия, относительно которых может быть получен разный ответ сервера, нужно прописывать в Vary, чтобы не допустить попадания кэша одного пользователя другому.

Нужно сказать, что существуют серьёзные проблемы при использовании сжатия. Во-первых, это нагрузка на процессор сервера. Ни в коем случае не нужно на лету сжимать часто скачиваемые ресурсы. В таком случае правильнее будет сжимать их сразу при изменении, и потом отдавать уже сжатые данные. Сжатие оптимально использовать совместно со стратегиями кэширования (см. ниже). Также существует несколько проблем в реализации сжатия некоторыми браузерами.

Проблемы со сжатием в различных браузерах:

  • IE5.5 и IE6 забывает об ETag при использовании заголовка вариантности кэширования Vary и просто при использовании кодирования gzip. В IE7 этой проблемы нет.
  • IE4.0 и IE5.0 вообще не умеют кэшировать сжатое содержимое.
  • Opera не умеет кэшировать сжатый text/css, text/javascript
  • IE5.5 и IE6 без сервис-пака из-за бага могут терять первые 2 килобайта сжатых данных (редкий случай, я не встречал).

Лучше всего себя в работе с кэшированием и сжатием показали Firefox и IE7b3.

Стратегии кэширования

Кэширование служебных изображений

  • Служебные изображения не нужно сжимать, это лишняя и бесполезная нагрузка на процессор, т.к. чаще всего такие изображения уже максимально сжаты в графическом редакторе.
  • Кэширование изображений нужно реализовывать на основе Expires, ETag, Last-Modified. Expires предлагает клиенту брать ответ на запрос из кэша в течение всего срока, Last-Modified и ETag обеспечивают «GET-запрос с условием», Last-Modified обеспечивает поддержку HTTP/1.0-клиентов, ETag — HTTP/1.1. Таким образом, если клиент и приходит за изображением, то происходит «GET-запрос с условием» и клиент получает изображение только если оно изменилось.
  • Принудительно заставить всех клиентов получить изображение заново, несмотря на срок «годности» Expires можно только изменив путь к изображению или переименовав его.

Кэширование таблиц стилей и скриптов

Если сайт «устоялся» и не планируются частые изменения, то стратегия кэширования идентична кэшированию изображений, за исключением того, что к таблицам стилей и скриптам можно также применить сжатие. Правда, нужно решить, что для Вас важнее — неудобство пользователей Opera, теряющих из-за сжатия возможность кэширования или же удобство пользователей остальных браузеров, умеющих кэшировать сжатые таблицы стилей и скрипты. В принципе, можно совместить требования. Получается следующее:

  • Добавляем в заголовок вариантности Vary вариант User-Agent для того, чтобы показать кэшу, что разным браузерам будет отдано разное содержимое.
  • Opera до 9-й версии включительно и ie4/5.0 нужно отдавать несжатое содержимое. Только так эти браузеры смогут кэшировать содержимое.
  • Для IE6 необходимо проверять только If-Modified-Since.

Кэширование (x)html-документов

Нужно определиться, необходимо ли Вам вообще кэширование (x)html-документов. Если клиент отобразит пользователю документ из кэша, Вы не получите «показа» баннера рекламной сети, и что ещё очевиднее, не сможете учесть посетителя на Ваш сайт — ведь по сути пользователь его не посетил :) Если же Вы цените удобство Ваших пользователей больше, чем рекламные показы, кэшируйте и (x)html. Стратегия кэширования похожа на используемую для таблиц стилей и скриптов, правда, с несколькими «но».

UPDATE:

как абсолютно правильно поправил меня anonymous в комментах, неизменность (x)html-документа совершенно не гарантирует неизменности картинки с баннерной сети, в любом работа с этой картинкой будет зависеть только от заголовкой, с которыми она была отдана баннерной сетью.
Если Вы проверяете заголовки вручную в скрипте и хотите учитывать посещения на сайт, можно просто считать обращения к этому скрипту, в любом случае даже при conditional GET скрипт будет запущен для проверки. Единственное, от чего придётся отказаться — от Expires, т.к. в течение срока устаревания, установленного в Expires клиент не будет посещать Ваш сайт вообще. Если же подсчёт посещаемости для Вас не важен и есть возможность устанавливать Expires, устанавливайте его :)

Иногда (x)html-код генерируется автоматически, потому использование времени последней модификации невозможно. Остаётся ETag. Cледовательно, учитывая неумение IE6 кэшировать сжатый контент на основе ETag, от сжатия, скорее всего, придётся отказаться; или же слать сжатое содержимое только тем браузерам, которые не забывают про Etag. Если же (x)html-код находится в статических файлах, можно и для IE6 использовать сжатие совместно с кэшированием на основе Last-Modified. Также, как и в случае с кэшированием таблиц стилей и скриптов, статические файлы можно заранее сжать, чтобы потом не тратить ресурсы на сжатие на лету.

P.S. если нужен пример скрипта, выполняющего кэширование чего-нибудь, пишите.


show cut

четверг, Август 10, 2006

технический перевод

Я не то чтобы не люблю турков — мне они индифферентны, но когда турок переводит документацию к компрессору на английский язык, а затем мне эту документацию приходится переводить на русский, невольно возникает некая «нелюбоффь» к этой личности :) Почему я, переводя инструкцию, общаюсь с компрессорщиком и выясняю, что тут турок перевёл явно неправильно, тут перевёл неправильно? Что за отношение к работе такое? Это уже не технический перевод, это декодирование и догадки. Ну ничего, из 27 страниц осталось 4 :) Я сделаю это! :) Но самое забавное, что я тут нашёл — это «blow down job». Это не то, что Вы подумали, это всего лишь процесс продувки — давление лишнее стравливается в холодильной камере ;) В общем, весёлый перевод.
И всё-таки сегодня я закончу статью про кэширование, чего бы мне это не стоило %)

show cut

четверг, Август 03, 2006

разные стили при js on/off

Часто на сайтах некий функционал реализован целиком на js. Всё бы хорошо, но некоторые несознательные граждане отключают выполнение javascript в браузере, тем самым выключая весь этот замечательный функционал :). И остаются потом пустые контролы, нефункционирующие кнопки и прочая прочая. Поэтому всё сознательное человечество пользователям с выключенным js не показывает элементы, которые без js бессмысленны. Раньше это делалось двумя вариантами (с небольшими вариациями):

  1. генерируя зависящее от js содержимое в самом js, соответственно человек без js и не получит этого содержимого
  2. скрывая в css содержимое, которое зависит от js, а потом по window.onload делать это содержимое видимым.

на самом деле, второй вариант в самом оптимальном виде выглядел так: в css прописывалось по умолчанию «сокрытие» содержимого display:none или ещё как и писался селектор навроде #js_enabled, в правилах которого это самое содержимое делалось display: block, и по window.onload к body применялся этот самый id js_enabled. Выходило дёшево и сердито — по умолчанию всё скрыто, после загрузки документа js (буде таковой включен), делал всё видимым.

Оба метода далеки от идеальности по многим причинам. И одна из главных проблем — необходимость дожидаться полной загрузки документа — там может быть множество замещаемых элементов (например, img), полной загрузки которых будет ждать window.onload.

Да, проблему ожидания загрузки замещаемых элементов событием window.onload можно решить, но остаётся ещё много проблем, например, занятость id/className-атрибутов у body, но это не решает самой главной проблемы существующих методов — увеличения связности компонентов документа. Порой сидишь и рисуешь связи между css/js/особенностями браузеров/серверным кодом, чтобы понять, откуда то или иное вылазит.

Я же предлагаю совершенно новый метод (во всяком случае, я долго искал и не нашёл ни одного похожего применения). Как я уже говорил, несмотря на то, что псевдопротокол javascript: не описан в стандартах, он функционирует во всех браузерах. Возвращённое после выполнения js-кода значение и будет являться содержимым «документа». Для иллюстрации предлагаю описать в html ссылку вида <a href="javascript:'new content'">link</a> и нажать на неё. Как я уже упоминал в статье про события, браузер выполнит «переход» на «страницу», содержимое которой будет определено возвращённым значением.
На этом и основан мой метод. Предположим, что у нас есть блок #js_control, который целиком и полностью зависит от js, и без js не нужный. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
  <title>test.</title>

  <style type="text/css">

    /*в этой таблице стилей описывается вид страницы без js */
    #js_control { display: none; }

  </style>

  <link rel="stylesheet" type="text/css" href="javascript:'

    /*вид страницы с js*/
    #js_control { display: block; }

  '"/>

</head>
<body>
  <div id="js_control">
    <input type="text" id="apply rule" />
    <input type="submit" onclick="someFunction()" />
  </div>
  <p>text</p>
</body>
</html>
В общем, получается, что в обычной таблице стилей мы описываем «умолчальное» состояние страницы, во второй, необычной таблице стилей описываем css, который будет применён при включенном js. Необычна она тем, что по css-код, который я в ней написал, является строкой. Когда браузер запросит таблицу стилей по её href'у, он выполнит javascript-код, который, в свою очередь вернёт строку, в которой я описал css-код. Браузер применит этот css-код к документу.
Таким образом, очень удобно происходит «разделение» — если js выключен, вторая таблица стилей не применится. Браузеры, в которых я это проверял: IE4/5/5.5/6/7b3, Opera5/8/9, FF1/1.5.

UPDATE:

только что проверил, opera8/8.5/9, FF1.5, IE4/5/5.5/6 — можно подключать и внешние таблицы стилей: <link rel="stylesheet" type="text/css" href="javascript:'@import url(http://host.com/js-enabled.css);'" /> К сожалению, IE4 и FF не понимают иной нотации, кроме @import url(someurl), причём если написать в кавычках, FF начинает кидаться эксепшнами странными, даже если всё нормально сэкранировать. Если же писать относительный урл, то опять же FF не подгружает почему-то, но как мне кажется, это всё настолько недокументировано, что можно простить странное поведение :)
В общем, получается такая ситуация: если правил немного и в них мало что нужно экранировать (да-да, весь css-код в этом случае суть строка), например, если мало правил с content, то удобнее прописать правила прямо в такой таблице стилей. Если же правил много или не хочется ничего экранировать, то проще написать последним методом, правда, придётся писать абсолютный адрес внешней таблицы стилей.


show cut

правильный горизонтальный список

С установкой IE7 совсем забыл о том, что у меня блок с архивами необлагорожен. Точнее, облагорожен на скорую руку. Итак, попробую осветить процесс %) В общем, дизайнерская моя мысль была такова: если блок размером мал, то список там должен быть тоже малым, горизонтальным, да с запятой после каждого элемента. Просто перечисление месяцев.

html-код, в общем-то, совершенно стандартен: <div id="archive">
  <h2>Archives:</h2>
  <ul>

    <li><a href="http://bitari.blogspot.com/2006_08_01_bitari_archive.html">Июль 2006</a></li>

    <li><a href="http://bitari.blogspot.com/2006_07_01_bitari_archive.html">Август 2006</a></li>

  </ul>
</div>
Сделать же хотелось перечисление, да после каждой ссылки запятая чтобы стояла. Так как html-код генерируется автоматически блоггером, его изменить я не могу. Остаётся css. Для того, чтобы добавлять содержимое, в css2.1 есть свойство content, применяется он только в сочетании с псевдоклассами :before и :after, таким образом можно добавлять содержимое до и после какого-либо элемента. К сожалению, ни в IE6, ни даже в IE7 это свойство не поддерживается.

Для семейства IE эту проблему решит сооветствующий javascript, помещённый в условные комментарии.

Изначальный CSS: /* отключаю маркеры, выставляю высоту и требую все содержимое,
   что выходит за пределы высоты, прокручивать скроллбаром */
#archive ul { height: 65px; width: 200px; list-style: none; overflow: auto }
И начинаю думать, как же быть с этими запятыми. Для начала, надо их поставить. Есть два варианта — ставить запятую после каждого list-item'а или до. /* до */
#arhive li:before { content: ", " }

/* или после */
#archive li:after { content: ", " }
В любом случае придётся избавиться от одной запятой — если ставить до, то окажется лишняя запятая перед первым элементом, если ставить после, будет лишней запятая после самого последнего элемента. Избавляемся:
вариант «до»: #archive li:before { content: ", " }
/* убираем запятую перед первым элементом */
#archive li:first-child:before { display: none }
вариант «после»: #archive li:after { content: ", " }
/* убираем запятую после последнего элемента */
#archive li:last-child:after { display: none }
Но! Как бы это не казалось странным, но псевдокласс :first-child описан в спецификации css2.1, а :last-child описан в CSS3 Selectors… В общем, :last-child поддерживает только Firefox, его авторы посчитали логичным реализовывать :first-child и :last-child oдновременно, пусть они и прописаны в разных спецификациях. Авторы же Operа так не посчитали. Потому вариант с установкой запятой после элемента отпадает — за неимением :last-child мы не сумеем убрать запятую после последнего элемента. Выбрали. Теперь возникает вопрос — а как же, собственно, укладывать наши list-item'ы в один ряд :) Для этого есть всего два варианта: сделать элементы списка поплавками или инлайн-элементами.
«поплавочное» решение: #archive ul { height: 65px; width: 200px; list-style: none; overflow: auto }
#archive li { float: left; }

#archive li:before { content: ", " }
/* убираем запятую перед первым элементом */
#archive li:first-child:before { display: none }
Этот вариант не подходит по той простой причине, что «всплывающие» элементы остаются блоковыми, соответственно, если такой блок не помещается в предоставленную родителем ширину, он «съезжает» вниз. А согласно спецификации («The formatting objects (e.g., boxes) generated by an element include generated content.»), наши сгенерированные запятые находятся внутри бокса элемента, соответственно если элемент этот «съедет», запятая уедет вместе с ним. Но это совершеннейшим образом некрасиво. В общем, при поддержке обоими браузерами :last-child, можно было бы использовать и поплавочный вариант.

Странно и непоследовательно то, что Opera9, не поддерживая css3-псевдокласс :last-child, использует модель генерируемого содержимого из css3.
Cпецификация CSS2.1 говорит нам:
User agents must ignore the following properties with :before and :after pseudo-elements: 'position', 'float', list properties, and table properties.
Спецификация же CSS3 нам говорит:
display property … Applies To: all elements, ::before, ::after, ::alternate, and ::outside
и
For compatability with previous levels of CSS, the '::before', '::after', '::first-line' and '::first-letter' pseudo-elements do not require two colons. This does not apply to any other pseudo-element. Authors are encouraged to use the new two-colon forms.
В общем, Opera9 позволяет нам позиционировать генерированное содержимое как угодно.

Но раз мы решили не пользоваться поплавочным вариантом, пойдём и другим путём — сделаем все list-item'ы инлайн-элементами: #archive ul { height: 65px; width: 200px; list-style: none; overflow: auto }
#archive li { display: inline }

#archive li:before { content: ", " }
/* убираем запятую перед первым элементом */
#archive li:first-child:before { display: none }
Таким образом, получилась куча инлайн-элементов, фактически — длинная строка, содержимое которой браузер вполне может переносить. Значение display у сгенерированного содержимого также inline… Но браузер переносит и по пробелам между месяцем и годом, но это уже совсем просто поправить — просто добавим white-space: nowrap для ссылок: #archive ul { height: 65px; width: 200px; list-style: none; overflow: auto }
#archive li { display: inline }

#archive li:before { content: ", " }
/* убираем запятую перед первым элементом */
#archive li:first-child:before { display: none }
#archive a { white-space: nowrap }
Всё уже работает, только вот запятые отстоят от элементов, к которым должны «прилипать». Объясняется этот отступ просто — перенос строки является пробельным символом :) Чтобы его устранить, добавим отрицательный левый отступ для сгенерированного содержимого. #archive ul { height: 65px; width: 200px; list-style: none; overflow: auto }
#archive li { display: inline }

#archive li:before { content: ", "; margin-left: -.5ex }
/* убираем запятую перед первым элементом */
#archive li:first-child:before { display: none }
#archive a { white-space: nowrap }
Во многих шрифтах запятая по ширине примерно где-то в половину ширины малой буквы «x», потому отступ я записал в ex. Всё, красота полнейшая. Напоследок покажу, как это будет выглядеть в блоге: #archive ul { height: 65px; width: 200px; list-style: none; overflow: auto }
#archive li { display: inline }

#archive li + li:before { content: ",\A0\A0"; margin-left: -.5ex; }
«+» — так называемый direct adjacent combinator, он специфицирует заданные элементы, следующие сразу за указанным элементом. Считайте, что про :first-child/:last-child прочитали в общеобразовательных целях ;)
ну и js для IE. Его надо будет поместить в conditional comments, дабы чего не случилось. <!--[if lte IE 7]><script type="text/javascript">
  var addCommas = function(){
    var lis = document.all['archive'].lastChild.childNodes;
    var comma = document.createTextNode(', ');
    for(var i=0; i<lis.length-1; ++i) lis[i].insertBefore(comma.cloneNode(true), lis[i].lastChild);
  }
</script><![endif]-->


show cut

среда, Август 02, 2006

IE7, standalone, впечатления.

Выкачал. Сначала поставил целиком и по-нормальному, посмотреть на него :) Отличный браузер. С юзабилити поработали, сразу видно. Не понравилось только то, что поисковый движок дополнительный можно добавить только с сайта microsoft :) Ну да это всё мелочи. Читает фиды. Всяких секьюрных функций — не пересчесть. Но самое приятное в том, что он удобен, Maxthon становится просто не нужен. Но при установке он прописывает себя везде, где можно, и IE6 из компьютера пропадает... Мне это не подходит.

Выкачал запускалку, очень удобная штука. Деинсталлировал IE7, сделал как в инструкции — распаковал содержимое установщика, положил в папку с распакованным файл ie7s.exe, запустил. Конечно, функционал не полный (фиды не работают и еще всякие вкусности), но для тестов вполне хватает. На conditional comments откликается верно, с IE6 сосуществует в таком виде ровно. Итого у меня на компьютере 5 разных версий IE %) Можно выпендриться и поставить еще и IE3.0, да только смысла нет — пользователи его либо в фидо, либо умерли.

Первым делом посмотрел, как выглядит блог в IE7... Выглядит, надо сказать, неплохо, только съехал чуть-чуть вверх блок с большой картинкой и текст понизу. Самое обидное, что в IE7 у меня не вылазят алёрты. Это существенно принижает ценность IE7 как standalone-версии. Я уже думаю о vmware с winXP+ie7. Пока только думаю. Так вот, про съехавший блок. Посмотрел в свой css, ничего не увидел такого, оказалось, что собака порылась в подгружаемой блоггером автоматически своей таблице стилей: @import url(http://www.blogger.com/css/navbar/main.css); Я видел, что «оперная» и FF'ная консоли ругаются на кучу underscore-хаков, но было как-то пофик. А щас вот посмотрел, таки да, начинаются у людей проблемы с IE7. #space-for-ie {
display:none;
_border:0 !important;
_display:block;
}
Ну вот IE7, как и FF/Opera, прочитал первое правило (underscore-bug у него решён), а на остальные внимания не обратил... В результате поехало %) Но что происходит? Не зря там стоял этот space-for-ie — он лечил известный IE-шный баг, который и в IE7 остался, к сожалению...

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

К сожалению, этот пакостный блок совсем убрать никак нельзя — и html и css-код его блоггером подставляется автоматически... Потому я просто поправлю css :) /* скрою совсем этот несчастный блок */
#space-for-ie { display: none; }

/* заменим пропадающий в IE6/7 margin-top у ребёнка на такой же padding-bottom, но у родителя:*/
body { padding-top: 50px !important;}
body #header{ margin-top: 0;}
Так правильнее, как мне кажется. И не нужны фильтры и хаки... Ещё бы blogger.com сам взял да поправил свои стили, а то уж больно много мусора в консолях FF/Opera после посещения какого-нибудь блога на блоггере. В общем, не знаю, приживётся ли IE7b3 на моей машине, надеюсь всё-таки, что скоро дождемся релиза, и что наконец-то поправят кучищу глюков.

P.S. Всё, пора спать. Сёдня бегал 40 минут, завтра утром предстоит с Дашей бежать до речки... Ещё столько нужно успеть:) Завтра, всё завтра.


show cut

вторник, Август 01, 2006

смена дизайна

Немножко поменял дизайн. Прошу любить и жаловаться.

Пока что куча багов, которые не видно глазом. Например, IE6 не умеет :after, :last-child, Opera9 не умеет :last-child, и если я знаю, как IE6 быстро поправить (expression'ом), то как поправить в Opera — хз. Точнее, js'ом-то можно, но вот нужно ли... Впрочем, будет свободные полчаса, порешаю все эти баги.

Сначала хотел вообще другой дизайн, и вроде бы уже нарисовал симпатично, но потом дело упёрлось в сокрытие navbar'a, я поискал было, но не нашёл внятного ответа, все думают, что скрытый navbar нарушает TOS, хз-хз... Будет время, спрошу в суппорте.

Ещё наконец-то начал читать книжку по ruby. Интересно, но катастрофически не хватает времени, а там нужно пробовать и пробовать...

P.S. Давно хотел сказать — в блоггере отвратительная работа с картинками — их можно добавить только из редактора сообщения.

Маленькое todo на неделю:

  1. редизайн anastasya.com
  2. продолжение статьи про события в js DOM
  3. если дадут материал, закончить а-линии
  4. дальнейшее очеловечивание блога

такие дела.


show cut

воскресенье, Июль 30, 2006

Учебник Javascript, история первая: Events

Пожалуй, события (а точнее, обработчики событий) в javascript играют чуть ли не самую важную роль — именно благодаря обработке событий возможно интерактивное общение веб-приложения с пользователем. Изначально обработчики событий начал поддерживать браузер Netscape 2, следом IE3, и затем уже все остальные браузеры. В этом посте я начну рассказывать про события в js, в течение месяца постараюсь «добить» тему событий и приступить к более интересным вещам.

Способы установки обработчиков

Существуют четыре варианта «навешивания» обработчиков на определенное событие:

  1. псевдопротокол javascript:. Псевдопротокол javascript: по-настоящему бывает необходим очень редко, но, к сожалению, используется повсеместно. Делают это так:<a href="javascript:alert('got click!')">link</a> Изначально этот протокол был создан для тестов — и до сих пор удобно проверять что-то по-быстрому, вписав в адресную строку выражение (например, в IE я раньше проверял hasLayout таким образом — писал в адресную строку javascript:alert(someElement.currentStyle.hasLayout)). Этот протокол никогда не был предназначен для навешивания обработчиков событий. Но люди поняли, что при нажатии на ссылку происходит выполнение содержимого её атрибута href (вспомните, что происходит при нажатии на ссылку, имеющую href="mailto:somemail@serv.com"), и стали использовать псевдопротокол javascript: для замены основного предназначения ссылки (исполнения роли идентификатора связанного документа посредством универсального локатора ресурса (URL)). Стоит отметить, что ни в одной спецификации не сказано про наличие протокола javascript:, потому я и называю его «псевдопротоколом» (в то же время mailto: как раз описан). Более того, WCAG говорит о том, что псевдопротокол javascript: использовать вообще нельзя, и в этом случае я с ними полностью согласен. Однако авторы браузеров из соображений обратной совместимости продолжают поддерживать эту пакость, а колхозные веб-девелоперы продолжают пользоваться этой дрянью. Основное неудобство и контрпродуктивность такого подхода в том, что теряется сам смысл ссылки. Ведь со ссылкой можно сделать множество различных вещей — добавить в закладки, открыть в новом окне, распечатать содержимое, сохранить содержимое. Весь этот функционал теряется при использовании этого псевдопротокола. Вообще же этот способ не является полноценным методом установки обработчика события, потому что этот метод может быть примёнен только для элементов, имеющих атрибуты src/href.
    При использовании ссылок с таким обработчиком необходимо помнить, что функция ни в коем случае ничего не должна возвращать, иначе произойдёт «переход» на страницу, содержимое которой будет состоять из возвращённого значения.
  2. inline-метод. Запись атрибута onevent html-элемента. Пример: <a href="someurl.html" onclick="alert('got click!')">link</a> Этот метод появился самым первым, и поддерживается абсолютно везде. Недостаток его заключается в том, что логика смешивается с содержимым. (если вдруг придётся изменить обработчики, придётся лезть в код (х)html). Применять же его можно, как мне кажется, лишь в самых простых сайтах-визитках, где весь js-функционал заключается, например, в открытии/скрытии какой-нибудь карты проезда. Ссылка сохраняет свой основной функционал, её можно положить в закладки и т.д. Если же Вы не можете держать в памяти все установленные обработчики в проекте, то этот способ неприемлим.
  3. Установка element.onclick в js. Данный метод сообразен предыдущему за тем исключением, что установка обработчика происходит не непосредственно в (x)html, а в скрипте, и это удобнее. Пример: где-то в html:
    <a href="someurl" id="myhref">link</a>

    где-то в javascript:
    var handler = function(){ alert('clicked') };
    document.getElementById('myhref').onclick = handler;
  4. addEventListener/attachEvent. addEventListener — специфицированный в DOM Level2 метод, attachEvent же встречается только в IE. Оба этих метода предоставляют наиболее многофункциональную реализацию добавления обработчика события. Пример: var handler = function(e){ alert('clicked'); };
    var elem = document.getElementById('note');
    addEventListener: elem.addEventListener('click', handler, false); attachEvent: elem.attachEvent('onclick', handler); Более подробно я расскажу об этих методах ниже.
Также существуют ещё два метода установки обработчиков событий:
  • в IE4 и старше: <p id="myP">text</p>

    <script for="myP" event="onclick">
      //code
    </script>
  • Opera9, Firefox1.5: XML events
Но мы эти методы рассматривать не будем, т.к. до поддержки XML events самому популярному браузеру (IE) гораздо дальше, чем до поддержки событийной модели w3c, а script for нарушает принцип отделения логики от содержания (и работает только в IE).

Как Вы успели заметить, первая пара методов отличается от второй коренным образом — обработчики, определяемые в (x)html-документе, представляют собой последовательность javascript-команд, обработчик же, установленный в самом скрипте, обязан быть объектом типа «функция».
Также при использовании второй пары способов возникает одна проблема — если скрипт расположен в документе до элемента, событие которого будет обрабатывать какая-то функция из скрипта, велика вероятность того, что браузер попытается «навесить» обработчик на ещё несуществующий элемент. Такое может произойти, если браузер уже получил содержимое скрипта и распарсил его, но ещё не получил сам элемент в (x)html-документе. Это далеко не такая редкая ситуация, как может показаться — скрипты, вынесенные во внешние файлы, часто кэшируются, и несколько документов используют одни и те же скрипты, потому браузер часто берёт файлы скриптов из кэша, соответственно, сразу же исполняя их. Впрочем, скрипты, описанные внутри элемента head (x)html-документа, также будут исполнены сразу же, не дожидаясь полной загрузки (x)html-документа в память. Проблема эта решается просто — в структуре DOM, предоставляемой каждым браузером скриптам, есть объект window, имеющий кучу свойств, методов и событий, и одно из таких событий — window.onload. Это событие вызывается браузером после получения и парсинга всего (х)html-документа. Соответственно, в js наиболее безопасно работать с документом и его элементами после появления этого события. Пример:

<html>
 <head>
  <title>test</title>
  <script type="text/javascript">
  window.onload = function(e) {
    //it's safe inside
    var handler = function(e){
      alert('clicked on paragraph');
    };
    document.all['text'].onclick = handler;
  };
  </script>
 </head>
 <body>
   <h1>header</h1>
   <p id="text">text</p>
 </body>
</html>
Так как пример будет работать во всех браузерах в quirks mode, я имею полное право использовать document.all (к тому же, так ещё и IE4 будет поддержан). Цель примера исключительно иллюстрационная, потому кому нужен standarts compliancy mode, переделайте document.all['text'] на document.getElementById('text').

Итак, мы выяснили, когда добавлять обработчики, договорились не использовать первые два метода (inline-описание и псевдопротокол javascript:). Теперь я подробнее остановлюсь на последних двух методах. Метод установки element.onclick хорош тем, что прост. Основной же его минус заключается в том, что он не позволяет добавить обработчик, он даёт лишь возможность заменить существующий (если таковой был определён). Получается, что с помощью установки свойства onclick невозможно использовать несколько обработчиков на одном элементе, что иногда бывает нужно. Вообще, конечно, добавить можно и через onclick, только геморрой. Методы же addEventListener и attachEvent позволяют именно добавлять обработчики (для них существуют и соответствующие методы удаления обработчиков — removeEventListener и detachEvent). При установке какому-то событию определенного элемента одного и того же обработчика, дупликат не будет установлен. В следующем примере обработчик divHandler будет вызван только один раз: <html>
<head>
  <title>test</title>
  <script type="text/javascript">
  window.onload = function(e) {
    var handler = function(e) { alert('clicked on div'); };
    document.all['text'].addEventListener('click', handler, false);
    document.all['text'].addEventListener('click', handler, false);
  };
  </script>
</head>
<body>
   <h1>header</h1>
   <div id="text">text</div>
</body>
</html>

Когда на одном элементе «висит» несколько обработчиков, они выполняются в том порядке, в каком были установлены — стэк, хранящий обработчики событий, имеет тип FIFO.

var elem = document.getElementById('note');
var handler1 = function(e){ alert('handler1'); };
var handler2 = function(e){ alert('handler2'); };
var handler3 = function(e){ alert('handler3'); };

note.addEventListener('click', handler1, false);
//note.attachEvent('onclick', handler1);

note.addEventListener('click', handler2, false);
//note.attachEvent('onclick', handler2);

note.addEventListener('click', handler3, false);
//note.attachEvent('onclick', handler3);

В этом примере выполнится сначала handler1, затем handler2, потом handler3.

Несмотря на то, что в msdn сказано, что обработчики, буде таких несколько на событии объекта, выполняются в рандомном порядке, опыт говорит, что они выполняются именно в FIFO-порядке.

Резюмируем:

  1. Обработчики могут устанавливаться четырьмя основными способами: используя псевдопротокол javascript:, c помощью установки атрибута элемента в (x)html, устанавливая свойство объекта в javascript, используя специальные методы (attachEvent для IE и addEventListener для реализующих стандарт DOM2 Events браузеров).
  2. Методы использования псевдопротокола javascript: и установки атрибута элемента малоприменимы в реальных условиях.
  3. Использование специальных методов (attachEvent/detachEvent и addEventListener/removeEventListener) позволяют, в отличие от метода установки свойства объекта, добавлять и удалять обработчики событий (а не устанавливать единственный и отменять вовсе). Метод же установки свойства объекта наиболее прост в использовании, и там, где точно хватит одного обработчика события, будет более удобен.
  4. Если у элемента несколько обработчиков одного события, при возникновении события они будут запущены в том же порядке, в каком были добавлены.
  5. Регистрация одного и того же обработчика события дважды невозможна, обработчики должны быть уникальны.

Порядок запуска обработчиков события

Так как (x)html-документ имеет иерархическую древовидную структуру, разработчики браузеров посчитали, что «пропускание события» по всей иерархии документа даст б`ольшую свободу веб-разработчикам в реализации интересных обработчиков.

Итак, предположим, у нас есть следующий код:

<html>
<head>
  <title>test</title>
  <script type="text/javascript">

  window.onload = function(e) {

    var pHandler = function(e){ alert('clicked on paragraph'); };
    var bodyHandler = function(e){ alert('clicked on body');}
    var docHandler = function(e){ alert('clicked on document'); };

    document.onclick = docHandler;
    document.body.onclick = bodyHandler;
    document.all['text'].onclick = pHandler;

  };

  </script>
</head>
<body>
   <h1>header</h1>
   <p id="text">text</p>
</body>
</html>

Если нажать на p, запустится сначала обработчик pHandler, затем bodyHandler и уже потом docHandler.

bubbling event model Событийная модель, в которой браузер выстраивает очередь обработчиков от целевого элемента, инициировавшего событие до корневого элемента document, называется bubbling-моделью (bubble, англ., — пузырь). Событие как бы «всплывает» по иерархии документа от элемента, вызвавшего его и до корня. При прохождении каждого родительского элемента браузер проверяет, не установлен ли у этого родителя обработчик события такого же типа, и если установлен, вызывает его.
Эта модель реализована в IE.

Если запустить этот пример в браузере Netscape4(с небольшими изменениями), обработчики будут выполнены в обратном порядке — docHandlerbodyHandlerdivHandler.

capturing event model Событийная модель, в которой браузер выстравивает очередь обработчиков от корневого элемента document до целевого элемента (инициировавшего событие), называется capturing-моделью (capturing, англ., — захват). При прохождении каждого элемента вниз по иерархии браузер проверяет наличие обработчика события такого же типа, и если обработчик есть, вызывает его.
Эта модель появилась в браузере Netscape3 и со времен Netscape4 в диком виде не наблюдается.

Вообще же Netscape4 — сущий баг, да и процент его на рынке исчезающе мал, потому на нём внимание заострять не будем.

Позже, когда проблемы с разными событийными моделями всем надоели, дядьки из W3C решили стандартизировать событийную модель. Назвали они ее нехитро — DOM Events. Они попытались объединить обе модели. К сожалению, этой спецификации следуют только браузеры Firefox1.5 и Opera9, но в свете проснувшегося интереса IETeam к следованию стандартов, к 8-й версии IE также можно ожидать поддержки этой спецификации. И потому я попытаюсь рассказать о ней. Как я уже сказал, событийная модель w3c объединяет capturing и bubbling модели с небольшими изменениями. В стандарте w3c для установки обработчика события используется метод addEventListener. Он описан в интерфейсе EventTarget, который авторы браузеров обязаны реализовать для любого элемента.

interface EventTarget {
  void         addEventListener(in DOMString type,
                                in EventListener listener,
                                in boolean useCapture);
  void         removeEventListener(in DOMString type,
                                   in EventListener listener,
                                   in boolean useCapture);
  boolean      dispatchEvent(in Event evt)
                                  raises(EventException);
};

Для нас сейчас существенно то, что третий параметр метода addEventListener (useCapture) — флаг, говорящий браузеру, на какой стадии прохождения события должен быть запущен добавляемый обработчик.
Реализующий w3c-модель браузер при появлении события сначала проходит по иерархии документа сверху вниз, как в capturing-модели, затем, когда добирается до целевого элемента, инициировавшего событие, исполняет его обработчик события и совершает путь снизу вверх по иерархии документа, как в bubbling-модели. В w3c-модели при обработке события выделяют capturing- и bubbling-фазы прохождения события. Таким образом можно выстраивать очень гибкие системы, особенно если учесть тот факт, что на любой фазе можно остановить дальнейшее «продвижение» прохождения события (об этом ниже).

Таким образом, любой элемент документа в w3c-модели (в отличие от IE bubbling model и NN4 capturing model) имеет два стэка для хранения обработчиков событий — для хранения обработчиков для capturing-стадии и для хранения обработчиков, назначенных на исполнение в bubbling-стадии.

Возьмём для примера следующий код:

<html>
<head>
  <title>test</title>
  <script type="text/javascript">

  window.onload = function(e) {

    var doc1Capturing  = function(e) { alert('document: first capturing handler'); };
    var doc2Capturing  = function(e) { alert('document: second capturing handler'); };
    var doc3Capturing  = function(e) { alert('document: third capturing handler'); };

    var doc1Bubbling   = function(e) { alert('document: first bubbling handler'); };
    var doc2Bubbling   = function(e) { alert('document: second bubbling handler'); };
    var doc3Bubbling   = function(e) { alert('document: third bubbling handler'); };

    var body1Capturing = function(e) { alert('body: first capturing handler '); };
    var body2Capturing = function(e) { alert('body: second capturing handler'); };
    var body3Capturing = function(e) { alert('body: third capturing handler '); };

    var body1Bubbling  = function(e) { alert('body: first bubbling handler '); };
    var body2Bubbling  = function(e) { alert('body: second bubbling handler'); };
    var body3Bubbling  = function(e) { alert('body: third bubbling handler '); };

    var div1Capturing  = function(e) { alert('div: first capturing handler '); };
    var div2Capturing  = function(e) { alert('div: second capturing handler'); };
    var div3Capturing  = function(e) { alert('div: third capturing handler '); };

    var div1Bubbling   = function(e) { alert('div: first bubbling handler '); };
    var div2Bubbling   = function(e) { alert('div: second bubbling handler'); };
    var div3Bubbling   = function(e) { alert('div: third bubbling handler '); };

    document.addEventListener('click', doc1Capturing, true);
    document.addEventListener('click', doc2Capturing, true);
    document.addEventListener('click', doc3Capturing, true);

    document.addEventListener('click', doc1Bubbling, false);
    document.addEventListener('click', doc2Bubbling, false);
    document.addEventListener('click', doc3Bubbling, false);

    document.body.addEventListener('click', body1Capturing, true);
    document.body.addEventListener('click', body2Capturing, true);
    document.body.addEventListener('click', body3Capturing, true);

    document.body.addEventListener('click', body1Bubbling, false);
    document.body.addEventListener('click', body2Bubbling, false);
    document.body.addEventListener('click', body3Bubbling, false);

    document.all['text'].addEventListener('click', div1Capturing, true);
    document.all['text'].addEventListener('click', div2Capturing, true);
    document.all['text'].addEventListener('click', div3Capturing, true);

    document.all['text'].addEventListener('click', div1Bubbling, false);
    document.all['text'].addEventListener('click', div2Bubbling, false);
    document.all['text'].addEventListener('click', div3Bubbling, false);

  };

  </script>
</head>
<body>
   <h1>header</h1>
   <div id="text">text</div>
</body>
</html>

Если нажать в приведённом примере на div, порядок выполения обработчиков должен быть следующим:

Вы видите текст, заменяющий изображение. Пожалуйста, включите графику в браузере.

Capturing-фаза:

  1. document:
    • doc1Capturing
    • doc2Capturing
    • doc3Capturing
  2. body:
    • body1Capturing
    • body2Capturing
    • body3Capturing

Bubbling-фаза:

  1. div:
    • div1Bubbling
    • div2Bubbling
    • div3Bubbling
  2. body:
    • body1Bubbling
    • body2Bubbling
    • body3Bubbling
  3. document:
    • doc1Bubbling
    • doc2Bubbling
    • doc3Bubbling

Я не забыл указать в capturing-фазе обработчики события, установленные на элемент div на исполнение в capturing-фазе (div1Capturing, div2Capturing, div3Capturing), они не должны быть запущены. Спецификация указывает, что обработчики событий, назначенные на capturing-фазу, должны выполняться только для родителей целевого элемента. В данном случае только Opera следует стандарту.

Этот баг в FF открыт уже 2 с половиной года, когда же будет решение, неизвестно.

При появлении события в соответствии с w3c-моделью браузер должен:
  • подготовить маршрут для обхода дерева от корневого элемента до целевого (приготовление к capturing-фазе). Этот пункт важен, так как в процессе прохождения фазы порядок исполнения обработчиков нельзя будет изменить даже посредством удаления элементов, на которые они установлены, можно будет лишь остановить процесс прохождения фазы вообще. Об этом позже.
  • запустить обхода дерева в capturing-фазе:
    для каждого последующего элемента в иерархии проверить наличие обработчиков события данного типа, в случае наличия таковых, запустить. Как только событие приходит к целевому элементу, исполнение capturing-фазы прекратить.
  • подготовить маршрут для обхода дерева от целевого элемента до корневого (приготовления для bubbling-фазы). Этот пункт важен, так как в процессе прохождения фазы порядок исполнения обработчиков нельзя будет изменить даже посредством удаления элементов, на которые они установлены, можно будет лишь остановить процесс прохождения фазы вообще. Об этом позже.
  • запустить обхода дерева в bubbling-фазе:
    для каждого последующего элемента в иерархии проверить наличие обработчиков события данного типа, в случае наличия таковых, запустить. После исполнения обработчиков события данного типа, назначенных на корневой элемент (document) на исполнение в bubbling-фазе закончить процесс прохождения фазы.
Фактически получается, что изменить порядок выполнения обработчиков можно только для обработчиков, назначенных на исполнение в bubbling-фазе и только из обработчиков, выполняющихся в capturing-фазе.

Резюмируем:

  1. bubbling-модель подразумевает прохождение дерева (x)html-документа снизу вверх, от целевого элемента, возбудившего события, до корневого элемента document. При прохождении каждого элемента проверяется, не зарегистрировано ли у него обработчиков события такого же типа, если да, запуск этих обработчиков.
  2. capturing-модель подразумевает прохождение дерева (x)html-документа сверху вниз, от корневого элемента document до целевого элемента, возбудившего событие. При прохождении каждого элемента проверяется, не зарегистрировано ли у него обработчиков события такого же типа, если да, запуск этих обработчиков.
  3. W3C DOM2 Events-модель сочетает в себе обе модели. При возбуждении события браузер обходит документ сначала сверху вниз по иерархии (capturing-фаза), при наличии выполняя обработчики события данного типа у каждого из предков целевого элемента; затем переходит к bubbling-фазе, при которой сначала запускаются обработчики целевого элемента, назначенные на bubbling-фазу, затем браузер идёт вверх по иерархии документа, запуская обработчики события данного типа (в случае наличия) у каждого следующего элемента в иерархии, достигая корневого элемента document, выполняя его обработчики события, назначенные на bubbling-фазу. Ещё раз акцентирую внимание: обработчики, назначенные на capturing-фазу, не должны быть запущены на целевом элементе. Я думаю, баг в FF всё-таки исправят и этот браузер тоже начнет следовать стандарту.
Что я планирую осветить в продолжении:
  1. останов фаз прохождения события
  2. доступ к объекту события
  3. доступ к объекту целевого элемента
  4. предотвращение запуска системного обработчика
  5. и многое другое…

show cut

воскресенье, Июль 23, 2006

здоровый образ жизни :)

Последние пару недель вместе с походами в тренажёрный зал начал понемногу бегать кроссом. Сначала была хотьба на беговой дорожке — 500м. → километр → миля → 700м. хотьбы + 300м. кросса... Потом стал бегать после того, как провожу Дашу (где-то порядка 6 минут быстрого кросса), постепенно наращивая расстояния (в предпоследний раз пробежал еще круг около парка(общее время порядка 15 минут, в последний раз сделал дальний крюк через Спутник, тоже вышло около 15 минут). И вот сегодня нужно было тащиться на дачу (7км. от дома). В общем, я побежал :) Ничего так, думал раньше сдохну, пробежал до дач, и уже внизу не добежал буквально 5 улиц (около 500м., я думаю). Состояние непередаваемо клёвое — под конец закололо в боку, но терпимо, зато сколько удивлённых взглядов поймал, когда бежал по дороге к ГЭСу :))) Супер :) Ну и, нечего говорить, после холодно-дачного душа, тарелки борща и второго жизнь просто-таки обрела второй смысл %)
Так что кроссам на средние дистанции быть! Даешь не больше 10% жира в организме %)

show cut

Archives:

Powered by Blogger