jqueryasp.net-mvc-3jquery-uivalidationjquery-ui-autocomplete

JQuery Autocomplete Combobox: MVC3 Validation not working


I'm trying to use JQuery UI Autocomplete (Combobox) with a dropdown list in ASP.NET MVC3.

Here is the relevant section of code in my view:

    <div class="editor-label">
        @Html.LabelFor(model => model.MerchantID, "Merchant")
    </div>
    <div class="editor-field">
        @Html.DropDownList("MerchantID", null, String.Empty, new { @class = "combobox" })
        @Html.ValidationMessageFor(model => model.MerchantID)
    </div>

    <div class="editor-label">
        @Html.LabelFor(model => model.FinancialAccountID, "FinancialAccount")
    </div>
    <div class="editor-field">
        @Html.DropDownList("FinancialAccountID", null, String.Empty, new { @class = "combobox" })
        @Html.ValidationMessageFor(model => model.FinancialAccountID)
    </div>

(there's anywhere from 4-10 dropdown lists.)

Here is the contents of the javascript file, essentially copied from the jquery-ui website:

(function ($) {
    $.widget("ui.combobox", {
        _create: function () {
            var input,
                    self = this,
                    select = this.element.hide(),
                    selected = select.children(":selected"),
                    value = selected.val() ? selected.text() : "",
                    wrapper = this.wrapper = $("<span>")
                        .addClass("ui-combobox")
                        .insertAfter(select);

            input = $("<input>")
                    .appendTo(wrapper)
                    .val(value)
                    .addClass("ui-state-default ui-combobox-input")
                    .autocomplete({
                        delay: 0,
                        minLength: 0,
                        source: function (request, response) {
                            var matcher = new RegExp($.ui.autocomplete.escapeRegex(request.term), "i");
                            response(select.children("option").map(function () {
                                var text = $(this).text();
                                if (this.value && (!request.term || matcher.test(text)))
                                    return {
                                        label: text.replace(
                                            new RegExp(
                                                "(?![^&;]+;)(?!<[^<>]*)(" +
                                                $.ui.autocomplete.escapeRegex(request.term) +
                                                ")(?![^<>]*>)(?![^&;]+;)", "gi"
                                            ), "<strong>$1</strong>"),
                                        value: text,
                                        option: this
                                    };
                            }));
                        },
                        select: function (event, ui) {
                            ui.item.option.selected = true;
                            self._trigger("selected", event, {
                                item: ui.item.option
                            });
                        },
                        change: function (event, ui) {
                            if (!ui.item) {
                                var matcher = new RegExp("^" + $.ui.autocomplete.escapeRegex($(this).val()) + "$", "i"),
                                    valid = false;
                                select.children("option").each(function () {
                                    if ($(this).text().match(matcher)) {
                                        this.selected = valid = true;
                                        return false;
                                    }
                                });
                                if (!valid) {
                                    // remove invalid value, as it didn't match anything
                                    $(this).val("");
                                    select.val("");
                                    input.data("autocomplete").term = "";
                                    return false;
                                }
                            }
                        }
                    })
                    .addClass("ui-widget ui-widget-content ui-corner-left");

            input.data("autocomplete")._renderItem = function (ul, item) {
                return $("<li></li>")
                        .data("item.autocomplete", item)
                        .append("<a>" + item.label + "</a>")
                        .appendTo(ul);
            };

            $("<a>")
                    .attr("tabIndex", -1)
                    .attr("title", "Show All Items")
                    .appendTo(wrapper)
                    .button({
                        icons: {
                            primary: "ui-icon-triangle-1-s"
                        },
                        text: false
                    })
                    .removeClass("ui-corner-all")
                    .addClass("ui-corner-right ui-combobox-toggle")
                    .click(function () {
                        // close if already visible
                        if (input.autocomplete("widget").is(":visible")) {
                            input.autocomplete("close");
                            return;
                        }

                        // work around a bug (likely same cause as #5265)
                        $(this).blur();

                        // pass empty string as value to search for, displaying all results
                        input.autocomplete("search", "");
                        input.focus();
                    });
        },

        destroy: function () {
            this.wrapper.remove();
            this.element.show();
            $.Widget.prototype.destroy.call(this);
        }
    });
})(jQuery);

$(function () {
    $(".combobox").combobox();
});

The problem I'm having is that there doesn't seem to be any validation done on the values entered. For example, if I leave it blank and submit, there's no error indicating that invalid information has been entered. However, if I comment out the combobox() call to disable the combobox functionality, validation works properly.

I have done some searching on stackoverflow for this issue, but I can't seem to find anything that matches this specific situation.

Edit: I have discovered that validation actually does happen, but only if I click another field before submitting, which is odd. So if I enter blank text into a combobox, then click into a text field, then submit, it works properly (shows validation errors.) But if I enter blank text into the combobox and submit, it doesn't show validation errors. Any ideas?


Solution

  • Ok, I figured out the problem.

    When the submit button was pressed, the javascript validation was called before the Autocomplete JQuery that cleared the selected item when bad input was detected. This is the part I mean:

    if (!valid) {
         // remove invalid value, as it didn't match anything
         $(this).val("");
         select.val("");
         input.data("autocomplete").term = "";
         return false;
    }
    

    So I made it clear the selected item as soon as the user starts typing and only reselect when a valid item is selected.

    source: function (request, response) {
        select.val(""); //as soon as typing is started, the select input is cleared.  This makes sure validation always occurs
        var matcher = new RegExp($.ui.autocomplete.escapeRegex(request.term), "i");
        response(select.children("option").map(function () {
            var text = $(this).text();
            if (this.value && (!request.term || matcher.test(text)))
                return {
                    label: text.replace(
                        new RegExp(
                            "(?![^&;]+;)(?!<[^<>]*)(" +
                            $.ui.autocomplete.escapeRegex(request.term) +
                            ")(?![^<>]*>)(?![^&;]+;)", "gi"
                        ), "<strong>$1</strong>"),
                    value: text,
                    option: this
                };
        }));
    },