Warning: Table './petronio_db1/hijack_cache_page' is marked as crashed and should be repaired query: SELECT data, created, headers, expire FROM hijack_cache_page WHERE cid = 'http://hijack.it/node/6' in /accounts/petronio/www/drupal/includes/database.mysql.inc on line 174

Warning: Cannot modify header information - headers already sent by (output started at /accounts/petronio/www/drupal/includes/database.mysql.inc:174) in /accounts/petronio/www/drupal/includes/bootstrap.inc on line 569

Warning: Cannot modify header information - headers already sent by (output started at /accounts/petronio/www/drupal/includes/database.mysql.inc:174) in /accounts/petronio/www/drupal/includes/bootstrap.inc on line 570

Warning: Cannot modify header information - headers already sent by (output started at /accounts/petronio/www/drupal/includes/database.mysql.inc:174) in /accounts/petronio/www/drupal/includes/bootstrap.inc on line 571

Warning: Cannot modify header information - headers already sent by (output started at /accounts/petronio/www/drupal/includes/database.mysql.inc:174) in /accounts/petronio/www/drupal/includes/bootstrap.inc on line 572

Warning: Table './petronio_db1/hijack_watchdog' is marked as crashed and should be repaired query: INSERT INTO hijack_watchdog (uid, type, message, severity, link, location, referer, hostname, timestamp) VALUES (0, 'php', '<em>Cannot modify header information - headers already sent by (output started at /accounts/petronio/www/drupal/includes/database.mysql.inc:174)</em> in <em>/accounts/petronio/www/drupal/includes/common.inc</em> on line <em>141</em>.', 2, '', 'http://hijack.it/node/6', '', '54.198.221.13', 1511355762) in /accounts/petronio/www/drupal/includes/database.mysql.inc on line 174

Warning: Table './petronio_db1/hijack_watchdog' is marked as crashed and should be repaired query: INSERT INTO hijack_watchdog (uid, type, message, severity, link, location, referer, hostname, timestamp) VALUES (0, 'php', '<em>Table &amp;#039;./petronio_db1/hijack_watchdog&amp;#039; is marked as crashed and should be repaired\nquery: INSERT INTO hijack_watchdog (uid, type, message, severity, link, location, referer, hostname, timestamp) VALUES (0, &amp;#039;flickr&amp;#039;, &amp;#039;Could not connect to Flickr, Error: Forbidden&amp;#039;, 1, &amp;#039;&amp;#039;, &amp;#039;http://hijack.it/node/6&amp;#039;, &amp;#039;&amp;#039;, &amp;#039;54.198.221.13&amp;#039;, 1511355762)</em> in <em>/accounts/petronio/www/drupal/includes/database.mysql.inc</em> on line <em>174</em>.', 2, '', 'http://hijack.it/node/6', '', '54.198.221.13', 1511355762) in /accounts/petronio/www/drupal/includes/database.mysql.inc on line 174
Autocompletamento dei campi input text con javascript e ajax | Hijack.it di Giacomo Petronio

Autocompletamento dei campi input text con javascript e ajax

  • warning: Cannot modify header information - headers already sent by (output started at /accounts/petronio/www/drupal/includes/database.mysql.inc:174) in /accounts/petronio/www/drupal/includes/common.inc on line 141.
  • user warning: Table './petronio_db1/hijack_watchdog' is marked as crashed and should be repaired query: INSERT INTO hijack_watchdog (uid, type, message, severity, link, location, referer, hostname, timestamp) VALUES (0, 'flickr', 'Could not connect to Flickr, Error: Forbidden', 1, '', 'http://hijack.it/node/6', '', '54.198.221.13', 1511355762) in /accounts/petronio/www/drupal/includes/database.mysql.inc on line 174.
ajax
Questa guida è basata sul tutorial che ho preso da qui e il cui autore è Nicholas C. Zakas.

L'effetto finale sarà questo:

NB: Con Internet Explorer 7 l'esempio qui riportato non funziona correttamente per problemi legati a incompatibilità con lo StyleSheet del tema Garland di Drupal. Motivo in più per usare Firefox. In ogni caso il codice è funzionante, quello che da problemi è il foglio di stile.

