Miłosz Orzeł

.net, js, html, arduino, java... no rants or clickbaits.

Jak zamknąć okna pop-up przy zamknięciu okna głównego lub wylogowaniu?

Wyobraź sobie, że dostałeś zlecenie serwisowe dla pewnej bardzo starej aplikacji webowej. Aplikacja ma jedno okno główne i okna pop-up, w których prezentowane są poufne informacje (np. dane płacowe). Klient chciałby by wszystkie pop-upy zostały automatycznie zamknięte gdy użytkownik opuści główne okno aplikacji lub naciśnie w tym oknie przycisk „wyloguj”… 

Więc… jak zamknąć wszystkie okna otworzone za pomocą metody window.open?

W sieci pytanie to pada bardzo często. Niestety najczęściej podawane jest naiwne rozwiązanie problemu, które polega na zapamiętaniu referencji do otwartych okien i późniejsze wywołanie metody close na pop-upach:

var popups = []; 

function openPopup() {
    var wnd = window.open('Home/Popup', 'popup' + popups.length, 'height=300,width=300');
    
    popups.push(wnd);
}

function closePopups() {
    for (var i = 0; i < popups.length; i++) {
        popups[i].close();
    }

    popups = [];
}

W praktyce to nie zadziała bo przecież tablica z referencjami zostaje wyczyszczona przy pełnym przeładowaniu strony (np. po kliknięciu w link albo przy postbacku)…

Inne sugerowane rozwiązanie to nadanie pop-upowi unikalnej nazwy (za pomocą drugiego parametru metody open) i późniejsze pozyskanie referencji do okna:

var wnd = window.open('', 'popup0');
wnd.close();

Chodzi o skorzystanie z faktu, że metoda window.open działa na 2 sposoby:

  1. Jeśli okno o zadanej nazwie nie istnieje, zostanie utworzone.
  2. Jeśli okno o zadanej nazwie istnieje, nie zostanie ponownie utworzone, zamiast tego zostanie zwrócona referencja do niego (jeśli zostanie podany niepusty URL zawartość okna zostanie dodatkowo przeładowana).

Problem leży w punkcie nr 1. Jeśli okno pop-up o danej nazwie nie było wcześniej otwarte, zawołanie open a potem close, spowoduje, że przez krótką chwilę pop-up będzie widoczny. Lipa…

A może da się przechować referencję do pop-upa między przeładowaniami strony?

Jeśli nie istnieje konieczność obsługi starych przeglądarek (mało prawdopodobne dla starej aplikacji) można by spróbować zapisać wskazanie na pop-up do localStorage. To jednak się nie uda:

var popup = window.open('http://morzel.net', 'test');
localStorage.setItem('key', JSON.stringify(popup)); 

TypeError: Converting circular structure to JSON

Stare tricki z utrzymaniem stanu strony oparte o cookies lub window.name też nie zadziałają.

 

Wiec… co z tym zrobić?

Nawet jeśli nie możesz sobie pozwolić na dużą zmianę jaką jest wprowadzenie ramek nie poddawaj się :)

Okna popup mają właściwość opener, która wskazuje na okno nadrzędne (tzn. to w którym znajdowało się wywołanie window.open). Okna pop-up mogą więc cyklicznie sprawdzać czy otwierające je okno główne jest nadal otwarte. Mogą też odwoływać się do zmiennych w oknie nadrzędnym. Fakt ten można wykorzystać do wymuszenia zamknięcia okien pop-up gdy użytkownik naciśnie przycisk „wyloguj” w oknie głównym. Gdy użytkownik jest zalogowany (i tylko wtedy!), należy dodać do obiektu window okna głównego znacznik zalogowania w postaci zmiennej (np. loggedIn).

Oto kod JS, który powinien zostać umieszczony na stronie wyświetlanej w pup-upie:

window.setInterval(function () {
    try {
        if (!window.opener || window.opener.closed === true || window.opener.loggedIn !== true) {
            window.close();
        }
    } catch (ex) {
        window.close(); // FF może rzucić wyjątek bezpieczeństwa przy próbie odwołania do loggedIn (dla zewnętrznego serwisu)
    }
}, 1000);

Sprawdzanie zmiennej z okna opener ma jeszcze jedną zaletę. Jeśli użytkownik w oknie głównym przejdzie do innej aplikacji (np. poprzez przycisk wstecz lub link do zewnętrznego serwisu) wówczas okna pop-up wykryją brak monitorowanej właściwości window.opener i zamkną się automatycznie.

Cóż, nie jest to kod który lubisz pisać ale osiąga pożądany efekt nawet pomimo bolesnych braków w API przeglądarek. Gdyby tylko zrobili metodę window.exists('name')…