AutoComplete mit JQuery und ASP.NET

Für die Suche in grossen Datenbeständen haben wir uns inzwischen ans AutoComplete gewöhnt: Kaum tippt man ein paar Buchstaben, da erscheint unter dem Eingabefeld eine Liste mit möglichen Suchresultaten.

Dahinter steckt AJAX und JavaScript. Und um die Sache zu vereinfachen, werde ich natürlich wie andernorts auch zu JQuery als JavaScript-Framework greifen. Für einmal zeige ich das Beispiel nicht mit Groovy und Grails, sondern in ASP.NET und mit Visual Web Developer Express Edition.

Das vorliegende Beispiel geht über die Standardbeispiele hinaus, indem es zeigt, wie man mit einem AutoComplete-Feld arbeitet, das nicht nur den Wert der Eingabe zurückliefert, sondern auch den zugehörigen Schlüssel.

Voraussetzungen und Vorbereitung

Das Beispiel geht davon aus, dass Sie mit den grundlegenden Mechanismen von AJAX vertraut sind und gewisse Vorkenntnisse in der Programmierung datenbankgestützter Websites haben, insbesondere in Bezug auf JavaScript, JQuery sowie SQL und ASP.Net.

Bevor Sie diese Aufgabe in Angriff nehmen können, gibt es ein paar Vorbedingungen, die erfüllt sein müssen.

  • Windows mit installiertem und laufendem IIS (lokal oder bei ihrem Provider)
  • ASP.NET in Version 3.5 auf dem IIS vorhanden
  • Visual Web Developer Express Edition 2008 oder höher lokal auf ihrem PC installiert
  • eine webfähige Datenbank wie MySQL, Oracle oder Access

Eine Anmerkung zu Access: Viele bekommen einen Lachanfall, wenn man Microsoft Access als webfähige Datenbank bezeichnet. Solange sich das Benutzeraufkommen in Grenzen hält und die Applikation sich auf SELECT-Statements begrenzt und Schreiboperationen auf der Datenbank nicht oder nur selten vorkommen, kann man Access durchaus für diesen Zweck einsetzen. Wenn Sie die Webapplikation bei einem Service Provider veröffentlichen möchten, bleibt Ihnen oft nicht anderes übrig, da Sie, wenn Sie für das Hosting nicht allzuviel ausgeben wollen,  auf einem IIS in den meisten Fällen gar keine andere Datenbank zur Verfügung haben.

Als nächstes erstellen Sie in Visual Web Developer einen neuen leeren Website. Achten Sie darauf, dass Sie diesen Site direkt auf dem lokalen IIS oder auf einem Remote Server  als virtuellen Server oder als Unterverzeichnis des Root erstellen und nicht auf dem Dateisystem. Das heisst, Sie müssen unbedingt Speicherort HTTP wählen. Dies deshalb, weil gewisse Menüs und Optionen in der Entwicklungsumgebung nicht zur Verfügung stehen, wenn man auf dem Dateisystem arbeitet.

Downloads

Des weiteren benötigen Sie natürlich in Ihrem Projekt JQuery und das AutoComplete-Plugin inklusive zugehörigem CSS. Laden Sie also folgende Dateien herunter und versorgen Sie diese in den angegebenen Ordnern:

  • Neueste JQuery-Version, im Oktober 2010 ist dies 1.4.2. Diese kommt in ein Verzeichnis \js\ unterhalb von Root.
  • AutoComplete-Plugin für JQuery, diese Datei kommt ebenfalls in \js\
  • Die CSS-Datei aus der JQuery-Demo stellen Sie in ein Verzeichnis \css\

Aufgabenstellung

Ich zeige das Beispiel anhand meiner Blumen-Datenbank. In dieser Access-Datenbank finden sich Informationen zu Blumen. Für jede Pflanze ist mindestens der wissenschaftliche, d.h. lateinische Name sowie ein deutscher Name gespeichert. Das Eingabefeld soll nun, sobald man den ersten Buchstaben eingibt, unterhalb des Eingabefeldes eine Liste aller Pflanzen zur Auswahl anzeigen, welche die eingegebene Buchstabenkombination im deutschen oder lateinischen Namen aufweisen.

