Disassembler

Artificial intelligence is no match for natural stupidity.
04září2012

Třístavový checkbox v HTML5


Vyrábím tu zrovna jedno administrační rozhraní, v němž potřebuji zobrazit několik položek, které mohou nabývat tří různých stavů. Stavy, které potřebuju vyjádřit, by se daly pracovně nazvat „vynuceně vypnuto“, „vynuceně zapnuto“ a „na vyžádání“. U těchto položek budou existovat vždy právě tři stavy. Nikdy ne více a nikdy ne méně. Jaký prvek tedy použít, aby byl intuitivní a v GUI nesežral moc místa?

Kulaté čtverečky


První ovládací prvek schopný obsluhovat více stavů, který většinu lidí napadne, je prachobyčejné dropdown menu (dropdown list, combo box, roletka,... Nadávejte tomu, jak uznáte za vhodné, prostě <select> element). Ten je ale moc velký, protože musí zobrazit text a ještě k tomu rozbalovací šipku vedle něj. Pak by se dal použít klasický textový vstup, ale ten rozhodně není intuitivní. Dál by se dalo čarovat třeba ještě s HTML5 sliderem, který ale není podporován ve všech prohlížečích. No a pak by se dal použít taky checkbox. Říkáte, že checkbox umí obsluhovat jen dva stavy - zaškrtnuto a nezaškrtnuto? Ano, v XHTML a HTML 4 a níže by to byla pravda. Ne však v HTML5.

V HTML5 se i s HTML elementy pracuje jako s objekty, takže mají svoje atributy a funkce. A interface HTMLInputElement, který <input type="checkbox"/> implementuje, náhodou obsahuje jakýsi indeterminate boolean, který, jak název napovídá, určuje nerozhodnost checkboxu, vyjádřenou nikoliv zaškrtnutím, ale vyplněním elementu. Když jeho původní určení trochu ohneme přes koleno, získáme tak třetí stav.

jQuery to zařídí


Relativně bezbolestná implementace takového třístavového checkboxu se pak dá provést pomocí nejlepšího přítele každého kodéra.

$(function() {                                                  // On DOM ready
    $('.tristate').each(function() {                            // pick every tristate class element
        renderTriStateCheckbox($(this));                        // and render it.
    }).bind(($.browser.msie ? 'click' : 'change'), function() { // On click or change
        var $el = $(this);                                      // get element,
        var val = $el.val();                                    // get element's value,
        $el.val(val == '0' ? '1' : (val == '1' ? '2' : '0'));   // increment or reset the value
        renderTriStateCheckbox($el);                            // and render it.
    });
});

function renderTriStateCheckbox($el) {
    switch ($el.val()) {                                        // Get element’s value.
    case '0':                                                   // If value is '0',
        $el.removeAttr('checked').prop('indeterminate', false); // paint empty.
        break;
    case '1':                                                   // If value is '1',
        $el.attr('checked', 'checked');                         // paint checked.
        break;
    case '2':                                                   // If value is '2'
        $el.prop('indeterminate', true);                        // paint indeterminate.
        break;
    }
}

Skript byl úspěšně otestován v Chrome 21, Firefoxu 14, IE 9 a Opeře 12. A díky JSFiddle si jej můžete otestovat i sami.

Kouzlení s eventem na čtvrtém řádku skriptu v sobě skrývá workaround pro IE a Operu. IE při použití onchange eventu vynechává iteraci při resetu stavu a Opera při použití onclick zatvrzele ignoruje výslednou podobu checkboxu. Firefox a Chrome fungují korektně s oběma událostmi. Safari v jakýchkoliv testech pozérsky ignoruji.

I přesto, že jsem v příkladu pro stavy použil hodnoty 0, 1 a 2, pracuju s nimi jako s textem. Jednak si ušetřím drbání se s ParseInt a jednak můžu pro stavy zvolit hodnoty jakékoliv jiné, třeba i textové, a kód bude fungovat pořád správně.