0

I know this issue has been done to death, but I still don't understand it. I've read StackOverFlow's very own "this" keyword explanation (along with the Mike West article within it) as well as other scope related questions. I don't know why I don't get it, so I look to the more savvy JavaScript developer for help.

From the code and comments it should be fairly self explanatory. But it is querying an ESRI map service, returning graphics and placing these on the map.

But the issue is in the showResults function where I call this.graphics, which is the map's graphics property (a graphics layer). I'm aware it's now out of scope (which took more time to figure out than I'd like to admit), but how can I put it back in scope so I can use it? Even if I have alter the code substantially...

define([
"dojo/_base/declare",
"dojo/on",
"dijit/_WidgetBase",
"dijit/_TemplatedMixin",
"dijit/_WidgetsInTemplateMixin",
"dijit/form/Button",
"dijit/form/Form",
"dijit/form/FilteringSelect",
"dijit/form/ValidationTextBox",
"dojo/_base/array",
"dojo/_base/Color",
"dojo/_base/lang",
"esri/tasks/find",
"dojo/text!./Find/templates/Find.html"
], function(declare, on, _WidgetBase, _TemplatedMixin, _WidgetsInTemplateMixin, Button, Form, FilteringSelect, ValidationTextBox, array, Color, lang, find, FindTemplate) {

//anonymous function to load CSS files required for this module
(function() {
    var css = [require.toUrl("gis/dijit/Find/css/Find.css")];
    var head = document.getElementsByTagName("head").item(0),
        link;
    for(var i = 0, il = css.length; i < il; i++) {
        link = document.createElement("link");
        link.type = "text/css";
        link.rel = "stylesheet";
        link.href = css[i].toString();
        head.appendChild(link);
    }
}());

// Query Dijit
return declare([_WidgetBase, _TemplatedMixin, _WidgetsInTemplateMixin], {
    widgetsInTemplate: true,
    templateString: FindTemplate,
    graphics: null,
    findTask: null,
    findParams: null,
    results: [],
    serverError: null,
    queryLayer: null,
    searchText: null,
    postCreate: function() {
        // Method is used to call a superclass method. It's good practice to assume that you are overriding a method that may 
        // do something important in a class up the inheritance chain
        this.inherited(arguments);

        // Create graphics layer and add it to the map
        this.graphics = new esri.layers.GraphicsLayer({id:"queryGraphics"});
        this.map.addLayer(this.graphics);

        // Create find task with url to map service
        this.findTask = new esri.tasks.FindTask("arcgis/rest/services/MapServer");

        // Create find parameters and define known values
        this.findParams = new esri.tasks.FindParameters();     
        this.findParams.outSpatialReference = this.map.spatialReference;
        this.findParams.returnGeometry = true;
        this.findParams.layerIds = [1];
        this.findParams.searchFields = ["OBJECTID", "Gauge ID", "FV_ID", "FDC_ID", "Flood_Stage", "Flood_Line", "Municipality", "WATERSHED"]; 

        // Listen for Submit button click
        on(this.submitButton, 'click', lang.hitch(this, 'execute'));
    },
    // Submit button click event
    execute: function execute() {   
        // Set the WHERE search text
        this.findParams.searchText = dojo.byId("searchText").value;
        // Sends a request to the ArcGIS REST map service resource to perform a search based 
        // on the FindParameters specified in the findParameters argument. On completion, the 
        // onComplete event is fired and the optional callback function is invoked.
        this.findTask.execute(this.findParams, this.showResults, this.showError); 
    },
    // Fires when the find operation is complete and returns an array of FindResult
    showResults: function showResults(results) {
        this.graphics.clear();
        // Build an array of attribute information and add each found graphic to the map
        dojo.forEach(results, function(result) {
            var symbol;
            switch(result.feature.geometry.type) {
                case "point":
                    symbol = new esri.symbol.SimpleMarkerSymbol(esri.symbol.SimpleMarkerSymbol.STYLE_CIRCLE, 10, new esri.symbol.SimpleLineSymbol(esri.symbol.SimpleLineSymbol.STYLE_SOLID, new Color([255, 0, 0]), 1), new Color([255, 0, 0, 1.0]));
                    break;
                case "polyline":
                    symbol = new esri.symbol.SimpleLineSymbol(esri.symbol.SimpleLineSymbol.STYLE_DASH, new Color([255, 0, 0]), 1);
                    break;
                case "polygon":
                    symbol = new esri.symbol.SimpleFillSymbol(esri.symbol.SimpleFillSymbol.STYLE_SOLID, new esri.symbol.SimpleLineSymbol(esri.symbol.SimpleLineSymbol.STYLE_DASHDOT, new Color([255, 0, 0]), 2), new Color([255, 255, 0, 0.0]));
                    break;
                default:
                    symbol = new esri.symbol.SimpleMarkerSymbol(esri.symbol.SimpleMarkerSymbol.STYLE_CIRCLE, 10, new esri.symbol.SimpleLineSymbol(esri.symbol.SimpleLineSymbol.STYLE_SOLID, new Color([255, 0, 0]), 1), new Color([255, 0, 0, 1.0]));
            }
            var graphic = new esri.Graphic(result.feature.geometry, symbol);
            this.graphics.add(graphic);
        });
    },
    // Fires if the find execution fails to complete
    showError: function showError(serverError) {
        alert("The server encountered an error. Error: " + serverError);
    }
});
});

