myTerminal/faded-multiselect

View on GitHub
src/scripts/faded-multiselect.js

Summary

Maintainability
D
1 day
Test Coverage
var FadedMultiselect = function (elementSelector, options) {
    var om = $(elementSelector).first(),
        labels = {
            buttonSelect: "Select",
            selected: "selected",
            noneSelected: "None selected",
            allSelected: "All selected"
        },
        parentTemplate = "" +
            "<div class='faded-multiselect'>" +
            "  <button class='faded-multiselect-button'>" +
            "    <span class='faded-multiselect-button-text'>" +
            "      " + labels.buttonSelect +
            "    </span>" +
            "    <div class='caret'>&#9662;</div>" +
            "  </button>" +
            "  <div class='faded-multiselect-dropdown'></div>" +
            "  <div class='original-multiselect'></div>" +
            "</div>",
        optionTemplate = "" +
            "<div class='faded-multiselect-dropdown-option'>" +
            "  <input type='checkbox'>" +
            "  <span>OptionX</span>" +
            "</div>",
        fmParent,
        scrollbar,

        init = function () {
            fmParent = $(parentTemplate);
            om = om.replaceWith(fmParent);
            fmParent.find(".original-multiselect").append(om);

            refresh();
            bindClose();
        },

        refresh = function () {
            if (options.maxDropdownHeight) {
                if (scrollbar) {
                    scrollbar.destroy();
                }
            }

            clearItems();

            addAllOption();
            copyItemsFromSelect();

            if (options.maxDropdownHeight) {
                fmParent.find(".faded-multiselect-dropdown").height(options.maxDropdownHeight);
                scrollbar = new FadedScrollbar(fmParent.find(".faded-multiselect-dropdown"));
            }
            
            fmParent.find(".faded-multiselect-button").bind("mousedown", onButtonClick);
            fmParent.find(".faded-multiselect-dropdown-option").bind("click", onItemClick);

            updateButtonText();
        },

        destroy = function () {
            clearItems();

            if (scrollbar) {
                scrollbar.destroy();
            }

            fmParent.replaceWith(om);
        },

        getValue = function () {
            return om.val();
        },

        bindClose = function () {
            $("html").bind("mousedown", function (evt) {
                evt.stopPropagation();
                closeDropdown();
            });
        },

        closeDropdown = function () {
            fmParent.removeClass("open");
        },

        openDropdown = function () {
            fmParent.addClass("open");

            if (scrollbar) {
                scrollbar.refresh();
            }
        },

        unbindEvents = function () {
            fmParent.find(".faded-multiselect-button").unbind("mousedown");
            fmParent.find(".faded-multiselect-dropdown-option").unbind("click");
        },

        clearItems = function () {
            unbindEvents();
            fmParent.find(".faded-multiselect-dropdown-option").remove();
        },

        addAllOption = function () {
            if (!options.allOption) {
                return;
            }

            var allOption = $(optionTemplate);

            allOption.find("span").html("All");
            allOption.attr("data-value", "");
            allOption.attr("data-special", "all");
            fmParent.find(".faded-multiselect-dropdown").append(allOption);
        },

        copyItemsFromSelect = function () {
            $.each(om.find("option"), function (i, e) {
                var oldOption = $(e),
                    newOption = $(optionTemplate);

                newOption.find("span").html(oldOption.html());
                newOption.attr("data-value", oldOption.val());
                fmParent.find(".faded-multiselect-dropdown").append(newOption);
            });

            loadItemStatesFromNative();
            loadSpecialItemStates();
        },

        loadItemStatesFromNative = function () {
            $.each(om.find("option"), function (i, e) {
                var oldOption = $(e),
                    isSelected = oldOption[0].selected,
                    value = $(e).attr("value"),
                    newOption = fmParent.find(".faded-multiselect-dropdown").find("[data-value=" + value + "]"),
                    checkbox = newOption.find("input[type=checkbox]");

                checkbox.attr("checked", isSelected);
            });
        },

        loadSpecialItemStates = function () {
            if (!options.allOption) {
                return;
            }

            var checkboxForAll = getCheckboxForAll();

            if (getValue().length === om.find("option").length) {
                checkboxForAll.attr("checked", true);
            } else if (!getValue().length) {
                checkboxForAll.attr("checked", false);
            }
        },

        saveItemStateToNative = function (item) {
            var itemSpecialTag = $(item).attr("data-special"),
                isItemToSelectAll = itemSpecialTag === "all";

            if (isItemToSelectAll) {
                toggleSelectAllItems();
            } else {
                var value = $(item).attr("data-value"),
                    option = om.find("option[value=" + value + "]")[0];

                option.selected = !option.selected;
            }

            if (options.onStateChange) {
                options.onStateChange(getValue(), itemSpecialTag || value);
            }
        },

        onButtonClick = function (event) {
            event.stopPropagation();

            if (fmParent.hasClass("open")) {
                closeDropdown();
            } else {
                openDropdown();
            }
        },

        onItemClick = function (event) {
            event.stopPropagation();

            var checkbox = $(this).find("input[type=checkbox]");

            checkbox.attr("checked", !checkbox.attr("checked"));
            saveItemStateToNative($(this));

            updateButtonText();
            setStateForAll();
        },

        setStateForAll = function () {
            var countOfSelectedItems = getValue().length,
                totalOptionsToSelect = om.find("option").length;

            getCheckboxForAll().attr("checked",
                                     countOfSelectedItems === totalOptionsToSelect);
        },

        getCheckboxForAll = function () {
            return fmParent.find("[data-special=all] input[type=checkbox]");
        },

        toggleSelectAllItems = function () {
            var shouldSelectAll = getValue().length !== om.find("option").length;

            $.each(om.find("option"), function (i, e) {
                $(e)[0].selected = shouldSelectAll;
            });

            refresh();
        },

        updateButtonText = function () {
            var countOfSelectedOptions = getValue().length,
                buttonLabel = fmParent.find(".faded-multiselect-button-text"),
                selectedOptionText,
                buttonText;

            if (options.buttonText) {
                buttonText = options.buttonText(getValue());
            } else {
                if (!countOfSelectedOptions) {
                    buttonText = labels.noneSelected;
                } else if (countOfSelectedOptions === om.find("option").length) {
                    buttonText = labels.allSelected;
                } else if (countOfSelectedOptions === 1) {
                    selectedOptionText = om.find("option[value=" + getValue()[0] + "]").text();
                    buttonText = selectedOptionText;
                } else {
                    buttonText = countOfSelectedOptions + " " + labels.selected;
                }
            }

            buttonLabel.text(buttonText);
        };

    options = options || {};
    init();

    return {
        init: init,
        refresh: refresh,
        destroy: destroy,
        getValue: getValue
    };
};