Google Suggest aneb našeptávač s XMLHttpRequest

22. srpna 2005

Pravděpodobně jste již viděli v akci našeptávač Seznamu, který není nepodobný řešení Google Suggest. Zatoužili jste někdy mít podobnou vymoženost na svém webu, ale nevíte, jak na to? Stačí trochu umět JavaScript a přečíst si tento článek.

Motivace

Úkolem je vytvořit inputbox, který bude při psaní nabízet různé pravděpodobné varianty textu, který chce uživatel napsat. Tímto dosáhneme zvýšení komfortu, protože návštěvník pak nemusí ťukat text celý, pouze si vybere vhodnou variantu, čímž se mu výrazně usnadní práce.

Takovouto vymoženost využijí hlavně servery, na kterých jejich návštěvníci hledají ve velkém množství položek podle krátkého textového klíče, například knihovny, různé obchody, databáze čehokoli a nebo vyhledávače, které jsou průkopníky této technologie. Velice snadno dosáhneme kýženého efektu zvýšení produktivity, protože obvykle již po několika napsaných znacích se v nabídce objeví hledaný výraz.

Popis řešení

Na čem si technologii odzkoušíme my? Abychom se nemuseli pachtit se serverovou částí, využijeme (nebo zneužijeme?) Google Suggest, na který se napojíme. Bohužel nám toto bude fungovat jen v nezabezpečeném Microsoft Internet Exploreru, protože stahovat data z cizího serveru je v rozporu s běžnou bezpečnostní politikou většiny prohlížečů. (Řešení tohoto problému pomocí vlastní proxystránky viz článek AJAX a minilink.org – interaktivní zkrácení odkazu.) Pokud ale budete komunikovat se serverem, ze kterého se načetla webová stránka, nenarazíte na žádný problém. Výsledek našeho snažení si můžete prohlédnout zde: falešný Google Suggest. Ve zdrojovém kódu naleznete vše potřebné.

Zkusíme si spustit libovolný nástroj pro sledování http požadavků,a snadno zjistíme, že prohlížeč vysílá dotazy ve tvaru:

http://www.google.com/complete/search?hl=en&js=true&qu=hledaný+text

Stejně tak si tedy budeme počínat my. Ještě se podíváme, jak může vypadat výsledek:

sendRPCDone(frameElement, „interval“,
  new Array(
    „interval international“,
    „interval“,
    „interval training“,
    …),
  new Array(
    „2,190,000 results“,
    „17,100,000 results“,
    „1,750,000 results“,
    …),
  new Array(„“));

Text jsem schválně naformátoval a zkrátil, aby bylo zřejmé, o co kráčí. Je to volání JavaScript funkce, která je uvnitř logiky Google. Jak vidíme, podstatný je hlavně třetí parametr, kde jsou v poli samotné navrhované texty, a ve čtvrtém parametru jsou hned odhadované počty výsledků těchto hledání.

Google umí pracovat i s prohlížeči, které XMLHttpRequest nepodporují, tam se dotaz vyvolá vytvořením neviditelného iframe, do něhož se načte stejná URL adresa, pouze s parametrem js nastaveným na false. Pak je odpověď navíc zabalena do HTML kódu, který zařídí zpracování tohoto JavaScriptového kódu. O tom se sami můžete přesvědčit. My ale tuto variantu programovat nebudeme, protože naším primárním cílem je naučit se pracovat s objektem XMLHttpRequest a ukázat si jeho použití v praxi.

Základním prvkem, kolem kterého se vše bude točit, je klasický textbox, tedy input type="text". Na tomto prvku budou navěšeny JavaScript události, které budou vyvolávány při psaní, přičemž je třeba průběžně posílat napsaný text na server, který vyhledá vhodné varianty textu a vrátí je klientovi. Ten je pak zobrazí pod textboxem například v nečíslovaném seznamu. Tento seznam (říkejme mu třeba suggestBox) bude samozřejmě vhodně ostylován.

Co je XMLHttpRequest?

Ve světě Windows, kde má tento objekt své kořeny, se jedná o doplněk XML parseru. Microsoftí XML Parser je knihovna objektových tříd, které programátorovi usnadňují práci s XML daty, jejich tvorbu a rozebíráním. V podstatě jde o implementaci Document Object Model (DOM). XML Parser je používán mnohými produkty Microsoftu, také MSIE, ve kterém je přístupný také přímo v JavaScriptu, prostřednictvím technologie ActiveX.

