Multiselekt

Dzisiaj zajmiemy się poprawianiem multi selektów. Domyślnie taki obiekt do najwygodniejszych nie należy. Kolejne jego elementy trzeba wybierać dusząc Ctrl, nie posiada sensownego filtrowania i ogólnie jest jakiś taki niemrawy. Jak zobaczycie poniżej, poprawa tego elementu wcale bardzo ciężka nie jest.

Zobacz demo

HTML

Zaczynamy od html:

<select multiple="multiple">
    <option value="1">Wartość 1</option>
    <option value="2">Wartość 2</option>
    <option value="3">Wartość 3</option>
    <option value="4">Wartość 4</option>
    <option value="5">Wartość 5</option>
    <option value="6">Wartość 6</option>
    <option value="7">Wartość 7</option>
    <option value="8">Wartość 8</option>
    <option value="9">Wartość 9</option>
    <option value="10">Wartość 10</option>
</select>

To wszystko :)

Nasze działanie będzie polegało na ukryciu powyższego selekta, a następnie na podstawie jego elementów stworzenie nowej listy z checboxami, polem do filtrowania i guzikami do zaznaczania lub odznaczania elementów na liście. Wszystko to możesz zobaczyć tutaj.

Poniższa grafika demonstruje końcowy wygląd z zaznaczonymi elementami (najedź na nią kursorem aby zobaczyć poszczególne sekcje):

multiselect-1

Wszystkie elementy naszego multi selekta będziemy tworzyć dynamicznie.
Zanim jednak zaczniemy to robić spójrzmy na finalny kod html, który wygenerujemy:

<div class="multiselect">

    <select id="xxx" multiple="multiple" style="display: none;">        
        <option value="">Przykładowa wartość 1</option>
        <option value="">Przykładowa wartość 2</option>
        //...
    </select>

    <div class="multiselect-cnt">
        <div class="multiselect-controls">
            <div class="filter-cnt">
                <label for="xxx_filter">Szukaj:</label>
                <input type="text" placeholder="Szukaj" id="xxx_filter">
            </div>
            <div class="buttons-cnt">
                <a class="select-all" href="#">Zaznacz widoczne</a>
                <a class="deselect-all" href="#">Odznacz widoczne</a>
            </div>
        </div>

        <div class="list-container">
            <ul class="list">
                <li><input type="checkbox" id="_xxx0"><label for="_xxx0">Przykładowa wartość 1</label></li>
                <li><input type="checkbox" id="_xxx1"><label for="_xxx1">Przykładowa wartość 2</label></li>
                //...
            </ul>
        </div>

        <div class="multiselect-info">Wybrano 0 z 9</div>
    </div>
</div>

Nasz selekt został ukryty (display:none). Został on też otoczony wrapperem .multiselect.
Do tego wrappera wrzuciliśmy dodatkowy div .multiselect-cnt w którym umieściliśmy dwa divy – jeden .multiselect-controls, który zawiera kontrolki naszego multiselekta (patrz screen), oraz div .list-container, w którym znajdować się będzie lista z checkboxami, którą wygenerujemy na podstawie elementów ukrytego selekta.

Najlepiej przeanalizuj powyższy kod HTML z powyższym screenem.

Tworzymy plugin

Zaczynamy od stworzenia podstawowego kodu naszego pluginu:

(function($) {     
    $.fn.multiSelectToCheckboxes = function(options) {
        options = $.extend({
            //...tutaj będą domyślne opcje pluginu...
        }, options);

        return this.each(function() {
            var $select = $(this); //tutaj pobraliśmy selekt

            //...tutaj będziemy kombinować...

            $select.hide(); //ukrywamy selekt - na czas debugowania warto to zakomentować
        })
    }
})(jQuery);

Podobnego kodu używaliśmy już kilka razy, więc stałym czytelnikom powinien on być znany. Jeżeli nie jest, zapraszam cię do tej lektury.

Pierwszym zadaniem będzie dla nas dynamiczne wygenerowanie wszystkich elementów. Kod HTML już widzieliśmy, więc po prostu musimy skrupulatnie za pomocą jQuery utworzyć element po elemencie.

