Tag-arkiv: JavaScript

Et par tricks med selvmodificerende JavaScript kode

JavaScript sproget er dynamisk, hvilket vil sige at mens ens script afvikles kan alt modificeres. Har man et objekt, er det muligt at tilføje og fjerne properties på det. Sagt med andre ord er det muligt at modificere den kode der afvikles, både ved at tilføje, fjerne og overskrive funktioner samt variable.

Note: I de efterfølgende eksempler er der lagt vægt på at illustrere den selvmodificerende kode, og ikke på at opstille gyldig HTML (doctype angivelser med videre er udeladt for overblikkets skyld), og tilsvarende anvendes blot “onload” istedet for en mere korrekt cross browser metode.

De fleste kender til selvmodificerende javascript kode fra event handlers. Når et DOM elements “onclick” sættes til at pege på en javascript funktion, udnyttes faktisk blot javascript sproget selvmodificerende egenskaber:

<html>
<body>
<input type="button" id="knap" value="Tryk på mig" />
<script type="text/javascript">
function haandterKnapKlik() {
  alert("Hehe - det kilder!");
}
var knapObjekt = document.getElementById("knap");
knapObjekt.onclick = haandterKnapKlik;
</script>
</body>
</html>

I ovenstående simple eksempel sættes onclick eventhandler funktionen ved hjælp af et inline script. Det samme resultat kan opnåes med dynamisk en tilføjet funktion som her:

<html>
<body>
<input type="button" id="knap" value="Tryk på mig" />
<script type="text/javascript">
var knapObjekt = document.getElementById("knap");
knapObjekt.onclick = function () {
  alert("Hehe - det kilder osse!");
}
</script>
</body>
</html>

Fordelen ved denne metode, er at global scopet ikke bliver “forurenet” med funktionsnavne og variable. Situationen kan nemt opstå hvor der er sammenfald på navne af funktioner og variable, og det kan være ganske lumsk at debugge frem til hvor sådanne fejl er begravet, da det er helt legalt at overskrive en funktion til blot at være en simpel variabel (eller et objekt) og omvendt.

Når man skal sikre at kode kun køres en gang, kan flere strategier anvendes. Typisk ser man implementationer svarende til:

<html>
<body onload="initialiser()">
var initialiseret = false;
function initialiser() {
  if (initialiseret !== false) {
    return;
  }
  // udfør initialiseringen her ...
  initialiseret = true;
}
</body>
</html>

Der er flere problemer med ovenstående. Dels ligger funktionen “initialiser” og variablen “initialiseret” i global scopet og er dermed i fare for at blive overskrevet af andre inkluderede scripts på siden, og der er intet der forhindrer at variablen “initialiseret” senere sættes til false, hvorefter koden fint kan genafvikles.

Istedet kan anvendes en anden strategi med selvmodificerende kode som angivet herunder:

<html>
<body onload="hinnerupnet.initialiser()">
var hinnerupnet = {
  initialiser: function () {
    // udfør initialiseringen her ...
    delete this.initialiser;
  },
  variabelBar: "Hejsa",
  metodeFoo: function () {
    alert(this.variabelBar);
  }
};
</body>
</html>

Funktionen “initialiser” er her pakket ind i et objekt kaldet “hinnerupnet”. Kun selve objekt variablen “hinnerupnet” er placeret i global scope. Både funktionen “initialiser”, “metodeFoo” og variablen “variabelBar” kan dermed kun tilgåes via objektet “hinnerupnet”. Chancerne for at andre scripts på siden skulle overskrive “hinnerupnet” objektet er minimale, og bør være under programmørens egen kontrol.

Ved første kald til “hinnerupnet.initialiser()” udføres koden og der afsluttes med helt at slette funktionen fra objektet. Eventuelle efterfølgende kald til funktionen leder til en exception:

>>> hinnerupnet.initialiser is not a function

Et alternativ fremt for helt at slette en funktion er at modificere koden heri, for eksempel:

<html>
<body onload="hinnerupnet.initialiser()">
var hinnerupnet = {
  initialiser: function () {
    // udfør initialiseringen her ...
    this.initialiser = function () {
      alert("Fejl: 'hinnerupnet' objektet er allerede initialiseret!");
    };
  }
};
</body>
</html>