Jak bylo řečeno, součástí parseru je také ona slavná třída XMLHttpRequest, která vlastně slouží k získávání XML dat prostřednictvím protokolu HTTP. Neposkytuje ovšem jen XML data, ale umí také data vracet například jako stream, tedy proud binárních dat, nebo jako řetězec, čehož využijeme. Mnoho vývojářů velice brzy zjistilo, že je tato třída nesmírně užitečná, a začali ji používat ve svých aplikacích. Brzy byly tyto možnosti implementovány do dalších prohlížečů, proto je máme k dispozici ve všech současných majoritních desktopových prohlížečích.

JavaScript kód

Při načtení stránky bychom nejdříve mohli zkusit vytvořit instanci třídy XMLHttpRequest – ta se pro každý prohlížeč bohužel vytváří jinak – a pokud se to nepodaří, taktně o tom informujeme uživatele provokujícím JavaScriptovým okénkem:

function init(){
  self.xml = getObj(„Msxml2.XMLHTTP“);
  if (!xml) xml = getObj(„Microsoft.XMLHTTP“);
  if (!xml)
  {
    if (typeof XMLHttpRequest!=“undefined“)
      xml = new XMLHttpRequest();
  };
  if (!xml) {
    alert(„Našeptávač nefunguje ve vašem “ +
      „neschopném prohlížeči.“);
  }
  self.encFunc = encodeURIComponent ?
      encodeURIComponent : escape;
}
function getObj (obj) {
  try { return new ActiveXObject(obj); }
  catch(e) { return null; }
}

Na konci funkce init() jsme si také připravili funkci, která nám bude sloužit pro zakódování hledaného textu do parametru URL. Touto funkcí by měla být encodeURIComponent(), ovšem pokud ji prohlížeč nezná, zafunguje escape() – zde ovšem mohou nastat problémy s diakritikou.

Nesmíme samozřejmě zapomenout zajistit, aby se nám funkce init() po načtení stránky opravdu spustila, například postaru takto:


<body onload=“init();“>

Dalším krokem bude napsání funkce, která nám umožní snadno skrývat nebo zobrazit suggestBox. Skrývání bude fungovat s půlsekundovým zpožděním:

function disp(st) {
  self.act = st;
  var ul = document.getElementById(„suggest“);
  if (st)
  {
    clearTimeout(self.timer);
    ul.style.visibility = „visible“;
  }
  else
  {
    var f = function(){ul.style.visibility = „hidden“;}
    self.timer = setTimeout(f, 500);
  }
}

Konečně se dostáváme k hlavnímu bodu, k samotnému požadavku na server, který zajistí funkce go(), která bude volána z event handleru našeho textboxu, při stisku tlačítka. HTTP požadavek bude vyvolán jen v tom případě, pokud se nám skutečně podařilo vytvořit XMLHttpRequest a pokud se při stisku tlačítka změnil obsah textboxu (tlačítkem by mohla být šipka a podobně).

Před vlastním HTTP requestem zkontrolujeme, zda právě nějaký neprobíhá. Pokud ano, stornujeme jej metodou abort(). Proces zpracování požadavku probíhá asynchronně, takže si otevřeme nový požadavek s danou URL, navěsíme na něj obsluhu události onreadystatechange a požadavek odešleme. Systém pak bude volat náš ovladač při každé změně stavu zpracování. Pokud je tento stav roven „4“, znamená to, že požadavek je vyřízen a výsledná data jsou k dispozici přes vlastnost responseText požadavku. V takovém případě tedy zobrazíme náš suggestBox a spustíme JavaScript, který nám přišel v odpovědi – ten se postará o samotné naplnění boxu našeptávače.

function go(){
  var q = document.getElementById(„q“);
  if (q.value != self.last){
    self.last = q.value;
    if (xml) {
      if (xml.readyState != 0) xml.abort();
      xml.open („GET“, „http://www.google.com/“ +
          „complete/search?hl=en&js=true&qu=“ +
          self.encFunc(q.value), true);
      xml.onreadystatechange = function(){
        if (xml.readyState == 4 && xml.responseText){
          disp(1);
          eval(xml.responseText);
        }
      }
      xml.send(null);
    }
  }
}

Jak jsme si ukázali, Google v odpovědi na požadavek vrací JavaScript kód, který volá Googlí funkci sendRPCDone(), my si tedy takovou funkci sami naprogramujeme, abychom pak příchozí data úspěšně zpracovali. Jejím úkolem bude vymazat starý obsah boxu našeptávače a naplnit jej novým. Budeme potřebovat pouze třetí volaný parametr, nadefinujeme tedy tři parametry, zbývající pak budou při volání funkce zahozeny:

