Jeg har på fornemmelsen, dine problemer opstår, fordi du bruger AJAX på en måde - eller til noget - som falder udenfor AJAX's styrker :)
Lad mig lige ridse den typiske anvendelse for AJAX op:
AJAX's helt stor styrke er ved database-opslag. I en traditionel web-applikation, kaldes et ASP- eller PHP-dokument, der igen kalder en DB og formaterer resultatet. Derefter sendes resultatet til brugerens browser, hvor det vises.
Ved brug af AJAX kan man spare på disse fulde sideudskrivninger. Når en side er nede i browseren, kan den ændres ved hjælp af AJAX og DOM - alt efter brugerinput/-forespøgsler.
Serveren kaldes og et helt simpelt recordset sendes til klienten. Her bladres det igennem og der oprettes markup-elementer med de modtagne data og evt. styles og får sat event-handlers, før de indsættes i sidens DOM-træ og derved vises.
Resultatet er, at vi flytter så meget som muligt af det traditionelle serverarbejde væk fra den storsvedende server og ned på klienten, der strutter af ubrugt CPU og RAM.
Der opnåes altså primært følgende fordele ved brug af AJAX (i tilfædig rækkefølge):
1) Serveren aflastes i væsentlig grad
2) Der transporteres færre bytes
3) Brugeroplevelsen bliver bedre, mere flydende og applikationsagtig
4) Endelig er det gamle ønske om en direkte database adgang via JS i en webapplikation næsten opfyldt
Dermed bliver f.eks. script-chats pludselig langt mere realistiske og services som Google Suggest [
http://www.google.com/webhp?complete=1&hl=en ] kommer indenfor rækkevidde med mere end 12 samtidige brugere :)
En forudsætning for at få fordelene ved AJAX er dog, at man udveksler så få og rå data som muligt mellem server og klient.
Det opnås ikke ved, at man formaterer XML som den færdige markup, lige til klistre ind i dokumentet. Derfor bruges AJAX bedst i kombination med traditionelle sideskift - udfra, hvad der er bedst egnet i den givne situation.
Der er mange fordele ved at bruge DOM fremfor strenge og 'innerHTML' - mere om det til sidst - og det behøver ikke være så besværligt.
Man kan f.eks. oprette et template-element udfra noget bestående HTML og efterfølgende klone dette element - i stedet for at bruge 'createElement' et hav af gange.
Hvis vi f.eks. har et HTML-dokument med en fortegnelse over en skoles klasser:
<a href="#" onclick="showKlasse('1. V')">1. V</a><br>
<a href="#" onclick="showKlasse('1. U')">1. U</a><br>
<a href="#" onclick="showKlasse('2. V')">2. V</a><br>
<a href="#" onclick="showKlasse('2. U')">2. U</a><br>
... osv ...
- så vil 'showKlasse' typisk sende en asynkron XmlHttp forespørgsel til serveren med variablerne (det kan du, så detaljerne er ikke interessante her):
contxtID = "getKlasse" // bestemmer, hvad der skal ske på serveren
klasseID = "2. V" // sat via argumentet i kaldet til 'showKlasse'
På serveren ses på 'contxtID' og en en passende funktion kaldes - f.eks. via en switch i PHP (eller Select Case i VBS).
I dette tilfælde kaldes en DB med en SQL-streng à la:
"SELECT `navn`, `sex`, `age` FROM `klasser` WHERE `klasse`='2. V'"
Det resulterende recordset gælder det nu om at få formateret så let som muligt - og så det fylder så lidt som muligt. Det kan gøres på flere måder.
Én måde er en XML-streng, der returneres til klienten som et XML-dokument.
En anden er JSON (JavaScript Object Notation), som jeg selv foretrækker, da det fylder mindre at sende - og efterfølgende er lettere og mere direkte tilgængeligt via JS på klienten.
For bedste performance bør man så vidt muligt ikke bygge strenge i PHP eller VBS/JS og returnere dem. Man bør i stedet anvende tilgængelige server komponenter, der er beregnet til at outputte XML eller JSON. De fungerer ofte hundreder - og til tider tusinder - af gange mere effektivt end den mest effektive, ækvivalente script-kode.
I XML kunne et typisk output se sådan ud:
<?xml version="1.0" encoding="iso-8859-1"?>
<data>
<contxtID>getKlasse</contxtID>
<klasseID>2. V</klasseID>
<rows>
<elev>
<navn>Ole</navn>
<sex>m</sex>
<age>7</age>
</elev>
<elev>
<navn>Sascha</navn>
<sex>f</sex>
<age>7</age>
</elev>
<elev>
<navn>Gaby</navn>
<sex>f</sex>
<age>7</age>
</elev>
</rows>
</data>
I JSON kunne det se sådan ud (dette er min egen favorit-notation - og faktisk en kombi af XML og JSON. Det kan også gøres i JSON alene):
<?xml version="1.0" encoding="iso-8859-1"?>
<data>
<![CDATA[
{
contxtID:"getKlasse",
klasseID:"2. V",
rows:[
{navn:"Ole",sex:"m",age:7},
{navn:"Sascha",sex:"f",age:7},
{navn:"Gaby",sex:"f",age:7}
]
}
]]>
</data>
I XmlHttp-objektets callback-handler spørges på 'contxtID' og værdien afgør, hvad der skal ske med responsen. I et XML-scenarium kunne det se sådan ud
(læg mærke til wrapper-funktionen 'gA', som sparer for en masse kode - og begynd så iøvrigt ved onload-handleren)
- det forudsættes, at 'myCallBack' kaldes, når XmlHttp-objektets 'readyState' er 4 og at objektet selv sendes med som argument:
--------------------------------------------------------------------------------
<script type="text/JScript">
function gA(o,tag){return o.getElementsByTagName(tag)}; // Handy wrapper
// - der returnerer et array af elementer under elementet 'o'
function myCallBack(oXmlHttp) {
var oDoc, oXml = oXmlHttp.responseXML;
oDoc = oXml.documentElement;
// Check for, hvad der skal gøres
switch ( gA(oDoc, "contxtID")[0].firstChild.nodeValue ) {
case "getKlasse":
visKlasse( gA(oDoc, "klasseID")[0].firstChild.nodeValue, gA(oDoc, "rows")[0] );
break;
}
}
function visKlasse(sKlasseID, oRowsNode) {
var aRows, aCells, oTmp, oDispl = document.getElementById("displ");
// Tøm tbody-elementet, hvis der allerede har været vist en klasse:
while (oDispl.firstChild) oDispl.removeChild(oDispl.firstChild);
// Find elev-elementerne i XML'en
// - og gå dem igennem:
aRows = gA(oRowsNode, "elev");
for (var i=0; i<aRows.length; i++) {
// Opret en række udfra vores template:
oTmp = oTempl.display.cloneNode(true);
// Find dens celler og fyld data i dem:
aCells = gA(oTmp, "td");
sSex = gA(aRows[i], "sex")[0].firstChild.nodeValue;
sSex = (sSex=="f")? "Pige" : (sSex=="m")? "Dreng" : "?";
aCells[0].firstChild.nodeValue = gA(aRows[i], "navn")[0].firstChild.nodeValue;
aCells[1].firstChild.nodeValue = sSex;
aCells[2].firstChild.nodeValue = gA(aRows[i], "age")[0].firstChild.nodeValue + " år";
// Føj rækken til tbody-elementet:
oDispl.appendChild(oTmp);
}
}
var oTempl = {};
window.onload = function() {
// Opret de templates, der er brug for i dokumentet
// - og slet derefter originalerne.
// Her kloner vi en tabelrække og sletter den.
var oTr, oDispl = document.getElementById("displ");
oTr = gA(oDispl, "tr")[0];
oTempl.display = oTr.cloneNode(true);
oDispl.removeChild(oTr);
// Herefter kunne vi oprette flere templates
// - og klistre dem på 'oTempl' som properties,
// hvis der var brug for flere i dokumentet.
}
</script>
<table>
<thead>
<th>Navn</th>
<th>Køn</th>
<th>Alder</th>
</thead>
<tbody id="displ">
<tr>
<td> </td>
<td> </td>
<td> </td>
</tr>
</tbody>
</table>
--------------------------------------------------------------------------------
Med JSON er det hele lidt simplere. Jeg nøjes med de to berørte funktioner:
function myCallBack(oXmlHttp) {
var sJson, oJson, oXml = oXmlHttp.responseXML;
// Find JSON-strengen i XML'en:
sJson = oXml.documentElement.firstChild.nodeValue;
// Opret et JS-objekt udfra strengen
// - og læg det i variablen 'oJson':
eval("oJson = " + sJson);
// Check for, hvad der skal gøres
switch ( oJson.contxtID ) {
case "getKlasse":
visKlasse( oJson.klasseID, oJson.rows );
break;
}
}
function visKlasse(sKlasseID, aRows) {
var aCells, oTmp, oDispl = document.getElementById("displ");
// Tøm tbody-elementet, hvis der i forvejen har været vist en klasse:
while (oDispl.firstChild) oDispl.removeChild(oDispl.firstChild);
for (var i=0; i<aRows.length; i++) {
// Opret en række udfra vores template:
oTmp = oTempl.display.cloneNode(true);
// Find dens celler og fyld data i dem:
aCells = gA(oTmp, "td");
sSex = aRows[i].sex;
sSex = (sSex=="f")? "Pige" : (sSex=="m")? "Dreng" : "?";
aCells[0].firstChild.nodeValue = aRows[i].navn;
aCells[1].firstChild.nodeValue = sSex;
aCells[2].firstChild.nodeValue = aRows[i].age + " år";
// Føj rækken til tbody-elementet:
oDispl.appendChild(oTmp);
}
}
--------------------------------------------------------------------------------
HTML'en, der klones, kan naturligvis være meget mere kompliceret med links, billeder og hvadsomhelst. Så navigerer man blot ned til elementerne og sætter værdier udfra responsen fra serveren.
Hvad angår det at bruge 'innerHTML', skal man tænke på, at noget af dokumentet overskrives - hvorved referencer kan gå tabt. Det sker ikke, når man indsætter, flytter og sletter elementer med DOM.
Det er ikke altid vigtigt, men man skal ikke lave ret komplicerede applikationer (og det kunne AJAX jo ellers godt lægge op til), før det giver seriøse problemer. Prøv f.eks. at indsætte det følgende i et HTML-dokoument og smid det i en browser:
--------------------------------------------------------------------------------
<script type="text/JavaScript">
function foo() {
alert("Knappen oBtn's ID: " + oBtn.id);
}
function bar() {
var oDiv, sHtml = "<button>Ny Knap</button>";
oDiv = document.getElementById("gnu");
oDiv.innerHTML += sHtml;
}
var oBtn = null;
window.onload = function() {
oBtn = document.getElementById("hest");
oBtn.onclick = foo
}
</script>
<div id="gnu" style="padding:20px;border:1px solid red">
<button id="hest">Kald foo</button>
</div>
Test knappen ovenfor<br>
- og indsæt endnu en knap i divet:<br>
<button onclick="bar()">Indsæt</button><br><br>
Test så igen :o|
--------------------------------------------------------------------------------
Hvad angår JSON, kan du læse mere her:
http://www.crockford.com/JSON/- og måske denne også kan være nyttig:
http://www.eksperten.dk/artikler/227De kode eksempler, jeg viser ovenfor, fungerer, men de er simplificerede og ikke særlig generiske.
Optimalt bør man skrive et JS-objekt, der kan håndtere requests frem og tilbage, samt evt. et objekt til at holde styr på DOM-templates - og gøre disse lidt mere 'sexy' end den, jeg viste :)