Zdarzenia "focus" i "blur" na rodzicu

0

Problem wydaje się banalny; mam wrażenie, że HTML jest przeciwko mnie. Żeby za bardzo się nie rozpisywać – mam kod mniej-więcej taki:

<div class="container">
  <input class="visible" />
  <input class="hidden" />
  <input class="hidden" />
</div>

W celu maksymalnego uproszczenia JS, zrobiłem jedynie jeden input z klasą hidden, ale docelowo będzie ich dwa lub trzy w tym miejscu. Taki jest do tego JS uruchamiany zaraz po wczytaniu DOM-u:

document.getElementsByClassName("hidden")[0].style.display = "none";

Chodzi o to, że chciałbym, by po aktywowaniu kontrolki input z klasą visible (coś jak zdarzenie focus; chodzi o mysz, palec, klawiaturę i każdy inny sposób) pokazywały się wszystkie kontrolki z klasą hidden, a po dezaktywowaniu te kontrolki chowały się (coś jak zdarzenie blur). Najważniejsze, by po pokazaniu ukrytych kontrolek była możliwość wpisania tekstu do każdej z kontrolek (i tych ukrytych, i tych widocznych) i "zasubmitowania".

Tutaj ten uproszczony przykład online, tyle udało mi się zrobić: https://jsfiddle.net/8bnjwk0g/4/ Niestety, nie działa, ponieważ choć kontrolka się pokazuje, to zaraz znika, gdy próbować ją kliknąć.


UPDATE:
Właśnie przyszło mi do głowy, że mogę ich wcale nie ukrywać, a jedynie zrobić niewidocznymi, ale 1) także nie wiem, czy zadziała, a 2) to ostateczność – wolę, by było zgodnie z logiką.

1

Tak na szybko: sugeruję nie dłubać w każdym inpucie z osobna, tylko zmodyfikować globalnie klasę CSS. Rozwiązania są dwa:

  • edytować klasę w arkuszu CSS,
  • włączyć / wyłączyć arkusz styli z odpowiednimi modyfikacjami.
    W praktyce nie pamiętam obecnie, jak to się robi, ale sobie wyguglasz :)

edit - Dobra, masz dwa linki odnośnie obu rozwiązań:
https://stackoverflow.com/questions/2027935/how-to-remove-css-property-using-javascript/2028029
https://guides.codechewing.com/js/disable-enable-stylesheet-javascript

0

@Freja Draco: mam wrażenie, że mnie nie zrozumiałaś (albo ja Ciebie nie rozumiem). Wiem, nie wyraziłem się jasno. Problemem nie wydaje się być CSS (przynajmniej w tym kodzie ;) ), tylko to, że zdarzenia focus oraz blur (inne mi nie przychodzą do głowy) działają w ten sposób: albo 1) idą od elementu input do obiektu document (tzw. bubbling), po drodze łapiąc rodzica elementu input, albo 2) idą tak samo, ale w drugą stronę (tzw. capturing). W pierwszym przypadku w ogóle nie działa pokazywanie, a w drugim pokazywanie co prawda działa, ale przy kliknięciu gdziekolwiek poza widocznym elementem input wszystkie pozostałe elementy input (tj. z klasą hidden) chowają się (czyli jest "prawie dobrze" – pokazuje się i chowa, ale nie w tych momentach, co bym chciał).

PS. Kurczę, ale ja to opisuję od tyłu. :( Nie wiem, jak inaczej to opisać, by uniknąć pisania elaboratu...


UPDATE: Przypomniałem sobie, że chyba podobnie do tego, co bym chciał uzyskać, działa kliknięcie np. w przycisk "Share" na stronie https://stackoverflow.com/ Tyle że tam jest to przycisk (click?), a tutaj chodzi mi o same zdarzenia focus oraz blur.


UPDATE2: @LukeJL, dodaję zrzut ekranu tutaj, by nie robić zamieszania w nowej odpowiedzi:

share.png

0

A weź napisz co chcesz osiągnąć od strony designu (ogólne designu, bo Chodzi o to, że chciałbym, by po aktywowaniu kontrolki input z klasą visible (coś jak zdarzenie focus; chodzi o mysz, palec, klawiaturę i każdy inny sposób) pokazywały się wszystkie kontrolki z klasą hidden, a po dezaktywowaniu te kontrolki chowały się (coś jak zdarzenie blur). - to już bardzo szczegółowe wymaganie). BTW to może ci się przydać:
https://developer.mozilla.org/en-US/docs/Web/Accessibility/Keyboard-navigable_JavaScript_widgets
chociaż to wygląda na to, że żeby to zrobić tak jak mówisz, być może trzeba będzie nieco zachodu wykonać i się nagłówkować z atrybutami HTML i zdarzeniami DOM (ja pare minut próbowałem kombinować z tym, ale nie udało mi się, z powodu braku kontroli nad fokusem - próbowałem nadawać tabindex kontenerowi, i łapać zdarzenie na kontenerze, ale to i tak było na nic, bo jak się sfocusował input, to kontener przestał mieć focusa.

(Dlatego właśnie się pytam, co tak ogólnie chcesz osiągnąć, bo może ten cały zachod jest niepotrzebny. A może po prostu wystarczy wsadzić inputa w <label> i łapać na nim focus (chociaż chyba nie, próbowałem z label i też nie daje. Tym niemniej szerszy pogląd na to, co jest twoim ogólnym celem (bez wchodzenia w szczegóły implementacji) pozwoliłoby wykminić lepsze rozwiązanie.

0

Hm, spróbuję jeszcze inaczej opisać, co chcę: chciałbym zrobić, by użytkownik po załadowaniu strony widział jeden element input. Następnie żeby mógł w niego kliknąć (lub tapnąć palcem, lub przejść TAB-em; z jakichkolwiek innych miejsc na stronie). Po aktywacji (tak to się chyba nazywa) powinny się pokazać pozostałe elementy input (jak napisałem, docelowo 2 lub 3). W każdy z tych elementów (i ten pierwszy, i te 2 lub 3, co się pokazały) użytkownik musi móc wpisać tekst i nacisnąć enter (które to naciśnięcie obsłużę sobie oddzielnie później jakimś zdarzeniem). Przy wpisywaniu tekstu do którejkolwiek kontrolki wszystkie powinny być oczywiście widoczne. ;) Przechodzenie między kontrolkami (tj. aktywowanie kolejnych z nich) powinno móc odbywać się na te same sposoby, co aktywacja pierwszej – klikaniem, palcem, TAB-em. Kontrolki, które były ukryte, mają się ukryć ponownie dopiero, gdy użytkownik wyjdzie z "focusem" poza rodzica wszystkich kontrolek (wszystkie kontrolki – widoczna i ukryte – będą siedzieć w jednym kontenerze).

1

O, mam! A jakbyś robił to, co robisz w pierwszym linku, tylko po prostu z lekkim opóźnieniem (bo twoim problemem jest to, że po sfokusowaniu pierwszego elementu i naciśnięciu tab, od razu się odpala blur i znika. Więc dajmy opóźnienie i niech znikanie będzie odpalone po chwili w setTimeout. Przy czym jeśli ktoś sfokusuje drugi input, to anulujmy setTimeout, czyli tak jakby znikania w ogóle nie było). Taki "event debouncing" trochę, coś podobnego przynajmniej.

wygląda na to, że działa, przynajmniej na pierwszy rzut oka (tylko by trzeba było mocniej to potestować, czy nie ma jakichś edge case'ów).
https://jsfiddle.net/9zgfpshm/1/

EDIT:
swoją drogą nawet przy setTimeout na 0 milisekund to rozwiązanie działa, czyli być może wcale nie chodzi nawet o opóźnienie, tylko o wymuszenie tego, żeby robiło się to asynchronicznie? Być może wtedy zamieniamy kolejność znikania i pokazywania, jeśli przekładamy znikanie na moment jak JS wejdzie w kolejną pętle zdarzeń? (ale to hipoteza, by trzeba było zobaczyć, co się kiedy wykonuje dokładnie).

2

Dobra (chyba) rozumiem. Sugeruję:

  • olać blur

  • zrobić dwa focusy / onclick czy co tam chcesz przypisane do:

    • całego HTML-> ukrywanie hiddenów,
    • wybranego elementu np. inputa -> pokazanie hiddenów.
  • a do tego jeszcze stopPropagation() dla elementów, która mają się dać sfokusować bez ukrywania hiddenów.

Tak to jest zrobione np. tutaj http://drakonica.pl odnośnie lupki w menu po prawej. Jak klikasz lupę, pojawia się panel wyszukiwarki i możesz sobie w nim pisać, a jak klikniesz gdzieś poza tym panelem, obok, poniżej, to panel się schowa.

1

Dziękuję wam! :) Ja zawsze myślę, że wszystko, co wymyślę, powinno już być wbudowane w przeglądarkę / w język, a tu się potem zwykle okazuje, że rzeczywiście jest, ale coś innego, bo ja myślałem od tyłu. Dobrze wybrałem, by was zawołać, że na pewno coś wam wpadnie do głowy. ;)

Zostały zaproponowane 3 rozwiązania. @LukeJL, co do tych Keyboard-navigable Java​Script widgets – wydaje się przydatne na pierwszy rzut, ale myślę, że w świetle pozostałych dwóch rozwiązań zostawię to na później, do ewentualnego dopieszczenia funkcjonalności, jeśli zostanie mi trochę czasu. Drugie Twoje rozwiązanie jest dobre, bo działa i ma mało kodu, ale chyba wybiorę rozwiązanie @Freja Draco jako bardziej eleganckie – o ile uda mi się coś z tym zrobić teraz. Popróbuję i napiszę Wam rezultaty!


UPDATE: Chyba prawie to zrozumiałem... Na razie udało mi się zrobić coś przeciwnego: po kliknięciu gdziekolwiek, <input> z ID hidden nie chowa się (hurra!), a po kliknięciu na niego samego – chowa się. xD

PS. Tutaj ostatni kod: https://jsfiddle.net/08rwan6t/18/ W godzinach późniejszych dziś będę myśleć, jak tu odwrócić to działanie.

PS2. Znalazłem fajną lekturkę w temacie zdarzeń DOM (to dla osób, które nie miały za wiele do czynienia ze zdarzeniami DOM): https://medium.com/@chengsieuly/event-propagation-event-bubbling-vs-event-capturing-e6f473f056c9

1

Nie wiem czy dobrze zrozumiałem. Jeśli tak to możesz w tym miejscu użyć selektora "focus-within" na formie.
Niestety przeglądarki microsoftu oraz samsung mobile, nie wspierają tego rozwiązania.
Przykład:
HTML:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>something</title>
        <link rel="stylesheet" type="text/css" href="css/input.css">
    </head>

    <body>
        <form id="form">
            <fieldset>
                <legend>example</legend>
                <label for="name" class="main">Name</label>
                <input type="text" id="name" class="main">
                
                <label for="othername" class="main">Other-Name</label>
                <input type="text" id="othername" class="main">
                
                <label for="surname">Surname</label>
                <input type="text" id="surname">
                
                <label for="mail">Mail</label>
                <input type="text" id="mail">
            
                <input type="button" value="some button">
            </fieldset>
        </form>
        <p>test example</p>
    </body>
</html>

CSS:

input{
    height: 2em;
    width: 80%;
}
label{
    display: block;
    height: 2em;
}
fieldset{
    width: 200px;
}

/*
    hide / show (inputs, labels)
*/
input,label{
    display: none;
}
.main{
    display: block;
}
form:focus-within fieldset input ~ * {
    display: block;
}

0

Ostatecznie okazało się, że udało się zrobić. Wykonałem to bez pomocy internetu, a z pomocą żelaznej logiki (tak, stosuję taką czasem i w przypadku DOM-u). Nie zrobiłem tego na przykładowym kodzie, a już na żywym organizmie mojego programu; myślę, że nie ma sensu pokazywać całości, ale sednem jest użycie właściwości relatedTarget:

elem.addEventListener("blur", function (event) {
    if (!event.relatedTarget ||
        !newItemFormFieldElementList.includes(event.relatedTarget)) {
        ...
    }
})

Nie studiowałem MDN dokładnie, bo właściwość tę znalazłem metodą prób i błędów (bez dostępu do internetu), więc trochę mnie dziwi, że jest zdefiniowana w interfejsie MouseEvent, skoro działa również z klawiaturą. No ale cóż. Jeszcze zostaje mi w takim razie parę rzeczy do dotyczania o DOM. :)

Dziękuję wszystkim za zaangażowanie, @Freja Draco, @LukeJL, @woki. Do następnego razu!


UPDATE: Tak, wiem, to jest pewne obejście – bo nie wiem, dlaczego to działa. Ale działa, a mnie o to chodziło. Głupio się z tym czuję, ale (jak to napisała ostatnio nawet tu na forum @aliszja, pewnie też pisało parę innych osób, może @MasterOf ?), lepiej jest w ogóle coś zrobić i potem ewentualnie poprawiać...


UPDATE2:

For events with no secondary target, relatedTarget returns null.
(MDN)

Ach, to dlatego zwracało null.

1 użytkowników online, w tym zalogowanych: 0, gości: 1