Første kald til “hinnerupnet.initialiser()” vil udføre den tiltænkte initialiseringskode, og slutteligt overskrive initialiseringsfunktionen således “hinnerupnet” objektet efterfølgende er:

var hinnerupnet = {
  initialiser: function () {
    alert("Fejl: 'hinnerupnet' objektet er allerede initialiseret!");
  }
};

Leg selv videre med mulighederne, de er mange!

Multiple redigerbare overlays til Google Maps API

Jeg har dags dato lagt sidste hånd på et lidt spændende proof-of-concept eksperiment. Vi skal til en opgave kunne vise valgfrie polygoner over et zoombart verdenskort. Disse opsatte og redigerbare kort-koordinat polygoner (længde og breddegrader) skal derefter nemt kunne anvendes som søgekriterier i en MS SQL database indeholdende en datatabel med blandt andet en GPS koordinat kolonne.

Google Maps virkede som et fornuftigt udgangspunkt hertil, så der gik jeg igang.

Den indledende øvelse, du kan se resultatet af herunder, gik på at lave en minimalistisk webside hvor en bruger nemt kan opsætte en eller flere regioner og her skal være istand til at redigere og slette disse. Polygonerne skal slutteligt kunne “oversættes” til en række kort-koordinater til den videre database behandling (der ligger udenfor proof-of-concept eksemplets omfang).

Multiple redigerbare overlays til Google Maps...

Prøv selv dette eksempel med multiple editerbare overlays.

Hvert polygon brugeren definerer i ovenstående eksempel kan udtrækkes på JSON form, indeholdende alle koordinater som længde- og breddegrader, som for eksempel:

{
  'points': [
    { 'lat': 55.70685277146149, 'lng': 12.535314559936523 },
    { 'lat': 55.70685277146149, 'lng': 12.538447380065918 },
    { 'lat': 55.70571631025774, 'lng': 12.540678977966308 },
    { 'lat': 55.705184338337496, 'lng': 12.538447380065918 },
    { 'lat': 55.70426546069018, 'lng': 12.53763198852539 },
    { 'lat': 55.705184338337496, 'lng': 12.535314559936523 },
    { 'lat': 55.70685277146149, 'lng': 12.535314559936523 }
]};

Disse koordinat data kan nu anvendes server-side som søgekriterier i MS SQL 2005, for eksempel ved brug af MsSqlSpatial udvidelsen. Eksempler på MS SQL GIS data indsættelse og forespørgsler findes på MsSqlSpatial siden. MS SQL 2008 har indbygget understøttelse af GIS datatyper og søgning heri – Geometry (planar) og Geography (geodetic).

Klasser og klassenedarvning i JavaScript

KlassediagramJeg havde i dag behov for at lære fra mig med hensyn til prototype’s JavaScript klasse implementation. Der er nogle udemærkede eksempler at finde men de var ikke optimalt nemme at forklare alle begreberne udfra – man skulle have lidt baggrundsviden med sig i hvert fald.

Det endte med at en mindre omskrivning af første eksempel på prototype hjemmesiden var alt der skulle til for at kunne formidle hvordan JavaScript klasser kan benyttes.

Her er det omskrevne eksempel, der blandt andet har flere instanser med og en lidt klarere constructor/super constructor illustration:

// Animal base class
var Animal = Class.create({
  initialize: function(type, name, sound) { // constructor
    this.type = type;
    this.name = name;
    this.sound = sound;
  },
  speak: function() {
    alert("The " + this.type + " named " + 
      this.name + " says " + this.sound);
  }
});

// Extended class (inherits from Animal)
var Dog = Class.create(Animal, {
  initialize: function($super, name) { // constructor
    $super("Dog", name, "Woof Woof!"); // call super class constructor
  },
  sit: function() { // extended function
    alert("The " + this.type + " named " + this.name + " is now sitting.");
  }
});

var duck = new Animal("Duck", "Daffy", "Quack!");
var dog1 = new Dog("Pluto");
var dog2 = new Dog("King");
duck.speak();
// --> alerts "The Duck named Daffy says Quack!"
dog1.speak();
// --> alerts "The Dog named Pluto says Woof Woof!"
dog2.sit();
// --> alerts "The Dog named King is now sitting."
dog2.speak();
// --> alerts "The Dog named King says Woof Woof!"