Autocomplete Beschränkung auf Liste

tsunami

Grünschnabel
Hallo,
ich habe mir ein Autocomplete aus Codeschnipseln zusammengebastelt. Funktioniert bis auf einen kleinen Schönheitsfehler.
Wenn ich Text eingebe, der nicht in der Liste ist, wird das Feld geleert. Aber leider erst, wenn ich die Tab-Taste drücke. Wenn ich direkt auf den Submit Button klicke, wird dummerweise der fehlerhafte Datensatz abgeschickt.
Wie bekomme ich es hin, dass eine Eingabe von Text, der nicht in der Luste ist, erst gar nicht möglich ist oder alternativ der Submit Button einfach ausgeblendet wird. =>
Code:
document.getElementById('rowxxx').style.display = 'none' ;
Klappt, wird aber auch nur nach "tab" wieder eingeblendet. Schön wäre, wenn es bei tab und klick auf den korrekten Datensatz geht.
Beispiel:
Die Liste:
id:"1"
value:"Apfel",
id:"2"
value:"Birne",
id:"3"
value:"Banane"

Gebe ich nun zB Bananne ein schickt er zwar den korrekten id-Wert ab und es ist nur ein Schönheitsfehler.
Gebe ich nun zB xxxxx funktioniert es, sofern keine vorherigen Eigaben gemacht wurden, ansonsten nimmt er die id, von der vorherigen EIngabe. Gebe ich Banane123 ein geht es auch, id korrekt.
Code:
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>jQuery UI Autocomplete - Custom data and display</title>
  <link rel="stylesheet" href="//code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css">
  <link rel="stylesheet" href="/resources/demos/style.css">
  <style>
  #project-label {
    display: block;
    font-weight: bold;
    margin-bottom: 1em;
  }
  #project-icon {
    float: left;
    height: 32px;
    width: 32px;
  }
  #project-description {
    margin: 0;
    padding: 0;
  }
  </style>
  <script src="https://code.jquery.com/jquery-1.12.4.js"></script>
  <script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
  <script>
  $( function() {
    var projects = [
{value: "1",label:"Apfel"},{value: "2",label:"Birne"},{value: "3",label:"Banane"},{value: "4",label:"Pflaume"}    ];

    $( "#project" ).autocomplete({
            minLength: 0,
            source: projects,
            focus: function( event, ui ) {
                $( "#project" ).val( ui.item.label );
                return false;
            },
          select: function( event, ui ) {
                $( "#project" ).val( ui.item.label );
                $( "#project-id" ).val( ui.item.value );
                return false;
            },
      selectFirst: true,
click: function (event, ui) {
        if (ui.item == null){ 
         //here is null if entered value is not match in suggestion list
            $(this).val((ui.item ? ui.item.id : ""));
//                 $( "#project-id" ).val( ui.item.value )="";
//               $( "#project" ).val( ui.item.value )="";
document.getElementById('rowxxx').disabled = true; 
        }
        else
        document.getElementById('rowxxx').disabled = false; 
    }
        })
        .autocomplete( "instance" )._renderItem = function( ul, item ) {
            return $( "<li>" )
                .append( "<div>" + item.label + "<br>" + item.desc + "</div>" )
                .appendTo( ul );
        };
    } );
  </script>


</head>
<body>

<div id="project-label">Select a project (type "j" for a start):</div>
<img id="project-icon" src="images/transparent_1x1.png" class="ui-state-default" alt="">
<form method="post" action="ajax2.php">
<input id="project" name="nummer" placeholder="fucking Javascript">
<input type="hidden" id="project-id" name="id">
<input type="submit" id="rowxxx">   </form>


</body>
</html>
Irgenwelche Ansätze? Vielen Dank
tsunami
 
