Artikel top billede

(Foto: Computerworld)

Web 2.0 programmering - del 2

En af hjørnestenene i moderne web-applikationer er brugen
af AJAX til kommunikation mellem frontend-koden i browseren og backend-kode på
serveren. I dette afsnit kigger vi nærmere på, hvordan du kan sende data frem
og tilbage mellem browseren og serveren.

Af Kenneth Geisshirt, Alt om Data

Denne artikel er oprindeligt bragt på Alt om Data. Computerworld overtog i november 2022 Alt om Data. Du kan læse mere om overtagelsen her.

Asynchronous JavaScript and XML (AJAX) kan opfattes som en slags Remote Procedure Call (RPC), hvor browseren kalder en backend-funktion på serveren. Data sendes som XML mellem browser (klienten) og server gennem en HTTP-forbindelse. Gennem tiderne har software-udviklere brugt RPC i mange forskellige afskygninger. I 1990'erne var Corba fra Open Management Group et meget hot emne. Corba er en avanceret form for RPC, hvor det er muligt at have flere backend-server med forskellige procedurer. Klienten kan ikke vide, hvor en procedure eksekveres, da en object broker styrer, hvorhen et kald sendes. Mens Corba er en åben standard, har Microsoft en tilsvarende lukket protokol ved navn DCOM. I dag bruges DCOM ikke af Microsoft, men Microsoft .NET har en række komponenter til RPC.

AJAX er, som nævnt, en form for RPC, hvor et HttpRequest-objekt i browseren bruges til kaldet. Alle moderne browsere understøtter sådan et objekt i en eller anden form. Internet Explorer 5 og 6 bruger et ActiveX-objekt, mens Internet Explorer 7, Firefox, Safari og Opera alle bruger XmlHttpRequest-objekt. Endvidere kan Internet Explorer 7 bruge det "gamle" ActiveX-objekt. Det betyder, at det er besværligt at foretaget AJAX-kald, da du bliver nødt til at bruge forskellige objekter afhængig af, hvilken browser, som er i brug. I infoboks "HttpRequest uden prototype" finder du en enkel udgave af, hvordan oprettelsen af et HttpRequest-objekt kan se ud, så det understøtter flest mulige browsere. Men brugen af dette objekt er besværlig, som du vil kunne se, hvis du vender tilbage til en ældre artikel om emnet (Alt om DATA 2/2008).

Der er dog en række begrænsninger, hvor den væsentligste er, at et AJAX-kald kun kan ske til samme server som browseren indlæste applikationen fra. Det betyder, at det ikke umiddelbart er muligt at kalde en procedure/funktion på en anden web-server end den, som applikationen kommer fra. Dette er gjort at sikkerhedshensyn, så det ikke er muligt at få for let adgang til data på serveren.

AJAX-kald - frontend

Nu lyder AJAX og XmlHttpRequest som noget besværligt noget. Heldigvis letter prototype udvikling af web-applikationer med AJAX-kald en hel del. I infoboks "AJAX med prototype (Javascript)" finder du et eksempel på et meget enkelt kald. Kaldet henter information og applikationens titel (som står øverst) og fod (hvor copyright-erklæringen står). Eksemplet er taget fra foto-applikationen, som blev beskrevet i første afsnit i serien.

Klassen Ajax.Request er en elegant wrapper omkring XmlHttpRequest-objektet og constructor tager en URL som første argument. Denne URL specificerer, hvilken backend-funktion, som skal kaldes i form af hvilken PHP-fil som skal komme i anvendelse. Det andet argument i constructor er valgfrie options. Første option er method som i dette tilfælde er sat til get.

Der er to muligheder, nemlig get og post på samme måde, som når du skriver en HTML-formular. Default-værdien for method er post. Der findes en række callbacks, hvor onSuccess er den mest anvendte. Som du kan se i eksemplet, så sættes værdien af onSuccess til en anonym funktion. Det kan lade sig gøre, da funktioner betragtes som værdier på linje med tal, strenge og arrays i Javascript (på engelsk kaldes dette for first class members). Funktionen kaldes, når AJAX-kaldet er gået godt og data er sendt tilbage til browseren. Her skal du huske, at AJAX-kald, som default er asynkrone, hvilket betyder at Javascript-kode som kommer efter Ajax.Request kan være blevet udført før onSuccess-funktionen, selvom koden kommer efter onSuccess i din Javascript-fil. Du må derfor ikke antage, at AJAX-kaldet har oprettet siden i den efterfølgende kode. Har du brug for at vente på at et AJAX-kald er færdig, må du lade det være synkront. Dette gøres ved at sætte værdien af asynchronous til false.

Den anonyme onSuccess-funktion får returdata fra serveren med i kaldet, i eksemplet som variablen msg. Data kan med fordel sendes i JSON-formatet, og med msg.responseText.evalJSON() får du oversat JSON-data til Javascript-objekter, se infoboks ”JSON” for mere information om JSON-formatet. I det konkrete eksempel er data meget enkelt, nemlig blot et objekt med attributterne title og footer.

AJAX-kald – backend

Det er på tide at se, hvordan backend-delen af et AJAX-kald ser ud. I infoboks "AJAX med prototype (PHP)" finder du backend-delen (i PHP) til AJAX-kaldet netop diskuteret. Som du kan se, er det meget enkelt at implementere. Koden læser konfigurationsfilen (config.ini) og konverterer den til JSON-format med funktionen json_encode og gemmes i variablen $out. Findes konfigurationsfilen ikke, er det naturligvis en fejl, og en passende fejlbesked sendes tilbage (også i JSON-format). Web-serveren (Apache i dette tilfælde) tager sig automatisk af det meste, så det er nok at udskrive variablen $out, for få data sendt tilbage til browseren.