Per ottenere tutto questo sarà necessario utilizzare javascript (programmazione ad oggetti) e scrivere qualche riga nel foglio di stile della pagina.
Se volete utilizzare il codice per implementare questo oggetto velocemente potete andare direttamente al metodo veloce, se invece volete vedere come viene realizzato l'autocompletamento, potete partire da qui:

Metodo Lento
Come potete notare, quando scrivete qualcosa nel campo input (per es 'b'), la parola viene automaticamente completata con il primo suggerimento trovato ('baldassarre'), selezionando la parte aggiunta ('aldassarre'):
suggerimento
Comincieremo creando il codice necessario per fare questa operazione.

Il programma sarà costituito da due entità, la classe principale che chiameremo AutoSuggestControl e una classe di utilità che fornirà i suggerimenti ad AutoSuggestControl e che chiameremo StateSuggestions.
Intanto creiamo la classe AutoSuggestControl. Per inizializzarla dovremo passargli la textbox che avrà l'autocompletamento, e un istanza della classe StateSuggestions:
function AutoSuggestControl(oTextbox, oProvider) {
    this.provider = oProvider;
    this.textbox = oTextbox;
    this.cur = -1;
    this.layer = null;
    this.init(); 
}
Oltre al riferimento alla textbox e all'istanza del suggeritore, ci sarà utile un indice (cur) per gestire la posizione del focus nella lista dropdown, un riferimento chiamato layer in cui salveremo il riferimento al <div> per creare la lista dropdown ed infine c'è la chiamata al metodo init() che inizializzerà l'oggetto.

Selezione del testo
Adesso creiamo il metodo della classe AutoSuggestControl per evidenziare la porzione della parola suggerita. Il metodo avrà come parametri la posizione del carattere di partenza e il numero dei caratteri da evidenziare nella textbox:
AutoSuggestControl.prototype.selectRange = function (iStart, iLength) {
    if (this.textbox.createTextRange) { // metodo per Explorer 
        var oRange = this.textbox.createTextRange();
        oRange.moveStart("character", iStart);

        oRange.moveEnd("character", iLength - this.textbox.value.length);
        oRange.select();
    } else if (this.textbox.setSelectionRange) { // metodo per Mozilla
        this.textbox.setSelectionRange(iStart, iLength);
    }

    this.textbox.focus();
};
Autocompletamento
Adesso che siamo in grado di selezionare una parte di parola, creaiamo il metodo che scrive nella textbox la parte rimanente della parola suggerita.
Il metodo avrà come parametro la parola suggerita.
AutoSuggestControl.prototype.typeAhead = function (sSuggestion) {

    if (this.textbox.createTextRange || this.textbox.setSelectionRange) {
        var iLen = this.textbox.value.length; 
        this.textbox.value = sSuggestion; 
        this.selectRange(iLen, sSuggestion.length); 
    }
};
nell'ordine questo metodo
  • salva la lunghezza della parola scritta dall'utente
  • scrive nella textbox la parola suggerita
