Bladeren bron

rewrite classes in a more readable way. closes #20

Igor Vaynberg 13 jaren geleden
bovenliggende
commit
8323051ce3
1 gewijzigde bestanden met toevoegingen van 861 en 856 verwijderingen
  1. 861 856
      select2.js

+ 861 - 856
select2.js

@@ -24,7 +24,9 @@
         return;
     }
 
-    var KEY = {
+    var KEY, AbstractSelect2, SingleSelect2, MultiSelect2;
+
+    KEY = {
         TAB: 9,
         ENTER: 13,
         ESC: 27,
@@ -298,1063 +300,1066 @@
         });
     });
 
+
     /**
+     * Creates a new class
      *
-     * @param opts
+     * @param superClass
+     * @param methods
      */
-    function AbstractSelect2() {
+    function clazz(superClass, methods) {
+        var clazz = function () {};
+        clazz.prototype = new superClass;
+        clazz.prototype.constructor = clazz;
+        clazz.prototype.parent = superClass.prototype;
+        clazz.prototype = $.extend(clazz.prototype, methods);
+        return clazz;
     }
 
-    AbstractSelect2.prototype.bind = function (func) {
-        var self = this;
-        return function () {
-            func.apply(self, arguments);
-        };
-    };
-
-    AbstractSelect2.prototype.init = function (opts) {
-        var results, search, resultsSelector = ".select2-results";
-
-        // prepare options
-        this.opts = this.prepareOpts(opts);
+    AbstractSelect2 = clazz(Object, {
 
-        // destroy if called on an existing component
-        if (opts.element.data("select2") !== undefined) {
-            this.destroy();
-        }
+        bind: function (func) {
+            var self = this;
+            return function () {
+                func.apply(self, arguments);
+            };
+        },
 
-        this.container = this.createContainer();
+        init: function (opts) {
+            var results, search, resultsSelector = ".select2-results";
 
-        if (opts.element.attr("class") !== undefined) {
-            this.container.addClass(opts.element.attr("class"));
-        }
+            // prepare options
+            this.opts = this.prepareOpts(opts);
 
-        // swap container for the element
-        this.opts.element
-            .data("select2", this)
-            .hide()
-            .after(this.container);
-        this.container.data("select2", this);
-
-        this.dropdown = this.container.find(".select2-drop");
-        this.results = results = this.container.find(resultsSelector);
-        this.search = search = this.container.find("input[type=text]");
+            // destroy if called on an existing component
+            if (opts.element.data("select2") !== undefined) {
+                this.destroy();
+            }
 
-        this.resultsPage = 0;
+            this.container = this.createContainer();
 
-        // initialize the container
-        this.initContainer();
+            if (opts.element.attr("class") !== undefined) {
+                this.container.addClass(opts.element.attr("class"));
+            }
 
-        installFilteredMouseMove(this.results);
-        this.container.delegate(resultsSelector, "mousemove-filtered", this.bind(this.highlightUnderEvent));
+            // swap container for the element
+            this.opts.element
+                .data("select2", this)
+                .hide()
+                .after(this.container);
+            this.container.data("select2", this);
+
+            this.dropdown = this.container.find(".select2-drop");
+            this.results = results = this.container.find(resultsSelector);
+            this.search = search = this.container.find("input[type=text]");
+
+            this.resultsPage = 0;
+
+            // initialize the container
+            this.initContainer();
+
+            installFilteredMouseMove(this.results);
+            this.container.delegate(resultsSelector, "mousemove-filtered", this.bind(this.highlightUnderEvent));
+
+            installDebouncedScroll(80, this.results);
+            this.container.delegate(resultsSelector, "scroll-debounced", this.bind(this.loadMoreIfNeeded));
+
+            // if jquery.mousewheel plugin is installed we can prevent out-of-bounds scrolling of results via mousewheel
+            if ($.fn.mousewheel) {
+                results.mousewheel(function (e, delta, deltaX, deltaY) {
+                    var top = results.scrollTop(), height;
+                    if (deltaY > 0 && top - deltaY <= 0) {
+                        results.scrollTop(0);
+                        killEvent(e);
+                    } else if (deltaY < 0 && results.get(0).scrollHeight - results.scrollTop() + deltaY <= results.height()) {
+                        results.scrollTop(results.get(0).scrollHeight - results.height());
+                        killEvent(e);
+                    }
+                });
+            }
 
-        installDebouncedScroll(80, this.results);
-        this.container.delegate(resultsSelector, "scroll-debounced", this.bind(this.loadMoreIfNeeded));
+            installKeyUpChangeEvent(search);
+            search.bind("keyup-change", this.bind(this.updateResults));
 
-        // if jquery.mousewheel plugin is installed we can prevent out-of-bounds scrolling of results via mousewheel
-        if ($.fn.mousewheel) {
-            results.mousewheel(function (e, delta, deltaX, deltaY) {
-                var top = results.scrollTop(), height;
-                if (deltaY > 0 && top - deltaY <= 0) {
-                    results.scrollTop(0);
-                    killEvent(e);
-                } else if (deltaY < 0 && results.get(0).scrollHeight - results.scrollTop() + deltaY <= results.height()) {
-                    results.scrollTop(results.get(0).scrollHeight - results.height());
+            this.container.delegate(resultsSelector, "click", this.bind(function (e) {
+                if ($(e.target).closest(".select2-result:not(.select2-disabled)").length > 0) {
+                    this.highlightUnderEvent(e);
+                    this.selectHighlighted(e);
+                } else {
                     killEvent(e);
+                    this.focusSearch();
                 }
-            });
-        }
+            }));
 
-        installKeyUpChangeEvent(search);
-        search.bind("keyup-change", this.bind(this.updateResults));
+            if ($.isFunction(this.opts.initSelection)) {
+                // initialize selection based on the current value of the source element
+                this.initSelection();
 
-        this.container.delegate(resultsSelector, "click", this.bind(function (e) {
-            if ($(e.target).closest(".select2-result:not(.select2-disabled)").length > 0) {
-                this.highlightUnderEvent(e);
-                this.selectHighlighted(e);
-            } else {
-                killEvent(e);
-                this.focusSearch();
+                // if the user has provided a function that can set selection based on the value of the source element
+                // we monitor the change event on the element and trigger it, allowing for two way synchronization
+                this.monitorSource();
             }
-        }));
-
-        if ($.isFunction(this.opts.initSelection)) {
-            // initialize selection based on the current value of the source element
-            this.initSelection();
-
-            // if the user has provided a function that can set selection based on the value of the source element
-            // we monitor the change event on the element and trigger it, allowing for two way synchronization
-            this.monitorSource();
-        }
-    };
+        },
 
-    AbstractSelect2.prototype.destroy = function () {
-        var select2 = this.opts.element.data("select2");
-        if (select2 !== undefined) {
-            select2.container.remove();
-            select2.opts.element
-                .removeData("select2")
-                .show();
-        }
-    };
+        destroy: function () {
+            var select2 = this.opts.element.data("select2");
+            if (select2 !== undefined) {
+                select2.container.remove();
+                select2.opts.element
+                    .removeData("select2")
+                    .show();
+            }
+        },
 
-    AbstractSelect2.prototype.prepareOpts = function (opts) {
-        var element, select;
+        prepareOpts: function (opts) {
+            var element, select;
 
-        opts = $.extend({}, {
-            formatResult: function (data) { return data.text; },
-            formatSelection: function (data) { return data.text; },
-            formatNoMatches: function () { return "No matches found"; },
-            formatInputTooShort: function (input, min) { return "Please enter " + (min - input.length) + " more characters"; },
-            minimumResultsForSearch: 0
-        }, opts);
+            opts = $.extend({}, {
+                formatResult: function (data) { return data.text; },
+                formatSelection: function (data) { return data.text; },
+                formatNoMatches: function () { return "No matches found"; },
+                formatInputTooShort: function (input, min) { return "Please enter " + (min - input.length) + " more characters"; },
+                minimumResultsForSearch: 0
+            }, opts);
 
-        element = opts.element;
+            element = opts.element;
 
-        if (element.get(0).tagName.toLowerCase() === "select") {
-            this.select = select = opts.element;
-        }
+            if (element.get(0).tagName.toLowerCase() === "select") {
+                this.select = select = opts.element;
+            }
 
-        // TODO add missing validation logic
-        if (select) {
-            /*$.each(["multiple", "ajax", "query", "minimumInputLength"], function () {
-             if (this in opts) {
-             throw "Option '" + this + "' is not allowed for Select2 when attached to a select element";
-             }
-             });*/
-            this.opts = opts = $.extend({}, {
-                miniumInputLength: 0
-            }, opts);
-        } else {
-            this.opts = opts = $.extend({}, {
-                miniumInputLength: 0
-            }, opts);
-        }
+            // TODO add missing validation logic
+            if (select) {
+                /*$.each(["multiple", "ajax", "query", "minimumInputLength"], function () {
+                 if (this in opts) {
+                 throw "Option '" + this + "' is not allowed for Select2 when attached to a select element";
+                 }
+                 });*/
+                this.opts = opts = $.extend({}, {
+                    miniumInputLength: 0
+                }, opts);
+            } else {
+                this.opts = opts = $.extend({}, {
+                    miniumInputLength: 0
+                }, opts);
+            }
 
-        if (select) {
-            opts.query = this.bind(function (query) {
-                var data = {results: [], more: false},
-                    term = query.term.toUpperCase(),
-                    placeholder = this.getPlaceholder();
-                element.find("option").each(function (i) {
-                    var e = $(this),
-                        text = e.text();
+            if (select) {
+                opts.query = this.bind(function (query) {
+                    var data = {results: [], more: false},
+                        term = query.term.toUpperCase(),
+                        placeholder = this.getPlaceholder();
+                    element.find("option").each(function (i) {
+                        var e = $(this),
+                            text = e.text();
 
-                    if (i === 0 && placeholder !== undefined && text === "") return true;
+                        if (i === 0 && placeholder !== undefined && text === "") return true;
 
-                    if (text.toUpperCase().indexOf(term) >= 0) {
-                        data.results.push({id: e.attr("value"), text: text});
-                    }
+                        if (text.toUpperCase().indexOf(term) >= 0) {
+                            data.results.push({id: e.attr("value"), text: text});
+                        }
+                    });
+                    query.callback(data);
                 });
-                query.callback(data);
-            });
-        } else {
-            if (!("query" in opts)) {
-                if ("ajax" in opts) {
-                    opts.query = ajax(opts.ajax);
-                } else if ("data" in opts) {
-                    opts.query = local(opts.data);
-                } else if ("tags" in opts) {
-                    opts.query = tags(opts.tags);
-                    opts.createSearchChoice = function (term) { return {id: term, text: term};}
-                    opts.initSelection = function (element) {
-                        var data = [];
-                        $(element.val().split(",")).each(function () {
-                            data.push({id: this, text: this});
-                        });
-                        return data;
+            } else {
+                if (!("query" in opts)) {
+                    if ("ajax" in opts) {
+                        opts.query = ajax(opts.ajax);
+                    } else if ("data" in opts) {
+                        opts.query = local(opts.data);
+                    } else if ("tags" in opts) {
+                        opts.query = tags(opts.tags);
+                        opts.createSearchChoice = function (term) { return {id: term, text: term};}
+                        opts.initSelection = function (element) {
+                            var data = [];
+                            $(element.val().split(",")).each(function () {
+                                data.push({id: this, text: this});
+                            });
+                            return data;
+                        }
                     }
                 }
             }
-        }
-        if (typeof(opts.query) !== "function") {
-            throw "query function not defined for Select2 " + opts.element.attr("id");
-        }
-
-        return opts;
-    };
-
-    /**
-     * Monitor the original element for changes and update select2 accordingly
-     */
-    AbstractSelect2.prototype.monitorSource = function () {
-        this.opts.element.bind("change", this.bind(function (e) {
-            if (this.opts.element.data("select2-change-triggered") !== true) {
-                this.initSelection();
+            if (typeof(opts.query) !== "function") {
+                throw "query function not defined for Select2 " + opts.element.attr("id");
             }
-        }));
-    };
-
-    /**
-     * Triggers the change event on the source element
-     */
-    AbstractSelect2.prototype.triggerChange = function () {
-        // Prevents recursive triggering
-        this.opts.element.data("select2-change-triggered", true);
-        this.opts.element.trigger("change");
-        this.opts.element.data("select2-change-triggered", false);
-    };
 
-    AbstractSelect2.prototype.opened = function () {
-        return this.container.hasClass("select2-dropdown-open");
-    };
+            return opts;
+        },
 
-    AbstractSelect2.prototype.alignDropdown = function () {
-        this.dropdown.css({
-            top: this.container.height()
-        });
-    };
+        /**
+         * Monitor the original element for changes and update select2 accordingly
+         */
+        monitorSource: function () {
+            this.opts.element.bind("change", this.bind(function (e) {
+                if (this.opts.element.data("select2-change-triggered") !== true) {
+                    this.initSelection();
+                }
+            }));
+        },
 
-    AbstractSelect2.prototype.open = function () {
-        if (this.opened()) return;
+        /**
+         * Triggers the change event on the source element
+         */
+        triggerChange: function () {
+            // Prevents recursive triggering
+            this.opts.element.data("select2-change-triggered", true);
+            this.opts.element.trigger("change");
+            this.opts.element.data("select2-change-triggered", false);
+        },
 
-        this.container.addClass("select2-dropdown-open").addClass("select2-container-active");
+        opened: function () {
+            return this.container.hasClass("select2-dropdown-open");
+        },
 
-        this.updateResults(true);
-        this.alignDropdown();
-        this.dropdown.show();
-        this.focusSearch();
-    };
+        alignDropdown: function () {
+            this.dropdown.css({
+                top: this.container.height()
+            });
+        },
 
-    AbstractSelect2.prototype.close = function () {
-        if (!this.opened()) return;
+        open: function () {
+            if (this.opened()) return;
 
-        this.dropdown.hide();
-        this.container.removeClass("select2-dropdown-open");
-        this.results.empty();
-        this.clearSearch();
-    };
+            this.container.addClass("select2-dropdown-open").addClass("select2-container-active");
 
-    AbstractSelect2.prototype.clearSearch = function () {
+            this.updateResults(true);
+            this.alignDropdown();
+            this.dropdown.show();
+            this.focusSearch();
+        },
 
-    };
+        close: function () {
+            if (!this.opened()) return;
 
-    AbstractSelect2.prototype.ensureHighlightVisible = function () {
-        var results = this.results, children, index, child, hb, rb, y, more;
+            this.dropdown.hide();
+            this.container.removeClass("select2-dropdown-open");
+            this.results.empty();
+            this.clearSearch();
+        },
 
-        children = results.children(".select2-result");
-        index = this.highlight();
+        clearSearch: function () {
 
-        if (index < 0) return;
+        },
 
-        child = $(children[index]);
+        ensureHighlightVisible: function () {
+            var results = this.results, children, index, child, hb, rb, y, more;
 
-        hb = child.offset().top + child.outerHeight();
+            children = results.children(".select2-result");
+            index = this.highlight();
 
-        // if this is the last child lets also make sure select2-more-results is visible
-        if (index === children.length - 1) {
-            more = results.find("li.select2-more-results");
-            if (more.length > 0) {
-                hb = more.offset().top + more.outerHeight();
-            }
-        }
+            if (index < 0) return;
 
-        rb = results.offset().top + results.outerHeight();
-        if (hb > rb) {
-            results.scrollTop(results.scrollTop() + (hb - rb));
-        }
-        y = child.offset().top - results.offset().top;
+            child = $(children[index]);
 
-        // make sure the top of the element is visible
-        if (y < 0) {
-            results.scrollTop(results.scrollTop() + y); // y is negative
-        }
-    };
+            hb = child.offset().top + child.outerHeight();
 
-    AbstractSelect2.prototype.moveHighlight = function (delta) {
-        var choices = this.results.children(".select2-result"),
-            index = this.highlight();
+            // if this is the last child lets also make sure select2-more-results is visible
+            if (index === children.length - 1) {
+                more = results.find("li.select2-more-results");
+                if (more.length > 0) {
+                    hb = more.offset().top + more.outerHeight();
+                }
+            }
 
-        while (index > -1 && index < choices.length) {
-            index += delta;
-            if (!$(choices[index]).hasClass("select2-disabled")) {
-                this.highlight(index);
-                break;
+            rb = results.offset().top + results.outerHeight();
+            if (hb > rb) {
+                results.scrollTop(results.scrollTop() + (hb - rb));
             }
-        }
-    };
+            y = child.offset().top - results.offset().top;
 
-    AbstractSelect2.prototype.highlight = function (index) {
-        var choices = this.results.children(".select2-result");
+            // make sure the top of the element is visible
+            if (y < 0) {
+                results.scrollTop(results.scrollTop() + y); // y is negative
+            }
+        },
 
-        if (arguments.length === 0) {
-            return indexOf(choices.filter(".select2-highlighted")[0], choices.get());
-        }
+        moveHighlight: function (delta) {
+            var choices = this.results.children(".select2-result"),
+                index = this.highlight();
 
-        choices.removeClass("select2-highlighted");
+            while (index > -1 && index < choices.length) {
+                index += delta;
+                if (!$(choices[index]).hasClass("select2-disabled")) {
+                    this.highlight(index);
+                    break;
+                }
+            }
+        },
 
-        if (index >= choices.length) index = choices.length - 1;
-        if (index < 0) index = 0;
+        highlight: function (index) {
+            var choices = this.results.children(".select2-result");
 
-        $(choices[index]).addClass("select2-highlighted");
-        this.ensureHighlightVisible();
+            if (arguments.length === 0) {
+                return indexOf(choices.filter(".select2-highlighted")[0], choices.get());
+            }
 
-        if (this.opened()) this.focusSearch();
-    };
+            choices.removeClass("select2-highlighted");
 
-    AbstractSelect2.prototype.highlightUnderEvent = function (event) {
-        var el = $(event.target).closest(".select2-result");
-        if (el.length > 0) {
-            this.highlight(el.index());
-        }
-    };
+            if (index >= choices.length) index = choices.length - 1;
+            if (index < 0) index = 0;
 
-    AbstractSelect2.prototype.loadMoreIfNeeded = function () {
-        var results = this.results,
-            more = results.find("li.select2-more-results"),
-            below, // pixels the element is below the scroll fold, below==0 is when the element is starting to be visible
-            offset = -1, // index of first element without data
-            page = this.resultsPage + 1;
+            $(choices[index]).addClass("select2-highlighted");
+            this.ensureHighlightVisible();
 
-        if (more.length === 0) return;
+            if (this.opened()) this.focusSearch();
+        },
 
-        below = more.offset().top - results.offset().top - results.height();
+        highlightUnderEvent: function (event) {
+            var el = $(event.target).closest(".select2-result");
+            if (el.length > 0) {
+                this.highlight(el.index());
+            }
+        },
 
-        if (below <= 0) {
-            more.addClass("select2-active");
-            this.opts.query({term: this.search.val(), page: page, callback: this.bind(function (data) {
-                var parts = [], self = this;
-                $(data.results).each(function () {
-                    parts.push("<li class='select2-result'>");
-                    parts.push(self.opts.formatResult(this));
-                    parts.push("</li>");
-                });
-                more.before(parts.join(""));
-                results.find(".select2-result").each(function (i) {
-                    var e = $(this);
-                    if (e.data("select2-data") !== undefined) {
-                        offset = i;
+        loadMoreIfNeeded: function () {
+            var results = this.results,
+                more = results.find("li.select2-more-results"),
+                below, // pixels the element is below the scroll fold, below==0 is when the element is starting to be visible
+                offset = -1, // index of first element without data
+                page = this.resultsPage + 1;
+
+            if (more.length === 0) return;
+
+            below = more.offset().top - results.offset().top - results.height();
+
+            if (below <= 0) {
+                more.addClass("select2-active");
+                this.opts.query({term: this.search.val(), page: page, callback: this.bind(function (data) {
+                    var parts = [], self = this;
+                    $(data.results).each(function () {
+                        parts.push("<li class='select2-result'>");
+                        parts.push(self.opts.formatResult(this));
+                        parts.push("</li>");
+                    });
+                    more.before(parts.join(""));
+                    results.find(".select2-result").each(function (i) {
+                        var e = $(this);
+                        if (e.data("select2-data") !== undefined) {
+                            offset = i;
+                        } else {
+                            e.data("select2-data", data.results[i - offset - 1]);
+                        }
+                    });
+                    if (data.more) {
+                        more.removeClass("select2-active");
                     } else {
-                        e.data("select2-data", data.results[i - offset - 1]);
+                        more.remove();
                     }
-                });
-                if (data.more) {
-                    more.removeClass("select2-active");
-                } else {
-                    more.remove();
-                }
-                this.resultsPage = page;
-            })});
-        }
-    };
-
-    /**
-     * @param initial whether or not this is the call to this method right after the dropdown has been opened
-     */
-    AbstractSelect2.prototype.updateResults = function (initial) {
-        var search = this.search, results = this.results, opts = this.opts;
+                    this.resultsPage = page;
+                })});
+            }
+        },
 
-        search.addClass("select2-active");
+        /**
+         * @param initial whether or not this is the call to this method right after the dropdown has been opened
+         */
+        updateResults: function (initial) {
+            var search = this.search, results = this.results, opts = this.opts;
 
-        function render(html) {
-            results.html(html);
-            results.scrollTop(0);
-            search.removeClass("select2-active");
-        }
+            search.addClass("select2-active");
 
-        if (search.val().length < opts.minimumInputLength) {
-            render("<li class='select2-no-results'>" + opts.formatInputTooShort(search.val(), opts.minimumInputLength) + "</li>");
-            return;
-        }
-
-        this.resultsPage = 1;
-        opts.query({term: search.val(), page: this.resultsPage, callback: this.bind(function (data) {
-            var parts = [], // html parts
-                def; // default choice
-
-            // create a default choice and prepend it to the list
-            if (this.opts.createSearchChoice && search.val() !== "") {
-                def = this.opts.createSearchChoice.call(null, search.val(), data.results);
-                if (def !== undefined && def !== null && def.id !== undefined && def.id != null) {
-                    if ($(data.results).filter(
-                        function () {
-                            return equal(this.id, def.id);
-                        }).length === 0) {
-                        data.results.unshift(def);
-                    }
-                }
+            function render(html) {
+                results.html(html);
+                results.scrollTop(0);
+                search.removeClass("select2-active");
             }
 
-            if (data.results.length === 0) {
-                render("<li class='select2-no-results'>" + opts.formatNoMatches(search.val()) + "</li>");
+            if (search.val().length < opts.minimumInputLength) {
+                render("<li class='select2-no-results'>" + opts.formatInputTooShort(search.val(), opts.minimumInputLength) + "</li>");
                 return;
             }
 
-            $(data.results).each(function () {
-                parts.push("<li class='select2-result'>");
-                parts.push(opts.formatResult(this));
-                parts.push("</li>");
-            });
+            this.resultsPage = 1;
+            opts.query({term: search.val(), page: this.resultsPage, callback: this.bind(function (data) {
+                var parts = [], // html parts
+                    def; // default choice
+
+                // create a default choice and prepend it to the list
+                if (this.opts.createSearchChoice && search.val() !== "") {
+                    def = this.opts.createSearchChoice.call(null, search.val(), data.results);
+                    if (def !== undefined && def !== null && def.id !== undefined && def.id != null) {
+                        if ($(data.results).filter(
+                            function () {
+                                return equal(this.id, def.id);
+                            }).length === 0) {
+                            data.results.unshift(def);
+                        }
+                    }
+                }
 
-            if (data.more === true) {
-                parts.push("<li class='select2-more-results'>Loading more results...</li>");
-            }
+                if (data.results.length === 0) {
+                    render("<li class='select2-no-results'>" + opts.formatNoMatches(search.val()) + "</li>");
+                    return;
+                }
 
-            render(parts.join(""));
-            results.children(".select2-result").each(function (i) {
-                var d = data.results[i];
-                $(this).data("select2-data", d);
-            });
-            this.postprocessResults(data, initial);
-        })});
-    };
+                $(data.results).each(function () {
+                    parts.push("<li class='select2-result'>");
+                    parts.push(opts.formatResult(this));
+                    parts.push("</li>");
+                });
 
-    AbstractSelect2.prototype.cancel = function () {
-        this.close();
-    };
+                if (data.more === true) {
+                    parts.push("<li class='select2-more-results'>Loading more results...</li>");
+                }
+
+                render(parts.join(""));
+                results.children(".select2-result").each(function (i) {
+                    var d = data.results[i];
+                    $(this).data("select2-data", d);
+                });
+                this.postprocessResults(data, initial);
+            })});
+        },
 
-    AbstractSelect2.prototype.blur = function () {
-        /* we do this in a timeout so that current event processing can complete before this code is executed.
-         this allows tab index to be preserved even if this code blurs the textfield */
-        window.setTimeout(this.bind(function () {
+        cancel: function () {
             this.close();
-            this.container.removeClass("select2-container-active");
-            this.clearSearch();
-            this.selection.find(".select2-search-choice-focus").removeClass("select2-search-choice-focus");
-            this.search.blur();
-        }), 10);
-    };
+        },
 
-    AbstractSelect2.prototype.focusSearch = function () {
-        /* we do this in a timeout so that current event processing can complete before this code is executed.
-         this makes sure the search field is focussed even if the current event would blur it */
-        window.setTimeout(this.bind(function () {
-            this.search.focus();
-        }), 10);
-    };
+        blur: function () {
+            /* we do this in a timeout so that current event processing can complete before this code is executed.
+             this allows tab index to be preserved even if this code blurs the textfield */
+            window.setTimeout(this.bind(function () {
+                this.close();
+                this.container.removeClass("select2-container-active");
+                this.clearSearch();
+                this.selection.find(".select2-search-choice-focus").removeClass("select2-search-choice-focus");
+                this.search.blur();
+            }), 10);
+        },
 
-    AbstractSelect2.prototype.selectHighlighted = function () {
-        var data = this.results.find(".select2-highlighted:not(.select2-disabled)").data("select2-data");
-        if (data) {
-            this.onSelect(data);
-        }
-    };
+        focusSearch: function () {
+            /* we do this in a timeout so that current event processing can complete before this code is executed.
+             this makes sure the search field is focussed even if the current event would blur it */
+            window.setTimeout(this.bind(function () {
+                this.search.focus();
+            }), 10);
+        },
 
-    AbstractSelect2.prototype.getPlaceholder = function () {
-        var placeholder = this.opts.element.data("placeholder");
-        if (placeholder !== undefined) return placeholder;
-        return this.opts.placeholder;
-    };
+        selectHighlighted: function () {
+            var data = this.results.find(".select2-highlighted:not(.select2-disabled)").data("select2-data");
+            if (data) {
+                this.onSelect(data);
+            }
+        },
 
-    /**
-     * Get the desired width for the container element.  This is
-     * derived first from option `width` passed to select2, then
-     * the inline 'style' on the original element, and finally
-     * falls back to the jQuery calculated element width.
-     *
-     * @returns The width string (with units) for the container.
-     */
-    AbstractSelect2.prototype.getContainerWidth = function () {
-        if (this.opts.width !== undefined)
-            return this.opts.width;
-
-        var style = this.opts.element.attr('style');
-        if (style !== undefined) {
-            var attrs = style.split(';');
-            for (var i = 0; i < attrs.length; i++) {
-                var matches = attrs[i].replace(/\s/g, '')
-                    .match(/width:(([-+]?([0-9]*\.)?[0-9]+)(px|em|ex|%|in|cm|mm|pt|pc))/);
-                if (matches != null && matches.length >= 1)
-                    return matches[1];
+        getPlaceholder: function () {
+            var placeholder = this.opts.element.data("placeholder");
+            if (placeholder !== undefined) return placeholder;
+            return this.opts.placeholder;
+        },
+
+        /**
+         * Get the desired width for the container element.  This is
+         * derived first from option `width` passed to select2, then
+         * the inline 'style' on the original element, and finally
+         * falls back to the jQuery calculated element width.
+         *
+         * @returns The width string (with units) for the container.
+         */
+        getContainerWidth: function () {
+            if (this.opts.width !== undefined)
+                return this.opts.width;
+
+            var style = this.opts.element.attr('style');
+            if (style !== undefined) {
+                var attrs = style.split(';');
+                for (var i = 0; i < attrs.length; i++) {
+                    var matches = attrs[i].replace(/\s/g, '')
+                        .match(/width:(([-+]?([0-9]*\.)?[0-9]+)(px|em|ex|%|in|cm|mm|pt|pc))/);
+                    if (matches != null && matches.length >= 1)
+                        return matches[1];
+                }
             }
+            return this.opts.element.width() + 'px';
         }
-        return this.opts.element.width() + 'px';
-    };
+    });
 
-    function SingleSelect2() {
-    }
+    SingleSelect2 = clazz(AbstractSelect2, {
+
+        createContainer: function () {
+            return $("<div></div>", {
+                "class": "select2-container",
+                "style": "width: " + this.getContainerWidth()
+            }).html([
+                "    <a href='javascript:void(0)' class='select2-choice'>",
+                "   <span></span><abbr class='select2-search-choice-close' style='display:none;'></abbr>",
+                "   <div><b></b></div>" ,
+                "</a>",
+                "    <div class='select2-drop' style='display:none;'>" ,
+                "   <div class='select2-search'>" ,
+                "       <input type='text' autocomplete='off'/>" ,
+                "   </div>" ,
+                "   <ul class='select2-results'>" ,
+                "   </ul>" ,
+                "</div>"].join(""));
+        },
 
-    SingleSelect2.prototype = new AbstractSelect2();
-    SingleSelect2.prototype.constructor = SingleSelect2;
-    SingleSelect2.prototype.parent = AbstractSelect2.prototype;
-
-    SingleSelect2.prototype.createContainer = function () {
-        return $("<div></div>", {
-            "class": "select2-container",
-            "style": "width: " + this.getContainerWidth()
-        }).html([
-            "    <a href='javascript:void(0)' class='select2-choice'>",
-            "   <span></span><abbr class='select2-search-choice-close' style='display:none;'></abbr>",
-            "   <div><b></b></div>" ,
-            "</a>",
-            "    <div class='select2-drop' style='display:none;'>" ,
-            "   <div class='select2-search'>" ,
-            "       <input type='text' autocomplete='off'/>" ,
-            "   </div>" ,
-            "   <ul class='select2-results'>" ,
-            "   </ul>" ,
-            "</div>"].join(""));
-    };
+        open: function () {
+
+            if (this.opened()) return;
 
-    SingleSelect2.prototype.open = function () {
+            this.parent.open.apply(this, arguments);
 
-        if (this.opened()) return;
+        },
 
-        this.parent.open.apply(this, arguments);
+        close: function () {
+            if (!this.opened()) return;
+            this.parent.close.apply(this, arguments);
+        },
 
-    };
+        cancel: function () {
+            this.parent.cancel.apply(this, arguments);
+            this.selection.focus();
+        },
 
-    SingleSelect2.prototype.close = function () {
-        if (!this.opened()) return;
-        this.parent.close.apply(this, arguments);
-    };
+        initContainer: function () {
 
-    SingleSelect2.prototype.cancel = function () {
-        this.parent.cancel.apply(this, arguments);
-        this.selection.focus();
-    };
+            var selection, container = this.container, clickingInside = false,
+                selector = ".select2-choice", selected;
+
+            this.selection = selection = container.find(selector);
 
-    SingleSelect2.prototype.initContainer = function () {
+            this.search.bind("keydown", this.bind(function (e) {
+                switch (e.which) {
+                case KEY.UP:
+                case KEY.DOWN:
+                    this.moveHighlight((e.which === KEY.UP) ? -1 : 1);
+                    killEvent(e);
+                    return;
+                case KEY.TAB:
+                case KEY.ENTER:
+                    this.selectHighlighted();
+                    killEvent(e);
+                    return;
+                case KEY.ESC:
+                    this.cancel(e);
+                    e.preventDefault();
+                    return;
+                }
+            }));
 
-        var selection, container = this.container, clickingInside = false,
-            selector = ".select2-choice", selected;
+            container.delegate(selector, "click", this.bind(function (e) {
+                clickingInside = true;
 
-        this.selection = selection = container.find(selector);
+                if (this.opened()) {
+                    this.close();
+                    selection.focus();
+                } else {
+                    this.open();
+                }
+                e.preventDefault();
 
-        this.search.bind("keydown", this.bind(function (e) {
-            switch (e.which) {
-            case KEY.UP:
-            case KEY.DOWN:
-                this.moveHighlight((e.which === KEY.UP) ? -1 : 1);
-                killEvent(e);
-                return;
-            case KEY.TAB:
-            case KEY.ENTER:
-                this.selectHighlighted();
+                clickingInside = false;
+            }));
+            container.delegate(selector, "keydown", this.bind(function (e) {
+                if (e.which === KEY.TAB || KEY.isControl(e) || KEY.isFunctionKey(e) || e.which === KEY.ESC) {
+                    return;
+                }
+                this.open();
+                if (e.which === KEY.PAGE_UP || e.which === KEY.PAGE_DOWN || e.which === KEY.SPACE) {
+                    // prevent the page from scrolling
+                    killEvent(e);
+                }
+                if (e.which === KEY.ENTER) {
+                    // do not propagate the event otherwise we open, and propagate enter which closes
+                    killEvent(e);
+                }
+            }));
+            container.delegate(selector, "focus", function () { container.addClass("select2-container-active"); });
+            container.delegate(selector, "blur", this.bind(function () {
+                if (clickingInside) return;
+                if (!this.opened()) this.blur();
+            }));
+
+            selection.delegate("abbr", "click", this.bind(function (e) {
+                this.val("");
                 killEvent(e);
-                return;
-            case KEY.ESC:
-                this.cancel(e);
-                e.preventDefault();
-                return;
-            }
-        }));
+                this.close();
+                this.triggerChange();
+            }));
 
-        container.delegate(selector, "click", this.bind(function (e) {
-            clickingInside = true;
+            this.setPlaceholder();
+        },
 
-            if (this.opened()) {
-                this.close();
-                selection.focus();
+        /**
+         * Sets selection based on source element's value
+         */
+        initSelection: function () {
+            var selected;
+            if (this.opts.element.val() === "") {
+                this.updateSelection({id: "", text: ""});
             } else {
-                this.open();
+                selected = this.opts.initSelection.call(null, this.opts.element);
+                if (selected !== undefined && selected !== null) {
+                    this.updateSelection(selected);
+                }
             }
-            e.preventDefault();
 
-            clickingInside = false;
-        }));
-        container.delegate(selector, "keydown", this.bind(function (e) {
-            if (e.which === KEY.TAB || KEY.isControl(e) || KEY.isFunctionKey(e) || e.which === KEY.ESC) {
-                return;
-            }
-            this.open();
-            if (e.which === KEY.PAGE_UP || e.which === KEY.PAGE_DOWN || e.which === KEY.SPACE) {
-                // prevent the page from scrolling
-                killEvent(e);
-            }
-            if (e.which === KEY.ENTER) {
-                // do not propagate the event otherwise we open, and propagate enter which closes
-                killEvent(e);
-            }
-        }));
-        container.delegate(selector, "focus", function () { container.addClass("select2-container-active"); });
-        container.delegate(selector, "blur", this.bind(function () {
-            if (clickingInside) return;
-            if (!this.opened()) this.blur();
-        }));
-
-        selection.delegate("abbr", "click", this.bind(function (e) {
-            this.val("");
-            killEvent(e);
             this.close();
-            this.triggerChange();
-        }));
+            this.setPlaceholder();
+        },
 
-        this.setPlaceholder();
-    };
+        prepareOpts: function () {
+            var opts = this.parent.prepareOpts.apply(this, arguments);
 
-    /**
-     * Sets selection based on source element's value
-     */
-    SingleSelect2.prototype.initSelection = function () {
-        var selected;
-        if (this.opts.element.val() === "") {
-            this.updateSelection({id: "", text: ""});
-        } else {
-            selected = this.opts.initSelection.call(null, this.opts.element);
-            if (selected !== undefined && selected !== null) {
-                this.updateSelection(selected);
+            if (opts.element.get(0).tagName.toLowerCase() === "select") {
+                // install sthe selection initializer
+                this.opts.initSelection = function (element) {
+                    var selected = element.find(":selected");
+                    // a single select box always has a value, no need to null check 'selected'
+                    return {id: selected.attr("value"), text: selected.text()};
+                };
             }
-        }
-
-        this.close();
-        this.setPlaceholder();
-    };
-
-    SingleSelect2.prototype.prepareOpts = function () {
-        var opts = this.parent.prepareOpts.apply(this, arguments);
 
-        if (opts.element.get(0).tagName.toLowerCase() === "select") {
-            // install sthe selection initializer
-            this.opts.initSelection = function (element) {
-                var selected = element.find(":selected");
-                // a single select box always has a value, no need to null check 'selected'
-                return {id: selected.attr("value"), text: selected.text()};
-            };
-        }
+            return opts;
+        },
 
-        return opts;
-    };
+        setPlaceholder: function () {
+            var placeholder = this.getPlaceholder();
 
-    SingleSelect2.prototype.setPlaceholder = function () {
-        var placeholder = this.getPlaceholder();
+            if (this.opts.element.val() === "" && placeholder !== undefined) {
 
-        if (this.opts.element.val() === "" && placeholder !== undefined) {
+                // check for a first blank option if attached to a select
+                if (this.select && this.select.find("option:first").text() !== "") return;
 
-            // check for a first blank option if attached to a select
-            if (this.select && this.select.find("option:first").text() !== "") return;
+                if (typeof(placeholder) === "object") {
+                    this.updateSelection(placeholder);
+                } else {
+                    this.selection.find("span").html(placeholder);
+                }
+                this.selection.addClass("select2-default");
 
-            if (typeof(placeholder) === "object") {
-                this.updateSelection(placeholder);
-            } else {
-                this.selection.find("span").html(placeholder);
+                this.selection.find("abbr").hide();
             }
-            this.selection.addClass("select2-default");
-
-            this.selection.find("abbr").hide();
-        }
-    };
-
-    SingleSelect2.prototype.postprocessResults = function (data, initial) {
-        var selected = 0, self = this;
-
-        // find the selected element in the result list
+        },
 
-        this.results.find(".select2-result").each(function (i) {
-            if (equal($(this).data("select2-data").id, self.opts.element.val())) {
-                selected = i;
-                return false;
-            }
-        });
+        postprocessResults: function (data, initial) {
+            var selected = 0, self = this;
 
-        // and highlight it
+            // find the selected element in the result list
 
-        this.highlight(selected);
+            this.results.find(".select2-result").each(function (i) {
+                if (equal($(this).data("select2-data").id, self.opts.element.val())) {
+                    selected = i;
+                    return false;
+                }
+            });
 
-        // hide the search box if this is the first we got the results and there are a few of them
+            // and highlight it
 
-        if (initial === true) {
-            this.search.parent().toggle(data.results.length >= this.opts.minimumResultsForSearch);
-        }
+            this.highlight(selected);
 
-    };
+            // hide the search box if this is the first we got the results and there are a few of them
 
-    SingleSelect2.prototype.onSelect = function (data) {
-        var old = this.opts.element.val();
+            if (initial === true) {
+                this.search.parent().toggle(data.results.length >= this.opts.minimumResultsForSearch);
+            }
 
-        this.opts.element.val(data.id);
-        this.updateSelection(data);
-        this.close();
-        this.selection.focus();
+        },
 
-        if (!equal(old, data.id)) { this.triggerChange(); }
-    };
+        onSelect: function (data) {
+            var old = this.opts.element.val();
 
-    SingleSelect2.prototype.updateSelection = function (data) {
-        this.selection
-            .find("span")
-            .html(this.opts.formatSelection(data));
+            this.opts.element.val(data.id);
+            this.updateSelection(data);
+            this.close();
+            this.selection.focus();
 
-        this.selection.removeClass("select2-default");
+            if (!equal(old, data.id)) { this.triggerChange(); }
+        },
 
-        if (this.opts.allowClear && this.getPlaceholder() !== undefined) {
-            this.selection.find("abbr").show();
-        }
-    };
+        updateSelection: function (data) {
+            this.selection
+                .find("span")
+                .html(this.opts.formatSelection(data));
 
-    SingleSelect2.prototype.val = function () {
-        var val, data = null;
+            this.selection.removeClass("select2-default");
 
-        if (arguments.length === 0) {
-            return this.opts.element.val();
-        }
+            if (this.opts.allowClear && this.getPlaceholder() !== undefined) {
+                this.selection.find("abbr").show();
+            }
+        },
 
-        val = arguments[0];
+        val: function () {
+            var val, data = null;
 
-        if (this.select) {
-            // val is an id
-            this.select
-                .val(val)
-                .find(":selected").each(function () {
-                    data = {id: $(this).attr("value"), text: $(this).text()};
-                    return false;
-                });
-            this.updateSelection(data);
-        } else {
-            // val is an object
-            this.opts.element.val((val === null) ? "" : val.id);
-            this.updateSelection(val);
-        }
-        this.setPlaceholder();
+            if (arguments.length === 0) {
+                return this.opts.element.val();
+            }
 
-    };
+            val = arguments[0];
 
-    SingleSelect2.prototype.clearSearch = function () {
-        this.search.val("");
-    };
+            if (this.select) {
+                // val is an id
+                this.select
+                    .val(val)
+                    .find(":selected").each(function () {
+                        data = {id: $(this).attr("value"), text: $(this).text()};
+                        return false;
+                    });
+                this.updateSelection(data);
+            } else {
+                // val is an object
+                this.opts.element.val((val === null) ? "" : val.id);
+                this.updateSelection(val);
+            }
+            this.setPlaceholder();
 
-    function MultiSelect2(opts) {
+        },
 
-    }
+        clearSearch: function () {
+            this.search.val("");
+        }
+    });
 
-    MultiSelect2.prototype = new AbstractSelect2();
-    MultiSelect2.prototype.constructor = AbstractSelect2;
-    MultiSelect2.prototype.parent = AbstractSelect2.prototype;
-
-    MultiSelect2.prototype.createContainer = function () {
-        return $("<div></div>", {
-            "class": "select2-container select2-container-multi",
-            "style": "width: " + this.getContainerWidth()
-        }).html([
-            "    <ul class='select2-choices'>",
-            //"<li class='select2-search-choice'><span>California</span><a href="javascript:void(0)" class="select2-search-choice-close"></a></li>" ,
-            "  <li class='select2-search-field'>" ,
-            "    <input type='text' autocomplete='off' style='width: 25px;'>" ,
-            "  </li>" ,
-            "</ul>" ,
-            "<div class='select2-drop' style='display:none;'>" ,
-            "   <ul class='select2-results'>" ,
-            "   </ul>" ,
-            "</div>"].join(""));
-    };
+    MultiSelect2 = clazz(AbstractSelect2, {
+
+        createContainer: function () {
+            return $("<div></div>", {
+                "class": "select2-container select2-container-multi",
+                "style": "width: " + this.getContainerWidth()
+            }).html([
+                "    <ul class='select2-choices'>",
+                //"<li class='select2-search-choice'><span>California</span><a href="javascript:void(0)" class="select2-search-choice-close"></a></li>" ,
+                "  <li class='select2-search-field'>" ,
+                "    <input type='text' autocomplete='off' style='width: 25px;'>" ,
+                "  </li>" ,
+                "</ul>" ,
+                "<div class='select2-drop' style='display:none;'>" ,
+                "   <ul class='select2-results'>" ,
+                "   </ul>" ,
+                "</div>"].join(""));
+        },
 
-    MultiSelect2.prototype.prepareOpts = function () {
-        var opts = this.parent.prepareOpts.apply(this, arguments);
+        prepareOpts: function () {
+            var opts = this.parent.prepareOpts.apply(this, arguments);
+
+            if (opts.element.get(0).tagName.toLowerCase() === "select") {
+                // install sthe selection initializer
+                this.opts.initSelection = function (element) {
+                    var data = [];
+                    element.find(":selected").each(function () {
+                        data.push({id: $(this).attr("value"), text: $(this).text()});
+                    });
+                    return data;
+                };
+            }
 
-        if (opts.element.get(0).tagName.toLowerCase() === "select") {
-            // install sthe selection initializer
-            this.opts.initSelection = function (element) {
-                var data = [];
-                element.find(":selected").each(function () {
-                    data.push({id: $(this).attr("value"), text: $(this).text()});
-                });
-                return data;
-            };
-        }
+            return opts;
+        },
 
-        return opts;
-    };
+        initContainer: function () {
 
-    MultiSelect2.prototype.initContainer = function () {
+            var selector = ".select2-choices", selection, data;
 
-        var selector = ".select2-choices", selection, data;
+            this.searchContainer = this.container.find(".select2-search-field");
+            this.selection = selection = this.container.find(selector);
 
-        this.searchContainer = this.container.find(".select2-search-field");
-        this.selection = selection = this.container.find(selector);
+            this.search.bind("keydown", this.bind(function (e) {
+                if (e.which === KEY.BACKSPACE && this.search.val() === "") {
+                    this.close();
 
-        this.search.bind("keydown", this.bind(function (e) {
-            if (e.which === KEY.BACKSPACE && this.search.val() === "") {
-                this.close();
+                    var choices,
+                        selected = this.selection.find(".select2-search-choice-focus");
+                    if (selected.length > 0) {
+                        this.unselect(selected.first());
+                        this.search.width(10);
+                        killEvent(e);
+                        return;
+                    }
 
-                var choices,
-                    selected = this.selection.find(".select2-search-choice-focus");
-                if (selected.length > 0) {
-                    this.unselect(selected.first());
-                    this.search.width(10);
-                    killEvent(e);
-                    return;
+                    choices = this.selection.find(".select2-search-choice");
+                    if (choices.length > 0) {
+                        choices.last().addClass("select2-search-choice-focus");
+                    }
+                } else {
+                    this.selection.find(".select2-search-choice-focus").removeClass("select2-search-choice-focus");
                 }
 
-                choices = this.selection.find(".select2-search-choice");
-                if (choices.length > 0) {
-                    choices.last().addClass("select2-search-choice-focus");
+                if (this.opened()) {
+                    switch (e.which) {
+                    case KEY.UP:
+                    case KEY.DOWN:
+                        this.moveHighlight((e.which === KEY.UP) ? -1 : 1);
+                        killEvent(e);
+                        return;
+                    case KEY.ENTER:
+                        this.selectHighlighted();
+                        killEvent(e);
+                        return;
+                    case KEY.ESC:
+                        this.cancel(e);
+                        e.preventDefault();
+                        return;
+                    }
                 }
-            } else {
-                this.selection.find(".select2-search-choice-focus").removeClass("select2-search-choice-focus");
-            }
 
-            if (this.opened()) {
-                switch (e.which) {
-                case KEY.UP:
-                case KEY.DOWN:
-                    this.moveHighlight((e.which === KEY.UP) ? -1 : 1);
-                    killEvent(e);
-                    return;
-                case KEY.ENTER:
-                    this.selectHighlighted();
-                    killEvent(e);
-                    return;
-                case KEY.ESC:
-                    this.cancel(e);
-                    e.preventDefault();
+                if (e.which === KEY.TAB || KEY.isControl(e) || KEY.isFunctionKey(e) || e.which === KEY.BACKSPACE || e.which === KEY.ESC) {
                     return;
                 }
-            }
 
-            if (e.which === KEY.TAB || KEY.isControl(e) || KEY.isFunctionKey(e) || e.which === KEY.BACKSPACE || e.which === KEY.ESC) {
-                return;
-            }
-
-            this.open();
+                this.open();
 
-            if (e.which === KEY.PAGE_UP || e.which === KEY.PAGE_DOWN) {
-                // prevent the page from scrolling
-                killEvent(e);
-            }
-        }));
+                if (e.which === KEY.PAGE_UP || e.which === KEY.PAGE_DOWN) {
+                    // prevent the page from scrolling
+                    killEvent(e);
+                }
+            }));
 
-        this.search.bind("keyup", this.bind(this.resizeSearch));
+            this.search.bind("keyup", this.bind(this.resizeSearch));
 
-        this.container.delegate(selector, "click", this.bind(function (e) {
-            this.open();
-            this.focusSearch();
-            e.preventDefault();
-        }));
+            this.container.delegate(selector, "click", this.bind(function (e) {
+                this.open();
+                this.focusSearch();
+                e.preventDefault();
+            }));
 
-        this.container.delegate(selector, "focus", this.bind(function () {
-            this.container.addClass("select2-container-active");
-            this.clearPlaceholder();
-        }));
+            this.container.delegate(selector, "focus", this.bind(function () {
+                this.container.addClass("select2-container-active");
+                this.clearPlaceholder();
+            }));
 
-        // set the placeholder if necessary
-        this.clearSearch();
-    };
+            // set the placeholder if necessary
+            this.clearSearch();
+        },
 
-    MultiSelect2.prototype.initSelection = function () {
-        var data;
-        if (this.opts.element.val() === "") {
-            this.updateSelection([]);
-        }
-        if (this.select || this.opts.element.val() !== "") {
-            data = this.opts.initSelection.call(null, this.opts.element);
-            if (data !== undefined && data != null) {
-                this.updateSelection(data);
+        initSelection: function () {
+            var data;
+            if (this.opts.element.val() === "") {
+                this.updateSelection([]);
+            }
+            if (this.select || this.opts.element.val() !== "") {
+                data = this.opts.initSelection.call(null, this.opts.element);
+                if (data !== undefined && data != null) {
+                    this.updateSelection(data);
+                }
             }
-        }
-
-        this.close();
 
-        // set the placeholder if necessary
-        this.clearSearch();
-    };
+            this.close();
 
-    MultiSelect2.prototype.clearSearch = function () {
-        var placeholder = this.getPlaceholder();
+            // set the placeholder if necessary
+            this.clearSearch();
+        },
 
-        this.search.val("").width(10);
+        clearSearch: function () {
+            var placeholder = this.getPlaceholder();
 
-        if (placeholder !== undefined && this.getVal().length === 0) {
-            this.search.val(placeholder).addClass("select2-default");
-            this.resizeSearch();
-        }
-    };
+            this.search.val("").width(10);
 
-    MultiSelect2.prototype.clearPlaceholder = function () {
-        if (this.search.hasClass("select2-default")) {
-            this.search.val("").removeClass("select2-default");
-        }
-    };
+            if (placeholder !== undefined && this.getVal().length === 0) {
+                this.search.val(placeholder).addClass("select2-default");
+                this.resizeSearch();
+            }
+        },
 
-    MultiSelect2.prototype.open = function () {
-        if (this.opened()) return;
-        this.parent.open.apply(this, arguments);
-        this.resizeSearch();
-        this.focusSearch();
-    };
+        clearPlaceholder: function () {
+            if (this.search.hasClass("select2-default")) {
+                this.search.val("").removeClass("select2-default");
+            }
+        },
 
-    MultiSelect2.prototype.close = function () {
-        if (!this.opened()) return;
-        this.parent.close.apply(this, arguments);
-    };
+        open: function () {
+            if (this.opened()) return;
+            this.parent.open.apply(this, arguments);
+            this.resizeSearch();
+            this.focusSearch();
+        },
 
-    MultiSelect2.prototype.updateSelection = function (data) {
-        var ids = [], filtered = [], self = this;
+        close: function () {
+            if (!this.opened()) return;
+            this.parent.close.apply(this, arguments);
+        },
 
-        // filter out duplicates
-        $(data).each(function () {
-            if (indexOf(this.id, ids) < 0) {
-                ids.push(this.id);
-                filtered.push(this);
-            }
-        });
-        data = filtered;
+        updateSelection: function (data) {
+            var ids = [], filtered = [], self = this;
 
-        this.selection.find(".select2-search-choice").remove();
-        $(data).each(function () {
-            self.addSelectedChoice(this);
-        });
-        self.postprocessResults();
-        this.alignDropdown();
-    };
+            // filter out duplicates
+            $(data).each(function () {
+                if (indexOf(this.id, ids) < 0) {
+                    ids.push(this.id);
+                    filtered.push(this);
+                }
+            });
+            data = filtered;
 
-    MultiSelect2.prototype.onSelect = function (data) {
-        this.addSelectedChoice(data);
-        if (this.select) { this.postprocessResults(); }
-        this.close();
-        this.search.width(10);
+            this.selection.find(".select2-search-choice").remove();
+            $(data).each(function () {
+                self.addSelectedChoice(this);
+            });
+            self.postprocessResults();
+            this.alignDropdown();
+        },
 
-        // since its not possible to select an element that has already been
-        // added we do not need to check if this is a new element before firing change
-        this.triggerChange();
+        onSelect: function (data) {
+            this.addSelectedChoice(data);
+            if (this.select) { this.postprocessResults(); }
+            this.close();
+            this.search.width(10);
 
-        this.focusSearch();
-    };
+            // since its not possible to select an element that has already been
+            // added we do not need to check if this is a new element before firing change
+            this.triggerChange();
 
-    MultiSelect2.prototype.cancel = function () {
-        this.close();
-        this.focusSearch();
-    };
+            this.focusSearch();
+        },
 
-    MultiSelect2.prototype.addSelectedChoice = function (data) {
-        var choice,
-            id = data.id,
-            parts,
-            val = this.getVal();
-
-        parts = ["<li class='select2-search-choice'>",
-            this.opts.formatSelection(data),
-            "<a href='javascript:void(0)' class='select2-search-choice-close' tabindex='-1'></a>",
-            "</li>"
-        ];
-
-        choice = $(parts.join(""));
-        choice.find("a")
-            .bind("click dblclick", this.bind(function (e) {
-            this.unselect($(e.target));
-            this.selection.find(".select2-search-choice-focus").removeClass("select2-search-choice-focus");
-            killEvent(e);
+        cancel: function () {
             this.close();
             this.focusSearch();
-        })).bind("focus", this.bind(function () {
-            this.container.addClass("select2-container-active");
-        }));
-
-        choice.data("select2-data", data);
-        choice.insertBefore(this.searchContainer);
+        },
 
-        val.push(id);
-        this.setVal(val);
-    };
+        addSelectedChoice: function (data) {
+            var choice,
+                id = data.id,
+                parts,
+                val = this.getVal();
+
+            parts = ["<li class='select2-search-choice'>",
+                this.opts.formatSelection(data),
+                "<a href='javascript:void(0)' class='select2-search-choice-close' tabindex='-1'></a>",
+                "</li>"
+            ];
+
+            choice = $(parts.join(""));
+            choice.find("a")
+                .bind("click dblclick", this.bind(function (e) {
+                this.unselect($(e.target));
+                this.selection.find(".select2-search-choice-focus").removeClass("select2-search-choice-focus");
+                killEvent(e);
+                this.close();
+                this.focusSearch();
+            })).bind("focus", this.bind(function () {
+                this.container.addClass("select2-container-active");
+            }));
 
-    MultiSelect2.prototype.unselect = function (selected) {
-        var val = this.getVal(),
-            index;
+            choice.data("select2-data", data);
+            choice.insertBefore(this.searchContainer);
 
-        selected = selected.closest(".select2-search-choice");
+            val.push(id);
+            this.setVal(val);
+        },
 
-        if (selected.length === 0) {
-            throw "Invalid argument: " + selected + ". Must be .select2-search-choice";
-        }
+        unselect: function (selected) {
+            var val = this.getVal(),
+                index;
 
-        index = indexOf(selected.data("select2-data").id, val);
+            selected = selected.closest(".select2-search-choice");
 
-        if (index >= 0) {
-            val.splice(index, 1);
-            this.setVal(val);
-            if (this.select) this.postprocessResults();
-        }
-        selected.remove();
-        this.triggerChange();
-        window.setTimeout(this.bind(this.alignDropdown), 20);
-    };
+            if (selected.length === 0) {
+                throw "Invalid argument: " + selected + ". Must be .select2-search-choice";
+            }
 
-    MultiSelect2.prototype.postprocessResults = function () {
-        var val = this.getVal(),
-            choices = this.results.find(".select2-result"),
-            self = this;
+            index = indexOf(selected.data("select2-data").id, val);
 
-        choices.each(function () {
-            var choice = $(this), id = choice.data("select2-data").id;
-            if (indexOf(id, val) >= 0) {
-                choice.addClass("select2-disabled");
-            } else {
-                choice.removeClass("select2-disabled");
+            if (index >= 0) {
+                val.splice(index, 1);
+                this.setVal(val);
+                if (this.select) this.postprocessResults();
             }
-        });
+            selected.remove();
+            this.triggerChange();
+            window.setTimeout(this.bind(this.alignDropdown), 20);
+        },
 
-        choices.each(function (i) {
-            if (!$(this).hasClass("select2-disabled")) {
-                self.highlight(i);
-                return false;
-            }
-        });
+        postprocessResults: function () {
+            var val = this.getVal(),
+                choices = this.results.find(".select2-result"),
+                self = this;
 
-    };
+            choices.each(function () {
+                var choice = $(this), id = choice.data("select2-data").id;
+                if (indexOf(id, val) >= 0) {
+                    choice.addClass("select2-disabled");
+                } else {
+                    choice.removeClass("select2-disabled");
+                }
+            });
 
-    MultiSelect2.prototype.resizeSearch = function () {
+            choices.each(function (i) {
+                if (!$(this).hasClass("select2-disabled")) {
+                    self.highlight(i);
+                    return false;
+                }
+            });
 
-        var minimumWidth, left, maxWidth, containerLeft, searchWidth;
+        },
 
-        minimumWidth = measureTextWidth(this.search) + 10;
+        resizeSearch: function () {
 
-        left = this.search.offset().left;
+            var minimumWidth, left, maxWidth, containerLeft, searchWidth;
 
-        maxWidth = this.selection.width();
-        containerLeft = this.selection.offset().left;
+            minimumWidth = measureTextWidth(this.search) + 10;
 
-        searchWidth = maxWidth - (left - containerLeft) - getSideBorderPadding(this.search);
+            left = this.search.offset().left;
 
-        if (searchWidth < minimumWidth) {
-            searchWidth = maxWidth - getSideBorderPadding(this.search);
-        }
+            maxWidth = this.selection.width();
+            containerLeft = this.selection.offset().left;
 
-        if (searchWidth < 40) {
-            searchWidth = maxWidth - getSideBorderPadding(this.search);
-        }
-        this.search.width(searchWidth);
-    };
+            searchWidth = maxWidth - (left - containerLeft) - getSideBorderPadding(this.search);
 
-    MultiSelect2.prototype.getVal = function () {
-        var val;
-        if (this.select) {
-            val = this.select.val();
-            return val === null ? [] : val;
-        } else {
-            val = this.opts.element.val();
-            return (val === null || val === "") ? [] : val.split(",");
-        }
-    };
+            if (searchWidth < minimumWidth) {
+                searchWidth = maxWidth - getSideBorderPadding(this.search);
+            }
 
-    MultiSelect2.prototype.setVal = function (val) {
-        var unique = [];
-        if (this.select) {
-            this.select.val(val);
-        } else {
-            // filter out duplicates
-            $(val).each(function () {
-                if (indexOf(this, unique) < 0) unique.push(this);
-            });
+            if (searchWidth < 40) {
+                searchWidth = maxWidth - getSideBorderPadding(this.search);
+            }
+            this.search.width(searchWidth);
+        },
 
-            this.opts.element.val(unique.length === 0 ? "" : unique.join(","));
-        }
-    };
+        getVal: function () {
+            var val;
+            if (this.select) {
+                val = this.select.val();
+                return val === null ? [] : val;
+            } else {
+                val = this.opts.element.val();
+                return (val === null || val === "") ? [] : val.split(",");
+            }
+        },
 
-    MultiSelect2.prototype.val = function () {
-        var val, data = [];
+        setVal: function (val) {
+            var unique = [];
+            if (this.select) {
+                this.select.val(val);
+            } else {
+                // filter out duplicates
+                $(val).each(function () {
+                    if (indexOf(this, unique) < 0) unique.push(this);
+                });
 
-        if (arguments.length === 0) {
-            return this.getVal();
-        }
+                this.opts.element.val(unique.length === 0 ? "" : unique.join(","));
+            }
+        },
 
-        val = arguments[0];
+        val: function () {
+            var val, data = [];
 
-        if (this.select) {
-            // val is a list of ids
-            this.setVal(val);
-            this.select.find(":selected").each(function () {
-                data.push({id: $(this).attr("value"), text: $(this).text()});
-            });
-            this.updateSelection(data);
-        } else {
-            val = (val === null) ? [] : val;
-            this.setVal(val);
-            // val is a list of objects
+            if (arguments.length === 0) {
+                return this.getVal();
+            }
 
-            $(val).each(function () { data.push(this.id); });
-            this.setVal(data);
-            this.updateSelection(val);
+            val = arguments[0];
+
+            if (this.select) {
+                // val is a list of ids
+                this.setVal(val);
+                this.select.find(":selected").each(function () {
+                    data.push({id: $(this).attr("value"), text: $(this).text()});
+                });
+                this.updateSelection(data);
+            } else {
+                val = (val === null) ? [] : val;
+                this.setVal(val);
+                // val is a list of objects
+
+                $(val).each(function () { data.push(this.id); });
+                this.setVal(data);
+                this.updateSelection(val);
+            }
         }
-    };
+    });
 
     $.fn.select2 = function () {