offline-editor-js/samples/appcache-features.html

516 lines
20 KiB
HTML

<!DOCTYPE html>
<html>
<!--<html manifest="appcache-features.appcache">-->
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta name="viewport" content="initial-scale=1, maximum-scale=1,user-scalable=no"/>
<title>Cache Features Sample</title>
<!--
This sample demonstrates using an application manifest to store features, html, css and js libraries locally.
No tiles are stored by this sample in order to keep the code as simple as possible. If you want to see
how tiles can be stored in the application cache, take a look at the appcache-tiles.html sample.
The use cases for using this sample are to ensure you can reload and restart you application
one it is offline.
It is strongly recommended that you use your own optimized build of the ArcGIS API for JavaScript
using the Web Optimizer: http://jso.arcgis.com/. You can reference the CDN or host it on your
own web server.
Use the included Grunt task to help generate the manifest file. There is manual
work involved in determining which files need to go into the manifest. The included manifest
file should work with this sample to give you an idea of what goes into the manifest.
A few things to know about manifest files:
- You cannot load an online url that has a redirect.
- If there is an error encountered, the manifest file will stop loading
- The /utils/appCacheManager.js library will auto detect if the manifest changed and
ask if you want to reload the application.
-->
<link rel="stylesheet" href="http://js.arcgis.com/3.9/js/dojo/dijit/themes/claro/claro.css">
<link rel="stylesheet" href="http://js.arcgis.com/3.9/js/esri/css/esri.css">
<style>
html, body, #map {
height: 100%;
width: 100%;
margin: 0;
padding: 0;
}
body {
background-color: #FFF;
overflow: hidden;
font-family: "Trebuchet MS";
}
.dj_ie .infowindow .window .top .right .user .content { position: relative; }
.dj_ie .simpleInfoWindow .content {position: relative;}
.esriPopup .contentPane {
color: white;
background-color: black;
height: 110px;
}
.dijitTextBox{ color: #000000; background-color: white;}
/* This is an editable text field */
.dijitSelect input, .dijitTextBox input {
color: black;
/*background-color: #ffffff !important;*/
}
.dijitTextBoxDisabled{
background-color: lightgray;
}
.esriAttributeInspector {height:100px;}
/* this is a non-editable text field */
.esriAttributeInspector .dijitTextBoxDisabled input {
color: #000000;
background-color: lightgray;
}
.dijitButtonNode{
width: 60px;
padding: 2px;
border-radius: 3px;
color: yellow;
}
.esriAttributeInspector .atiLayerName {display:none;}
.saveButton {
margin:0px;
width:16px;
height:16px;
}
.msgTextArea{
width:16px;
height:16px;
margin-left: 60px;
width: auto;
background-color: #000000;
border-color: #000000;
color: white;
font-family: "Trebuchet MS";
}
.basic-btn{
background-color: #000000;
border-color: #ffffff 1px;
color: #ffffff;
padding: 8px;
position: relative; float: left;
}
#button-div1{
position: relative;
background: #000000;
color: #ffffff;
width: 100%;
height: 50px;
}
#right-div{
height: 50px;
position: relative; float: right;
}
#img-offline-indicator{
padding: 8px;
position: relative; float: right;
}
#pending-edits{
padding: 8px;
position: relative; float: right;
}
#tile-info{
background-color: #000000;
color: white;
padding: 8px;
position: relative; float: right;
}
</style>
<script src="../vendor/offline/offline.min.js"></script>
<script>
Offline.options = {
checks: {
image: {
url: function() {
return 'http://esri.github.io/offline-editor-js/tiny-image.png?_=' + (Math.floor(Math.random() * 1000000000));
}
},
active: 'image'
}
}
var locationPath = location.pathname.replace(/\/[^/]+$/, "");
var dojoConfig = {
paths: {
edit: locationPath + "/../lib/edit",
vendor: locationPath + "/../vendor",
utils: locationPath + "/../utils",
tiles: locationPath + "/../lib/tiles"
}
}
</script>
<!-- This is a custom build of the ArcGIS API for JavaScript using the new Web Optimizer Tool -->
<script src="http://js.arcgis.com/o/agup_hack4co/appcacheFeatures/dojo/dojo.js" data-dojo-config="async: true"></script>
<!-- Use this tag below if you are hosting your ArcGIS API for JavaScript files locally -->
<!--<script src="libs/dojo/dojo/dojo.js" data-dojo-config="async: true"></script>-->
</head>
<body>
<div id="button-div1">
<button class="basic-btn" data-dojo-type="dijit/form/ToggleButton" id="btn-online-offline">1. Go Offline</button>
<button class="basic-btn" id="btn-get-tiles">2. Download Tiles</button>
<textarea contenteditable="false" id="tile-info"></textarea>
<div id="right-div">
<img id="img-offline-indicator" src="../samples/images/blue-pin.png"/>
<div id="pending-edits">Pending edits: 0</div>
</div>
</div>
<div id="test"></div>
<div id="map"></div>
<script>
require([
"esri/map",
"esri/tasks/query",
"esri/layers/FeatureLayer",
"esri/Color",
"esri/symbols/SimpleMarkerSymbol",
"esri/renderers/SimpleRenderer",
"esri/dijit/AttributeInspector",
"edit/offlineFeaturesManager",
"tiles/offlineTilesEnabler",
"edit/editsStore",
"utils/appCacheManager",
"dojo/dom-construct",
"dojo/on",
"dojo/dom",
"dijit/form/Button",
"dijit/form/SimpleTextarea",
"dojo/domReady!"],
function(Map,Query,FeatureLayer,Color,SimpleMarkerSymbol,SimpleRenderer,AttributeInspector,
OfflineFeaturesManager,OfflineTileEnabler,editsStore,AppCacheManager,domConstruct,on,dom,Button,SimpleTextArea) {
var textTimer;
var _isOnline = true;
var appCacheManager;
var offlineFeaturesManager;
var map,busStopsFeatureLayer,currentFeature;
var imgOfflineIndicator,btnOnlineOffline;
var offlineTileEnabler, baseMapLayer;
var minZoomAdjust = -1, maxZoomAdjust = 1, mMinZoom, mMaxZoom, zoom = 18;
var pendingEdits = document.getElementById("pending-edits");
var btnGetTiles = document.getElementById("btn-get-tiles");
var tileInfo = document.getElementById("tile-info")
tileInfo.innerHTML = "Tile count: 0\r\nBytes: 0";
var redPinPath = "../samples/images/red-pin.png";
var bluePinPath = "../samples/images/blue-pin.png"
var defaultSymbol = new SimpleMarkerSymbol().setStyle(
SimpleMarkerSymbol.STYLE_DIAMOND).setColor(
new Color([255,0,0,0.5]));
initOffline();
/**
* There have been a few bugs in the offline detection library (offline.min.js)
* This is a utility check to 100% validate if the application is online or
* offline prior to launching any map functionality.
*/
verifyOnline(function(result){ console.log("VERIFY ONLINE " + result)
result == true ? _isOnline = true : _isOnline = false;
startMap();
})
function startMap(){
//Make sure map shows up after a browser refresh
Offline.check();
Offline.state === 'up' ? zoom = 18 : zoom = 17;
map = new Map("map", {
basemap: "topo",
center: [-104.98,39.74], // long, lat
zoom: 10,
sliderStyle: "small"
});
busStopsFeatureLayer = new FeatureLayer("http://services.arcgis.com/IZtlGBUe4KTzLOl4/arcgis/rest/services/BPX_RTD_BusStops2/FeatureServer/0",{
mode: FeatureLayer.MODE_SNAPSHOT,
outFields: ["OBJECTID","BSID","ROUTES","STOPNAME"]
});
// Set the graphics to red boxes to make it easy to click on them
// on a mobile device.
busStopsFeatureLayer.setRenderer(new SimpleRenderer(defaultSymbol));
map.on("load",function(evt){
initAppCacheManager();
initOfflineTileEnabler();
})
map.on("layers-add-result",initEditing);
map.addLayers([busStopsFeatureLayer]);
}
function initOffline(){
offlineFeaturesManager = new OfflineFeaturesManager();
offlineFeaturesManager.on(offlineFeaturesManager.events.EDITS_ENQUEUED, updateStatus);
offlineFeaturesManager.on(offlineFeaturesManager.events.EDITS_SENT, updateStatus);
offlineFeaturesManager.on(offlineFeaturesManager.events.ALL_EDITS_SENT, updateStatus);
imgOfflineIndicator = document.getElementById("img-offline-indicator");
imgOfflineIndicator.offlineColor = "blue";
Offline.check();
Offline.on('up', goOnline);
Offline.on('down', goOffline);
on(dom.byId('btn-online-offline'), 'click', goOnlineOffline);
}
function initAppCacheManager(){
appCacheManager = new AppCacheManager(true,true);
appCacheManager.on(appCacheManager.CACHE_EVENT,cacheEventHandler);
appCacheManager.on(appCacheManager.CACHE_ERROR,cacheErrorHandler);
appCacheManager.on(appCacheManager.CACHE_LOADED,cacheLoadedHandler);
}
function initEditing(evt){
offlineFeaturesManager.extend(busStopsFeatureLayer);
map.infoWindow.on("hide", function() {
busStopsFeatureLayer.clearSelection();
});
btnOnlineOffline = document.getElementById("btn-online-offline");
var layerInfos = [{
'featureLayer': busStopsFeatureLayer,
'showAttachments': false,
'isEditable': true,
'showDeleteButton': false,
'fieldInfos': [
{'fieldName': 'OBJECTID', 'isEditable':false, 'label':'ID:'},
{'fieldName': 'BSID', 'isEditable':false, 'label':'Bus stop ID:'},
{'fieldName': 'ROUTES', 'isEditable':false,'label':'Routes:'},
{'fieldName': 'STOPNAME', 'isEditable':true, 'label':'Stop name:'}
]
}];
var attInspector = new AttributeInspector({
layerInfos:layerInfos
}, domConstruct.create("div"));
//add a save button next to the delete button
var saveButton = new Button({ label: "Save", "class": "saveButton"});
domConstruct.place(saveButton.domNode, attInspector.deleteBtn.domNode, "after");
var textArea = new SimpleTextArea({name:"msgTextArea",rows:"1",class:"msgTextArea"})
domConstruct.place(textArea.domNode, saveButton.domNode, "after");
saveButton.on("click", function(){
clearTimeout(textTimer);
busStopsFeatureLayer.applyEdits(null, [currentFeature], null,
function(response){
textArea.set("value" , "Item successfully saved.");
textTimer = setTimeout(function(){
textArea.set("value" , " ");
},10000);
},function(err){
alert("There was error applying an edit: " + JSON.stringify(err));
}
);
});
attInspector.on("attribute-change", function(evt) {
//store the updates to apply when the save button is clicked
currentFeature.attributes[evt.fieldName] = evt.fieldValue;
console.log("CHANGE " + evt.fieldValue)
});
attInspector.on("next", function(evt) {
currentFeature = evt.feature;
console.log("Next " + currentFeature.attributes.objectid);
});
busStopsFeatureLayer.on("click", function(evt) {
currentFeature = evt.graphic
var query = new Query();
query.objectIds = [evt.graphic.attributes.OBJECTID];
busStopsFeatureLayer.selectFeatures(query,FeatureLayer.SELECTION_NEW,
function(evt){
console.log("Success: " + JSON.stringify(evt[0].attributes))
},function(err){
console.log("ERROR " + JSON.stringify(err));
});
// NOTE: To make the info window for mobile use you would need
// to switch a mobile friendly 'view' containing all the attributes.
// This pattern is for testing only. It's on the TO-DO list to make
// this mobile friendly.
map.infoWindow.setTitle(currentFeature.attributes.STOPNAME);
map.infoWindow.show(evt.screenPoint,map.getInfoWindowAnchor(evt.screenPoint));
}.bind(this));
map.infoWindow.setContent(attInspector.domNode);
map.infoWindow.resize(350, 270);
}
function updateStatus(){
if( editsStore.hasPendingEdits())
{
var edits = editsStore._retrieveEditsQueue();
pendingEdits.innerHTML = "Pending edits: " + edits.length;
}
else
{
pendingEdits.innerHTML = "Pending edits: 0";
}
}
function goOnline(){
offlineFeaturesManager.goOnline(function(success,error){
if(error === undefined){
btnOnlineOffline.innerHTML = "Go Offline";
imgOfflineIndicator.src = bluePinPath;
imgOfflineIndicator.offlineColor = "blue";
console.log("Online.");
}
else{
alert("There was a problem syncing offline edits: " + JSON.stringify(error));
}
});
}
function goOffline(){
btnOnlineOffline.innerHTML = "Go Online";
imgOfflineIndicator.src = redPinPath;
imgOfflineIndicator.offlineColor = "red";
offlineFeaturesManager.goOffline();
}
function goOnlineOffline(){
if(offlineFeaturesManager.getOnlineStatus() == offlineFeaturesManager.ONLINE){
goOffline();
}
else{
goOnline();
}
}
/**
* ************************************
* TILE MANAGEMENT CODE GOES BELOW HERE
* ************************************
*/
function initOfflineTileEnabler(){
mMaxZoom = map.getMaxZoom();
mMinZoom = map.getMinZoom();
offlineTileEnabler = new OfflineTileEnabler();
baseMapLayer = offlineTileEnabler.getBasemapLayer(map); console.log("Offline State: " + Offline.state)
offlineTileEnabler.extend(baseMapLayer,function(success)
{
if( success )
{
console.log("Offline tile lib is enabled. Application state is: " + Offline.state);
Offline.check();
//using null sets this for CORS
baseMapLayer.offline.proxyPath = null;
// on(btnGetTiles,"click",downloadTiles);
updateOfflineUsage();
}
else
{
alert("error initializing storage - browser doesn't support indexeddb or websql")
}
}.bind(this),_isOnline);
}
function updateOfflineUsage()
{
baseMapLayer.offline.store.usedSpace(function(result,err){
if(result != null){
tileInfo.innerHTML = "DB Tile count: " + result.tileCount + "\r\nDB Bytes: " + result.sizeBytes;
}
else{
tileInfo.innerHTML = "DB Tile count: " + count + "\r\nDB Bytes: Error";
}
})
}
/**
* Utility function to validate min and max zoom settings of the map
*/
function getMinMaxZoom(){
var zoom = {};
var min = map.getLevel() + minZoomAdjust;
var max = map.getLevel() + maxZoomAdjust;
zoom.max = Math.min(mMaxZoom, max); //prevent errors by setting the tile layer floor
zoom.min = Math.max(mMinZoom, min); //prevent errors by setting the tile layer ceiling
return zoom;
}
/**
* Attempts an http request to verify if app is online or offline.
* Use this in conjunction with the offline checker library: offline.min.js
* @param callback
*/
function verifyOnline(callback){
var req = new XMLHttpRequest();
req.open("GET", "images/blue-pin.png?" + (Math.floor(Math.random() * 1000000000)), true);
req.onload = function()
{
if( req.status === 200 && req.responseText !== "")
{
callback(true);
}
else
{
console.log("verifyOffline failed");
callback(false);
}
};
req.onerror = function(e)
{
console.log("verifyOffline failed: " + e);
callback(false);
};
req.send(null);
}
function cacheLoadedHandler(evt){
if(evt == appCacheManager.CACHE_LOADED) console.log("Application cache successfully loaded!")
}
function cacheEventHandler(evt){
console.log("CACHE EVENT: " + JSON.stringify(evt));
}
function cacheErrorHandler(evt){
console.log("CACHE ERROR: " + JSON.stringify(evt));
}
}
);
</script>
</body>
</html>