Browse Source

initial checkin

Igor Vaynberg 13 năm trước cách đây
commit
53e214d32f
6 tập tin đã thay đổi với 1128 bổ sung0 xóa
  1. 16 0
      LICENSE
  2. 100 0
      README.md
  3. 231 0
      select2.css
  4. 781 0
      select2.js
  5. BIN
      select2.png
  6. BIN
      spinner.gif

+ 16 - 0
LICENSE

@@ -0,0 +1,16 @@
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License.  You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied.  See the License for the
+specific language governing permissions and limitations
+under the License.

+ 100 - 0
README.md

@@ -0,0 +1,100 @@
+Select2
+=================
+
+Select2 is a jQuery based replacement for select boxes. It supports searching, remote data sets, and infinite scrolling of results. Look and feel of Select2 is based on the excellent [Chosen](http://harvesthq.github.com/chosen/) library.
+
+To get started -- checkout http://ivaynberg.github.com/select2!
+
+
+Versioning
+----------
+
+For transparency and insight into our release cycle, and for striving to maintain backward compatibility, Bootstrap will be maintained under the Semantic Versioning guidelines as much as possible.
+
+Releases will be numbered with the follow format:
+
+`<major>.<minor>.<patch>`
+
+And constructed with the following guidelines:
+
+* Breaking backward compatibility bumps the major
+* New additions without breaking backward compatibility bumps the minor
+* Bug fixes and misc changes bump the patch
+
+For more information on SemVer, please visit http://semver.org/.
+
+
+Bug tracker
+-----------
+
+Have a bug? Please create an issue here on GitHub!
+
+https://github.com/twitter/bootstrap/issues
+
+
+Twitter account
+---------------
+
+Keep up to date on announcements and more by following Bootstrap on Twitter, <a href="http://twitter.com/TwBootstrap">@TwBootstrap</a>.
+
+
+Mailing list
+------------
+
+Have a question? Ask on our mailing list!
+
[email protected]
+
+http://groups.google.com/group/twitter-bootstrap
+
+
+IRC
+---
+
+Server: irc.freenode.net
+
+Channel: ##twitter-bootstrap (the double ## is not a typo)
+
+
+Developers
+----------
+
+We have included a makefile with convenience methods for working with the Bootstrap library.
+
++ **build** - `make`
+Runs the LESS compiler to rebuild the `/less` files and compiles the docs pages. Requires lessc and uglify-js. <a href="http://twitter.github.com/bootstrap/less.html#compiling">Read more in our docs &raquo;</a>
+
++ **watch** - `make watch`
+This is a convenience method for watching just Less files and automatically building them whenever you save. Requires the Watchr gem.
+
+
+Authors
+-------
+
+**Mark Otto**
+
++ http://twitter.com/mdo
++ http://github.com/markdotto
+
+**Jacob Thornton**
+
++ http://twitter.com/fat
++ http://github.com/fat
+
+
+Copyright and license
+---------------------
+
+Copyright 2012 Twitter, Inc.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this work except in compliance with the License.
+You may obtain a copy of the License in the LICENSE file, or at:
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.

+ 231 - 0
select2.css

@@ -0,0 +1,231 @@
+.select2-container {
+    position: relative;
+    display: inline-block;
+    /* inline-block for ie7 */
+    zoom: 1;
+    *display: inline;
+
+}
+
+.select2-container .select2-choice {
+    background-color: #fff;
+    background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #eeeeee), color-stop(0.5, white));
+    background-image: -webkit-linear-gradient(center bottom, #eeeeee 0%, white 50%);
+    background-image: -moz-linear-gradient(center bottom, #eeeeee 0%, white 50%);
+    background-image: -o-linear-gradient(top, #eeeeee 0%, #ffffff 50%);
+    background-image: -ms-linear-gradient(top, #eeeeee 0%, #ffffff 50%);
+    filter: progid:DXImageTransform.Microsoft.gradient(startColorstr = '#eeeeee', endColorstr = '#ffffff', GradientType = 0);
+    background-image: linear-gradient(top, #eeeeee 0%, #ffffff 50%);
+    -webkit-border-radius: 4px;
+    -moz-border-radius: 4px;
+    border-radius: 4px;
+    -moz-background-clip: padding;
+    -webkit-background-clip: padding-box;
+    background-clip: padding-box;
+    border: 1px solid #aaa;
+    display: block;
+    overflow: hidden;
+    white-space: nowrap;
+    position: relative;
+    height: 26px;
+    line-height: 26px;
+    padding: 0 0 0 8px;
+    color: #444;
+    text-decoration: none;
+}
+
+.select2-container .select2-choice span {
+    margin-right: 26px;
+    display: block;
+    overflow: hidden;
+    white-space: nowrap;
+    -o-text-overflow: ellipsis;
+    -ms-text-overflow: ellipsis;
+    text-overflow: ellipsis;
+}
+
+.select2-container .select2-drop {
+    background: #fff;
+    border: 1px solid #aaa;
+    border-top: 0;
+    position: absolute;
+    top: 29px;
+    left: 0;
+    -webkit-box-shadow: 0 4px 5px rgba(0, 0, 0, .15);
+    -moz-box-shadow: 0 4px 5px rgba(0, 0, 0, .15);
+    -o-box-shadow: 0 4px 5px rgba(0, 0, 0, .15);
+    box-shadow: 0 4px 5px rgba(0, 0, 0, .15);
+    z-index: 999;
+
+    -webkit-border-radius: 0 0 4px 4px;
+    -moz-border-radius: 0 0 4px 4px;
+    border-radius: 0 0 4px 4px;
+    -moz-background-clip: padding;
+    -webkit-background-clip: padding-box;
+    background-clip: padding-box;
+}
+
+.select2-container .select2-choice div {
+    -webkit-border-radius: 0 4px 4px 0;
+    -moz-border-radius: 0 4px 4px 0;
+    border-radius: 0 4px 4px 0;
+    -moz-background-clip: padding;
+    -webkit-background-clip: padding-box;
+    background-clip: padding-box;
+    background: #ccc;
+    background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #ccc), color-stop(0.6, #eee));
+    background-image: -webkit-linear-gradient(center bottom, #ccc 0%, #eee 60%);
+    background-image: -moz-linear-gradient(center bottom, #ccc 0%, #eee 60%);
+    background-image: -o-linear-gradient(bottom, #ccc 0%, #eee 60%);
+    background-image: -ms-linear-gradient(top, #cccccc 0%, #eeeeee 60%);
+    filter: progid:DXImageTransform.Microsoft.gradient(startColorstr = '#cccccc', endColorstr = '#eeeeee', GradientType = 0);
+    background-image: linear-gradient(top, #cccccc 0%, #eeeeee 60%);
+    border-left: 1px solid #aaa;
+    position: absolute;
+    right: 0;
+    top: 0;
+    display: block;
+    height: 100%;
+    width: 18px;
+}
+
+.select2-container .select2-choice div b {
+    background: url('select2.png') no-repeat 0 1px;
+    display: block;
+    width: 100%;
+    height: 100%;
+}
+
+.select2-container .select2-search {
+    padding: 3px 4px;
+    position: relative;
+    margin: 0;
+    white-space: nowrap;
+    z-index: 1010;
+}
+
+.select2-container .select2-search input {
+    background: #fff url('select2.png') no-repeat 100% -22px;
+    background: url('select2.png') no-repeat 100% -22px, -webkit-gradient(linear, left bottom, left top, color-stop(0.85, white), color-stop(0.99, #eeeeee));
+    background: url('select2.png') no-repeat 100% -22px, -webkit-linear-gradient(center bottom, white 85%, #eeeeee 99%);
+    background: url('select2.png') no-repeat 100% -22px, -moz-linear-gradient(center bottom, white 85%, #eeeeee 99%);
+    background: url('select2.png') no-repeat 100% -22px, -o-linear-gradient(bottom, white 85%, #eeeeee 99%);
+    background: url('select2.png') no-repeat 100% -22px, -ms-linear-gradient(top, #ffffff 85%, #eeeeee 99%);
+    background: url('select2.png') no-repeat 100% -22px, linear-gradient(top, #ffffff 85%, #eeeeee 99%);
+    margin: 1px 0;
+    padding: 4px 20px 4px 5px;
+    outline: 0;
+    border: 1px solid #aaa;
+    font-family: sans-serif;
+    font-size: 1em;
+}
+
+.select2-container .select2-search input.select2-active {
+    background: #fff url('spinner.gif') no-repeat 100%;
+    background: url('spinner.gif') no-repeat 100%, -webkit-gradient(linear, left bottom, left top, color-stop(0.85, white), color-stop(0.99, #eeeeee));
+    background: url('spinner.gif') no-repeat 100%, -webkit-linear-gradient(center bottom, white 85%, #eeeeee 99%);
+    background: url('spinner.gif') no-repeat 100%, -moz-linear-gradient(center bottom, white 85%, #eeeeee 99%);
+    background: url('spinner.gif') no-repeat 100%, -o-linear-gradient(bottom, white 85%, #eeeeee 99%);
+    background: url('spinner.gif') no-repeat 100%, -ms-linear-gradient(top, #ffffff 85%, #eeeeee 99%);
+    background: url('spinner.gif') no-repeat 100%, linear-gradient(top, #ffffff 85%, #eeeeee 99%);
+    margin: 1px 0;
+    padding: 4px 20px 4px 5px;
+    outline: 0;
+    border: 1px solid #aaa;
+    font-family: sans-serif;
+    font-size: 1em;
+}
+
+
+/* active styles */
+.select2-container-focused .select2-choice {
+  -webkit-box-shadow: 0 0 5px rgba(0,0,0,.3);
+  -moz-box-shadow   : 0 0 5px rgba(0,0,0,.3);
+  -o-box-shadow     : 0 0 5px rgba(0,0,0,.3);
+  box-shadow        : 0 0 5px rgba(0,0,0,.3);
+  border: 1px solid #5897fb;
+}
+
+.select2-dropdown-open .select2-choice {
+  border: 1px solid #aaa;
+  -webkit-box-shadow: 0 1px 0 #fff inset;
+  -moz-box-shadow   : 0 1px 0 #fff inset;
+  -o-box-shadow     : 0 1px 0 #fff inset;
+  box-shadow        : 0 1px 0 #fff inset;
+  background-color: #eee;
+  background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, white), color-stop(0.5, #eeeeee));
+  background-image: -webkit-linear-gradient(center bottom, white 0%, #eeeeee 50%);
+  background-image: -moz-linear-gradient(center bottom, white 0%, #eeeeee 50%);
+  background-image: -o-linear-gradient(bottom, white 0%, #eeeeee 50%);
+  background-image: -ms-linear-gradient(top, #ffffff 0%,#eeeeee 50%);
+  filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#ffffff', endColorstr='#eeeeee',GradientType=0 );
+  background-image: linear-gradient(top, #ffffff 0%,#eeeeee 50%);
+  -webkit-border-bottom-left-radius : 0;
+  -webkit-border-bottom-right-radius: 0;
+  -moz-border-radius-bottomleft : 0;
+  -moz-border-radius-bottomright: 0;
+  border-bottom-left-radius : 0;
+  border-bottom-right-radius: 0;
+}
+
+
+.select2-dropdown-open .select2-choice div {
+  background: transparent;
+  border-left: none;
+}
+.select2-dropdown-open .select2-choice div b {
+  background-position: -18px 1px;
+}
+
+/* results */
+.select2-container .select2-results {
+  margin: 0 4px 4px 0;
+  padding: 0 0 0 4px;
+  position: relative;
+  overflow-x: hidden;
+  overflow-y: auto;
+  max-height: 200px;
+}
+.select2-container .select2-results li {
+  line-height: 80%;
+  padding: 7px 7px 8px;
+  margin: 0;
+  list-style: none;
+  cursor: pointer;
+  display: list-item;
+}
+
+.select2-container .select2-results .select2-highlighted {
+  background: #3875d7;
+  color: #fff;
+}
+.select2-container .select2-results li em {
+  background: #feffde;
+  font-style: normal;
+}
+.select2-container .select2-results .select2-highlighted em {
+  background: transparent;
+}
+.select2-container .select2-results .select2-no-results {
+  background: #f4f4f4;
+  display: list-item;
+}
+
+.select2-more-results.select2-active {
+    background: #f4f4f4 url('spinner.gif') no-repeat 100%;
+}
+
+.select2-more-results {
+  background: #f4f4f4;
+  display: list-item;
+}
+
+
+
+
+
+/*
+.select2-container .select2-drop { border: 1px solid red !important;}
+.select2-container .select2-drop .select2-search { border: 1px solid green !important;}
+.select2-container .select2-drop .select2-search input { border: 1px solid blue !important;}
+*/

+ 781 - 0
select2.js

@@ -0,0 +1,781 @@
+/*
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements.  See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership.  The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License.  You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied.  See the License for the
+ specific language governing permissions and limitations
+ under the License.
+ */
+(function ($) {
+    "use strict";
+    /*global document, window, jQuery, console */
+
+    var KEY, Util, DropDown, ResultList, Selection, Select2, Queries;
+
+    function createClass(def) {
+        var type = function (attrs) {
+            var self = this;
+            if (def.attrs !== undefined) {
+                $.each(def.attrs, function (name, body) {
+                    if (attrs[name] !== undefined) {
+                        self[name] = attrs[name];
+                    } else {
+                        if (body.required === true) {
+                            throw "Value for required attribute: " + name + " not defined";
+                        }
+                        if (body.init !== undefined) {
+                            self[name] = typeof (body.init) === "function" ? body.init.apply(self) : body.init;
+                        }
+                    }
+                });
+            }
+
+            if (def.methods !== undefined && def.methods.init !== undefined) {
+                self.init(attrs);
+            }
+        };
+
+        if (def.methods !== undefined) {
+            if (def.methods.bind !== undefined) {
+                throw "Class cannot declare a method called 'bind'";
+            }
+
+            $.each(def.methods, function (name, body) {
+                type.prototype[name] = body;
+            });
+
+            type.prototype.bind = function (func) {
+                var self = this;
+                return function () {
+                    func.apply(self, arguments);
+                };
+            };
+        }
+
+        return type;
+    }
+
+    KEY = {
+        TAB: 9,
+        ENTER: 13,
+        ESC: 27,
+        SPACE: 32,
+        LEFT: 37,
+        UP: 38,
+        RIGHT: 39,
+        DOWN: 40,
+        SHIFT: 16,
+        CTRL: 17,
+        ALT: 18,
+        PAGE_UP: 33,
+        PAGE_DOWN: 34,
+        HOME: 36,
+        END: 35,
+        BACKSPACE: 8,
+        DELETE: 46
+    };
+
+    Util = {};
+
+    Util.debounce = function (threshold, fn) {
+        var timeout;
+        return function () {
+            window.clearTimeout(timeout);
+            timeout = window.setTimeout(fn, threshold);
+        };
+    };
+
+    Util.debounceEvent = function (element, threshold, event, debouncedEvent, direct) {
+        debouncedEvent = debouncedEvent || event + "-debounced";
+        direct = direct || true;
+
+        var notify = Util.debounce(threshold, function (e) {
+            element.trigger(debouncedEvent, e);
+        });
+
+        element.on(event, function (e) {
+            if (direct && element.get().indexOf(e.target) < 0) {
+                return;
+            }
+            notify(e);
+        });
+    };
+
+    (function () {
+
+        var lastpos;
+
+        /**
+         * Filters mouse events so an event is fired only if the mouse moved.
+         * Filters out mouse events that occur when mouse is stationary but
+         * the elements under the pointer are scrolled
+         */
+        Util.filterMouseEvent = function (element, event, filteredEvent, direct) {
+            filteredEvent = filteredEvent || event + "-filtered";
+            direct = direct || false;
+
+            element.on(event, "*", function (e) {
+                if (direct && element.get().indexOf(e.target) < 0) {
+                    return;
+                }
+                if (lastpos === undefined || lastpos.x !== e.pageX || lastpos.y !== e.pageY) {
+                    $(e.target).trigger(filteredEvent, e);
+                    lastpos = {x: e.pageX, y: e.pageY};
+                }
+            });
+        };
+    }());
+
+    DropDown = createClass({
+        attrs: {
+            container: {required: true},
+            element: {required: true},
+            bus: {required: true}
+        },
+        methods: {
+            open: function () {
+                if (this.isOpen()) {
+                    return;
+                }
+
+                this.container.addClass("select2-dropdown-open");
+
+                // register click-outside-closes-dropdown listener
+                $(document).on("mousedown.dropdown", this.bind(function (e) {
+                    var inside = false,
+                        container = this.container.get(0);
+                    $(e.target).parents().each(function () {
+                        return !(inside = (this === container));
+                    });
+                    if (!inside) {
+                        this.close();
+                    }
+                }));
+
+                this.element.show();
+                this.bus.trigger("opened");
+            },
+
+            close: function () {
+                if (!this.isOpen()) {
+                    return;
+                }
+
+                this.container.removeClass("select2-dropdown-open");
+
+                $(document).off("mousedown.dropdown");
+                this.element.hide();
+                this.bus.trigger("closed");
+            },
+
+            isOpen: function () {
+                return this.container.hasClass("select2-dropdown-open");
+            },
+
+            toggle: function () {
+                if (this.isOpen()) {
+                    this.close();
+                } else {
+                    this.open();
+                }
+            }
+        }
+
+    });
+
+    ResultList = createClass({
+        attrs: {
+            element: {required: true},
+            bus: {required: true},
+            formatInputTooShort: {required: true},
+            formatNoMatches: {required: true},
+            formatResult: {required: true},
+            minimumInputLength: {required: true},
+            query: {required: true},
+            selection: {required: true}
+        },
+        methods: {
+            init: function () {
+                var self = this;
+
+                this.search = this.element.find("input");
+                this.results = this.element.find("ul");
+                this.scrollPosition = 0;
+                this.vars = {};
+
+                this.search.on("keyup", function (e) {
+                    if (e.which >= 48 || e.which === KEY.SPACE || e.which === KEY.BACKSPACE || e.which === KEY.DELETE) {
+                        self.update();
+                    }
+                });
+
+                this.search.on("keydown", function (e) {
+                    switch (e.which) {
+                    case KEY.TAB:
+                        e.preventDefault();
+                        self.select();
+                        return;
+                    case KEY.ENTER:
+                        e.preventDefault();
+                        e.stopPropagation();
+                        self.select();
+                        return;
+                    case KEY.UP:
+                        self.moveSelection(-1);
+                        e.preventDefault();
+                        e.stopPropagation();
+                        return;
+                    case KEY.DOWN:
+                        self.moveSelection(1);
+                        e.preventDefault();
+                        e.stopPropagation();
+                        return;
+                    case KEY.ESC:
+                        e.preventDefault();
+                        e.stopPropagation();
+                        self.cancel();
+                        return;
+                    }
+                });
+
+//                this.results.on("mouseleave", "li.select2-result", this.bind(this.unhighlight));
+                Util.filterMouseEvent(this.results, "mousemove");
+                this.results.on("mousemove-filtered", this.bind(function (e) {
+                    var el = $(e.target).closest("li.select2-result");
+                    if (el.length < 1) {
+                        return;
+                    }
+                    this.setSelection(el.index());
+                }));
+                this.results.on("click", this.bind(function (e) {
+                    var el = $(e.target).closest("li.select2-result");
+                    if (el.length < 1) {
+                        return;
+                    }
+                    this.bus.trigger("selected", [el.data("select2-result")]);
+                }));
+
+                Util.debounceEvent(this.results, 100, "scroll");
+
+                this.results.on("scroll-debounced", this.bind(function (e) {
+                    this.scrollPosition = this.results.scrollTop();
+
+                    var more = this.results.find("li.select2-more-results"), below;
+
+                    if (more.length === 0) {
+                        return;
+                    }
+
+                    // pixels the element is below the scroll fold, below==0 is when the element is starting to be visible
+                    below = more.offset().top - this.results.offset().top - this.results.height();
+
+                    if (below <= 0) {
+                        more.addClass("select2-active");
+                        this.query({term: this.search.val(), vars: this.vars, callback: this.bind(this.append)});
+                    }
+
+                }));
+            },
+            open: function (e) {
+                this.search.focus();
+                this.results.scrollTop(this.scrollPosition);
+                if (this.results.children().length === 0) {
+                    // first time the dropdown is opened, update the results
+                    this.update();
+                }
+            },
+            close: function () {
+                //this.search.val("");
+                //this.clear();
+            },
+            clear: function () {
+                this.results.empty();
+            },
+            showInputTooShort: function () {
+                this.show("<li class='select2-no-results'>" + this.formatInputTooShort(this.search.val(), this.minimumInputLength) + "</li>");
+            },
+            showNoMatches: function () {
+                this.show("<li class='select2-no-results'>" + this.formatNoMatches(this.search.val()) + "</li>");
+            },
+            show: function (html) {
+                this.results.html(html);
+                this.results.scrollTop(0);
+                this.search.removeClass("select2-active");
+            },
+            update: function () {
+                var html = "";
+
+                if (this.search.val().length < this.minimumInputLength) {
+                    this.showInputTooShort();
+                    return;
+                }
+
+                this.search.addClass("select2-active");
+                this.vars = {};
+                this.query({term: this.search.val(), vars: this.vars, callback: this.bind(this.process)});
+            },
+            process: function (data) {
+                if (data.results.length === 0) {
+                    this.showNoMatches();
+                    return;
+                }
+
+                var html = this.stringizeResults(data.results), selectedId = this.selection.val(), selectedIndex = 0;
+
+                if (data.more === true) {
+                    html += "<li class='select2-more-results'>Loading more results...</li>";
+                }
+                this.vars = data.vars || {};
+
+                this.show(html);
+
+                this.findChoices().each(function (i) {
+                    if (selectedId === data.results[i].id) {
+                        selectedIndex = i;
+                    }
+                    $(this).data("select2-result", data.results[i]);
+                });
+
+                this.setSelection(selectedIndex);
+
+            },
+            append: function (data) {
+
+                var more = this.results.find("li.select2-more-results"), html, offset;
+
+                this.vars = data.vars || {};
+
+                if (data.results.length === 0) {
+                    more.remove();
+                    return;
+                }
+
+                html = this.stringizeResults(data.results);
+
+                offset = this.results.find("li.select2-result").length;
+
+                more.before(html);
+
+                this.results.find("li.select2-result").each(function (i) {
+                    if (i >= offset) {
+                        $(this).data("select2-result", data.results[i - offset]);
+                    }
+                });
+
+                if (data.more !== true) {
+                    more.remove();
+                } else {
+                    more.removeClass("select2-active");
+                }
+
+            },
+            stringizeResults: function (results, html) {
+                var i, l, classes;
+                html = html || "";
+                for (i = 0, l = results.length; i < l; i += 1) {
+                    html += "<li class='select2-result'>";
+                    html += this.formatResult(results[i]);
+                    html += "</li>";
+                }
+                return html;
+            },
+
+            findChoices: function () {
+                return this.results.children("li.select2-result");
+            },
+
+            removeSelection: function () {
+                this.findChoices().each(function () {
+                    $(this).removeClass("select2-highlighted");
+                });
+            },
+
+            setSelection: function (index) {
+                this.removeSelection();
+
+                var children = this.findChoices(),
+                    child = $(children[index]),
+                    hb,
+                    rb,
+                    y,
+                    more;
+
+                child.addClass("select2-highlighted");
+
+                this.search.focus();
+
+                hb = child.offset().top + child.outerHeight();
+
+                // if this is the last child lets also make sure select2-more-results is visible
+                if (index === children.length - 1) {
+                    more = this.results.find("li.select2-more-results");
+                    if (more.length > 0) {
+                        hb = more.offset().top + more.outerHeight();
+                    }
+                }
+
+                rb = this.results.offset().top + this.results.outerHeight();
+                if (hb > rb) {
+                    this.results.scrollTop(this.results.scrollTop() + (hb - rb));
+                }
+                y = child.offset().top - this.results.offset().top;
+
+                // make sure the top of the element is visible
+                if (y < 0) {
+                    this.results.scrollTop(this.results.scrollTop() + y); // y is negative
+                }
+
+            },
+
+            getSelectionIndex: function () {
+                var children = this.findChoices(), i = 0, l = children.length;
+                for (; i < l; i += 1) {
+                    if ($(children[i]).hasClass("select2-highlighted")) {
+                        return i;
+                    }
+                }
+                return -1;
+            },
+
+            moveSelection: function (delta) {
+                var current = this.getSelectionIndex(),
+                    children = this.findChoices(),
+                    next = current + delta;
+
+                if (current >= 0 && next >= 0 && next < children.length) {
+                    this.setSelection(next);
+                }
+            },
+
+            select: function () {
+                var selected = this.results.find("li.select2-highlighted");
+                if (selected.length > 0) {
+                    this.bus.trigger("selected", [selected.data("select2-result")]);
+                }
+            },
+
+            cancel: function () {
+                this.bus.trigger("cancelled");
+            },
+
+            val: function (data) {
+                var choices = this.findChoices(), index;
+
+                choices.each(function (i) {
+                    if ($(this).data("select2-result").id === data) {
+                        index = i;
+                        return false;
+                    }
+                });
+
+                if (index === undefined && data.id !== undefined) {
+                    choices.each(function (i) {
+                        if ($(this).data("select2-result").id === data.id) {
+                            index = i;
+                            return false;
+                        }
+                    });
+                }
+
+                if (index !== undefined) {
+                    this.setSelection(index);
+                    this.select();
+                    return;
+                }
+
+                this.bus.trigger("selected", data);
+            }
+
+        }
+    });
+
+    Selection = createClass({
+        attrs: {
+            bus: {required: true},
+            element: {required: true},
+            display: {init: function () {
+                return this.element.find("span");
+            }},
+            hidden: {required: true},
+            formatSelection: {required: true},
+            placeholder: {},
+            dropdown: {required: true}
+        },
+        methods: {
+            init: function () {
+                if (this.placeholder) {
+                    this.select(this.placeholder);
+                }
+                this.element.click(this.dropdown.bind(this.dropdown.toggle));
+
+                var self = this;
+                this.element.on("keydown", function (e) {
+                    switch (e.which) {
+                    case KEY.TAB:
+                    case KEY.SHIFT:
+                    case KEY.CTRL:
+                    case KEY.ALT:
+                    case KEY.LEFT:
+                    case KEY.RIGHT:
+                        return;
+                    }
+                    self.dropdown.open();
+                });
+            },
+            select: function (data) {
+                this.display.html(this.formatSelection(data));
+                this.hidden.val(data.id);
+            },
+            focus: function () {
+                this.element.focus();
+            },
+            val: function () {
+                return this.hidden.val();
+            }
+        }
+
+    });
+
+    Queries = {};
+    Queries.select = function (select2, element) {
+        var options = [];
+        element.find("option").each(function () {
+            var e = $(this);
+            options.push({id: e.attr("value"), text: e.text()});
+        });
+        return function (query) {
+            var data = {results: [], more: false},
+                text = query.term.toUpperCase();
+            $.each(options, function (i) {
+                if (this.text.toUpperCase().indexOf(text) >= 0) {
+                    data.results.push(this);
+                }
+            });
+            query.callback(data);
+        };
+    };
+    Queries.ajax = function (select2, el) {
+        var timeout, // current scheduled but not yet executed request
+            requestSequence = 0, // sequence used to drop out-of-order responses
+            quietMillis = select2.ajax.quietMillis || 100;
+
+        return function (query) {
+            window.clearTimeout(timeout);
+            timeout = window.setTimeout(function () {
+                requestSequence += 1; // increment the sequence
+                var requestNumber = requestSequence, // this request's sequence number
+                    options = select2.ajax, // ajax parameters
+                    data = options.data; // ajax data function
+
+                data = data.call(this, query.term, query.vars);
+
+                $.ajax({
+                    url: options.url,
+                    dataType: options.dataType,
+                    data: data
+                }).success(
+                    function (data) {
+                        if (requestNumber < requestSequence) {
+                            return;
+                        }
+                        query.callback(options.results(data, query.vars));
+                    }
+                );
+            }, quietMillis);
+        };
+    };
+
+    Select2 = createClass({
+        attrs: {
+            el: {required: true},
+            formatResult: {init: function () {
+                return function (data) {
+                    return data.text;
+                };
+            }},
+            formatSelection: {init: function () {
+                return function (data) {
+                    return data.text;
+                };
+            }},
+            formatNoMatches: {init: function () {
+                return function () {
+                    return "No matches found";
+                };
+            }},
+            formatInputTooShort: {init: function () {
+                return function (input, min) {
+                    return "Please enter " + (min - input.length) + " more characters to start search";
+                };
+            }},
+            minimumInputLength: {init: 0},
+            placeholder: {init: undefined},
+            ajax: {init: undefined},
+            query: {init: undefined}
+        },
+        methods: {
+            init: function () {
+                var self = this, width, dropdown, results, selected, select;
+
+                this.el = $(this.el);
+
+                width = this.el.outerWidth();
+
+                this.container = $("<div></div>", {
+                    "class": "select2-container",
+                    style: "width: " + width + "px"
+                });
+                this.container.html(
+                    "   <a href='javascript:void(0)' class='select2-choice'>" +
+                        "   <span></span>" +
+                        "   <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>" +
+                        "<input type='hidden'/>"
+                );
+
+                this.el.data("select2", this);
+                this.el.hide();
+                this.el.after(self.container);
+                if (this.el.attr("class") !== undefined) {
+                    this.container.addClass(this.el.attr("class"));
+                }
+                this.container.data("select2", this);
+                this.container.find("input[type=hidden]").attr("name", this.el.attr("name"));
+
+                if (this.query === undefined && this.el.get(0).tagName.toUpperCase() === "SELECT") {
+                    this.query = "select";
+                    select = true;
+                }
+                if (Queries[this.query] !== undefined) {
+                    this.query = Queries[this.query](this, this.el);
+                }
+
+                (function () {
+
+                    var dropdown, searchContainer, search, width;
+
+                    function getSideBorderPadding(e) {
+                        return e.outerWidth() - e.width();
+                    }
+
+                    // position and size dropdown
+                    dropdown = self.container.find("div.select2-drop");
+                    width = self.container.outerWidth() - getSideBorderPadding(dropdown);
+                    dropdown.css({top: self.container.height(), width: width});
+
+                    // size search field
+                    searchContainer = self.container.find(".select2-search");
+                    search = searchContainer.find("input");
+                    width = dropdown.width();
+                    width -= getSideBorderPadding(searchContainer);
+                    width -= getSideBorderPadding(search);
+                    search.css({width: width});
+                }());
+
+                dropdown = new DropDown({
+                    element: this.container.find("div.select2-drop"),
+                    container: this.container,
+                    bus: this.el
+                });
+
+                this.selection = new Selection({
+                    bus: this.el,
+                    element: this.container.find(".select2-choice"),
+                    hidden: this.container.find("input[type=hidden]"),
+                    formatSelection: this.formatSelection,
+                    placeholder: this.placeholder,
+                    dropdown: dropdown
+                });
+
+                this.results = new ResultList({
+                    element: this.container.find("div.select2-drop"),
+                    bus: this.el,
+                    formatInputTooShort: this.formatInputTooShort,
+                    formatNoMatches: this.formatNoMatches,
+                    formatResult: this.formatResult,
+                    minimumInputLength: this.minimumInputLength,
+                    query: this.query,
+                    selection: this.selection
+                });
+
+                this.el.on("selected", function (e, result) {
+                    dropdown.close();
+                    self.selection.select(result);
+                });
+
+                this.el.on("cancelled", function () {
+                    dropdown.close();
+                });
+
+                this.el.on("opened", this.bind(function () {
+                    this.results.open();
+                }));
+
+                this.el.on("closed", this.bind(function () {
+                    this.container.removeClass("select2-dropdown-open");
+                    this.results.close();
+                    this.selection.focus();
+                }));
+
+                // if attached to a select do some default initialization
+                if (select) {
+                    this.results.update(); // build the results
+                    selected = this.el.find("option[selected]");
+                    if (selected.length < 1 && this.placeholder === undefined) {
+                        selected = $(this.el.find("option")[0]);
+                    }
+                    if (selected.length > 0) {
+                        this.val({id: selected.attr("value"), text: selected.text()});
+                    }
+                }
+
+            },
+            val: function () {
+                var data;
+                if (arguments.length === 0) {
+                    return this.selection.val();
+                } else {
+                    data = arguments[0];
+                    this.results.val(data);
+                }
+            }
+        }
+    });
+
+    $.fn.select2 = function () {
+        var args = Array.prototype.slice.call(arguments, 0), value, tmp;
+        this.each(function () {
+            if (args.length === 0) {
+                tmp = new Select2({el: this});
+            } else if (typeof (args[0]) === "object") {
+                args[0].el = this;
+                tmp = new Select2(args[0]);
+            } else if (typeof (args[0]) === "string") {
+                var select2 = $(this).data("select2");
+                value = select2[args[0]].apply(select2, args.slice(1));
+                return false;
+            } else {
+                throw "Invalid arguments to select2 plugin: " + args;
+            }
+        });
+        return (value === undefined) ? this : value;
+    };
+
+}(jQuery));

BIN
select2.png


BIN
spinner.gif