Комментарии 21
Однозначно в закладки. Сколько не искал, нигде нет нормальной информации о таких случаях. Везде только 1 маленький компонент и ничего более.
Уже намучился писать костыли для модальных окон (не переживайте, это pet-проекты, никакого продакшена), а времени погрузиться во внутреннюю кухню Vue нет (хотя может и стоит найти для этого время).
Спасибо большое за подробную статью. Продолжайте писать дальше о таких моментах.
Уже намучился писать костыли для модальных окон (не переживайте, это pet-проекты, никакого продакшена), а времени погрузиться во внутреннюю кухню Vue нет (хотя может и стоит найти для этого время).
Спасибо большое за подробную статью. Продолжайте писать дальше о таких моментах.
Да, внутри Vue, не менее интересен, чем снаружи. Рекомендую его всячески ковырять и испытывать на прочность при случае. Откроете много интересных возможностей, о которых не напишут в документации. Впрочем — это применимо для любого инструмента.
Лично для меня, изучать его — одно удовольствие. В данный момент времени — Vue — для меня идеал из себе подобных.
Хотя я постоянно мониторю эту сферу, ведь мир JS так изменчив :)
Писать однозначно буду. И немало.
Лично для меня, изучать его — одно удовольствие. В данный момент времени — Vue — для меня идеал из себе подобных.
Хотя я постоянно мониторю эту сферу, ведь мир JS так изменчив :)
Писать однозначно буду. И немало.
вообще не нравится, что верстка кривая, что реализация
Понравиться всем невозможно.
Да и нет такой цели. Цель в другом — делиться знаниями с людьми. И конкретно в этой статье речь о Vue. О случаях необычных и нестандартных, по крайне мере для новичка. А верстка здесь дело 10-е.
Но я буду рад, если вы опишите эти недостатки в реализации. Да и по верстке не помешает. Думаю и другим будет интересно.
Да и нет такой цели. Цель в другом — делиться знаниями с людьми. И конкретно в этой статье речь о Vue. О случаях необычных и нестандартных, по крайне мере для новичка. А верстка здесь дело 10-е.
Но я буду рад, если вы опишите эти недостатки в реализации. Да и по верстке не помешает. Думаю и другим будет интересно.
Код не смотрел, смотрел только демки по ссылке.
Окна ни разу не модальные — TAB-ом фокус вполне себе ходит по «фоновым» элементам, позволяя работать с немодальными контролами:
Окна ни разу не модальные — TAB-ом фокус вполне себе ходит по «фоновым» элементам, позволяя работать с немодальными контролами:
Смысл статьи не в том, чтобы сделать полностью рабочую версию в виде плагина. В этой статье рассказывается, как именно это можно реализовать, а именно, один переиспользуемый динамический компонент через рендер-функции.
Ситуация с табами очень напоминает историю про хакера и солонки в кафе.
Ситуация с табами очень напоминает историю про хакера и солонки в кафе.
Ситуация с табами очень напоминает историю про хакера и солонки в кафе.Вы под лозунгом «всё не предусмотришь» предлагаете всё делать по принципу «и так сойдёт»?
Есть описание, что должен представлять из себя модальный диалог:
"...users cannot interact with content outside an active dialog window...", "...That is, Tab and Shift + Tab do not move focus outside the dialog. However, unlike most non-modal dialogs, modal dialogs do not provide means for moving keyboard focus outside the dialog window without closing the dialog...".
Смысл статьи не в том, чтобы сделать полностью рабочую версию в виде плагина. В этой статье рассказывается, как именно это можно реализоватьНе важно, насколько хорошо исполнено что-то, если оно плохо делает то, для чего предназначается.
Согласен с вами. И спасибо за подсказку. Уже все поправил и перезалил.
Вот добавленное в коде
modal-wrapper.js
и modal.js
mounted() {
// подписываемся на событие keydown
if (typeof document !== 'undefined') {
document.body.addEventListener('keydown', this.handleTabKey)
}
}
destroyed() {
// отписываемся
if (typeof document !== 'undefined') {
document.body.removeEventListener('keydown', this.handleTabKey)
}
},
methods: {
handleTabKey(e) {
if (e.keyCode === 9 && this.modals.length) {
e.preventDefault() // если есть окна, глушим Tab/Shift-Tab
}
}
// пока полностью отключаю Tab. Надо подумать, как лучше его глушить только вне активного окна.
}
и modal.js
mounted() {
if(this.$el.focus) {
this.$el.focus() // фокус переводим на окно, при монтировании
}
},
render(h) {
...
return h('div', {
style,
attrs: { tabindex: -1}, // цепляем tabindex
class: ['vu-modal__cmp', {
...
},
пока полностью отключаю Tab. Надо подумать, как лучше его глушить только вне активного окна.Это как раз понятно:
function nodeContains(parentNode, node) {
return parentNode && node && (node === parentNode || contains());
function contains() {
return parentNode.contains ? parentNode.contains(node) : !!(parentNode.compareDocumentPosition(node) & Node.DOCUMENT_POSITION_CONTAINED_BY);
}
}
if (nodeContains(activeWindow.node, document.activeElement)) {
// сфокусированный элемент находится в activeWindow
} else {
// сфокусированный элемент находится вне activeWindow
}
Но такой подход («глушить tab») не закрывает проблему:
- При tab-е «снаружи» страницы (с активного контрола браузера) keydown не «стреляет», и происходит фокусировка на первый фокусируемый элемент страницы (в примере это link «Vue-universal-modal», указывающий на "#/")
- Если не «глушить» tab внутри активного окна (а по-хорошему, глушить его нельзя, иначе пользователь не сможет tab-ом перемещяться по фокусируемым элементам активного окна), то нужно как-то определять момент, когда «сфокусирован элемент внутри окна, но при tab-бировании фокус уйдёт вовне», а вот как это сделать — я уже не очень представляю :)
На досуге буду думать над этой проблемой. Решение всегда можно найти, если хорошо поискать :)
Рекомендую обратить внимание на этот комментарий. Да и вообще на статью :)
Я делал однопиксельные скрытые поля ввода в начале и конце модального окна, которые при фокусе перекидывали фокус обратно в окно.
Так же, чтобы нельзя было из адресной строки перейти табом к контролам позади модального окна, можно вставить спрятанные поля ввола в начале и конце документа.
Во-первых — реализация в целом понравилась. Но по-моему это для простых случаев, когда в модал нужно показать уже имеющиеся данные.
Но у меня были задачи, когда нужно в модалку вывести другой компонент + чтобы была возможность дать странице с открытой модалкой свой url.
Пример: таблица со списком пользователей, клик на имя открывает модалку с компонентом UserProfile, который в свою очередь достает инфо по id.
Также компонент UserProfile пользуется и на отдельной странице — профиль пользователя.
Честно, мне пришлось городить свои огороды с named router-view.
Но у меня были задачи, когда нужно в модалку вывести другой компонент + чтобы была возможность дать странице с открытой модалкой свой url.
Пример: таблица со списком пользователей, клик на имя открывает модалку с компонентом UserProfile, который в свою очередь достает инфо по id.
Также компонент UserProfile пользуется и на отдельной странице — профиль пользователя.
Честно, мне пришлось городить свои огороды с named router-view.
Так любой компонент мы и сейчас можем вывести в окне.
А по поводу страницы с модалкой со своим url. Может создавать нужный роут в момент открытия окна? В реальном времени. У vue-router вроде есть возможность динамического добавления. Надо только подумать, как после закрытия окна удалять этот роут. Покопаться в исходниках роутера надо. И так каждый раз. Думаю на производительность приложения сильно этот момент не повлияет.
А для этих динамически добавляемых/удаляемых роутов сделать шаблонную страницу, где будем выводить окно. По сути окна то у нас не привязаны к страницам сейчас. Их обертка висит в корневом шаблоне приложения. Страница-пустышка нужна для роута. Роут нужен для правильной обработки адресной строки в браузере. Как то так, на первый взгляд.
А по поводу страницы с модалкой со своим url. Может создавать нужный роут в момент открытия окна? В реальном времени. У vue-router вроде есть возможность динамического добавления. Надо только подумать, как после закрытия окна удалять этот роут. Покопаться в исходниках роутера надо. И так каждый раз. Думаю на производительность приложения сильно этот момент не повлияет.
А для этих динамически добавляемых/удаляемых роутов сделать шаблонную страницу, где будем выводить окно. По сути окна то у нас не привязаны к страницам сейчас. Их обертка висит в корневом шаблоне приложения. Страница-пустышка нужна для роута. Роут нужен для правильной обработки адресной строки в браузере. Как то так, на первый взгляд.
А как быть в случае, если поп-ап является частью формы?
К примеру в нём происходит выбор адреса или режима работы. (реальный случай)
То есть как раз такой случай, когда поп-ап должен иметь доступ к родительской модели. И при этом в разметке он там совсем плохо размещается.
Решал такие задачи через vue-portal, но у него минусов хватает.
Для этого и есть коллбек onClose. Вызываем из формы наше окно с выбором чего-то там, передавая в опциях нужные параметры родительской модели. И в опциях передаем коллбек onClose, в который мы будем при закрытии окна методом this.$modals.close(data) передавать нужный ответ (data) и его уже использовать в родительской модели.
Код для наглядности
Как-то так, если я правильно понял суть проблемы.
// запускаем из формы окно
this.$modals.open({
component: ModalForm // компонент, отображаемый в окне, где происходит выбор чего-то,
props: {
params1, // родительские данные, которые будут использоваться в окне
params2,
...
},
onClose(data) { // data мы будем передавать при закрытии окна
// здесь мы уже в родительской области видимости, и используем data как нам нужно
...
return false // если здесь мы вернем false, то окно не закроется. Например при каком-то условии, по значениям из data
}
})
// а в окне, например, при клике по кнопке 'Сохранить' вызываем
this.$modals.close(data)
// тем самым запуская закрытие окна, но прежде чем окно закроется, запускается коллбек onClose, если он есть в опциях окна, c переданными в него данными
Как-то так, если я правильно понял суть проблемы.
Спасибо за пример.
Это рабочий вариант, но он несёт ряд усложнений.
Нам нужно сделать отдельный компонент, описать в нём логику передачи нужных данных в коллбэке, определить метод в родительском компоненте, который эти данные примет и использует…
Согласитесь, это всё значительно сложнее, чем свойство showModal и пара текстовых инпутов внутри модалки с привязкой v-model.
По большому счёту, все эти пляски с шинами событий, коллбэками, порталами и прочим — просто из-за проблем с вёрсткой. Ну не хочет глубоко вложенный элемент с positon: fixed вырваться от своих родителей и стать сильным и независимым. Добавим transform к родителю и всё, приплыли.
Я использовал оба подхода.
Получалось и примерно ваше решение, и магия с github.com/LinusBorg/portal-vue
Не то, чтобы теорема Эскобара, но всё равно не идеально, везде какой противный компромисс.
В целом, считаю что решение которое предлагаете вы — менее Vue-way (в смысле «вау пара свойств и магия»), но, пожалуй, более адекватно и надежно.
Это рабочий вариант, но он несёт ряд усложнений.
Нам нужно сделать отдельный компонент, описать в нём логику передачи нужных данных в коллбэке, определить метод в родительском компоненте, который эти данные примет и использует…
Согласитесь, это всё значительно сложнее, чем свойство showModal и пара текстовых инпутов внутри модалки с привязкой v-model.
По большому счёту, все эти пляски с шинами событий, коллбэками, порталами и прочим — просто из-за проблем с вёрсткой. Ну не хочет глубоко вложенный элемент с positon: fixed вырваться от своих родителей и стать сильным и независимым. Добавим transform к родителю и всё, приплыли.
Я использовал оба подхода.
Получалось и примерно ваше решение, и магия с github.com/LinusBorg/portal-vue
Не то, чтобы теорема Эскобара, но всё равно не идеально, везде какой противный компромисс.
В целом, считаю что решение которое предлагаете вы — менее Vue-way (в смысле «вау пара свойств и магия»), но, пожалуй, более адекватно и надежно.
Ну правильно, для каждой ситуации — свое решение.
Но главное преимущества подхода, который используется здесь — позволяет убрать дублирование кода. Вот нужно вам одно и тоже окно на нескольких страницах, вы будете его вставлять на каждую, и само окно станет привязано к данным страницам. Более того, если нужно будет сделать изменения в этом окне, нужно будет лазить по всем местам, где оно используется и делать изменения. А так мы скинули все наши компоненты с окнами в одно место, например в папку modals и все. Одно окно с дефолтными настройками — используем где хотим, и когда надо, через опции заменяем его дефолтные настройки.
Мне например так намного удобнее. И добавить родительский метод, который примет данные, для меня более очевиден. Так как компонент в окне по сути — это отдельная сущность, и изменять данные родителя напрямую, не есть хорошо.
Но главное преимущества подхода, который используется здесь — позволяет убрать дублирование кода. Вот нужно вам одно и тоже окно на нескольких страницах, вы будете его вставлять на каждую, и само окно станет привязано к данным страницам. Более того, если нужно будет сделать изменения в этом окне, нужно будет лазить по всем местам, где оно используется и делать изменения. А так мы скинули все наши компоненты с окнами в одно место, например в папку modals и все. Одно окно с дефолтными настройками — используем где хотим, и когда надо, через опции заменяем его дефолтные настройки.
Мне например так намного удобнее. И добавить родительский метод, который примет данные, для меня более очевиден. Так как компонент в окне по сути — это отдельная сущность, и изменять данные родителя напрямую, не есть хорошо.
через npm i vue-universal-modal поставить не получилось. Как вообще включить это в проект? Только руками?
Зарегистрируйтесь на Хабре, чтобы оставить комментарий
Ох уж эти модальные окна или почему я полюбил render-функции в VueJs