/**
 * jQuery jEC (jQuery Editable Combobox) 1.3.1
 * http://code.google.com/p/jquery-jec
 *
 * Copyright (c) 2008-2009 Lukasz Rajchel (lukasz@rajchel.pl | http://rajchel.pl)
 * Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php)
 * and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses.
 *
 * Documentation :  http://code.google.com/p/jquery-jec/wiki/Documentation
 * Changelog     :  http://code.google.com/p/jquery-jec/wiki/Changelog
 *
 * Contributors  :  Lukasz Rajchel, Artem Orlov
 */

/*jslint white: true, onevar: true, undef: true, nomen: true, eqeqeq: true, plusplus: true,
bitwise: true, regexp: true, strict: true, newcap: true, immed: true, maxerr: 50, indent: 4, 
maxlen: 100*/
/*global Array, Math, String, clearInterval, document, jQuery, setInterval*/
/*members ':', Handle, Remove, Set, acceptedKeys, addClass, all, append, appendTo, array, attr, 
before, bind, blinkingCursor, blinkingCursorInterval, blur, bool, browser, ceil, change, charCode, 
classes, clearCursor, click, css, cursorState, data, destroy, disable, each, editable, enable, eq, 
expr, extend, filter, find, floor, fn, focus, focusOnNewOption, fromCharCode, get, getId, 
handleCursor, ignoredKeys, ignoreOptGroups, inArray, init, initJS, integer, isArray, isPlainObject, 
jEC, jECTimer, jec, jecKill, jecOff, jecOn, jecPref, jecValue, keyCode, keyDown, keyPress, 
keyRange, keyUp, keys, length, max, maxLength, min, msie, object, openedState, optionClasses, 
optionStyles, parent, position, pref, push, random, remove, removeAttr, removeClass, removeData, 
safari, setEditableOption, styles, substring, text, trigger triggerChangeEvent, unbind, uneditable, 
useExistingOptions, val, value, valueIsEditable*/
'use strict';
(function ($) {

    $.jEC = (function () {
        var pluginClass = 'jecEditableOption', cursorClass = 'hasCursor', options = {},
            values = {}, lastKeyCode, defaults, Validators, EventHandlers, Combobox,
            activeCombobox;

        defaults = {
            position: 0,
            ignoreOptGroups: false,
            maxLength: 255,
            classes: [],
            styles: {},
            optionClasses: [],
            optionStyles: {},
            triggerChangeEvent: false,
            focusOnNewOption: false,
            useExistingOptions: false,
            blinkingCursor: false,
            blinkingCursorInterval: 1000,
            ignoredKeys: [],
            acceptedKeys: [[32, 126], [191, 382]]
        };

        Validators = (function () {
            return {
                integer: function (value) {
                    return typeof value === 'number' && Math.ceil(value) === Math.floor(value);
                },

                keyRange: function (value) {
                    var min, max;
                    if (typeof value === 'object' && !$.isArray(value)) {
                        min = value.min;
                        max = value.max;
                    } else if ($.isArray(value) && value.length === 2) {
                        min = value[0];
                        max = value[1];
                    }
                    return Validators.integer(min) && Validators.integer(max) && min <= max;
                }
            };
        }());

        EventHandlers = (function () {
            var getKeyCode;

            getKeyCode = function (event) {
                var charCode = event.charCode;
                if (charCode !== undefined && charCode !== 0) {
                    return charCode;
                } else {
                    return event.keyCode;
                }
            };

            return {
                // focus event handler
                // enables blinking cursor
                focus: function (event) {
                    var opt = options[Combobox.getId($(this))];
                    if (opt.blinkingCursor && $.jECTimer === undefined) {
                        activeCombobox = $(this);
                        $.jECTimer = setInterval($.jEC.handleCursor, opt.blinkingCursorInterval);
                    }
                },

                // blur event handler
                // disables blinking cursor
                blur: function (event) {
                    if ($.jECTimer !== undefined) {
                        clearInterval($.jECTimer);
                        $.jECTimer = undefined;
                        activeCombobox = undefined;
                        Combobox.clearCursor($(this));
                    }
                    Combobox.openedState($(this), false);
                },

                // keydown event handler
                // handles keys pressed on select (backspace and delete must be handled
                // in keydown event in order to work in IE)
                keyDown: function (event) {
                    var keyCode = getKeyCode(event), option, value;

                    lastKeyCode = keyCode;

                    switch (keyCode) {
                    case 8:  // backspace
                    case 46: // delete
                        option = $(this).find('option.' + pluginClass);
                        if (option.val().length >= 1) {
                            value = option.text().substring(0, option.text().length - 1);
                            option.val(value).text(value).prop('selected', true);
                        }
                        return (keyCode !== 8);
                    default:
                        break;
                    }
                },

                // keypress event handler
                // handles the rest of the keys (keypress event gives more informations
                // about pressed keys)
                keyPress: function (event) {
                    var keyCode = getKeyCode(event), opt = options[Combobox.getId($(this))],
                        option, value, specialKeys, exit = false, text;

                    Combobox.clearCursor($(this));
                    if (keyCode !== 9 && keyCode !== 13 && keyCode !== 27) {
                        // special keys codes
                        specialKeys = [37, 38, 39, 40, 46];
                        // handle special keys
                        $.each(specialKeys, function (i, val) {
                            if (keyCode === val && keyCode === lastKeyCode) {
                                exit = true;
                            }
                        });

                        // don't handle ignored keys
                        if (!exit && $.inArray(keyCode, opt.ignoredKeys) === -1) {
                        	
                            // remove selection from all options
                            $(this).find('option:selected').prop('selected',false);

                            if ($.inArray(keyCode, opt.acceptedKeys) !== -1) {
                                option = $(this).find('option.' + pluginClass).first();
                                text = option.text();
                                
                                if (text.length < opt.maxLength) {
                                    value = text + String.fromCharCode(keyCode);
                                    option.val(value).text(value).attr('label',value);
                                }
                                
                                option.prop('selected', true);
                            }
                        }

                        return false;
                    }
                },
                
                keyUp: function (event) {
                    var opt = options[Combobox.getId($(this))];
                    if (opt.triggerChangeEvent) {
                        $(this).trigger('change');
                    }
                },

                // change event handler
                // handles editable option changing based on a pre-existing values
                change: function () {
                    var opt = options[Combobox.getId($(this))];
                    if (opt.useExistingOptions) {
                        Combobox.setEditableOption($(this));
                    }
                },

                click: function () {
                    if (!$.browser.safari) {
                        Combobox.openedState($(this), !Combobox.openedState($(this)));
                    }
                }
            };
        }());

        // Combobox
        Combobox = (function () {
            var Parameters, EditableOption, generateId, setup;

            // validates and set combobox parameters
            Parameters = (function () {
                var Set, Remove, Handle;

                Set = (function () {
                    var parseKeys, Handles;
                    
                    parseKeys = function (value) {
                        var keys = [];
                        if ($.isArray(value)) {
                            $.each(value, function (i, val) {
                                var j, min, max;
                                if (Validators.keyRange(val)) {
                                    if ($.isArray(val)) {
                                        min = val[0];
                                        max = val[1];
                                    } else {
                                        min = val.min;
                                        max = val.max;
                                    }
                                    for (j = min; j <= max; j += 1) {
                                        keys.push(j);
                                    }
                                } else if (typeof val === 'number' && Validators.integer(val)) {
                                    keys.push(val);
                                }
                            });
                        }
                        return keys;
                    };
                    
                    Handles = (function () {
                        return {
                            integer: function (elem, name, value) {
                                var id = Combobox.getId(elem), opt = options[id];
                                if (opt !== undefined && Validators.integer(value)) {
                                    opt[name] = value;
                                    return true;
                                }
                                return false;
                            },
                            bool: function (elem, name, value) {
                                var id = Combobox.getId(elem), opt = options[id];
                                if (opt !== undefined && typeof value === 'boolean') {
                                    opt[name] = value;
                                    return true;
                                }
                                return false;
                            },
                            array: function (elem, name, value) {
                                if (typeof value === 'string') {
                                    value = [value];
                                }
                                var id = Combobox.getId(elem), opt = options[id];
                                if (opt !== undefined && $.isArray(value)) {
                                    opt[name] = value;
                                    return true;
                                }
                                return false;
                            },
                            object: function (elem, name, value) {
                                var id = Combobox.getId(elem), opt = options[id];
                                if (opt !== undefined && value !== null && 
                                    typeof value === 'object' && !$.isArray(value)) {
                                    opt[name] = value;
                                }
                            },
                            keys: function (elem, name, value) {
                                var id = Combobox.getId(elem), opt = options[id];
                                if (opt !== undefined && $.isArray(value)) {
                                    opt[name] = parseKeys(value);
                                }
                            }
                        };
                    }());

                    return {
                        position: function (elem, value) {
                            if (Handles.integer(elem, 'position', value)) {
                                var id = Combobox.getId(elem), opt = options[id], optionsCount;
                                optionsCount =
                                    elem.find('option:not(.' + pluginClass + ')').length;
                                if (value > optionsCount) {
                                    opt.position = optionsCount;
                                }
                            }
                        },
                        
                        ignoreOptGroups: function (elem, value) {
                            Handles.bool(elem, 'ignoreOptGroups', value);
                        },
                        
                        maxLength: function (elem, value) {
                            if (Handles.integer(elem, 'maxLength', value)) {
                                var id = Combobox.getId(elem), opt = options[id];
                                if (value < 0 || value > 255) {
                                    opt.maxLength = 255;
                                }
                            }
                        },

                        classes: function (elem, value) {
                            Handles.array(elem, 'classes', value);
                        },

                        optionClasses: function (elem, value) {
                            Handles.array(elem, 'optionClasses', value);
                        },

                        styles: function (elem, value) {
                            Handles.object(elem, 'styles', value);
                        },

                        optionStyles: function (elem, value) {
                            Handles.object(elem, 'optionStyles', value);
                        },
                        
                        triggerChangeEvent: function (elem, value) {
                            Handles.bool(elem, 'triggerChangeEvent', value);
                        },

                        focusOnNewOption: function (elem, value) {
                            Handles.bool(elem, 'focusOnNewOption', value);
                        },

                        useExistingOptions: function (elem, value) {
                            Handles.bool(elem, 'useExistingOptions', value);
                        },

                        blinkingCursor: function (elem, value) {
                            Handles.bool(elem, 'blinkingCursor', value);
                        },

                        blinkingCursorInterval: function (elem, value) {
                            Handles.integer(elem, 'blinkingCursorInterval', value);
                        },

                        ignoredKeys: function (elem, value) {
                            Handles.keys(elem, 'ignoredKeys', value);
                        },

                        acceptedKeys: function (elem, value) {
                            Handles.keys(elem, 'acceptedKeys', value);
                        }
                    };
                }());

                Remove = (function () {
                    var removeClasses, removeStyles;

                    removeClasses = function (elem, classes) {
                        $.each(classes, function (i, val) {
                            elem.removeClass(val);
                        });
                    };

                    removeStyles = function (elem, styles) {
                        $.each(styles, function (key, val) {
                            elem.css(key, '');
                        });
                    };

                    return {
                        classes: function (elem) {
                            var id = Combobox.getId(elem), opt = options[id];
                            if (opt !== undefined) {
                                removeClasses(elem, opt.classes);
                            }
                        },

                        optionClasses: function (elem) {
                            var id = Combobox.getId(elem), opt = options[id];
                            if (opt !== undefined) {
                                removeClasses(elem.find('option.' + pluginClass),
                                    opt.optionClasses);
                            }
                        },

                        styles: function (elem) {
                            var id = Combobox.getId(elem), opt = options[id];
                            if (opt !== undefined) {
                                removeStyles(elem, opt.styles);
                            }
                        },

                        optionStyles: function (elem) {
                            var id = Combobox.getId(elem), opt = options[id];
                            if (opt !== undefined) {
                                removeStyles(elem.find('option.' + pluginClass),
                                    opt.optionStyles);
                            }
                        },

                        all: function (elem) {
                            Remove.classes(elem);
                            Remove.optionClasses(elem);
                            Remove.styles(elem);
                            Remove.optionStyles(elem);
                        }
                    };
                }());

                Handle = (function () {
                    var setClasses, setStyles;

                    setClasses = function (elem, classes) {
                        $.each(classes, function (i, val) {
                            elem.addClass(val);
                        });
                    };

                    setStyles = function (elem, styles) {
                        $.each(styles, function (key, val) {
                            elem.css(key, val);
                        });
                    };

                    return {
                        position: function (elem) {
                            var opt = options[Combobox.getId(elem)], option, uneditableOptions,
                                container;
                            option = elem.find('option.' + pluginClass);

                            uneditableOptions = elem.find('option:not(.' + pluginClass + ')');
                            if (opt.position < uneditableOptions.length) {
                                container = uneditableOptions.eq(opt.position);
                                
                                if (!opt.ignoreOptGroups && 
                                    container.parent('optgroup').length > 0) {
                                    uneditableOptions.eq(opt.position).parent().before(option);
                                } else {
                                    uneditableOptions.eq(opt.position).before(option);
                                }
                            } else {
                                elem.append(option);
                            }
                        },

                        classes: function (elem) {
                            var id = Combobox.getId(elem), opt = options[id];
                            if (opt !== undefined) {
                                setClasses(elem, opt.classes);
                            }
                        },

                        optionClasses: function (elem) {
                            var id = Combobox.getId(elem), opt = options[id];
                            if (opt !== undefined) {
                                setClasses(elem.find('option.' + pluginClass),
                                    opt.optionClasses);
                            }
                        },

                        styles: function (elem) {
                            var id = Combobox.getId(elem), opt = options[id];
                            if (opt !== undefined) {
                                setStyles(elem, opt.styles);
                            }
                        },

                        optionStyles: function (elem) {
                            var id = Combobox.getId(elem), opt = options[id];
                            if (opt !== undefined) {
                                setStyles(elem.find('option.' + pluginClass),
                                    opt.optionStyles);
                            }
                        },

                        focusOnNewOption: function (elem) {
                            var id = Combobox.getId(elem), opt = options[id];
                            if (opt !== undefined && opt.focusOnNewOption) {
                                elem.find('option.' + pluginClass)
                                    .prop('selected', true);
                            }
                        },

                        useExistingOptions: function (elem) {
                            var id = Combobox.getId(elem), opt = options[id];
                            if (opt !== undefined && opt.useExistingOptions) {
                                Combobox.setEditableOption(elem);
                            }
                        },

                        all: function (elem) {
                            Handle.position(elem);
                            Handle.classes(elem);
                            Handle.optionClasses(elem);
                            Handle.styles(elem);
                            Handle.optionStyles(elem);
                            Handle.focusOnNewOption(elem);
                            Handle.useExistingOptions(elem);
                        }
                    };
                }());

                return {
                    Set: Set,
                    Remove: Remove,
                    Handle: Handle
                };
            }());

            EditableOption = (function () {
                return {
                    init: function (elem) {
                        if (!elem.find('option.' + pluginClass).length) {
                            var editableOption = $('<option>');
                            editableOption.addClass(pluginClass);
                            elem.append(editableOption);
                        }

                        elem.on('keydown', EventHandlers.keyDown);
                        elem.on('keypress', EventHandlers.keyPress);
                        elem.on('keyup', EventHandlers.keyUp);
                        elem.on('change', EventHandlers.change);
                        elem.on('focus', EventHandlers.focus);
                        elem.on('blur', EventHandlers.blur);
                        elem.on('click', EventHandlers.click);
                    },

                    destroy: function (elem) {
                        elem.find('option.' + pluginClass).remove();
                        elem.off('keydown', EventHandlers.keyDown);
                        elem.off('keypress', EventHandlers.keyPress);
                        elem.off('keyup', EventHandlers.keyUp);
                        elem.off('change', EventHandlers.change);
                        elem.off('focus', EventHandlers.focus);
                        elem.off('blur', EventHandlers.blur);
                        elem.off('click', EventHandlers.click);
                    }
                };
            }());

            // generates unique identifier
            generateId = function () {
                while (true) {
                    var random = Math.floor(Math.random() * 100000);

                    if (options[random] === undefined) {
                        return random;
                    }
                }
            };

            // sets combobox
            setup = function (elem) {
                EditableOption.init(elem);
                Parameters.Handle.all(elem);
            };

            // Combobox public members
            return {
                // create editable combobox
                init: function (settings) {
                    return $(this).filter(':uneditable').each(function () {
                    
                        var id = generateId(), elem = $(this);

                        elem.data('jecId', id);

                        // override passed default options
                        options[id] = $.extend(true, {}, defaults);

                        // parse keys
                        Parameters.Set.ignoredKeys(elem, options[id].ignoredKeys);
                        Parameters.Set.acceptedKeys(elem, options[id].acceptedKeys);

                        if (typeof settings === 'object' && !$.isArray(settings)) {
                            $.each(settings, function (key, val) {
                                if (val !== undefined) {
                                    switch (key) {
                                    case 'position':
                                        Parameters.Set.position(elem, val);
                                        break;
                                    case 'ignoreOptGroups':
                                        Parameters.Set.ignoreOptGroups(elem, val);
                                        break;
                                    case 'maxLength':
                                        Parameters.Set.maxLength(elem, val);
                                        break;
                                    case 'classes':
                                        Parameters.Set.classes(elem, val);
                                        break;
                                    case 'optionClasses':
                                        Parameters.Set.optionClasses(elem, val);
                                        break;
                                    case 'styles':
                                        Parameters.Set.styles(elem, val);
                                        break;
                                    case 'optionStyles':
                                        Parameters.Set.optionStyles(elem, val);
                                        break;
                                    case 'triggerChangeEvent':
                                        Parameters.Set.triggerChangeEvent(elem, val);
                                        break;
                                    case 'focusOnNewOption':
                                        Parameters.Set.focusOnNewOption(elem, val);
                                        break;
                                    case 'useExistingOptions':
                                        Parameters.Set.useExistingOptions(elem, val);
                                        break;
                                    case 'blinkingCursor':
                                        Parameters.Set.blinkingCursor(elem, val);
                                        break;
                                    case 'blinkingCursorInterval':
                                        Parameters.Set.blinkingCursorInterval(elem, val);
                                        break;
                                    case 'ignoredKeys':
                                        Parameters.Set.ignoredKeys(elem, val);
                                        break;
                                    case 'acceptedKeys':
                                        Parameters.Set.acceptedKeys(elem, val);
                                        break;
                                    }
                                }
                            });
                        }

                        setup($(this));
                    });
                },

                // creates editable combobox without using existing select elements
                initJS: function (options, settings) {
                    var select, addOptions;

                    select = $('<select>');
                    
                    addOptions = function (elem, options) {
                        if ($.isArray(options)) {
                            $.each(options, function (i, val) {
                                if ($.isPlainObject(val)) {
                                    $.each(val, function (key, value) {
                                        if ($.isArray(value)) {
                                            var og = $('<optgroup>').attr('label', key);
                                            addOptions(og, value);
                                            og.appendTo(select);
                                        } else if (typeof value === 'number' || 
                                            typeof value === 'string') {
                                            $('<option>').text(value).prop('value', key)
                                                .appendTo(elem);
                                        }
                                    });
                                } else if (typeof val === 'string' || typeof val === 'number') {
                                    $('<option>').text(val).prop('value', val).appendTo(elem);
                                }
                            });
                        }
                    };
                    
                    addOptions(select, options);

                    return select.jec(settings);
                },

                // destroys editable combobox
                destroy: function () {
                    return $(this).filter(':editable').each(function () {
                        $(this).jecOff();
                        $.removeData($(this).get(0), 'jecId');
                        $.removeData($(this).get(0), 'jecCursorState');
                        $.removeData($(this).get(0), 'jecOpenedState');
                    });
                },

                // enable editablecombobox
                enable: function () {
                    return $(this).filter(':editable').each(function () {
                        var id = Combobox.getId($(this)), value = values[id];

                        setup($(this));

                        if (value !== undefined) {
                            $(this).jecValue(value);
                        }
                    });
                },

                // disable editable combobox
                disable: function () {
                    return $(this).filter(':editable').each(function () {
                        var val = $(this).find('option.' + pluginClass).val();
                        values[Combobox.getId($(this))] = val;
                        Parameters.Remove.all($(this));
                        EditableOption.destroy($(this));
                    });
                },

                // gets or sets editable option's value
                value: function (value, setFocus) {
                    if ($(this).filter(':editable').length > 0) {
                        if (value === null || value === undefined) {
                            // get value
                            return $(this).find('option.' + pluginClass).val();
                        } else if (typeof value === 'string' || typeof value === 'number') {
                            // set value
                            return $(this).filter(':editable').each(function () {
                                var option = $(this).find('option.' + pluginClass);
                                option.val(value).text(value);
                                if (typeof setFocus !== 'boolean' || setFocus) {
                                    option.prop('selected', true);
                                }
                            });
                        }
                    }
                },

                // gets or sets editable option's preference
                pref: function (name, value) {
                    if ($(this).filter(':editable').length > 0) {
                        if (typeof name === 'string') {
                            if (value === null || value === undefined) {
                                // get preference
                                return options[Combobox.getId($(this))][name];
                            } else {
                                // set preference
                                return $(this).filter(':editable').each(function () {
                                    switch (name) {
                                    case 'position':
                                        Parameters.Set.position($(this), value);
                                        Parameters.Handle.position($(this));
                                        break;
                                    case 'classes':
                                        Parameters.Remove.classes($(this));
                                        Parameters.Set.classes($(this), value);
                                        Parameters.Handle.position($(this));
                                        break;
                                    case 'optionClasses':
                                        Parameters.Remove.optionClasses($(this));
                                        Parameters.Set.optionClasses($(this), value);
                                        Parameters.Set.optionClasses($(this));
                                        break;
                                    case 'styles':
                                        Parameters.Remove.styles($(this));
                                        Parameters.Set.styles($(this), value);
                                        Parameters.Set.styles($(this));
                                        break;
                                    case 'optionStyles':
                                        Parameters.Remove.optionStyles($(this));
                                        Parameters.Set.optionStyles($(this), value);
                                        Parameters.Handle.optionStyles($(this));
                                        break;
                                    case 'focusOnNewOption':
                                        Parameters.Set.focusOnNewOption($(this), value);
                                        Parameters.Handle.focusOnNewOption($(this));
                                        break;
                                    case 'useExistingOptions':
                                        Parameters.Set.useExistingOptions($(this), value);
                                        Parameters.Handle.useExistingOptions($(this));
                                        break;
                                    case 'blinkingCursor':
                                        Parameters.Set.blinkingCursor($(this), value);
                                        break;
                                    case 'blinkingCursorInterval':
                                        Parameters.Set.blinkingCursorInterval($(this), value);
                                        break;
                                    case 'ignoredKeys':
                                        Parameters.Set.ignoredKeys($(this), value);
                                        break;
                                    case 'acceptedKeys':
                                        Parameters.Set.acceptedKeys($(this), value);
                                        break;
                                    }
                                });
                            }
                        }
                    }
                },

                // sets editable option to the value of currently selected option
                setEditableOption: function (elem) {
                    var value = elem.find('option:selected').text();
                    elem.find('option.' + pluginClass).prop('value', elem.val())
                        .text(value).prop('selected', true);
                },

                // get combobox id
                getId: function (elem) {
                    return elem.data('jecId');
                },

                valueIsEditable: function (elem) {
                    return elem.find('option.' + pluginClass).get(0) ===
                        elem.find('option:selected').get(0);
                },

                clearCursor: function (elem) {
                    $(elem).find('option.' + cursorClass).each(function () {
                        var text = $(this).text();
                        $(this).removeClass(cursorClass).text(text.substring(0, text.length - 1));
                    });
                },

                cursorState: function (elem, state) {
                    return elem.data('jecCursorState', state);
                },

                openedState: function (elem, state) {
                    return elem.data('jecOpenedState', state);
                },

                //handles editable cursor
                handleCursor: function () {
                    if (activeCombobox !== undefined && activeCombobox !== null) {
                        if ($.browser.msie && Combobox.openedState(activeCombobox)) {
                            return;
                        }

                        var state = Combobox.cursorState(activeCombobox), elem;
                        if (state) {
                            Combobox.clearCursor(activeCombobox);
                        } else if (Combobox.valueIsEditable(activeCombobox)) {
                            elem = activeCombobox.find('option:selected');
                            elem.addClass(cursorClass).text(elem.text() + '|');
                        }
                        Combobox.cursorState(activeCombobox, !state);
                    }
                }
            };
        }());

        // jEC public members
        return {
            init: Combobox.init,
            enable: Combobox.enable,
            disable: Combobox.disable,
            destroy: Combobox.destroy,
            value: Combobox.value,
            pref: Combobox.pref,
            initJS: Combobox.initJS,
            handleCursor: Combobox.handleCursor
        };
    }());

    // register functions
    $.fn.extend({
        jec: $.jEC.init,
        jecOn: $.jEC.enable,
        jecOff: $.jEC.disable,
        jecKill: $.jEC.destroy,
        jecValue: $.jEC.value,
        jecPref: $.jEC.pref
    });

    $.extend({
        jec: $.jEC.initJS
    });

    // register selectors
    $.extend($.expr[':'], {
        editable: function (a) {
            var data = $(a).data('jecId');
            return data !== null && data !== undefined;
        },

        uneditable: function (a) {
            var data = $(a).data('jecId');
            return data === null || data === undefined;
        }
    });

}(jQuery));