UPDATE AFTER ANSWERS:

So, it wasn't one answer that solved this but a combination of two. I incorporated Buffalo's answer of

this.findTask.execute(this.findParams, lang.hitch(this, this.showResults), this.showError);

and Buffalo's comment to Craig's answer of

dojo.forEach(results, function(result) {  
...
}, this);

but Craig's would have worked here as well. This combined with Tamil's answer I'm most certain would have worked too.

So I'll mark Buffalo's as the answer, but all three are worth looking at for any further readers.

Community
  • 1
  • 1

3 Answers3

1

You could use Function.bind(Beware! its not supported in all the browsers).

Change:

this.findTask.execute(this.findParams, this.showResults, this.showError); 

To:

this.findTask.execute(this.findParams, this.showResults.bind(this), this.showError); 

Or(If you are concerned about Function.bind browser support):

Use closure & Function.apply

execute: function execute() {  
...
...
  var self = this;
  this.findTask.execute(this.findParams, function() {
    self.showResults.apply(self, arguments);
  }, this.showError); 
}
1

The hitch method in dojo/_base/lang is going to be your best friend here. What I assume the findTask function does is exceucte some asynchronous request and then invokes the 2nd param with the results.

First, add dojo/_base/lang to your Dependency list.

Then, change the line

this.findTask.execute(this.findParams, this.showResults, this.showError); 

to

this.findTask.execute(this.findParams, lang.hitch(this,this.showResults), this.showError); 

What lang#hitch does is take the first parameter (in this case the instance of your widget), and make it be the scope when the second parameter executes. In this case we want showResults to execute in the current scope.

BuffaloBuffalo
  • 7,371
  • 4
  • 25
  • 27
  • You're correct about findtask and your answer along with Craig's (and subsequently your comment) solved my issue. Much appreciated, to all answers. Thanks. – Andrew Thiele Jul 04 '13 at 16:24
1

Your problem is with the following code

    dojo.forEach(results, function(result) {

        ...

        this.graphics.add(graphic);
    });

the function being executed does not have the scope of the widget. Make it:

    dojo.forEach(results, lang.hitch(this, function(result) {

    }));
Craig Swing
  • 8,112
  • 2
  • 28
  • 43
  • 2
    `dojo.forEach` also has a third, optional parameter which is the scope to execute the function in: `dojo.forEach(results,function(result){},myScope);` – BuffaloBuffalo Jul 03 '13 at 20:34