offline-editor-js/samples/appcache-features.html
2014-06-08 12:03:28 -06:00

691 lines
27 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="../samples/css/modular-popup.css">
<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";
}
#map {
z-index: 1;
position: absolute;
top: 50px;
left: 0;
}
#popup {
height: 100%;
width: 100%;
z-index: 98;
font-size: x-large;
position: absolute;
top: -9999px !important;
left: -9999px !important;
}
#popup-body {
position: relative;
top: 10%;
left: 10%;
z-index: 100;
height: 80%;
width: 80%;
border-radius: 10px;
background-color: black;
display: table;
opacity: 0.7;
}
.basic-btn{
background-color: #000000;
border-color: #ffffff 1px;
color: #ffffff;
padding: 8px;
position: relative; float: left;
}
#button-div1{
position: relative;
z-index: 2;
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;
position: relative; float: right;
}
#pending-edits{
padding: 8px;
position: relative; float: right;
}
@media (max-width: 500px) {
#pending-edits {
font-size: small;
}
}
@media (max-width: 450px) {
#pending-edits {
font-size: x-small;
}
}
</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>
<!-- Required when using the offline-editor-js library with Safari -->
<script src="../vendor/IndexedDBShim/dist/IndexedDBShim.min.js"></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>
<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="popup">
<div id="popup-body">
<div id="row1" class="mod-popup-table-row">
<div class="mod-popup-label-top-left">ID</div>
<div class="mod-popup-input">
<input id="stop-main-id" type="text" disabled class="mod-popup-stop-input-disabled" value="test"/>
</div>
</div>
<div id="row2" class="mod-popup-table-row">
<div class="mod-popup-label">Bustop ID</div>
<div class="mod-popup-input">
<input id="stop-id" disabled class="mod-popup-stop-input-disabled" value="test"/>
</div>
</div>
<div id="row3" class="mod-popup-table-row">
<div class="mod-popup-label">Routes</div>
<div class="mod-popup-input">
<input id="stop-routes" class="mod-popup-stop-input" value="test"/>
</div>
</div>
<div id="row4" class="mod-popup-table-row">
<div class="mod-popup-label">Stopnames</div>
<div class="mod-popup-input">
<input id="stop-names" class="mod-popup-stop-input" value="test"/>
</div>
</div>
<div id="row5" class="mod-popup-table-row">
<div class="mod-popup-button-div-bottom-left">
<button id="mod-popup-close-btn" class="mod-popup-button-cancel" >Close</button>
</div>
<div class="mod-popup-button-div">
<button id="mod-popup-save-btn" class="mod-popup-button" style="margin-right: 10px;">Save</button>
<button id="mod-popup-delete-btn" disabled class="mod-popup-button" style="background-color: grey; text-decoration:line-through;">Delete</button>
</div>
</div>
</div>
<div id="modal-background" class="mod-popup-modal-background"></div>
</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) {
// Variables for edit handling
var textTimer;
var _isOnline = true;
var features = [];
var offlineFeaturesManager;
var layerDefinition = {};
var map,busStopsFeatureLayer,currentFeature;
var imgOfflineIndicator;
var pendingEdits = document.getElementById("pending-edits");
// Variables for modal popup
var modPopup = document.getElementById("popup");
var closeBtn = document.getElementById("mod-popup-close-btn");
var saveBtn = document.getElementById("mod-popup-save-btn");
var deleteBtn = document.getElementById("mod-popup-delete-btn");
var stopMainID = document.getElementById("stop-main-id");
var stopID = document.getElementById("stop-id");
var stopRoutes = document.getElementById("stop-routes");
var stopNames = document.getElementById("stop-names");
// Variables for tile handling
var _wantToCancel;
var globalState = {};
var offlineTileEnabler, baseMapLayer;
var btnGetTiles = document.getElementById("btn-get-tiles");
var minZoomAdjust = -1, maxZoomAdjust = 1, mMinZoom, mMaxZoom, zoom = 18;
var EXTENT_BUFFER = 0; //buffers the map extent in meters
// Symbols and images
var redPinPath = "../samples/images/red-pin.png";
var bluePinPath = "../samples/images/blue-pin.png";
var defaultSymbol;
// Modify symbol size based on screen size.
// Bigger screens get smaller symbols. Smaller screens get larger symbols.
if (document.documentElement.clientHeight > 768 || document.documentElement.clientWidth > 1024) {
defaultSymbol= new SimpleMarkerSymbol().setStyle(
SimpleMarkerSymbol.STYLE_DIAMOND).setColor(
new Color([255,0,0,0.5])).setSize(20); // scripts
}
else{
defaultSymbol= new SimpleMarkerSymbol().setStyle(
SimpleMarkerSymbol.STYLE_DIAMOND).setColor(
new Color([255,0,0,0.5])).setSize(35);
}
// Miscellaneous variables
var appCacheManager;
var btnOnlineOffline = document.getElementById("btn-online-offline");
setClickListeners();
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();
if(_isOnline == false){
var fLayer = JSON.parse(localStorage.offlineLayerDef);
var fFeatures = JSON.parse(localStorage.offlineFeature);
reconstituteGraphicsLayer(fLayer,fFeatures,function(result){
initEditing(result);
});
}
})
map.on("layers-add-result",initEditing);
busStopsFeatureLayer.on("update-end",function(evt){
layerDefinition.objectIdFieldName = evt.target.objectIdField;
layerDefinition.globalIdFieldName = evt.target.globalIdField;
layerDefinition.geometryType = evt.target.geometryType;
layerDefinition.spatialReference = evt.target.spatialReference;
layerDefinition.fields = evt.target.fields;
features = evt.target.graphics;
convertGraphicLayerToJSON(features,function(result){
var featureJSON = JSON.stringify(result);
if(typeof(Storage) !== "undefined") {
localStorage.offlineLayerDef = JSON.stringify(layerDefinition);
localStorage.offlineFeature = featureJSON;
console.log("Done pushing layerDef and features to localStorage.")
} else {
alert("The offline library is not supported on this browser.")
}
})
})
map.addLayers([busStopsFeatureLayer]);
}
function setClickListeners(){
closeBtn.onclick = function(evt){
hideModalPopup();
}
saveBtn.onclick = function(evt){
modPopup.graphic.attributes.ROUTES = stopRoutes.value;
modPopup.graphic.attributes.STOPNAME = stopNames.value;
busStopsFeatureLayer.applyEdits(null,[modPopup.graphic],null,function(result){
console.log("Successfully saved changes to: " + modPopup.graphic.attributes.STOPNAME);
hideModalPopup();
},
function(err){
alert("There was a problem while trying to save: " + modPopup.graphic.attributes.STOPNAME);
})
}
deleteBtn.onclick = function(evt){
busStopsFeatureLayer.applyEdits(null,null,[modPopup.graphic],function(result){
console.log("Successfully deleted: " + modPopup.graphic.attributes.STOPNAME);
hideModalPopup();
},
function(err){
alert("There was a problem while trying to delete: " + modPopup.graphic.attributes.STOPNAME);
})
}
}
function convertGraphicLayerToJSON(graphics,callback){
var length = graphics.length;
var array = [];
for(var i=0; i < length; i++){
var jsonGraphic = graphics[i].toJson();
array.push(jsonGraphic);
if(i == (length - 1)) callback(array);
}
}
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);
setFeatureLayerClickHandler()
}
function setFeatureLayerClickHandler(){
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))
var atts = evt[0].attributes;
showModalPopup(evt[0]);
stopID.value = atts.OBJECTID;
stopMainID.value = atts.BSID;
stopNames.value = atts.STOPNAME;
stopRoutes.value = atts.ROUTES;
},function(err){
console.log("ERROR " + JSON.stringify(err));
});
}.bind(this));
}
function reconstituteGraphicsLayer(featureLayer,featuresArr,callback){
if(featureLayer == null){
alert("No features available available locally.")
}
else{
var featureDefinition = {
"layerDefinition":featureLayer,
"featureSet":{
"features": featuresArr,
"geometryType": "esriGeometryPoint"
}
}
busStopsFeatureLayer = new FeatureLayer(featureDefinition,{
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));
var mapListen = map.on("update-end",function(evt){
console.log("Feature has been added back to the map while offline.")
mapListen.remove();
})
map.addLayer(busStopsFeatureLayer);
callback(true);
}
}
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 = "1. Go Offline";
imgOfflineIndicator.src = bluePinPath;
imgOfflineIndicator.offlineColor = "blue";
console.log("Online.");
}
else{
alert("There was a problem syncing offline edits: " + JSON.stringify(error));
}
});
updateOfflineUsage();
if(typeof baseMapLayer != "undefined") baseMapLayer.goOnline();
}
function goOffline(){
btnOnlineOffline.innerHTML = "1. Go Online";
imgOfflineIndicator.src = redPinPath;
imgOfflineIndicator.offlineColor = "red";
offlineFeaturesManager.goOffline();
if(typeof baseMapLayer != "undefined") baseMapLayer.goOffline();
}
function goOnlineOffline(){
if(offlineFeaturesManager.getOnlineStatus() == offlineFeaturesManager.ONLINE){
goOffline();
}
else{
goOnline();
}
}
function showModalPopup(graphic){
modPopup.style.position = "static";
modPopup.graphic = graphic; // assign graphic to modPopup as a property.
}
function hideModalPopup(){
modPopup.style.position = "absolute";
}
/**
* ************************************
* 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);
}
/**
* Manually starts the process to download and store tiles
* in the local database
*/
function downloadTiles(){
baseMapLayer.deleteAllTiles(function(success,err){
if(success == false){
alert("There was a problem deleting the tile cache");
}
else{
console.log("success deleting tile cache");
var self = this.data;
if( globalState.downloadState == 'downloading')
{
console.log("cancel!");
_wantToCancel = true;
btnGetTiles.innerHTML = "cancelling..";
}
else
{
var zoom = getMinMaxZoom();
var extent = baseMapLayer.getExtentBuffer(EXTENT_BUFFER,map.extent);
_wantToCancel = false;
baseMapLayer.prepareForOffline(zoom.min, zoom.max, extent, reportProgress.bind(this));
globalState.downloadState = 'downloading';
}
}
}.bind(this))
}
/**
* Reports the process while downloading tiles.
*/
function reportProgress(progress)
{
console.log("downloading tiles...");
if(progress.hasOwnProperty("countNow")){
var percent = Math.floor(progress.countNow / progress.countMax * 100);
btnGetTiles.innerHTML = 'Saving to phone ' + percent + "% - Tap to Cancel";
}
if( progress.finishedDownloading )
{
btnGetTiles.innerHTML = "Saving to phone 100% - Tap to Cancel";
updateOfflineUsage();
if( progress.cancelRequested )
{
globalState.downloadState = 'cancelled';
alert("Tile download was cancelled");
}
else
{
globalState.downloadState = 'downloaded';
alert("Tile download complete");
}
btnGetTiles.innerHTML = '2. Download Tiles';
}
return _wantToCancel; //determines if a cancel request has been issued
}
function updateOfflineUsage()
{
baseMapLayer.offline.store.usedSpace(function(result,err){
if(result != null){
console.log( "DB Tile count: " + result.tileCount + ", DB Bytes: " + result.sizeBytes);
}
else{
console.log("DB Tile count: " + count + ", DB 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>