Введение

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

Итак, приступим!

Поддержка браузерами

Вместо тысячи слов:

Поддержка браузерами

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

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

Поле для экспериментов получается у нас небольшое. Жаль.

Уходя от горьких нот в части введения и поддержки браузерами, давайте перейдём к самому интересному — практике.

Разметка

Раньше диалоговые и модальные окна верстались примерно так:

<!-- Dialog -->
<div class="modal" id="myWindowOne" role="dialog">
  <div class="modal-header">
    <h3>Dialog</h3>
  </div>
  <div class="modal-main">
    <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Facilis, neque quasi. Distinctio sapiente modi, sequi libero unde blanditiis impedit sit dolorem vero deleniti facilis a debitis voluptatum. Laboriosam, nostrum sint!</p>
  </div>
  <div class="modal-footer">
    <button class="btn" data-action="modal-close">Close</button>
  </div>
</div>

<!-- Show dialog -->
<button class="btn" data-tools="dialog" data-target="#myWindowOne">Show dialog</button>

Сейчас же это делается немного иначе:

<!-- Dialog -->
<dialog class="modal" id="myWindowOne" role="dialog">
  <header class="modal-header">
    <h1>Dialog</h1>
  </header>
  <main class="modal-main">
    <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Facilis, neque quasi. Distinctio sapiente modi, sequi libero unde blanditiis impedit sit dolorem vero deleniti facilis a debitis voluptatum. Laboriosam, nostrum sint!</p>
  </main>
  <footer class="modal-footer">
    <button class="btn" data-action="modal-close">Close</button>
  </footer>
</dialog>
    
<!-- Show dialog -->
<button class="btn" data-tools="dialog" data-target="#myWindowOne">Show dialog</button>

Отличий не так много, но они существенны:

  • Элемент скрыт по умолчанию.
  • Поддерживается семантическая вёрстка внутри элемента <dialog>.

Если добавить к элементу атрибут open, то перед нами предстанет само диалоговое окно:

Внешний вид элемента dialog по умолчанию

Предпросмотр

Мне такой вид совсем не улыбается и работать с таким «красавцем» мне не хочется. Давайте приукрасим наш элемент:

Внешний вид элемента dialog после стилизации

В данной статье речь идёт не об умении использовать CSS, поэтому стили диалогового окна вы можете написать сами или посмотреть по ссылкам на jsFiddle прилагаемым к каждому пункту :)

Франкенштейн

Для того, чтобы наш элемент был полезным, его нужно оживить. Поможет нам в этом интерфейс элемента <dialog>. В этом разделе статьи мы обговорим теорию использования элемента.

И перед тем, как начать говорить о методах и свойствах, которые предоставляет нам API, нужно расставить все точки над понятием диалогового и модального окна:

Диалоговое окно

Диалоговым окном называют такое окно, которое временно появляется поверх приложения (в нашем случае сайта) и запрашивает и/или предоставляет различную информацию.

Модальное окно

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

Благодаря интерфейсу HTMLDialogElement, который несёт в себе несколько свойств, методов и событий, мы сможем управлять нашим диалогом. Рассмотрим этот интерфейс подробнее.

Методы

Нам доступны три метода, которые позволяют:

  • show() — Открыть диалоговое окно.
  • showModal() — Открыть модальное окно.
  • close() — Закрыть окно.

Свойства

В свойствах тоже нет большого разнообразия:

  • returnValue — Возвращаемое значение, которое можно передать в close().
  • open — Логическое значение. Если true, то окно показано, если false, то окно скрыто.

События

А тут у нас всё ещё хуже:

  • close — Событие закрытия окна.

Разное

Помимо основных методов и свойств, элемент <dialog> также поддерживает:

  • form[method="dialog"] — Интеграция с формы с элементом. Действует только внутри диалогового и модального окна.
  • autofocus — Перемещение фокуса на элемент управления формой внутри окна.
  • cancel — Закрытие окна при нажатии кнопки Esc.

Маловато будет! Перейдём к примерам работы.

Реализация

Давайте попробуем реализовать диалоговые и модальные окна и их взаимодействие с окружающими и внутренними элементами.

Для примеров я буду использовать библиотеку Zepto.

Zepto

Библиотека Zepto по своей сути является аналогом всем известной библиотеки jQuery. Основное отличие заключается в поддержке браузеров и некотором несущественном расхождении в API.

Вот именно сейчас в вас может заиграть желание сетовать на отсутствие ванильного JavaScript в примерах, однако поспешу вас разочаровать — камнем преткновения для нас с вами будет сайт «You Might Not Need jQuery», на котором вы сможете посмотреть реализацию используемых плюшек библиотеки Zepto на ванильном JavaScript. Не думаю, что эта процедура займёт у вас уйму времени. Мне же библиотека даст возможность обратить немного больше внимания на интерфейс HTMLDialogElement, чем на реализацию некоторых побочных функций.

Открытие и закрытие диалогового окна.

Для того, чтобы работать со многими диалоговыми окнами как раз и были введены атрибуты data-tools и data-target.

// Получаем массив всех кнопок с атрибутом [data-tools = dialog]
var dialogs = $('button[data-tools="dialog"]');

// Перебираем в цикле каждый элемент
$.each(dialogs, function(i) {
  var dialog = $(dialogs[i]);
  
  // Извлекаем ID вызываемого диалогового окна [data-target = ID]
  var target = $(dialog.data('target'));
  
  // Обрабатываем событие нажатие кнопки
  dialog.on("click", function(even) {
    // Отображаем окно ([0] здесь применяется для вызова нативного метода show)
    target[0].show();
  });
  
  // Обрабатываем нажатие кнопки закрытия
  target.find('button[data-action="modal-close"]').on("click", function(even) {
    // Закрываем окно ([0] здесь применяется для вызова нативного метода close)
    target[0].close();
  });
});

