In c#, come sappiamo, il this fa riferimento all'istanza della classe corrente e quindi se dovessimo invocare un metodo di un oggetto, ci aspetteremmo che il this sia di quell'oggetto.
In Javascript non è esattamente così e per ottenere lo stesso risultato occorre capire il contesto di esecuzione.
Quando una funzione in Javascript è invocata, viene creato un contesto di esecuzione.
La proprietà this è un riferimento all'oggetto che è considerato il contesto (o scope) dell'invocazione della funzione.
Vediamo un esempio:
var point = {
x : 100,
y : 200,
toString : function () {
console.debug("coordX:" + this.x + " coordY:" + this.y);
}
}
point.toString();
Il risultato che si ottiene è
Il this viene risolto nel contesto dell'oggetto dal quale è chiamato.
Proseguiamo nell'esempio, creando dinamicamente dei div ed 'agganciando' sull'evento click il metodo point.toString :
var point = {
x:100,y:200,
toString:function () {
console.debug("coordX:" + this.x + " coordY:" + this.y);
}
}
var container = dojo.byId("nodeContainer");
for (var i = 0; i < 5; i++) {dojo.create("div", {
id:"node-" + i,
className:"myNode",
innerHTML:"Fake button " + (i + 1)
}, container);
}
dojo.query(".myNode").forEach(function (node) {
node.onclick = point.toString;});
Se dovessimo cliccare su ogni div con classe myNode, dovremmo aspettarci il risultato visto precedentemente. Invece si ottiene:
Questo perchè point.toString è eseguito nel contesto del nodo nel quale è 'agganciato' e non nel contesto dell'oggetto, di conseguenza x e y non sono definiti in questo contesto.
Infatti, se dovessimo modificare il codice di cui sopra nel seguente modo:
dojo.query(".myNode").forEach(function (node) {
node.x = 300;
node.y =400;
node.onclick = point.toString;
});
Otterremmo:
poichè nel contesto corrente (node) sono ora presenti x e y.
Il linguaggio Javascript consente di cambiare il contesto al volo attraverso i metodi Function.apply e Function.call.
Dojo rende disponibile, in maniera semplice, un modo per 'bindare' ad uno specifico contesto: dojo.hitch.
In sintesi, dojo.hitch crea un nuovo oggetto funzione associato ad uno specifico contesto, oggetto che potrà essere richiamato senza preoccuparsi se il contesto è cambiato.
Vediamo un esempio:
var x = 100;
var myFunction = function() {
console.debug(this.x);
}
var myObject = { x: 200};
var boundFunction = dojo.hitch(myObject, myFunction);
myFunction();
boundFunction();
myFunction();
Come si può notare, la prima chiamata ha definito a livello di contesto x=100, mentre nella seconda cambia il contesto di esecuzione e risulta x =200.
Se richiamiamo myFunction, il suo contesto non cambia e ristampa x=100, non è quindi influenzato dal cambio di contesto dojo.hitch effettuato sulla funzione stessa al passo precedente.
Qui potete vedere un esempio utilizzato nelle api javascript ESRI
Il dojo.hitch è pertanto utile consentendo di impostare un contesto di esecuzione di una funziona senza timore che cambi il significato della parola chiave this.
Passiamo ora a dojo.partial.
Quando una funzione è definita, la sua firma diventa fissa e quindin non possiamo aggiungere o togliere argomenti senza ridefinire la funzione stessa.
Questo potrebbe essere un problema se dovessimo agganciarci ad una firma di una funzione senza copiare o riscrivere la funzione originale.
Vediamo un esempio che restituisce i valori univoci per ogni campo:
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"
"http://www.w3.org/TR/html4/strict.dtd">
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<title>requestfieldvalues</title>
<script type="text/javascript" src="http://serverapi.arcgisonline.com/jsapi/arcgis/?v=2.8"></script>
<script type="text/javascript" charset="utf-8">
dojo.require("esri.utils");
dojo.require("esri.tasks.query");
var queryTask, query;
function init() {
queryTask = new esri.tasks.QueryTask("http://sampleserver1.arcgisonline.com/ArcGIS/rest/services/Specialty/ESRI_StatesCitiesRivers_USA/MapServer/2");
query = new esri.tasks.Query();
query.where = "1=1";
getFieldList();
}
dojo.addOnLoad(init);
function getFieldList() {
esri.request({
url: "http://sampleserver1.arcgisonline.com/ArcGIS/rest/services/Specialty/ESRI_StatesCitiesRivers_USA/MapServer/0",
content: { f: "json" },
handleAs: "json",
callbackParamName: "callback",
load: function (response, io) {
var fields = response.fields;
var options = [];
dojo.forEach(fields, function (field) {
options.push("<option value=\"" + field.name + "\">" + field.alias + "</option>");
});
dojo.byId("fields").innerHTML = options.join("");
},
error: function (error) {
alert(error.message);
}
});
}
function getFieldValues(field) {
query.outFields = [field];
queryTask.execute(query, dojo.partial(displayValues, field));
}
function displayValues(field, featureSet) {
var options = [], values = [], value;
dojo.forEach(featureSet.features, function (feature) {
value = feature.attributes[field];
if (dojo.indexOf(values, value) == -1) {
values.push(value);
options.push("<option value=\"" + value + "\">" + value + "</option>");
}
});
dojo.byId("values").innerHTML = options.join("");
}
</script>
</head>
<body>
<select id="fields" onchange="getFieldValues(this.value);"></select>
<select id="values"></select>
</body>
</html>
In questo caso queryTask.execute prevede il parametro query e la funzione di callback che ha come firma la stessa dell'evento onComplete(featureSet).
Nella funzione getFieldValues possiamo 'precaricare' il field selezionato con dojo.partial nella funzione displayValues, cosicchè la funzione stessa potrà essere utilizzata come funzione di callback perchè la firma corrisponde a quella di callback del queryTask.
Infine, se volessimo 'fondere' le caratteristiche di dojo.hitch ('bindare' il contesto) con quelle di dojo.partial (preimpostare gli argomenti), potremmo utilizzare sempre dojo.hitch.
In questo caso dojo.hitch assembla una nuova funzione con associati il contesto e gli argomenti precaricati.
Alla luce di quanto sopra, l'esempio iniziale risulta modificato come segue:
var point = {
x:100,
y:200,
toString:function (z) {
console.debug("coordX:" + this.x + " coordY:" + this.y + " coordZ:" + z);
}
}
var container = dojo.byId("nodeContainer");
for (var i = 0; i < 5; i++) {
dojo.create("div", {
id:"node-" + i,
className:"myNode",
innerHTML:"Fake button " + (i + 1)
}, container);
}
dojo.query(".myNode").forEach(function (node) {
node.onclick = dojo.hitch(point, point.toString, 300);
});
Ottenendo il seguente risultato: