4.x: hitTest firing both callback and errback

1042
3
Jump to solution
04-13-2017 01:59 PM
EllaHaines
New Contributor III

I'm trying to use the HitTest() method to have one kind of popup whenever the user clicks on a feature, and another kind of popup when they click anywhere else on the map. However, the HitTest method seems to be firing both the callback AND the errback methods when I click on a feature, so it is showing the non-feature popup every time I click on a feature. How do I get it to do something only when the hit test fails?

I tested something similar in js bin, and it's doing the same thing when I use the popup function I'm trying to use, but not when I just had it print to the console for the errback instead. So maybe the problem is my AddPointGraphic function somehow. Am I using this wrongly somehow?

https://jsbin.com/jakumihuwa/1/edit?html,output

Just the view: https://output.jsbin.com/jakumihuwa/

//Why is it firing both the callback and errback?
var screenPoint = {
   x: event.x,
   y: event.y
}
view.hitTest(screenPoint).then(
   //callback
   function (response) {
      alert("hit")
}).otherwise(AddPointGraphic(event));

function AddPointGraphic(event) {
   //clear previous graphics
   view.graphics.removeAll();

   //create new graphic
   //console.log(event.mapPoint);
   var pointGeometry = event.mapPoint;

   var commentPointAtt = {
      long: event.mapPoint.longitude,
      lat: event.mapPoint.latitude
   }

   var commentPoint = new Graphic({
      geometry: pointGeometry,
      symbol: symbol,
      attributes: commentPointAtt,
      popupTemplate: commentPopupTemplate
   });
   view.graphics.add(commentPoint);
   view.popup.open({
      features: [commentPoint],
      location: event.mapPoint
   });
}

I tried using it in the hitTest(screenPoint).then(callback, errback) errback spot, but it did the same thing. I am new to 4.x and would appreciate any help.
Tags (1)
0 Kudos
1 Solution

Accepted Solutions
ThomasSolow
Occasional Contributor III

The reason the .otherwise is always running is because you're invoking the function that you're passing into it:

view.hitTest(screenPoint).then(
   //callback
   function (response) {
      alert("hit")
}).otherwise(AddPointGraphic(event))

// ^ AddPointGraphic is being invoked because it 
// is followed by ().  

.otherwise is like .then, you should pass in a function as an argument, but you shouldn't invoke the function.  It's also important to note that .otherwise will get passed the error, not the event.

To go a little further, .otherwise (.catch for native JS promises) is triggered in the event of an error or if the promise that it's attached to is rejected.  In this case, you don't want to do anything if hitTest throws an error.  HitTest throwing an error doesn't mean "I didn't find any graphics," it means "Something broke."

As in Robert's example, I think the correct way to handle this functionality is to handle all of this logic in the .then callback.  You can check to see if any graphics were found in the .then callback, and decide what to do based on that.

View solution in original post

3 Replies
RobertScheitlin__GISP
MVP Emeritus

Ella,

   I am not sure on the otherwise issue but here is a way that works fine:

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no">
  <meta name="description" content="[Access features with click events - 4.3]">
  <!--
  ArcGIS API for JavaScript, https://js.arcgis.com
  For more information about the view-hittest sample, read the original sample description at developers.arcgis.com.
  https://developers.arcgis.com/javascript/latest/view-hittest/index.html
  -->
