Статьи

Хорошо себя ведет DHTML: тематическое исследование

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

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

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

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

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

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

Приведенные здесь примеры кода предполагают, что вы немного знакомы с JavaScript, HTML и DOM. Тем не менее, любой веб-разработчик или дизайнер должен иметь возможность получить некоторую пользу от знакомства с этим процессом.

Скрипт ярлыков

Обычное использование DHTML в Интернете — создание так называемой динамической метки. Динамическая метка используется для маркировки поля формы. Тем не менее, текст для метки отображается внутри поля формы, а не рядом с ним (что было бы более обычным).

Когда поле формы получает внимание, метка исчезает, так что пользователь может печатать. Если пользователь ничего не печатает, метка восстанавливается, как только пользователь щелкает или открывает вкладки вне поля.

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

Наивный разработчик может реализовать скрипт динамической метки следующим образом:

<input type="text" name="username" value="username"  onfocus="if (this.value == 'username') this.value = '';"  onblur="if (this.value == '') this.value = 'username';" /> 

Пример A отображает этот тип реализации.

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

Давайте посмотрим на проблемы по очереди:

  1. Опирается на JavaScript

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

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

    В нашем случае JavaScript, который создает наш эффект, тесно связан с кодом сервера, который обрабатывает формы. Код сервера должен знать, какие метки есть для каждого поля формы, и уметь отфильтровывать их из отправки формы. Это потому, что мы поместили текст метки в атрибут value каждого поля. Если пользователь не вводит что-либо в одно (или любое) из этих полей, метка фактически будет отправлена.

    Чтобы увидеть пример этого в действии, просто нажмите Отправить, не вводя ничего в Пример А.

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

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

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

    Если мы обнаружим ошибку в скрипте или захотим внести изменения, мы должны помнить, чтобы вносить изменения для каждой метки. Если мы решим изменить текст метки, мы должны изменить его в трех местах. Немодульные программы трудно поддерживать и развивать, потому что они подвержены ошибкам. Легко делать ошибки и трудно их отлаживать.

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

    • Не полагается на JavaScript
    • Не соединяется с любым другим компонентом
    • Не привязывается исключительно к каким-либо событиям
    • Модульный дизайн

    5 шагов к написанию хорошего поведения DHTML

    Наши цели для производственного сценария Dynamic Label мало чем отличаются от целей большинства усовершенствований DHTML для веб-страниц. Фактически, почти все сценарии, которые я пишу, разделяют эти же цели.

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

    1. Определите основную логическую структуру эффекта.
    2. Создайте полный рабочий пример эффекта.
    3. Определите все требования агента пользователя.
    4. Напишите код для преобразования логической структуры при выполнении требований агента.
    5. Тщательно протестируйте каждую целевую платформу.
    Шаг 1: Определите основную логическую структуру эффекта

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

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

    Лучший способ избежать использования JavaScript — это создать эффекты DHTML поверх простой логической структуры документа, которая не требует этого. Эффект будет включен динамически на клиенте, если он поддерживается. Если нет, пользователь увидит основной документ.

    Логическая структура нашей динамической метки прекрасно работает благодаря наличию HTML-элемента label .

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

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

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

    Шаг 2. Создание полноценного рабочего примера эффекта в лучшем случае

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

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

    1. Скрыть обычный элемент метки HTML.
    2. Присоедините функции JavaScript к событиям onfocus и onblur соответствующего поля, которые показывают и скрывают метку в нужное время.

    Самый простой способ выполнить первую задачу — использовать правило CSS следующим образом:

     <style type="text/css">  label {    display:none;    }  </style> 

    Если вы не знакомы с CSS, вы можете быстро ознакомиться здесь с SitePoint.com или W3C .

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

    Решение, конечно, состоит в том, чтобы дать специальный класс меткам, которые мы хотим вести динамически:

     <style type="text/css">  label.dynamic {    display:none;    }  </style> 

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

    Это требует определенных знаний об объектной модели документа. Если вы ржавеете в деталях или никогда не уделяли времени изучению, вы можете освежиться в W3C . Поставщики браузеров также часто имеют хорошие ресурсы (например, Microsoft и Mozilla ), хотя они явно склоняются к своим собственным реализациям.

    В идеальном мире, когда мы узнали, как работает DOM, мы могли бы использовать следующий код для выполнения нашей задачи. Он использует getElementsByTagName методы getElementById , а также свойство className . Каждый из них определен в DOM Level 1 .

    Этот код также использует метод addEventListener из DOM Level 2 Events .

     n setupLabels() {  // get all the labels on the entire page  var objLabels = document.getElementsByTagName("LABEL");  var objField;   for (var i = 0; i < objLabels.length; i++) {    // if the label is supposed to be dynamic...    if ("dynamicLabel" == objLabels[i].className) {      // get the field associated with it      objField = document.getElementById(objLabels[i].htmlFor);      // add event handlers to the onfocus and onblur events      objField.addEventListener("focus", focusDynamicLabel, false);      objField.addEventListener("blur", blurDynamicLabel, false);      // save a copy of the label text      objField._labelText = objLabels[i].firstChild.nodeValue;      // initialize the display of the label      objField.value = objField._labelText;    }  }  } 

    Однако этот код не будет работать для IE / Windows, потому что он не полностью совместим с DOM; он не поддерживает модуль событий DOM Level 2. Вместо этого он поддерживает собственный интерфейс, который делает то же самое. Поскольку IE / windows имеет такую ​​огромную пользовательскую базу — и мы хотели бы видеть наш эффект — мы добавили небольшой хак в наш скрипт, чтобы приспособить его к другой объектной модели (обратите внимание, что измененные строки выделены жирным шрифтом):

     function setupLabels() {  // get all the labels on the entire page  var objLabels = document.getElementsByTagName("LABEL");  var objField;   for (var i = 0; i < objLabels.length; i++) {    // if the label is supposed to be dynamic...    if ("dynamicLabel" == objLabels[i].className) {      // get the field associated with it      objField = document.getElementById(objLabels[i].htmlFor);      // add event handlers to the onfocus and onblur events  addEvent(objField, "focus", focusDynamicLabel);      addEvent(objField, "blur", blurDynamicLabel);      // save a copy of the label text      objField._labelText = objLabels[i].firstChild.nodeValue;      // initialize the display of the label      objField.value = objField._labelText;    }  }  }   function addEvent(objObject, strEventName, fnHandler) {  // DOM-compliant way to add an event listener    if (objObject.addEventListener)    objObject.addEventListener(strEventName, fnHandler, false);  // IE/windows way to add an event listener    else if (objObject.attachEvent)    objObject.attachEvent("on" + strEventName, fnHandler);  } 

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

     addEvent(window, "load", setupLabels); 

    Теперь все, что нам нужно сделать, это реализовать focusDynamicLabel и blurDynamicLabel . Это просто — это как оригинальный код из нашего первого скрипта динамической метки. Единственное отличие состоит в том, что он должен быть обобщен, чтобы одна и та же функция работала для каждой динамической метки на странице.

    В полностью совместимом с DOM браузере мы могли бы использовать свойство target объекта события (также определенного в событиях уровня 2 DOM ), чтобы получить ссылку на элемент, вызвавший событие, и манипулировать им:

     function focusDynamicLabel(event) {  // get the form field that fired this event  var elm = event.target;  // if it is currently displaying the label...  if (elm._labelText == elm.value) {    // ... turn it off    elm.value = "";  }  }   function blurDynamicLabel(event) {  // get the form field that fired this event  var elm = event.target;  // if it's empty...  if ("" == elm.value) {    // ... display the label text    elm.value = elm._labelText;  }  } 

    Но опять же, IE / windows реализует эту функциональность немного по-другому , используя свойство srcElement вместо стандартизированной target и делая объект события доступным через window.event вместо стандартного способа неявной передачи его функциям-обработчикам событий.

    Нам понадобится еще одна небольшая хакерская и вспомогательная функция:

     function focusDynamicLabel(event) {  // get the form field that fired this event    var elm = getEventSrc(event);  // if it is currently displaying the label...  if (elm._labelText == elm.value) {    // ... turn it off    elm.value = "";  }  }   function blurDynamicLabel(event) {  // get the form field that fired this event    var elm = getEventSrc(event);  // if it's empty...  if ("" == elm.value) {    // ... display the label text    elm.value = elm._labelText;  }  }   function getEventSrc(e) {  // get a reference to the IE/windows event object    if (!e) e = window.event;   // DOM-compliant name of event source property    if (e.target)    return e. target;  // IE/windows name of event source property    else if (e.srcElement)    return e.srcElement;  } 

    Пример C показывает нашу работу до сих пор.

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

    Но как насчет связи между DHTML и кодом, который обрабатывает форму? Если мы оставим поле формы пустым и нажмем кнопку «Отправить», «Имя пользователя» будет отправлено на серверный процесс. Нам все еще нужно решить эту проблему.

    Каждая форма имеет событие onsubmit которое запускается непосредственно перед отправкой ее значений на сервер. Нам просто нужно перебрать каждую форму на странице и добавить наш обработчик события к этому событию. Хорошее место для этого — наша функция настройки:

     function setupLabels() {  // get all the labels on the entire page  var objLabels = document.getElementsByTagName("LABEL");  var objField;   for (var i = 0; i < objLabels.length; i++) {    // if the label is supposed to be dynamic...    if ("dynamicLabel" == objLabels[i].className) {      // get the field associated with it      objField = document.getElementById(objLabels[i].htmlFor);      // add event handlers to the onfocus and onblur events      addEvent(objField, "focus", focusDynamicLabel);      addEvent(objField, "blur", blurDynamicLabel);      // save a copy of the label text      objField._labelText = objLabels[i].firstChild.nodeValue;      // initialize the display of the label      objField.value = objField._labelText;    }  }   // for each form in the document, handle the onsubmit event with the  // resetLabels function    for (var i = 0; i < document.forms.length; i++) {    addEvent(document.forms[i], "submit", resetLabels);  }  } 

    Чтобы реализовать функцию resetLabels , мы делаем противоположное тому, что мы делали в настройке: перебираем каждую метку в форме и проверяем, является ли она динамической меткой. Если это так, и он отображает текст метки, мы сбрасываем его значение в пустую строку.

     function resetLabels(event) {  var elm = getEventSrc(event);  // get all label elements in this form  var objLabels = elm.getElementsByTagName("LABEL");  var objField;   for (var i = 0; i < objLabels.length; i++) {    // if the label is dynamic...    if ("dynamicLabel" == objLabels[i].className) {      // get its associated form field      objField = document.getElementById(objLabels[i].htmlFor);      // if the field is displaying the label, reset it to empty string      if (objField._labelText == objField.value) {        objField.value = "";      }    }  }  } 

    Пример D показывает нашу работу в конце шага 2. Мы успешно преобразовали наш оригинальный структурированный документ в динамический эффект, который мы хотели. Он больше не связан с кодом, обрабатывающим форму, он хорошо работает с другими сценариями и имеет хорошо модульный код.

    Шаг 3: Определите все требования пользователя-агента

    Этот шаг прост: мы просто просматриваем код из шага 2 и идентифицируем все объекты, функции и другие требования браузера, которые мы использовали. Мы будем использовать эту информацию для создания функции JavaScript, которая отсеивает все браузеры, которые не отвечают этим требованиям.

    В скрипте меток мы использовали много разных технологий DOM, но нам действительно нужно протестировать только три:

    • document.getElementById
    • window.attachEvent или
    • window.addEventListener

    Мы можем сделать это с помощью этой простой функции:

     function supportsDynamicLabels() {  // return true if the browser supports getElementById and a method to  // create event listeners  return document.getElementById &&    (window.attachEvent || window.addEventListener);  } 

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

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

    Рекомендации W3C фактически предлагают браузеру способ указать, какие уровни DOM он поддерживает, с hasFeature метода hasFeature . Как ни странно, этот метод не очень хорошо поддерживается.

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

    Шаг 4: Преобразование логической структуры при выполнении требований агента.

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

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

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

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

    Мы удаляем правило таблицы стилей, добавленное на шаге 2, и заменяем его следующим JavaScript:

     if (supportsDynamicLabels()) {  document.writeln('<style type="text/css">');  document.writeln('label { display:none; }');  document.writeln('</style>');  }  

    Мы также перемещаем функцию настройки в ветку «if», потому что хотим, чтобы она запускалась только при соблюдении наших требований:

     if (supportsDynamicLabels()) {  document.writeln('<style type="text/css">');  document.writeln('label { display:none; }');  document.writeln('</style>');   addEvent(window, "load", setupLabels);  } 

    Пример E показывает завершенный эффект.

    Шаг 5: Тщательно протестируйте на всех целевых платформах

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

    Например, простой поиск в Google обнаружит, что Windows IE 5+ , Gecko и Safari, кажется, реализуют необходимые нам функции.

    Однако, если бы вы запустили Пример E в Safari 1.0, вы заметили бы большую проблему: эффект запускается только один раз! При первом щелчке в текстовом поле метка исчезает правильно. Но после размытия ничего не происходит. Текстовое поле остается пустым, и вы никогда не сможете вернуть ярлык снова.

    Оказывается, в Safari есть ошибка — он не запускает onblur для текстового поля, пока не будет сфокусировано следующее текстовое поле. В нашем случае это означает, что если пользователь просто вкладывает или щелкает вне текстового поля, не фокусируя другое текстовое поле, наша метка больше не появится.

    Проблема Safari с onblur является примером ошибки реализации, которую нельзя проверить с помощью простого обнаружения функций. Нам потребуется обновить функцию тестирования функций, чтобы протестировать браузер Safari специально. Следующее изменение сделает свое дело:

     function supportsDynamicLabels() {  return    document.getElementById &&    (window.attachEvent || window.addEventListener) &&  null == navigator.appVersion.match(/Safari/d+$/);  }  

    Добавленная строка использует регулярное выражение для проверки свойства appVersion объекта навигатора и возврата true, если текущий браузер не является Safari.

    При тестировании на конкретный браузер часто лучше тестировать на конкретное частное свойство в объектной модели этого браузера. Например, IE имеет свойство window.clientInformation , которое можно использовать, чтобы однозначно отличить его от других браузеров.

    Однако Safari, похоже, не поддерживает какие-либо собственные свойства. Поэтому мы должны прибегнуть к проверке свойства appVersion этого объекта навигатора. Вы также можете протестировать свойство userAgent , но оно менее надежно, так как оно может быть изменено пользователями некоторых браузеров.

    Пример F показывает нашу последнюю работу. Мы успешно превратили наш первый, плохо себя ведущий скрипт динамической метки в нечто гораздо лучшее. Наш окончательный код является полностью модульным, не использует JavaScript, хорошо работает с другими сценариями и не связан ни с какими другими компонентами.

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

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

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