function sendRPCDone(frameEl, searchText, results){
  var ul = document.getElementById(„suggest“);
  while (ul.childNodes.length > 0)
    ul.removeChild(ul.childNodes[0]);
  for (var i=0; i < results.length; i++){
    var li = document.createElement(„LI“);
    var label = results[i];
    li.innerText = label;
    ul.appendChild(li);
    li.onclick = function(){
      document.getElementById(„q“).value =
        this.innerText;
    }
  }
}

Aby uživatel nápovědu pouze neviděl, ale také ji mohl snadno použít, doplnil jsem nejjednodušší možnost – po kliku na zvolenou variantu se tato automaticky stane obsahem textboxu. To je zařízeno prostřednictvím obsluhy události onclick na položce seznamu.

HTML kód

Náš HTML kód tedy, jak jsme si řekli, bude obsahovat hlavně textbox s handlery na různé události. Při stisku tlačítka je třeba načítat nápovědu, při opuštění prvku nápovědu skryjeme a po opětovném zaměření prvku nápovědu zobrazíme. Pomocí atributu autocomplete="off" zabráníme prohlížeči, aby sám použil svůj doplňovací systém – kdyby nám náš suggestBox překryl svým, asi by to nevypadalo moc pěkně.

Za textboxem bude následovat odesílací tlačítko a pod ním bude suggestBox. Celé to zabalíme do formuláře, který bude odesílat data na Google. Z tohoto důvodu zahrneme i další skryté elementy. Také z tohoto důvodu použijeme na stránce kódování UTF-8, aby nám správně zafungovala čeština.

<form action=“http://www.google.com/search“>
  <input type=“hidden“ name=“complete“ value=“1″/>
  <input type=“hidden“ name=“hl“ value=“en“/>
  <input type=“text“ name=“q“ id=“q“ size=“40″
    onkeyup=“go();“ onfocus=“disp(1);“ onblur=“disp(0);“
    autocomplete=“off“ />
  <input type=“submit“ value=“Hledat“ />
  <br/>
  <ul id=“suggest“></ul>
</form>

A úplně nakonec doladíme vzhled boxu našeptávače. Důležité je hlavně absolutní pozicování, aby nám okénko „viselo“ nad okolním textem, a styl visibility=hidden, abychom na počátku měli suggestBox skrytý, než začneme psát do textboxu:

#suggest {
  font: 11px sans-serif;
  width: 200px;
  position: absolute;
  border: 2px solid #ddd;
  margin: 0px;
  list-style-type: none;
  cursor: default;
  visibility: hidden;
}

Náš našeptávač bychom samozřejmě mohli v mnohých směrech vylepšovat, například aby nehledal, pokud je textbox prázdný, nebo aby se suggestBox nezobrazil, pokud nejsou žádné výsledky, popřípadě bychom mohli uživatele nechat pohybovat se v boxu našeptávače šipkami, aby nemusel natahovat ruku k myši, když zrovna píše. To jsou však již podružnosti, jejichž řešení přesahuje rámec tohoto článku.

A drobná poznámka nakonec. Nejsem zaměstnancem Google, ba ani s touto společností nemám nic společného. Pouze jejich produkty shledávám velmi kvalitními a užitečnými, a proto na nich rád demonstruji různé ukázky, pokud je to v tu chvíli vhodné.

Odkazy a zdroje

Původní diskuse ke článku

Původní diskuse k tomuto článku naleznete zde.

Předchozí článek martinbrodeur.net
Další článek dundeeweb.net
Štítky: Články

Mohlo by vás také zajímat

Nejnovější

3 komentářů

  1. DEVELO

    Dub 26, 2010 v 20:03

    Fakt nic moc, napsal bych více užitečný článek(o našeptávači :-P) ale za money :-)

    Odpovědět
  2. Miroslav Kucera

    Dub 26, 2010 v 22:39

    A co vam brani :-) Clanky na interval.cz jsou honorovane.

    Odpovědět
  3. Juraj

    Srp 10, 2011 v 1:44

    Zdravim, chcel som si to vyskusat a pri tomto kode mi vrati chybu

    XMLHttpRequest cannot load http://www.domena.sk
    is not allowed by Access-Control-Allow-Origin.

    viete to zdovodnit alebo mi s tim pomoct ?

    Odpovědět

Napsat komentář

Vaše e-mailová adresa nebude zveřejněna. Vyžadované informace jsou označeny *