I've been fairly successful using the dojox/mobile components. I used the example 'Find Nearby' a year or so ago, in one of it's earlier iterations: Find nearby locations | ArcGIS API for JavaScript At the time, the example was written with API version 2.7. Because it was based on a really old version of the API, as well as in legacy, I updated it, comparing the code I had with the updated example. I thought I had it all updated and working.
Something in these changes has caused this to stop working on an iPhone. I'm assuming there is something with either a meta tag, or CSS change or maybe both. The original example had a line item specifically for iPhone.css. I'm wondering if this is missing from the example or if it's combined in some other css and no long necessary. It looks like there is still a css available for it, under the newer directory structure as
<link type="text/css" href="https://js.arcgis.com/3.9/dojox/mobile/themes/iphone/iphone.css" rel='stylesheet'>
I added that entry into my code, but that didn't help.
I've also not sure what apple meta tags are necessary.
Since this was working until I started trying to update it, I'm hoping someone can review this and give me some idea of why this doesn't work in particular on an iPhone. I have had it tested on Android and a Windows phone and it works there. I've tried various emulation web sites, and they all work as well.
This link is public facing:
Warming Center Locations
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"/>
<title>Warming Center Locations</title>
<link type="text/css" rel="stylesheet" href="https://js.arcgis.com/3.9/js/dojo/dijit/themes/claro/claro.css">
<link type="text/css" href="https://js.arcgis.com/3.9/dojox/mobile/themes/iphone/iphone.css" rel='stylesheet'>
<link rel="stylesheet" href="https://js.arcgis.com/3.9/js/esri/css/esri.css">
<link rel="stylesheet" href="css/style.css">
<script type="text/javascript">
var dojoConfig = {
mblAlwaysHideAddressBar: true,
parseOnLoad: false
};
</script>
<script type="text/javascript" src="https://js.arcgis.com/3.9compact/"></script>
<script type="text/javascript">
// var pathName = "https://ogi.oa.mo.gov";
var pathName = "https://ogitest.oa.mo.gov";
var locator,geomService, map, spatialReference, resizeEvt, currentLocation, currentGraphic, startAddr;
var searchQuery,siteFeatureLayer, infoTemplate, siteInfoTemplate;
var startSymbol, siteRenderer;
var routeGraphicLayer,routePolylineSymbol, segmentGraphicsLayer;
var routeTask, routeParams, directionFeatures, itemList;
var currentBufferDist = 25;
var pointSource = "";
require(["dojox/mobile","dojox/mobile/parser","dojox/mobile/compat","dojox/mobile/deviceTheme",
"dojo/dom", "dijit/registry", "dojo/on", "dojo/query", "dojo/sniff", "dojo/keys",
"esri/map", "esri/SpatialReference", "esri/geometry/Extent", "esri/dijit/Popup","esri/InfoTemplate",
"esri/layers/FeatureLayer", "esri/layers/ArcGISDynamicMapServiceLayer", "esri/layers/GraphicsLayer",
"esri/tasks/locator","esri/tasks/GeometryService", "esri/tasks/query", "esri/tasks/QueryTask",
"esri/tasks/RouteTask", "esri/tasks/RouteParameters", "esri/tasks/FeatureSet","esri/units",
"esri/tasks/BufferParameters",
"esri/renderers/SimpleRenderer", "esri/symbols/SimpleLineSymbol", "esri/symbols/SimpleFillSymbol",
"esri/symbols/SimpleMarkerSymbol","esri/symbols/PictureMarkerSymbol", "esri/symbols/TextSymbol",
"esri/symbols/Font","esri/geometry/Point","esri/geometry/webMercatorUtils","esri/geometry/mathUtils",
"esri/graphic", "dojo/_base/Color", "dojo/_base/array", "dojox/mobile/ListItem","dojox/mobile/Button",
"dojox/mobile/RoundRectList","dojox/mobile/View","dojox/mobile/ToolBarButton","dojox/mobile/Heading",
"dojox/mobile/RoundRect","dojox/mobile/TextBox","dojox/mobile/ContentPane",
"dojo/domReady!"],
function(mobile, parser, compat,dTheme, dom, registry, on, query, has,keys,
Map, SpatialReference, Extent,Popup, InfoTemplate,
FeatureLayer, ArcGISDynamicMapServiceLayer, GraphicsLayer,
Locator, GeometryService, Query, QueryTask,
RouteTask,RouteParameters,FeatureSet,esriUnits,BufferParameters,
SimpleRenderer, SimpleLineSymbol, SimpleFillSymbol,SimpleMarkerSymbol, PictureMarkerSymbol,
TextSymbol, Font, Point, webMercatorUtils, mathUtils, Graphic, Color, arrayUtils, ListItem, Button, RoundRectList){
parser.parse();
routeTask = new RouteTask(pathName+"/arcgis/rest/services/BaseMap/Route/NAServer/Route");
geomService = new GeometryService(pathName+"/arcgis/rest/services/Utilities/Geometry/GeometryServer");
locator = new Locator("https://geocode.arcgis.com/arcgis/rest/services/World/GeocodeServer");
locator.on("address-to-locations-complete", showGeocodeResults);
resizeEvt = (window.onorientationchange !== undefined && !has('android')) ? "orientationchange" : "resize";
on(window, resizeEvt, resizeMap);
var spatialReference = new SpatialReference({ wkid : 102100});
var startExtent = new Extent(-10723197, 4186914, -9829190, 4992866, spatialReference);
var map = new Map("mapDiv", {
extent : startExtent,
basemap:"streets",
autoResize:false
});
map.on("load", mapLoadHandler);
startSymbol = new PictureMarkerSymbol('images/bluedot.png', 20, 20);
routePolylineSymbol = new SimpleLineSymbol(SimpleLineSymbol.STYLE_SOLID, new Color([115, 44, 123, 0.6]), 4.5);
var routeRenderer = new SimpleRenderer(routePolylineSymbol);
routeGraphicLayer = new GraphicsLayer({id : "routeGraphicLayer"});
routeGraphicLayer.setRenderer(routeRenderer);
map.addLayer(routeGraphicLayer);
var segmentSymbol = new SimpleLineSymbol(SimpleLineSymbol.STYLE_SOLID, new Color([102, 255, 255, 0.5]), 10);
var segmentRenderer = new SimpleRenderer(segmentSymbol);
segmentGraphicsLayer = new GraphicsLayer({
id : "segmentGraphicsLayer"
});
segmentGraphicsLayer.setRenderer(segmentRenderer);
map.addLayer(segmentGraphicsLayer);
var locSymbol = new SimpleMarkerSymbol(SimpleMarkerSymbol.STYLE_SQUARE, 4, new SimpleLineSymbol(SimpleLineSymbol.STYLE_SOLID, new Color("#f79d17"), 2), new Color("#f79d17"));
var siteSymbol = new PictureMarkerSymbol('images/i_pin_red_sm.png', 32, 32);
siteRenderer = new SimpleRenderer(siteSymbol);
siteInfoTemplate = new InfoTemplate();
siteInfoTemplate.setTitle("<b>Site Information</b>");
siteInfoTemplate.setContent(generateInfoContent);
siteFeatureLayer = new FeatureLayer(pathName+"/arcgis/rest/services/DHSS/warmingCenter/MapServer/0", {
id : "siteFeatureLayer",
mode : FeatureLayer.MODE_SELECTION,
outFields : ['*'],
infoTemplate : siteInfoTemplate
});
siteFeatureLayer.setRenderer(siteRenderer);
map.addLayer(siteFeatureLayer);
searchQuery = new Query();
routeParams = new RouteParameters();
routeParams.returnRoutes = true;
routeParams.returnDirections = true;
routeParams.directionsLengthUnits = esriUnits.MILES;
routeParams.outSpatialReference = map.spatialReference;
routeParams.stops = new FeatureSet();
//-------------------------------------------------
//map resize events needed for mobile maps
function resizeMap() {
mobile.hideAddressBar();
adjustMapHeight();
map.resize();
map.reposition();
}
function adjustMapHeight() {
var headHeight = registry.byId('mapHeader').domNode.clientHeight;
var availHeight = mobile.getScreenSize().h - headHeight - 60;
if (has('iphone') || has('ipod')) {
availHeight += iphoneAdjustment();
}
dom.byId("mapDiv").style.height = availHeight + "px";
dom.byId("mapDiv").style.width = "100%";
}
function iphoneAdjustment() {
var sz = mobile.getScreenSize();
if (sz.h > sz.w) { //its portrait
//Need to add address bar height back to map because it has not been hidden yet 44 = height of bottom safari button bar
return screen.availHeight - window.innerHeight - 44;
} else { // must be landscape
//Need to react to full screen / bottom button bar visible toggles
var _conn = on(window, 'resize', function() {
_conn.remove();
resizeMap();
});
return 0;
}
}
//functions for geolocation API -------------------------
function mapLoadHandler(evt){
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(estimateAddressLocation, geoLocationError);
} else {
alert("No geolocation support. You must specify a starting address to begin your search.");
registry.byId('locationDiv').domNode.innerHTML = "Your location not detected";
registry.byId('accuracyDiv').domNode.innerHTML = "Use 'Enter my starting address' to set your current location.";
registry.byId('mainHeader').set("busy", false);
}
registry.byId('mapView').on('AfterTransitionIn', resizeMap);
}
function estimateAddressLocation(location) {
var pt = new Point([location.coords.longitude, location.coords.latitude], new SpatialReference({ wkid : 4326 }));
// Geolocation api returns an address but iPhone returns null so use reverse geocoding to get address
locator.locationToAddress(pt, 100, function(candidate) {
if (candidate.address) {
startAddr = candidate.address.Address;
var address = candidate.address.Address + "<br/> "
+ candidate.address.City + ", "
+ candidate.address.Region
+ " " + candidate.address.Postal;
registry.byId('locationDiv').domNode.innerHTML = "Placed near: <br/> " + address;
var webMercPt = webMercatorUtils.geographicToWebMercator(candidate.location);
registry.byId('accuracyDiv').domNode.innerHTML = "Location accurate within "
+ location.coords.accuracy + " meters <br />of the address listed. Last Updated: "
+ new Date(location.timestamp).toLocaleString();
if (pointSource == "Geocode") {
currentLocation = candidate.location;
}else {
currentLocation = webMercPt; // webMercator
}
registry.byId('mainHeader').set("busy", false);
if (!currentGraphic) { //currentGraphic used as the starting location for buffer search
var attr = { "Source" : "Geolocated point"};
currentGraphic = new Graphic(currentLocation, startSymbol, attr);
pointSource = "geoLocation";
map.graphics.add(currentGraphic);
map.centerAndZoom(currentLocation, 16);
} else {
currentGraphic.setGeometry(currentLocation);
}
}
}, function(error) {
registry.byId('locationDiv').innerHTML = 'Unable to approximate address for your current location';
});
}
function geoLocationError(error){
if (watchProcess !== null) {
navigator.geolocation.clearWatch(watchProcess);
watchProcess = null;
}
switch (error.code) {
case error.PERMISSION_DENIED:
alert("Geolocation access denied or disabled. To enable geolocation on your iPhone, go to Settings > General > Location Services");
break;
case error.POSITION_UNAVAILABLE:
alert("Location information not available");
break;
case error.TIMEOUT:
alert("Request to get located timed out.");
break;
default:
alert("unknown error");
break;
}
}
//
function emptyContainer (container) {
var itemChildren = container.getChildren();
if (itemChildren.length > 0) {
arrayUtils.forEach(itemChildren, function(widget){
widget.destroyRecursive();
});
}
}
function generateInfoContent(graphic){
var formatString = "";
var disclaimerTest = graphic.attributes.HOURS_OF_OPERATION_DISCLAIMER;
locTest = graphic.attributes.LOC_CODE;
formatString = "<b>"+graphic.attributes.FACILITY +"</b><br/>" +graphic.attributes.ADDRESS+"<br/>"
+ graphic.attributes.CITY+", " + graphic.attributes.STATE + "</br>"
+ "Phone: " + graphic.attributes.PHONE + "<br/>"
+ "Hours: " + graphic.attributes.HOURS_OF_OPERATION;
if (disclaimerTest){
formatString = formatString + "<br/>" + graphic.attributes.HOURS_OF_OPERATION_DISCLAIMER;
}
switch (locTest){
case "CENT":
formatString = formatString + "</br></br> ** Location Approximate ** ";
break;
default:
}
return formatString;
}
//search functions -------------------------------
function queryFeatures() {
itemList = registry.byId('searchResults');
emptyContainer(itemList);
var bufferParams = new BufferParameters();
bufferParams.geometries = [currentGraphic.geometry];
bufferParams.distances = [currentBufferDist];
bufferParams.unit = GeometryService.UNIT_STATUTE_MILE;
bufferParams.outSpatialReference = map.spatialReference;
geomService.buffer(bufferParams, function(geometries) {
var bufferGeometry = geometries[0];
var geometryExtent = bufferGeometry.getExtent();
query.geometry = geometryExtent;
query.outSpatialReference = map.spatialReference;
siteFeatureLayer.selectFeatures(query, FeatureLayer.SELECTION_NEW, function(features) {
if (features.length === 0) {//If no results are found, suggest increasing search distance
var noResults = new ListItem({
label : "No Results"
});
noResults.set("class", "mblVariableHeight");
noResults.domNode.innerHTML = "No results found. Try a larger search distance using the settings option.";
itemList.addChild(noResults);
return;
}
//format returned distance to something more readable
arrayUtils.forEach(features, function(feature) {
feature.distance = Math.round((mathUtils.getLength(feature.geometry, currentGraphic.geometry) / 1609.344) * 100) / 100;
});
//sorts the returned features so closest features are at the top
var sortedFeatures = features.sort(function(a, b) {
return a.distance - b.distance;
});
//create the formatted string to populate the listitem, add a map button that solves the route between the current location and site
arrayUtils.forEach(sortedFeatures, function(result) {
var formatString = result.attributes.FACILITY + "<br />" + result.attributes.ADDRESS
+ " - " + result.attributes.CITY + "<br />"
+ result.attributes.PHONE + " -- "
+ result.distance + " miles<br/>"
var resultItem = new ListItem({
label : result.attributes.FACILITY
});
resultItem.set("variableHeight", true);
resultItem.domNode.innerHTML = formatString;
var button = new Button({
label : 'Map It',
moveTo : 'mapView'
}).placeAt(resultItem.domNode);
var locationDetails = function(result) {
mapResults(result);
};
on(button, 'click', function (){
locationDetails(result)
});
itemList.addChild(resultItem);
});
}, function(error) {
alert(error.message);
});
}, function(error) {
alert(error.message);
});
}
//Routing functions ------------------------------------------
function mapResults(feature) {
var att;
var widget = registry.byId('resultsView');
widget.performTransition('mapView', 1, "slide", null);
routeGraphicLayer.clear();
segmentGraphicsLayer.clear();
routeParams.stops.features.length = 0;
var startLoc = new Graphic(currentLocation, locSymbol, {'name':startAddr});
var endLoc = new Graphic(feature.geometry, locSymbol, {'name':feature.attributes.FACILITY});
routeParams.outSpatialReference = map.spatialReference;
routeParams.stops.features.push(startLoc);
routeParams.stops.features.push(endLoc);
on(routeTask,'solve-complete', showRoute);
on(routeTask, 'error', routeError);
routeTask.solve(routeParams);
}
// adds the route graphics to the graphicsLayer and populates the directions list
function showRoute(solveResult) {
var dirLi;
var directions = solveResult.result.routeResults[0].directions;
directionFeatures = directions.features;
var dirView = registry.byId('routeDirections');
emptyContainer(dirView);
var dirList = new RoundRectList();
dirView.addChild(dirList);
arrayUtils.forEach(directions.features, function(feature, index) {
routeGraphicLayer.add(new Graphic(feature.geometry, routePolylineSymbol));
dirLi = new ListItem({
label:feature.attributes.text
});
if (index === 0 || index === directions.features.length - 1) {//first and list entries don't use a click event
dirList.addChild(dirLi);
}else{
dirLi.set("clickable", true);
dirLi.set("rightText", "Map");
on (dirLi, 'click', function () {
zoomToSegment(index);
});
dirList.addChild(dirLi);
}
});
map.setExtent(directions.extent, true);
}
function routeError(error) {
alert("Problem finding route \n" + error.message );
registry.byId("directions").domNode.innerHTML = "<b>Problem finding route</b> " ;
}
//Zoom to the appropriate segment afer clicking a hyperlink in the directions list
function zoomToSegment(index) {
var segment = directionFeatures[index];
segmentGraphicsLayer.clear();
var segmentGraphic = new Graphic(segment.geometry);
segmentGraphicsLayer.add(segmentGraphic);
var adjExtent =segment.geometry.getExtent().expand(3);
map.setExtent(adjExtent);
var widget = registry.byId('directionsView');
widget.performTransition('mapView', -1, "fade", null);
}
// geocoding functions -----------------------------------------------
function locate() {
map.graphics.clear();
var address = {
"SingleLine": dom.byId("txtAddress").value
};
locator.outSpatialReference = map.spatialReference;
var options = {
address: address,
outFields: ["*"],
searchExtent: startExtent
};
locator.addressToLocations(options);
}
function showGeocodeResults(evt){
var geometry, candidate;
var MOcandidates = arrayUtils.filter(evt.addresses, function(candidate){
return candidate.attributes.Region == "Missouri";
});
candidate = MOcandidates[0];
var attr = {
"address": candidate.address
};
geometry = candidate.location;
currentLocation = geometry;
pointSource = "Geocode";
var graphic = new Graphic(geometry, startSymbol, attr);
map.graphics.add(graphic);
currentGraphic = graphic;
startAddr = candidate.address;
registry.byId('locationDiv').domNode.innerHTML = candidate.address;
registry.byId('accuracyDiv').domNode.innerHTML = "Not using geolocation, searching from address.";
}
//event listeners -----------------
registry.byId('geoLocateItem').on("click", function() {
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(estimateAddressLocation, geoLocationError);
} else {
alert("No geolocation support. You must specify a starting address to begin your search.");
registry.byId('locationDiv').innerHTML = "Your location not detected";
registry.byId('accuracyDiv').innerHTML = "Use 'Enter my starting address' to set your current location.";
}
});
//function for changing search distance
query(".distanceList").on("click", function(){
var dist = String(this.id);
currentBufferDist = dist;
console.log("search distance set to " + dist);
});
on(registry.byId('centerItem'), 'click', function (){
if (!currentGraphic) {
alert("No geolocation support. You must specify a starting address to begin your search");
} else {
queryFeatures();
}
});
on(registry.byId("btnGo"), 'click', function(){
locate();
var w = registry.byId('addressView');
w.performTransition('mainView', -1, "slide");
});
on(dom.byId("txtAddress"), "keydown", function(evt){
switch (evt.keyCode){
case keys.ENTER:
var w = registry.byId('addressView');
w.performTransition('mainView', -1, "slide");
// console.log("enter has been pressed");
locate();
break;
default:
// console.log("some other key: " + evt.keyCode);
}
});
});//end main function
</script>
<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-12671191-9']);
_gaq.push(['_trackPageview']);
(function() {
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
</script>
</head>
<body class="claro">
<div id="mainView" data-dojo-type="dojox/mobile/View" >
<h1 id="mainHeader" data-dojo-type="dojox/mobile/Heading"> Find a Warming Center </h1>
<ul data-dojo-type="dojox/mobile/EdgeToEdgeList" >
<li id="centerItem" data-dojo-type="dojox/mobile/ListItem"
data-dojo-props="icon:'images/i_pin_red_sm.png', moveTo:'resultsView', variableHeight:true">
Warming Centers
</li>
<li id="AddressItem" data-dojo-type="dojox/mobile/ListItem"
data-dojo-props="icon:'images/mailbox.png', moveTo:'addressView',variableHeight:true" >
Enter a starting address
</li>
<li id="geoLocateItem" data-dojo-type="dojox/mobile/ListItem"
data-dojo-props="icon:'images/crosshairs.png',variableHeight:true, clickable: true" >
Use my current location
</li>
<li id="settingsItem" data-dojo-type="dojox/mobile/ListItem"
data-dojo-props="icon:'images/settings.png', moveTo:'settingsView',variableHeight:true" >
Change Search Distance
</li>
</ul>
<h2 data-dojo-type="dojox/mobile/RoundRectCategory">Current Location</h2>
<div id="locationDiv" data-dojo-type="dojox/mobile/RoundRect" ></div>
<div id="accuracyDiv" data-dojo-type="dojox/mobile/RoundRect" ></div>
</div>
<div id="resultsView" data-dojo-type="dojox/mobile/View" >
<h2 id="resultsHeader" data-dojo-type="dojox/mobile/Heading" data-dojo-props="label:'Search Results', variableHeight:'true'">
<div data-dojo-type="dojox/mobile/ToolBarButton"
data-dojo-props="label:'Home', moveTo:'mainView', transition:'slide',transitionDir:-1">
</div>
</h2>
<ul id="searchResults" data-dojo-type="dojox/mobile/RoundRectList"></ul>
</div>
<div id="addressView" data-dojo-type="dojox/mobile/View">
<div data-dojo-type="dojox/mobile/ToolBarButton"
data-dojo-props="label:'Back', moveTo:'mainView', transition:'slide',transitionDir:-1">
</div>
<div id="addrPane" data-dojo-type="dojox/mobile/RoundRect">
<h4> Enter address:</h4>
<input id="txtAddress" class="textBox" type="text" placeHolder="Search by address or place" style="width:270px ">
</input>
<div id="btnGo" data-dojo-type="dojox/mobile/ToolBarButton"> GO </div>
</div>
</div>
<div id="settingsView" data-dojo-type="dojox/mobile/View">
<div data-dojo-type="dojox/mobile/ToolBarButton"
data-dojo-props="label:'Back', moveTo:'mainView', transition:'slide',transitionDir:-1">
</div>
<ul id="searchDistList" data-dojo-type="dojox/mobile/EdgeToEdgeList"
data-dojo-props="select:'single'" >
<li id="5" class="distanceList" data-dojo-type="dojox/mobile/ListItem"
data-dojo-props="variableHeight:true, clickable: true">
5 miles
</li>
<li id="10" class="distanceList" data-dojo-type="dojox/mobile/ListItem"
data-dojo-props="variableHeight:true, clickable: true">
10 miles
</li>
<li id="25" class="distanceList" data-dojo-type="dojox/mobile/ListItem"
data-dojo-props="variableHeight:true, clickable: true, checked:true">
25 miles
</li>
<li id="50" class="distanceList" data-dojo-type="dojox/mobile/ListItem"
data-dojo-props="variableHeight:true, clickable: true">
50 miles
</li>
</ul>
</div>
<div id="directionsView" data-dojo-type="dojox/mobile/View">
<h2 id="dirHeader" data-dojo-type="dojox/mobile/Heading" data-dojo-props="label:'Directions', variableHeight:'true'">
<div data-dojo-type="dojox/mobile/ToolBarButton"
data-dojo-props="label:'Back', moveTo:'mapView', transition:'slide',transitionDir:-1">
</div>
</h2>
<div id="routeDirections" data-dojo-type="dojox/mobile/RoundRect" ></div>
</div>
<div id="mapView" data-dojo-type="dojox/mobile/View">
<h2 id="mapHeader" data-dojo-type="dojox/mobile/Heading" data-dojo-props="label:'Map', variableHeight:'true'">
<div data-dojo-type="dojox/mobile/ToolBarButton"
data-dojo-props="label:'Search Results', moveTo:'resultsView', transition:'slide',transitionDir:-1">
</div>
<div id="btnDirections" data-dojo-type="dojox/mobile/ToolBarButton"
data-dojo-props="label:'Directions', moveTo:'directionsView', transition:'slide',transitionDir:1" style="float:right;"> </div>
</h2>
<div id="mapDiv"> </div>
</div>
</body>
</html>