<title>Access features with click events - 4.3</title>

  <style>
    html,
    body,
    #viewDiv {
      padding: 0;
      margin: 0;
      height: 100%;
      width: 100%;
    }
    
    #info {
      background-color: black;
      opacity: 0.75;
      color: orange;
      font-size: 18pt;
      padding: 8px;
      visibility: hidden;
    }
  </style>

  <link rel="stylesheet" href="https://js.arcgis.com/4.3/esri/css/main.css">
  <script src="https://js.arcgis.com/4.3/"></script>

  <script>
    require([
      "esri/Map",
      "esri/views/MapView",
      "esri/layers/FeatureLayer",
      "esri/renderers/UniqueValueRenderer",
      "esri/symbols/SimpleLineSymbol",
      "esri/widgets/Popup",
       "esri/PopupTemplate",
       "esri/geometry/Point",
  "esri/symbols/SimpleMarkerSymbol",
  "esri/Graphic",
      "dojo/dom",
      "dojo/domReady!"
    ], function(
      Map,
      MapView,
      FeatureLayer,
      UniqueValueRenderer,
      SimpleLineSymbol,
      Popup,
      PopupTemplate,
      Point,
      SimpleMarkerSymbol,
      Graphic,
      dom
    ) {

      var layer = new FeatureLayer({
        url: "https://gis05s.hdrgateway.com/arcgis/rest/services/Arizona/I11_SegmentRecommendations/MapServer/0",
        // sublayers: [{
        //     id: 0,
        //     //popupTemplate: segmentPopupTemplate
        // }]
    })
    
    //--set up popups
    var commentPopupTemplate = new PopupTemplate({
        content: "<button type='button' id='mapCommentBtn' class='btn btn-info' data-long='{long}' data-lat='{lat}'>Place Comment Here</button>"
    });

    var segmentPopupTemplate = new PopupTemplate({
        content: "<button type='button' id='segmentCommentBtn' class='btn btn-info' data-id={Segment_ID}>Comment on Segment</button>"
    });
    
    //--set up graphic symbol
    var symbol = new SimpleMarkerSymbol({
        style: "round",
        color: "red",
        size: "12px"
    });

      var map = new Map({
        basemap: "dark-gray",
        layers: [layer]
      });

      var view = new MapView({
        container: "viewDiv",
        map: map,
        center: [-111.77215576168958, 32.872667054353876],
        zoom: 10
      });

      view.ui.add("info", "top-right");

      // Set up a click event handler and retrieve the screen x, y coordinates 
      view.on("click", function(evt) {
        var screenPoint = {
          x: evt.x,
          y: evt.y
        };

        // the hitTest() checks to see if any graphics in the view
        // intersect the given screen x, y coordinates
        view.hitTest(screenPoint)
          .then(getGraphics)
          //.otherwise(AddPointGraphic(evt));
      });
      
      function AddPointGraphic(event) {
        //clear previous graphics and popups
        view.graphics.removeAll();

        //create new graphic
        var pointGeometry = view.toMap(event);
        
        var commentPointAtt = {
            long: pointGeometry.longitude,
            lat: pointGeometry.latitude
        }

        var commentPoint = new Graphic({
            geometry: pointGeometry,
            symbol: symbol,
            attributes: commentPointAtt,
            popupTemplate: commentPopupTemplate
        });
        view.graphics.add(commentPoint);
        view.popup.open({
            features: [commentPoint],
            location: pointGeometry
        });
    }

      function getGraphics(response) {
        if(response.results.length == 0){
          console.info(response);
          AddPointGraphic(response.screenPoint);
        }else{
          alert("hitTest");
        }
      }

      view.then(function() {
        layer.then(function() {
          // update the default renderer's
          // symbol when the layer loads
          var renderer = layer.renderer.clone();
          renderer.symbol.width = 4;
          renderer.symbol.color = [128, 128, 128, 0.8];
          renderer.symbol.cap = "round";
          layer.renderer = renderer;
        });
      });
    });
  </script>
</head>

<body>
  <div id="viewDiv"></div>
  <div id="info">
    <span id="name"></span><br>
    <span id="category"></span><br>
    <span id="wind"></span>
  </div>
</body>

</html>
ThomasSolow
Occasional Contributor III

The reason the .otherwise is always running is because you're invoking the function that you're passing into it:

view.hitTest(screenPoint).then(
   //callback
   function (response) {
      alert("hit")
}).otherwise(AddPointGraphic(event))

// ^ AddPointGraphic is being invoked because it 
// is followed by ().  

.otherwise is like .then, you should pass in a function as an argument, but you shouldn't invoke the function.  It's also important to note that .otherwise will get passed the error, not the event.

To go a little further, .otherwise (.catch for native JS promises) is triggered in the event of an error or if the promise that it's attached to is rejected.  In this case, you don't want to do anything if hitTest throws an error.  HitTest throwing an error doesn't mean "I didn't find any graphics," it means "Something broke."

As in Robert's example, I think the correct way to handle this functionality is to handle all of this logic in the .then callback.  You can check to see if any graphics were found in the .then callback, and decide what to do based on that.

EllaHaines
New Contributor III

Thanks Robert and Thomas, I wish I could mark both answers as correct!

I thought I'd summarize what worked for me, based on your answers.

//don't invoke the callback with parens/trying to pass extra arguments
//don't need the errback in this case
view.hitTest(screenPoint).then(callback);

function callback(response) {
     if(response.results.length == 0){
       //action for not hitting anything
     }else{
       //action for hitting something
     }
 }
0 Kudos