Webform

Sobald das Projekt mit den entsprechenden Dateien vorhanden ist, erstellen Sie ein neues Webform. Den Code müssen Sie nicht in einer eigenen Datei platzieren, dieses Webform wird keinen serverseitigen Code aufweisen. Bei mir heisst diese Datei JQueryAutoComplete.aspx und ich arbeite mit C#. Kopieren Sie folgenden Code in die Datei:

<%@ Page Language="C#" AutoEventWireup="true"
 CodeBehind="JQueryAutoComplete.aspx.cs"
 Inherits="webblumen.aspxversuche.JQueryAutoComplete" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" >
  <head runat="server">
    <title>AutoComplete mit JQuery</title>
    <script src="/js/jquery-1.4.2.min.js" type="text/javascript">
    </script>
    <link rel="stylesheet" href="/css/jquery.autocomplete.css"
      type="text/css" />
    <script type="text/javascript" src="/js/jquery.autocomplete.js">
    </script>
    <script type="text/javascript">
      $(document).ready(function() {
        $("#fldInput").autocomplete("JQueryAutoCompleteHandler.ashx", {
          //Daten kommen in der Form
          // name_l_d | idPflanze | name_d_l

          //Anzeige in Liste "1/5: name_l_d"
          formatItem: function(row, i, max) {
            return i + "/" + max + ": " + row[0];
          },
          //Anzeige im Textfeld "idPflanze name_l_d"
          formatResult: function(row) {
            return row[1] + " " + row[0];
          }
        });
        //Auswahl wird unterhalb Inputfeld angezeigt
        $("#fldInput").result(function(event, data, formatted) {
          if (data) {
            $("#resultat").html("Schl&uuml;ssel: " + data[1]
              + "<br />Wert: " + data[0] + "<br />" + data[2]));
          }
        });

      });
    </script>
  </head>
  <body>
    Blume:
    <input id="fldInput" type="text" size="50"/>
    <br />
    <div id="resultat"></div>
  </body>
</html>

Der sichtbare Teil der Seite selbst besteht nur aus einem Input-Feld namens fldInput und einem Div-Tag mit der id „resultat„, das später die ausgewählten Daten anzeigen wird. Beachten Sie, dass es sich um ein ganz gewöhnliches Text-Feld handelt, nicht um ein Kombinationsfeld. Die angezeigte Liste wird nämlich mit JavaScript automatisch generiert und angezeigt.

Im Header werden erst einmal die oben erwähnten Dateien eingebunden, also jquery-1.4.2.min.js und die zwei Dateien für die AutoComplete-Komponente, d.h. jquery.autocomplete.css und  jquery.autocomplete.js.

Das Wichtigste steckt natürlich im JavaScript-Code. Doch sehen wir uns zuerst den serverseitigen Code an, der die Daten für das AutoComplete-Feld liefert.

Generischer Handler für die serverseitige DB-Abfrage

Erzeugen Sie unter Datei – Neue Datei einen generischen Handler. Bei mir heisst diese Datei JQueryAutoCompleteHandler.ashx. Dieser Handler holt aus den übergebenen Daten den Suchstring, macht mit diesem eine Datenbank-Abfrage und liefert die gefundenen Datensätze in folgendem Format zurück:

BezeichnungA1|Schluesselwert1|BezeichnungB1
BezeichnungA2|Schluesselwert2|BezeichnungB2
BezeichnungA3|Schluesselwert3|BezeichnungB3

Anscheinend erkennt das JavaScript-Plugin | automatisch als Trennzeichen, so dass man später in JavaScript direkt auf diese zwei Felder zugreifen kann.

Der Code für diesen Handler sieht folgendermassen aus:

<%@ WebHandler Language="C#" %>

using System;
using System.Web;
using System.Data.OleDb;

public class JQueryAutoCompleteHandler : IHttpHandler {

