]> _ Git - Animations.git/commitdiff
#7485
authorVincent Vanwaelscappel <vincent@cubedesigners.com>
Wed, 16 Apr 2025 09:58:17 +0000 (11:58 +0200)
committerVincent Vanwaelscappel <vincent@cubedesigners.com>
Wed, 16 Apr 2025 09:58:17 +0000 (11:58 +0200)
Discac/Discac.zip
Discac/_doc/publish.bat
Discac/checkbox.png [new file with mode: 0644]
Discac/index.html [new file with mode: 0644]
Discac/jquery.multi-select.js [new file with mode: 0644]

index 4d38196bdb4ae2dde968401f2318bbb84488c0fc..ef28acbdf66f47b791825becb3cc6d4ac72a66cd 100644 (file)
Binary files a/Discac/Discac.zip and b/Discac/Discac.zip differ
index 21e3cc3235580010cb661334b5afbbf5de51ebd3..01f857e7fd768795220c4f13c328998b6a31077a 100644 (file)
@@ -1,3 +1,3 @@
 @echo off
 "C:\Program Files\7-Zip\7z.exe" a -tzip ../Discac.zip ../ -xr0!_doc -xr0!*.zip -xr0!*.less -xr0!*.map
-scp ../Discac.zip extranet@workshop.fluidbook.com:/data1/extranet/www/fluidbook/books/working/21516
\ No newline at end of file
+scp -P 51895 ../Discac.zip toolbox@toolbox.fluidbook.com:/application/protected/fluidbookpublication/working/32262
\ No newline at end of file
diff --git a/Discac/checkbox.png b/Discac/checkbox.png
new file mode 100644 (file)
index 0000000..9163bea
Binary files /dev/null and b/Discac/checkbox.png differ
diff --git a/Discac/index.html b/Discac/index.html
new file mode 100644 (file)
index 0000000..b599f08
--- /dev/null
@@ -0,0 +1,510 @@
+<!DOCTYPE html>
+<html lang="fr">
+<head>
+    <meta charset="UTF-8">
+    <meta name="width" content="440">
+    <meta name="height" content="760">
+    <title></title>
+    <link rel="preconnect" href="https://fonts.gstatic.com">
+    <link href="https://fonts.googleapis.com/css2?family=Open+Sans:wght@400&display=swap" rel="stylesheet">
+    <style>
+        * {
+            padding: 0;
+            margin: 0;
+        }
+
+        html, body {
+            height: 100%;
+        }
+
+        body {
+            font-family: "Open Sans", sans-serif;
+            font-size: 17px;
+            color: #fff;
+            background-color: #93c23a;
+        }
+
+        a {
+            color: #fff;
+            text-decoration: none;
+            display: block;
+            background-color: #93c23a;
+            padding: 5px 25px;
+            position: relative;
+        }
+
+        a:hover {
+            background-color: #66a11a;
+            transition: background-color 200ms;
+        }
+
+        a span {
+            position: absolute;
+            right: 25px;
+            top: 5px;
+        }
+
+        h2 {
+            padding: 25px 25px 0px;
+            font-weight: 400;
+            font-size: 17px;
+        }
+
+        main {
+            max-width: 440px;
+            margin: 0 auto;
+        }
+
+        #selects {
+            padding: 25px;
+        }
+
+        .multi-select-container {
+            display: block;
+            position: relative;
+        }
+
+        .multi-select-menu {
+            position: absolute;
+            left: 0;
+            top: 0.8em;
+            float: left;
+            min-width: 100%;
+            background: #fff;
+            margin: 1em 0;
+            padding: 0.4em 0;
+            display: none;
+            z-index: 2;
+        }
+
+
+        .multi-select-menu label {
+            color: #464646;
+            display: block;
+            padding: 3px 10px;
+            font-size: 16px;
+        }
+
+        .multi-select-menu input {
+            margin-right: 0.3em;
+            vertical-align: 0.1em;
+        }
+
+        .multi-select-button {
+            margin: 4px 0;
+            display: block;
+            color: #474747;
+            font-size: 0.875em;
+            padding: 0.2em 2em 0.2em 0.6em;
+            white-space: nowrap;
+            overflow: hidden;
+            text-overflow: ellipsis;
+            background-color: #fff;
+            border-radius: 4px;
+            cursor: default;
+            position: relative;
+        }
+
+        .multi-select-button.active {
+            background-color: #66a11a;
+            color: #fff;
+        }
+
+        .multi-select-button.active:after {
+            border-color: #fff transparent transparent transparent;
+        }
+
+        .multi-select-container--open .multi-select-button.active::after {
+            border-color: transparent transparent #fff transparent
+        }
+
+        .multi-select-button:after {
+            content: "";
+            display: inline-block;
+            width: 0;
+            height: 0;
+            border-style: solid;
+            border-width: 0.4em 0.4em 0 0.4em;
+            border-color: #474747 transparent transparent transparent;
+            margin-left: 0.4em;
+            vertical-align: 0.1em;
+            position: absolute;
+            right: 10px;
+            top: 11px;
+        }
+
+        .multi-select-presets {
+            position: relative;
+            padding-top: 5px;
+            border-top: 1px solid #eee;
+        }
+
+        .multi-select-presets button {
+            cursor: pointer;
+            position: absolute;
+            top: 5px;
+            right: 5px;
+            background-color: #66a11a;
+            color: #fff;
+            text-transform: uppercase;
+            border: 0;
+            border-radius: 4px;
+            padding: 2px 15px;
+            font-size: 17px;
+            font-family: "Open Sans", sans-serif;
+        }
+
+        .multi-select-container--open .multi-select-menu {
+            display: block;
+        }
+
+        .multi-select-container--open .multi-select-button:after {
+            border-width: 0 0.4em 0.4em 0.4em;
+            border-color: transparent transparent #999 transparent;
+        }
+
+        .checkbox {
+            display: inline-block;
+            width: 19px;
+            height: 19px;
+            background-image: url("checkbox.png");
+            background-size: 19px auto;
+            background-repeat: no-repeat;
+            background-position: 0 100%;
+            margin-right: 5px;
+            vertical-align: top;
+            position: relative;
+            top: 1px;
+        }
+
+        .checkbox[checked] {
+            background-position: 0 0;
+        }
+
+        #emptyres {
+            padding: 0 25px;
+        }
+
+    </style>
+</head>
+<body>
+<main>
+    <h2>Filtrer ma recherche</h2>
+    <div id="selects">
+
+    </div>
+    <nav>
+        <a href="#" data-largeur="70,90,140" data-vasque="VS,VD,VP" data-meuble="1T,2T" data-profondeur="z54"
+           data-poignees="yes" data-page="4">Loft<span>4</span></a>
+        <a href="#" data-largeur="70,90,140" data-vasque="VS,VD,VP" data-meuble="2T" data-profondeur="z54"
+           data-poignees="no" data-page="10">Allure<span>10</span></a>
+        <a href="#" data-largeur="70,140" data-vasque="VS,VD,VP" data-meuble="2T" data-profondeur="z54"
+           data-poignees="yes" data-page="16">Cozy<span>16</span></a>
+        <a href="#" data-largeur="70,90,105,140" data-vasque="VS,VD,VG,VP" data-meuble="2T,P" data-profondeur="z54"
+           data-poignees="yes" data-page="22">Intégrale<span>22</span></a>
+        <a href="#" data-largeur="105" data-vasque="VG,VP" data-meuble="2T" data-profondeur="z54" data-poignees="yes"
+           data-page="28">Mythic<span>28</span></a>
+        <a href="#" data-largeur="70,90,140" data-vasque="VS,VD,VP" data-meuble="3T" data-profondeur="z54"
+           data-poignees="yes" data-page="32">Trendy<span>32</span></a>
+        <a href="#" data-largeur="60,90,120" data-vasque="VS,VD,VG,VP" data-meuble="1T,2T" data-profondeur="z46"
+           data-poignees="no" data-page="38">Rivage<span>38</span></a>
+        <a href="#" data-largeur="90,120" data-vasque="VS,VD,VG" data-meuble="2T" data-profondeur="z46"
+           data-poignees="no" data-page="46">Harmonie<span>46</span></a>
+        <a href="#" data-largeur="60,90,120" data-vasque="VS,VD,VG,VP" data-meuble="1T,2T" data-profondeur="z46"
+           data-poignees="yes" data-page="52">Swing<span>52</span></a>
+        <a href="#" data-largeur="60,90,120" data-vasque="VS,VD,VG,VP" data-meuble="1T,2T" data-profondeur="z46"
+           data-poignees="yes" data-page="60">Millésime<span>60</span></a>
+        <a href="#" data-largeur="90,120" data-vasque="VG,VP" data-meuble="2T" data-profondeur="z46"
+           data-poignees="yes" data-page="68">Jazz<span>68</span></a>
+        <a href="#" data-largeur="90,120" data-vasque="VS,VD,VP" data-meuble="1T,2T" data-profondeur="z46"
+           data-poignees="yes" data-page="74">Fabrik<span>74</span></a>
+        <a href="#" data-largeur="60,70,90,120" data-vasque="VS,VD" data-meuble="2T,P" data-profondeur="z46"
+           data-poignees="yes" data-page="80">Chango<span>80</span></a>
+        <a href="#" data-largeur="w124" data-vasque="VG" data-meuble="P" data-profondeur="z65" data-poignees="yes"
+           data-page="86">Lave-linge<span>86</span></a>
+        <a href="#" data-largeur="w40" data-vasque="VS" data-meuble="P" data-profondeur="z22" data-poignees="yes"
+           data-page="90">Lave-mains<span>90</span></a>
+    </nav>
+    <div id="emptyres">Aucun produit ne correspond aux filtres sélectionnés</div>
+</main>
+<script src="../../jquery.js"></script>
+<script src="jquery.multi-select.js"></script>
+<script>
+    var selects = {
+        profondeur: {
+            label: 'Profondeurs',
+            data: {
+                'z54': '54 cm',
+                'z46': '46 cm',
+                'z22': '22 cm',
+                'z65': '65 cm'
+            }
+        },
+
+        poignees: {
+            label: 'Poignées',
+            data: {
+                'yes': 'Oui',
+                'no': 'Non',
+            }
+        },
+        largeur: {
+            label: 'Largeurs',
+            data: {
+//                '40': '40 cm',
+                '60': '60 cm',
+                '70': '70 cm',
+                '90': '90 cm',
+                '105': '105 cm',
+                '120': '120 cm',
+                //              '124': '124 cm',
+                '140': '140 cm'
+            }
+        },
+
+        vasque: {
+            // Simple vasque,Double vasque,Vasque à gauche,Vasque à poser
+            label: 'Vasques',
+            data: {
+                'VS': 'Simple vasque',
+                'VD': 'Double vasque',
+                'VG': 'Vasque à gauche',
+                'VP': 'Vasque à poser',
+            }
+        },
+
+
+        meuble: {
+            // Simple vasque,Double vasque,Vasque à gauche,Vasque à poser
+            label: 'Sous-vasques',
+            data: {
+                '1T': '1 tiroir',
+                '2T': '2 tiroirs',
+                '3T': '3 tiroirs',
+                'P': 'Porte',
+            }
+        },
+
+
+    }
+
+    $(function () {
+        var se = {};
+        var mem = {profondeur: [], poignees: [], largeur: [], vasque: [], meuble: []};
+        if (parent.fluidbook.discac !== undefined) {
+            mem = $.extend({}, mem, parent.fluidbook.discac);
+        }
+        $.each(selects, function (k, v) {
+            var allValues = [];
+            var s = '<select id="' + k + '" name="' + k + '" multiple>';
+            $.each(v.data, function (value, label) {
+                allValues.push(value);
+                var selected = mem[k].indexOf(value) >= 0 ? ' selected ' : '';
+                s += '<option value="' + value + '"' + selected + '>' + label + '</option>';
+            });
+            s += '</select>';
+            $("#selects").append(s);
+            se[k] = $("#" + k).multiSelect({
+                'noneText': v.label,
+                presets: [
+                    {
+                        name: 'Tout sélectionner',
+                        options: allValues,
+                    },
+                ]
+            }).on('change', function () {
+                updateChapters();
+            });
+        });
+
+        updateChapters();
+
+        function updateCheckboxes() {
+            $(":checkbox").checkbox();
+            $(":radio").checkbox();
+        }
+
+        function updateChapters() {
+            var save = {};
+            $('select').each(function () {
+                save[$(this).attr('name')] = $(this).val();
+            });
+            parent.fluidbook.discac = save;
+
+            var hasVisible = false;
+            $('nav a').each(function () {
+                var hide = false;
+                var a = $(this);
+                $("#selects select").each(function () {
+                    var vals = $(this).val();
+                    if (vals.length === 0) {
+                        return;
+                    }
+                    var name = $(this).attr('name');
+                    var attrvalues = $(a).attr('data-' + name).split(',');
+                    var inter = vals.filter(function (n) {
+                        return attrvalues.indexOf(n) !== -1;
+                    });
+                    if (inter.length === 0) {
+                        hide = true;
+                    }
+                });
+                if (hide) {
+                    $(a).hide();
+                } else {
+                    hasVisible = true;
+                    $(a).show();
+                }
+                updateCheckboxes();
+            });
+
+            if (hasVisible) {
+                $("#emptyres").hide();
+            } else {
+                $("#emptyres").show();
+            }
+
+        }
+
+        $(document).on('click', 'a', function () {
+            parent.location.hash = "#/page/" + $(this).data('page');
+            return false;
+        });
+
+        updateChapters();
+
+
+    });
+    (function ($) {
+        $.propHooks.checked = {
+            set: function (el, value) {
+                if (el.checked !== value) {
+                    el.checked = value;
+                    $(el).trigger('checkboxchange');
+                }
+            }
+        };
+
+        function JQcheckbox(element) {
+            this.element = element;
+            this.type = this.element.attr('type');
+            this.input = element;
+            this.substitute;
+            this.label = element.parents("label");
+            if (this.label.length == 0) {
+                this.label = $('label[for="' + element.attr('id') + '"]');
+            }
+            this.label.attr('data-value', element.attr('value'));
+            this.init();
+        }
+
+        JQcheckbox.prototype = {
+            init: function () {
+                var $this = this;
+                this.element.hide();
+                this.element.next(".checkbox").remove();
+                this.substitute = $('<div class="' + this.type + '"></div>');
+
+                var ignore = ['name', 'id', 'type', 'checked', 'style', 'value', 'class'];
+
+                $.each(this.element.attributes, function (k, v) {
+                    if (ignore == undefined || ignore.indexOf(k) == -1) {
+                        $this.substitute.attr(k, v);
+                    }
+                });
+                this.element.after(this.substitute);
+                this.initEvents();
+                this.initState();
+            },
+            initEvents: function () {
+                var $this = this;
+                this.substitute.off('click');
+                this.substitute.on('click', function () {
+                    $this.click();
+                    return false;
+                });
+
+                this.element.off('checkboxchange');
+                this.element.on('checkboxchange', function () {
+                    $this.initState();
+                });
+                this.label.off('click');
+                this.label.on('click', function (e) {
+                    if (e.target == this) {
+                        $this.click();
+                        return false;
+                    }
+                });
+
+            },
+            click: function () {
+                var change = false;
+                if (this.type == 'radio') {
+                    change = this.check();
+                } else {
+                    change = this.toggle();
+                }
+
+                if (change) {
+                    this.element.trigger('change');
+                }
+            },
+            initState: function () {
+                if (!this.element.prop('checked')) {
+                    this.uncheck();
+                } else {
+                    this.check();
+                }
+            },
+            toggle: function () {
+                this.element.trigger('change');
+                if (this.element.prop('checked')) {
+                    this.uncheck();
+                } else {
+                    this.check();
+                }
+                return true;
+            },
+            check: function () {
+                if (this.type == 'radio') {
+                    try {
+                        $(':radio[name="' + this.element.attr('name') + '"]').not(this.element).each(function () {
+                            $(this).data('checkbox').uncheck();
+                        });
+                    } catch (err) {
+
+                    }
+                }
+
+                if (this.substitute.attr('checked') != 'checked') {
+                    this.substitute.attr('checked', 'checked');
+                    this.element.prop('checked', true);
+                    this.label.attr('data-checked', 'checked');
+                    return true;
+                }
+                return false;
+            },
+            uncheck: function () {
+                this.label.attr('data-checked', null);
+                this.substitute.attr('checked', null);
+                this.element.prop('checked', false);
+            }
+
+
+        };
+
+        jQuery.fn.checkbox = function () {
+            return this.each(function () {
+                var $this = $(this);
+                if ($(this).data('checkbox') != undefined) {
+                    $(this).data('checkbox').initState();
+                    return;
+                }
+                $(this).data('checkbox', new JQcheckbox($this));
+            })
+        };
+    })(jQuery);
+
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/Discac/jquery.multi-select.js b/Discac/jquery.multi-select.js
new file mode 100644 (file)
index 0000000..8143f30
--- /dev/null
@@ -0,0 +1,442 @@
+// jquery.multi-select.js
+// by mySociety
+// https://github.com/mysociety/jquery-multi-select
+
+;(function ($) {
+
+    "use strict";
+
+    var pluginName = "multiSelect",
+        defaults = {
+            'containerHTML': '<div class="multi-select-container">',
+            'menuHTML': '<div class="multi-select-menu">',
+            'buttonHTML': '<span class="multi-select-button">',
+            'menuItemsHTML': '<div class="multi-select-menuitems">',
+            'menuItemHTML': '<label class="multi-select-menuitem">',
+            'presetsHTML': '<div class="multi-select-presets">',
+            'modalHTML': undefined,
+            'menuItemTitleClass': 'multi-select-menuitem--titled',
+            'activeClass': 'multi-select-container--open',
+            'noneText': '-- Select --',
+            'allText': undefined,
+            'presets': undefined,
+            'positionedMenuClass': 'multi-select-container--positioned',
+            'positionMenuWithin': undefined,
+            'viewportBottomGutter': 20,
+            'menuMinHeight': 200
+        };
+
+    /**
+     * @constructor
+     */
+    function MultiSelect(element, options) {
+        this.element = element;
+        this.$element = $(element);
+        this.settings = $.extend({}, defaults, options);
+        this._defaults = defaults;
+        this._name = pluginName;
+        this.init();
+    }
+
+    function arraysAreEqual(array1, array2) {
+        if (array1.length != array2.length) {
+            return false;
+        }
+
+        array1.sort();
+        array2.sort();
+
+        for (var i = 0; i < array1.length; i++) {
+            if (array1[i] !== array2[i]) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    $.extend(MultiSelect.prototype, {
+
+        init: function () {
+            this.checkSuitableInput();
+            this.findLabels();
+            this.constructContainer();
+            this.constructButton();
+            this.constructMenu();
+            this.constructModal();
+
+            this.setUpBodyClickListener();
+            this.setUpLabelsClickListener();
+
+            this.$element.hide();
+        },
+
+        checkSuitableInput: function (text) {
+            if (this.$element.is('select[multiple]') === false) {
+                throw new Error('$.multiSelect only works on <select multiple> elements');
+            }
+        },
+
+        findLabels: function () {
+            this.$labels = $('label[for="' + this.$element.attr('id') + '"]');
+        },
+
+        constructContainer: function () {
+            this.$container = $(this.settings['containerHTML']);
+            this.$element.data('multi-select-container', this.$container);
+            this.$container.insertAfter(this.$element);
+        },
+
+        constructButton: function () {
+            var _this = this;
+            this.$button = $(this.settings['buttonHTML']);
+            this.$button.attr({
+                'role': 'button',
+                'aria-haspopup': 'true',
+                'tabindex': 0,
+                'aria-label': this.$labels.eq(0).text()
+            })
+                .on('keydown.multiselect', function (e) {
+                    var key = e.which;
+                    var returnKey = 13;
+                    var escapeKey = 27;
+                    var spaceKey = 32;
+                    var downArrow = 40;
+                    if ((key === returnKey) || (key === spaceKey)) {
+                        e.preventDefault();
+                        _this.$button.click();
+                    } else if (key === downArrow) {
+                        e.preventDefault();
+                        _this.menuShow();
+                        var group = _this.$presets || _this.$menuItems;
+                        group.children(":first").focus();
+                    } else if (key === escapeKey) {
+                        _this.menuHide();
+                    }
+                }).on('click.multiselect', function (e) {
+                _this.menuToggle();
+            })
+                .appendTo(this.$container);
+
+            this.$element.on('change.multiselect', function () {
+                _this.updateButtonContents();
+            });
+
+            this.updateButtonContents();
+        },
+
+        updateButtonContents: function () {
+            var _this = this;
+            var options = [];
+            var selected = [];
+
+            this.$element.find('option').each(function () {
+                var text = /** @type string */ ($(this).text());
+                options.push(text);
+                if ($(this).is(':selected')) {
+                    selected.push($.trim(text));
+                }
+            });
+
+            this.$button.empty();
+
+            if (selected.length == 0) {
+                this.$button.removeClass('active').text(this.settings['noneText']);
+            } else if ((selected.length === options.length) && this.settings['allText']) {
+                this.$button.removeClass('active').text(this.settings['allText']);
+            } else {
+                this.$button.addClass('active').text(this.settings['noneText'] + ' : ' + selected.join(', '));
+            }
+        },
+
+        constructMenu: function () {
+            var _this = this;
+
+            this.$menu = $(this.settings['menuHTML']);
+            this.$menu.attr({
+                'role': 'menu'
+            }).on('keyup.multiselect', function (e) {
+                var key = e.which;
+                var escapeKey = 27;
+                if (key === escapeKey) {
+                    _this.menuHide();
+                    _this.$button.focus();
+                }
+            })
+                .appendTo(this.$container);
+
+            this.constructMenuItems();
+
+            if (this.settings['presets']) {
+                this.constructPresets();
+            }
+        },
+
+        constructMenuItems: function () {
+            var _this = this;
+
+            this.$menuItems = $(this.settings['menuItemsHTML']);
+            this.$menu.append(this.$menuItems);
+
+            this.$element.on('change.multiselect', function (e, internal) {
+                // Don't need to update the menu items if this
+                // change event was fired by our tickbox handler.
+                if (internal !== true) {
+                    _this.updateMenuItems();
+                }
+            });
+
+            this.updateMenuItems();
+        },
+
+        updateMenuItems: function () {
+            var _this = this;
+            this.$menuItems.empty();
+
+            this.$element.children('optgroup,option').each(function (index, element) {
+                var $item;
+                if (element.nodeName === 'OPTION') {
+                    $item = _this.constructMenuItem($(element), index);
+                    _this.$menuItems.append($item);
+                } else {
+                    _this.constructMenuItemsGroup($(element), index);
+                }
+            });
+        },
+
+        upDown: function (type, e) {
+            var key = e.which;
+            var upArrow = 38;
+            var downArrow = 40;
+
+            if (key === upArrow) {
+                e.preventDefault();
+                var prev = $(e.currentTarget).prev();
+                if (prev.length) {
+                    prev.focus();
+                } else if (this.$presets && type === 'menuitem') {
+                    this.$presets.children(':last').focus();
+                } else {
+                    this.$button.focus();
+                }
+            } else if (key === downArrow) {
+                e.preventDefault();
+                var next = $(e.currentTarget).next();
+                if (next.length || type === 'menuitem') {
+                    next.focus();
+                } else {
+                    this.$menuItems.children(':first').focus();
+                }
+            }
+        },
+
+        constructPresets: function () {
+            var _this = this;
+            this.$presets = $(this.settings['presetsHTML']);
+            this.$menu.append(this.$presets);
+
+            $.each(this.settings['presets'], function (i, preset) {
+                var unique_id = _this.$element.attr('name') + '_preset_' + i;
+                var $item = $(_this.settings['menuItemHTML'])
+                    .attr({
+                        'for': unique_id,
+                        'role': 'menuitem'
+                    })
+                    .text(' ' + preset.name)
+                    .on('keydown.multiselect', _this.upDown.bind(_this, 'preset'))
+                    .appendTo(_this.$presets);
+
+                var $input = $('<input>')
+                    .attr({
+                        'type': 'checkbox',
+                        'name': _this.$element.attr('name') + '_presets',
+                        'id': unique_id
+                    })
+                    .prependTo($item);
+
+                $input.on('change.multiselect', function () {
+                    if ($(this).prop('checked')) {
+                        _this.$element.val(preset.options);
+                    } else {
+                        _this.$element.val([]);
+                    }
+                    _this.$element.trigger('change');
+                });
+            });
+            _this.$presets.append('<button>Valider</button>');
+            this.$presets.on('click','button',function(){
+                _this.menuHide();
+                return false;
+            });
+
+            this.$element.on('change.multiselect', function () {
+                _this.updatePresets();
+            });
+
+            this.updatePresets();
+        },
+
+        updatePresets: function () {
+            var _this = this;
+
+            $.each(this.settings['presets'], function (i, preset) {
+                var unique_id = _this.$element.attr('name') + '_preset_' + i;
+                var $input = _this.$presets.find('#' + unique_id);
+
+                if (arraysAreEqual(preset.options || [], _this.$element.val() || [])) {
+                    $input.prop('checked', true);
+                } else {
+                    $input.prop('checked', false);
+                }
+            });
+        },
+
+        constructMenuItemsGroup: function ($optgroup, optgroup_index) {
+            var _this = this;
+
+            $optgroup.children('option').each(function (option_index, option) {
+                var $item = _this.constructMenuItem($(option), optgroup_index + '_' + option_index);
+                var cls = _this.settings['menuItemTitleClass'];
+                if (option_index !== 0) {
+                    cls += 'sr';
+                }
+                $item.addClass(cls).attr('data-group-title', $optgroup.attr('label'));
+                _this.$menuItems.append($item);
+            });
+        },
+
+        constructMenuItem: function ($option, option_index) {
+            var unique_id = this.$element.attr('name') + '_' + option_index;
+            var $item = $(this.settings['menuItemHTML'])
+                .attr({
+                    'for': unique_id,
+                    'role': 'menuitem'
+                })
+                .on('keydown.multiselect', this.upDown.bind(this, 'menuitem'))
+                .text(' ' + $option.text());
+
+            var $input = $('<input>')
+                .attr({
+                    'type': 'checkbox',
+                    'id': unique_id,
+                    'value': $option.val()
+                })
+                .prependTo($item);
+
+            if ($option.is(':disabled')) {
+                $input.attr('disabled', 'disabled');
+            }
+            if ($option.is(':selected')) {
+                $input.prop('checked', 'checked');
+            }
+
+            $input.on('change.multiselect', function () {
+                if ($(this).prop('checked')) {
+                    $option.prop('selected', true);
+                } else {
+                    $option.prop('selected', false);
+                }
+
+                // .prop() on its own doesn't generate a change event.
+                // Other plugins might want to do stuff onChange.
+                $option.trigger('change', [true]);
+            });
+
+            return $item;
+        },
+
+        constructModal: function () {
+            var _this = this;
+
+            if (this.settings['modalHTML']) {
+                this.$modal = $(this.settings['modalHTML']);
+                this.$modal.on('click.multiselect', function () {
+                    _this.menuHide();
+                })
+                this.$modal.insertBefore(this.$menu);
+            }
+        },
+
+        setUpBodyClickListener: function () {
+            var _this = this;
+
+            // Hide the $menu when you click outside of it.
+            $('html').on('click.multiselect', function () {
+                _this.menuHide();
+            });
+
+            // Stop click events from inside the $button or $menu from
+            // bubbling up to the body and closing the menu!
+            this.$container.on('click.multiselect', function (e) {
+                e.stopPropagation();
+            });
+        },
+
+        setUpLabelsClickListener: function () {
+            var _this = this;
+            this.$labels.on('click.multiselect', function (e) {
+                e.preventDefault();
+                e.stopPropagation();
+                _this.menuToggle();
+            });
+        },
+
+        menuShow: function () {
+            $('html').trigger('click.multiselect'); // Close any other open menus
+            this.$container.addClass(this.settings['activeClass']);
+
+            if (this.settings['positionMenuWithin'] && this.settings['positionMenuWithin'] instanceof $) {
+                var menuLeftEdge = this.$menu.offset().left + this.$menu.outerWidth();
+                var withinLeftEdge = this.settings['positionMenuWithin'].offset().left +
+                    this.settings['positionMenuWithin'].outerWidth();
+
+                if (menuLeftEdge > withinLeftEdge) {
+                    this.$menu.css('width', (withinLeftEdge - this.$menu.offset().left));
+                    this.$container.addClass(this.settings['positionedMenuClass']);
+                }
+            }
+
+            var menuBottom = this.$menu.offset().top + this.$menu.outerHeight();
+            var viewportBottom = $(window).scrollTop() + $(window).height();
+            if (menuBottom > viewportBottom - this.settings['viewportBottomGutter']) {
+                this.$menu.css({
+                    'maxHeight': Math.max(
+                        viewportBottom - this.settings['viewportBottomGutter'] - this.$menu.offset().top,
+                        this.settings['menuMinHeight']
+                    ),
+                    'overflow': 'scroll'
+                });
+            } else {
+                this.$menu.css({
+                    'maxHeight': '',
+                    'overflow': ''
+                });
+            }
+        },
+
+        menuHide: function () {
+            this.$container.removeClass(this.settings['activeClass']);
+            this.$container.removeClass(this.settings['positionedMenuClass']);
+            this.$menu.css('width', 'auto');
+        },
+
+        menuToggle: function () {
+            if (this.$container.hasClass(this.settings['activeClass'])) {
+                this.menuHide();
+            } else {
+                this.menuShow();
+            }
+        }
+
+    });
+
+    $.fn[pluginName] = function (options) {
+        return this.each(function () {
+            if (!$.data(this, "plugin_" + pluginName)) {
+                $.data(this, "plugin_" + pluginName,
+                    new MultiSelect(this, options));
+            }
+        });
+    };
+
+})(jQuery);