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

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

Обычно для этого нужно присвоить атрибуту value элемента input значение имени поля. Использую немного JavaScript и серверных скриптов можно гарантировать, что пользователь не подтвердит форму со значениями «Логин» и «Пароль». Чтобы скрыть пароль от посторонних глаз, в поле для его ввода символы заменяются на звездочки и точки, и это не позволяет нам поместить в это поле полезный текст, но даже если бы этой проблемы не было использование значений по умолчанию вместо меток значительно ухудшают аксессибилити формы.

Компактная и доступная форма

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

Начнем с доступной разметки

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

<form name="login" action="#" method="post">
    <div id="username">     
        <label for="username-field" class="overlabel">
            Username
        </label>

        <input id="username-field" type="text" name="username"
            title="Username" value="" tabindex="1" />
    </div>
    <div id="password">
        <label for="password-field" class="overlabel">
            Password
        </label>
        <input id="password-field" type="password" name="password"
            title="Password" value="" tabindex="2" />
    </div>
    <div id="submit">
        <input type="submit" name="submit" value="Login"
            tabindex="3" />
    </div>
</form>

Элементы label и input попарно заключены в контейнер div, чтобы разметка была удобной при просмотре с отключенным CSS, конечно, можно использовать элемент fieldset, но мне кажется одно поле это еще не набор полей.

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

В дополнение к элементам label, для каждого input мы установим атрибут title, это позволит пользователям видеть название поля даже после того как элемент label будет скрыт.

Потом добавим стили.

form#login {
  position:relative;
}

div#username,
div#password {
  position:relative;
  float:left;
  margin-right:3px;
}

input#username-field,
input#password-field {
  width:10em;
}

label.overlabel {
  position:absolute;
  top:3px;
  left:5px;
  z-index:1;
  color:#999;
}

В этих стилях нет ничего удивительного, используется абсолютное позиционирование и z-index, чтобы поместить элементы label на передний план и float, чтобы выстроить элементы input в одну линию.

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

Скрипт

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

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

  label.overlabel {
    color:#999;
  }

  label.overlabel-apply {
    position:absolute;
    top:3px;
    left:5px;
    z-index:1;
    color:#999;
  }

В целях обеспечения хорошего уровня аксессибилити используем для скрытия текста отрицательное значение text-indent вместо display:none.

function initOverLabels () {
  if (!document.getElementById) return;      

  var labels, id, field;

  // устанавливаем обработчики onfocus и onblur
  // чтобы скрывать и отображать метки
  // с именем класса 'overlabel'
  labels = document.getElementsByTagName('label');
  for (var i = 0; i < labels.length; i++) {

    if (labels[i].className == 'overlabel') {

      // пропускаем метки которые не связаны
      // с полем ввода
      id = labels[i].htmlFor || labels[i].getAttribute('for');
      if (!id || !(field = document.getElementById(id))) {
        continue;
      } 

      // изменяем класс метки 
      // чтобы расположить ее перед полем ввода.
      labels[i].className = 'overlabel-apply';

      // скрываем метку если поле
      // имеет значение по умолчанию
      if (field.value !== '') {
        hideLabel(field.getAttribute('id'), true);
      }

      // устанавливаем обработчики
      // скрывающие и отображающие метки
      field.onfocus = function () {
        hideLabel(this.getAttribute('id'), true);
      };
      field.onblur = function () {
        if (this.value === '') {
          hideLabel(this.getAttribute('id'), false);
        }
      };

      // обрабатываем клики на метки (для Safari).
      labels[i].onclick = function () {
        var id, field;
        id = this.getAttribute('for');
        if (id && (field = document.getElementById(id))) {
          field.focus();
        }
      };

    }
  }
};

function hideLabel (field_id, hide) {
  var field_for;
  var labels = document.getElementsByTagName('label');
  for (var i = 0; i < labels.length; i++) {
    field_for = labels[i].htmlFor 
	|| labels[i].getAttribute('for');
    if (field_for == field_id) {
      labels[i].style.textIndent = (hide) ? '-1000px' : '0px';
      return true;
    }
  }
}

window.onload = function () {
  setTimeout(initOverLabels, 50);
};

Скрипт перебирает все элементы label на странице в поисках тех имя класса которых overlabel, определяет связан ли данный label с каким либо элементом input при помощи атрибута for, таким label присваивается класс overlabel-apply и устанавливаются обработчики событий onfocus и onblur для соответствующего элемента input.

Раньше я уже упоминал, что этот скрипт работает независимо от атрибутов name и id элементов input, он находит элемента label с именем класса overlabel, потому что только классы позволяют задать стили для множества элементов на странице, просто здесь они используются еще и для того чтобы придать этим элементам дополнительную функциональность. Снимаю шляпу перед Daniel Nolan, который использовал этот метод в его Image Rollover code еще несколько лет назад.

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

Заметьте, что только в последней части функции initOverLabels используется код специфический для конкретного браузера. Клик на элементе label обычно передает фокус соответствующему элементу input, но в Safari это не так и label не позволяют пользователю воспользоваться полями для ввода данных. Чтобы преодолеть эту проблему мы добавим элементам label обработчик onclick который передает фокус связанному элементу input.

Итог

Работающий пример использует чистую и доступную технику для размещения имени поля в самом поле без использования атрибута value. Подтверждение формы без ввода соответствующих значений передает серверному скрипту пустые поля вместо «Логин» и «Пароль». Кроме того, мы можем быстро добавить эту технику к любой форме, изменив имя класса элементов label и добавив немного CSS и JavaScript.

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

Translated with the permission of A List Apart Magazine and the author[s].