il metodo autosuggest()
Questo metodo riceverà un array di stringhe contenente tutte le parole suggerite e un booleano. Il parametro booleano serve per sapere se mostrare o no la lista dropdown.
Controlla che l'array non sia vuoto, se è vuoto nasconde la lista dropdown, altrimenti chiama la funzione typeAhead per scrivere nella text box la prima parola conenuta nell'array.
AutoSuggestControl.prototype.autosuggest = function(aSuggestions,  bTypeAhead) {
    if (aSuggestions.length > 0) {
        if (bTypeAhead) {
            this.typeAhead(aSuggestions[0]);
        }
        this.showSuggestions(aSuggestions);
    } else {
        this.hideSuggestions();
    }
};
La gestione degli eventi
Dovremo gestire i tasti premuti sulla tastiera, controllare quale tasto è stato premuto e agire di conseguenza. Se per esempio viene premuto SHIFT non dobbiamo fare nulla, e questo vale per tutta una serie di tasti, qua sotto potete vedere i tasti che non gestiremo con il relativo codice:
Key Code Key Code
Backspace 8 Print Screen 44
Tab 9 Delete 46
Enter 13 F1 112
Shift 16 F2 113
Ctrl 17 F3 114
Alt 18 F4 115
Pause/Break 19 F5 116
Caps Lock 20 F6 117
Esc 27 F7 118
Page Up 33 F8 119
Page Down 34 F9 120
End 35 F10 121
Home 36 F11 122
Left Arrow 37 F12 123
Up Arrow 38
Right Arrow 39
Down Arrow 40
Quindi i tasti qui sopra non li gestiremo, mentre gestiremo i tasti con codice 8 e 46 (Backspace e Delete) mostrando la lista dropdown ma senza modificare la parola, chiameremo quindi requestSuggestions con il secondo parametro a false, che, ricordo, serve proprio a evitare che venga eseguito il metodo typeAhead. Per tutti gli altri tasti chiameremo requestSuggestions lasciando anche che venga eseguito typeAhead:
AutoSuggestControl.prototype.handleKeyUp = function (oEvent) {
    var iKeyCode = oEvent.keyCode;
    
    if (iKeyCode == 8 || iKeyCode == 46) {
        this.provider.requestSuggestions(this, false);

    } else if (iKeyCode < 32 || (iKeyCode >= 33 && iKeyCode <= 46) || 
    (iKeyCode >= 112 && iKeyCode <= 123)) {

        //ignore
    } else {
        //this.provider.requestSuggestions(this);
        this.provider.requestSuggestions(this, true);
    }
};
Il fornitore dei suggerimenti
Possiamo ora scrivere la classe che suggerirà le parole, ovvero SuggestionProvider. Essa avrà un metodo, requestSuggestion() che fornirà ad AutoSuggestControl una lista di parole. Inoltre in questa classe salveremo la lista delle parole tra cui cercare i suggerimenti.
function StateSuggestions() {

    this.states = [
    "Abelardo","Antonio","Amilcare","Aida",
    "Baldassarre","Beatrice","Benedetta","Benito","Berto","Bruno",

    "Caio","Calimero","Calliope","Callimaco",
    "Dafne","Dante","Danilo","Davide","Denis","Domenico",
    "Emilio","Ernesto","Evaristo",
    "Fabio","Francesco","Federico","Ferruccio",
    "Giacomo","Giuseppe","Gianfranco","Gigi","Geronimo",
    "Luigi","Libero","Laura","Loretta","Luca",
    "Mario","Michele","Mauro","Mattia",
    "Nicola","Nino","Nivea",
    "Oreste","Olga","Olimpia","Oscar","Osvaldo","Omar",
    "Pippo","Peppino","Pasquale","Patrizia","Piero",
    "Quarto","Quirino",
    "Roberta","Roberto","Romeo","Remigio","Rosanna","Raffaele",
    "Sabina","Saverio","Silvano","Silvio","Simone","Sempronio",
    "Tizio","Tiziano","Tullio",
    "Ubaldo","Ugo","Umberto","Ulisse",
    "Veronica","Vinicio","Valentina","Valentino","Virgilio","Vladimiro",
    "Walter","Wanda","Wilma",

    "Zenone","Zaccaria"
    ];
}
Ma la parte interessante è il metodo requestSuggestions:
come parametri accetterà il reference al controllore (per richiamare la sua funzione autosuggest) più il booleano per eseguire o meno il typeAhead:
StateSuggestions.prototype.requestSuggestions =
  function (oAutoSuggestControl /*:AutoSuggestControl*/,  bTypeAhead) {

    var aSuggestions = [];
    var sTextboxValue = oAutoSuggestControl.textbox.value;

    if (sTextboxValue.length > 0){

        // trasformo tutto in minuscolo

        var sTextboxValueLC = sTextboxValue.toLowerCase();

        //cerco le parole che combaciano
        for (var i=0; i < this.states.length; i++) {

            // trasformo anche i suggerimenti in minuscolo
            var sStateLC = this.states[i].toLowerCase();

            if (sStateLC.indexOf(sTextboxValueLC) == 0) {
             aSuggestions.push(sTextboxValue + this.states[i].substring(sTextboxValue.length));
            } 
        }
    }

    //fornisco i suggerimenti al controllore AutoSuggestControl
    oAutoSuggestControl.autosuggest(aSuggestions, bTypeAhead);
};
La lista dropDown
La lista che compare sotto la nostra text box, non è altro che un div con uno stile adatto e posto esattamente sotto la text box. Vedremo che per calcolare la posizione esatta non sarà così semplice.
autocomplete
Questo, intanto, è lo stile del conenitore dei suggerimenti, il box:

div.suggestions {
    -moz-box-sizing: border-box;
    box-sizing: border-box;
    border: 1px solid black;
    position: absolute;
}

Mentre questo è lo stile per i singoli elementi, le stringhe da suggerire:

div.suggestions div {
    cursor: default;
    padding: 0px 3px;
}

Infine questo è lo stile per l'elemento selezionato:

div.suggestions div.current {
    background-color: #3366cc;
    color: white;
}

In questa parte della guida andrò più veloce poichè la maggiorparte sono metodi che interagiscono con il DOM del documento.

Ora vediamo di implementare intanto il metodo per nascondere la lista dropDown, a questo scopo modificheremo lo stile del membro "layer" della classe AutoSuggestControl, che corrisponderà al DIV del box contenente i suggerimenti.

AutoSuggestControl.prototype.hideSuggestions = function () {

    this.layer.style.visibility = "hidden";
};  

Vediamo ora come evidenziare uno degli elementi della lista:

AutoSuggestControl.prototype.highlightSuggestion = 
  function (oSuggestionNode) {

    for (var i=0; i < this.layer.childNodes.length; i++) {
        var oNode = this.layer.childNodes[i];
        if (oNode == oSuggestionNode) {
            oNode.className = "current"
        } else if (oNode.className == "current") {
            oNode.className = "";
        }

    }
};

Ora è arrivato il momento di creare la lista dropdown con il DIV. Il metodo createDropDown, oltre ad inserire i DIV nel documento, registrerò i gestori degli eventi per la lista.

AutoSuggestControl.prototype.createDropDown = function () {

    this.layer = document.createElement("div");
    this.layer.className = "suggestions";
    this.layer.style.visibility = "hidden";
    this.layer.style.width = this.textbox.offsetWidth;
    document.body.appendChild(this.layer);

    // gestione mouse
    var oThis = this;

    this.layer.onmousedown = this.layer.onmouseup =
    this.layer.onmouseover = function (oEvent) {
        oEvent = oEvent || window.event;

        oTarget = oEvent.target || oEvent.srcElement;

        if (oEvent.type == "mousedown") {
            oThis.textbox.value = oTarget.firstChild.nodeValue;
            oThis.hideSuggestions();
        } else if (oEvent.type == "mouseover") {
            oThis.highlightSuggestion(oTarget);
        } else { // mouseup
            oThis.textbox.focus();
        }
    };
};

Dobbiamo ora posizionare la lista nel punto esatto, sotto il text box, per farlo sembrare una vera dropdown menu.
Per farlo dobbiamo trovare le coordinate del punto in basso a sinistra del box, e calcolarne la larghezza.
Scriviamo due metodi che ci aiutano nell' arduo compito:

AutoSuggestControl.prototype.getLeft = function () {

    var oNode = this.textbox;
    var iLeft = 0;

    while(oNode.tagName != "BODY") {
        iLeft += oNode.offsetLeft;
        oNode = oNode.offsetParent;
    }

    return iLeft;
};

AutoSuggestControl.prototype.getTop = function () {

    var oNode = this.textbox;
    var iTop = 0;

    while(oNode.tagName != "BODY") {
        iTop += oNode.offsetTop;
        oNode = oNode.offsetParent;
    }

    return iTop;
};
I due metodi risalgono di nodo in nodo, dalla textbox al nodo "BODY", prendendo l'offset sinistro (o alto) rispetto al nodo superiore, e sommandoli ogni volta. Alla fine del ciclo avremo in pixel la distanza della textbox dal bordo sinistro e alto della pagina.

