From 56eb0ae10c3c3be4b9c61574f58496ffd6fad19b Mon Sep 17 00:00:00 2001 From: Igor Vaynberg Date: Sun, 25 Mar 2012 23:19:41 -0700 Subject: [PATCH] updated for changes in master --- index.html | 360 ++++++++---- js/jquery.mousewheel.js | 84 +++ select2-1.0.html | 585 +++++++++++++++++++ select2-master/select2.css | 361 ++++++++++++ select2-master/select2.js | 1120 ++++++++++++++++++++++++++++++++++++ select2-master/select2.png | Bin 0 -> 396 bytes select2-master/spinner.gif | Bin 0 -> 1849 bytes 7 files changed, 2397 insertions(+), 113 deletions(-) create mode 100755 js/jquery.mousewheel.js create mode 100755 select2-1.0.html create mode 100755 select2-master/select2.css create mode 100755 select2-master/select2.js create mode 100755 select2-master/select2.png create mode 100755 select2-master/spinner.gif diff --git a/index.html b/index.html index c11a8699..74c5b70f 100755 --- a/index.html +++ b/index.html @@ -9,11 +9,10 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+

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 library.

+

+ Learn more on GitHub» + Download» +

+
+ +
+
+

Examples

+
+
+
+ + +
+
+

The Basics

+

Select2 takes a select box like this:

+

+

and turns it into:

+

+
+

+
+
+

Example Code

+
+<head>
+    <link href="select2.css" rel="stylesheet"/>
+    <script src="select2.js"></script>
+    <script>
+        $(document).ready(function() { $("#e1").select2(); });
+    </script>
+</head>
+<body>
+    <select id="e1">
+        <option value="AL">Alabama</option>
+        ...
+        <option value="WY">Wyoming</option>
+    </select>
+</body>
+
+
+
+ +
+
+

Placeholders

+

A placeholder text can be defined that will be displayed until a selection is made:

+

+
+

+
+
+

Example Code

+
+            
+
+
+ +
+
+

Minimum Input

+

Select2 supports a minimum input setting which is useful for large remote datasets where short search terms are not very useful:

+

+
+

+
+
+

Example Code

+

+         
+
+ +
+
+

Templating

+

Various display options of the Select2 component can be changed:

+

+
+

+
+
+

Example Code

+

+         
+
+ +
+
+

Loading Data

+

Select2 uses a function to load result data. Here is a trivial example that creates choices that consist of user's input echoed a number of times:

+

+ +

+
+
+

Example Code

+
+            
+
+
+ +
+
+

Loading Remote Data

+

Select2 comes with AJAX/JSONP support built in. In this example we will search for a movie using Rotten Tomatoes API:

+

+ +

+

Example Code

+

+           
+
+
+
+

Infinite Scroll with Remote Data

+

Select2 supports lazy-appending of results when the result list is scrolled to the end. + In order to enable the remote service must support some sort of a paging mechanism and + the query function given to Select2 must take advantage of it. The following example demonstrates + how this can be set up. Search for some keyword and then scroll the result list to the end to + see more results load:

+

+ +

+

Example Code

+

+            
+
+ +
+
+

Pragmatic Access

+

Select2 supports methods that allow pragmatic control of the componentL

+

+
+

+

+ + +

+
+
+

Example Code

+

+         
+
+ +
+

Documentation


+
+ +
+

Constructor

