I'm fairly new to JavaScript and jQuery and this is my first attempt at creating a plugin. The code below I've written to parse a JSONP feed from a search engine API (funnelback) using ajax() calls. The returned object is used to create a set of results, some pagination links, a facetted navigation etc, everything you'd expect from a basic search results UI. I suspect there is a fair amount of redundant code, code that could be merged or performance hits written in so I'd appreciate your expert opinion as to what I could modify to improve these aspects.I'd also appreciate any best practice advice I could incorporate to further develop my own skills and knowledge.You'll notice there's a reference to the jQuery version used, this is because this plugin can be used with a range of CMS templates that have anything from jQuery 1.3.2 to 1.7 and beyond, so I need to cover the bases. (in reference to the on(), delegate() and live() methods) (function($) {$.fn.uclfunnelback = function(options) {    // define the default values of parameters    // these can be overwritten from the plugin call    var defaults = {        collection: 'enterprise-case-studies-demo',        query: '!showmeall',        categories: [{facet_group_id: discipline, facet_group_title: Discipline, facet_items: {mathematical-physical-sciences: Mathematical & Physical Sciences, arts-humanities: Arts & Humanities, engineering: Engineering, built-environment: Built Environment, social-historical-science: Social & Historical Science, life-medical-sciences: Life & Medical Sciences, laws: Laws}}, {facet_group_id: mechanisms, facet_group_title: Mechanisms, facet_items: {research-collaborations-studentships: Research Collaborations Studentships, student-engagement: Student Engagement, placements: Placements, business-support: Business Support, consultancy: Consultancy, partnerships: Partnerships, licenses: Licenses, spin-out: Spin Out, subsidiary: Subsidiary}}, {facet_group_id: grand_challenges, facet_group_title: Grand Challenges, facet_items: {sustainable-cities: Sustainable Cities, intercultural-interaction: Intercultural Interaction, global-health: Global Health, human-wellbeing: Human Wellbeing}}]    };    options = $.extend(defaults, options);    // Define the global vars    var jq_version = $().jquery,        facetsSelected = {},        qs,        facetqs,        objid,        categories = {},        facetgroups,        facetTotals = {},        catArray = options.categories,        catLen = catArray.length;    // jq_version - The jquery version being used.    // facetsSelected - The facets that are selected by the facetted navigation, used to generate the facetLabels    // qs - The main query string passed to ajaxCall()    // facetqs - The query string that is generated by the facetted navigation, used to perform the ajax call    // objid - The main id of the container div into which everything is output    // categories - The empty JavaScript object for all of the facet categories, used by facetLabels and checkBoxTotals    // facetgroups - The collections of facets from each facet group    // facetTotals - The JavaScript object for all the total articles matching each category, used by facettedNav and checkBoxTotals    // catArray - Assign the user supplied list of categories to an Array, used by facettedNav & below    // catLen - Predetermine the array length to speed up the for loop, used by facettedNav & below    // Merge each of the category facets together into the JavaScript object 'categories'    for (var i=0; i < catLen; i++) {        facetgroups = catArray[i].facet_items;        $.extend(categories, facetgroups);    }    Object.size = function(obj) {        var size = 0, key;        for (key in obj) {            if (obj.hasOwnProperty(key)) {                size++;            }        }        return size;    };    function AjaxTimer(status) {        // A timer to record the funnelback json response time        var aCounter = 0;        var airSeconds = $('#air_seconds');        var anInterval;        if (status === start) {            anInterval = setInterval(function () {                aCounter += 1;                airSeconds.text(aCounter);            }, 1000);        } else {            clearInterval(anInterval);            anInterval = null;        }    }    function showLoading() {        // Display the AJAX loading icon & the timer if slow to load        $('#awaiting_response').show();        $('#fb-data').hide();        $(#error).hide();        AjaxTimer(start);    }    function ajaxError(request, type, errorThrown) {        // Handle the AJAX load errors        AjaxTimer(stop);        $(#awaiting_response).hide();        $('#fb-data').hide();        $(#error).show();        var message = There was an error requesting the data.;        switch (type) {        case 'timeout':            message += The data took too long to retrieve.;            break;        case 'notmodified':            message += The request was not modified but was retrieved from the cache.;            break;        case 'parsererror':            message += The data was badly formatted.;            break;        default:            message += HTTP Error: ( + request.status +   + request.statusText + ).;        }        message += <br />;        $(#error).append(message);    }    function paginationClicky(pageNo) {        // Click function for the pagination buttons        if ( facetqs ) {            ajaxCall(pageNo, facetqs);        } else {            ajaxCall(pageNo, null);        }    }    function bindPaginationEvent(pagclass, pageNo) {        // Bind the pagination events, with modifications for older versions of jQuery         if (jq_version >= '1.7') {            // As of jQuery 1.7, the .live() method is deprecated. Use .on() to attach event handlers.             $(objid).off('click', pagclass);            $(objid).on('click', pagclass, function(e) {                paginationClicky(pageNo);                e.preventDefault();            });        } else if ( jq_version >= '1.4.3' && jq_version < '1.7') {            // Users of jQuery versions 1.4.3 to 1.7 should use .delegate() in preference to .live().            $(objid).undelegate(pagclass, 'click');            $(objid).delegate(pagclass, 'click', function(e){                paginationClicky(pageNo);                e.preventDefault();            });        } else {            // Users of jquery 1.3.2 have no choice but to use live()            $(pagclass).die('click');            $(pagclass).live('click', function(e){                paginationClicky(pageNo);                e.preventDefault();            });        }    }    function checkboxToggle(checkbox) {        // Toggle function for the checkboxes        if (checkbox.attr(checked)) {            facetsSelected[checkbox.attr('value')] = checkbox.val();        } else {            delete facetsSelected[checkbox.attr('value')];        }        facetFormSubmit();    }    function bindCheckboxToggle(selectorStr) {        // Bind the checkbox toggle events, with modifications for older versions of jQuery        var checkboxSelector = 'input:checkbox.' + selectorStr;        if (jq_version >= '1.7') {            // As of jQuery 1.7, the .live() method is deprecated. Use .on() to attach event handlers.             $(objid).off('change', checkboxSelector);            $(objid).on('change', checkboxSelector, function() {                var $this = $(this);                checkboxToggle($this);            });        } else if ( jq_version >= '1.4.3' && jq_version < '1.7') {            // Users of jQuery versions 1.4.3 to 1.7 should use .delegate() in preference to .live().            $(objid).undelegate(checkboxSelector, 'change');            $(objid).delegate(checkboxSelector, 'change', function(){                var $this = $(this);                checkboxToggle($this);            });        } else {            // Users of jquery 1.3.2 have no choice but to use live()            $(checkboxSelector).die('change');            $(checkboxSelector).live('change', function(){                var $this = $(this);                checkboxToggle($this);            });        }    }    function bindFacetLabel(facetid) {        // Bind the facetLables click events, with modifications for older versions of jQuery        var facetselector = 'a.' + facetid;        var checkboxSelector = 'input:checkbox.' + facetid;        if (jq_version >= '1.7') {            // As of jQuery 1.7, the .live() method is deprecated. Use .on() to attach event handlers.             $(objid).off('click', facetselector);            $(objid).on('click', facetselector, function(e) {                delete facetsSelected[facetid];                $(checkboxSelector).removeAttr('checked');                e.preventDefault();                facetFormSubmit();            });        } else if ( jq_version >= '1.4.3' && jq_version < '1.7') {            // Users of jQuery versions 1.4.3 to 1.7 should use .delegate() in preference to .live().            $(objid).undelegate(facetselector, 'click');            $(objid).delegate(facetselector, 'click', function(e){                delete facetsSelected[facetid];                $(checkboxSelector).removeAttr('checked');                e.preventDefault();                facetFormSubmit();            });        } else {            // Users of jquery 1.3.2 have no choice but to use live()            $(facetselector).die('click');            $(facetselector).live('click', function(e){                delete facetsSelected[facetid];                $(checkboxSelector).removeAttr('checked');                e.preventDefault();                facetFormSubmit();            });        }    }    function facetLabels(totalMatching) {        // Generate the facet labels        var freeText = $(#fbquery).val();        var res, csText;        var facetLen = Object.size(facetsSelected);        var facetoutput = ;        if (totalMatching === 1) {            res =  result;            csText  =  Item;        } else {            res =  results;            csText  =  Items;        }        if (facetLen > 0) {            facetoutput += <div id='pillboxes'><h3>Active Filters</h3><ul>;            for (var prop in facetsSelected) {                if (facetsSelected.hasOwnProperty(prop)) {                    facetoutput += <li><a class='+ prop +' href='#'> + categories[prop] + </a></li>;                    bindFacetLabel(prop);                }            }            facetoutput += </ul><div style='clear:both;'></div></div>;        }        facetoutput += <h3><span class='resultCount'> + totalMatching + </span> ;        if (facetLen === 0 && !freeText) {            facetoutput += csText +  found;        } else if (facetLen === 0 && freeText) {            facetoutput += res +  matched your search for <span class='searchTerm'>' + freeText + '</span>;        } else {            var facetCounter = 0;            for (var facet in facetsSelected) {                if (facetsSelected.hasOwnProperty(facet)) {                    facetCounter++;                    var facetTitle = categories[facetsSelected[facet]];                    if (facetCounter === 1) {                        facetoutput += res +  matched your search for <span class='searchTerm'> + facetTitle + </span>;                    } else if (facetCounter === facetLen - 1) {                        facetoutput += , <span class='searchTerm'> + facetTitle + </span>;                    } else {                        if (!freeText) {                            facetoutput +=  and <span class='searchTerm'> + facetTitle + </span>;                        } else {                            facetoutput += , <span class='searchTerm'> + facetTitle + </span>;                        }                    }                }            }            if (freeText) {                facetoutput +=  and <span class='searchTerm'>' + freeText + '</span>;            }        }        facetoutput += </h3>;        $('.resultcount').html(facetoutput);    }    function searchResults(currStart, data) {        // Generate the basic search results        // the results are derived from the json feed (data).        var i,            res_output = ,            searchItems = data.response.resultPacket.results;        res_output += <ol start=' + currStart + '><br />;        for (i in searchItems) {            if (searchItems.hasOwnProperty(i)) {                res_output += <li><h4><a href=' + searchItems[i].liveUrl + '> + searchItems[i].title + </a></h4><p> + searchItems[i].summary+</p></li>;            }        }        res_output += '</ol>';        $(objid).html(res_output);    }    function pagination (currStart, numPages, currPage, numRanks, nextStart, prevStart) {        // Generate the pagination links              var linksString = <p class='fb-page-nav'>;        var page_class;        if (currStart > 1) {            // previous links            linksString += <a class='fb-previous-result-page' href='#'>Prev 10</a> ;            bindPaginationEvent('.fb-previous-result-page',prevStart);        }        var startRank = 1;        if (numPages > 1) {            for (i=0; i<numPages; i++){                if (i !== currPage) {                    // if the page is not the current page                    page_class = fb-result-page+ (1+i);                    linksString += &nbsp;<a class=' + page_class + '  href='#'> + (1+i) +</a> ;                    bindPaginationEvent('a.' + page_class,startRank);                } else {                    // if the page is the current page                    linksString += &nbsp;<a class='fb-current-result-page' href='#'> + (1+i) + </a>;                    bindPaginationEvent('.fb-current-result-page',startRank);                }                startRank += numRanks;            }        }        if (nextStart) {            // next links            linksString += &nbsp; <a class='fb-next-result-page fb-page-nav' href='#'>Next 10</a>;            bindPaginationEvent('.fb-next-result-page',nextStart);        }        linksString += </p>;        $('.pagination').html(linksString);    }    function contains(array, value) {        // function to determine if a value is contained within an array        // Faster than inArray() and indexOf() and..        // supported by *all* browsers unlike indexOf()        var index = -1,        length = array.length;        while (++index < length) {            if (array[index] === value) {                return true;            }        }        return false;    }    function checkboxTotals(facetData) {        // Calculate the facet checkbox totals        var temp, tally, isMultiFacetted;        // iterate the 'categories' json array        for (var facet in categories) {            if (categories.hasOwnProperty(facet)) {                facetTotals[facet] = 0;            }        }        // iterate the rmcs json object {}        for (var i in facetData) {            if (facetData.hasOwnProperty(i)) {                temp = i.slice(2,i.length);                tally = facetData[i];                isMultiFacetted = contains(temp, ',');                if (isMultiFacetted) {                    // if temp is more than one item                    // split it and make an array                    var token = temp.split(,);                    var tokenlen = token.length;                    // iterate the array                     // and compare the item (j) found with each item in categories object                    // if j and c same then increment facetTotals for the appropriate facet                    for (var j=0; j<tokenlen; j++) {                        for (var c in categories) {                            if (categories.hasOwnProperty(c)) {                                if (token[j] === c) {                                    facetTotals[c] += tally;                                    break;                                }                            }                        }                    }                } else {                    facetTotals[temp] += tally;                }            }        }    }    function resetForm() {        // Event handler to reset the form back to the defaults        $('.reset-button').die('click');        $('.reset-button').live('click', function(){            facetsSelected = {};            ajaxCall(null, null);        });    }    function facettedNav() {        // Generate the facetted Navigation        var facetoutput = '';        // catArray & catLen are global vars.        for (var i = 0; i < catLen; i++) {            // Generate the facet titles from the json objects in catArray            facetoutput += '<h3>' + catArray[i].facet_group_title + '</h3>\n<ul>';            // iterate the json array of the facet iems to generate the checkboxes            var itemsObj = catArray[i].facet_items;            var groupID = catArray[i].facet_group_id;            var metadataClass = 'meta_' + groupID.charAt(0).toUpperCase() + '_sand';            for (var key in itemsObj) {                if (itemsObj.hasOwnProperty(key)) {                    // if there total no of results for any given facet is greater than 0, display the facet checkbox                    if (facetTotals[key] > 0) {                        if (facetsSelected.hasOwnProperty(key)) {                            facetoutput += '<li id=' + key + ' style=opacity: 1;><label><input type=checkbox class=' + key + ' name=' + metadataClass  + ' value=' + key + ' checked>' + itemsObj[key] + ' <span class=count>(' + facetTotals[key] + ')</span> </label></li>';                        } else {                            facetoutput += '<li id=' + key + ' style=opacity: 1;><label><input type=checkbox class=' + key + ' name=' + metadataClass  + ' value=' + key + '>' + itemsObj[key] + ' <span class=count>(' + facetTotals[key] + ')</span> </label></li>';                        }                        bindCheckboxToggle(key);                    } else {                        // else show the facet checkbox but disable it and lowever the labels opacity                        facetoutput += '<li id=' + key + ' style=opacity: 0.5;><label><input type=checkbox class=' + key + ' disabled=disabled name=' + metadataClass  + ' value=' + key + '>' + itemsObj[key] + ' <span class=count>(' + facetTotals[key] + ')</span> </label></li>';                    }                }            }            facetoutput += '</ul>';        }        // Add the reset button        facetoutput += '<input class=reset-button type=reset name=reset value=Reset>';        resetForm();        // Insert the generated output to the appropriate div        $('.facetted-nav').html(facetoutput);    }    function facetFormSubmit() {        // Generte the new query string everytime the form is submit        facetqs = $('#facetsearch').serialize();        ajaxCall(null, facetqs);    }    function parseResults(data){        // Parse the json feed and render the results        // 1st stop the timer        AjaxTimer(stop);        // & show or hide the appropriate divs        $('#awaiting_response').hide();        $('#fb-data').show();        $(#error).hide();        // collate the data from the json feed        var totalMatching = data.response.resultPacket.resultsSummary.totalMatching;        var numRanks = data.response.resultPacket.resultsSummary.numRanks;        var currStart = data.response.resultPacket.resultsSummary.currStart;        var prevStart = data.response.resultPacket.resultsSummary.prevStart;        var nextStart = data.response.resultPacket.resultsSummary.nextStart;        var numPages = Math.ceil(totalMatching/numRanks);        var currPage = Math.floor(currStart/numRanks);        var facetData = data.response.resultPacket.rmcs;        // Calculate the checkbox totals        checkboxTotals(facetData);        // Display the facetted navigation beneath the search        facettedNav();        // Display the heading and the filtering info        facetLabels(totalMatching);        // Display the search results in the appropriate div        searchResults(currStart, data);        // Display the pagination        pagination(currStart, numPages, currPage, numRanks, nextStart, prevStart);        // Bind an event handler to the form submit JavaScript event        $('#facetsearch').submit(function(e) {            facetFormSubmit();            e.preventDefault();        });    }    function ajaxCall(startRank, facetqs) {        // This is the universal ajax call method        // Reset the query string to '' first        qs = '';        // Build the query string for the ajax() call        // Generate the 'query' element so that if facets are checked they concatenate        if (facetqs && facetqs !== 'query=') {            // Trim 'query=' off the beginning of facetqs            var trimmedFacetqs = facetqs.substring(6);            qs += '&query=' + trimmedFacetqs;        } else {            // If no facets checked, set the default 'query' element to options.query            qs += '&query=' + options.query;        }        // Append the collection        qs += '&collection=' + options.collection;        // And append the startRank, if there is one        if (startRank) {            qs += '&start_rank=' + startRank;        }        // set the base funnelback url        var baseurl = 'http://funnelback-uat.ucl.ac.uk/s/search.json?callback=?';        // var testingurl = 'http://localhost:8217/silva/default-funnelback-search/search.json';        $.ajax({            type:'GET',            data: qs,            url: baseurl,            dataType: jsonp,            // url: testingurl,            // dataType: json, // Test dataType            beforeSend: showLoading,            timeout: 5000,            error: ajaxError,            scriptCharset: utf-8,            success: function (json) {                parseResults(json);            }        });    }    // Initiate the plugin    return this.each(function() {        // set some variables        var obj = $(this);        objid = # + obj.attr('id');        // perform the inital ajaxCall        ajaxCall(null, null);    });};})(jQuery);I appreciate any help!!
jQuery plugin: Parsing a JSONP feed using ajax()
javascript;jquery;performance