Предпросмотр

Открытие и закрытие модального окна. Затемнение.

Работа с модальными окнами полностью повторяет таковую с диалоговым окном с той лишь разницей, что метод show() заменяется на showModal.

Теперь наше окно выглядит иначе:

Внешний вид элемента dialog в режиме модального окна

Кратко об изменениях:

  • Центрирование не только по горизонтали, но и по вертикали.
  • Окно отбрасывает на всю страницу фон.
  • Пользователь не может взаимодействовать с объектами на странице, отличными от объектов самого окна.

Управлять «тенью» можно с помощью CSS, используя псевдоэлемент ::backdrop:

.modal::backdrop {
  background-color: #2575ed;
  opacity: .5;
}

Для примера, я изменил цвет затемнения на синий:

Внешний вид элемента dialog в режиме модального окна с изменённым цветом фона

Предпросмотр

Возвращение значения из модального или диалогового окна.

Для передачи значения мы будем использовать метод close() и событие закрытия close.

// Получаем массив всех кнопок с атрибутом [data-tools = dialog]
var dialogs = $('button[data-tools="dialog"]');

// Перебираем в цикле каждый элемент
$.each(dialogs, function(i) {
  var dialog = $(dialogs[i]);

  // Извлекаем ID вызываемого диалогового окна [data-target = ID]
  var target = $(dialog.data('target'));

  // Обрабатываем событие нажатие кнопки
  dialog.on("click", function(even) {
    // Отображаем окно ([0] здесь применяется для вызова нативного метода show)
    target[0].show();
  });

  // Обрабатываем нажатие кнопки закрытия
  target.find('button[data-action="modal-close"]').on("click", function(even) {
    // Получаем введённое значение
    var value = $('input[name="value"]').val();
    
    // Закрываем окно ([0] здесь применяется для вызова нативного метода close)
    // Возврат значения из диалога
    target[0].close(value);
  });

  // Обрабатываем событие закрытия модального окна
  target.on("close", function(even) {
    // Для примера, отображаем возвращённые данные, используя alert
    alert(this.returnValue);
  });
});

После закрытия модального или диалогового окна, будь это событие нажатия на кнопку закрытия или нажатие клавиши Esc, будет отображено введённое значение:

Возвращение введённого значения

Разумеется, что картинка «фотошоп» для наглядности, так как alert появился после закрытия модального окна.

Предпросмотр

Интеграция формы в диалоговое или модальное окно

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

Немного изменим нашу разметку:

<!-- Dialog -->
<dialog class="modal" id="myWindowOne" role="dialog">
  <form method="dialog">
    <header class="modal-header">
      <h1>Dialog</h1>
    </header>
    <main class="modal-main">
      <p>Luke, I Am Your Father!</p>
    </main>
    <footer class="modal-footer">
      <button type="submit" class="btn btn-close" data-action="modal-close" value="no">No</button>
      <button type="submit" class="btn btn-accept" data-action="modal-accept" value="yes" autofocus>Yes</button>
    </footer>
  </form>
</dialog>

Теперь наше модальное окно выглядит так:

Интеграция формы с модальным окном

Осталось только слегка поменять наш скрипт. Нам нужно избавиться от обработчика кнопки закрытия окна и обработать возвращаемое значение.

// Получаем массив всех кнопок с атрибутом [data-tools = dialog]
var dialogs = $('button[data-tools="dialog"]');

// Перебираем в цикле каждый элемент
$.each(dialogs, function(i) {
  var dialog = $(dialogs[i]);

  // Извлекаем ID вызываемого диалогового окна [data-target = ID]
  var target = $(dialog.data('target'));

  // Обрабатываем событие нажатие кнопки
  dialog.on("click", function(even) {
    // Отображаем окно ([0] здесь применяется для вызова нативного метода show)
    target[0].show();
  });

  // Обрабатываем событие закрытия модального окна
  target.on("close", function(even) {
    // Для примера, отображаем возвращённые данные, используя alert
    var value = this.returnValue === 'yes' ? "Yes!" : "Noooooooooooooooooooooooooooooooo!";
    alert(value);
  });
});

Вот и всё! Соизвольте наслаждаться полученным результатом:

Возвращённый результат формы

Предпросмотр

Спасение от Google

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

Для его использования необходимо подключить к странице сам полифил (CSS и JS) и, скажем так, активировать его:

dialogPolyfill.registerDialog(dialog);

При всём этом, затемнение фона так же поддерживается. Всего лишь нужно заменить конструкцию вида:

#mydialog::backdrop {
  background-color: green;
}

На вот такую:

#mydialog + .backdrop {
  background-color: green;
}

Деликатное решение проблемы :)

Вывод

Использовать элемент <dialog> можно уже сейчас (если вам не мешает лишний полифил), однако, стоит обратить внимание на рациональность этого решения в вашем проекте.

Помимо всех удобств, что мы уже рассмотрели на примерах в статье, есть ещё одно, которое позволяет не придерживаться идеологии z-index для диалоговых и модальных окон — Стек «верхнего слоя». Браузер сам заботится о том, что последнее вызванное вами окно будет находиться поверх всех остальных и его не перекроет какой-либо другой элемент.

Читайте новостные порталы по тематике веб-разработки, причём регулярно и не только на хорошо известных вам языках.