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.
Solved! Go to Solution.
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.
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>
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.
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 } }