/* * * jQuery MultiComplete * ===================== * Written by Tom Hallam * http://tomhallam.github.com/jQuery-Multicomplete/ * Licenced with the Creative Commons Attribution-ShareAlike 3.0 Unported (CC BY-SA 3.0) licence * See: http://creativecommons.org/licenses/by-sa/3.0/ * */ (function ($) { $.fn.multicomplete = function (options) { // Set up the default options var defaults = { // Debug mode provides verbose error messages debug: true, // Source source: [], // AJAX Method (if source is a URL) method: 'POST', // Minimum length before a search can be triggered minimum_length: 3, // Delay before performing a search, ignored if source is local search_delay: 500, // Case sensitive search? case_sensitive: false, // Highlight found words? highlight_results: true, // Left offset for results panel offset_left: 0, // Top offset for results panel offset_top: 5, // Result template result_template: function(row) { // -- Please fill this in! }, // Events ------- // On result click result_click: null, // On form submit form_submit: null }, // Extend the base object settings = $.extend(defaults, options); // Object to keep track of our timers var timers = [], // And our result groups groups = {}, // The results themselves results = [], // And the query query = ''; // Iterate over the selected elements and apply the behavior. return this.each(function () { multicomplete_init($(this)); }); // Bootstrapper function to attach events to the elements selected function multicomplete_init(el) { // Create a unique ID for this element id = 'multicomplete-' + Math.floor(Math.random() * 1000); $(el).data('multicomplete-id', id); // Make a new key in our timer object timers[id] = {}; // We need to set a timer for input to trigger based on a delay $(el).on('keyup', function() { // Keep a local copy of the value query = $(this).val(); // Clear any old timers window.clearTimeout(timers[id]); // If there's nothing left in the input box, do nothing and hide my result panel if(query.length == 0) { $('.panel-multicomplete-results.' + id).hide(); return; } // Reset the results array results = []; // Make sure the query is hitting the minimum length constraint if(query.length > settings.minimum_length) { // Where are we sending this search? switch(typeof(settings.source)) { case 'string': timers[id] = window.setTimeout(function(){ multicomplete_searchajax(function() { multicomplete_render(el); }); }, settings.search_delay); break; case 'object': multicomplete_searchobject(); multicomplete_render(el); break; } } }).attr('autocomplete', 'off'); // Hide the DIV when someone clicks outside of the result pane. $(document).on('mouseup', function(e) { if($('.panel-multicomplete-results.' + id).has(e.target).length === 0) { $('.panel-multicomplete-results.' + id).hide(); } }); } // Parse a result group function multicomplete_parsegroup(group_results, group_name) { // Loop through the group $(group_results).each(function(index, row) { for(var field in row) { // No numbers (for now) if(typeof row[field] == 'number') { return true; } if(typeof row[field] == 'object') { multicomplete_parsegroup(row); } else { // If we find a result then push into our results array. if(multicomplete_match(row[field])) { results.push({ 'field': field, 'row': row, 'group': group_name }); return true; } } } }); } // Does this string match the query function multicomplete_match(field) { if(settings.case_sensitive == true) { return field.indexOf(query) > -1; } else { return field.toLowerCase().indexOf(query.toLowerCase()) > -1; } } // Search an object function multicomplete_searchobject() { if(settings.source.length == 0) { if(settings.debug == true) { alert('Source was set to an array, but the array was empty.'); } } // Loop through the source for(var group_name in settings.source) { if(settings.source[group_name].length) groups[group_name] = multicomplete_parsegroup(settings.source[group_name], group_name); } } // Search an AJAX endpoint function multicomplete_searchajax(callback) { // Perform the remote call. ajax = $.ajax({ 'type': settings.method, 'url': settings.source, 'dataType': 'json', 'data': { 'query': query }, 'success': function(data) { // Loop through the source for(var group_name in data) { if(data[group_name].length) groups[group_name] = multicomplete_parsegroup(data[group_name], group_name); } // Call the callback callback.call(this, data); }, 'error': function(error) { if(settings.debug == true) { if(error.status == 412) { alert('Your remote data source is not valid JSON! Remember to use double quotes instead of single.'); } if(error.status == 404) { alert('Your remote data source does not exist on this server.'); } if(error.status == 500) { alert('The remote server encountered an error whilst processing your source.'); } } } }); } // Render a search result function multicomplete_render(el) { // Where is the element l = el.offset().left, t = el.offset().top, mc_html_class = 'panel-multicomplete-results ' + el.data('multicomplete-id'), mc_class = '.panel-multicomplete-results.' + el.data('multicomplete-id'); // Is there already a results div for this element? if($(mc_class).length == 0) { // Build the div $('
').css({ 'position': 'absolute', 'left': (l + settings.offset_left), 'top': (t + $(el).height() + settings.offset_top) }).appendTo('body'); } else { $(mc_class).empty().show(); } // Were there any results? if(results.length == 0 && !$(mc_class + ' .panel-no-results').length) { $('