+
+ + + + + + + + + + + + + + + + +
ParameterTypeDescription
minimumInputLengthintNumber of characters necessary to start a search
placeholderobjectInitial value that is selected if no other selection is made
formatSelectionfunction + Function used render the current selection. +
formatSelection(object)
+ + + + +
ParameterTypeDescription
objectobjectThe selected result object returned from the query function
<returns>stringHtml that represents the selection
+ The default implementation expects the object to have a text property that is returned. +
formatResultfunction + Function used to render a result that the user can select. +
formatResult(object)
+ + + + +
ParameterTypeDescription
objectobjectOne of the result objects returned from the query function
<returns>stringHtml that represents the result
+ The default implementation expects the object to have a text property that is returned. +
formatNoMatchesfunction + Function used to render the "No matches" message +
formatNoMatches(term)
+ + + + +
ParameterTypeDescription
termstringSearch string entered by user
<returns>stringMessage html
+
formatInputTooShortfunction + Function used to render the "Search input too short" message +
formatInputTooShort(term, minLength)
+ + + + + +
ParameterTypeDescription
termstringSearch string entered by user
minLengthintMinimum required term length
<returns>stringMessage html
+
queryfunction/string + Function used to query results for the search term. +
query(options)
+ + + + + + +
ParameterTypeDescription
options.termstringSearch string entered by user
options.varsobjectObject from the previous result, see options.callback
options.callbackfunctionCallback function that should be called with the result object. The results object: + + + + + +
ParameterTypeDescription
result.results[object]Array of result objects. The default renderers expect objects with id and text keys. The id attribute is required, even if custom renderers are used.
result.varsobjectA user-defined object that will be passed into the next invocation of query if more results need to be loaded
result.morebooleantrueif more results are available for the current search term
+
+
+ "ajax" - use the built in ajax query function. This function will handle throttling and dropping out-of-order responses. +
ajaxobject + Options for the built in ajax query function. + + + + + + + + +
ParameterTypeDescription
urlstringAjax url
dataTypestringtData type for the request. ajax, jsonp, other formats supported by jquery
quietMillisintNumber of milliseconds to wait for the user to stop typing before issuing the ajax request
datafunction + Function to generate query parameters for the ajax request. +
data(term, vars)
+ + + + + +
ParameterTypeDescription
termstringSearch term
varsobjectThe vars object from the previous search's result
<returns>objectObject containing url paramters
+
resultsfunction + Function used to build the query results object from the ajax response +
results(term, vars)
+ + + + + +
ParameterTypeDescription
termstringSearch term
varsobjectThe vars object from the previous search's result
<returns>objectResults object. See "options.callback" in the "query" function for format.
+
+
+ +
+

val

+
+

Gets or sets the selection. If the value parameter is not specified, the id attribute of the currently selected element is returned. If the value parameter is specified it will become the current selection.

+ + + + + +
ParameterTypeDescription
value (optional)objectResult object for the new selection, or the id attribute of a loaded result object
+ Example:
alert("Selected value is: "+$("#e8").select2("val")); $("#e8").select2("val", {id:"CA", text:"Califoria"});
+ +
+

Events

+
+ +
+

opened

+
+

Signalled when the search dropdown has been opened

+
+

closed

+
+

Signalled when the search dropdown has been closed

+
+

cancelled

+
+

Signalled when the user cancells the selection of a result. For example, when ESC is pressed after the dropdown has been opened

+
+

selected

+
+

Signals a selection has been made

+ + +
+

Community

