Sezamie, zamknij się!
Czasami banalne czynności okazują się mieć ukrytą głębię. Tak też jest w przypadku… zamykania.
Quiz
Na początek szybki quiz:

Zatem policzmy na szybko:
- Przy pomocy JS-owego API.
- Przy pomocy formularza z atrybutem
[method=dialog]. - Przy pomocy klawiatury (naciśnięcie Esc).
- Przy pomocy kliknięcia w tło (dzięki atrybutowi
[closedby]). - Przy pomocy przycisku “Wstecz” na Androidzie (przycisku telefonu, nie przeglądarki!).
Jestem pewien, że gdyby się uprzeć, dałoby się znaleźć jeszcze kilka innych metod. Niemniej jedno jest pewne: element dialog można zamknąć na zaskakująco dużo sposobów.
Platformowe zamykanie
W przypadku natywnego elementu dialog te wszystkie metody dostajemy “w gratisie”. Jednak gdy tworzymy swój własny modal, musimy sami zadbać o jego poprawne zamykanie:
- JS-owe API możemy bez problemu dodać (i tak modal by bez niego nie działał).
- Zamykanie przy pomocy formularza też da się łatwo zrobić – wystarczy przechwycić zdarzenie
submit. - Obsługa naciśnięcia Esc to z kolei zdarzenie
keydown, względniekeyup. - Kliknięcie w tło da się przechwycić, nasłuchując zdarzenia na całym dokumencie.
- Z kolei zamykanie po naciśnięciu przycisku “Wstecz”… Ha! Tego akurat się nie da!
A przynajmniej: nie da się w sensowny sposób. Zawsze można dodać nowy wpis do historii przeglądania, ale takie rozwiązanie ma zdecydowanie więcej wad niż zalet. Dodatkowo: ten sposób zamykania działa tylko na Androidzie. Urządzenia desktopowe raczej nie mają uniwersalnego przycisku “Wstecz”. Nowy wpis w historii zepsułby tam przycisk “Wstecz” przeglądarki. Więc własne rozwiązanie musiałoby dodatkowo wykrywać, czy ma do czynienia z Androidową przeglądarką… Innymi słowy: trzeba by stworzyć skrajnie przeinżynierowany kod, który i tak często by zawodził.
Zresztą całe te rozważania pomijają jedną, niezwykle istotną kwestię: przeglądarki WWW mogą działać na przeróżnych urządzeniach. Na Androidzie da się zamknąć dialog przy pomocy przycisku “Wstecz”. A co z innymi urządzeniami? Jak choćby smartwatch sterowany głosem? Prawda jest taka, że gdy to przeglądarka zamyka dla nas dialog, pozwala to zrobić na wszystkie sposoby, na które można coś zamknąć na danej platformie. A platformy różnią się dostępnymi sposobami. Na desktopie można nacisnąć Esc, na Androidzie ten nieszczęsny przycisk “Wstecz”, na mikrofali trzeci przycisk od góry…
Podsumowując, nie da się w sensowny sposób stworzyć własnego modala tak, żeby był zamykalny dokładnie tak, jak sobie dana platforma to wymyśliła. Jako osoby tworzące strony po prostu nie mamy dostępu do informacji o samej platformie ani do jej “mechanizmów zamykania” (jakkolwiek by tego nie nazwać). Przeglądarka za to ma – i możliwe, że wkrótce się nim podzieli!
Patrz, jak się zamyka…
Kilka lat temu Chrome zaproponował nowe API, CloseWatchera. To bardzo proste API, którego jedynym celem jest nasłuchiwanie na zamknięcie. Co więcej: to API dostosowuje się do platformy, na której uruchomiona jest przeglądarka, przez co zamykanie zadziała dokładnie tak samo, jak w przypadku natywnego dialoga!
Wyobraźmy sobie więc, że mamy na stronie wjeżdżające menu. Fajnie byłoby dać osobom odwiedzającym stronę możliwość jego zamykania przy pomocy Esc czy, wspomnianego w tym artykule zdecydowanie zbyt dużą liczbę razy, przycisku “Wstecz”. Gdy ktoś kliknie na przycisk “Menu”, to z lewej strony na ekran wjedzie menu. To też dobry moment, żeby stworzyć naszego obserwatora zamykania:
function openMenu() { // 1
// Pokazujemy element itd.
const closeWatcher = new CloseWatcher(); // 2
closeWatcher.addEventListener( 'close', closeMenu ); // 3
}
function closeMenu() { // 4
// Zamykamy menu
}
W funkcji otwierającej menu (1) tworzymy nową instancję CloseWatchera (2). Następnie przypinamy mu nasłuchiwacz na zdarzenie close (3) i gdy to zajdzie, odpalamy funkcję closeMenu() (4), która zamyka menu. I to tyle.
Teraz, jeśli ktoś naciśnie Esc albo tapnie przycisk “Wstecz”, menu się automatycznie zamknie. Można też stworzyć przycisk, który będzie wprost wołał CloseWatchera:
closeMenuButton.addEventListener( 'click', closeWatcher.close );
Metoda CloseWatcher#close() zamyka otwartą rzecz. Istnieje też metoda CloseWatcher#requestClose(), która przed samym zamknięciem odpali zdarzenie cancel, pozwalające anulować zamykanie (np. jeśli ktoś nie wypełnił formularza i chcemy się upewnić, że to zrobi przed zamknięciem modala).
Warto też zaznaczyć, że CloseWatcher jest jednorazowy. W momencie, gdy zajdzie zdarzenie close, przestanie on reagować na wszelkie próby zamykania. Dlatego trzeba tworzyć nowy obiekt CloseWatcher za każdym razem, gdy otwieramy menu.
A na koniec – małe demko zamykalnego, wjeżdżającego menu:
Jedyny problem? Kompatybilność. Na razie to API jest dostępne jedynie w Chrome. A szkoda, zdecydowanie uprościłoby życie tym wszystkim, którzy muszą coś zamknąć.
Komentarze
Przejdź do komentarzy bezpośrednio na Githubie.