Kawałek Kodu

Niektórzy mówią, że cokolwiek by się nie stało, winni są temu cykliści. To o czym dziś piszę wiąże się z cyklistami wyłącznie poprzez etymologię. Oczywiście nie ponoszą oni jakiejkolwiek winy w tej sprawie, więc jeśli i Ty jesteś zapalonym rowerzystą, a jednocześnie fanem programowania, to myślę, że bez obaw i z przyjemnością zapoznasz się z wpisem. Pompuj dętkę i zaczynamy!

I tak w kółko.

Czym jest rzeczony cycle button? Jest to jeden z elementów UI, który pojawia się jako natywna kontrolka w niektórych systemach operacyjnych. Niestety nie istnieje jako element formularza HTML. A szkoda, bo w pewnych sytuacjach ma przewagę w stosunku do elementu SELECT, ale i odwrotnie. W przypadku elementu SELECT musimy kilknąć w listę, aby ją rozwinąć, a następnie wybrać element i ponownie kliknąć. Zawsze mamy więc dwa kliknięcia + wyszukanie elementu na liście. Cycle button działa trochę inaczej. Jego standardowy wariant przypomina listę, lecz jest nierozwijalny. Klikając na ikonkę umiejscowioną tam gdzie strzałka w liście (lub po lewej), a przypominającą ikonę "undo/redo/rotate", wybieramy kolejne opcje. Jeśli mamy dużo opcji, to aby dostać się do pożądanej, czasem trzeba trochę się naklikać. Jednak i tak wydaje się, że nadal jest to element wygodniejszy. Dla wątpiących powstała hybryda cycle button oraz drop-down list - element, który możemy używać zarówno jak bohatera dzisiejszego wpisu, ale również jak listę, czyli w razie potrzeby rozwinąć.

Tak wygląda wersja standardowa cycle button z systemu Workbench:

Radio na haczyku.

Checkbox hack już chyba każdy zna, no prawie. Kto nie wie niech doczyta, bo dziś wykorzystamy tą metodę modyfikując ją w ciekawy sposób, aby stworzyć właśnie cycle button.
Zaczniemy od przykładu, który będzie bazą dla naszego przycisku. W standardowym checkbox/radio hack wiążemy zazwyczaj jeden element INPUT oraz jeden element LABEL. Tym razem również powiążemy te dwa elementy, tyle, że LABEL za checkboxem będzie przynależeć do kolejnego checkbox. Trochę zagmatwane? Rozjaśnijmy to przykładem:

<input type="radio" name="cb" id="cb1" checked autofocus />
<label for="cb2">opcja 1</label>
<input type="radio" name="cb" id="cb2" />
<label for="cb3">opcja 2</label>
<input type="radio" name="cb" id="cb3" />
<label for="cb4">opcja 3</label>
<input type="radio" name="cb" id="cb4" />
<label for="cb1">opcja 4</label>

+

/* ukrywamy checkboxy
*/

input[type="radio"] {
  height: 0;
  width: 0;
  position: absolute;
}

/* etykieta również
   ukryta
*/
label {
  display: none;
}

/* ale pokazuje się
   jeśli poprzedzający
   checkbox jest
   zaznaczony
*/
input[type="radio"]:checked+label {
  display: block;
}

Kod CSS jest typowy dla checkbox hack - pozwala wyświetlić LABEL dla zaznaczonego checkboxa, tak więc dla pierwszego zaznaczonego wyświetli się pierwszy LABEL. Natomiast dosyć dziwne może się wydawać ustawienie LABEL oraz INPUT kiedy popatrzymy na id etykiet oraz ich zawartość tekstową. Najprościej zacząć jak zwykle od... początku. Jeśli mamy zaznaczony pierwszy checkbox, to wyświetli się pierwsza etykieta - ta z zawartością "opcja 1". I jak na razie jest wszystko ok, a żeby było jeszcze bardziej ok, chętni mogą dodać odpowiednie wartości atrybutu value dla checkboxów. Zauważmy jednak, że wspomniana etykieta jest powiązana poprzez id z drugim checkboxem. Dlaczego? A dlatego, że dzięki kliknięciu na nią zaznaczymy checkbox numer 2, a ten poprzez CSS wyświetli LABEL z tekstem "opcja 2". Klikając dalej dochodzimy do wyświetlenia etykiety "opcja 4". Klikając na tą wywołamy zaznaczenie pierwszego checkbox i wyświetlenie znów "opcja 1".
I jak, dostrzegasz teraz cykliczność w tym rozwiązaniu niczym w zdarzeniach otaczającego świata?

Oczywiście zawartością etykiet nie musi być tekst, mogą to być np. kolory. Rozwiązanie możemy wykorzystać na wiele sposobów, póki nie ograniczy nas wyobraźnia. Ale skoro tematem dzisiejszego wpisu jest cycle button, to doprowadźmy powyższy przykład do stanu kiedy będzie przypominał przycisk, a do tego już niewiele potrzeba.

<div class="cycle-button" tabindex="0">
  <input type="radio" name="cb" id="cb1" checked autofocus />
  <label for="cb2">opcja 1</label>
  <input type="radio" name="cb" id="cb2" />
  <label for="cb3">opcja 2</label>
  <input type="radio" name="cb" id="cb3" />
  <label for="cb4">opcja 3</label>
  <input type="radio" name="cb" id="cb4" />
  <label for="cb1">opcja 4</label>
</div>

Tu niewiele się zmienia, dodaliśmy kontener, w którym przechowujemy wcześniejsze elementy, za to w CSS:

/* nasz kontener
   będzie wyglądać
   jak systemowy przycisk
*/
.cycle-button {
  display: inline-block;
  border: 1px solid #939393;
  background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M 7.1601562 3 L 8.7617188 5 L 18 5 C 18.551 5 19 5.448 19 6 L 19 15 L 16 15 L 20 20 L 24 15 L 21 15 L 21 6 C 21 4.346 19.654 3 18 3 L 7.1601562 3 z M 4 4 L 0 9 L 3 9 L 3 18 C 3 19.654 4.346 21 6 21 L 16.839844 21 L 15.238281 19 L 6 19 C 5.449 19 5 18.552 5 18 L 5 9 L 8 9 L 4 4 z"/></svg>'), linear-gradient(#f6f6f6, #dedede);
  background-size: auto 80%, auto 100%;
  background-position: calc(100% - 5px) center, 0 0;
  background-repeat: no-repeat, repeat;
}

/* po naciśnięciu
   jego gradient zostanie
   odwrócony
*/
.cycle-button:active {
  background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M 7.1601562 3 L 8.7617188 5 L 18 5 C 18.551 5 19 5.448 19 6 L 19 15 L 16 15 L 20 20 L 24 15 L 21 15 L 21 6 C 21 4.346 19.654 3 18 3 L 7.1601562 3 z M 4 4 L 0 9 L 3 9 L 3 18 C 3 19.654 4.346 21 6 21 L 16.839844 21 L 15.238281 19 L 6 19 C 5.449 19 5 18.552 5 18 L 5 9 L 8 9 L 4 4 z"/></svg>'), linear-gradient(#dedede, #f6f6f6);
}

.cycle-button input[type="radio"] {
  height: 0;
  width: 0;
  position: absolute;
}

.cycle-button label {
  padding: 1px 30px 1px 6px;
  display: none;
}

.cycle-button input[type="radio"]:checked+label {
  display: block;
}

I po tych niewielkich zmianach tak wygląda nasz przycisk:

Zachowując tradycję i cykliczność, zapraszam Cię na kolejny wpis. Do przeczytania!

 

Przydatne linki:
O cycle button w Wikipedii.