Zo, de eerste Javascript applicatie
“powered by Knutselsmurf” is een feit.
Ik beschouwde Javascript altijd als een speelgoedtaal waarmee je advertenties en tellers en andere overbodige toeters en bellen aan HTML kon toevoegen. Dat oordeel dateert van 10 jaar geleden, toen ik het voor het eerst gebruikte bij de website van mijn vriendin. Het was in de tijd dat je mensen nog kon imponeren met plaatjes waar wat aan veranderde als je muiscursor er overheen ging.
Ongeveer een jaar geleden kwam de taal opnieuw in mijn gedachten, toen ik iets interactiefs wilde maken voor OBA. Een soort formulier, het was nergens voor nodig de server lastig te vallen voordat alles er op stond. Formuliervalidatie is bij uitstek een taak voor Javascript.
OBA is een website die wordpress als motor heeft. De content wordt up to date gehouden door een ander systeem dat ik gebouwd heb. Dat gebruikt alleen PHP, maar voor deze nieuwe applicatie zou PHP geen goed idee zijn. We gebruiken namelijk een reverse caching proxy om de website lekker snel te houden, en alles wat PHP gebruikt breekt dat systeem. Het prototype gebruikte wel PHP, maar de uiteindelijke versie zou helemaal met Javascript moeten werken.
De gebruikelijke manier om persoonlijke voorkeuren door te geven aan een website is via cookies.
Dat wilde ik niet. Die cookies zouden erg groot worden, waardoor het systeem traag zou gaan werken. Bovendien wilde ik alleen maar statische content laten serven door nginx. Tot een half jaar geleden werkte OBA nog met PHP op de webserver, en je zag het systeem met de dag trager worden. Vanaf het moment dat ik het systeem splitste in een front-end en een back-end, kreeg het vleugels. Het paste weer met gemak in de 512MB VPS.
Telkens als een van de leden iets publiceert, wordt de OBA voorpagina geupdate. Daarnaast wordt elke 10 minuten 1/6 deel van de blogs op de voorpagina bezocht door onze spider, om de comment-counts op te halen. Normaal zie je dat pas, als je de hele pagina opnieuw ophaalt.
Ik heb een systeem bedacht, waarbij d.m.v. AJAX technieken slechts dat deel van de pagina dat nieuw is wordt opgehaald. WordPress kan dat niet, de template van het theme produceert altijd 1 pagina. Ik moest dus een paar extra ingangen maken, zodat mijn script afzonderlijke posts kon ophalen.
Ik heb een tijdje nagedacht over verschillende alternatieven.
AJAX ( Asynchronous Javascript And XML ) is een verzameling al langer bestaande technieken waarmee het mogelijk wordt een deel van een pagina te vernieuwen, terwijl de rest blijft staan. Als je bijvoorbeeld met je F5 toets een pagina vernieuwt, wordt de hele pagina vervangen. Daarnaast bestaat al sinds mensenheugenis Flash ( zoals de bekende Youtube filmpjes ge-embed in pagina’s ).
Bij AJAX is het geen embedded object waarin je iets ziet bewegen, de hele pagina kan veranderen. Websites hebben deze technieken massaal omarmd, overal zie je de extra venstertjes die bovenop de website lijken te zweven, allemaal mogelijk dankzij AJAX technieken.
De X in AJAX staat voor XML, maar het kan in feite elk soort data zijn. MijnOBA gebruikt JSON ( JavaScript Object Notation ).
Oorspronkelijk reageerde Javascript op de muis en het toetsenbord, om websites een beetje interactief te maken.
AJAX technieken voegen daar het netwerk aan toe. Het script dat aan een pagina gekoppeld is reageert nu ook op het binnenkomen van data vanaf internet.
En daarmee verdwijnt het verschil tussen een website en een applicatie grotendeels, de website wordt een App.
Met de webserver waarover wij kunnen beschikken is het niet mogelijk data ongevraagd naar de clients te pushen. Dat zou de snelste methode zijn. Wij gebruiken het iets langzamere polling, waarbij de client op gezette tijden een request naar de server stuurt.
Ik heb gespeeld met het idee om alle nieuwe posts naar elke client te sturen, maar dat zou telkens een piek in het dataverkeer veroorzaken. OBA levert geen tijdkritische informatie. Ik had een client een verzoek kunnen laten sturen als ” stuur mij alles dat nieuw is sinds …. “. Maar dat soort verzoeken kan niet makkelijk afgehandeld worden door een proxyserver.
Daarom heb ik een tussenstap gemaakt :
Een inhoudsopgave. En om het dataverkeer nog verder te decimeren, heb ik de inhoudsopgave gesplitst in een “max 1 uur oud” en een “alles”.
Clients die inschakelen vragen de “alles” op en daarna elke 5 minuten de “recent” versie, en die 2 worden gemerged door het script.
Die 2 filetjes kan nginx heel simpel cachen.
De inhoudsopgave bevat precies genoeg info voor het script om te bepalen of het de post opvraagt of niet :
Auteurnr, post-nummer, publicatietimestamp, updatetimestamp.
Het filetje dat gekoppeld is aan het postnummer bevat alles waarmee Javascript een div element kan maken zoals wordpress het ook zou maken.
Het bleek verrassend eenvoudig om wordpress zo aan te passen dat het die json filetjes produceert. Het is niet eens nodig binnen wordpress events af te vangen, je hoeft alleen maar op verzoek van de proxyserver een json filetje te maken. Die proxyserver zorgt er wel voor dat zulke verzoeken niet vaker komen dan 1 keer per minuut, wat de back-end makkelijk aan kan.
Omdat het een wordpress extentie is, maak ik natuurlijk gebruik van alles wat wordpress al aan boord heeft. De scripts die de json filetjes produceren zijn klonen van de xmlrpc interface. De applicatie zelf is gebouwd als een page-template van een theme. WordPress heeft een mechanisme om scripts te schedulen, zodat een pagina die een bepaald script nodig heeft daar op tijd over kan beschikken, zonder dat het dubbel wordt gedownload. Erg handig.
Themes verdelen een pagina netjes in widget area’s en de applicatie gebruikt een widget voor de bediening. Het ziet er uit alsof het zo hoort, en het is heel makkelijk te maken.
jQuery.
Wordpress maakt zelf gebruik van de jQuery javascript library. jQuery is goud waard. In eerste instantie viel het me tegen wat je er mee kon, soms spaarde het wat tikwerk uit. De grote kracht van jQuery zit echter in het opvangen van browserverschillen. Zonder jQuery zou je daar constant rekening mee moeten houden, nu hoef je alleen maar te denken : hoe vertel ik dit aan jQuery ?
Internet Explorer.
Waarschijnlijk het meest gehate stuk software ter wereld, en verdiend.
Crapware.
Het is me inmiddels duidelijk wat de strategie van Microsoft is :
Neem zitting in standaardisatiecommittees. Daar kun je leren wat je concurrenten van plan zijn, en die plannen kun je daar frustreren.
Ongeveer een jaar voordat een nieuwe standaard uitgekristaliseerd is, breng je een product op de markt dat niet compatible is met de toekomstige standaard. Je klanten willen de boot niet missen, dus die gaan software ontwikkelen voor jouw systeem. Concurrenten die zich aan de afgesproken standaard houden, zien zich straks geconfronteerd met voldongen feiten en zijn gedwongen 2 sets standaards te volgen : jouw standaard en de echte standaard.
Klanten die jouw browser gebruiken, zien zich later door het verschijnen van volgens de nieuwe standaard werkende software gedwongen de volgende versie van jouw browser aan te schaffen, anders kunnen ze bepaalde websites niet goed gebruiken. Je geeft die browser gratis weg, maar hij werkt natuurlijk alleen op de nieuwste Windows versie, die niet gratis is.
Toen MijnOBA klaar was, ben ik nog ruim een week bezig geweest om aanpassingen aan te brengen opdat het ook met MSIE zou werken.
Volgens mij heeft Microsoft veel mensen in dienst die gewend zijn aan Basic ( Beginners All-pupose Symbolic Instruction Code, nog erger dan Cobol )
Mogelijk willen ze ook klanten van dienst zijn die dat alleen begrijpen.
Javascript (ECMA-script) is een object-georienteerde scripttaal die je natuurlijk prima kan gebruiken alsof het Basic is. Ga je bij Microsoft kijken hoe hun baksels werken, dan krijg je zulke voorbeelden.
Variabelen hebben bij Microsoft voorbeeldcode allemaal een global scope, en niets is object-georienteerd.
Het MijnOBA script is helemaal objectgeorienteerd geschreven. Dat bleek lastiger dan ik dacht, want als je ergens op klikt, wordt er een clickhandler aangeroepen, een method van je object. Binnen die functie ga je er van uit dat “this” de betekenis heeft : “binnen dit object”.
Zo werkt het wel in andere object-georienteerde talen, maar niet in JavaScript.
Daar is de betekenis van “this” afhankelijk van het event dat de method aangeroepen heeft.
Oeps.
Er is een mooie oplossing voor : bound functions. Je kan een functie definieren met dezelfde eigenschappen, maar met een aan je object gekoppelde “this”.
Je moet het een paar keer lezen, maar over veel dingen is goed nagedacht door de makers van Javascript.
Alleen hebben ze dat bij Microsoft niet gedaan, dus je moet nogal wat code toevoegen om dingen als bound functions te laten werken als het script wordt uitgevoerd door JScript van de MSIE browser.
Ik vind gemiddeld genomen object-georienteerd programmeren “de nieuwe kleren van de keizer”, maar een van de voordelen is wel, dat een object zijn eigen namespace is. Er kan nooit een conflict ontstaan, en je hoeft dus ook geen belachelijk lange namen te gebruiken voor variabelen.
De AJAX completion-handlers zitten dus ook in mijn object.
Ik vertel jQuery dat ik data nodig heb van de server, en welke method die data gaat verwerken als hij het opgehaald heeft.
Dat werkt bijna magisch. ( op wat timing probleempjes na )
Alleen kunnen zelfs de slimme jongens van jQuery geen manier verzinnen om dat te vertalen naar de Microsoft methode.
Ten eerste, omdat je server extra headers moet toevoegen aan de responses, anders is het niet veilig, volgens Microsoft.
Ten tweede, omdat Microsoft code een object (XDomainRequest) in de global space nodig heeft om de server-reply ergens aan te kunnen koppelen.
Als je eenmaal gewend bent aan jQuery Ajax, is het een vreemde gewaarwording dat dezelfde methode bij Microsoft niet werkt.
http://msdn.microsoft.com/en-us/library/cc288060(v=vs.85).aspx#properties
( er stond een fout in, ik heb een mailtje van Microsoft waarin ze me bedanken dat ik ze er op wees, ik denk dat ik het ga inlijsten )
Als je een XDomainRequest object als lokale variabele gebruikt in een functie, zul je in de access log van de server zien waarom het niet werkt :
“Client closed connection while awaiting server-reply”
Juist ja.
Het moest even indalen.
2 Dagen werk naar de knoppen.
Je hoeft ook niet te proberen XDomainRequest een method van je Object te laten aanroepen als callback, zoals je met jQuery doet.
Wil niet, je moet een functie in de global space definieren.
Die functie heeft vervolgens de Eval methode nodig om de callback aan te roepen. Iedereen weet dat Eval de deur is naar cross-site scripting attacks.
De attacker zou immers de hosts file kunnen manipuleren, waardoor het script data van een andere server krijgt. Ik heb dus een controle in de Microsoft callback gebouwd, om zeker te zijn dat die data afkomstig is van mijn eigen server.
tools :
Zonder softwaregereedschap had ik het nooit voor elkaar gekregen. Ik heb een server met Ubuntu en dezelfde combinatie van nginx en apache waar OBA ook mee werkt. Nginx heeft een gebruiksaanwijzing, maar die moet je niet teveel vertrouwen, testen is beter.
Firebug is een browserextentie waarmee je Javascript kan debuggen. Het voegt een intelligent console toe aan je browser, zodat je boodschappen naar een console kan sturen zonder dat je schermoutput verandert. Internet Explorer heeft er een nagemaakte versie van met minder mogelijkheden. Daar heb je eigenlijk niet veel aan. Er is een Firebug voor Firefox en voor Google Chrome. De Firefox versie is de meest uitgebreide, die kan ook gebruikt worden als console voor de FirePHP plugin, een PHP debugger.
Informatie over Javascript, browsers, DOM etc. komt van Mozilla Developer Network. Vroeger gebruikte ik vaak informatie van W3Schools, maar ik heb door schade en schande geleerd dat die website niet deugt. Hij staat altijd bovenin de zoekresultaten, maar SEO-optimalisatie is het enige waar ze goed in zijn. De informatie die je krijgt is vaak verouderd, incompleet of gewoon onjuist.
Ik heb JS-Lint gebruikt om er achter te komen hoe het met de kwaliteit van mijn javascript code zat. Browsers klagen niet snel. JS-Lint is een kritische syntax-checker voor Javascript. Het was erg leerzaam voor mij als beginnend Javascript programmeur om te zien waar hij zoal op let.
Ten slotte gebruik ik YUI-compressor om het script zo klein mogelijk te maken. Het is geen compiler, maar hij haalt alle overbodige tekst uit het script. Comments, whitespace, returns gaan er uit en variabele-namen worden ingekort tot de kleinst mogelijke lengte. Je scripts worden er ongeveer half zo groot door. Een bijkomend voordeel is, dat ze niet makkelijk te doorgronden zijn voor amateur-hackers.
Overigens is het belangrijk je code zo te ontwerpen dat het makkelijk te debuggen is. Het is moeilijk om daar richtlijnen voor te schrijven.
Ik verdeel mijn code in routines die een beschrijvende naam hebben, en ik begin het testen door elke routine zijn eigen naam naar het scherm te laten schrijven. Later haal ik dat weg, maar door hier een tijdje naar te kijken wordt het een soort liedje in je hoofd, en ga je het programma veel beter begrijpen. Dit soort dingen leer je niet van een opleidingsinstituut, ik heb ooit bij Volmac (Gap Gemini/ Sogeti ) een cursus gehad waar ik je leerde dat iedereen op de hele wereld op dezelfde manier ( hun manier ) software moest maken, anders kon iemand anders het niet lezen. De namen van alle routines moesten beginnen met een letter en dan 3 cijfers, en je mocht in het hele programma alleen hoofdletters gebruiken, anders konden sommige compilers het niet verwerken. Het was natuurlijk Cobol, en hoewel de mogelijkheden om het voor mensen ook leesbaar te maken in Cobol erg beperkt zijn, mocht je ook nog eens een deel van die mogelijkheden niet gebruiken. Debuggen was niet de bedoeling, je moest alles van tevoren ontwerpen, dan moest je een uitvoervoorspelling maken en dan mocht je maximaal 10 keer de compiler gebruiken en als het dan niet werkte kreeg je strafpunten.
Mijn eigen methode werkt andersom. Ik maak veel fouten. Er zijn mensen die minder fouten maken dan ik, maar er bestaan geen mensen die geen fouten maken. Ik maak helemaal geen ontwerp op papier. Ik heb een computer. Ik maak geen uitvoervoorspelling. Er komt uitvoer uit. Als die me niet bevalt, pas ik het programma aan. Ik maak geen ontwerp in pseudo-code. Ik schrijf meteen in code wat ik bedoel en in comments wat ik nog moet maken.
Ik stop debug hulpmiddelen in mijn code, en die blijven er in zitten. Een goede foutafhandeling verdient zichzelf terug, al lijkt het in eerste instantie overdreven.
Ik heb nagedacht over misbruik en namaak. OBA heeft daar in het verleden last van gehad.
Dit script kan niet gebruikt worden om even heel simpel alle content van OBA op je eigen website te zetten.
Onze server controleert natuurlijk wat een browser als referer opgeeft, daar zal het al meteen op stuk lopen.
Daarnaast maakt de Same Origin Policy die browsers hanteren het onmogelijk om een client te bewegen data op te halen van OBA terwijl het script afkomstig is van een namaker.
Maar dat is niet genoeg. Een server zou OBA kunnen bezoeken en alles kopieren.
De constructie met een table-of-content bood echter een handvat om misbruik te ontmoedigen.
De namaker zou een filetje nodig hebben dat ik louter en alleen om juridische redenen heb toegevoegd.
Dat filetje bevat alleen een copyright notice en een paar willekeurige getallen.
Maar die getallen heb je echt nodig om de table-of-content te vinden.
Technisch is het een obstakel van niks, maar juridisch is het vrij stevig : zonder schending van de auteurswet lukt het niet om een mirror van OBA te maken. Bovendien maakt het veranderen van het filetje elke kopie waardeloos : bona-fide clients veranderen dan mee en namaak scripts niet.
Leave a Reply