Parametre

Meget ofte er det nødvendig at sende parametre med over til serveren i et AJAX-kald. Der er to muligheder for parametre i Ajax.Request-klassen. For det første kan du sætte parametre på URL'en, som du ville gøre ved ganske almindelig link til web-applikation. For eksempel kan du sætte variablen foo til bar ved at bruge URL'en /zphoto/readconfig.php?foo=bar som første argument i constructor.

Den anden måde er, efter min mening, meget pænere set fra et Javascript-synspunkt. Der er en option ved navn parameters, hvor alle parametre kan opremses som et ”hash array”. Hvis method er sat til get vil Ajax.Request bygge en URL op med parametrene, og hvis method er post vil parametrene blive sendt som en del af POST BODY. Det betyder, at det er let at skifte mellem post og get, uden at du skal skrive parameterhåndtering om. I infoboks ”AJAX med parametre (Javascript)”, kan du se, hvordan du sender parametre med. Eftersom parametre kommer over på samme måde, som når du sender data med over i en HTML-formular, virker PHPs $_REQUEST på sædvanlig måde. I infoboks ”AJAX med parametre (PHP)” kan du se at parameteren tagtext kan findes som $_REQUEST['tagtext'].

Effekter

Som du allerede har set, bruger Ajax.Request-klassen sig af callback-funktioner. Mens funktionen onSuccess bruges, når backend-funktionen returnerer uden fejl, findes der også callback-funktionen onLoading. Funktionen kaldes i det øjeblik, AJAX-kaldet går i gang. Det betyder, at du kan skrive et besked til brugeren for at vise, at der sker noget.

I infoboks ”AJAX-kald med effekt”, kan du se et eksempel på, hvordan du kan bruge onLoading. Selve Javascript-funktionen loadPhotosToScroller indlæser billederne i administrationsdelen af foto-applikationen. I tilfælde af mange billeder, er det rart for brugeren at vide, at der faktisk sker noget. Derfor udskrives der en lille tekst (Loading) så snart AJAX-kaldet går i gang. Det er værd at bemærke, at der ikke sker noget i browseren fra afslutning af onLoading til begyndelsen af onSuccess. Skal du give brugeren en illusion af, at browseren har kontakt med serveren, kan du bruge et animeret GIF-billede. Som du kan se i eksempel, fjerner onSuccess-funktionen beskeden til brugeren som det første med linjen $('scroller-inner').update().

Næste gang

AJAX-kald er meget lettere at skrive med prototype end uden. Der findes flere AJAX-klasser i prototype. Disse klasser er specialiserede til forskellige opgaver. For eksempel kan du bruge Ajax.PeriodicalUpdater til at opdatere indholdet af en DIV eller lignende med faste mellemrum.

I næste afsnit kommer vi videre med en række smarte venner af prototype. Du vil se, hvor let drag-n-drop kan implementeres i dine web-applikationer og, hvordan du kan uploade filer (billeder i min foto-applikationer) uden at skulle generere en ny web-side.

HttpRequest under prototype

function aod_http_request() {
var req = false;
if (window.XMLHttpRequest) {
try {
req = new XMLHttpRequest();
} catch(e) {
req = false;
}
} else if (window.ActiveXObject) {
try {
req = new ActiveXObject("Msxml2.XMLHTTP");
} catch(e) {
try {
req = new ActiveXObject("Microsoft.XMLHTTP");
} catch(e) {
req = false;
}
}
}
return req;
}

AJAX med prototype (Javascript)

function setupPage() {
new Ajax.Request('/zphoto/readconfig.php', {
method: 'get',
onSuccess: function(msg) {
json = msg.responseText.evalJSON();
$('headline').update(json.title);
$('footer').update(json.footer);
}
});
}

AJAX med prototype (PHP)

if (file_exists('config.ini')) {
$cfg = parse_ini_file('config.ini');
$out = json_encode($cfg);
} else {
$out = json_encode("Error in reading configuration file");
}

print $out;

AJAX med parametre (Javascript)

function addTag() {
var text = $F('add_tag');
new Ajax.Request('/zphoto/admin/addtag.php', {
method: 'get',
parameters: {
tagtext: text
},
onSuccess: function(msg) {
var out = msg.responseText.evalJSON();
if (out.status == 'false') {
alert('Failed to save tag');
} else {
var li = new Element('li').update(text);
$('tags').appendChild(li);
}
}
});
}

AJAX med parametre (PHP)

include_once('db.php');

$out = array();
$query = "INSERT INTO tags (text) VALUES ('".$_REQUEST['tagtext']."')";
if (mysql_query($query)) {
$out['status'] = 'true';
} else {
$out['status'] = 'false';
}
print json_encode($out);

AJAX-kald med effekt

function loadPhotosToScroller() {
new Ajax.Request('/zphoto/loadimages.php', {
method: 'get',
onLoading: function() {
$('scroller-inner').update('Loading');
},
onSuccess: function(msg) {
$('scroller-inner').update();
// Fjernet kode til håndtering af returdata
});
}
});
}

[themepacific_accordion]
[themepacific_accordion_section title="Fakta"]

Fejlfinding med Firebug

[/themepacific_accordion_section]
[themepacific_accordion_section title="Fakta"]

Links

[/themepacific_accordion_section]
[themepacific_accordion_section title="Fakta"]

JSON

[/themepacific_accordion_section]
[/themepacific_accordion]