(function($) {     
    $.fn.multiSelectToCheckboxes = function(options) {
        options = $.extend({  //domyślne opcje pluginu będziemy omawiać w trakcie omawiania kodu
            filterLabel : 'Szukaj:',
            filterPlaceholder : 'Szukaj',
            minFilterTextLength : 0,
            selectAllText : 'Zaznacz widoczne',
            deselectAllText : 'Odznacz widoczne',
            infoText : 'Wybrano {0} z {1}'
        }, options);

        return this.each(function() {
            var $select = $(this);

            var $wrapper = $('<div class="multiselect"></div>'); //tworzymy wrapper
            $select.wrap($wrapper); //i okrywamy nim selekt

            var $container = $('<div class="multiselect-cnt"></div>'); //tworzymy kontener dla kontrolek i listy

            var $ulContainer = $('<div class="list-container"></div>'); //tworzymy kontener listy
            var $ul = $('<ul class="list"></ul>'); //tworzmym listę z checkboxami
            $ulContainer.append($ul); //listę wrzucamy do kontenera listy

            var $controls = createMultiselectControl($select, $ul); //tworzymy kontrolki (filter, buttony)

            $container.append($controls).append($ulContainer); //kontrolki i kontener z listą wrzucamy do kontenera kontrolek :)

            var $infoDiv = $('<div class="multiselect-info"></div>'); //tworzymy div z informacjami o zaznaczaniu elementów na liście
            $container.append($infoDiv); //wrzucamy go do kontenera kontrolek

            $select.parent().append($container); //caly kontener z kontrolkami, listą itp. wrzucamy do wrappera

            fillUl($select, $ul); //po stworzeniu wszystkich elementów na podstawie selekta tworzymy elementy listy
            updateSelectInformation($select); //i aktualizujemy informacje o zaznaczeniu

            $select.hide(); //na koniec ukrywamy domyślny selekt
        })
    }
})

Stworzenie kontrolek multiselekta

Pierwsza z funkcji jaką natrafisz w powyższym kodzie to createMultiselectControl().

Będzie ona tworzyła div .multiselect-controls, w którym znajdą się kontrolki.
Pierwszą z nich jest pole do filtrowania, które składa się z labelki oraz input:text. Podpinamy mu zdarzenie keyup, które wykonuje prostą sztuczkę. Po wpisaniu minimalnej liczby znaków (którą przekażemy w opcjach naszego pluginu) za pomocą selektora :contains wykrywamy które elementy listy należy pokazać, a które ukryć. Moglibyśmy tutaj użyć indexOf, ale contains jest wygodniejsze. Jeżeli :contains zwróci pusty wynik, wtedy do kontenera z listą wrzucamy div .empty-list z komunikatem o braku wyników.

function createMultiselectControl($select, $ul) {
    var id = $select.attr('id');
    var $headerContainer = $('<div class="multiselect-controls"></div>');

    //tworzymy pole filtra
    var $filterDiv = $('<div class="filter-cnt"></div>');                
    var $filterLabel = $('<label for="'+id+'_filter"">'+options.filterLabel+'</label>') //tekst labelki ustawiamy w opcjach pluginu
    var $filterInput = $('<input type="text" id="'+id+'_filter" placeholder="'+options.filterPlaceholder+'" />'); //placeholder pola ustawiamy w opcjach
    $filterInput.on('keyup', function() {                
        var $li = $ul.children('li');
        var val = $(this).val();

        $ul.parent().find('.empty-list').remove();

        if (val.length >= options.minFilterTextLength) {                        
            $li.hide();
            var $liVisible = $li.filter(':contains("'+val+'")');
            $liVisible.show();

            if ($li.length) {
                if (!$liVisible.length) {
                    $ul.parent().append('<strong class="empty-list">Brak wyników</strong>')
                }
            }
        }
    });
    $filterDiv.append($filterLabel).append($filterInput);

    //kolejne kontrolki
    ...
    return $headerContainer;
}

Kolejnymi elementami są przyciski do zaznaczania i odznaczania widocznych elementów listy. Moglibyśmy je zaprogramować tak, że zamiast widocznych (linijka 35) wybierały wszystkie elementy, ale wydaje mi się to mniej użyteczne.