alternativ der Submit Button einfach ausgeblendet wird.
Einen Ansatz dafür habe ich schon, aber er ist nicht besonders performant, weil bei jeder Änderung im Eingabefeld die komplette Liste abgesucht werden muss. Wenn diese nicht übermäßig lang wird, ist dies sicher vertretbar.
Code:
        $(function () {
            var projects = [
        { value: "1", label: "Apfel" }, { value: "2", label: "Birne" }, { value: "3", label: "Banane" }, { value: "4", label: "Pflaume" }];

            $("#project").autocomplete({
                minLength: 0,
                source: projects,
                focus: function (event, ui) {
                    $("#project").val(ui.item.label);
                    return false;
                },
                select: function (event, ui) {
                    $("#project").val(ui.item.label);
                    $("#project-id").val(ui.item.value);
                    $("#rowxxx").prop("disabled", false);
                    return false;
                },
                selectFirst: true,
                click: function (event, ui) {
                    if (ui.item == null) {
                        //here is null if entered value is not match in suggestion list
                        $(this).val((ui.item ? ui.item.id : ""));
                        //                 $( "#project-id" ).val( ui.item.value )="";
                        //               $( "#project" ).val( ui.item.value )="";
                        document.getElementById('rowxxx').disabled = true;
                    }
                    else
                        document.getElementById('rowxxx').disabled = false;
                }
            })
                .autocomplete("instance")._renderItem = function (ul, item) {
                    return $("<li>")
                        .append("<div>" + item.label + "<br>" + item.desc + "</div>")
                        .appendTo(ul);
                };
            $("#project").on("input", function () {
                var found = false;
                inp = $(this);
                $.each(projects, function (idx, item) {
                    if (item.label == inp.val()) {
                        found = true;
                        return false;
                    } else return true;
                });
                if (found) $("#rowxxx").prop("disabled", false);
                else $("#rowxxx").prop("disabled", true);
            });
        });
    </script>
 
Hi,
erstmal vielen Dank für die superschnelle Antwort! Hm, jedesmal alle durchsuchen geht wenn der User in nem Büro mit guter EDV und Internet sitzt.
Wie gesagt so funktioniert der Autocomplete, nur muss ich jedemal "Tab" drücken, damit das Script eine Eingabekontrolle durchführt. Und da bin ich zuwenig javascripter, irgendwie müsste man einen anderen eventhandler nehmen.
Rein vom Ablauf müsste er sich die Inhalt des EIngabefeldes merken und sobald die Anzahl der in der Liste enthaltener Elemente 0 ist, auslösen und zB den Submit ausblenden. Oder in sowas wie ein temp Array schreiben. Bei Banane, Birne, Apfel und Pflaume und der Eingabe "B", würde man dann alle Elemente die nicht mit "B" anfangen ignorieren und alle die mit "B" anfangen in das temp Array schreiben. Dann müsste er in diesem Fall nur zwei Elemente prüfen, bei "Ba" nur noch eins, Bei "Bax" hätte das array die Größe 0 und exit. Das Grundprinzip ist doch umgesetzt. Es wird in der Vorschlagsliste ja auch nur die passenden Vorschläge angezeigt. Nur die Limitierung fehlt.

Vielleicht muss ich auch in die ganz andere Richtung und es mit datalist lösen, html5. Nur da habe ich keine Möglichkeit gefunden, die id zu übermitteln und den Namen an zu zeigen. Zudem wird es derzeit noch etwas unterschiedlich bei den Browsern gehandhabt.
 
Wieviele Elemente hätte denn die endgültige Liste ungefähr?

Rein vom Ablauf müsste er sich die Inhalt des EIngabefeldes merken und sobald die Anzahl der in der Liste enthaltener Elemente 0 ist, auslösen und zB den Submit ausblenden.
Glaube nicht, dass das ausreichend ist, denn wenn im Eingabefeld nur ein Teil des Elementes steht, ist die Liste nicht leer, aber Du willst auch nicht, dass es so abgeschickt wird.

Oder in sowas wie ein temp Array schreiben. Bei Banane, Birne, Apfel und Pflaume und der Eingabe "B", würde man dann alle Elemente die nicht mit "B" anfangen ignorieren und alle die mit "B" anfangen in das temp Array schreiben.
Daran hatte ich auch gedacht, aber wahrscheinlich ist das Anlegen eines temp-Arrays auch nicht performanter als ein Suchvorgang, denn es muss ja auch bei jeder Änderung im Eingabefeld aktualisiert werden.