+
+ + + +
+ + + + + +
+ + diff --git a/select2-master/select2.css b/select2-master/select2.css new file mode 100755 index 00000000..566ea513 --- /dev/null +++ b/select2-master/select2.css @@ -0,0 +1,361 @@ +.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-choice abbr { + display: block; + position: absolute; + right: 26px; + top: 8px; + width: 12px; + height: 12px; + font-size: 1px; + background: url(select2.png) right top no-repeat; + cursor: pointer; + text-decoration: none; + border:0; + outline: 0; +} +.select2-container .select2-choice abbr:hover { + background-position: right -11px; + cursor: pointer; +} + +.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; +} + + +.select2-container-active .select2-choice, +.select2-container-active .select2-choices { + -webkit-box-shadow: none; + -moz-box-shadow: none; + box-shadow: none; + outline: thin dotted #333; + outline: 5px auto -webkit-focus-ring-color; + outline-offset: -2px; + +} + +.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-container .select2-results .select2-disabled.select2-highlighted { + color: #666; + background: #f4f4f4; + display: list-item; + cursor: default; +} +.select2-container .select2-results .select2-disabled { + background: #f4f4f4; + display: list-item; + cursor: default; +} + + +.select2-more-results.select2-active { + background: #f4f4f4 url('spinner.gif') no-repeat 100%; +} + +.select2-more-results { + background: #f4f4f4; + display: list-item; +} + +/* multiselect */ + +.select2-container-multi .select2-choices { + background-color: #fff; + background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(1%, #eeeeee), color-stop(15%, #ffffff)); + background-image: -webkit-linear-gradient(top, #eeeeee 1%, #ffffff 15%); + background-image: -moz-linear-gradient(top, #eeeeee 1%, #ffffff 15%); + background-image: -o-linear-gradient(top, #eeeeee 1%, #ffffff 15%); + background-image: -ms-linear-gradient(top, #eeeeee 1%, #ffffff 15%); + background-image: linear-gradient(top, #eeeeee 1%, #ffffff 15%); + border: 1px solid #aaa; + margin: 0; + padding: 0; + cursor: text; + overflow: hidden; + height: auto !important; + height: 1%; + position: relative; +} +.select2-container-multi .select2-choices li { + float: left; + list-style: none; +} +.select2-container-multi .select2-choices .select2-search-field { + white-space: nowrap; + margin: 0; + padding: 0; +} + +.select2-container-multi .select2-choices .select2-search-field input { + color: #666; + background: transparent !important; + font-family: sans-serif; + font-size: 100%; + height: 15px; + padding: 5px; + margin: 1px 0; + outline: 0; + border: 0; + -webkit-box-shadow: none; + -moz-box-shadow : none; + -o-box-shadow : none; + box-shadow : none; +} + + +.select2-default { + color: #999 !important; +} + +.select2-container-multi .select2-choices .select2-search-choice { + -webkit-border-radius: 3px; + -moz-border-radius : 3px; + border-radius : 3px; + -moz-background-clip : padding; + -webkit-background-clip: padding-box; + background-clip : padding-box; + background-color: #e4e4e4; + filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#f4f4f4', endColorstr='#eeeeee', GradientType=0 ); + background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(20%, #f4f4f4), color-stop(50%, #f0f0f0), color-stop(52%, #e8e8e8), color-stop(100%, #eeeeee)); + background-image: -webkit-linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%); + background-image: -moz-linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%); + background-image: -o-linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%); + background-image: -ms-linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%); + background-image: linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%); + -webkit-box-shadow: 0 0 2px #ffffff inset, 0 1px 0 rgba(0,0,0,0.05); + -moz-box-shadow : 0 0 2px #ffffff inset, 0 1px 0 rgba(0,0,0,0.05); + box-shadow : 0 0 2px #ffffff inset, 0 1px 0 rgba(0,0,0,0.05); + color: #333; + border: 1px solid #aaaaaa; + line-height: 13px; + padding: 3px 20px 3px 5px; + margin: 3px 0 3px 5px; + position: relative; + cursor: default; +} +.select2-container-multi .select2-choices .select2-search-choice span { + cursor: default; +} +.select2-container-multi .select2-choices .select2-search-choice-focus { + background: #d4d4d4; +} +.select2-search-choice-close { + display: block; + position: absolute; + right: 3px; + top: 4px; + width: 12px; + height: 13px; + font-size: 1px; + background: url(select2.png) right top no-repeat; +} +.select2-container-multi .select2-choices .select2-search-choice .select2-search-choice-close:hover { + background-position: right -11px; +} +.select2-container-multi .select2-choices .select2-search-choice-focus .select2-search-choice-close { + background-position: right -11px; +} + + +.select2-container-multi .select2-results { + margin: -1px 0 0; + padding: 0; +} + +/* end multiselect */ diff --git a/select2-master/select2.js b/select2-master/select2.js new file mode 100755 index 00000000..cc162fa8 --- /dev/null +++ b/select2-master/select2.js @@ -0,0 +1,1120 @@ +/* + 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 uid = 0, KEY; + + 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, + isArrow: function (k) { + k = k.which ? k.which : k; + switch (k) { + case KEY.LEFT: + case KEY.RIGHT: + case KEY.UP: + case KEY.DOWN: + return true; + } + return false; + }, + isControl: function (k) { + k = k.which ? k.which : k; + switch (k) { + case KEY.SHIFT: + case KEY.CTRL: + case KEY.ALT: + return true; + } + return false; + }, + isFunctionKey: function (k) { + k = k.which ? k.which : k; + return k >= 112 && k <= 123; + } + }; + + function getSideBorderPadding(element) { + return element.outerWidth() - element.width(); + } + + function installKeyUpChangeEvent(element) { + element.on("keydown", function () { + element.data("keyup-change-value", element.val()); + }); + element.on("keyup", function () { + if (element.val() !== element.data("keyup-change-value")) { + element.trigger("keyup-change"); + } + }); + } + + /** + * 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. + */ + $(document).on("mousemove", function (e) { + $(this).data("select2-lastpos", {x: e.pageX, y: e.pageY}); + }); + function installFilteredMouseMove(element) { + var doc = $(document); + element.on("mousemove", function (e) { + var lastpos = doc.data("select2-lastpos"); + + if (lastpos === undefined || lastpos.x !== e.pageX || lastpos.y !== e.pageY) { + $(e.target).trigger("mousemove-filtered", e); + } + }); + } + + function debounce(threshold, fn) { + var timeout; + return function () { + window.clearTimeout(timeout); + timeout = window.setTimeout(fn, threshold); + }; + } + + function installDebouncedScroll(threshold, element) { + var notify = debounce(threshold, function (e) { element.trigger("scroll-debounced", e);}); + element.on("scroll", function (e) { + if (element.get().indexOf(e.target) >= 0) notify(e); + }); + } + + function killEvent(event) { + event.preventDefault(); + event.stopPropagation(); + } + + function measureTextWidth(e) { + var sizer, width; + sizer = $("
").css({ + position: "absolute", + left: "-1000px", + top: "-1000px", + display: "none", + fontSize: e.css("fontSize"), + fontFamily: e.css("fontFamily"), + fontStyle: e.css("fontStyle"), + fontWeight: e.css("fontWeight"), + letterSpacing: e.css("letterSpacing"), + textTransform: e.css("textTransform"), + whiteSpace: "nowrap" + }); + sizer.text(e.val()); + $("body").append(sizer); + width = sizer.width(); + sizer.remove(); + return width; + } + + /** + * + * @param opts + */ + function AbstractSelect2() { + } + + AbstractSelect2.prototype.bind = function (func) { + var self = this; + return function () { + func.apply(self, arguments); + }; + }; + + AbstractSelect2.prototype.init = function (opts) { + var results, search; + + this.uid = uid; + uid = uid + 1; + + // prepare options + this.opts = this.prepareOpts(opts); + + this.container = this.createContainer(); + + if (opts.element.attr("class") !== undefined) { + this.container.addClass(opts.element.attr("class")); + } + + // 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(".select2-results"); + this.search = search = this.container.find("input[type=text]"); + // initialize the container + + this.resultsPage = 0; + + this.initContainer(); + + installFilteredMouseMove(this.results); + results.on("mousemove-filtered", this.bind(this.highlightUnderEvent)); + + installDebouncedScroll(80, this.results); + results.on("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); + } + }); + } + + installKeyUpChangeEvent(search); + search.on("keyup-change", this.bind(this.updateResults)); + + results.on("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(); + } + })); + }; + + AbstractSelect2.prototype.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"; } + }, opts); + + element = 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); + } + + 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 (text.toUpperCase().indexOf(term) >= 0) { + data.results.push({id: e.attr("value"), text: text}); + } + }); + query.callback(data); + }); + } else { + if (!("query" in opts) && opts.ajax) { + opts.query = (function () { + var timeout, // current scheduled but not yet executed request + requestSequence = 0, // sequence used to drop out-of-order responses + quietMillis = opts.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 = opts.ajax, // ajax parameters + data = options.data; // ajax data function + + data = data.call(this, query.term, query.page); + + $.ajax({ + url: options.url, + dataType: options.dataType, + data: data + }).success( + function (data) { + if (requestNumber < requestSequence) { + return; + } + query.callback(options.results(data, query.page)); + } + ); + }, quietMillis); + }; + }()); + } + } + if (typeof(opts.query) !== "function") { + throw "query function not defined for Select2 " + opts.element.attr("id"); + } + + return opts; + }; + + AbstractSelect2.prototype.opened = function () { + return this.container.hasClass("select2-dropdown-open"); + }; + + AbstractSelect2.prototype.alignDropdown = function () { + this.dropdown.css({ + top: this.container.height(), + width: this.container.outerWidth() - getSideBorderPadding(this.dropdown) + }); + }; + + AbstractSelect2.prototype.open = function () { + var width; + + if (this.opened()) return; + + this.container.addClass("select2-dropdown-open").addClass("select2-container-active"); + + this.alignDropdown(); + this.dropdown.show(); + + // register click-outside-closes-dropdown listener + $(document).on("click.id" + this.uid, this.bind(function (e) { + if ($(e.target).closest(this.container).length === 0) { + this.blur(); + } + })); + }; + + AbstractSelect2.prototype.close = function () { + if (!this.opened()) return; + + this.dropdown.hide(); + this.container.removeClass("select2-dropdown-open"); + $(document).off("click.id" + this.uid); + + if (this.select) { + // TODO see if we can always clear here and reset on open + this.search.val(""); // not using clearSearch() because it may set a placeholder + this.updateResults(); // needed since we just set the search text to "" + } else { + this.results.empty(); + } + + this.clearSearch(); + }; + + AbstractSelect2.prototype.clearSearch = function () { + + }; + + AbstractSelect2.prototype.ensureHighlightVisible = function () { + var results = this.results, children, index, child, hb, rb, y, more; + + children = results.children(".select2-result"); + index = this.highlight(); + + if (index < 0) return; + + child = $(children[index]); + + 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 = results.find("li.select2-more-results"); + if (more.length > 0) { + hb = more.offset().top + more.outerHeight(); + } + } + + rb = results.offset().top + results.outerHeight(); + if (hb > rb) { + results.scrollTop(results.scrollTop() + (hb - rb)); + } + y = child.offset().top - results.offset().top; + + // make sure the top of the element is visible + if (y < 0) { + results.scrollTop(results.scrollTop() + y); // y is negative + } + }; + + AbstractSelect2.prototype.moveHighlight = function (delta) { + var choices = this.results.children(".select2-result"), + index = this.highlight(); + + while (index > -1 && index < choices.length) { + index += delta; + if (!$(choices[index]).hasClass("select2-disabled")) { + this.highlight(index); + break; + } + } + }; + + AbstractSelect2.prototype.highlight = function (index) { + var choices = this.results.children(".select2-result"); + + if (arguments.length === 0) { + return choices.get().indexOf(choices.filter(".select2-highlighted")[0]); + } + + choices.removeClass("select2-highlighted"); + + if (index >= choices.length) index = choices.length - 1; + if (index < 0) index = 0; + + $(choices[index]).addClass("select2-highlighted"); + this.ensureHighlightVisible(); + + if (this.opened()) this.focusSearch(); + }; + + AbstractSelect2.prototype.highlightUnderEvent = function (event) { + var el = $(event.target).closest(".select2-result"); + if (el.length > 0) { + this.highlight(el.index()); + } + }; + + 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; + + 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("
  • "); + parts.push(self.opts.formatResult(this)); + parts.push("
  • "); + }); + 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 { + more.remove(); + } + this.resultsPage = page; + })}); + } + }; + + AbstractSelect2.prototype.updateResults = function () { + var search = this.search, results = this.results, opts = this.opts; + + search.addClass("select2-active"); + + function render(html) { + results.html(html); + results.scrollTop(0); + search.removeClass("select2-active"); + } + + if (search.val().length < opts.minimumInputLength) { + render("
  • " + opts.formatInputTooShort(search.val(), opts.minimumInputLength) + "
  • "); + return; + } + + this.resultsPage = 1; + opts.query({term: search.val(), page: this.resultsPage, callback: this.bind(function (data) { + var parts = []; // html parts + + if (data.results.length === 0) { + render("
  • " + opts.formatNoMatches(search.val()) + "
  • "); + return; + } + + $(data.results).each(function () { + parts.push("
  • "); + parts.push(opts.formatResult(this)); + parts.push("
  • "); + }); + + if (data.more === true) { + parts.push("
  • Loading more results...
  • "); + } + + render(parts.join("")); + results.children(".select2-result").each(function (i) { + var d = data.results[i]; + $(this).data("select2-data", d); + }); + this.postprocessResults(); + })}); + }; + + AbstractSelect2.prototype.cancel = function () { + this.close(); + }; + + 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 () { + 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); + }; + + AbstractSelect2.prototype.selectHighlighted = function () { + var data = this.results.find(".select2-highlighted:not(.select2-disabled)").data("select2-data"); + if (data) { + this.onSelect(data); + } + }; + + AbstractSelect2.prototype.getPlaceholder = function () { + var placeholder = this.opts.element.data("placeholder"); + if (placeholder !== undefined) return placeholder; + return this.opts.placeholder; + }; + + function SingleSelect2() { + } + + SingleSelect2.prototype = new AbstractSelect2(); + SingleSelect2.prototype.constructor = SingleSelect2; + SingleSelect2.prototype.parent = AbstractSelect2.prototype; + + SingleSelect2.prototype.createContainer = function () { + return $("
    ", { + "class": "select2-container", + "style": "width: " + this.opts.element.outerWidth() + "px" + }).html([ + " ", + " ", + "
    " , + "
    ", + " "].join("")); + }; + + SingleSelect2.prototype.open = function () { + + var width; + + if (this.opened()) return; + + this.parent.open.apply(this, arguments); + + // size the search field + + width = this.dropdown.width(); + width -= getSideBorderPadding(this.container.find(".select2-search")); + width -= getSideBorderPadding(this.search); + this.search.css({width: width}); + + if (!this.select) this.updateResults(); + + this.ensureHighlightVisible(); + + this.focusSearch(); + }; + + SingleSelect2.prototype.close = function () { + if (!this.opened()) return; + this.parent.close.apply(this, arguments); + }; + + SingleSelect2.prototype.cancel = function () { + this.parent.cancel.apply(this, arguments); + this.selection.focus(); + }; + + SingleSelect2.prototype.initContainer = function () { + + var selection, container = this.container, clickingInside = false, + selected; + + this.selection = selection = container.find(".select2-choice"); + + this.search.on("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; + } + })); + + selection.on("click", this.bind(function (e) { + clickingInside = true; + + if (this.opened()) { + this.close(); + selection.focus(); + } else { + this.open(); + } + e.preventDefault(); + + clickingInside = false; + })); + selection.on("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); + } + })); + selection.on("focus", function () { container.addClass("select2-container-active"); }); + selection.on("blur", this.bind(function () { + if (clickingInside) return; + if (!this.opened()) this.blur(); + })); + + selection.find("abbr") + .on("click", this.bind(function (e) { + this.val(""); + killEvent(e); + this.close(); + } + )); + + if (this.select) { + selected = this.select.find(":selected"); + this.updateSelection({id: selected.attr("value"), text: selected.text()}); + + // preload all results + this.updateResults(); + } + + this.setPlaceholder(); + }; + + SingleSelect2.prototype.setPlaceholder = function () { + var placeholder = this.getPlaceholder(); + + 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; + + if (typeof(placeholder) === "object") { + this.updateSelection(placeholder); + } else { + this.selection.find("span").html(placeholder); + } + this.selection.addClass("select2-default"); + + this.selection.find("abbr").hide(); + } + }; + + SingleSelect2.prototype.postprocessResults = function () { + var selected = 0, self = this; + this.results.find(".select2-result").each(function (i) { + if ($(this).data("select2-data").id === self.opts.element.val()) { + selected = i; + return false; + } + }); + this.highlight(selected); + }; + + SingleSelect2.prototype.onSelect = function (data) { + this.opts.element.val(data.id); + this.updateSelection(data); + this.close(); + this.selection.focus(); + }; + + SingleSelect2.prototype.updateSelection = function (data) { + this.selection.find("span").html(this.opts.formatSelection(data)); + this.selection.removeClass("select2-default"); + if (this.opts.allowClear && this.getPlaceholder() !== undefined) { + this.selection.find("abbr").show(); + } + }; + + SingleSelect2.prototype.val = function () { + var val, data = null; + + if (arguments.length === 0) { + return this.opts.element.val(); + } + + val = arguments[0]; + + if (this.select) { + // val is an id + this.select.val(val); + this.select.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(); + + }; + + SingleSelect2.prototype.clearSearch = function () { + this.search.val(""); + }; + + function MultiSelect2(opts) { + + } + + MultiSelect2.prototype = new AbstractSelect2(); + MultiSelect2.prototype.constructor = AbstractSelect2; + MultiSelect2.prototype.parent = AbstractSelect2.prototype; + + MultiSelect2.prototype.createContainer = function () { + return $("
    ", { + "class": "select2-container select2-container-multi", + "style": "width: " + this.opts.element.outerWidth() + "px" + }).html([ + " " , + ""].join("")); + }; + + MultiSelect2.prototype.initContainer = function () { + + var selection, data; + + this.searchContainer = this.container.find(".select2-search-field"); + this.selection = selection = this.container.find(".select2-choices"); + + this.search.on("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; + } + + 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"); + } + + 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; + } + } + + if (e.which === KEY.TAB) { + this.blur(); + return; + } + + if (e.which === KEY.TAB || KEY.isControl(e) || KEY.isFunctionKey(e) || e.which === KEY.BACKSPACE || e.which === KEY.ESC) { + return; + } + + this.open(); + + if (e.which === KEY.PAGE_UP || e.which === KEY.PAGE_DOWN) { + // prevent the page from scrolling + killEvent(e); + } + })); + + this.search.on("keyup", this.bind(this.resizeSearch)); + + this.selection.on("click", this.bind(function (e) { + if (this.select) { + this.open(); + } + this.focusSearch(); + e.preventDefault(); + })); + + this.search.on("focus", this.bind(function () { + this.container.addClass("select2-container-active"); + this.clearPlaceholder(); + })); + + if (this.select) { + data = []; + this.select.find(":selected").each(function () { + data.push({id: $(this).attr("value"), text: $(this).text()}); + }); + + this.updateSelection(data); + // preload all results + this.updateResults(); + } + + // set the placeholder if necessary + this.clearSearch(); + }; + + MultiSelect2.prototype.clearSearch = function () { + var placeholder = this.getPlaceholder(); + + this.search.val("").width(10); + + if (placeholder !== undefined && this.getVal().length === 0) { + this.search.val(placeholder).addClass("select2-default"); + this.resizeSearch(); + } + }; + + MultiSelect2.prototype.clearPlaceholder = function () { + if (this.search.hasClass("select2-default")) { + this.search.val("").removeClass("select2-default"); + } + }; + + MultiSelect2.prototype.open = function () { + if (this.opened()) return; + this.parent.open.apply(this, arguments); + this.resizeSearch(); + this.focusSearch(); + }; + + MultiSelect2.prototype.close = function () { + if (!this.opened()) return; + this.parent.close.apply(this, arguments); + }; + + MultiSelect2.prototype.updateSelection = function (data) { + var self = this; + this.selection.find(".select2-search-choice").remove(); + $(data).each(function () { + self.addSelectedChoice(this); + }); + self.postprocessResults(); + this.alignDropdown(); + }; + + MultiSelect2.prototype.onSelect = function (data) { + this.addSelectedChoice(data); + if (this.select) { this.postprocessResults(); } + this.close(); + this.search.width(10); + this.focusSearch(); + }; + + MultiSelect2.prototype.cancel = function () { + this.close(); + this.focusSearch(); + }; + + MultiSelect2.prototype.addSelectedChoice = function (data) { + var choice, + id = data.id, + parts, + val = this.getVal(); + + parts = ["
  • ", + this.opts.formatSelection(data), + "", + "
  • " + ]; + + choice = $(parts.join("")); + choice.find("a") + .on("click", 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(); + })).on("focus", this.bind(function () { + this.container.addClass("select2-container-active"); + })).on("blur", this.bind(function () { + this.blur(); + })); + + choice.data("select2-data", data); + choice.insertBefore(this.searchContainer); + + val.push(id); + this.setVal(val); + }; + + MultiSelect2.prototype.unselect = function (selected) { + var val = this.getVal(), + index; + + selected = selected.closest(".select2-search-choice"); + + if (selected.length === 0) { + throw "Invalid argument: " + selected + ". Must be .select2-search-choice"; + } + + index = val.indexOf(selected.data("select2-data").id); + + if (index >= 0) { + val.splice(index, 1); + this.setVal(val); + if (this.select) this.postprocessResults(); + } + selected.remove(); + window.setTimeout(this.bind(this.alignDropdown), 20); + }; + + MultiSelect2.prototype.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 (val.indexOf(id) >= 0) { + choice.addClass("select2-disabled"); + } else { + choice.removeClass("select2-disabled"); + } + }); + + choices.each(function (i) { + if (!$(this).hasClass("select2-disabled")) { + self.highlight(i); + return false; + } + }); + + }; + + MultiSelect2.prototype.resizeSearch = function () { + + var minimumWidth, left, maxWidth, containerLeft, searchWidth; + + minimumWidth = measureTextWidth(this.search) + 10; + + left = this.search.offset().left; + + maxWidth = this.selection.width(); + containerLeft = this.selection.offset().left; + + searchWidth = maxWidth - (left - containerLeft) - getSideBorderPadding(this.search); + + if (searchWidth < minimumWidth) { + searchWidth = maxWidth - getSideBorderPadding(this.search); + } + + if (searchWidth < 40) { + searchWidth = maxWidth - getSideBorderPadding(this.search); + } + this.search.width(searchWidth); + }; + + 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(","); + } + }; + + MultiSelect2.prototype.setVal = function (val) { + if (this.select) { + this.select.val(val); + } else { + this.opts.element.val(val.length === 0 ? "" : val.join(",")); + } + }; + + MultiSelect2.prototype.val = function () { + var val, data = []; + + if (arguments.length === 0) { + return this.getVal(); + } + + 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 () { + + var args = Array.prototype.slice.call(arguments, 0), + opts, + select2, + value, multiple, allowedMethods = ["val"]; + + this.each(function () { + if (args.length === 0 || typeof(args[0]) === "object") { + opts = args.length === 0 ? {} : args[0]; + opts.element = $(this); + + if (opts.element.get(0).tagName.toLowerCase() === "select") { + multiple = opts.element.prop("multiple"); + } else { + multiple = opts.multiple || false; + } + + select2 = multiple ? new MultiSelect2() : new SingleSelect2(); + select2.init(opts); + } else if (typeof(args[0]) === "string") { + + if (allowedMethods.indexOf(args[0]) < 0) { + throw "Unknown method: " + args[0]; + } + + value = undefined; + select2 = $(this).data("select2"); + value = select2[args[0]].apply(select2, args.slice(1)); + if (value !== undefined) {return false;} + } else { + throw "Invalid arguments to select2 plugin: " + args; + } + }); + return (value === undefined) ? this : value; + }; + +}(jQuery)); diff --git a/select2-master/select2.png b/select2-master/select2.png new file mode 100755 index 0000000000000000000000000000000000000000..d08e4b7e624c44f4fb862f23f046262780847490 GIT binary patch literal 396 zcmV;70dxL|P)~c7VWO z{Q}IDn{i!TF4#od;X3Q_#kIK3wRl==hwEDRz~*jyTo-K>u-u(nATT<+akgC{FyDRG qFW$B*q*ia+6;i9W9%5_PVDkwXFTX6NOX?H=0000v)e5ZBQx4|Y-Q?nr@Px3?9h(3ZWr3^tj=`TP57gKr87N$ zp2wWee1GRRCwo_xahnw)5cxNPJbCg2L6DV|6`#+yw6v6!mDS$f9-JvFD^n;GQ&UrZ zzh5jCkByB101O60U0q#p_1BM>Cv-vP?&s4@g_((4_1L=L$(a91)0=J91Gas#R{McE znYG^9*0A5YZ>#;~+Wkn(W5B0^yELIYLP!K}mB~<)AM@1&nqekynuaEGqPrzoH|KodRXJy)%+w_fu3nE5>@Bd_b zqC$EQ;{c`T&?EsNO|igL9gC7Ygxv?aQUEXMq?~>wg{EyW;VcJ37CUF#HjrT=KQO_* zS>M9yydXk18D(+QDJ1>r);Lav_uYKp$T?4vr{Q$lTo&pKv^?(>L-)G2*lwH!Ah7k? z7oH<8h-(KTKt5V6$8gF)C7Io&P5=SjTh)=zV=E2EUhQZP##L8S{d%UK>>+y82>+FV+#^BzW7u3F)Bb>=lYQ%%j`F>ASe zo*cw@V#u6T`A2He;70mR(V&iV&-7{qP~=SRf&jm9-T{*ZeZ}$rd0#6c&fLG^xJcf5 z+p<`wJYgW+_s*V{uI$nMB;%8`S_3>PfGOj3Rq}@Cx^+j?rk92fANSFDBYnOqQ>Vdj z)(|$AhP4t&Lb=Gvo2#3Gl%9<=Gv`Mz?Po@P4iLF!x}GUWJICDlFk-hS^Whyh7x~VH z@0vD1>HYD4&e+~yzS*-sFR{9`{QEEZO1zg7>R&7cHts-6j!xHVdA8eI+ZlVzd%`es zJT@$#GX(gvCJ1oJN%yLBK}{V=V;seo;!w|Yte!W1%5qLNFWqvZW>h&IiH+oPT=b@E zPhGzv5=(Un*X>v`>%8h_nj^NdYcE6NHS_ifkCV$*D)Tqrbu`s;<=t<4 zAHNqNV?6(g<1PY-w@#I-WYFViz?9TrkMr)u0g`O`u|>T;k|2sV*YF^punvT;$SuTy{j3Gv)yqD!R_CF>yR)MzmmYS5v+~R zXAdD%ng9?df;wd8GxR#%3O+gz};Vo;)sK%Bj-q>Oq%R7JU-KD?vYu>#2UjaDo z&8$>5xW~?KPD_#XFToU1hIb*VOMidUr6iYiO0N|i-7s`T8!cFT`rN!^1Pt78J93i6 z5HI1wIM$94m{3SLDvISDe6$ZG1;eq_D9RTaaC>=cO{@Bs>$IlPCPJJ$h$)-3vzNUQ6OsN#_zWxey!_9%hxwH2_dEJi=yY|1c7nDm2_Lm!Cof8-R_+9UkS zcBE(o47yE)oMR(Q=dp1a2wTX5KvvGyLqlWTa7V&!A*|w|)ax~1_~aJ0=_Lilg*0iQk7#ZD EAHN$8j{pDw literal 0 HcmV?d00001