function createMultiselectControl($select, $ul) {
    var id = $select.attr('id');
    var $headerContainer = $('<div class="multiselect-controls"></div>');

    //tworzymy pole filtra
    var $filterDiv = $('<div class="filter-cnt"></div>');                
    var $filterLabel = $('<label for="'+id+'_filter"">'+options.filterLabel+'</label>') //label pola ma ustawiany tekst w opcjach pluginu
    var $filterInput = $('<input type="text" id="'+id+'_filter" placeholder="'+options.filterPlaceholder+'" />'); //pole ma ustawiany placeholder w opcjach
    $filterInput.on('keyup', function() {                
        var $li = $ul.children('li');
        var val = $(this).val();

        $ul.parent().find('.empty-list').remove();

        if (val.length >= options.minFilterTextLength) {                        
            $li.hide();
            var $liVisible = $li.filter(':contains("'+val+'")');
            $liVisible.show();

            if ($li.length) {
                if (!$liVisible.length) {
                    $ul.parent().append('<strong class="empty-list">Brak wyników</strong>')
                }
            }
        }
    });
    $filterDiv.append($filterLabel).append($filterInput);


    //guziki do zaznaczania i odznaczania widocznych elementów listy
    var $buttonsDiv = $('<div class="buttons-cnt"></div>');
    var $buttonSelectAll = $('<a href="#" class="select-all">'+options.selectAllText+'</a>');
    var $buttonDeselectAll = $('<a href="#" class="deselect-all">'+options.deselectAllText+'</a>');
    $buttonSelectAll.on('click', function(e) {
        e.preventDefault();
        $ul.find('li:visible :checkbox').prop('checked', 1);
        $ul.find(':checkbox').trigger('change');
    })
    $buttonDeselectAll.on('click', function(e) {
        e.preventDefault();
        $ul.find('li:visible :checkbox').prop('checked', 0);
        $ul.find(':checkbox').trigger('change');
    })
    $buttonsDiv.append($buttonSelectAll).append($buttonDeselectAll);

    //filtr i przyciski dołączamy do diva
    $headerContainer.append($filterDiv).append($buttonsDiv);            

    //który następnie zwracamy
    return $headerContainer;
}

Guziki stworzyliśmy w postaci linków. Moglibyśmy tutaj stworzyć buttony lub inputy, ale ogólnie linki styluje się łatwiej niż wszelkie buttony (dla firefoxa trzeba stosować hacki by wyrównywać tekst w pionie itp), a nam nie będzie to przeszkadzać, szczególnie że od razu wywołujemy e.preventDefault(). Przyciski będą odpowiednio zaznaczać i odznaczać aktualnie widoczne elementy listy (które będzie pokazywać lub ukrywać pole filtra). Robi to dokładnie linijka:

$ul.find('li:visible :checkbox').prop('checked', 1);

Jeżeli chcesz, by zaznaczały lub odznaczały wszystkie elementy listy, wtedy usuń po prostu selektor :visible. Myślę jednak, że w powyższej formie przyciski te będą miały większy sens. Szczególnie że teraz możesz sobie coś odfiltrować (np wszystkie elementy z cyfrą 1) i od razu je zaznaczyć olewając całą resztę.

Stworzenie listy z checkboxami

Wszystkie kontrolki itp rzeczy już mamy. Teraz musimy wygenerować elementy listy na podstawie optionów z selekta.
Zrobimy to za pomocą funkcji fillUl:

function fillUl($select, $ul) {                
    var baseId = "_" + $(parent).attr("id");
    $select.find("option, optgroup").each(function(index) {
        var $t = $(this);

        if ($t.is("option")) {
            var id = baseId + index;
            var $li = $("<li></li>").appendTo($ul);
            var $checkbox = $("<input type='checkbox' id='" + id + "'/>")
                .appendTo($li)
                .on('change', function() { //podpinamy zdarzenie change
                    var $chk = $(this);
                    if ($chk.is(":checked")) {
                        $chk.parent().addClass('select');
                        $t.attr("selected", "selected");                                    
                    } else {
                        $chk.parent().removeClass('select');
                        $t.removeAttr("selected");
                    }
                    updateSelectInformation($select); //aktualizujemy informację o liczbie wybranych elementów
                });
            if ($t.is(":selected")) {
                $checkbox.attr("checked", "checked");
                $checkbox.parent().addClass('select');
            }
            $checkbox.after("<label for='" + id + "'>" + $t.text() + "</label>");
        } else {
            var $li = $('<li class="group-name"><strong>' + $t.attr('label') + '</strong></li>').appendTo($ul);
        }
    });
};

