/* * jQuery Plugin: Tokenizing Autocomplete Text Entry * Version 1.1 * * Copyright (c) 2009 James Smith (http://loopj.com) * Licensed jointly under the GPL and MIT licenses, * choose which one suits your project best! * */(function($) {$.fn.tokenInput = function (url, options) {    var settings = $.extend({        url: url,        hintText: "Type in a search term",        noResultsText: "No results",        searchingText: "Searching...",        searchDelay: 300,        minChars: 1,        tokenLimit: null,        jsonContainer: null,        method: "GET",        contentType: "json",        queryParam: "q",        onResult: null    }, options);    settings.classes = $.extend({        tokenList: "token-input-list",        token: "token-input-token",        tokenDelete: "token-input-delete-token",        selectedToken: "token-input-selected-token",        highlightedToken: "token-input-highlighted-token",        dropdown: "token-input-dropdown",        dropdownItem: "token-input-dropdown-item",        dropdownItem2: "token-input-dropdown-item2",        selectedDropdownItem: "token-input-selected-dropdown-item",        inputToken: "token-input-input-token"    }, options.classes);    return this.each(function () {        var list = new $.TokenList(this, settings);    });};$.TokenList = function (input, settings) {    //    // Variables    //    // Input box position "enum"    var POSITION = {        BEFORE: 0,        AFTER: 1,        END: 2    };    // Keys "enum"    var KEY = {        BACKSPACE: 8,        TAB: 9,        RETURN: 13,        ESC: 27,        LEFT: 37,        UP: 38,        RIGHT: 39,        DOWN: 40,        COMMA: 188    };    // Save the tokens    var saved_tokens = [];        // Keep track of the number of tokens in the list    var token_count = 0;    // Basic cache to save on db hits    var cache = new $.TokenList.Cache();    // Keep track of the timeout    var timeout;    // Create a new text input an attach keyup events    var input_box = $("<input type=\"text\">")        .css({            outline: "none"        })        .focus(function () {            if (settings.tokenLimit == null || settings.tokenLimit != token_count) {                show_dropdown_hint();            }        })        .blur(function () {            hide_dropdown();        })        .keydown(function (event) {            var previous_token;            var next_token;            switch(event.keyCode) {                case KEY.LEFT:                case KEY.RIGHT:                case KEY.UP:                case KEY.DOWN:                    if(!$(this).val()) {                        previous_token = input_token.prev();                        next_token = input_token.next();                        if((previous_token.length && previous_token.get(0) === selected_token) || (next_token.length && next_token.get(0) === selected_token)) {                            // Check if there is a previous/next token and it is selected                            if(event.keyCode == KEY.LEFT || event.keyCode == KEY.UP) {                                deselect_token($(selected_token), POSITION.BEFORE);                            } else {                                deselect_token($(selected_token), POSITION.AFTER);                            }                        } else if((event.keyCode == KEY.LEFT || event.keyCode == KEY.UP) && previous_token.length) {                            // We are moving left, select the previous token if it exists                            select_token($(previous_token.get(0)));                        } else if((event.keyCode == KEY.RIGHT || event.keyCode == KEY.DOWN) && next_token.length) {                            // We are moving right, select the next token if it exists                            select_token($(next_token.get(0)));                        }                    } else {                        var dropdown_item = null;                        if(event.keyCode == KEY.DOWN || event.keyCode == KEY.RIGHT) {                            dropdown_item = $(selected_dropdown_item).next();                        } else {                            dropdown_item = $(selected_dropdown_item).prev();                        }                        if(dropdown_item.length) {                            select_dropdown_item(dropdown_item);                        }                        return false;                    }                    break;                case KEY.BACKSPACE:                    previous_token = input_token.prev();                    if(!$(this).val().length) {                        if(selected_token) {                            delete_token($(selected_token));                        } else if(previous_token.length) {                            select_token($(previous_token.get(0)));                        }                        return false;                    } else if($(this).val().length == 1) {                        hide_dropdown();                    } else {                        // set a timeout just long enough to let this function finish.                        setTimeout(function(){do_search(false);}, 5);                    }                    break;                case KEY.TAB:                case KEY.RETURN:                case KEY.COMMA:                  if(selected_dropdown_item) {                    add_token($(selected_dropdown_item));                    return false;                  }                  break;                case KEY.ESC:                  hide_dropdown();                  return true;                default:                    if(is_printable_character(event.keyCode)) {                      // set a timeout just long enough to let this function finish.                      setTimeout(function(){do_search(false);}, 5);                    }                    break;            }        });    // Keep a reference to the original input box    var hidden_input = $(input)                           .hide()                           .focus(function () {                               input_box.focus();                           })                           .blur(function () {                               input_box.blur();                           });    // Keep a reference to the selected token and dropdown item    var selected_token = null;    var selected_dropdown_item = null;    // The list to store the token items in    var token_list = $("<ul />")        .addClass(settings.classes.tokenList)        .insertAfter(hidden_input)        .click(function (event) {            var li = get_element_from_event(event, "li");            if(li && li.get(0) != input_token.get(0)) {                toggle_select_token(li);                return false;            } else {                input_box.focus();                if(selected_token) {                    deselect_token($(selected_token), POSITION.END);                }            }        })        .mouseover(function (event) {            var li = get_element_from_event(event, "li");            if(li && selected_token !== this) {                li.addClass(settings.classes.highlightedToken);            }        })        .mouseout(function (event) {            var li = get_element_from_event(event, "li");            if(li && selected_token !== this) {                li.removeClass(settings.classes.highlightedToken);            }        })        .mousedown(function (event) {            // Stop user selecting text on tokens            var li = get_element_from_event(event, "li");            if(li){                return false;            }        });    // The list to store the dropdown items in    var dropdown = $("<div>")        .addClass(settings.classes.dropdown)        .insertAfter(token_list)        .hide();    // The token holding the input box    var input_token = $("<li />")        .addClass(settings.classes.inputToken)        .appendTo(token_list)        .append(input_box);    init_list();    //    // Functions    //    // Pre-populate list if items exist    function init_list () {        li_data = settings.prePopulate;        if(li_data && li_data.length) {            for(var i in li_data) {                var this_token = $("<li><p>"+li_data[i].name+"</p> </li>")                    .addClass(settings.classes.token)                    .insertBefore(input_token);                $("<span>x</span>")                    .addClass(settings.classes.tokenDelete)                    .appendTo(this_token)                    .click(function () {                        delete_token($(this).parent());                        return false;                    });                $.data(this_token.get(0), "tokeninput", {"id": li_data[i].id, "name": li_data[i].name});                // Clear input box and make sure it keeps focus                input_box                    .val("")                    .focus();                // Don't show the help dropdown, they've got the idea                hide_dropdown();                // Save this token id                var id_string = li_data[i].id + ","                hidden_input.val(hidden_input.val() + id_string);                                //Increment token count                token_count++;                                if(settings.tokenLimit != null && settings.tokenLimit >= li_data.length) {                  input_box.hide();                  hide_dropdown();                }            }        }    }    function is_printable_character(keycode) {        if((keycode >= 48 && keycode <= 90) ||      // 0-1a-z           (keycode >= 96 && keycode <= 111) ||     // numpad 0-9 + - / * .           (keycode >= 186 && keycode <= 192) ||    // ; = , - . / ^           (keycode >= 219 && keycode <= 222)       // ( \ ) '          ) {              return true;          } else {              return false;          }    }    // Get an element of a particular type from an event (click/mouseover etc)    function get_element_from_event (event, element_type) {        var target = $(event.target);        var element = null;        if(target.is(element_type)) {            element = target;        } else if(target.parent(element_type).length) {            element = target.parent(element_type+":first");        }        return element;    }    // Inner function to a token to the list    function insert_token(id, value) {      var this_token = $("<li><p>"+ value +"</p> </li>")      .addClass(settings.classes.token)      .insertBefore(input_token);      // The 'delete token' button      $("<span>x</span>")          .addClass(settings.classes.tokenDelete)          .appendTo(this_token)          .click(function () {              delete_token($(this).parent());              return false;          });      $.data(this_token.get(0), "tokeninput", {"id": id, "name": value});      return this_token;    }    // Add a token to the token list based on user input    function add_token (item) {        var li_data = $.data(item.get(0), "tokeninput");        var this_token = insert_token(li_data.id, li_data.name);        // Clear input box and make sure it keeps focus        input_box            .val("")            .focus();        // Don't show the help dropdown, they've got the idea        hide_dropdown();        // Save this token id        var id_string = li_data.id + ","        hidden_input.val(hidden_input.val() + id_string);                token_count++;                if(settings.tokenLimit != null && settings.tokenLimit >= token_count) {            input_box.hide();            hide_dropdown();        }    }    // Select a token in the token list    function select_token (token) {        token.addClass(settings.classes.selectedToken);        selected_token = token.get(0);        // Hide input box        input_box.val("");        // Hide dropdown if it is visible (eg if we clicked to select token)        hide_dropdown();    }    // Deselect a token in the token list    function deselect_token (token, position) {        token.removeClass(settings.classes.selectedToken);        selected_token = null;        if(position == POSITION.BEFORE) {            input_token.insertBefore(token);        } else if(position == POSITION.AFTER) {            input_token.insertAfter(token);        } else {            input_token.appendTo(token_list);        }        // Show the input box and give it focus again        input_box.focus();    }    // Toggle selection of a token in the token list    function toggle_select_token (token) {        if(selected_token == token.get(0)) {            deselect_token(token, POSITION.END);        } else {            if(selected_token) {                deselect_token($(selected_token), POSITION.END);            }            select_token(token);        }    }    // Delete a token from the token list    function delete_token (token) {        // Remove the id from the saved list        var token_data = $.data(token.get(0), "tokeninput");        // Delete the token        token.remove();        selected_token = null;        // Show the input box and give it focus again        input_box.focus();        // Delete this token's id from hidden input        var str = hidden_input.val()        var start = str.indexOf(token_data.id+",");        var end = str.indexOf(",", start) + 1;        if(end >= str.length) {            hidden_input.val(str.slice(0, start));        } else {            hidden_input.val(str.slice(0, start) + str.slice(end, str.length));        }                token_count--;                if (settings.tokenLimit != null) {            input_box                .show()                .val("")                .focus();        }    }    // Hide and clear the results dropdown    function hide_dropdown () {        dropdown.hide().empty();        selected_dropdown_item = null;    }    function show_dropdown_searching () {        dropdown            .html("<p>"+settings.searchingText+"</p>")            .show();    }    function show_dropdown_hint () {        dropdown            .html("<p>"+settings.hintText+"</p>")            .show();    }    // Highlight the query part of the search term	function highlight_term(value, term) {		return value.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + term + ")(?![^<>]*>)(?![^&;]+;)", "gi"), "<b>$1</b>");	}    // Populate the results dropdown with some results    function populate_dropdown (query, results) {        if(results.length) {            dropdown.empty();            var dropdown_ul = $("<ul>")                .appendTo(dropdown)                .mouseover(function (event) {                    select_dropdown_item(get_element_from_event(event, "li"));                })                .mousedown(function (event) {                    add_token(get_element_from_event(event, "li"));                    return false;                })                .hide();            for(var i in results) {                if (results.hasOwnProperty(i)) {                    var this_li = $("<li>"+highlight_term(results[i].name, query)+"</li>")                                      .appendTo(dropdown_ul);                    if(i%2) {                        this_li.addClass(settings.classes.dropdownItem);                    } else {                        this_li.addClass(settings.classes.dropdownItem2);                    }                    if(i == 0) {                        select_dropdown_item(this_li);                    }                    $.data(this_li.get(0), "tokeninput", {"id": results[i].id, "name": results[i].name});                }            }            dropdown.show();            dropdown_ul.slideDown("fast");        } else {            dropdown                .html("<p>"+settings.noResultsText+"</p>")                .show();        }    }    // Highlight an item in the results dropdown    function select_dropdown_item (item) {        if(item) {            if(selected_dropdown_item) {                deselect_dropdown_item($(selected_dropdown_item));            }            item.addClass(settings.classes.selectedDropdownItem);            selected_dropdown_item = item.get(0);        }    }    // Remove highlighting from an item in the results dropdown    function deselect_dropdown_item (item) {        item.removeClass(settings.classes.selectedDropdownItem);        selected_dropdown_item = null;    }    // Do a search and show the "searching" dropdown if the input is longer    // than settings.minChars    function do_search(immediate) {        var query = input_box.val().toLowerCase();        if (query && query.length) {            if(selected_token) {                deselect_token($(selected_token), POSITION.AFTER);            }            if (query.length >= settings.minChars) {                show_dropdown_searching();                if (immediate) {                    run_search(query);                } else {                    clearTimeout(timeout);                    timeout = setTimeout(function(){run_search(query);}, settings.searchDelay);                }            } else {                hide_dropdown();            }        }    }    // Do the actual search    function run_search(query) {        var cached_results = cache.get(query);        if(cached_results) {            populate_dropdown(query, cached_results);        } else {			var queryStringDelimiter = settings.url.indexOf("?") < 0 ? "?" : "&";			var callback = function(results) {			  if($.isFunction(settings.onResult)) {			      results = settings.onResult.call(this, results);			  }              cache.add(query, settings.jsonContainer ? results[settings.jsonContainer] : results);              populate_dropdown(query, settings.jsonContainer ? results[settings.jsonContainer] : results);            };                        if(settings.method == "POST") {			    $.post(settings.url + queryStringDelimiter + settings.queryParam + "=" + query, {}, callback, settings.contentType);		    } else {		        $.get(settings.url + queryStringDelimiter + settings.queryParam + "=" + query, {}, callback, settings.contentType);		    }        }    }};// Really basic cache for the results$.TokenList.Cache = function (options) {    var settings = $.extend({        max_size: 50    }, options);    var data = {};    var size = 0;    var flush = function () {        data = {};        size = 0;    };    this.add = function (query, results) {        if(size > settings.max_size) {            flush();        }        if(!data[query]) {            size++;        }        data[query] = results;    };    this.get = function (query) {        return data[query];    };};})(jQuery);