Question

I am trying to implement JQueryUI's Multiple value autocomplete however I am getting this error at line 464 of the main JQuery file (Jquery-2.1.0.js).

I am not sure of whether it is anything to do with my implementation which is below, or with the way I am referencing the Jquery and Jquery UI in my layout file. Please let me know:

File reference in Layout

<head>
    <meta charset="utf-8" />
    <title>@ViewBag.Title - My ASP.NET MVC Application</title>
    <link href="~/favicon.ico" rel="shortcut icon" type="image/x-icon" />
    <meta name="viewport" content="width=device-width" />

    <link href="~/Content/ui-lightness/jquery-ui-1.10.4.custom.min.css" rel="stylesheet" />
    <link href="~/Content/menuStyles.css" rel="stylesheet" />

    <script src="~/Scripts/jquery-2.1.0.min.js"></script>  
    <script src="~/Scripts/jquery-ui-1.10.4.min.js"></script>

    <script src="~/Scripts/menu_jquery.js"></script>  
    <link href="~/Content/demos.css" rel="stylesheet" />

    @Styles.Render("~/Content/css")
    @Scripts.Render("~/bundles/modernizr")
</head>

@Scripts.Render("~/bundles/jquery")
@Scripts.Render("~/bundles/jqueryui")
@RenderSection("scripts", required: false)

Bundle Config

 public static void RegisterBundles(BundleCollection bundles)
    {
        bundles.Add(new ScriptBundle("~/bundles/jquery").Include(
                    "~/Scripts/jquery-{version}.js"));

        bundles.Add(new ScriptBundle("~/bundles/jqueryui").Include(
                    "~/Scripts/jquery-ui-{version}.js"));

        bundles.Add(new ScriptBundle("~/bundles/jqueryval").Include(
                    "~/Scripts/jquery.unobtrusive*",
                    "~/Scripts/jquery.validate*"));

        // Use the development version of Modernizr to develop with and learn from. Then, when you're
        // ready for production, use the build tool at http://modernizr.com to pick only the tests you need.
        bundles.Add(new ScriptBundle("~/bundles/modernizr").Include(
                    "~/Scripts/modernizr-*"));

        bundles.Add(new StyleBundle("~/Content/css").Include("~/Content/demos.css"));

        bundles.Add(new StyleBundle("~/Content/themes/base/css").Include(
                    "~/Content/themes/base/jquery.ui.core.css",
                    "~/Content/themes/base/jquery.ui.resizable.css",
                    "~/Content/themes/base/jquery.ui.selectable.css",
                    "~/Content/themes/base/jquery.ui.accordion.css",
                    "~/Content/themes/base/jquery.ui.autocomplete.css",
                    "~/Content/themes/base/jquery.ui.button.css",
                    "~/Content/themes/base/jquery.ui.dialog.css",
                    "~/Content/themes/base/jquery.ui.slider.css",
                    "~/Content/themes/base/jquery.ui.tabs.css",
                    "~/Content/themes/base/jquery.ui.datepicker.css",
                    "~/Content/themes/base/jquery.ui.progressbar.css",
                    "~/Content/themes/base/jquery.ui.theme.css"));
    }

And this is how I am implementing the Autocomplete plugin:

I put all of this in a Partial View.

    <div class="ui-widget" style="text-align:left">
        <input id="city"/>
    </div> 

<style>
    .ui-autocomplete-loading {
        background: white url('Images/ui-anim_basic_16x16.gif') right center no-repeat;
    }
    #city { width: 25em; }
    </style>
