Autocomplete mit JQuery UI Autocomplete und ASP.NET MVC und Listen von komplexen Typen List<T>

Written on November 01, 2010

Im letzten Posting habe ich gezeigt, wie man eine List als JsonResult zurückgeben kann, um eine Autocomplete-Textbox mit JQuery UI Autocomplete zu befüllen.

Einfache Listen mit string-Werten sind vielleicht ganz nett, wenn man für eine generische Suche á la Google Bing nur Vorschläge liefern will.

Was aber, wenn man Autocomplete z.B. in einem CRM integriert und eine Liste von Kunden vorschlägt?

Wäre es dann für den Anwender nicht praktisch, wenn er statt nach dem bereits selektierten (weil tatsächlich vorhandnenen) Kunden zu suchen, gleich zu den Details des Kunden kommen würde?

Auch das lässt sich leicht mit JQuery UI und ASP.NET MVC realisieren.

Zunächst erweitern wir unsere Kunden-Liste aus dem letzten Beispiel um eine Id und den Ort:

_kunden = new List<kunde>()
            {
                new Kunde() {Id = Guid.NewGuid(), Name = "Alfreds Futterkiste", Ort = "Berlin"},
                new Kunde()
                    {Id = Guid.NewGuid(), Name = "Ana Trujillo Emparedados y helados", Ort = "México D.F."},
                new Kunde() {Id = Guid.NewGuid(), Name = "Antonio Moreno Taquería", Ort = "México D.F."},
                new Kunde() {Id = Guid.NewGuid(), Name = "Around the Horn", Ort = "London"},
                new Kunde() {Id = Guid.NewGuid(), Name = "Frankenversand", Ort = "München"},
                new Kunde() {Id = Guid.NewGuid(), Name = "France restauration", Ort = "Nantes"},
                new Kunde() {Id = Guid.NewGuid(), Name = "Königlich Essen", Ort = "Brandenburg"},
                new Kunde() {Id = Guid.NewGuid(), Name = "Toms Spezialitäten", Ort = "Münster"}
            };

Wie man sieht, ist die Liste jetzt auch nicht mehr List sondern List.

Die Generierung des Suchergebnisses erfolgt analog zum bekannten Beispiel:

List<Kunde> result =
                _kunden.Where(kunde => kunde.Name.ToLower().StartsWith(term.ToLower())).ToList();

Starten wir nun die Anwendung und tippen z.B. "alfred", sehen wir... nichts:

Wo ist Alfreds Futterkiste?

Warum ist das so?

Eine Überprüfung des JsonResults per Fiddler zeigt eindeutig, dass der Controller die Daten liefert:

HTTP/1.1 200 OK
Server: ASP.NET Development Server/10.0.0.0
Date: Mon, 01 Nov 2010 18:37:08 GMT
X-AspNet-Version: 2.0.50727
X-AspNetMvc-Version: 2.0
Cache-Control: private
Content-Type: application/json; charset=utf-8
Content-Length: 91
Connection: Close

[{"Id":"bbf55dd9-4d7d-4144-8383-32044af5ebc7","Name":"Alfreds Futterkiste","Ort":"Berlin"}]

Was ist also schiefgelaufen?

Der Grund für dieses Problem ist, dass JQuery UI Autocomplete ein Json in folgendem Format erwartet:

[{"value":"bbf55dd9-4d7d-4144-8383-32044af5ebc7","label":"Alfreds Futterkiste, Berlin"}]

Wie bekommen wir nun dieses Resultat von unserem Controller erstellt?

Dank LINQ, anonymen Typen und Listen ist dies kein Problem -- wir ändern unsere Abfrage wie folgt:

var result = from kunde in _kunden
                where kunde.Name.ToLower().StartsWith(term.ToLower())
                select new
                        {
                            value = kunde.Id,
                            label = string.Format("{0}, {1}", kunde.Name, kunde.Ort)
                        };

Ein Neustart der Anwendung liefert jetzt die gewünschten Ergebnisse:

Suchliste mit Value und Label

Ein unschönes Verhalten ergibt sich allerdings noch:

Iteriert man per Pfeiltasten über die Vorschläge, wird in der Textbox statt des Namens die Id des Eintrags angezeigt:

Die Id hat in der Textbox nichts verloren...

Dieses Verhalten lässt sich leicht mit etwas Javascript korrigieren. JQuery UI Autocomplete liefert das Event "focus" mit, das gefeuert wird, wenn man in der Vorschlagsliste von einem Eintrag zum nächsten springt und der neue gewählte Eintrag den Fokus erhält.

In den EventArgs ist der aktuelle Eintrag mit Value und Label enthalten, so dass wir den Wert in der Textbox einfach überschreiben können:

<script type="text/javascript">
    $(function () {
        $('#customers').autocomplete({
            source: '<%= Url.Action("Search") %>',
            minLength: 1,
            focus: function (event, ui) {
                var selectObject = ui.item;
                $('#customers').val(selectObject.label);
                return false;
            }
        });
    });
</script>

Das gleiche Verhalten tritt nochmals auf, wenn man einen Eintrag auswählt. Die Lösung ist hier analog, jedoch heißt das Event "select":

<script type="text/javascript">
    $(function () {
        $('#customers').autocomplete({
            source: '<%= Url.Action("Search") %>',
            minLength: 1,
            focus: function (event, ui) {
                var selectObject = ui.item;
                $('#customers').val(selectObject.label);
                return false;
            },
            select: function (event, ui) {
                var selectObject = ui.item;
                $('#customers').val(selectObject.label);
                return false;
            }
        });
    });
</script>

Das bereinigte Resultat sieht nun so aus:

Kunde selektiert und Text in der Textbox

Wem übrigens das Verhalten, das nichts passiert, wenn man ohne Auswahl Enter drückt, nicht gefällt, findet bei Thomas eine Lösung.

Um von der Auswahl nun direkt die Kunden-Details angezeigt zu bekommen, benötigen wir nun ein ViewResult, das uns diese zurückliefert:

public ActionResult Details(Guid Id) {
    ensureData();
    Kunde model = _kunden.Where(kunde => kunde.Id == Id).FirstOrDefault();
    return View(model);
}

Die zugehörige partial View lassen wir aus dem Controller generieren:

generateview

Schließlich erweitern wir unser Autocomplete-Script im select-Event noch um einen JQuery-Ajax-Call, der das ViewResult abruft und unter der Such-Textbox einbaut:

<script type="text/javascript">
    $(function () {
        $('#customers').autocomplete({
            source: '<%= Url.Action("Search") %>',
            minLength: 1,
            focus: function (event, ui) {
                var selectObject = ui.item;
                $('#customers').val(selectObject.label);
                return false;
            },
            select: function (event, ui) {
                var selectObject = ui.item;
                $('#customers').val(selectObject.label);
                $.ajax(
                        {
                        type: "GET",
                        url: '<%= Url.Action("Details") %>',
                        data: "Id=" + selectObject.value,
                        dataType: "html",
                        success: function (result) {
                               var domElement = $(result)
                               $("#details").empty();
                               $("#details").append(domElement);
                        },
                        error: function (error) {
                               alert(error);
                        }

                        }
                        )
                return false;
            }
        });
    });
</script>

Das Resultat sieht dann wie folgt aus:

Gesucht? Gefunden!

Die Beispiel-Solution kann hier heruntergeladen werden:

MvcJQueryAutocompleteCustomType.zip (355.22 kb)

DotNetKicks-DE Image