Usiamo i due metodi appena visti per far apparire la lista nel punto esatto creando questo metodo che accetta una lista di suggerimenti e li mostra nel layer, in pratica crea un div dentro a layer per ogni suggerimento e ci scrive dentro la parola:

AutoSuggestControl.prototype.showSuggestions = function (aSuggestions) {

    var oDiv = null;
    this.layer.innerHTML = "";

    for (var i=0; i < aSuggestions.length; i++) {

        oDiv = document.createElement("div");
        oDiv.appendChild(document.createTextNode(aSuggestions[i]));

        this.layer.appendChild(oDiv);
    }

    this.layer.style.left = this.getLeft() + "px";
    this.layer.style.top = (this.getTop()+this.textbox.offsetHeight) + "px";
    this.layer.style.visibility = "visible";
};

Possiamo pensare ora alla gestione degli eventi della tastiera, come l'uso delle freccie per muoversi in alto e in basso nella lista dropdown. A questo scopo creiamo i metodi che ci permettono di navigare nella lista:

AutoSuggestControl.prototype.nextSuggestion = function () {
    var cSuggestionNodes = this.layer.childNodes;

    if (cSuggestionNodes.length > 0 && this.cur < cSuggestionNodes.length-1) {

        var oNode = cSuggestionNodes[++this.cur];
        this.highlightSuggestion(oNode);
        this.textbox.value = oNode.firstChild.nodeValue;
    }
};

AutoSuggestControl.prototype.previousSuggestion = function () {
    var cSuggestionNodes = this.layer.childNodes;

    if (cSuggestionNodes.length > 0 && this.cur > 0) {
        var oNode = cSuggestionNodes[--this.cur];
        this.highlightSuggestion(oNode);
        this.textbox.value = oNode.firstChild.nodeValue;
    }
};

Per gestire i tasti su e giu dobbiamo creare un metodo che gestirà questi due eventi e richiamerà i metodi appena scritti per navigare nella lista

// funzione che verrà bindata per gestire gli eventi key arrow
AutoSuggestControl.prototype.handleKeyDown = function (oEvent) {
    switch(oEvent.keyCode) {
        case 38: //up arrow
            this.previousSuggestion();
            break;
        case 40: //down arrow
            this.nextSuggestion();
            break;
        case 13: //enter
            this.hideSuggestions();
            break;
    }
};
Abbiamo finalmente finito di scrivere i metodi funzionali alla nostra applicazione, ma dobbiamo ancora inizializzarli, questo ci permetterà di legare gli eventi della tastiera con i metodi di gestione che abbiamo scritto. Ecco quindi il metodo init:

AutoSuggestControl.prototype.init = function () {
    var oThis = this;
    //quando si verifica un evento onkeyup, esegui questa funzione 
    //passandogli l'oggetto oEvent che è una cosa propria del DOM:
    this.textbox.onkeyup = function (oEvent) { 

        // può essere che con Explorer non ci sia l'oggett oEvent, 
        // al suo posto viene utilizzato l'oggetto event della classe window:
        if (!oEvent) { 
            oEvent = window.event;
        }

        // assegno a handleKeyUp la gestione dell'evento onkeyUp
        oThis.handleKeyUp(oEvent); 
    };


    // per gestire le freccie e l'enter per il menu a tendina
    this.textbox.onkeydown = function (oEvent) {

        if (!oEvent) {
            oEvent = window.event;
        }

        oThis.handleKeyDown(oEvent);
    };

    // per gestire la perdita del focus, dovrò nascondere la tendina
    this.textbox.onblur = function () {
        oThis.hideSuggestions();
    };

    this.createDropDown();
};

Si conclude così la guida per la creazione di un text box con l'autocompletamento e con l'autosuggeritore.
Per provarlo da voi leggete ancora il metodo veloce qui sotto, dove trovate anche il file javascript con tutto il codice necessario.


