view.hittest throwing error when triggered by view's pointer-move event

3524
4
Jump to solution
04-06-2017 07:52 AM
BrandonFlessner
Occasional Contributor

I'm trying to change the cursor to a pointer in my application when features are hovered over. But I'm getting "TypeError: Cannot read property 'position' of undefined" when I mouseover the map before features load. Here's a sample I modified: JS Bin - Collaborative JavaScript Debugging 

If the mouse is moving over the map when the page is being loaded, the error is thrown. If not then it works fine. What's the best way to deal with this? Or is there a better way of changing the cursor to pointer when hovering over features?

0 Kudos
1 Solution

Accepted Solutions
RobertScheitlin__GISP
MVP Emeritus

Brandon,

   Ok, here is what I got to work without exception then:

<!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/core/watchUtils",
      "esri/Map",
      "esri/views/MapView",
      "esri/layers/FeatureLayer",
      "esri/renderers/UniqueValueRenderer",
      "esri/symbols/SimpleLineSymbol",
      "dojo/dom",
      "dojo/domReady!"
    ], function(
      watchUtils,
      Map,
      MapView,
      FeatureLayer,
      UniqueValueRenderer,
      SimpleLineSymbol,
      dom
    ) {

      var layer = new FeatureLayer({
        url: "https://sampleserver6.arcgisonline.com/arcgis/rest/services/Hurricanes/MapServer/1",
        outFields: ["*"]
      });

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

      var view = new MapView({
        container: "viewDiv",
        map: map,
        center: [-61.125537, 35.863534],
        zoom: 4
      });

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

      
      function changeCursor(response){
        if (response.results.length > 0){
          document.getElementById("viewDiv").style.cursor = "pointer";
        } else {
          document.getElementById("viewDiv").style.cursor = "default";
        }
      }

      function getGraphics(response) {
        // the topmost graphic from the click location
        // and display select attribute values from the
        // graphic to the user
        var graphic = response.results[0].graphic;
        var attributes = graphic.attributes;
        var category = attributes.CAT;
        var wind = attributes.WIND_KTS;
        var name = attributes.NAME;

        dom.byId("info").style.visibility = "visible";
        dom.byId("name").innerHTML = name;
        dom.byId("category").innerHTML = "Category " + category;
        dom.byId("wind").innerHTML = wind + " kts";

        // symbolize all line segments with the given
        // storm name with the same symbol
        var renderer = new UniqueValueRenderer({
          field: "NAME",
          defaultSymbol: layer.renderer.symbol || layer.renderer.defaultSymbol,
          uniqueValueInfos: [{
            value: name,
            symbol: new SimpleLineSymbol({
              color: "orange",
              width: 5,
              cap: "round"
            })
          }]
        });
        layer.renderer = renderer;
      }

      view.then(function() {
        view.whenLayerView(layer).then(function(lview) {
          watchUtils.whenFalseOnce(lview, "updating", function(){
            // Set up a click event handler and retrieve the screen x, y coordinates 
            view.on("pointer-move", 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( function(response){
                    changeCursor(response);
                    getGraphics(response);
                  });      
            });
          });
        });
        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>

View solution in original post

4 Replies
RobertScheitlin__GISP
MVP Emeritus

Brandon,

   You wait to add the event listener until the layer is loaded then:

<!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",
      "dojo/dom",
      "dojo/domReady!"
    ], function(
      Map,
      MapView,
      FeatureLayer,
      UniqueValueRenderer,
      SimpleLineSymbol,
      dom
    ) {

      var layer = new FeatureLayer({
        url: "https://sampleserver6.arcgisonline.com/arcgis/rest/services/Hurricanes/MapServer/1",
        outFields: ["*"]
      });

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

      var view = new MapView({
        container: "viewDiv",
        map: map,
        center: [-61.125537, 35.863534],
        zoom: 4
      });

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

      
      
      function changeCursor(response){
        if (response.results.length > 0){
          document.getElementById("viewDiv").style.cursor = "pointer";
        } else {
          document.getElementById("viewDiv").style.cursor = "default";
        }
      }

      function getGraphics(response) {
        // the topmost graphic from the click location
        // and display select attribute values from the
        // graphic to the user
        var graphic = response.results[0].graphic;
        var attributes = graphic.attributes;
        var category = attributes.CAT;
        var wind = attributes.WIND_KTS;
        var name = attributes.NAME;

        dom.byId("info").style.visibility = "visible";
        dom.byId("name").innerHTML = name;
        dom.byId("category").innerHTML = "Category " + category;
        dom.byId("wind").innerHTML = wind + " kts";

        // symbolize all line segments with the given
        // storm name with the same symbol
        var renderer = new UniqueValueRenderer({
          field: "NAME",
          defaultSymbol: layer.renderer.symbol || layer.renderer.defaultSymbol,
          uniqueValueInfos: [{
            value: name,
            symbol: new SimpleLineSymbol({
              color: "orange",
              width: 5,
              cap: "round"
            })
          }]
        });
        layer.renderer = renderer;
      }

      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;
          
          // Set up a click event handler and retrieve the screen x, y coordinates 
          view.on("pointer-move", 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( function(response){
                  changeCursor(response);
                  getGraphics(response);
                });      
          });
        });
      });
    });
  </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>
0 Kudos
BrandonFlessner
Occasional Contributor

Thanks for your reply Robert. Unfortunately the error is still being thrown. I added in some console logging and recorded this gif. (Notice it's critical that the mouse is in motion over the map as its loading)

view.then(function() {
        console.log('view ready');
        layer.then(function() {
          console.log('layer ready');
          ...

hittest type error before features are loaded

Here's my current "solution":

view.on('pointer-move', function(evt){
  if (!view.updating && view.ready){ // if view is updating, an error is raised
    try {
      view.hitTest({x: evt.x, y: evt.y}).then(changeCursor);
    } catch (err) {
      console.log(err);
    }
  }
});‍‍‍‍‍‍‍‍‍

The if statement isn't really doing anything -- I was searching for the right properties to watch. The try and catch blocks keep the app responding to mouse events but it seems like there should be a better way.

RobertScheitlin__GISP
MVP Emeritus

Brandon,

   Ok, here is what I got to work without exception then:

<!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/core/watchUtils",
      "esri/Map",
      "esri/views/MapView",
      "esri/layers/FeatureLayer",
      "esri/renderers/UniqueValueRenderer",
      "esri/symbols/SimpleLineSymbol",
      "dojo/dom",
      "dojo/domReady!"
    ], function(
      watchUtils,
      Map,
      MapView,
      FeatureLayer,
      UniqueValueRenderer,
      SimpleLineSymbol,
      dom
    ) {

      var layer = new FeatureLayer({
        url: "https://sampleserver6.arcgisonline.com/arcgis/rest/services/Hurricanes/MapServer/1",
        outFields: ["*"]
      });

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

      var view = new MapView({
        container: "viewDiv",
        map: map,
        center: [-61.125537, 35.863534],
        zoom: 4
      });

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

      
      function changeCursor(response){
        if (response.results.length > 0){
          document.getElementById("viewDiv").style.cursor = "pointer";
        } else {
          document.getElementById("viewDiv").style.cursor = "default";
        }
      }

      function getGraphics(response) {
        // the topmost graphic from the click location
        // and display select attribute values from the
        // graphic to the user
        var graphic = response.results[0].graphic;
        var attributes = graphic.attributes;
        var category = attributes.CAT;
        var wind = attributes.WIND_KTS;
        var name = attributes.NAME;

        dom.byId("info").style.visibility = "visible";
        dom.byId("name").innerHTML = name;
        dom.byId("category").innerHTML = "Category " + category;
        dom.byId("wind").innerHTML = wind + " kts";

        // symbolize all line segments with the given
        // storm name with the same symbol
        var renderer = new UniqueValueRenderer({
          field: "NAME",
          defaultSymbol: layer.renderer.symbol || layer.renderer.defaultSymbol,
          uniqueValueInfos: [{
            value: name,
            symbol: new SimpleLineSymbol({
              color: "orange",
              width: 5,
              cap: "round"
            })
          }]
        });
        layer.renderer = renderer;
      }

      view.then(function() {
        view.whenLayerView(layer).then(function(lview) {
          watchUtils.whenFalseOnce(lview, "updating", function(){
            // Set up a click event handler and retrieve the screen x, y coordinates 
            view.on("pointer-move", 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( function(response){
                    changeCursor(response);
                    getGraphics(response);
                  });      
            });
          });
        });
        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>
JackFairfield
Occasional Contributor II

Thank you so much for your question! I had the EXACT same issue when trying to do a hittest.  I ended up using your try catch solution, because your final solution didn't seem to work in my case.  I am updating the renderer after it loads, so even though updating is false once, it is actually still updating later and the event breaks if you try to move the mouse at the wrong time.  The try catch worked great.  Didn't realize there was a Try Catch in javascript.  I understand it isn't the best possible solution, but oh well.  Thanks again.

0 Kudos