Вход Регистрация
Файл: plugins/searchsys/static/select2.js
Строк: 2766
<?php
(function ($) {
    if(
typeof $.fn.each2 == "undefined"){
        $.
fn.extend({
            
/*
             * 4-10 times faster .each replacement
             * use it carefully, as it overrides jQuery context of element on each iteration
             */
            
each2 : function (c) {
                var 
= $([0]), = -1this.length;
                while (
                    ++
l
                        
&& (j.context j[0] = this[i])
                        && 
c.call(j[0], ij) !== false //"this"=DOM, i=index, j=jQuery object
                    
);
                return 
this;
            }
        });
    }
})(
jQuery);

(function ($, 
undefined) {
    
"use strict";
    
/*global document, window, jQuery, console */

    
if (window.OCSEsel2 !== undefined) {
        return;
    }

    var 
KEYAbstractEsel2SingleEsel2MultiEsel2nextUidsizer;

    
KEY = {
        
TAB9,
        
ENTER13,
        
ESC27,
        
SPACE32,
        
LEFT37,
        
UP38,
        
RIGHT39,
        
DOWN40,
        
SHIFT16,
        
CTRL17,
        
ALT18,
        
PAGE_UP33,
        
PAGE_DOWN34,
        
HOME36,
        
END35,
        
BACKSPACE8,
        
DELETE46,
        
isArrow: function (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 (e) {
            var 
e.which;
            switch (
k) {
                case 
KEY.SHIFT:
                case 
KEY.CTRL:
                case 
KEY.ALT:
                    return 
true;
            }

            if (
e.metaKey) return true;

            return 
false;
        },
        
isFunctionKey: function (k) {
            
k.which k.which k;
            return 
>= 112 && <= 123;
        }
    };

    
nextUid=(function() { var counter=1; return function() { return counter++; }; }());

    function 
indexOf(value, array) {
        var 
0= array.lengthv;

        if (
typeof value === "undefined") {
            return -
1;
        }

        if (
value.constructor === String) {
            for (; 
l1) if (value.localeCompare(array[i]) === 0) return i;
        } else {
            for (; 
l1) {
                
= array[i];
                if (
v.constructor === String) {
                    if (
v.localeCompare(value) === 0) return i;
                } else {
                    if (
=== value) return i;
                }
            }
        }
        return -
1;
    }

    
/**
     * Compares equality of a and b taking into account that a and b may be strings, in which case localeCompare is used
     * @param a
     * @param b
     */
    
function equal(ab) {
        if (
=== b) return true;
        if (
=== undefined || === undefined) return false;
        if (
=== null || === null) return false;
        if (
a.constructor === String) return a.localeCompare(b) === 0;
        if (
b.constructor === String) return b.localeCompare(a) === 0;
        return 
false;
    }

    
/**
     * Splits the string into an array of values, trimming each value. An empty array is returned for nulls or empty
     * strings
     * @param string
     * @param separator
     */
    
function splitVal(stringseparator) {
        var 
valil;
        if (
string === null || string.length 1) return [];
        
val string.split(separator);
        for (
0val.lengthl1val[i] = $.trim(val[i]);
        return 
val;
    }

    function 
getSideBorderPadding(element) {
        return 
element.outerWidth() - element.width();
    }

    function 
installKeyUpChangeEvent(element) {
        var 
key="keyup-change-value";
        
element.bind("keydown", function () {
            if ($.
data(elementkey) === undefined) {
                $.
data(elementkeyelement.val());
            }
        });
        
element.bind("keyup", function () {
            var 
val= $.data(elementkey);
            if (
val !== undefined && element.val() !== val) {
                $.
removeData(elementkey);
                
element.trigger("keyup-change");
            }
        });
    }

    $(
window).bind("resize", function(e) {
        var 
dropdown=$("#esel2-drop");
        if (
dropdown.length>0) {
            
// there is an open dropdown
            // adjust dropdown positioning so it sizes with the content
            
dropdown.data("esel2").positionDropdown();
        }
    });
    $(
window).scroll(function(e) {

        var 
dropdown=$("#esel2-drop");
        if (
dropdown.length>0) {
            
// there is an open dropdown

            // adjust dropdown positioning so it scrolls with the content
            
dropdown.data("esel2").positionDropdown();
        }
    });

    $(
document).delegate("body""mousemove", function (e) {
        $.
data(document"esel2-lastpos", {xe.pageXye.pageY});
    });

    
/**
     * 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.
     */
    
function installFilteredMouseMove(element) {
        
element.bind("mousemove", function (e) {
            var 
lastpos = $.data(document"esel2-lastpos");
            if (
lastpos === undefined || lastpos.!== e.pageX || lastpos.!== e.pageY) {
                $(
e.target).trigger("mousemove-filtered"e);
            }
        });
    }

    
/**
     * Debounces a function. Returns a function that calls the original fn function only if no invocations have been made
     * within the last quietMillis milliseconds.
     *
     * @param quietMillis number of milliseconds to wait before invoking fn
     * @param fn function to be debounced
     * @param ctx object to be used as this reference within fn
     * @return debounced version of fn
     */
    
function debounce(quietMillisfnctx) {
        
ctx ctx || undefined;
        var 
timeout;
        return function () {
            var 
args arguments;
            
window.clearTimeout(timeout);
            
timeout window.setTimeout(function() {
                
fn.apply(ctxargs);
            }, 
quietMillis);
        };
    }

    
/**
     * A simple implementation of a thunk
     * @param formula function used to lazily initialize the thunk
     * @return {Function}
     */
    
function thunk(formula) {
        var 
evaluated false,
            
value;
        return function() {
            if (
evaluated === false) { value formula(); evaluated true; }
            return 
value;
        };
    };

    function 
installDebouncedScroll(thresholdelement) {
        var 
notify debounce(threshold, function (e) { element.trigger("scroll-debounced"e);});
        
element.bind("scroll", function (e) {
            if (
indexOf(e.targetelement.get()) >= 0notify(e);
        });
    }

    function 
killEvent(event) {
        
event.preventDefault();
        
event.stopPropagation();
    }

    function 
measureTextWidth(e) {
        if (!
sizer){
            var 
style e[0].currentStyle || window.getComputedStyle(e[0], null);
            
sizer = $("<div></div>").css({
                
position"absolute",
                
left"-10000px",
                
top"-10000px",
                
display"none",
                
fontSizestyle.fontSize,
                
fontFamilystyle.fontFamily,
                
fontStylestyle.fontStyle,
                
fontWeightstyle.fontWeight,
                
letterSpacingstyle.letterSpacing,
                
textTransformstyle.textTransform,
                
whiteSpace"nowrap"
            
});
            
sizer.attr("class","esel2-sizer");
            $(
"body").append(sizer);
        }
        
sizer.text(e.val());
        return 
sizer.width();
    }

    function 
markMatch(texttermmarkup) {
        var 
match=text.toUpperCase().indexOf(term.toUpperCase()),
            
tl=term.length;

        if (
match<0) {
            
markup.push(text);
            return;
        }

        
markup.push(text.substring(0match));
        
markup.push("<span class='esel2-match'>");
        
markup.push(text.substring(matchmatch tl));
        
markup.push("</span>");
        
markup.push(text.substring(match tltext.length));
    }

    
/**
     * Produces an ajax-based query function
     *
     * @param options object containing configuration paramters
     * @param options.transport function that will be used to execute the ajax request. must be compatible with parameters supported by $.ajax
     * @param options.url url for the data
     * @param options.data a function(searchTerm, pageNumber, context) that should return an object containing query string parameters for the above url.
     * @param options.dataType request data type: ajax, jsonp, other datatatypes supported by jQuery's $.ajax function or the transport function if specified
     * @param options.traditional a boolean flag that should be true if you wish to use the traditional style of param serialization for the ajax request
     * @param options.quietMillis (optional) milliseconds to wait before making the ajaxRequest, helps debounce the ajax function if invoked too often
     * @param options.results a function(remoteData, pageNumber) that converts data returned form the remote request to the format expected by Esel2.
     *      The expected format is an object containing the following keys:
     *      results array of objects that will be used as choices
     *      more (optional) boolean indicating whether there are more results available
     *      Example: {results:[{id:1, text:'Red'},{id:2, text:'Blue'}], more:true}
     */
    
function ajax(options) {
        var 
timeout// current scheduled but not yet executed request
            
requestSequence 0// sequence used to drop out-of-order responses
            
handler null,
            
quietMillis options.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
                    
data options.data// ajax data function
                    
transport options.transport || $.ajax,
                    
traditional options.traditional || false,
                    
type options.type || 'GET'// set type of request (GET or POST)

                
data data.call(thisquery.termquery.pagequery.context);

                if( 
null !== handler) { handler.abort(); }

                
handler transport.call(null, {
                    
urloptions.url,
                    
dataTypeoptions.dataType,
                    
datadata,
                    
typetype,
                    
traditionaltraditional,
                    
success: function (data) {
                        if (
requestNumber requestSequence) {
                            return;
                        }
                        
// TODO 3.0 - replace query.page with query so users have access to term, page, etc.
                        
var results options.results(dataquery.page);
                        
query.callback(results);
                    }
                });
            }, 
quietMillis);
        };
    }

    
/**
     * Produces a query function that works with a local array
     *
     * @param options object containing configuration parameters. The options parameter can either be an array or an
     * object.
     *
     * If the array form is used it is assumed that it contains objects with 'id' and 'text' keys.
     *
     * If the object form is used ti is assumed that it contains 'data' and 'text' keys. The 'data' key should contain
     * an array of objects that will be used as choices. These objects must contain at least an 'id' key. The 'text'
     * key can either be a String in which case it is expected that each element in the 'data' array has a key with the
     * value of 'text' which will be used to match choices. Alternatively, text can be a function(item) that can extract
     * the text.
     */
    
function local(options) {
        var 
data options// data elements
            
dataText,
            
text = function (item) { return ""+item.text; }; // function used to retrieve the text portion of a data item that is matched against the search

        
if (!$.isArray(data)) {
            
text data.text;
            
// if text is not a function we assume it to be a key name
            
if (!$.isFunction(text)) {
                
dataText data.text// we need to store this in a separate variable because in the next step data gets reset and data.text is no longer available
                
text = function (item) { return item[dataText]; };
            }
            
data data.results;
        }

        return function (
query) {
            var 
query.termfiltered = { results: [] }, process;
            if (
=== "") {
                
query.callback({resultsdata});
                return;
            }

            
process = function(datumcollection) {
                var 
groupattr;
                
datum datum[0];
                if (
datum.children) {
                    
group = {};
                    for (
attr in datum) {
                        if (
datum.hasOwnProperty(attr)) group[attr]=datum[attr];
                    }
                    
group.children=[];
                    $(
datum.children).each2(function(ichildDatum) { process(childDatumgroup.children); });
                    if (
group.children.length) {
                        
collection.push(group);
                    }
                } else {
                    if (
query.matcher(ttext(datum))) {
                        
collection.push(datum);
                    }
                }
            };

            $(
data).each2(function(idatum) { process(datumfiltered.results); });
            
query.callback(filtered);
        };
    }

    
// TODO javadoc
    
function tags(data) {
        
// TODO even for a function we should probably return a wrapper that does the same object/string check as
        // the function for arrays. otherwise only functions that return objects are supported.
        
if ($.isFunction(data)) {
            return 
data;
        }

        
// if not a function we assume it to be an array

        
return function (query) {
            var 
query.termfiltered = {results: []};
            $(
data).each(function () {
                var 
isObject this.text !== undefined,
                    
text isObject this.text this;
                if (
=== "" || query.matcher(ttext)) {
                    
filtered.results.push(isObject this : {idthistextthis});
                }
            });
            
query.callback(filtered);
        };
    }

    
/**
     * Checks if the formatter function should be used.
     *
     * Throws an error if it is not a function. Returns true if it should be used,
     * false if no formatting should be performed.
     *
     * @param formatter
     */
    
function checkFormatter(formatterformatterName) {
        if ($.
isFunction(formatter)) return true;
        if (!
formatter) return false;
        throw new 
Error("formatterName must be a function or a falsy value");
    }

    function 
evaluate(val) {
        return $.
isFunction(val) ? val() : val;
    }

    function 
countResults(results) {
        var 
count 0;
        $.
each(results, function(iitem) {
            if (
item.children) {
                
count += countResults(item.children);
            } else {
                
count++;
            }
        });
        return 
count;
    }

    
/**
     * Default tokenizer. This function uses breaks the input on substring match of any string from the
     * opts.tokenSeparators array and uses opts.createSearchChoice to create the choice object. Both of those
     * two options have to be defined in order for the tokenizer to work.
     *
     * @param input text user has typed so far or pasted into the search field
     * @param selection currently selected choices
     * @param selectCallback function(choice) callback tho add the choice to selection
     * @param opts esel2's opts
     * @return undefined/null to leave the current input unchanged, or a string to change the input to the returned value
     */
    
function defaultTokenizer(inputselectionselectCallbackopts) {
        var 
original input// store the original so we can compare and know if we need to tell the search to update its text
            
dupe false// check for whether a token we extracted represents a duplicate selected choice
            
token// token
            
index// position at which the separator was found
            
il// looping variables
            
separator// the matched separator

        
if (!opts.createSearchChoice || !opts.tokenSeparators || opts.tokenSeparators.length 1) return undefined;

        while (
true) {
            
index = -1;

            for (
0opts.tokenSeparators.lengthli++) {
                
separator opts.tokenSeparators[i];
                
index input.indexOf(separator);
                if (
index >= 0) break;
            }

            if (
index 0) break; // did not find any token separator in the input string, bail

            
token input.substring(0index);
            
input input.substring(index separator.length);

            if (
token.length 0) {
                
token opts.createSearchChoice(tokenselection);
                if (
token !== undefined && token !== null && opts.id(token) !== undefined && opts.id(token) !== null) {
                    
dupe false;
                    for (
0selection.lengthli++) {
                        if (
equal(opts.id(token), opts.id(selection[i]))) {
                            
dupe true; break;
                        }
                    }

                    if (!
dupeselectCallback(token);
                }
            }
        }

        if (
original.localeCompare(input) != 0) return input;
    }

    
/**
     * blurs any Esel2 container that has focus when an element outside them was clicked or received focus
     *
     * also takes care of clicks on label tags that point to the source element
     */
    
$(document).ready(function () {
        $(
document).delegate("body""mousedown touchend", function (e) {
            var 
target = $(e.target).closest("div.esel2-container").get(0), attr;
            if (
target) {
                $(
document).find("div.esel2-container-active").each(function () {
                    if (
this !== target) $(this).data("esel2").blur();
                });
            } else {
                
target = $(e.target).closest("div.esel2-drop").get(0);
                $(
document).find("div.esel2-drop-active").each(function () {
                    if (
this !== target) $(this).data("esel2").blur();
                });
            }

            
target=$(e.target);
            
attr target.attr("for");
            if (
"LABEL" === e.target.tagName && attr && attr.length 0) {
                
target = $("#"+attr);
                
target target.data("esel2");
                if (
target !== undefined) { target.focus(); e.preventDefault();}
            }
        });
    });

    
/**
     * Creates a new class
     *
     * @param superClass
     * @param methods
     */
    
function clazz(SuperClassmethods) {
        var 
constructor = function () {};
        
constructor.prototype = new SuperClass;
        
constructor.prototype.constructor constructor;
        
constructor.prototype.parent SuperClass.prototype;
        
constructor.prototype = $.extend(constructor.prototypemethods);
        return 
constructor;
    }

    
AbstractEsel2 clazz(Object, {

        
// abstract
        
bind: function (func) {
            var 
self this;
            return function () {
                
func.apply(selfarguments);
            };
        },

        
// abstract
        
init: function (opts) {
            var 
resultssearchresultsSelector ".esel2-results";

            
// prepare options
            
this.opts opts this.prepareOpts(opts);

            
this.id=opts.id;

            
// destroy if called on an existing component
            
if (opts.element.data("esel2") !== undefined &&
                
opts.element.data("esel2") !== null) {
                
this.destroy();
            }

            
this.enabled=true;
            
this.container this.createContainer();

            
this.containerId="s2id_"+(opts.element.attr("id") || "autogen"+nextUid());
            
this.containerSelector="#"+this.containerId.replace(/([;&,.+*~':"!^#$%@[]()=>|])/g, '\$1');
            this.container.attr("id", this.containerId);

            // cache the body so future lookups are cheap
            this.body = thunk(function() { return opts.element.closest("body"); });

            if (opts.element.attr("class") !== undefined) {
                this.container.addClass(opts.element.attr("class").replace(/validate[[S ]+] ?/, ''));
            }

            this.container.css(evaluate(opts.containerCss));
            this.container.addClass(evaluate(opts.containerCssClass));

            // swap container for the element
            this.opts.element
                .data("esel2", this)
                .hide()
                .before(this.container);
            this.container.data("esel2", this);

            this.dropdown = this.container.find(".esel2-drop");
            this.dropdown.addClass(evaluate(opts.dropdownCssClass));
            this.dropdown.data("esel2", this);

            this.results = results = this.container.find(resultsSelector);
            this.search = search = this.container.find("input.esel2-input");

            search.attr("tabIndex", this.opts.element.attr("tabIndex"));

            this.resultsPage = 0;
            this.context = null;

            // initialize the container
            this.initContainer();
            this.initContainerWidth();

            installFilteredMouseMove(this.results);
            this.dropdown.delegate(resultsSelector, "mousemove-filtered", this.bind(this.highlightUnderEvent));

            installDebouncedScroll(80, this.results);
            this.dropdown.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);
                    }
                });
            }

            installKeyUpChangeEvent(search);
            search.bind("keyup-change", this.bind(this.updateResults));
            search.bind("focus", function () { search.addClass("esel2-focused"); if (search.val() === " ") search.val(""); });
            search.bind("blur", function () { search.removeClass("esel2-focused");});

            this.dropdown.delegate(resultsSelector, "mouseup", this.bind(function (e) {
                if ($(e.target).closest(".esel2-result-selectable:not(.esel2-disabled)").length > 0) {
                    this.highlightUnderEvent(e);
                    this.selectHighlighted(e);
                } else {
                    this.focusSearch();
                }
                killEvent(e);
            }));

            // trap all mouse events from leaving the dropdown. sometimes there may be a modal that is listening
            // for mouse events outside of itself so it can close itself. since the dropdown is now outside the esel2'
s
            
// dom it will trigger the popup close, which is not what we want
            
this.dropdown.bind("click mouseup mousedown", function (e) { e.stopPropagation(); });

            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();
            }

            if (
opts.element.is(":disabled") || opts.element.is("[readonly='readonly']")) this.disable();
        },

        
// abstract
        
destroy: function () {
            var 
esel2 this.opts.element.data("esel2");
            if (
esel2 !== undefined) {
                
esel2.container.remove();
                
esel2.dropdown.remove();
                
esel2.opts.element
                    
.removeData("esel2")
                    .
unbind(".esel2")
                    .
show();
            }
        },

        
// abstract
        
prepareOpts: function (opts) {
            var 
elementselectidKeyajaxUrl;

            
element opts.element;

            if (
element.get(0).tagName.toLowerCase() === "select") {
                
this.select select opts.element;
            }

            if (
select) {
                
// these options are not allowed when attached to a select because they are picked up off the element itself
                
$.each(["id""multiple""ajax""query""createSearchChoice""initSelection""data""tags"], function () {
                    if (
this in opts) {
                        throw new 
Error("Option '" this "' is not allowed for Esel2 when attached to a <select> element.");
                    }
                });
            }

            
opts = $.extend({}, {
                
populateResults: function(containerresultsquery) {
                    var 
populate,  dataresultchildrenid=this.opts.idself=this;

                    
populate=function(resultscontainerdepth) {

                        var 
ilresultselectablecompoundnodelabelinnerContainerformatted;
                        for (
0results.lengthl1) {

                            
result=results[i];
                            
selectable=id(result) !== undefined;
                            
compound=result.children && result.children.length 0;

                            
node=$("<li></li>");
                            
node.addClass("esel2-results-dept-"+depth);
                            
node.addClass("esel2-result");
                            
node.addClass(selectable "esel2-result-selectable" "esel2-result-unselectable");
                            if (
compound) { node.addClass("esel2-result-with-children"); }
                            
node.addClass(self.opts.formatResultCssClass(result));

                            
label=$("<div></div>");
                            
label.addClass("esel2-result-label");

                            
formatted=opts.formatResult(resultlabelquery);
                            if (
formatted!==undefined) {
                                
label.html(self.opts.escapeMarkup(formatted));
                            }

                            
node.append(label);

                            if (
compound) {

                                
innerContainer=$("<ul></ul>");
                                
innerContainer.addClass("esel2-result-sub");
                                
populate(result.childreninnerContainerdepth+1);
                                
node.append(innerContainer);
                            }

                            
node.data("esel2-data"result);
                            
container.append(node);
                        }
                    };

                    
populate(resultscontainer0);
                }
            }, $.
fn.esel2.defaultsopts);

            if (
typeof(opts.id) !== "function") {
                
idKey opts.id;
                
opts.id = function (e) { return e[idKey]; };
            }

            if (
select) {
                
opts.query this.bind(function (query) {
                    var 
data = { results: [], morefalse },
                        
term query.term,
                        
childrenfirstChildprocess;

                    
process=function(elementcollection) {
                        var 
group;
                        if (
element.is("option")) {
                            if (
query.matcher(termelement.text(), element)) {
                                
collection.push({id:element.attr("value"), text:element.text(), elementelement.get(), csselement.attr("class")});
                            }
                        } else if (
element.is("optgroup")) {
                            
group={text:element.attr("label"), children:[], elementelement.get(), csselement.attr("class")};
                            
element.children().each2(function(ielm) { process(elmgroup.children); });
                            if (
group.children.length>0) {
                                
collection.push(group);
                            }
                        }
                    };

                    
children=element.children();

                    
// ignore the placeholder option if there is one
                    
if (this.getPlaceholder() !== undefined && children.length 0) {
                        
firstChild children[0];
                        if ($(
firstChild).text() === "") {
                            
children=children.not(firstChild);
                        }
                    }

                    
children.each2(function(ielm) { process(elmdata.results); });

                    
query.callback(data);
                });
                
// this is needed because inside val() we construct choices from options and there id is hardcoded
                
opts.id=function(e) { return e.id; };
                
opts.formatResultCssClass = function(data) { return data.css; }
            } else {
                if (!(
"query" in opts)) {
                    if (
"ajax" in opts) {
                        
ajaxUrl opts.element.data("ajax-url");
                        if (
ajaxUrl && ajaxUrl.length 0) {
                            
opts.ajax.url ajaxUrl;
                        }
                        
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 {idtermtextterm}; };
                        
opts.initSelection = function (elementcallback) {
                            var 
data = [];
                            $(
splitVal(element.val(), opts.separator)).each(function () {
                                var 
id thistext thistags=opts.tags;
                                if ($.
isFunction(tags)) tags=tags();
                                $(
tags).each(function() { if (equal(this.idid)) { text this.text; return false; } });
                                
data.push({ididtexttext});
                            });

                            
callback(data);
                        };
                    }
                }
            }
            if (
typeof(opts.query) !== "function") {
                throw 
"query function not defined for Esel2 " opts.element.attr("id");
            }

            return 
opts;
        },

        
/**
         * Monitor the original element for changes and update esel2 accordingly
         */
        // abstract
        
monitorSource: function () {
            
this.opts.element.bind("change.esel2"this.bind(function (e) {
                if (
this.opts.element.data("esel2-change-triggered") !== true) {
                    
this.initSelection();
                }
            }));
        },

        
/**
         * Triggers the change event on the source element
         */
        // abstract
        
triggerChange: function (details) {

            
details details || {};
            
details= $.extend({}, details, { type"change"valthis.val() });
            
// prevents recursive triggering
            
this.opts.element.data("esel2-change-triggered"true);
            
this.opts.element.trigger(details);
            
this.opts.element.data("esel2-change-triggered"false);

            
// some validation frameworks ignore the change event and listen instead to keyup, click for selects
            // so here we trigger the click event manually
            
this.opts.element.click();

            
// ValidationEngine ignorea the change event and listens instead to blur
            // so here we trigger the blur event manually if so desired
            
if (this.opts.blurOnChange)
                
this.opts.element.blur();
        },


        
// abstract
        
enable: function() {
            if (
this.enabled) return;

            
this.enabled=true;
            
this.container.removeClass("esel2-container-disabled");
        },

        
// abstract
        
disable: function() {
            if (!
this.enabled) return;

            
this.close();

            
this.enabled=false;
            
this.container.addClass("esel2-container-disabled");
        },

        
// abstract
        
opened: function () {
            return 
this.container.hasClass("esel2-dropdown-open");
        },

        
// abstract
        
positionDropdown: function() {
            var 
offset this.container.offset(),
                
height this.container.outerHeight(),
                
width this.container.outerWidth(),
                
dropHeight this.dropdown.outerHeight(),
                
viewportBottom = $(window).scrollTop() + document.documentElement.clientHeight,
                
dropTop offset.top height,
                
dropLeft offset.left,
                
enoughRoomBelow dropTop dropHeight <= viewportBottom,
                
enoughRoomAbove = (offset.top dropHeight) >= this.body().scrollTop(),
                
aboveNow this.dropdown.hasClass("esel2-drop-above"),
                
bodyOffset,
                
above,
                
css;

            
// console.log("below/ droptop:", dropTop, "dropHeight", dropHeight, "sum", (dropTop+dropHeight)+" viewport bottom", viewportBottom, "enough?", enoughRoomBelow);
            // console.log("above/ offset.top", offset.top, "dropHeight", dropHeight, "top", (offset.top-dropHeight), "scrollTop", this.body().scrollTop(), "enough?", enoughRoomAbove);

            // fix positioning when body has an offset and is not position: static

            
if (this.body().css('position') !== 'static') {
                
bodyOffset this.body().offset();
                
dropTop -= bodyOffset.top;
                
dropLeft -= bodyOffset.left;
            }

            
// always prefer the current above/below alignment, unless there is not enough room

            
if (aboveNow) {
                
above true;
                if (!
enoughRoomAbove && enoughRoomBelowabove false;
            } else {
                
above false;
                if (!
enoughRoomBelow && enoughRoomAboveabove true;
            }

            if (
above) {
                
dropTop offset.top dropHeight;
                
this.container.addClass("esel2-drop-above");
                
this.dropdown.addClass("esel2-drop-above");
            }
            else {
                
this.container.removeClass("esel2-drop-above");
                
this.dropdown.removeClass("esel2-drop-above");
            }

            
css = $.extend({
                
topdropTop,
                
leftdropLeft,
                
widthwidth
            
}, evaluate(this.opts.dropdownCss));

            
this.dropdown.css(css);
        },

        
// abstract
        
shouldOpen: function() {
            var 
event;

            if (
this.opened()) return false;

            
event = $.Event("open");
            
this.opts.element.trigger(event);
            return !
event.isDefaultPrevented();
        },

        
// abstract
        
clearDropdownAlignmentPreference: function() {
            
// clear the classes used to figure out the preference of where the dropdown should be opened
            
this.container.removeClass("esel2-drop-above");
            
this.dropdown.removeClass("esel2-drop-above");
        },

        
/**
         * Opens the dropdown
         *
         * @return {Boolean} whether or not dropdown was opened. This method will return false if, for example,
         * the dropdown is already open, or if the 'open' event listener on the element called preventDefault().
         */
        // abstract
        
open: function () {

            if (!
this.shouldOpen()) return false;

            
window.setTimeout(this.bind(this.opening), 1);

            return 
true;
        },

        
/**
         * Performs the opening of the dropdown
         */
        // abstract
        
opening: function() {
            var 
cid this.containerIdselector this.containerSelector;

            
this.clearDropdownAlignmentPreference();

            if (
this.search.val() === " ") { this.search.val(""); }

            
this.container.addClass("esel2-dropdown-open").addClass("esel2-container-active");

            
this.updateResults(true);

            if(
this.dropdown[0] !== this.body().children().last()[0]) {
                
this.dropdown.detach().appendTo(this.body());
            }

            
this.dropdown.show();
            
// move the global id to the correct dropdown
            
$("#esel2-drop").removeAttr("id");
            
this.dropdown.attr("id""esel2-drop");

            
// show the elements
            
this.dropdown.show();
            
this.positionDropdown();
            
this.dropdown.addClass("esel2-drop-active");

            
this.ensureHighlightVisible();

            
this.focusSearch();
        },

        
// abstract
        
close: function () {
            if (!
this.opened()) return;

            var 
self this;

            
this.clearDropdownAlignmentPreference();

            
this.dropdown.removeAttr("id"); // only the active dropdown has the esel2-drop id

            
this.dropdown.hide();
            
this.container.removeClass("esel2-dropdown-open").removeClass("esel2-container-active");
            
this.results.empty();
            
this.clearSearch();

            
this.opts.element.trigger($.Event("close"));
        },

        
// abstract
        
clearSearch: function () {

        },

        
// abstract
        
ensureHighlightVisible: function () {
            var 
results this.resultschildrenindexchildhbrbymore;

            
index this.highlight();

            if (
index 0) return;

            if (
index == 0) {

                
// if the first element is highlighted scroll all the way to the top,
                // that way any unselectable headers above it will also be scrolled
                // into view

                
results.scrollTop(0);
                return;
            }

            
children results.find(".esel2-result-selectable");

            
child = $(children[index]);

            
hb child.offset().top child.outerHeight();

            
// if this is the last child lets also make sure esel2-more-results is visible
            
if (index === children.length 1) {
                
more results.find("li.esel2-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));
            }
            
child.offset().top results.offset().top;

            
// make sure the top of the element is visible
            
if (0) {
                
results.scrollTop(results.scrollTop() + y); // y is negative
            
}
        },

        
// abstract
        
moveHighlight: function (delta) {
            var 
choices this.results.find(".esel2-result-selectable"),
                
index this.highlight();

            while (
index > -&& index choices.length) {
                
index += delta;
                var 
choice = $(choices[index]);
                if (
choice.hasClass("esel2-result-selectable") && !choice.hasClass("esel2-disabled")) {
                    
this.highlight(index);
                    break;
                }
            }
        },

        
// abstract
        
highlight: function (index) {
            var 
choices this.results.find(".esel2-result-selectable").not(".esel2-disabled");

            if (
arguments.length === 0) {
                return 
indexOf(choices.filter(".esel2-highlighted")[0], choices.get());
            }

            if (
index >= choices.lengthindex choices.length 1;
            if (
index 0index 0;

            
choices.removeClass("esel2-highlighted");

            $(
choices[index]).addClass("esel2-highlighted");
            
this.ensureHighlightVisible();

        },

        
// abstract
        
countSelectableResults: function() {
            return 
this.results.find(".esel2-result-selectable").not(".esel2-disabled").length;
        },

        
// abstract
        
highlightUnderEvent: function (event) {
            var 
el = $(event.target).closest(".esel2-result-selectable");
            if (
el.length && !el.is(".esel2-highlighted")) {
                var 
choices this.results.find('.esel2-result-selectable');
                
this.highlight(choices.index(el));
            } else if (
el.length == 0) {
                
// if we are over an unselectable item remove al highlights
                
this.results.find(".esel2-highlighted").removeClass("esel2-highlighted");
            }
        },

        
// abstract
        
loadMoreIfNeeded: function () {
            var 
results this.results,
                
more results.find("li.esel2-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,
                
self=this,
                
term=this.search.val(),
                
context=this.context;

            if (
more.length === 0) return;
            
below more.offset().top results.offset().top results.height();

            if (
below <= 0) {
                
more.addClass("esel2-active");
                
this.opts.query({
                    
termterm,
                    
pagepage,
                    
contextcontext,
                    
matcherthis.opts.matcher,
                    
callbackthis.bind(function (data) {

                        
// ignore a response if the esel2 has been closed before it was received
                        
if (!self.opened()) return;


                        
self.opts.populateResults.call(thisresultsdata.results, {termtermpagepagecontext:context});

                        if (
data.more===true) {
                            
more.detach().appendTo(results).text(self.opts.formatLoadMore(page+1));
                            
window.setTimeout(function() { self.loadMoreIfNeeded(); }, 10);
                        } else {
                            
more.remove();
                        }
                        
self.positionDropdown();
                        
self.resultsPage page;
                    })});
            }
        },

        
/**
         * Default tokenizer function which does nothing
         */
        
tokenize: function() {

        },

        
/**
         * @param initial whether or not this is the call to this method right after the dropdown has been opened
         */
        // abstract
        
updateResults: function (initial) {
            var 
search this.searchresults this.resultsopts this.optsdataself=thisinput;

            
// if the search is currently hidden we do not alter the results
            
if (initial !== true && (this.showSearchInput === false || !this.opened())) {
                return;
            }

            
search.addClass("esel2-active");

            function 
postRenderterm ) {
                
results.scrollTop(0);
                
search.removeClass("esel2-active");
                
self.positionDropdown();
                
opts.postRender(term);
            }

            function 
render(htmlterm) {
                
results.html(self.opts.escapeMarkup(html));
                
postRender(term);
            }

            if (
opts.maximumSelectionSize >=1) {
                
data this.data();
                if ($.
isArray(data) && data.length >= opts.maximumSelectionSize && checkFormatter(opts.formatSelectionTooBig"formatSelectionTooBig")) {
                    
render("<li class='esel2-selection-limit'>" opts.formatSelectionTooBig(opts.maximumSelectionSize) + "</li>"search.val());
                    return;
                }
            }

            
this.dropdown.removeClass('esel2-dropdown-too-short'); //MCOMPOSE
            
if (search.val().length opts.minimumInputLength && checkFormatter(opts.formatInputTooShort"formatInputTooShort")) {
                
this.dropdown.addClass('esel2-dropdown-too-short');
                
render("<li class='esel2-no-results'>" opts.formatInputTooShort(search.val(), opts.minimumInputLength) + "</li>"search.val());
                return;
            }
            else {
                
render("<li class='esel2-searching'>" opts.formatSearching() + "</li>"search.val());
            }

            
// give the tokenizer a chance to pre-process the input
            
input this.tokenize();
            if (
input != undefined && input != null) {
                
search.val(input);
            }

            
this.resultsPage 1;
            
opts.query({
                
termsearch.val(),
                
pagethis.resultsPage,
                
contextnull,
                
matcheropts.matcher,
                
callbackthis.bind(function (data) {
                    var 
def// default choice

                    // ignore a response if the esel2 has been closed before it was received
                    
if (!this.opened()) return;

                    
// save context, if any
                    
this.context = (data.context===undefined) ? null data.context;

                    
// create a default choice and prepend it to the list
                    
if (this.opts.createSearchChoice && search.val() !== "") {
                        
def this.opts.createSearchChoice.call(nullsearch.val(), data.results);
                        if (
def !== undefined && def !== null && self.id(def) !== undefined && self.id(def) !== null) {
                            if ($(
data.results).filter(
                                function () {
                                    return 
equal(self.id(this), self.id(def));
                                }).
length === 0) {
                                
data.results.unshift(def);
                            }
                        }
                    }

                    if (
data.results.length === && checkFormatter(opts.formatNoMatches"formatNoMatches")) {
                        
render("<li class='esel2-no-results'>" opts.formatNoMatches(search.val()) + "</li>"search.val());
                        return;
                    }

                    
results.empty();
                    
self.opts.populateResults.call(thisresultsdata.results, {termsearch.val(), pagethis.resultsPagecontext:null});

                    if (
data.more === true && checkFormatter(opts.formatLoadMore"formatLoadMore")) {
                        
results.append("<li class='esel2-more-results'>" self.opts.escapeMarkup(opts.formatLoadMore(this.resultsPage)) + "</li>");
                        
window.setTimeout(function() { self.loadMoreIfNeeded(); }, 10);
                    }

                    
this.postprocessResults(datainitial);

                    
postRender(search.val());
                })});
        },

        
// abstract
        
cancel: function () {
            
this.close();
        },

        
// abstract
        
blur: function () {
            
this.close();
            
this.container.removeClass("esel2-container-active");
            
this.dropdown.removeClass("esel2-drop-active");
            
// synonymous to .is(':focus'), which is available in jquery >= 1.6
            
if (this.search[0] === document.activeElement) { this.search.blur(); }
            
this.clearSearch();
            
this.selection.find(".esel2-search-choice-focus").removeClass("esel2-search-choice-focus");
        },

        
// abstract
        
focusSearch: function () {
            
// need to do it here as well as in timeout so it works in IE
            
this.search.show();
            
this.search.focus();

            
/* 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 () {
                
// reset the value so IE places the cursor at the end of the input box
                
this.search.show();
                
this.search.focus();
                
this.search.val(this.search.val());
            }), 
10);
        },

        
// abstract
        
selectHighlighted: function () {
            var 
index=this.highlight(),
                
highlighted=this.results.find(".esel2-highlighted").not(".esel2-disabled"),
                
data highlighted.closest('.esel2-result-selectable').data("esel2-data");
            if (
data) {
                
highlighted.addClass("esel2-disabled");
                
this.highlight(index);
                
this.onSelect(data);
            }
        },

        
// abstract
        
getPlaceholder: function () {
            return 
this.opts.element.attr("placeholder") ||
                
this.opts.element.attr("data-placeholder") || // jquery 1.4 compat
                
this.opts.element.data("placeholder") ||
                
this.opts.placeholder;
        },

        
/**
         * Get the desired width for the container element.  This is
         * derived first from option `width` passed to esel2, then
         * the inline 'style' on the original element, and finally
         * falls back to the jQuery calculated element width.
         */
        // abstract
        
initContainerWidth: function () {
            function 
resolveContainerWidth() {
                var 
styleattrsmatchesil;

                if (
this.opts.width === "off") {
                    return 
null;
                } else if (
this.opts.width === "element"){
                    return 
this.opts.element.outerWidth() === 'auto' this.opts.element.outerWidth() + 'px';
                } else if (
this.opts.width === "copy" || this.opts.width === "resolve") {
                    
// check if there is inline style on the element that contains width
                    
style this.opts.element.attr('style');
                    if (
style !== undefined) {
                        
attrs style.split(';');
                        for (
0attrs.lengthl1) {
                            
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];
                        }
                    }

                    if (
this.opts.width === "resolve") {
                        
// next check if css('width') can resolve a width that is percent based, this is sometimes possible
                        // when attached to input type=hidden or elements hidden via css
                        
style this.opts.element.css('width');
                        if (
style.indexOf("%") > 0) return style;

                        
// finally, fallback on the calculated width of the element
                        
return (this.opts.element.outerWidth() === 'auto' this.opts.element.outerWidth() + 'px');
                    }

                    return 
null;
                } else if ($.
isFunction(this.opts.width)) {
                    return 
this.opts.width();
                } else {
                    return 
this.opts.width;
                }
            };

            var 
width resolveContainerWidth.call(this);
            if (
width !== null) {
                
this.container.attr("style""width: "+width);
            }
        }
    });

    
SingleEsel2 clazz(AbstractEsel2, {

        
// single

        
createContainer: function () {
            var 
container = $("<div></div>", {
                
"class""esel2-container"
            
}).html([
                    
"    <a href='#' onclick='return false;' class='esel2-choice'>",
                    
"   <span></span><abbr class='esel2-search-choice-close' style='display:none;'></abbr>",
                    
"   <div><b></b></div>" ,
                    
"</a>",
                    
"    <div class='esel2-drop esel2-offscreen'>" ,
                    
"   <div class='esel2-search'>" ,
                    
"       <input type='text' autocomplete='off' class='esel2-input'/>" ,
                    
"   </div>" ,
                    
"   <ul class='esel2-results'>" ,
                    
"   </ul>" ,
                    
"</div>"].join(""));
            return 
container;
        },

        
// single
        
opening: function () {
            
this.search.show();
            
this.parent.opening.apply(thisarguments);
            
this.dropdown.removeClass("esel2-offscreen");
        },

        
// single
        
close: function () {
            if (!
this.opened()) return;
            
this.parent.close.apply(thisarguments);
            
this.dropdown.removeAttr("style").addClass("esel2-offscreen").insertAfter(this.selection).show();
        },

        
// single
        
focus: function () {
            
this.close();
            
this.selection.focus();
        },

        
// single
        
isFocused: function () {
            return 
this.selection[0] === document.activeElement;
        },

        
// single
        
cancel: function () {
            
this.parent.cancel.apply(thisarguments);
            
this.selection.focus();
        },

        
// single
        
initContainer: function () {

            var 
selection,
                
container this.container,
                
dropdown this.dropdown,
                
clickingInside false;

            
this.selection selection container.find(".esel2-choice");

            
this.search.bind("keydown"this.bind(function (e) {
                if (!
this.enabled) return;

                if (
e.which === KEY.PAGE_UP || e.which === KEY.PAGE_DOWN) {
                    
// prevent the page from scrolling
                    
killEvent(e);
                    return;
                }

                if (
this.opened()) {
                    switch (
e.which) {
                        case 
KEY.UP:
                        case 
KEY.DOWN:
                            
this.moveHighlight((e.which === KEY.UP) ? -1);
                            
killEvent(e);
                            return;
                        case 
KEY.TAB:
                        case 
KEY.ENTER:
                            
this.selectHighlighted();
                            
killEvent(e);
                            return;
                        case 
KEY.ESC:
                            
this.cancel(e);
                            
killEvent(e);
                            return;
                    }
                } else {

                    if (
e.which === KEY.TAB || KEY.isControl(e) || KEY.isFunctionKey(e) || e.which === KEY.ESC) {
                        return;
                    }

                    if (
this.opts.openOnEnter === false && e.which === KEY.ENTER) {
                        return;
                    }

                    
this.open();

                    if (
e.which === KEY.ENTER) {
                        
// do not propagate the event otherwise we open, and propagate enter which closes
                        
return;
                    }
                }
            }));

            
this.search.bind("focus"this.bind(function() {
                
this.selection.attr("tabIndex""-1");
            }));
            
this.search.bind("blur"this.bind(function() {
                if (!
this.opened()) this.container.removeClass("esel2-container-active");
                
window.setTimeout(this.bind(function() { this.selection.attr("tabIndex"this.opts.element.attr("tabIndex")); }), 10);
            }));

            
selection.bind("mousedown"this.bind(function (e) {
                
clickingInside true;

                if (
this.opened()) {
                    
this.close();
                    
this.selection.focus();
                } else if (
this.enabled) {
                    
this.open();
                }

                
clickingInside false;
            }));

            
dropdown.bind("mousedown"this.bind(function() { this.search.focus(); }));

            
selection.bind("focus"this.bind(function() {
                
this.container.addClass("esel2-container-active");
                
// hide the search so the tab key does not focus on it
                
this.search.attr("tabIndex""-1");
            }));

            
selection.bind("blur"this.bind(function() {
                if (!
this.opened()) {
                    
this.container.removeClass("esel2-container-active");
                }
                
window.setTimeout(this.bind(function() { this.search.attr("tabIndex"this.opts.element.attr("tabIndex")); }), 10);
            }));

            
selection.bind("keydown"this.bind(function(e) {
                if (!
this.enabled) return;

                if (
e.which === KEY.PAGE_UP || e.which === KEY.PAGE_DOWN) {
                    
// prevent the page from scrolling
                    
killEvent(e);
                    return;
                }

                if (
e.which === KEY.TAB || KEY.isControl(e) || KEY.isFunctionKey(e)
                    || 
e.which === KEY.ESC) {
                    return;
                }

                if (
this.opts.openOnEnter === false && e.which === KEY.ENTER) {
                    return;
                }

                if (
e.which == KEY.DELETE) {
                    if (
this.opts.allowClear) {
                        
this.clear();
                    }
                    return;
                }

                
this.open();

                if (
e.which === KEY.ENTER) {
                    
// do not propagate the event otherwise we open, and propagate enter which closes
                    
killEvent(e);
                    return;
                }

                
// do not set the search input value for non-alpha-numeric keys
                // otherwise pressing down results in a '(' being set in the search field
                
if (e.which 48 ) { // '0' == 48
                    
killEvent(e);
                    return;
                }

                var 
keyWritten String.fromCharCode(e.which).toLowerCase();

                if (
e.shiftKey) {
                    
keyWritten keyWritten.toUpperCase();
                }

                
// focus the field before calling val so the cursor ends up after the value instead of before
                
this.search.focus();
                
this.search.val(keyWritten);

                
// prevent event propagation so it doesnt replay on the now focussed search field and result in double key entry
                
killEvent(e);
            }));

            
selection.delegate("abbr""mousedown"this.bind(function (e) {
                if (!
this.enabled) return;
                
this.clear();
                
killEvent(e);
                
this.close();
                
this.triggerChange();
                
this.selection.focus();
            }));

            
this.setPlaceholder();

            
this.search.bind("focus"this.bind(function() {
                
this.container.addClass("esel2-container-active");
            }));
        },

        
// single
        
clear: function() {
            
this.opts.element.val("");
            
this.selection.find("span").empty();
            
this.selection.removeData("esel2-data");
            
this.setPlaceholder();
        },

        
/**
         * Sets selection based on source element's value
         */
        // single
        
initSelection: function () {
            var 
selected;
            if (
this.opts.element.val() === "") {
                
this.close();
                
this.setPlaceholder();
            } else {
                var 
self this;
                
this.opts.initSelection.call(nullthis.opts.element, function(selected){
                    if (
selected !== undefined && selected !== null) {
                        
self.updateSelection(selected);
                        
self.close();
                        
self.setPlaceholder();
                    }
                });
            }
        },

        
// single
        
prepareOpts: function () {
            var 
opts this.parent.prepareOpts.apply(thisarguments);

            if (
opts.element.get(0).tagName.toLowerCase() === "select") {
                
// install the selection initializer
                
opts.initSelection = function (elementcallback) {
                    var 
selected element.find(":selected");
                    
// a single select box always has a value, no need to null check 'selected'
                    
if ($.isFunction(callback))
                        
callback({idselected.attr("value"), textselected.text()});
                };
            }

            return 
opts;
        },

        
// single
        
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;

                
this.selection.find("span").html(this.opts.escapeMarkup(placeholder));

                
this.selection.addClass("esel2-default").addClass('invitation');

                
this.selection.find("abbr").hide();
            }
        },

        
// single
        
postprocessResults: function (datainitial) {
            var 
selected 0self thisshowSearchInput true;

            
// find the selected element in the result list

            
this.results.find(".esel2-result-selectable").each2(function (ielm) {
                if (
equal(self.id(elm.data("esel2-data")), self.opts.element.val())) {
                    
selected i;
                    return 
false;
                }
            });

            
// and highlight it

            
this.highlight(selected);

            
// hide the search box if this is the first we got the results and there are a few of them

            
if (initial === true) {
                
showSearchInput this.showSearchInput countResults(data.results) >= this.opts.minimumResultsForSearch;
                
this.dropdown.find(".esel2-search")[showSearchInput "removeClass" "addClass"]("esel2-search-hidden");

                
//add "esel2-with-searchbox" to the container if search box is shown
                
$(this.dropdownthis.container)[showSearchInput "addClass" "removeClass"]("esel2-with-searchbox");
            }

        },

        
// single
        
onSelect: function (data) {
            var 
old this.opts.element.val();

            
this.opts.element.val(this.id(data));
            
this.updateSelection(data);
            
this.close();
            
this.selection.focus();

            if (!
equal(oldthis.id(data))) { this.triggerChange(); }
        },

        
// single
        
updateSelection: function (data) {

            var 
container=this.selection.find("span"), formatted;

            
this.selection.data("esel2-data"data);

            
container.empty();
            
formatted=this.opts.formatSelection(datacontainer);
            if (
formatted !== undefined) {
                
container.append(this.opts.escapeMarkup(formatted));
            }

            
this.selection.removeClass("esel2-default").removeClass('invitation');

            if (
this.opts.allowClear && this.getPlaceholder() !== undefined) {
                
this.selection.find("abbr").show();
            }
        },

        
// single
        
val: function () {
            var 
valdata nullself this;

            if (
arguments.length === 0) {
                return 
this.opts.element.val();
            }

            
val arguments[0];

            if (
this.select) {
                
this.select
                    
.val(val)
                    .
find(":selected").each2(function (ielm) {
                        
data = {idelm.attr("value"), textelm.text()};
                        return 
false;
                    });
                
this.updateSelection(data);
                
this.setPlaceholder();
            } else {
                if (
this.opts.initSelection === undefined) {
                    throw new 
Error("cannot call val() if initSelection() is not defined");
                }
                
// val is an id. !val is true for [undefined,null,'']
                
if (!val) {
                    
this.clear();
                    return;
                }
                
this.opts.element.val(val);
                
this.opts.initSelection(this.opts.element, function(data){
                    
self.opts.element.val(!data "" self.id(data));
                    
self.updateSelection(data);
                    
self.setPlaceholder();
                });
            }
        },

        
// single
        
clearSearch: function () {
            
this.search.val("");
        },

        
// single
        
data: function(value) {
            var 
data;

            if (
arguments.length === 0) {
                
data this.selection.data("esel2-data");
                if (
data == undefineddata null;
                return 
data;
            } else {
                if (!
value || value === "") {
                    
this.clear();
                } else {
                    
this.opts.element.val(!value "" this.id(value));
                    
this.updateSelection(value);
                }
            }
        }
    });

    
MultiEsel2 clazz(AbstractEsel2, {

        
// multi
        
createContainer: function () {
            var 
container = $("<div></div>", {
                
"class""esel2-container esel2-container-multi"
            
}).html([
                    
"    <ul class='esel2-choices'>",
                    
//"<li class='esel2-search-choice'><span>California</span><a href="javascript:void(0)" class="esel2-search-choice-close"></a></li>" ,
                    
"  <li class='esel2-search-field'>" ,
                    
"    <input type='text' autocomplete='off' class='esel2-input'>" ,
                    
"  </li>" ,
                    
"</ul>" ,
                    
"<div class='esel2-drop esel2-drop-multi' style='display:none;'>" ,
                    
"   <ul class='esel2-results'>" ,
                    
"   </ul>" ,
                    
"</div>"].join(""));
            return 
container;
        },

        
// multi
        
prepareOpts: function () {
            var 
opts this.parent.prepareOpts.apply(thisarguments);

            
// TODO validate placeholder is a string if specified

            
if (opts.element.get(0).tagName.toLowerCase() === "select") {
                
// install sthe selection initializer
                
opts.initSelection = function (element,callback) {

                    var 
data = [];
                    
element.find(":selected").each2(function (ielm) {
                        
data.push({idelm.attr("value"), textelm.text()});
                    });

                    if ($.
isFunction(callback))
                        
callback(data);
                };
            }

            return 
opts;
        },

        
// multi
        
initContainer: function () {

            var 
selector ".esel2-choices"selection;

            
this.searchContainer this.container.find(".esel2-search-field");
            
this.selection selection this.container.find(selector);

            
this.search.bind("keydown"this.bind(function (e) {
                if (!
this.enabled) return;

                if (
e.which === KEY.BACKSPACE && this.search.val() === "") {
                    
this.close();

                    var 
choices,
                        
selected selection.find(".esel2-search-choice-focus");
                    
/*
                     * MCOMPOSE
                     *
                     *if (selected.length > 0) {
                     this.unselect(selected.first());
                     this.search.width(10);
                     killEvent(e);
                     return;
                     }*/

                    
choices selection.find(".esel2-search-choice");
                    if (
choices.length 0) {
                        
//choices.last().addClass("esel2-search-choice-focus");
                        
this.unselect(choices.last()); // MCOMPOSE
                    
}
                } else {
                    
selection.find(".esel2-search-choice-focus").removeClass("esel2-search-choice-focus");
                }

                if (
this.opened()) {
                    switch (
e.which) {
                        case 
KEY.UP:
                        case 
KEY.DOWN:
                            
this.moveHighlight((e.which === KEY.UP) ? -1);
                            
killEvent(e);
                            return;
                        case 
KEY.ENTER:
                        case 
KEY.TAB:
                            
this.selectHighlighted();
                            
killEvent(e);
                            return;
                        case 
KEY.ESC:
                            
this.cancel(e);
                            
killEvent(e);
                            return;
                    }
                }

                if (
e.which === KEY.TAB || KEY.isControl(e) || KEY.isFunctionKey(e)
                    || 
e.which === KEY.BACKSPACE || e.which === KEY.ESC) {
                    return;
                }

                if (
this.opts.openOnEnter === false && e.which === KEY.ENTER) {
                    return;
                }

                
this.open();

                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("blur"this.bind(function(e) {
                
this.container.removeClass("esel2-container-active");
                
this.search.removeClass("esel2-focused");
                
this.clearSearch();
                
e.stopImmediatePropagation();
            }));

            
this.container.delegate(selector"mousedown"this.bind(function (e) {
                if (!
this.enabled) return;
                if ($(
e.target).closest(".esel2-search-choice").length 0) {
                    
// clicked inside a esel2 search choice, do not open
                    
return;
                }
                
this.clearPlaceholder();
                
this.open();
                
this.focusSearch();
                
e.preventDefault();
            }));

            
this.container.delegate(selector"focus"this.bind(function () {
                if (!
this.enabled) return;
                
this.container.addClass("esel2-container-active");
                
this.dropdown.addClass("esel2-drop-active");
                
this.clearPlaceholder();
            }));

            
// set the placeholder if necessary
            
this.clearSearch();
        },

        
// multi
        
enable: function() {
            if (
this.enabled) return;

            
this.parent.enable.apply(thisarguments);

            
this.search.removeAttr("disabled");
        },

        
// multi
        
disable: function() {
            if (!
this.enabled) return;

            
this.parent.disable.apply(thisarguments);

            
this.search.attr("disabled"true);
        },

        
// multi
        
initSelection: function () {
            var 
data;
            if (
this.opts.element.val() === "") {
                
this.updateSelection([]);
                
this.close();
                
// set the placeholder if necessary
                
this.clearSearch();
            }
            if (
this.select || this.opts.element.val() !== "") {
                var 
self this;
                
this.opts.initSelection.call(nullthis.opts.element, function(data){
                    if (
data !== undefined && data !== null) {
                        
self.updateSelection(data);
                        
self.close();
                        
// set the placeholder if necessary
                        
self.clearSearch();
                    }
                });
            }
        },

        
// multi
        
clearSearch: function () {
            var 
placeholder this.getPlaceholder();

            if (
placeholder !== undefined  && this.getVal().length === && this.search.hasClass("esel2-focused") === false) {
                
this.search.val(placeholder).addClass("esel2-default").addClass('invitation');
                
// stretch the search box to full width of the container so as much of the placeholder is visible as possible
                
this.resizeSearch();
            } else {
                
// we set this to " " instead of "" and later clear it on focus() because there is a firefox bug
                // that does not properly render the caret when the field starts out blank
                
this.search.val(" ").width(10);
            }
        },

        
// multi
        
clearPlaceholder: function () {
            if (
this.search.hasClass("esel2-default")) {
                
this.search.val("").removeClass("esel2-default").removeClass('invitation');
            } else {
                
// work around for the space character we set to avoid firefox caret bug
                
if (this.search.val() === " "this.search.val("");
            }
        },

        
// multi
        
opening: function () {
            
this.parent.opening.apply(thisarguments);

            
this.clearPlaceholder();
            
this.resizeSearch();
            
this.focusSearch();
        },

        
// multi
        
close: function () {
            if (!
this.opened()) return;
            
this.parent.close.apply(thisarguments);
        },

        
// multi
        
focus: function () {
            
this.close();
            
this.search.focus();
        },

        
// multi
        
isFocused: function () {
            return 
this.search.hasClass("esel2-focused");
        },

        
// multi
        
updateSelection: function (data) {
            var 
ids = [], filtered = [], self this;

            
// filter out duplicates
            
$(data).each(function () {
                if (
indexOf(self.id(this), ids) < 0) {
                    
ids.push(self.id(this));
                    
filtered.push(this);
                }
            });
            
data filtered;

            
this.selection.find(".esel2-search-choice").remove();
            $(
data).each(function () {
                
self.addSelectedChoice(this);
            });
            
self.postprocessResults();
        },

        
tokenize: function() {
            var 
input this.search.val();
            
input this.opts.tokenizer(inputthis.data(), this.bind(this.onSelect), this.opts);
            if (
input != null && input != undefined) {
                
this.search.val(input);
                if (
input.length 0) {
                    
this.open();
                }
            }

        },

        
// multi
        
onSelect: function (data) {
            
this.addSelectedChoice(data);
            if (
this.select) { this.postprocessResults(); }

            if (
this.opts.closeOnSelect) {
                
this.close();
                
this.search.width(10);
            } else {
                if (
this.countSelectableResults()>0) {
                    
this.search.width(10);
                    
this.resizeSearch();
                    
this.positionDropdown();
                } else {
                    
// if nothing left to select close
                    
this.close();
                }
            }

            
// 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({ addeddata });

            
//this.focusSearch();
        
},

        
// multi
        
cancel: function () {
            
this.close();
            
this.focusSearch();
        },

        
// multi
        
addSelectedChoice: function (data) {
            var 
choice=$(
                    
"<li style="padding-left5px;"><div></div></li>"),
                
id this.id(data),
                
val this.getVal(),
                
formatted;

            
formatted=this.opts.formatSelection(datachoice);

            
choice.find("div").replaceWith("<div>"+formatted+"</div>");

            
/*
            choice.find(".esel2-search-choice-close")
                .bind("mousedown", killEvent)
                .bind("click dblclick", this.bind(function (e) {
                    if (!this.enabled) return;

                    $(e.target).closest(".esel2-search-choice").fadeOut('fast', this.bind(function(){
                        this.unselect($(e.target));
                        this.selection.find(".esel2-search-choice-focus").removeClass("esel2-search-choice-focus");
                        this.close();
                        this.focusSearch();
                    })).dequeue();
                    killEvent(e);
                })).bind("focus", this.bind(function () {
                    if (!this.enabled) return;
                    this.container.addClass("esel2-container-active");
                    this.dropdown.addClass("esel2-drop-active");
                }));

            choice.data("esel2-data", data);*/
            
choice.insertBefore(this.searchContainer);

            
val.push(id);
            
this.setVal(val);
        },

        
// multi
        
unselect: function (selected) {
            var 
val this.getVal(),
                
data,
                
index;

            
selected selected.closest(".esel2-search-choice");

            if (
selected.length === 0) {
                throw 
"Invalid argument: " selected ". Must be .esel2-search-choice";
            }

            
data selected.data("esel2-data");

            
index indexOf(this.id(data), val);

            if (
index >= 0) {
                
val.splice(index1);
                
this.setVal(val);
                if (
this.selectthis.postprocessResults();
            }
            
selected.remove();
            
this.triggerChange({ removeddata });
        },

        
// multi
        
postprocessResults: function () {
            var 
val this.getVal(),
                
choices this.results.find(".esel2-result-selectable"),
                
compound this.results.find(".esel2-result-with-children"),
                
self this;

            
choices.each2(function (ichoice) {
                var 
id self.id(choice.data("esel2-data"));
                if (
indexOf(idval) >= 0) {
                    
choice.addClass("esel2-disabled").removeClass("esel2-result-selectable");
                } else {
                    
choice.removeClass("esel2-disabled").addClass("esel2-result-selectable");
                }
            });

            
compound.each2(function(ie) {
                if (
e.find(".esel2-result-selectable").length==0) {
                    
e.addClass("esel2-disabled");
                } else {
                    
e.removeClass("esel2-disabled");
                }
            });

            
choices.each2(function (ichoice) {
                if (!
choice.hasClass("esel2-disabled") && choice.hasClass("esel2-result-selectable")) {
                    
self.highlight(0);
                    return 
false;
                }
            });

        },

        
// multi
        
resizeSearch: function () {

            var 
minimumWidthleftmaxWidthcontainerLeftsearchWidth,
                
sideBorderPadding getSideBorderPadding(this.search);

            
minimumWidth measureTextWidth(this.search) + 10;

            
left this.search.offset().left;

            
maxWidth this.selection.width();
            
containerLeft this.selection.offset().left;

            
searchWidth maxWidth - (left containerLeft) - sideBorderPadding;
            if (
searchWidth minimumWidth) {
                
searchWidth maxWidth sideBorderPadding;
            }

            if (
searchWidth 40) {
                
searchWidth maxWidth sideBorderPadding;
            }
            
this.search.width(searchWidth);
        },

        
// multi
        
getVal: function () {
            var 
val;
            if (
this.select) {
                
val this.select.val();
                return 
val === null ? [] : val;
            } else {
                
val this.opts.element.val();
                return 
splitVal(valthis.opts.separator);
            }
        },

        
// multi
        
setVal: function (val) {
            var 
unique;
            if (
this.select) {
                
this.select.val(val);
            } else {
                
unique = [];
                
// filter out duplicates
                
$(val).each(function () {
                    if (
indexOf(thisunique) < 0unique.push(this);
                });
                
this.opts.element.val(unique.length === "" unique.join(this.opts.separator));
            }
        },

        
// multi
        
val: function () {
            var 
valdata = [], self=this;

            if (
arguments.length === 0) {
                return 
this.getVal();
            }

            
val arguments[0];

            if (!
val) {
                
this.opts.element.val("");
                
this.updateSelection([]);
                
this.clearSearch();
                return;
            }

            
// val is a list of ids
            
this.setVal(val);

            if (
this.select) {
                
this.select.find(":selected").each(function () {
                    
data.push({id: $(this).attr("value"), text: $(this).text()});
                });
                
this.updateSelection(data);
            } else {
                if (
this.opts.initSelection === undefined) {
                    throw new 
Error("val() cannot be called if initSelection() is not defined")
                }

                
this.opts.initSelection(this.opts.element, function(data){
                    var 
ids=$(data).map(self.id);
                    
self.setVal(ids);
                    
self.updateSelection(data);
                    
self.clearSearch();
                });
            }
            
this.clearSearch();
        },

        
// multi
        
onSortStart: function() {
            if (
this.select) {
                throw new 
Error("Sorting of elements is not supported when attached to <select>. Attach to <input type='hidden'/> instead.");
            }

            
// collapse search field into 0 width so its container can be collapsed as well
            
this.search.width(0);
            
// hide the container
            
this.searchContainer.hide();
        },

        
// multi
        
onSortEnd:function() {

            var 
val=[], self=this;

            
// show search and move it to the end of the list
            
this.searchContainer.show();
            
// make sure the search container is the last item in the list
            
this.searchContainer.appendTo(this.searchContainer.parent());
            
// since we collapsed the width in dragStarted, we resize it here
            
this.resizeSearch();

            
// update selection

            
this.selection.find(".esel2-search-choice").each(function() {
                
val.push(self.opts.id($(this).data("esel2-data")));
            });
            
this.setVal(val);
            
this.triggerChange();
        },

        
// multi
        
data: function(values) {
            var 
self=thisids;
            if (
arguments.length === 0) {
                return 
this.selection
                    
.find(".esel2-search-choice")
                    .
map(function() { return $(this).data("esel2-data"); })
                    .
get();
            } else {
                if (!
values) { values = []; }
                
ids = $.map(values, function(e) { return self.opts.id(e)});
                
this.setVal(ids);
                
this.updateSelection(values);
                
this.clearSearch();
            }
        }
    });

    $.
fn.esel2 = function () {

        var 
args = Array.prototype.slice.call(arguments0),
            
opts,
            
esel2,
            
valuemultipleallowedMethods = ["val""destroy""opened""open""close""focus""isFocused""container""onSortStart""onSortEnd""enable""disable""positionDropdown""data"];

        
this.each(function () {
            if (
args.length === || typeof(args[0]) === "object") {
                
opts args.length === ? {} : $.extend({}, args[0]);
                
opts.element = $(this);

                if (
opts.element.get(0).tagName.toLowerCase() === "select") {
                    
multiple opts.element.attr("multiple");
                } else {
                    
multiple opts.multiple || false;
                    if (
"tags" in opts) {opts.multiple multiple true;}
                }

                
esel2 multiple ? new MultiEsel2() : new SingleEsel2();
                
esel2.init(opts);
            } else if (
typeof(args[0]) === "string") {

                if (
indexOf(args[0], allowedMethods) < 0) {
                    throw 
"Unknown method: " args[0];
                }

                
value undefined;
                
esel2 = $(this).data("esel2");
                if (
esel2 === undefined) return;
                if (
args[0] === "container") {
                    
value=esel2.container;
                } else {
                    
value esel2[args[0]].apply(esel2args.slice(1));
                }
                if (
value !== undefined) {return false;}
            } else {
                throw 
"Invalid arguments to esel2 plugin: " args;
            }
        });
        return (
value === undefined) ? this value;
    };

    
// plugin defaults, accessible to users
    
$.fn.esel2.defaults = {
        
width"copy",
        
closeOnSelecttrue,
        
openOnEntertrue,
        
containerCss: {},
        
dropdownCss: {},
        
containerCssClass"",
        
dropdownCssClass"",
        
formatResult: function(resultcontainerquery) {
            var 
markup=[];
            
markMatch(result.textquery.termmarkup);
            return 
markup.join("");
        },
        
formatSelection: function (datacontainer) {
            return 
data data.text undefined;
        },
        
formatResultCssClass: function(data) {return undefined;},
        
formatNoMatches: function () { return "No matches found"; },
        
formatInputTooShort: function (inputmin) { return "Please enter " + (min input.length) + " more characters"; },
        
formatSelectionTooBig: function (limit) { return "You can only select " limit " item" + (limit == "" "s"); },
        
formatLoadMore: function (pageNumber) { return "Loading more results..."; },
        
formatSearching: function () { return "Searching..."; },
        
postRender: function( term ) { },
        
minimumResultsForSearch0,
        
minimumInputLength0,
        
maximumSelectionSize0,
        
id: function (e) { return e.id; },
        
matcher: function(termtext) {
            return 
text.toUpperCase().indexOf(term.toUpperCase()) >= 0;
        },
        
separator",",
        
tokenSeparators: [],
        
tokenizerdefaultTokenizer,
        
escapeMarkup: function (markup) {
            if (
markup && typeof(markup) === "string") {
                return 
markup.replace(/&/g"&amp;");
            }
            return 
markup;
        },
        
blurOnChangefalse
    
};

    
// exports
    
window.OCSEsel2 = {
        
query: {
            
ajaxajax,
            
locallocal,
            
tagstags
        
}, util: {
            
debouncedebounce,
            
markMatchmarkMatch
        
}, "class": {
            
"abstract"AbstractEsel2,
            
"single"SingleEsel2,
            
"multi"MultiEsel2
        
}
    };

}(
jQuery));
?>
Онлайн: 2
Реклама