Kruse-Net.dk

Det man blogger er man selv...

Leg med AJAX

Opdatering: Eksemplet nedenfor har stadig en vis relevans kode-mæssigt, men da Google har droppet deres SOAP API fungerer det ikke længere.

Dagens menu står på kald af SOAP-baserede web services via AJAX. Og hvis ikke du for nylig har forsynet dig med en kop kaffe, så var det måske en god idé at gøre det før du læser meget videre. For det er en hel del komponenter jeg hermed bringer i spil på samme tid. For de utålmodige starter jeg med resultatet — indlejrede Google søgninger:

Bemærk at Google kun tillader 1000 forespørgsler pr. dag pr. nøgle, så hvis der ikke kommer noget svar kan det være derfor.

Det var sjovt, vis mig koden!

Ovenstående er frembragt af følgende:

::HLIGHTBLOCK1::

Okay, men hvor er koden?

Den ligger selvfølgelig i striben af inkluderede javascript filer. Lad os tage dem fra en ende af.

  1. prototype-1.5.0_pre0.js – Sam Stephensons Prototype bibliotek er vel efterhånden noget af det nærmeste man kommer til en standard for AJAX kommunikation, og i øvrigt et rigtigt pænt generelt Javascript bibliotek. Jeg bruger en pre-release af version 1.5.0 her, frisk fra Subversion repositoriet. Jeg har selv fundet stor hjælp i denne dokumentation til Prototype, som dog kun dækker funktionalitet i 1.4. Der er dog kun et enkelt sted jeg bruger en funktion fra 1.5, og det tilmed en meget lille en, så tingene ville ret let kunne omskrives til at køre på 1.4.0 (det er testet).
  2. DocumentX.js – definerer xml og loadXML() på Document klassen, ifald den findes. Dermed kan xml strenge ind- og udlæses i Mozilla på samme måde som i Internet Explorer.
  3. StringX.js – definerer (bl.a.) en metode fix_broken_utf8() på String klassen, der bruges til at rette op på en encoding fejl i Firefox.
  4. xmlhttp.jsAJAX Extended er et smart lille bibliotek af Alex Serebryakov, der skifter brugen af XMLHttpRequest ud med dynamiske script tags, hvormed bl.a. cross-domain problemer undgås. Kræver dog en server-side proxy. Jeg bruger version 0.9 her, men har forbedret lidt på den, så tag min udgave. Alle ændringer er tydeligt markeret.
  5. prototype-xmlhttp.js – justerer Prototype til at anvende AJAX Extended i stedet for XMLHttpRequest på kun tre linier!
  6. ws.js – IBM’s James Snell har skrevet WS-AJAX der er “a cross-platform, JavaScript-based SOAP Web services client based on the Asynchronous JavaScript and XML (Ajax) design pattern for Web applications”. WS-AJAX tager sig af SOAP og message parsing og benytter Prototype (og dermed i mit tilfælde AJAX Extended) til at kommunikere med serveren. Versionen der er tilgængelig på IBM’s side lider af den brist at den kun kan snakke med en web service der ligger på den server scriptet anvendes fra. Et problem jeg har løst ved at anvende AJAX Extended. Desuden bruger jeg en nyere version af Prototype end den James Snell bruger, og der er af den årsag indført nogle rettelser i min udgave af ws.js. Min udgave er baseret på version 0.1.
  7. ws-x.js – retter et problem i XML.getElementsByQName(), og tilføjer en enkelt ofte anvendt metode get_first_child() til WS-AJAX.
  8. Google.js – en Google API klient, og klassen der binder det hele sammen.

Hvordan hænger det sammen?

Når der klikkes på søge-knappen ovenfor kaldes Google.search() metoden i Google.js, her set i uddrag:

::HLIGHTBLOCK2::

Metoden opretter et WS-AJAX Call objekt, udfylder det, og beder om at få udført en SOAP RPC forespørgsel til Google. WS-AJAX bygger en SOAP kuvert med parametrene i og beder derefter Prototype om at levere et objekt der kan transportere kuverten til modtageren. Normalt ville Prototype levere et XMLHttpRequest objekt (det vil den hvis prototype-xmlhttp.js filen fjernes), men her leveres i stedet XMLHTTP objektet fra AJAX Extended. WS-AJAX beder om at få sendt kuverten, så AJAX Extended sender den videre til server-side proxy scriptet (via en eller flere ganske komplicerede javascript uri’er), som kommunikerer med Google og sender svaret tilbage. WS-AJAX parser svaret (et XML dokument) til et SOAP objekt, som gives som parameter til funktionen der erklæres nederst i ovenstående uddrag. Funktionen tager sig derefter af at placere data de ønskede steder på siden.

Pyyh… Det var en lang omgang. Men eftersom det eneste der skal ændres for at snakke med en anden web service er det der ligger i Google.js (ca. 60 linier kode), er det faktisk ikke så kompliceret.

Jeg vil se mere!

Det tænkte jeg nok. Der er en detalje ved html koden ovenfor som ikke er behagelig — den indeholder “funktionalitet” i form af inline script kode. Den slags hører ikke hjemme i html. Heldigvis er det til at råde bod på. Ind træder Behaviour af Ben Nolan. “Behaviors” har længe været kendt i Internet Explorer: I CSS stylesheetet kan man via en særlig behavior: erklæring tilknytte funktionalitet i form af script kode til elementer, på samme måde som der tilknyttes layout. Både Netscape og Microsoft har forsøgt at “sælge” denne idé til W3C, først hver for sig — hhv. Action Sheets og HTML Components, begge i 1998 — og siden sammen i 1999 under titlen “ Behavioral Extensions to CSS“. Men det er aldrig blevet til mere end et Working Draft.

I mangel af en egentlig standard er det heldigvis muligt at implementere konceptet i Javascript, som Ben Nolan har gjort. Med Behaviour kan vi skrive Google eksemplet om som følger:

<input type="text" id="search_string" />
<input type="button" value="Søg" id="search_button" />
<img src="/graphics/spinner-small.gif" alt="spinner"  id="searchingGoogle" style="display: none"/>

<p id="result_section" style="display: none">10 første resultater (af <span id="estimated_count">?</span>):<br /><ul id="results"></ul></p>

Funktionaliteten erklæres i en Javascript fil (her placeret i Google.js for at holde det enkelt):

Google.behaviours = {
  '#search_string' : function(element) {
    Behaviour.addEventObserver(element, 'keydown',
      function(e) {
        if (!e) var e = window.event;
        if (e.keyCode == Event.KEY_RETURN) {
          $('search_button').click();
        }
      }, false
    );
  },
  
  '#search_button' : function(element) {
    Behaviour.addEventObserver(element, 'click',
      function() {
        Google.search($F('search_string'));
      }, false
    );
  }
};

Behaviour.register(Google.behaviours);

Bemærk at Bens version af Behaviour, omend en rigtigt god start, lider af en række fejl og mangler. Mange af disse har venlige personer rådet bod på, men rettelserne er desværre ikke blevet tilføjet den officielle udgave. Du bør derfor anvende min version af behaviour.js indtil Ben får frigivet noget der er bedre. Den kræver i øjeblikket Prototype 1.5, men ville forholdsvis let kunne rettes til at køre uden.

Ovenstående eksempel er ret simpelt, men man kan også anvende mere komplicerede CSS selector udtryk indeholdende både element-navne, klasser, ét id samt alle CSS2 og CSS3 attribut selectors. F.eks. “#mydiv table tr.even a[href*=w3.org]“.

God fornøjelse!

Skriv en kommentar