Открываем всплывающее окно через window.open: параметры, блокировщики, безопасность

РазделJavaScript и CSSУровеньНовичок Чтение8 минОпубликовано ТегиJavaScriptwindow.openASP.NET

Окно выбора даты, справка к полю, печатная форма: в Web Forms-проектах всплывающие окна встречаются повсюду. Разбираем window.open так, чтобы окно открывалось, не блокировалось и не открывало дыру.

Главное. Открывайте окна только по клику, оставляйте настоящую ссылку в href как запасной путь и проверяйте результат window.open. Для чужих адресов отрезайте связь через noopener.

Когда всплывающее окно уместно

Отдельное окно браузера хорошо подходит для вспомогательных задач: выбрать запись из большого справочника и вернуть её в форму, показать версию документа для печати, открыть подробную справку, не теряя заполненную форму. В приложениях на ASP.NET Web Forms 2008 года такие окна были стандартным приёмом, потому что модальных диалогов на HTML почти никто не делал.

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

Синтаксис window.open

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

Метод возвращает ссылку на объект нового окна. Через неё можно вызвать focus, чтобы поднять окно поверх остальных, или close, чтобы закрыть его из родительской страницы.

popup.jsJavaScript
// Открывает (или переиспользует) окно с заданным именем
function OpenPopup(url, name, width, height) {
  var features = [
    'width=' + width,
    'height=' + height,
    'resizable=yes',   // пусть пользователь меняет размер
    'scrollbars=yes',  // без прокрутки часть формы может пропасть
    'status=no',
    'toolbar=no',
    'menubar=no'
  ].join(',');
  var win = window.open(url, name, features);
  if (win) win.focus();
  return win;
}
Не отключайте resizable и scrollbars: при крупном системном шрифте содержимое не влезет в заданный размер, и пользователь не сможет добраться до кнопки «Сохранить».

Центрирование окна

По умолчанию браузер ставит новое окно куда ему удобно. Чтобы окно появилось по центру текущего, нужно рассчитать параметры left и top из положения и размеров родительского окна. Свойства для этого в старых браузерах назывались по-разному: screenX и outerWidth в Firefox, screenLeft и document.documentElement.clientWidth в Internet Explorer. Функция ниже берёт то, что доступно.

popup.jsJavaScript
function OpenCentered(url, name, width, height) {
  var x = window.screenX != null ? window.screenX : window.screenLeft;
  var y = window.screenY != null ? window.screenY : window.screenTop;
  var w = window.outerWidth || document.documentElement.clientWidth;
  var h = window.outerHeight || document.documentElement.clientHeight;
  var left = Math.round(x + (w - width) / 2);
  var top = Math.round(y + (h - height) / 2);
  var features = 'width=' + width + ',height=' + height +
    ',left=' + left + ',top=' + top + ',resizable=yes,scrollbars=yes';
  var win = window.open(url, name, features);
  if (win) win.focus();
  return win;
}

Блокировщики и запасной вариант

Блокировщик пропускает окно, только если вызов window.open произошёл синхронно внутри обработчика клика или нажатия клавиши. Если открыть окно в setTimeout, после ответа AJAX-запроса или при загрузке страницы, вызов вернёт null. Проверяйте результат и сообщайте пользователю, что окно заблокировано.

Лучший приём для доступности: делать всплывающее окно обычной ссылкой с настоящим адресом в href и перехватывать клик. Если скрипт не сработал или окно заблокировано, страница просто откроется в текущей вкладке. Ссылку можно открыть и в новой вкладке колёсиком мыши, а поисковик и скринридер видят нормальный переход.

Default.aspxASPX
<a href="Lookup.aspx?field=<%= txtCustomer.ClientID %>"
   onclick="return !OpenPopup(this.href, 'lookup', 600, 450);">
  Выбрать клиента
</a>
<%-- Если окно открылось, OpenPopup вернёт объект,
     и return false отменит обычный переход --%>

Возврат значения в родительскую форму

Окно справочника должно передать выбранное значение обратно. У дочернего окна есть свойство window.opener со ссылкой на окно, которое его открыло, поэтому можно найти нужное поле и записать в него значение, а затем закрыть себя. Перед обращением стоит проверить, что родитель ещё открыт.

В ASP.NET имена элементов на клиенте отличаются от серверных ID из-за контейнеров и мастер-страниц. Передавайте в окно ClientID поля через строку запроса, как в примере выше, а не жёстко прописывайте идентификатор.

Lookup.aspx (скрипт)JavaScript
// Вызывается при выборе строки в справочнике
function ReturnValue(value) {
  var p = window.opener;
  if (p && !p.closed) {
    var id = new RegExp('[?&]field=([^&]+)').exec(location.search);
    var box = id && p.document.getElementById(decodeURIComponent(id[1]));
    if (box) box.value = value;
  }
  window.close();
}

Безопасность: opener работает в обе стороны

Ссылка window.opener есть у любой страницы, открытой из вашей, в том числе у чужой. Если сайт открывает внешний адрес в новом окне, эта страница может выполнить opener.location = «поддельный адрес» и незаметно подменить вашу вкладку страницей входа злоумышленника. Атака известна как tabnabbing.

Для внешних ссылок добавляйте rel=«noopener noreferrer» к тегу a, а в window.open передавайте noopener в строке параметров. В этом случае вызов вернёт null, поэтому такой вариант подходит только для окон, которыми не нужно управлять.

Как это делают сегодня

Вместо отдельного окна для справочника сегодня используют встроенный элемент dialog с методом showModal: он модальный, доступен с клавиатуры и не блокируется. Метод showModalDialog из времён Internet Explorer удалён из браузеров. Для ссылок с target=«_blank» браузеры теперь сами подразумевают noopener, но явный атрибут остаётся хорошей привычкой.

Вопросы и ответы

Почему window.open возвращает null?

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

Как открыть ссылку в новой вкладке, а не в окне?

Вызовите window.open без строки параметров или используйте ссылку с target=«_blank». Окно с размерами браузер откроет отдельно, без них решение о вкладке остаётся за пользовательскими настройками.

Почему opener равен null в дочернем окне?

Окно открыто с noopener, ссылкой с rel=«noopener» или страница перешла на другой домен с политикой COOP. Для обмена данными в таком случае используйте postMessage или BroadcastChannel.

Читайте также