  public void ProcessRequest (HttpContext context) {
    // hier wird der eingegebene Wert aus dem Parameter q gelesen
    string input = context.Request.QueryString["q"];
    string sql = "SELECT top 20 name_l_d, idPflanze, name_d_l "
      + "FROM qryNameD where name_l_d like ?";
    String CONN =
      System.Configuration.ConfigurationManager.ConnectionStrings[
        "blumenConnectionString"].ConnectionString;

    using (OleDbConnection connection = new OleDbConnection(CONN))
    using (OleDbCommand command = new OleDbCommand(sql, connection)) {
      connection.Open();
      command.Parameters.AddWithValue("input", "%" + input + "%");
      using (OleDbDataReader reader = command.ExecuteReader()) {
        while (reader.Read()) {
          //liefert die Daten in der Form
          //name_l_d | idPflanze | name_d_l
          context.Response.Write(reader.GetString(0) + "|"
            + reader.GetInt32(1).ToString() + "|"
            + reader.GetString(2) + Environment.NewLine);
        }
      }
    }
  }

   public bool IsReusable {
     get {
       return false;
     }
   }

}

IHttpHandler ist ein ASP.Net-Interface, dass es ermöglicht, Requests zu bedienen, ohne dass eine ganze HTML-Seite zurückgeliefert wird. Typische Anwendungen dafür sind AJAX-Requests, welche HTML oder direkt Daten in einem bestimmten Format wie JSON zurückerwarten. Daneben ist es mit einem solchen Handler auch möglich, Fremdformate zurückzuliefern, z.B. eine PDF- Excel- oder Bilddatei, welche direkt aus der Datenbank erzeugt wird.

Der vorliegende Handler holt sich als erstes die Eingabe aus dem vordefinierten Parameter q.

    string input = context.Request.QueryString["q"];

Anschliessend macht man eine Datenbankverbindung und holt mit einem parametrisierten Select-Statement alle Datensätze aus der Access-Datenbank, die dem Kriterium entsprechen. Sie erinnern sich, es geht in diesem Beispiel um Blumen. Mit der Parametrisierung werden SQL-Injection-Versuche abgefangen.

    string sql = "SELECT top 20 name_l_d, idPflanze, name_d_l "
      + "FROM qryNameD where name_l_d like ?";

Mit AddWithValue weisst man dem Parameter einen Wert zu:

      command.Parameters.AddWithValue("input", "%" + input + "%");

Zurückgeliefert wird ein String, der pro Zeile drei Felder des jeweiligen Datensatzes zurückliefert. Als erstes im Feld name_l_d die Bezeichnung, bei welcher der lateinische Name dem deutschen vorangestellt ist. Als zweites die eindeutige Id der Pflanze in idPflanze, mit der in einem zweiten Request weitere Informationen zu dieser Pflanze geholt werden könnten. Als drittes finden sich schliesslich im Feld name_d_l noch die Bezeichnungen in umgekehrter Reihenfolge, d.h. der deutsche Name kommt vor dem lateinischen.

Dieses dritte Feld gebe ich vor allem mit, um zu zeigen, dass man bei dieser JavaScript-Lösung nicht wie im HTML-Kombinationsfeld auf das Paar aus Wert und Bezeichnung begrenzt ist, sondern beliebig viele Felder pro Datensatz zurückliefern kann.

          context.Response.Write(reader.GetString(0) + "|"
            + reader.GetInt32(1).ToString() + "|"
            + reader.GetString(2) + Environment.NewLine);

Der Befehl context.Response.Write setzt den String in genau dieser Reihenfolge aus den drei Feldern der resultierenden Datensätze zusammen und liefert ihn zurück an die aufrufende Seite, wo er dann mit den bereits gezeigten JQuery-Routinen verarbeitet wird.

AJAX mit JQuery

Damit kommen wir zurück zu unserem JavaScript-Code, den wir im Abschnitt Webform bereits gesehen haben. AJAX kommt insofern ins Spiel, dass die Eingabe von ein paar Buchstaben zwar einen serverseitigen Request auslösen soll, aber natürlich nicht die ganze Seite neu laden darf, sondern nur die vom AJAX-Request zurückgelieferten Daten in die Seite einbaut.

Der Aufruf befindet sich innerhalb des in JQuery üblichen

$(document).ready(function() {

und lautet:

$("#fldInput").autocomplete("JQueryAutoCompleteHandler.ashx", {
  ...
} 

Die Methode autocomplete wird dem Eingabefeld mit der Id fldInput angehängt. Als erstes Argument übergibt man den Handler JQueryAutoCompleteHandler, der serverseitig den AJAX-Request verarbeitet.

Zwischen den geschweiften Klammern befinden sich nun die Optionen. Das AutoComplete-Plugin hat eine ganze Anzahl Optionen, von denen man meist nur ein bis zwei verwendet. Für speziellere Bedürfnisse lohnt es sich allerdings, die Dokumentation zu diesen Optionen genau anzusehen.

Das vorliegende Beispiel verwendet zwei Optionen:

  1. formatItem definiert, wie die vom Handler zurückgelieferten Daten in der Liste unterhalb des Eingabefeldes aussehen
          formatItem: function(row, i, max) {
            return i + "/" + max + ": " + row[0];
          },
  1. formatResult dagegen definiert, welche Daten ins Textfeld selbst geschrieben werden, sobald die Benutzerin ein Item aus der Liste ausgewählt hat.
          formatResult: function(row) {
            return row[1] + " " + row[0];
          }

In beiden Fällen erfolgt der Zugriff auf die Information über eine Variable row, die in JavaScript ein 0-basierter Array ist. row[0] liefert name_l_d, row[1] liefert den Schlüssel idPflanze und row[2] liefert name_d_l.

Eine Besonderheit ist nun, dass die AutoComplete-Komponente für die zurückgelieferten rows automatisch zwei Variablen zur Verfügung stellt: i ist eine Laufnummer und max gibt die Anzahl der zurückgelieferten Datensätze an. Damit die Liste nicht zu lang wird, wurde sie übrigens serverseitig im Select-Statement mit dem Access-spezifischen Zusatz “ Top 20“ willkürlich auf 20 Datensätze begrenzt.

Für die Liste nutze ich die zwei Variablen, indem ich vor der eigentlichen Bezeichnung noch die Laufnummer und die Gesamtzahl angebe. Das sieht dann so aus:

JQuery AutoComplete-Plugin mit aufgeklappter Liste

Nach der Auswahl wird das Schlüsselfeld und die Bezeichnung angezeigt:

Das Eingabefeld nach der Auswahl

Das Eingabefeld nach der Auswahl

Der letzte, mit $(„#fldInput“).result eingeleitete Block im JQuery-Script regelt, was nach Abschluss der Eingabe passiert. Result hat ein Argument, nämlich eine Funktion, die wiederum drei Argumente entgegennimmt. In unserem Beispiel interessiert von diesen Drei nur data, die gewählte Zeile aus der AutoComplete-Liste. Wenn data etwas zurückliefert, dann werden innerhalb des DIV-Tags mit der Id resultat die drei Teile dieser Information angezeigt, allerdings nicht in der ursprünglichen Reihenfolge.

        $("#fldInput").result(function(event, data, formatted) {
          if (data) {
            $("#resultat").html("Schl&uuml;ssel: " + data[1]
              + "<br />Wert: " + data[0] + "<br />" + data[2]));
          }
        });

Damit sind wir am Ende des Beispiels. Es wäre nun reizvoll, das gleiche Beispiel mit Grails und mit PHP durchzuspielen. Im Grossen und Ganzen änderte sich dabei nur der serverseitige Event-Handler, die Webseite und das JavaScript dagegen blieben in allen drei Frameworks fast dieselben. Aber das ist ein Thema für einen anderen Beitrag.

Quellen

Ausgangspunkt für meine eigenen Versuche war die folgende Seite:

http://www.eggheadcafe.com/tutorials/aspnet/18b6a1fa-a7cf-4507-84f2-15056fe65bb7/autocompletion-with-jque.aspx

Die Dokumentation zum AutoComplete-Plugin findet man hier:

http://docs.jquery.com/Plugins/Autocomplete/autocomplete#toptions

Comments are closed.