Möglicher Weise könnte man nur die gefilterte Teilliste durchsuchen, die das Autocomplete anzeigt. Offenbar ist diese in der Funktion _renderMenu( ul, items ) unter items verfügbar. Würde zwar nicht der Semantik entsprechen, aber sollte funktionieren. Würde dann so aussehen:
Code:
            $("#project").autocomplete("instance")._renderMenu = function (ul, items) {
                var that = this;
                $.each( items, function( index, item ) {
                    that._renderItemData( ul, item );
                });
                inp = $("#project");
                if (inp.val() == "") {
                    $("#rowxxx").prop("disabled", true);
                } else {
                    var found = false;
                    $.each(items, function (idx, item) {
                        if (item.label == inp.val()) {
                            found = true;
                            return false;
                        } else return true;
                    });
                    if (found) $("#rowxxx").prop("disabled", false);
                    else $("#rowxxx").prop("disabled", true);
                }
            };
 
Zuletzt bearbeitet:
vlt hilft dir Folgendes:
HTML:
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>jQuery UI Autocomplete - Custom data and display</title>
  <link rel="stylesheet" href="//code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css">
  <link rel="stylesheet" href="/resources/demos/style.css">
  <style>
  #project-label {
    display: block;
    font-weight: bold;
    margin-bottom: 1em;
  }
  #project-icon {
    float: left;
    height: 32px;
    width: 32px;
  }
  #project-description {
    margin: 0;
    padding: 0;
  }
  </style>
  <script src="https://code.jquery.com/jquery-1.12.4.js"></script>
  <script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
  <script type="text/javascript">
  var projects = [
{value: "1",label:"Apfel"},{value: "2",label:"Birne"},{value: "3",label:"Banane"},{value: "4",label:"Pflaume"}    ];
   
  $( function() {
   document.getElementById('rowxxx').disabled = true;
   
    $( "#project" ).autocomplete({
            minLength: 0,
            source: projects,
            focus: function( event, ui ) {
                $( "#project" ).val( ui.item.label );
                return false;
            },
          select: function( event, ui ) {
                $( "#project" ).val( ui.item.label );
                $( "#project-id" ).val( ui.item.value );
                return false;
            },
      selectFirst: true,
click: function (event, ui) {
        if (ui.item == null){
         //here is null if entered value is not match in suggestion list
            $(this).val((ui.item ? ui.item.id : ""));
//                 $( "#project-id" ).val( ui.item.value )="";
//               $( "#project" ).val( ui.item.value )="";
document.getElementById('rowxxx').disabled = true;
        }
        else
        document.getElementById('rowxxx').disabled = false;
    }
        })
        .autocomplete( "instance" )._renderItem = function( ul, item ) {
            return $( "<li>" )
                .append( "<div>" + item.label + "<br>" + item.desc + "</div>" )
                .appendTo( ul );
        };
    } );
   function pruefe(){
       var i=0;
       var equal=false;
       while(i<projects.length){
           if(document.Formular.nummer.value==projects[i].label || document.Formular.nummer.value==projects[i].label.toLowerCase()){equal=true;}
           i++;
       }
       if(equal){
           document.getElementById('rowxxx').disabled = false;
       }else{
           document.getElementById('rowxxx').disabled = true;
       }
   }
  </script>
</head>
<body>
<div id="project-label">Select a project (type "j" for a start):</div>
<img id="project-icon" src="images/transparent_1x1.png" class="ui-state-default" alt="">
<form method="post" action="ajax2.php" name="Formular">
<input type="text" id="project" onkeyup="pruefe()" name="nummer" placeholder="Project name">
<input type="hidden" id="project-id" name="id">
<input type="submit" id="rowxxx">   </form>
</body>
</html>
 
Das ist doch genau das gleiche wie das in meinem Posting #1. Die Liste muss bei jedem Tastendruck komplett abgesucht werden. Der TO hatte jedoch Bedenken, dass es nicht genügend performant ist, wenn es auf einem Gerät mit geringer Leistungsfähigkeit ausgeführt wird:
Hm, jedesmal alle durchsuchen geht wenn der User in nem Büro mit guter EDV und Internet sitzt.
 