Robimy pętlę (each) po elementach ukrytego selekta ($select) i na ich podstawie generujemy kolejne elementy LI z checkboxami. Każdemu z takich checkboxów podpinamy zdarzenie change. Gdy taki checkbox jest kliknięty, wtedy odpowiedni element ukrytego selekta dostaje atrybut selected (zauważ, że cały kod dzieje się w pętli each, więc wiemy o który kontrenie element selekta chodzi), a dane LI dostaje klasę .selected.

Dla elementów optgroup generujemy proste LI, które nie mają interakcji.
Moglibyśmy tutaj generować podlisty (w końcu optgroup to grupy optionów), ale wtedy mielibyśmy problemy z filtrowniem takich list i było by to dość hmmm upierdliwe dla użytkownika (wpisujesz np 1, pokazuje ci optgroup w którym jest np lista 200 optionów których już nie ukryjesz…).

Za każdym razem po kliknięciu na checkbox aktualizujemy też informację o liczbie wybranych elementów. Robimy to za pomocą funkcji updateSelectInformation():

function updateSelectInformation($select) {
    var $input = $select.parent().find('.multiselect-info');                
    var $chk = $select.parent().find(':checkbox');
    var $chkSelected = $chk.filter(':checked');
    var tempArray = [$chkSelected.length, $chk.length];

    var informationText = options.infoText.replace(/{(\d+)}/g, function(match, number) { 
        return typeof tempArray[number] != 'undefined'? tempArray[number]: match
    });

    $input.html(informationText);
}

Aby nasz plugin był jeszcze bardziej przyjazny, zrobiliśmy możliwość zdefiniowania komunikatu o zaznaczonych opcjach w opcjach pluginu.
Nasz komunikat składa się z trzech części. Jakiegoś tekstu (np. „Wybrano…”), w którym znajdują się dwie zmienne: ilość wybranych checkboxów oraz ilość wszystkich checkbów.
Ma wiec ogólnie postać „Wybrałeś {%} z {%}”.
Zapis ten może ci się wydawać znajomy. Szczególnie, jeżeli używałeś PHP, w którym istnieje funkcja printf(), która działa właśnie na takich tekstach, wstawiając pod poszczególne % przekazane w kolejnych parametrach zmienne.

W javascript takiej funkcji niestety nie ma, ale od czego jest wujek Google. Wyszukujemy „javascript printf” i jako pierwszy wynik dostajemy link do niezastąpionego http://stackoverflow.com/questions/610406/javascript-equivalent-to-printf-string-format.
Przepis podany pod tym linkiem wykorzystamy w naszej funkcji. Popatrz sobie w opcje naszego pluginu lub w gotowy kod jak to dokładnie działa.

Nasz plugin jest już praktycznie skończony. Pozostaje nadać mu stosowny wygląd i dopracować mały szczegół (o którym za chwilę).

Wpierw zajmijmy się wyglądem:

/* multiselect */    
    .multiselect {
        width:600px;
    }        
    .multiselect .multiselect-controls {
        height:30px;
    }

/* filter */    
    .multiselect .filter-cnt {
        width:290px; 
        margin-right:10px; 
        display: inline-block; 
        vertical-align: top;
    }
    .multiselect .filter-cnt label {
        display: none;
    }
    .multiselect .filter-cnt input {
        width:100%; 
        display: block; 
        height:30px; 
        -webkit-box-sizing:border-box; 
        -moz-box-sizing:border-box; 
        box-sizing:border-box; 
        border:1px solid #ddd; 
        padding:0 10px 0 25px; 
        border-radius:3px; 
        background: url(search.png) 5px center no-repeat;
    }
    .ie .multiselect .filter-cnt input {
        line-height:30px;
    }
    .multiselect .filter-cnt input:focus,
    .multiselect .filter-cnt input:active {
        border:1px solid #bbb; 
        background-color:#fafafa; 
        box-shadow:inset 0 0 3px rgba(0,0,0,0.2);
    }

/* przyciski select-all i unselect-all */
    .multiselect .buttons-cnt {
        width:300px; 
        display: inline-block; 
        vertical-align: top; 
        text-align: right; 
        float:right;
    }
    .multiselect .buttons-cnt a {
        text-align:left; 
        display: inline-block; 
        max-width:130px; 
        padding-left:35px; 
        height:30px; 
        color:#444; 
        font:11px/30px sans-serif; 
        margin:0 2px; 
        text-decoration: none; 
        outline:none; 
        border-radius:3px;
    }
    .multiselect .buttons-cnt a:hover {
        color:#000;
    }
    .multiselect .buttons-cnt a.select-all {
        background: url(ok.png) 10px center no-repeat;
    }
    .multiselect .buttons-cnt a.deselect-all {
        background: url(no.png) 10px center no-repeat;
    }

/* informacja o zaznaczonych */
    .multiselect .multiselect-info {
        font:11px sans-serif; 
        color:#888; 
        text-align: right;
    }

/* lista z checkboxami */    
    .multiselect .list-container {
        overflow: hidden; 
        overflow-y:auto; 
        height:209px; 
        margin:10px 0 0 0; 
        border:1px solid #fff; 
        border-radius: 3px;
        box-shadow:inset 0 0 6px rgba(0,0,0,.2); 
        position: relative;
    }
    .multiselect .empty-list {
        font:bold 13px sans-serif; 
        margin:90px 10px 0 10px; 
        text-align: center; 
        color:#444; 
        display: block; 
        padding:10px;
    }
    .multiselect ul {
        padding:0; 
        margin:0; 
        list-style:none; 
        font:13px sans-serif;
    }        
    .multiselect ul li {
        padding:7px; 
        border-bottom:1px solid #eee; 
        position: relative; 
        border-left:3px solid #ddd;
    }
    .multiselect ul li:last-child {border-bottom: 0;}
    .multiselect ul li input {
        position: absolute; 
        top:3px; left:3px;
    }
    .multiselect ul li label {
        display: block; 
        width:100%; 
        height:100%; padding-left:30px; 
        -webkit-box-sizing:border-box; 
        -moz-box-sizing:border-box; 
        box-sizing:border-box; 
        white-space: nowrap; 
        text-overflow:ellipsis; 
        overflow: hidden; 
        font-size:12px; 
        color:#333;
    }
    .multiselect ul .group-name {
        color:#444; 
        border-left:0; 
        margin:0;
    }
    .multiselect ul li.select {
        background: #f5f5f5; 
        border-left:3px solid #89CE7E;
    }
    .multiselect ul li.select:first-child {
        box-shadow:inset 0 2px 4px -2px rgba(0,0,0,.2);
    }
    .multiselect ul li.select:last-child {
        box-shadow:inset 0 -2px 4px -2px rgba(0,0,0,.2);
    }

Ostatnim zadaniem jest dla nas zajęcie się wspomnianym szczegółem.
Czasami zachodzi konieczność dynamicznej zmiany elementów w jakimś selekcie. W naszym przypadku taka zmiana nie wpłynęła by na wygląd wygenerowanej wcześniej listy z checkboxami (bo wygenerowaliśmy ją na bazie jeszcze starej zawartości selekta).
Jak to naprawić? Bardzo prosto.

Po zmianie zawartości selekta, naszą listę wystarczy opróżnić, a następnie za pomocą funkcji fillUl() (którą już użyliśmy!) uzupełnić raz jeszcze.
Domyślnie selekty nie mają zdarzenia wykrywającego, czy coś się w nich zmieniło. Dlatego w tym przypadku aktualizację podpinamy pod nasze zdarzenie „update” (równie dobrze może to być zdarzenie siorpaj)

$.fn.multiSelectToCheckboxes = function(options) {
    //...
    //...

    function updateList($select, $ul) {
        $ul.empty();
        fillUl($select, $ul);                
    };    

    return this.each(function() {
        var $select = $(this);

        var $wrapper = $('<div class="multiselect"></div>'); 
        $select.wrap($wrapper);

        var $container = $('<div class="multiselect-cnt"></div>');

        var $ulContainer = $('<div class="list-container"></div>');
        var $ul = $('<ul class="list"></ul>');
        $ulContainer.append($ul);

        var $controls = createMultiselectControl($select, $ul);

        $container.append($controls).append($ulContainer);

        var $infoDiv = $('<div class="multiselect-info"></div>');
        $container.append($infoDiv);

        $select.parent().append($container);

        fillUl($select, $ul);
        updateSelectInformation($select);

        $select.hide();

        $select.on('update', function() {
            updateList($select, $ul)
        })
    });
}

Cały kod

Gotowy cały kod pluginu ma postać:

(function($) {
 
    $.fn.multiSelectToCheckboxes = function(options) {
        options = $.extend({
            filterLabel : 'Szukaj:',
            filterPlaceholder : 'Szukaj',
            minFilterTextLength : 0,
            selectAllText : 'Zaznacz widoczne',
            deselectAllText : 'Odznacz widoczne',
            infoText : 'Wybrano {0} z {1}'
        }, options);

        function fillUl($select, $ul) {                
            var baseId = "_" + $select.attr("id");
            $select.find("option, optgroup").each(function(index) {
                var $t = $(this);
                if ($t.is("option")) {
                    var id = baseId + index;
                    var $li = $("<li></li>").appendTo($ul);
                    var $checkbox = $("<input type='checkbox' id='" + id + "'/>")
                        .appendTo($li)
                        .on('change', function() {
                            var $chk = $(this)
                            if ($chk.is(":checked")) {
                                $chk.parent().addClass('select');
                                $t.attr("selected", "selected");                                    
                            } else {
                                $chk.parent().removeClass('select');
                                $t.removeAttr("selected");
                            }
                            updateSelectInformation($select);
                        });
                    if ($t.is(":selected")) {
                        $checkbox.attr("checked", "checked");
                        $checkbox.parent().addClass('select');
                    }
                    $checkbox.after("<label for='" + id + "'>" + $t.text() + "</label>");
                } else {
                    var $li = $('<li class="group-name"><strong>' + $t.attr('label') + '</strong></li>').appendTo($ul);
                }
            });
            return $ul;
        };

        function updateList($select, $ul) {
            $ul.empty();
            fillUl($select, $ul);                
        };
     
        function createMultiselectControl($select, $ul) {
            var id = $select.attr('id');
            var $headerContainer = $('<div class="multiselect-controls"></div>');

            //text filter container
            var $filterDiv = $('<div class="filter-cnt"></div>');                
            var $filterLabel = $('<label for="'+id+'_filter"">'+options.filterLabel+'</label>')
            var $filterInput = $('<input type="text" id="'+id+'_filter" placeholder="'+options.filterPlaceholder+'" />');
            $filterInput.on('keyup', function() {                
                var $li = $ul.children('li');
                var val = $(this).val();
                $ul.parent().find('.empty-list').remove();

                if (val.length >= options.minFilterTextLength) {                        
                    $li.hide();
                    var $liVisible = $li.filter(':contains("'+val+'")');
                    $liVisible.show();
                                    
                    if ($li.length) {
                        if (!$liVisible.length) {
                            $ul.parent().append('<strong class="empty-list">Brak wyników</strong>')
                        }
                    }
                }
            });
            $filterDiv.append($filterLabel).append($filterInput);

            //select, deselect buttons
            var $buttonsDiv = $('<div class="buttons-cnt"></div>');
            var $buttonSelectAll = $('<a href="#" class="select-all">'+options.selectAllText+'</a>');
            var $buttonDeselectAll = $('<a href="#" class="deselect-all">'+options.deselectAllText+'</a>');
            $buttonSelectAll.on('click', function(e) {
                e.preventDefault();
                $ul.find('li:visible :checkbox').prop('checked', 1);
                $ul.find(':checkbox').trigger('change');
            })
            $buttonDeselectAll.on('click', function(e) {
                e.preventDefault();
                $ul.find('li:visible :checkbox').prop('checked', 0);
                $ul.find(':checkbox').trigger('change');
            })
            $buttonsDiv.append($buttonSelectAll).append($buttonDeselectAll);

            $headerContainer.append($filterDiv).append($buttonsDiv);            

            return $headerContainer;
        }            

        function updateSelectInformation($select) {
            var $input = $select.parent().find('.multiselect-info');                
            var $chk = $select.parent().find(':checkbox');
            var $chkSelected = $chk.filter(':checked');
            var tempArray = [$chkSelected.length, $chk.length];

            var informationText = options.infoText.replace(/{(\d+)}/g, function(match, number) { 
                return typeof tempArray[number] != 'undefined'? tempArray[number]: match
            });

            $input.html(informationText);
        }           

        return this.each(function() {
            var $select = $(this);
            var $wrapper = $('<div class="multiselect"></div>');
            $select.wrap($wrapper);

            var $ulContainer = $('<div class="list-container"></div>');
            var $ul = $('<ul class="list"></ul>');
            $ulContainer.append($ul);

            var $controls = createMultiselectControl($select, $ul);                
            
            var $container = $('<div class="multiselect-cnt"></div>');

            $container.append($controls).append($ulContainer);               
            
            //information div
            var $infoDiv = $('<div class="multiselect-info"></div>');
            $container.append($infoDiv);
            $select.parent().append($container);
            
            fillUl($select, $ul);
            updateSelectInformation($select);                

            $select.hide();
            $select.on('update', function() {
                updateList($select, $ul)
            })
        });
        
    };
})(jQuery);

