How do I use the "this./lang.hitch" correctly in Custom WAB DE widget?

1412
4
Jump to solution
01-29-2020 03:52 PM
Arne_Gelfert
Occasional Contributor III

Okay, I'm venturing into somewhat new terrain here... I've read countless examples providing various levels of clarity on the subject but I'm banging my head against the wall trying to make my example work.

After working through a few easy examples of custom widget for Web AppBuilder and successfully interacting with map and layers, etc. I wanted to try and bring in some of the JSAPI modules.

Objective: hook a dom input field up to event listener/handler that executes a query against a map service. After a few circuitous failed attempts, I settled for trying to adapt this ESRI example: 

https://developers.arcgis.com/javascript/3/jshelp/intro_querytask.html.

In my imports I have the following:

define(['dojo/_base/declare', 
'jimu/BaseWidget',
'dijit/_WidgetsInTemplateMixin',     //The Mixin is like my MultiVitamin. 
                                     //Not sure I need it here but I've 
                                     //needed in the past and learned the hard way.

'esri/tasks/QueryTask',  //query and QueryTask (watch for upper/lower case!) 
'esri/tasks/query',      //are needed for the query.

"dojo/_base/lang",       //to hitch a lang

"dojo/on",               //"On" and "dom" I'm using for the event handling.
"dojo/dom"],

  function(declare, 
    BaseWidget,
    _WidgetsInTemplateMixin,
    QueryTask,
    Query,
    lang,
    on,
    dom)‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

My template code all follows the Custom Widget template, so I won't include here. All my custom code I packed in the onOpen lifecycle function, starting with:

onOpen: function(){
   this.inherited(arguments);  // Dont' know if I really need this - do I?
   console.log('onOpen');
           
   var searchBtn = dom.byId('searchBtn');
   on(searchBtn,"click",function(evt){
   lang.hitch(this,lang.hitch(this,executeQueryTask()));   // Just my last attempt...
   });‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

So this handles the input event. Next I set up my query:

var query = new Query();
query.returnGeometry = false;
query.outFields = ["NAME"];  // Any field will do right now
  
queryUrl = "https:// ... this is the URL to my service ends in ....MapServer/0";
var queryTask = new QueryTask({queryUrl});‍‍‍‍‍‍‍‍‍‍‍‍

Finally, simplifying the above ESRI example. I set up three (3) functions:

  1. Reads the current input in the DOM field.
  2. Executes the query using the JS API.
  3. Displays the results passed from 2) and print record count to the console.
    function showResults(featureSet) {
       var resultFeaturesCount = featureSet.features.length;
       console.log(resultFeaturesCount);
       }
          
    function getInput(){
       return document.getElementById('searchField').value;  
       }
            
    function executeQueryTask() {
       query.where = `NAME like '%${getInput()}%'`
       queryTask.execute(query,showResults);
       }‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

So I think I understand what I'm trying to do. My widget looks great. My event handler works. But the error has been the same all along:

Uncaught TypeError: Cannot read property 'query' of undefined
    at Object.execute (init.js:2315)
    at Object.c.<computed> [as execute] (init.js:1148)
    at executeQueryTask (Widget.js?wab_dv=2.14:101)
    at HTMLInputElement.<anonymous> (Widget.js?wab_dv=2.14:67)

Everything I've read (most of it by our generous contributor Robert Scheitlin, GISP) leads me to believe this is all about scope. So what am I missing? The scope-hitch-lang still confuses me.

0 Kudos
1 Solution

Accepted Solutions
RobertScheitlin__GISP
MVP Emeritus

Arne,

   OK here is your onOpen function fixed:

onOpen: function(){
   this.inherited(arguments);  // You should leave this
   
   on(this.searchBtn, "click", lang.hitch(this, function(evt){
     this.executeQueryTask();
   }));
...‍‍‍‍‍‍‍‍

Next I would recommend how to access a dom element in your widget.html the proper 'widget way', using data-dojo-attach-point instead of "id".

//widget.html
<div>
  <input type="text" data-dojo-attach-point="searchField" />
  <button type="button" data-dojo-attach-point="searchBtn">Search</button>
</div>‍‍‍‍‍‍‍‍‍‍‍‍‍

Then in your code you access it by:

executeQueryTask: function() {
   this.query.where = "NAME like '" + this.searchField.value + "%'";
   this.queryTask.execute(query, lang.hitch(this, this.showResults));
},‍‍‍‍‍‍‍‍

showResults: function(featureSet) {
   var resultFeaturesCount = featureSet.features.length;
   console.log(resultFeaturesCount);
},‍‍‍‍‍‍‍‍‍

Notice I have moved your function out of whatever function you had them nested in changed them to the widgets "this" scope instead.

You should setup things like you query and queryTask inside the postCreate function.

      postCreate: function () {
        this.inherited(arguments);
        this.query = new Query();
        this.query.returnGeometry = false;
        this.query.outFields = ["NAME"];  // Any field will do right now
  
        this.queryUrl = "https:// ... this is the URL to my service ends in ....MapServer/0";
        this.queryTask = new QueryTask(this.queryUrl);
...

View solution in original post

4 Replies
RobertScheitlin__GISP
MVP Emeritus

Arne,

   OK here is your onOpen function fixed:

onOpen: function(){
   this.inherited(arguments);  // You should leave this
   
   on(this.searchBtn, "click", lang.hitch(this, function(evt){
     this.executeQueryTask();
   }));
...‍‍‍‍‍‍‍‍

Next I would recommend how to access a dom element in your widget.html the proper 'widget way', using data-dojo-attach-point instead of "id".

//widget.html
<div>
  <input type="text" data-dojo-attach-point="searchField" />
  <button type="button" data-dojo-attach-point="searchBtn">Search</button>
</div>‍‍‍‍‍‍‍‍‍‍‍‍‍

Then in your code you access it by:

executeQueryTask: function() {
   this.query.where = "NAME like '" + this.searchField.value + "%'";
   this.queryTask.execute(query, lang.hitch(this, this.showResults));
},‍‍‍‍‍‍‍‍

showResults: function(featureSet) {
   var resultFeaturesCount = featureSet.features.length;
   console.log(resultFeaturesCount);
},‍‍‍‍‍‍‍‍‍

Notice I have moved your function out of whatever function you had them nested in changed them to the widgets "this" scope instead.

You should setup things like you query and queryTask inside the postCreate function.

      postCreate: function () {
        this.inherited(arguments);
        this.query = new Query();
        this.query.returnGeometry = false;
        this.query.outFields = ["NAME"];  // Any field will do right now
  
        this.queryUrl = "https:// ... this is the URL to my service ends in ....MapServer/0";
        this.queryTask = new QueryTask(this.queryUrl);
...
Arne_Gelfert
Occasional Contributor III

Thank you, WAB Jedi Master! That was great. A few comments...

  • Thanks for reminding me to use the "dojo-attach-point" instead of "id". You said the same in your last reply on another thread, and I didn't heed the advice. But I guess - judging from my study of gazillions of threads here - that's the story of your life - haha - repetition. I'll try to remember going forward. Also, now I don't need the dom.ById reference any more. 
  • Moved my execute(0 and showresults) into the widget's scope.
  • Moved the query set up into postCreate().
  • Only thing left in onOpen() is the event listener/handler.
  • Only thing I had to tweak from your code, based on my above changes was in the execute() function:

Yours:

this.queryTask.execute(query, lang.hitch(this, this.showResults));

Mine:

this.queryTask.execute(this.query, this.showResults);

Thanks for helping me along wrapping my brain around some of this scoping stuff. I might create another thread asking for feedback on the best primers on some of these concepts. I've read a handful. But there may be betters ones. Again, I think you should write a book!

0 Kudos
RobertScheitlin__GISP
MVP Emeritus

Arne,

  OK. I see I forgot the this. in front of query. You will likely want to add back the lang.hitch portion though if you will access any of the widgets scope inside your showResults function. Other wise you will not be able to access things like this.map, etc, etc. 

0 Kudos
Arne_Gelfert
Occasional Contributor III

Yes, yes, yes ... so right. I thought I didn't need it, so I took it out. But...

Next, I couldn't explain why I can reference and udpate a "data-dojo-attach-point" from within the onOpen() method but was aggravatingly unable to do so from within the showResults() method. Well, because it wasn't getting passed the reference to the widget scope.

I think I'm beginning to understand this.And all the sudden the door opens to all those examples out there you've been saying we should study to understand the custom widget world!

Thank you!