Metodo Veloce
Prendete il codice javascript da qui che dovrete includere nella vostra pagina, per esempio scrivendo nell'header qualcosa come
<script type="text/javascript" src="/scripts/autocompletamento.js"></script>

Aggiungete ancora questo script nell'header della pagina che avrà la text box:

<script type="text/javascript">
window.onload = function () {
var oTextbox = new AutoSuggestControl(document.getElementById("txt1"), new StateSuggestions());
}
</script>

Inserite nel vostro foglio di stile CSS queste righe:

div.suggestions {
    -moz-box-sizing: border-box;
    /*box-sizing: border-box;*/
    border: 1px solid black;
    position: absolute;
}
div.suggestions div {

    cursor: default;
    padding: 0px 3px;
    background-color: #eef;
}
div.suggestions div.current {
    background-color: #6580D8;
    color: white;
}

a questo punto dovete creare un campo input text con id=txt1:
<input id="txt1" type="text"/>
Per modificare le parole suggerite aprite il file javascript, troverete un array con tutte le parole possibili, vi basterà modificare quell'array scrivendo le parole che volete che vi vengano suggerite.


Se trovate degli errori nel codice o qualche malfunzionamento non esitate a scrivermi alla mail che trovate nella sezione contatti.

Comments

sfondo

Ciao! Ottima guida!! nonostante nel css sia impostato il colore di sfondo del div che mostra i risultati dell'autocompletamento, mi viene visualizzato sfondo trasparente. Hai idea del perchè? Ho provato con Firefox, Safari, Chrome. Stesso comportamento.

Grazie!

Nic.


Warning: Table './petronio_db1/hijack_watchdog' is marked as crashed and should be repaired query: INSERT INTO hijack_watchdog (uid, type, message, severity, link, location, referer, hostname, timestamp) VALUES (0, 'php', '<em>Table &amp;#039;./petronio_db1/hijack_cache_page&amp;#039; is marked as crashed and should be repaired\nquery: LOCK TABLES hijack_cache_page WRITE</em> in <em>/accounts/petronio/www/drupal/includes/database.mysql.inc</em> on line <em>174</em>.', 2, '', 'http://hijack.it/node/6', '', '54.198.221.13', 1511355762) in /accounts/petronio/www/drupal/includes/database.mysql.inc on line 174

Warning: Table './petronio_db1/hijack_watchdog' is marked as crashed and should be repaired query: INSERT INTO hijack_watchdog (uid, type, message, severity, link, location, referer, hostname, timestamp) VALUES (0, 'php', '<em></em> in <em>/accounts/petronio/www/drupal/includes/database.mysql.inc</em> on line <em>174</em>.', 2, '', 'http://hijack.it/node/6', '', '54.198.221.13', 1511355762) in /accounts/petronio/www/drupal/includes/database.mysql.inc on line 174

Warning: Table './petronio_db1/hijack_watchdog' is marked as crashed and should be repaired query: INSERT INTO hijack_watchdog (uid, type, message, severity, link, location, referer, hostname, timestamp) VALUES (0, 'php', '<em>Table &amp;#039;./petronio_db1/hijack_sessions&amp;#039; is marked as crashed and should be repaired\nquery: UPDATE hijack_sessions SET uid = 0, cache = 0, hostname = &amp;#039;54.198.221.13&amp;#039;, session = &amp;#039;messages|a:1:{s:5:\\&amp;quot;error\\&amp;quot;;a:2:{i:0;s:225:\\&amp;quot;user warning: Table &amp;amp;#039;./petronio_db1/hijack_cache_page&amp;amp;#039; is marked as crashed and should be repaired\\nquery: LOCK TABLES hijack_cache_page WRITE in /accounts/petronio/www/drupal/includes/database.mysql.inc on line 174.\\&amp;quot;;i:1;s:88:\\&amp;quot;user warning: in /accounts/petronio/www/drupal/includes/database.mysql.inc on line 174.\\&amp;quot;;}}&amp;#039;, timestamp = 1511355762 WHERE s in /accounts/petronio/www/drupal/includes/database.mysql.inc on line 174