<script>
    $(function () {

        debugger;
        $('#city').autocomplete({
            source: function (request, response) {
                $.ajax({
                    url: "Home/GetWhatever",
                    data: "{ 'pre':'" + request.term + "'}",
                    dataType: "json",
                    type: "POST",
                    contentType: "application/json; charset=utf-8",
                    success: function (data) {
                        response($.map(data.d, function (item) {
                            return {
                                SubCategoryName: item.SubCategoryName,
                                SubCategoryID: item.SubCategoryID,
                                json: item
                            }
                        }))
                    },
                    error: function (XMLHttpRequest, textStatus, errorThrown) {
                        alert(textStatus);
                    }
                });
            },
            focus: function (event, ui) {
                $('#city').val(ui.item.SubCategoryName);
                return false;
            },
            select: function (event, ui) {
                $('#city').val(ui.item.SubCategoryID);
                return false;
            },
        }).data("ui-autocomplete")._renderItem = function (ul, item) {
            return $("<li>")
            .append("<a>Company:" + item.SubCategoryName + "<br>Industry: " + item.SubCategoryID + "</a>")
            .appendTo(ul);
        };
    });

Please let me know if anything is wrong with the above.

Many thanks in advance.

Update

I have got this to work with a bit of tweaking of my referencing as below:

<head>
    <meta charset="utf-8" />
    <title>@ViewBag.Title - My ASP.NET MVC Application</title>
    <link href="~/favicon.ico" rel="shortcut icon" type="image/x-icon" />
    <meta name="viewport" content="width=device-width" />

    <script src="~/Scripts/jquery-2.1.0.min.js"></script>  

    @Scripts.Render("~/bundles/jqueryui")

    <link href="~/Content/ui-lightness/jquery-ui-1.10.4.custom.min.css" rel="stylesheet" />
    <link href="~/Content/menuStyles.css" rel="stylesheet" />
    <script src="~/Scripts/menu_jquery.js"></script>  
    <link href="~/Content/demos.css" rel="stylesheet" />

    @Styles.Render("~/Content/css")
    @Scripts.Render("~/bundles/modernizr")
</head>
<body> 


    @RenderSection("scripts", required: false)

I have also changed this:

response($.map(data.d, function (item) {

to this:

response($.map(data, function (item) { // without the .d

However this is not working as it only selects one value and it is the ID only that is being selected as in this screenshot:

Before selection:

enter image description here

After selection:

enter image description here

I am not sure why this is happening, if you could help please..

Update: this is the actual Json, after I fixed it. The only problem now is that it doesn't multi-select

$(document).ready(function () {

    debugger;
    $('#city').autocomplete({
        source: function (request, response) {
            $.ajax({
                url: "Home/GetWhatever",
                data: "{ pre: request.term }",
                dataType: "json",
                type: "POST",
                contentType: "application/json; charset=utf-8",
                success: function (data) {
                    response($.map(data, function (item) {
                        return {
                            SubCategoryName: item.SubCategoryName,
                            SubCategoryID: item.SubCategoryID,
                            json: item
                        };
                    }));
                },
                error: function (XMLHttpRequest, textStatus, errorThrown) {
                    alert(textStatus);
                }
            });
        },
        focus: function (event, ui) {
            $('#city').val(ui.item.SubCategoryName);
            return false;
        },
        select: function (event, ui) {
            $('#city').val(ui.item.SubCategoryName);
            return false;
        },
    }).data("ui-autocomplete")._renderItem = function (ul, item) {
        return $("<li>")
        .append("<a>" + item.SubCategoryName + " " + item.SubCategoryID + "</a>")
        .appendTo(ul);
    };
});

** @Daedalus Data view from Fiddler**

I just noticed it is duplicated and this likely to be the reason it is not multi-selecting? enter image description here

Was it helpful?

Solution

Full disclaimer, most of the code in this answer was copied(then altered) from the jQuery UI API manual page for Autocomplete.

That out of the way, your autocomplete is not allowing for multiple selections for a simple reason: it requires a custom handler to be set up in order for such to happen, and you never set up that handler.

Secondly, the code is doing what its supposed to.. You specifically select the SubCategoryID property from your item object, which given your comment, contains the ID of the item.

Given what you have said in the question and comments, I would surmise this is not your goal. So, that in mind, here is the correct code to do what I think you want to achieve. I'm putting in comments to explain what I do(and again to note, the core of this is taken from the API page, with a slight alteration):

$(function () {
    $('#city').autocomplete({
        source: function (request, response) {
            $.ajax({
                url: "Home/GetWhatever",
                data: "{ pre: request.term }",
                dataType: "json",
                type: "POST",
                contentType: "application/json; charset=utf-8",
                success: function (data) {
                    response($.map(data, function (item) {
                        return {
                            SubCategoryName: item.SubCategoryName,
                            SubCategoryID: item.SubCategoryID,
                            json: item
                        };
                    }));
                },
                error: function (XMLHttpRequest, textStatus, errorThrown) {
                    alert(textStatus);
                }
            });
        },
        focus: function (event, ui) {
            /**
             * Here is the modifications I altered from the API manual page;
             * all I really added was extra focus event handling code and
             * an id field, if you just want the ids to be added to a
             * hidden input or such.
             */
            //Grab the current value of the input(s) and turn them into arrays
            var terms = this.value.split(/,\s*/),
                ids = $("#cityids").val().split(/,\s*/);
            //Remove the current input         ^ This is regex, it matches by            
            terms.pop();                      //  a comma followed by zero or
            ids.pop();                        //  more spaces.
            //Add the selected item to the end of the array(s)
            terms.push(ui.item.SubCategoryName);
            ids.push(ui.item.SubCategoryID);
            //Set the value of the inputs to the new strings.
            $("#city").val(terms.join(", "));
            $("#cityids").val(ids.join(", "));
            return false;
        },
        select: function (event, ui) {
            //Grab the current values of the input(s) and turn them into arrays
            var terms = this.value.split(/,\s*/),
                ids = $("#cityids").val().split(/,\s*/); 
            //Remove the current input                  
            terms.pop();
            ids.pop();
            //Add the selected item to the end of the array(s)
            terms.push(ui.item.SubCategoryName);
            ids.push(ui.item.SubCategoryID);
            //Add placeholder to get the comma-and-space at the end
            terms.push("");
            ids.push("");
            //Set the value of the inputs to the new strings.
            $("#city").val(terms.join(", "));
            $("#cityids").val(ids.join(", "));
            return false;
        },
    }).data("ui-autocomplete")._renderItem = function (ul, item) {
        return $("<li>")
            .append("<a>" + item.SubCategoryName + " " + item.SubCategoryID + "</a>")
            .appendTo(ul);
    };
});

DEMO

Update:

In regards to my oversight regarding the search terms, the following should do it, as well as the delete term bit;

Replace your 'success' handler code/content with the following:

var terms = request.term.split(/,\s*/),
    cur_term;
// Get the current term
if (terms[terms.length - 1] == "") {
    cur_term = terms[terms.length - 2];
} else if (terms.length > 1) {
    cur_term = terms[terms.length - 1]
} else {
    cur_term = request.term;
}
response($.map(data, function (item) {
    var reqterm = $.ui.autocomplete.escapeRegex(cur_term),
        //escape any regex
        reg = new RegExp("^"+reqterm,"gi"),
        //create the regex object with current term
        match = item.SubCategoryName.match(reg);
        //match the item name against the regex
    if (match !== null) {
        return { // match found, add object
            SubCategoryName: item.SubCategoryName,
            SubCategoryID: item.SubCategoryID,
            json: item
        }
    } else {
        return null; // No search term found, remove item from array.
    }
}));

The above searches for the current term in the data, only returning matches if any are found.

Secondly, insert this code before your .data() code, so it goes between the }) and .data(); example: }).data(/*etc*/) becomes }).keydown(/*etc*/).data(/*etc*/)

.keydown(function(e) {
    var key = e.which;
    if (key == 8) { // backspace, add keycodes here to account for all keyboards
        var terms = $("#city").val().split(/,\s*/), //current terms
            ids = $("#cityids").val().split(/,\s*/), //current ids
            placeholder = terms[terms.length - 1]; //current placeholder
        // remove the current input, as well as the placeholder if applicable
        terms.pop();
        ids.pop();
        if (placeholder == "") {
            terms.pop();
            ids.pop();
        }
        // add the placeholders back
        terms.push(" ");
        ids.push(" ");
        $("#city").val(terms.join(", "));
        $("#cityids").val(ids.join(", "));
    }
})

DEMO_2

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top