Das ist doch genau das gleiche wie das in meinem Posting #1. Die Liste muss bei jedem Tastendruck komplett abgesucht werden. Der TO hatte jedoch Bedenken, dass es nicht genügend performant ist, wenn es auf einem Gerät mit geringer Leistungsfähigkeit ausgeführt wird:
Stimmt sry.

Könnte man mehrere Objekte anlegen, indem man alle möglichen Namen als Eigenschaft Name speichert und man dann mit der Value vom Input Feld direkt hinspringt? Das würde ja nur beim Erstellen der Objekte (Laden der Seite) Ressourcen verbrauchen, danach ja nicht mehr. Und man muss ja auch erst überprüfen, wenn der User eine Schreibpause einlegt -> 1 Sekunden Timer;

Oder gibt es eine Dynamische Variablen Lösung, indem man den Namen als Variablenbezeichnung setzen kann und somit dann im nachhinein nur prüfen muss, ob diese Variable vorhanden ist?

Ansonsten würde ich eine "LastHitID" Var setzen, die speichert, wo der erste Treffer beim letzten Suchen war und die Prüfung dann dort anfängt. Nachdem der User allerdings Backspace gedrückt hat, sollte die "LastHitID" sich verringern (lokales Array). Dies funktioniert halt nur, wenn der User linear schreibt und nicht plötzlich den ersten Buchstaben ändert etc.

Man kann die Liste, falls sie nicht schon ist, lokal alphabetisch sortieren und die Prüfung dann aufhören, wenn der Buchstabe im Alphabet höher ist.
man kann dann auch die Liste einteilen, das man den Index mit dem ersten Treffer zu jedem Buchstaben speichert, damit man die nächste Prüfung erst da beginnen muss. Oder man fängt erst dort an zu suchen, wo die Wahrscheinlichkeit am größten ist:
Länge des Arrays/24
Falls man nichts findet, weil der Buchstabe im Alphabet höher ist (Siehe oben) kann man ja wieder bei Lange/24 anfangen aber rückwärts "laufen", bis auch hier der Alphabet niedriger ist. Kombiniert mit dem 1 Sekunden Timer und der LastHitID müsste man einige Ressourcen sparen können.

Was meinst du Sempervivum?
 
Könnte man mehrere Objekte anlegen, indem man alle möglichen Namen als Eigenschaft Name speichert und man dann mit der Value vom Input Feld direkt hinspringt? Das würde ja nur beim Erstellen der Objekte (Laden der Seite) Ressourcen verbrauchen, danach ja nicht mehr. Und man muss ja auch erst überprüfen, wenn der User eine Schreibpause einlegt -> 1 Sekunden Timer;

Oder gibt es eine Dynamische Variablen Lösung, indem man den Namen als Variablenbezeichnung setzen kann und somit dann im nachhinein nur prüfen muss, ob diese Variable vorhanden ist?
Wahrscheinlich willst Du auf so etwas hinaus:
Code:
var projects = {"Apfel": 1, "Birne": 2, "Banane": 3, "Pflaume": 4};
Dann ist von der Notation her ein Direktzugriff möglich:
Code:
if (projects[inp.val()]) { /* Der String ist in der Liste */ }
Ich bin aber skeptisch, ob das zwangsläufig effizienter ist, denn der Interpreter muss dennoch den Wert in dem Objekt aufsuchen.

Ein gängiges Verfahren, um eine effiziente Suche zu implementieren, ist, das Array zu sortieren und dann eine binäre Suche durchzuführen:
https://de.wikipedia.org/wiki/Binäre_Suche
Auch daran habe ich gedacht, allerdings fällt dann zunächst der Aufwand für das Sortieren an und, damit man nicht bei jedem Tastendruck neu sortieren muss, das Array beibehalten und immer komplett (binär) durchsucht werden.
 
Wahrscheinlich willst Du auf so etwas hinaus:
var projects = {"Apfel": 1, "Birne": 2, "Banane": 3, "Pflaume": 4};
Nicht ganz sondern ein Skript, das einmal das Array durchläuft und für jeden Namen eine Variable anlegt, mit dem Wert als Bezeichnung. Also in etwa so:
Javascript:
var ID=0:
for(var i in Array){
document.getElementsByTagName('head')[0].innerHTML+='<script type="text/javascript">'+i+'='+ID+';</script>';
ID++;
}
Aber da ist denke ich sortieren besser.

