AJAX (asynchroon javascript xml) Introductie Laten we maar eens beginnen met dit buzz-word van het jaar 2005 uit elkaar plooien: Asynchroon: Dit slaat op het niet-synchroon laden van gegevens met het laden van de rest van de pagina. Je kan de gegevens dus achteraf op ieder willekeurig moment opvragen. Dit is ook meteen de kracht van AJAX. Je hoeft dus niet altijd de hele pagina te laden, maar kan nu ook een klein stukje opvragen. Dit is veel sneller en doordat het scherm niet eens hoeft te flikkeren geeft dit veel meer het gevoel van een echte interactieve applicatie. Javascript: XMLHTTPRequest, zoals het officieel heet, wordt ook gebruikt in Flash, Java en nog een hele collectie andere programmeer-talen. Maar wij hebben het hier over javascript, dat zo ongeveer door iedere moderne browser (vanaf IE 5.5+ en Mozilla 1.4+) wordt ondersteund. Dit maakt AJAX ook echt het overwegen waard. dummy A: puur voor de uitspraak, want AJX bekt niet lekker. XML: XMLHTTPRequest was ontworpen voor het inladen van XML-documenten, maar daar is het zeker niet tot gelimiteerd! Ik bespreek hier drie methoden van het formuleren van gegevens. Als extratje laat ik nog even een klein beetje event-afhandeling zien, om jullie te bevorderen het te gebruiken. Ik vereis wel dat je weet wat ik bedoel met objecten (bijna gelijk aan die van PHP) en dat je weet dat Java niet Javascript is, en natuurlijk dat Javascript hoofdletter gevoelig is. Voorbeelden Ik heb nog een paar voorbeelden bijgevoegd, die ik zelf heb gebruikt/gemaakt om XMLHTTPRequest te begrijpen. Ze zijn allemaal heel simpel, en niet hier besproken of gedocumenteerd o.i.d. Bij vragen, kan je natuurlijk het forum raadplegen. phphulp.ikhoefgeen.nl/xmlhttprequest.zip
Javascript events Ikzelf maak maar al te graag gebruik van events. Dit is een soort alternatief voor de onmouseover en onclick attributen in je tags. De functie die ik maar al te graag gebruik, ook weer puur om van de ongelijkheden in verschillende browsers af te komen: function addevent(obj, eventtype,fn, usecapture) if (obj.addeventlistener) obj.addeventlistener(eventtype, fn, usecapture); return true; else if (obj.attachevent) var r = obj.attachevent("on"+eventtype, fn); return r; Een voorbeeld van gebruik van deze functie: Stel, we hebben een plaatje met als id afbeelding (<img src="" id="afbeelding" alt=""/>). Deze kunnen we vanuit Javascript oproepen via document.getelementbyid('afbeelding') en dan aan een variabele toewijzen. Pas op: Je kan pas een element uit de pagina selecteren nadat de pagina helemaal geladen is. Gebruik hiervoor het onload-event. Zelf vind ik dit de meest handige aanpak. Maak een functie genaamd init() en zet hier alles in wat er moet gebeuren zodra de pagina geladen is (variabelen een waarde geven, andere functies starten bijvoorbeeld) Plaats deze regel onderaan in je code: addevent(window, 'load', init); Die een schietgebedje. Er is een redelijke kans dat je computer kan exploderen. Nu moeten we dus een even hangen aan dit object. Zie het totaalvoorbeeld, aangezien dat naar mijn mening veel makkelijker uitlegt dan al die code tussen tekst: function init() var afbeeldingobject = document.getelementbyid('afbeelding'); addevent(afbeeldingobject, 'click', laatlezerschrikken); function laatlezerschrikken() alert('boeh!'); addevent(window, 'load', init);
Javascript events (extra en overbodig) Nog even een klein voorbeeldje van wat je nog meer kan met events. Dit is echt overbodige luxe, maar ik vind toch dat je het moet weten :-) function init() var inputobject = document.getelementbyid('invulformulier'); addevent(inputobject, 'keyup', geefoverbodigeinformatie); return; function geefoverbodigeinformatie(e) if(e.keycode == 13) alert('jij grapjas, jij drukte op enter!'); //voor meer keycodes, probeer eens alert(e.keycode); return; addevent(window, 'load', init); In dit geval is er een element wat je de aandacht kan geen, zoals een formulier-element (<select/> en <input/> bijvoorbeeld) maar ook op window werkt het. Zo kan je dus vrij gemakkelijk de escape-knop afvangen. Experimenteren met de return (false/true/niets) levert weer andere effecten op.
Het XMLHTTPRequest object Het Object aanmaken De implantatie van XMLHTTPRequest verschilt helaas wel per browser. Gelukkig merken we dat eigenlijk alleen in het initialiseren (het aanmaken) van het object. De rest is zo goed als algemeen onder alle implantaties. Deze functie gebruik ik altijd voor het maken van een object. Hij is eigenlijk heel onlogisch en niet netjes en maakt gebruik van een hoopje bij elkaar gebietste hacks voor zowel IE als Mozilla, maar werkt wel. function createhttphandler() httphandler = false; /*@cc_on @*/ /*@if (@_jscript_version >= 5) // JScript gives us Conditional compilation, we can cope with old IE versions. // and security blocked creation of the objects. try httphandler = new ActiveXObject("Msxml2.XMLHTTP"); catch (e) try httphandler = new ActiveXObject("Microsoft.XMLHTTP"); catch (E) httphandler = false; @end @*/ if (!httphandler && typeof XMLHttpRequest!='undefined') httphandler = new XMLHttpRequest(); return httphandler; Een voorbeeld van het gebruiken van deze functie is overdreven simpel: var XMLHTTPObject = createhttphandler(); En nu hebben we ons XMLHTTPObject in de gelijknamige variabele. Initialiseren ervan kan het beste pas waneer de pagina geladen is, omdat elementen in je pagina pas beschikbaar zijn waneer de pagina klaar is met laden. Zie mijn event-pagina s voor een van de vele methoden. Het object zijn parameters meegeven Je hebt nu een leuk object, maar dat object heeft echt geen flauw idee wat het moet doen. Het is er gewoon, als een zombie. Dus kleden we het wat aan. XMLHTTPObject.open('GET', 'pagina.php?wachtwoord=1234', true); De eerste parameter (string), in mijn voorbeeld GET kan GET of POST zijn. Ik neem aan dat het verschil wel bekend is. GET geeft zijn parameters via de url mee, POST via de body van de aanvraag.
Mijn tweede parameter, pagina.php, is de url die aangevraagd moet worden. Hier kan je ook nog standaard GET-parameters aan toevoegen, zoals bekend. Derde parameter (boonlean) is de keuze of we een synchroon of een asynchroon vraagje willen stellen. Zet je deze op false, dan zal je pagina bevriezen waneer de aanvraag wordt uitgevoerd. Of dat nou echt is wat we willen... de open()-functie doet niets anders dan parameters instellen. Er wordt nog geen data verzonden, en is nog niets in gang gezet! Okee, nu weten we welke pagina we willen hebben, en op wat voor manier. Maar wat moet er gebeuren met het antwoord? XMLHTTPObject.onreadystatechange=function() if (XMLHTTPObject.readyState==4) //die iets leuks onreadystatechange is een mooi voorbeeld van een andere manier van events gebruiken. Waneer ons object naar een andere fase van de aanvraag springt, moet deze functie worden uitgevoerd. In mijn voorbeeld maak ik gebruik van een naamloze functie, die direct aan het event wordt geplakt. Je kan function() ook vervangen door bijjvoorbeeld geefoverbodigeinformatie uit mijn event(extra)-voorbeeld. Ik denk dat je via addevent() (weer uit mijn event-voorbeeld) ook dit event kan uitbuiten, maar dat heb ik nog nooit geprobeerd, en deze methode lijkt mij onderweg ook handiger. Dan komen we meteen bij mijn if-lus. Deze kijkt in welke fase mijn object dan wel ooit niet bezig is. Er zijn 4 fasen. 1. ik doe niks, maar ben er klaar voor 2. ik doe de aanvraag 3. ik krijg antwoorden binnen 4. ik heb alle antwoorden binnen Vanaf fase 3 zou je in principe de binnengetrokken data kunnen gaan gebruiken, maar waarschijnlijk is de afhandeling sneller dan het ophalen, en dan loopt het zootje vast. Niet doen dus, altijd wachten op fase 4. Houd er rekening mee dat deze functie pas uitgevoerd wordt nadat de aanvraag dus gestart is, en dat is nog steeds niet het geval. Ik ga mijn cliff-hanger niet nu al verraden. En we hebben data, en wel in het object. En dat gaan we eruit halen, muhaha! var inhouddiv = document.getelementbyid('inhoud'); inhouddiv.innerhtml = XMLHTTPObject.responseText; En dan zit er in onze div het antwoord van de aanvraag. Als alternatief kan je responsexml gebruiken, maar daar kom ik op terug in de pagina s over alternatieve data-formaten.
We kunnen nog wat meer parameters meesturen, waar we later geen spijt van zullen krijgen: XMLHTTPObject.setRequestHeader("Cache-Control", "no-cache"); XMLHTTPObject.setRequestHeader("X_USERAGENT", "MijnAjaxApplicatie"); Ik stuur nog een header mee om caching te voorkomen. Als alternatief kan je je url nooit hetzelfde laten worden, door bijvoorbeeld een timestamp als parameter mee te sturen. Ik stuur ook nog een header mee waaraan ik de aanvraag van mijn object straks in mijn script aan terug kan herkennen. Later wordt wel duidelijk waarom ik dat zou willen. De daadwerkelijke request XMLHTTPObject.send(null); Hahaha, dat was het dan. send() zet het allemaal in werking. send() moet altijd een parameter meekrijgen, namelijk de inhoud van de body van de aanvraag. Ik maakte een GETaanvraag, dus de body blijft leeg. Ik moet toch null invullen, anders krijg ik foutmeldingen. In het geval van een POST-aanvraag vul je daar in plaats van null je gegevens in, geschreven als parameter=waare¶meter2=waarde. Komt je vast bekend voor.
De afhandeling van de vraag Het voorgaande was het javascript-gebeuren, nu weer terug naar PHP. Ik neem aan dat je PHP wel aardig beheerst, en dat ik hier dus niet iedere stap uit hoef te leggen. Ik heb een aanvraag gedaan naar pagina.php, met als GET-parameters wachtwoord. Dat wil ik nu gehashed weer terug sturen naar de cliënt. <?php echo md5($_get['wachtwoord']);?> Dat is inderdaad aanzienlijk makkelijker dan het aanvragen. Maar ik had ook nog een aparte header mee gestuurd, iets met USERAGENT. Waar was dat dan goed voor? Dat was om de compatibiliteit te verhogen. Zo kan je snel kijken of je te maken hebt met een gebruiker die de pagina via mijn object aanroept, of gewoon met de browser er heen gaat. In dat laatste geval kan je dan complete HTML-uitvoer geven. Voor de AJAX klant stuur je alleen de benodigde informatie terug. De rest heeft hij immers nog. Hoe herken ik mijn object dan nu? <?php if(isset($_server['http_x_useragent']) && $_SERVER['HTTP_X_USERAGENT'] == 'MijnAjaxApplicatie') //een ajax-klant else //een saaie browser die een saaie standaard aanroep doet.?>
De 4 manieren om data te schrijven Bij mijn weten zijn er 4 manieren om gegevens de coderen. 1. Als gewone saaie HTML met alles erop er eraan 2. Als platte tekst, die gesplitst moet worden 3. Als XML 4. Als JSON Manier 1: Gewone HTML Deze heb ik al hierboven in mijn voorgaande voorbeelden gebruikt, omdat deze de makkelijkste methode is. Maar daarbij ook de smerigste. Je stuurt namelijk allemaal zooi op die de browser ook prima zelf kan: de opmaak. Een antwoord wordt daardoor vele malen groter en minder abstract. Die abstractie is makkelijk bij het maken van meerdere applicaties, of het aanpassen van het uiterlijk. Heb je die abstractie niet, dan moet je dus ook je PHP-bestanden allemaal aanpassen. Manier 2: Platte tekst Deze manier gebruik ik zelf graag (Tot nu toe) omdat hij bijna net zo simpel is, maar ook abstractie geeft. Javascript voorbeeld: (mijn antwoord zit in XMLHTTPObject.responseText ) //de if-lus e.d. if(xmlhttpobject.repsonsetext!= '') var dataalsstring = XMLHTTPObject.repsonseText; var dataalsarray = new Array(); dataalsarray = dataalsstring.split(' '); alert('titel:'+dataalsarray[0]+'\n' +'Auteur:'+dataAlsArray[1]+'\n' +'Pagina\'s:'+dataAlsArray[2]); Als antwoord had ik opgestuurd: De Ijzeren Wil Bas Haring 174 Het grote nadeel is dat het antwoord zo abstract is, dat je niet weet wat wat is. Misschien was de drukkerij wel De Ijzeren Wil genaamd, en heette het boek Bas Haring. Ik moet mijn applicatie uit het hoofd kennen om de data te kunnen snappen. Dit is niet erg als je systeem alleen voor jezelf is, en je er later nooit meer naar om hoeft te kijken, maar kut waneer dat dus niet het geval is. Daarbij komt nog eens het probleem bij uitbreiden. Wil je meer data versturen, bijvoorbeeld voor een ander object dat wel gebruik maakt van hetzelfde adres (PHP-bestand), dan moet je de toevoegingen erachter zetten. Manier 3: Als XML Bij XML kan je overal je gegevens uitbreiden, alle kanten op. En al je gegevens in je antwoord zijn mooi gelabeld, dus het is duidelijk voor buitenstaanders, of jezelf over 1 jaar.
Nadeel is de grote hoeveelheid code die je meestuurt (voor iedere waarde moet je 2 maal een sleutel meesturen. Een begin-tag en een eind-tag.) en de nog grotere hoeveelheid code die je nodig hebt om de boel te parsen. Dit gaat bijna hetzelfde als het parsen van de html-pagina. var antwoord = XMLHTTPObject.responseXML; var root = antwoord.documentelement; var boeken = root.getelementsbytagname('boek'); var schrijver = null; var titel = null; var paginas = null; for(i = 0; i < boeken.length; i++) paginas = getnodevalue(boeken.item(i), 'paginas'); schrijver = getnodevalue(boeken.item(i), 'schrijver'); titel = getnodevalue(boeken.item(i), 'titel'); alert('titel:'+titel+'\n' +'Auteur:'+schrijver+'\n' +'Pagina\'s'+paginas); function getnodevalue(obj,tag)//dankje Peter Paul Koch (www.quikrsmode.org) return obj.getelementsbytagname(tag)[0].firstchild.nodevalue; Het antwoord ziet er dan ongeveer zo uit: <?xml version="1.0" encoding="utf-8"?> <boeken> <boek> <titel>de Ijzeren Wil</titel> <schrijver>bas Haring</schrijver> <paginas>168</paginas> </boek> </boeken> Let wel op dat waneer je het antwoord met PHP maakt, je de header Content-type: application/xml meestuurt, anders wordt het niets, en ziet javascript het gewoon als saaie HTML, en bestaat het reponsexml niet eens. Manier 4: JSON (het beste uit te spreken als Jason (ja, nog een griek)) Bij deze methode gaat het erom dat je het antwoord als javascript-object (arrays en objecten zijn in Javascript heel erg vergelijkbaar) terugstuurt. Het voordeel is dat je het antwoord niet meer hoeft te parsen. Dat scheelt overdreven veel code, en nog meer processorrondjes. Nadeel (vinden sommigen tenminste) is het moeten gebruiken van eval(), om de string (het antwoord) om te zetten naar een object.
Helaas kan ik hier nog niet meer informatie over geven, aangezien dit ook nieuw voor mij is. Dit stukje komt dan waarschijnlijk nog in de nabije toekomst.