Oraz jego przykładowe odpalenie:

<select multiple="multiple">
    <option value="1">Wartość 1</option>
    <option value="2">Wartość 2</option>
    <option value="3">Wartość 3</option>
    <option value="4">Wartość 4</option>
    <option value="5">Wartość 5</option>
    <option value="6">Wartość 6</option>
    <option value="7">Wartość 7</option>
    <option value="8">Wartość 8</option>
    <option value="9">Wartość 9</option>
    <option value="10">Wartość 10</option>
</select>

<script>
$(function() {
    $('select[multiple]').multiSelectToCheckboxes({
        filterLabel : 'Szukaj:',
        filterPlaceholder : '...',
        minFilterTextLength : 0,
        selectAllText : 'Zaznacz',
        deselectAllText : 'Odznacz',
        infoText : '{0} z {1}'
    });
})
</script>

Zobacz demo

Komentarze

  • alufers

    Super, akurat tego szukałem. :D

  • Pingback: Multiselekt wersja ukryta - doman.art.pl()

  • Comandeer

    IMO struktura jest lekko przekombinowana (postarałbym się wyrzucić parę divów) i brakuje mi ARIA (kilka role by się przydało + aria-hidden + jakiś ogólny opis selecta)
    no i od przycisków przecież jest odpowiedni znacznik – button. nie widzę sensu w zastępowaniu go przez a[href=#] (co jest po prostu obskurnym hackiem)
    myślę, że lepiej sprawowałby się przycisk typu „Zaznacz/odznacz wszystko” (lub po prostu odwracający zaznaczenie)
    no i osobiście bardziej odpowiadałby mi plugin, który przy odświeżeniu strony przy pomocy AJAX-a, automatycznie wyłapuje wszystkie select[multiple] (np łapiąc się callbacków CSS-owych animacji, jak to swego czasu robiło X-Tags)
    tak, wiem, marudzę ;)

    • kartofelek007

      Teraz tak:
      1) Aria – no można, chociaż ja w takich rzeczach z niej nie korzystam :)

      2) Istnieje button. Tylko kiedyś z nimi były problemy pod IE i został niesmak. Nad inputami nie masz pełnej kontroli i :before nie działa dla nich w Firefoxie. Więc w stylowaniu nie masz takich możliwości jak przy linkach, które i tak są generowane dynamicznie skryptem.
      3) Zbyt skomplikowana struktura. To oczywiście można poprawił i dać o wiele łatwiejszą. Ja wolę czasami dodać jakiś div, by potem w którymś z kolei użyciu nie brakowało możliwości stylowania :D
      4) Ajax. To tylko prosty plugin. Można dodać takie rzeczy o których piszesz, tylko czy aby potem nie dostaniemy kolejnego przeładowanego pluginu, który miał zamienić tylko selekt, a autor dodatkowo wmontował w niego obsługę paralaxu :} No a przecież po ajaxie możesz zawsze użyć update, które napisaliśmy.

      Ogólnie: Zmiany można a nawet trzeba wprowadzać. Jak już kiedyś pisałem – ufam czytelnikom że sobie poradzą :) Także to co napisałeś to dobra praca domowa.