Auch daran habe ich gedacht, allerdings fällt dann zunächst der Aufwand für das Sortieren an und, damit man nicht bei jedem Tastendruck neu sortieren muss, das Array beibehalten und immer komplett (binär) durchsucht werden.
Nun ob ich das originale Array durchsuche oder das temporäre macht ja kein Unterschied, nur das sortieren und falls es ja schon sortiert ist, fällt auch das weg :)
Außerdem glaube ich das wir beim ersten durchsuchen des Arrays nicht davon weg kommen, sondern erst beim zweiten mal.
Außer ich verstehe das momentan falsch und es geht auch um RAM. Außerdem kann man dann doch das originale Array löschen.

Was mir noch einfällt:
Haben wir evtl. die Möglichkeit, beim laden der Seite, beziehungsweise des Arrays, es gleich zu sortieren, also die Funktion des Browsers schon zu ändern oder zu nutzen?
Das wir vorm laden des Arrays ein lokales leeres Array erstellen, und dann während dem laden sortiert füllen, oder beim leerlauf der Seite?
Vielleicht auch, das man beim ersten durchsuchen die Werte die er rausnimmt gleich einsortiert, solange er sucht und ab dem letzten Eintrag das Array ins sortierte "wechselt"?
Falls die Liste irgendwo extern gespeichert ist, kann man ja jedesmal beim Leerlauf der Seite anfangen, das Array zu sortieren und ein einfachen Int Wert beim letzten sortierten Eintrag zu erstellen. So ist das Array stück für Stück sortiert, um dann die binäre Suche nutzen zu können.
Beim nächsten Aufruf der Seite, wird dann dort weiter sortiert, wo die Int Variable hinzeigt.
Ansonsten könnte ich mir nur vorstellen, im Binären Modus zu suchen, kombiniert mit dem 1 Sekunden Timer.
 
Hallo,
erstmal sorry, hatte die letzten Tage nicht genug Zeit. Zweitens waow. Das sind einige Ansätze. Vielen Dank, Wie gesagt, php kann ich. Rein von der Logik kann man nicht hingehen und die Array-Ids nehmen, deren Werte zutreffen könnten. Und diese ids dann in eien Art Hilfsarray packen. Bei jedem Buchstaben müssten dann nur noch die ids des Hauptarray geprüft werden, die im Hilfs Array zu finden sind. Die Position des Cursors müsste man sich merken und wenn die neue position < alte Position ist, Hilfsarray leeren, weil xBanan würd ein 0 resultieren und wenn man dann das x löscht, würde 1 rauskommen.
Bsp.: var Hauptarray {"Apfel": 1, "Birne": 2, "Banane": 3, "Pflaume": 4,"Ananas":5,"Anakonda":6};
User gibt A ein -> var Hilfsarray: {1,5,6}
Autotext zeigt nur Var redHauptarray {"Apfel":1,"Ananas":5,"Anakonda":6) Per if habe ich alle anderen ids übersprungen.
Dann gibt der user N (AN) ein -> var Hilfsarray: {5,6}
Autotext zeigt nur {"Ananas":5,"Anakonda":6}
Das ausfiltern der Ids würde ich über ein Hilfs php Array machen. Denn dann verteile ich die Rechenlast auf Client und Server. Es geht max um 5000 Einträge. Wenn ich dann noch sage minLength=4, dann muss das Script erst ab dem dritten Buchstaben anfangen zu rechnen und die Einträge dürften sich deutlich reduzieren, wenn nicht gleich der erste Buchstabe ein Tippfehler ist.
Dann hätte man bei Nachnamen zb "Schn" als Eingabe, Schmidt, Schmitt, Schulte, und 90% ausgeschlossen. Übrig bleiben würden dann doch nur vielleicht max. 50 (Schnieder, Schneider, Schnudke,...) Bei ASch wärs sofort 0. Gleiches bei den anderen Massennamen wie Meier und Müller. In China würde es nicht gehen vermutlich (5 Telefonbücher mit wang ;) und Amerika mit Jones ebenfalls nicht. Aber diese beiden Fälle werden nicht vorkommen.
mfG
tsunami
 
Zurück