mirror of
https://github.com/Esri/offline-editor-js.git
synced 2026-02-07 14:26:22 +00:00
Merge pull request #199 from andygup/gh-pages
Update gh-pages. Closes #198.
This commit is contained in:
commit
44e6f46066
98
.jshintrc
Normal file
98
.jshintrc
Normal file
@ -0,0 +1,98 @@
|
||||
{
|
||||
// JSHint Configuration, esri jsapi
|
||||
// Modified from [jshint web defaults][1].
|
||||
// Differences between the default and our file are noted
|
||||
// Options that are commented out completely are uneccesary or problematic for our codebase
|
||||
// [1] : https://github.com/jshint/jshint/blob/2.x/examples/.jshintrc
|
||||
// See http://jshint.com/docs/ for more details
|
||||
|
||||
"maxerr" : 5000, // {int} Maximum error before stopping ** Get ALL the errors **
|
||||
|
||||
// Enforcing - true = enforce this rule, false = don't enforce this rule
|
||||
"bitwise" : true, // true: Prohibit bitwise operators (&, |, ^, etc.)
|
||||
"camelcase" : false, // true: Identifiers must be in camelCase
|
||||
"curly" : true, // true: Require {} for every new block or scope
|
||||
"eqeqeq" : false, // true: Require triple equals (===) for comparison ** Just use triples with undefined, null, false, 0 and 1 **
|
||||
"forin" : true, // true: Require filtering for..in loops with obj.hasOwnProperty() ** Still needed until IE8 support is dropped **
|
||||
"immed" : true, // true: Require immediate invocations to be wrapped in parens e.g. `(function () { } ());` ** Avoids confusion and minification errors **
|
||||
"latedef" : false, // true: Require variables/functions to be defined before being used
|
||||
"newcap" : true, // true: Require capitalization of all constructor functions e.g. `new F()` ** Coding style enforcement **
|
||||
"noarg" : true, // true: Prohibit use of `arguments.caller` and `arguments.callee`
|
||||
"noempty" : true, // true: Prohibit use of empty blocks
|
||||
"nonew" : true, // true: Prohibit use of constructors for side-effects (without assignment) ** Coding style enforcement **
|
||||
"plusplus" : false, // true: Prohibit use of `++` & `--`
|
||||
"quotmark" : true, // Quotation mark consistency: ** Use the same style. Doubles should be used it most cases **
|
||||
// false : do nothing (default)
|
||||
// true : ensure whatever is used is consistent
|
||||
// "single" : require single quotes
|
||||
// "double" : require double quotes
|
||||
"undef" : true, // true: Require all non-global variables to be declared (prevents global leaks)
|
||||
"unused" : "vars", // true: Require all defined variables be used
|
||||
"strict" : false, // true: Requires all functions run in ES5 Strict Mode ** Dojo style and existing codebase conflicts **
|
||||
"trailing" : false, // true: Prohibit trailing whitespaces
|
||||
//"indent" : 4, // {int} Number of spaces to use for indentation
|
||||
//"maxparams" : false, // {int} Max number of formal params allowed per function
|
||||
//"maxdepth" : false, // {int} Max depth of nested blocks (within functions)
|
||||
//"maxstatements" : false, // {int} Max number statements per function
|
||||
//"maxcomplexity" : false, // {int} Max cyclomatic complexity per function
|
||||
//"maxlen" : false, // {int} Max number of characters per line
|
||||
|
||||
// Relaxing - false = continue to enforce this rule, true = don't enforce this rule (relax it)
|
||||
"asi" : false, // true: Tolerate Automatic Semicolon Insertion (no semicolons)
|
||||
"boss" : false, // true: Tolerate assignments where comparisons would be expected
|
||||
"debug" : false, // true: Allow debugger statements e.g. browser breakpoints.
|
||||
"eqnull" : true, // true: Tolerate use of `== null`
|
||||
"es5" : false, // true: Allow ES5 syntax (ex: getters and setters)
|
||||
"esnext" : false, // true: Allow ES.next (ES6) syntax (ex: `const`)
|
||||
"moz" : false, // true: Allow Mozilla specific syntax (extends and overrides esnext features)
|
||||
// (ex: `for each`, multiple try/catch, function expression…)
|
||||
"evil" : false, // true: Tolerate use of `eval` and `new Function()`
|
||||
"expr" : false, // true: Tolerate `ExpressionStatement` as Programs
|
||||
"funcscope" : true, // true: Tolerate defining variables inside control statements ** Other variable checks keep use from abusing this **
|
||||
"globalstrict" : false, // true: Allow global "use strict" (also enables 'strict')
|
||||
"iterator" : false, // true: Tolerate using the `__iterator__` property
|
||||
"lastsemic" : false, // true: Tolerate omitting a semicolon for the last statement of a 1-line block
|
||||
"laxbreak" : false, // true: Tolerate possibly unsafe line breakings
|
||||
"laxcomma" : false, // true: Tolerate comma-first style coding
|
||||
"loopfunc" : true, // true: Tolerate functions being defined in loops ** Almost required in some callback & promise style code **
|
||||
"multistr" : false, // true: Tolerate multi-line strings
|
||||
"proto" : false, // true: Tolerate using the `__proto__` property
|
||||
"scripturl" : true, // true: Tolerate script-targeted URLs ** If this is being used, there is probably a good reason **
|
||||
"smarttabs" : false, // true: Tolerate mixed tabs/spaces when used for alignment
|
||||
"shadow" : false, // true: Allows re-define variables later in code e.g. `var x=1; x=2;`
|
||||
"sub" : false, // true: Tolerate using `[]` notation when it can still be expressed in dot notation
|
||||
"supernew" : false, // true: Tolerate `new function () { ... };` and `new Object;`
|
||||
"validthis" : true, // true: Tolerate using this in a non-constructor function ** We don't run in `strict mode` & coding style conflicts **
|
||||
|
||||
// Environments
|
||||
"browser" : true, // Web Browser (window, document, etc)
|
||||
"devel" : true, // Development/debugging (alert, confirm, etc)
|
||||
"couch" : false, // CouchDB
|
||||
"dojo" : false, // Dojo Toolkit ** Don't use global dojo objects. Use AMD style coding **
|
||||
"jquery" : false, // jQuery
|
||||
"mootools" : false, // MooTools
|
||||
"node" : false, // Node.js
|
||||
"nonstandard" : false, // Widely adopted globals (escape, unescape, etc)
|
||||
"prototypejs" : false, // Prototype and Scriptaculous
|
||||
"rhino" : false, // Rhino
|
||||
"worker" : false, // Web Workers ** Make a jshint comment when this is `true` **
|
||||
"wsh" : false, // Windows Scripting Host
|
||||
"yui" : false, // Yahoo User Interface
|
||||
|
||||
// Legacy ** According to jshint docs, these options are NOT to be used or relied on. Removing them.
|
||||
//"nomen" : false, // true: Prohibit dangling `_` in variables
|
||||
//"onevar" : false, // true: Allow only one `var` statement per function
|
||||
//"passfail" : false, // true: Stop on first error
|
||||
//"white" : false, // true: Check against strict whitespace and indentation rules
|
||||
|
||||
// Custom Globals - additional predefined global variables
|
||||
// Using both `predef` and `globals` to support tools with older jshint parsers
|
||||
"predef" : [
|
||||
"define",
|
||||
"require"
|
||||
],
|
||||
"globals" : { // ** `false` = don't allow variable to be redefined locally
|
||||
"define": false,
|
||||
"require": false
|
||||
}
|
||||
}
|
||||
27
README.md
27
README.md
@ -12,6 +12,8 @@ This repo contains the following libraries:
|
||||
* `editsStore` - Provides static helper methods for working with the offline data store.
|
||||
- `/tiles`: stores portions of tiled maps client-side and uses the cached tiles when device is offline
|
||||
* `offlineTilesEnabler` Extends and overrides a tiled map service.
|
||||
- `/tpk`: lets you work with TPK files.
|
||||
* `TPKLayer` - parses a TPK file and displays it as a tiled map layer.
|
||||
- `/utils`: contains various helper libraries.
|
||||
- `/samples`: sample apps to show how to use different aspects of the offline library capabilities.
|
||||
|
||||
@ -40,14 +42,22 @@ Extends and overrides a feature layer. This library allows you to extend esri.la
|
||||
|
||||
|
||||
##offlineTilesEnabler
|
||||
Extends and overrides a tiled map service. Provides the ability to customize the extent used to cut the tiles. See the detailed description of basemap.prepareForOffline() in the "How To Use" section below to learn different options.
|
||||
Extends and overrides a tiled map service. Provides the ability to customize the extent used to cut the tiles. See the detailed description of basemap.prepareForOffline() in the "How To Use" section to learn different options.
|
||||
|
||||
* __Click [here](doc/offlinetilesenabler.md) to see the full API doc for `offlineTilesEnabler`__
|
||||
|
||||
##TPKLayer
|
||||
|
||||
Extends TileMapServiceLayer. You can display TPK files with this library.
|
||||
|
||||
* __Click [here](doc/tpklayer.md) to see the full API doc for `TPKLayer`__
|
||||
|
||||
#How to use
|
||||
|
||||
* [Learn more about using the `tile` library](doc/howtousetiles.md)
|
||||
* [Learn more about using the `edit` library](doc/howtouseeditlibrary.md)
|
||||
* [Learn more about using the `tpk` library](doc/howtousetpklibrary.md)
|
||||
* [Learn more abuut using an application cache with this library](doc/howtouseappcache.md)
|
||||
|
||||
|
||||
##Setup Instructions
|
||||
@ -57,6 +67,17 @@ Extends and overrides a tiled map service. Provides the ability to customize the
|
||||
3. Run `git submodule init` and `git submodule update`
|
||||
4. Try out the apps in the `/samples` folder.
|
||||
|
||||
|
||||
##Samples
|
||||
* `appcache-features.html` - shows how to work with the application manifest, tiles and features.
|
||||
* `appcache-tiles.html` - shows how to work with the application manifest and map tiles.
|
||||
* `attachments-editor.html` - demonstrates how to work with this library and feature attachments.
|
||||
* `military-offline.html` - shows working with points, lines and polygons locally.
|
||||
* `tpklayer.html` - shows how to work with TPK files.
|
||||
* `tiles-indexed-db.html` - shows how to work with storing tiles locally.
|
||||
* `Gruntfile.js` - a node.js app and its associated `package.json` file to help with creating an application manifest file.
|
||||
|
||||
|
||||
##Dependencies
|
||||
|
||||
* ArcGIS API for JavaScript (v3.8+)
|
||||
@ -69,9 +90,11 @@ Extends and overrides a tiled map service. Provides the ability to customize the
|
||||
- IMPORTANT: There is a know [issue](https://github.com/axemclion/IndexedDBShim/issues/115) with IndexedDBShim on Safari. The workaround is to switch from using /dist/IndexedDBShim.min.js to just using IndexedDBShim.js and then modify line #1467 to a more appropriate size that will meet all your storage needs, for example: ```var DEFAULT_DB_SIZE = 24 * 1024 * 1024```
|
||||
* [jasmine.async](https://github.com/derickbailey/jasmine.async.git) - library to help implementing tests of async functionality (used in tests)
|
||||
|
||||
* Non sub-module based library(s)
|
||||
* Non sub-module based libraries
|
||||
* [FileSaver.js](https://github.com/Esri/offline-editor-js/blob/master/lib/tiles/README.md) - library to assist with uploading and downloading of files containing tile information.
|
||||
* [grunt-manifest](https://github.com/gunta/grunt-manifest) node.js library to assist with the creation of manifest files.
|
||||
* [zip](http://gildas-lormeau.github.io/zip.js/) A library for zipping and unzipping files.
|
||||
* [xml2json](https://code.google.com/p/x2js/) A library for converting XML to JSON. Seems to handle complex XML.
|
||||
|
||||
## Resources
|
||||
|
||||
|
||||
68
doc/howtouseappcache.md
Normal file
68
doc/howtouseappcache.md
Normal file
@ -0,0 +1,68 @@
|
||||
Tips on using application cache
|
||||
===============================
|
||||
|
||||
If you have a requirement to reload your application or restart the browser while offline then you will need to use the [application cache](http://appcachefacts.info/). Some developers also use application caches to speed up page reload performance. For example, Google uses an application cache when load their main web page.
|
||||
|
||||
The application cache will allow you to store any file that is required for offline use. The list includes html files, JavaScript libraries, CSS and images. Any file that your application requires to run normally will have to be referenced in the application cache.
|
||||
|
||||
Once an application is stored in the application cache it will be available the next time an application restarts.
|
||||
|
||||
## Using AppCaches with your ArcGIS web app
|
||||
|
||||
**Step 1** Make sure you are using an optimized build of the ArcGIS API for JavaScript. You can create an optimized build at [http://jso.arcgis.com/](http://jso.arcgis.com/). This will create a single file that contains all the necessary modules for your app. There are options to host the build via CDN or locally. Either approach will work.
|
||||
|
||||
NOTE: You cannot use the regular CDN for the ArcGIS API for JavaScript because the URL contains a redirect. Redirects are not allowed in an application cache and it will fail to load.
|
||||
|
||||
**Step 2** Create the application cache file. We have a [Grunt.js](http://gruntjs.com/) task included in the /samples directory to assist with this step. You will need to make some adjustments to the package.json file. It acts as the configuration file for the Grunt task.
|
||||
|
||||
**Step 3** Reference the application cache file from within your application. Here's an example of the syntax:
|
||||
|
||||
```html
|
||||
<html manifest="appcache-features.appcache">
|
||||
|
||||
```
|
||||
|
||||
**Step 4** Be sure to include and use the `/utils/appCacheManager.js` library as a module in your application. This will enable you to monitor what's going on in the application cache and capture specific events. Here is a psuedo code example of how to instantiate it:
|
||||
|
||||
```js
|
||||
|
||||
appCacheManager = new AppCacheManager(true,true);
|
||||
appCacheManager.on(appCacheManager.CACHE_EVENT,cacheEventHandler);
|
||||
appCacheManager.on(appCacheManager.CACHE_ERROR,cacheErrorHandler);
|
||||
appCacheManager.on(appCacheManager.CACHE_LOADED,cacheLoadedHandler);
|
||||
|
||||
```
|
||||
|
||||
In the `/samples` directory there are two examples, `appcache-features.html` and `appcache-tiles.html` that demonstrate how to use tiles, features and the appCacheManager with the application cache.
|
||||
|
||||
###Configuring your web server
|
||||
Your web server must be able to serve up the MIME TYPE `TEXT/cache-manifest`. If this is missing there's a really good chance that the application cache file won't be served up to your app.
|
||||
|
||||
### Clearing the application cache in a browser
|
||||
|
||||
When you do testing with an application cache, any time you make a change to your application HTML, CSS or JS you will need to delete the existing application cache. Otherwise, any changes you make will not be reflected in the app.
|
||||
|
||||
**Simply deleting your web cache the normal way won't clear an application cache!**
|
||||
|
||||
In Chrome you can navigate to chrome://appcache-internals/ then select the appropriate cache and delete it. If you are testing on an Android device you can remotely debug from your laptop's Chrome instance.
|
||||
|
||||
In Safari iPhone and iPad go to settings and select "Clear Cookies and Data."
|
||||
|
||||
If you want to test on Firefox then try Tools > Options > Advanced > Network > Offline data > Clear Now. More info is available [here](https://developer.mozilla.org/en-US/docs/Web/HTML/Using_the_application_cache#Storage_location_and_clearing_the_offline_cache).
|
||||
|
||||
As for IE, this library doesn't currently support any versions.
|
||||
|
||||
### Where to place the file
|
||||
|
||||
The application cache file can live anywhere in your web directory. It's common to see it to be placed in the root.
|
||||
|
||||
###Support
|
||||
Most modern browsers support application cache including IE v10 and v11, Firefox v28+, Chrome v33+, Safari v7+, iOS Safari v3.2+, and Android browser 2.1+. For more detailed info refer to [caniuse.com](http://caniuse.com/#search=appcache).
|
||||
|
||||
### References
|
||||
|
||||
[Support for application cache](http://caniuse.com/#search=appcache)
|
||||
|
||||
[Appcache Facts](http://appcachefacts.info/)
|
||||
|
||||
[Using the application cache - Mozilla Developer Network](https://developer.mozilla.org/en-US/docs/Web/HTML/Using_the_application_cache)
|
||||
@ -3,9 +3,14 @@ How to use the tiles library
|
||||
|
||||
##`tiles` library
|
||||
|
||||
|
||||
The `tiles` library allows a developer to extend a tiled layer with offline support.
|
||||
|
||||
There are two approaches to using this set of libraries. The first approach is if you are using an ArcGIS.com Web Map, and the second approach is if you need to be able to restart or reload your application offline.
|
||||
|
||||
## Approach 1 - ArcGIS.com Map
|
||||
|
||||
Approach #1 is best for partial offline use cases and it uses the `offlineTilesEnabler.js` library. This approach will not allow you to reload or restart the application.
|
||||
|
||||
**Step 1** Configure paths for dojo loader to find the tiles and vendor modules (you need to set paths relative to the location of your html document), before loading ArcGIS JavaScript API
|
||||
|
||||
```html
|
||||
@ -19,7 +24,7 @@ The `tiles` library allows a developer to extend a tiled layer with offline supp
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<script src="//js.arcgis.com/3.7compact"></script>
|
||||
<script src="//js.arcgis.com/3.8compact"></script>
|
||||
|
||||
```
|
||||
|
||||
@ -124,3 +129,72 @@ It calculates the geographic boundary of each of the tiles stored in the indexed
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Approach #2 - Custom TileLayer
|
||||
|
||||
This approach is best if you have requirements for restarting or reloading your application while offline. For this approach use the `OfflineTilesEnablerLayer.js` library. This library extends TileMapServiceLayer and you can use it with any Esri tiled basemap layer.
|
||||
|
||||
|
||||
**Step 1** Configure paths for dojo loader to find the tiles and vendor modules (you need to set paths relative to the location of your html document), before loading ArcGIS JavaScript API
|
||||
|
||||
```html
|
||||
<script>
|
||||
// configure paths BEFORE loading arcgis or dojo libs
|
||||
var locationPath = location.pathname.replace(/\/[^/]+$/, "");
|
||||
var dojoConfig = {
|
||||
paths: {
|
||||
tiles: locationPath + "/../../lib/tiles",
|
||||
vendor: locationPath + "/../../vendor"
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<script src="//js.arcgis.com/3.8compact"></script>
|
||||
|
||||
```
|
||||
|
||||
**Step 2** Include the `tiles/offlineTilesEnabler` library in your app.
|
||||
|
||||
```js
|
||||
require([
|
||||
"esri/map",
|
||||
"tiles/OfflineTilesEnablerLayer"],
|
||||
function(Map,OfflineTilesEnablerLayer)
|
||||
{
|
||||
...
|
||||
});
|
||||
```
|
||||
|
||||
**Step 3** Create a new instance of `OfflineTilesEnablerLayer`. Note, when you instantiate the `Map` leave off the `basemap` property because we are adding a customer tile layer as our basemap. `OfflineTilesEnablerLayer` has three properties in the constructor. The first is the REST endpoint of the basemap you want to use, the second is the callback and the last is an optional parameter to preset the layer as online or offline. This will help with with drawing tiles correctly during offline restarts or reloads.
|
||||
|
||||
```js
|
||||
tileLayer = new OfflineTilesEnablerLayer("http://server.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer",function(evt){
|
||||
console.log("Tile Layer Loaded.");
|
||||
},_isOnline);
|
||||
|
||||
var map = new Map("map",{
|
||||
center: [-104.98,39.74], // long, lat
|
||||
zoom: 8,
|
||||
sliderStyle: "small"
|
||||
});
|
||||
|
||||
map.addLayer(tileLayer);
|
||||
|
||||
|
||||
```
|
||||
|
||||
All map events will continue to work normally. Although some methods that are typically available will now have to be accessed through the OfflineTilesEnablerLayer such as `getLevel()`, `getMaxZoom()`, and `getMinZoom()`.
|
||||
|
||||
To get the current extent you will need to monitor the `zoom-end` and `pan-end` events like this:
|
||||
|
||||
```js
|
||||
|
||||
|
||||
map.on("zoom-end",function(evt){
|
||||
_currentExtent = evt.extent;
|
||||
});
|
||||
|
||||
map.on("pan-end",function(evt){
|
||||
_currentExtent = evt.extent;
|
||||
});
|
||||
|
||||
```
|
||||
102
doc/howtousetpklibrary.md
Normal file
102
doc/howtousetpklibrary.md
Normal file
@ -0,0 +1,102 @@
|
||||
How to use the TPKLayer library
|
||||
===============================
|
||||
|
||||
## `TPKLayer` Library
|
||||
|
||||
The `TPKLayer` Library allows you to display at TPK file as a map.
|
||||
|
||||
**Step 1** Unzip the TPK file. This creates an array of Entry objects. Depending on your operating system you may have to rename the TPK file to .zip so that it becomes a recognized MIME type for the html input element.
|
||||
|
||||
```js
|
||||
|
||||
//IMPORTANT: Tell zip.js where to find its associated scripts
|
||||
zip.workerScriptsPath = locationPath + "/../lib/tpk/";
|
||||
zip.createReader(new zip.BlobReader(blob), function (zipReader) {
|
||||
zipReader.getEntries(function (entries) {
|
||||
initMap(entries);
|
||||
zipReader.close(function(evt){
|
||||
console.log("Done reading zip file.")
|
||||
})
|
||||
}, function (err) {
|
||||
alert("There was a problem reading the file!: " + err);
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
```
|
||||
**Step 2** Create a new instance of TPKLayer and pass the array of Entry objects from the zipReader into the `extend()` method's constructor. Then add the layer to the map. As soon as this code executes the layer will start parsing the TPK file.
|
||||
|
||||
|
||||
```js
|
||||
|
||||
tpkLayer = new TPKLayer();
|
||||
|
||||
//Listen for progress events to provide UX feedback
|
||||
tpkLayer.on("progress", function (evt) {
|
||||
evt == "start" ? loading.style.visibility = "visible" : loading.style.visibility = "hidden";
|
||||
})
|
||||
|
||||
tpkLayer.extend(entries);
|
||||
|
||||
map = new Map("map");
|
||||
map.addLayer(tpkLayer);
|
||||
|
||||
```
|
||||
|
||||
**Listen for errors**
|
||||
|
||||
It is a best practice to listen for the following events and handle them appropriately in the user interface.
|
||||
|
||||
```js
|
||||
|
||||
tpkLayer.on("validationEvent", function(evt){
|
||||
//evt.msg is the string message
|
||||
//evt.err is the error
|
||||
if(evt.msg == tpkLayer.NO_SUPPORT_ERROR){
|
||||
//Let the user know the library isn't supported.
|
||||
}
|
||||
})
|
||||
|
||||
tpkLayer.on("databaseErrorEvent", function(evt){
|
||||
//evt.msg is the string message
|
||||
//evt.err is the error
|
||||
if(evt.msg == tpkLayer.DB_INIT_ERROR){
|
||||
//Let the user know there was a db problem.
|
||||
}
|
||||
})
|
||||
|
||||
```
|
||||
|
||||
|
||||
**To clear the database**
|
||||
|
||||
When you need to delete all tiles from the existing data use the following pattern.
|
||||
|
||||
```js
|
||||
|
||||
tpkLayer.store.deleteAll(function(success,error){
|
||||
if(success){
|
||||
//do something
|
||||
}
|
||||
else{
|
||||
//let user know something went wrong
|
||||
}
|
||||
})
|
||||
|
||||
```
|
||||
**Additional Considerations**
|
||||
|
||||
There are a few things to keep in mind when working with TPK files and JavaScript.
|
||||
|
||||
The following three properties will affect the size of the TPK file. Minimizing all of these will help with application performance. Zoom as close into your area of interest as possible.
|
||||
|
||||
* Number of layers
|
||||
* Size of the extent
|
||||
* Minimum and maximum zoom level
|
||||
|
||||
It's a general recommended to keep the size of the local database below 75MBs, with a maximum of 100MBs for best performance. Allowing the database to grow to large can result in browser crashes and slow app performance.
|
||||
|
||||
The amount of memory allowed to the browser is dependant on many variables including the available device memory, other applications already running and the number of open browser tabs.
|
||||
|
||||
|
||||
|
||||
@ -1,30 +1,25 @@
|
||||
API Doc for offlineTilesEnabler
|
||||
===============================
|
||||
|
||||
There are two different libraries for taking tiles offline: offlineTilesEnabler.js and OfflineTilesEnablerLayer.js. The first one, offlineTilesEnabler.js, is for use with ArcGIS.com web maps and partial offline scenario. You won't be able to restart or reload your app when using this library.
|
||||
|
||||
If you have a requirement to allow restarting or reloading then you should use OfflineTilesEnablerLayer.js. The OfflineTilesEnablerLayer.js library lets you create a custom basemap layer that extends TiledMapServiceLayer. To view the docs for this library scroll down on this page.
|
||||
|
||||
##offlineTilesEnabler
|
||||
Extends and overrides a tiled map service. Provides the ability to customize the extent used to cut the tiles. See the detailed description of basemap.prepareForOffline() in the "How To Use" section below to learn different options.
|
||||
Extends and overrides a tiled map service. For use with ArcGIS.com maps or partial-offline situations that don't require a browser restart or reload.
|
||||
|
||||
Provides the ability to customize the extent used to cut the tiles. See the detailed description of basemap.prepareForOffline() in the "How To Use" section below to learn different options.
|
||||
|
||||
###Constructor
|
||||
Constructor | Description
|
||||
--- | ---
|
||||
`OfflineTilesEnabler()` | Creates an instance of the offlineTilesEnabler class. This library allows you to extend an ArcGISTiledMapServiceLayer with offline capability as well as manage the online/offline resynchronization process.
|
||||
`OfflineTilesEnabler()` | Creates an instance of the offlineTilesEnabler class. This library allows you to extend an ArcGISTiledMapServiceLayer with partial offline capability as well as manage the online/offline resynchronization process.
|
||||
|
||||
|
||||
###Methods
|
||||
Methods | Returns | Description
|
||||
--- | --- | ---
|
||||
`extend(layer, callback)`|`callback(boolean, string)` |Overrides an ArcGISTiledMapServiceLayer. Callback is called after indexedDb store is initialized and informs the application whether it is indexedDb is supported or not.
|
||||
|
||||
###Properties
|
||||
Property | Description
|
||||
--- | ---
|
||||
`layer.offline.proxyPath`| For CORS enabled servers this can be set to `null`. The default is to use the internal resource-proxy path: `libs/offline-editor-js/resource-proxy/proxy.php.` Don't forget to check your proxy configuration to allow connections for all possible services that you might be using. More information on using proxies with ArcGIS can be found here: [https://developers.arcgis.com/javascript/jshelp/ags_proxy.html](https://developers.arcgis.com/javascript/jshelp/ags_proxy.html).
|
||||
|
||||
###ArcGISTiledMapServiceLayer Overrides
|
||||
|
||||
Methods | Returns | Description
|
||||
--- | --- | ---
|
||||
`getTileUrl(level, row, col)` | Url | Use the tile url's level, row and column. Retrieves tiles as requested by the ArcGIS API for JavaScript. If a tile is in cache it is returned. If it is not in cache then one is retrieved over the internet.
|
||||
`goOffline()` | nothing | This method puts the layer in offline mode. When in offline mode, the layer will not fetch any tile from the remote server. It will look up the tiles in the indexed db database and display them in the layer. If the tile can't be found in the local database it will show up blank (even if there is actual connectivity). The pair of methods `goOffline()` and `goOnline() `allows the developer to manually control the behaviour of the layer. Used in conjunction with the offline dectection library, you can put the layer in the appropriate mode when the offline condition changes.
|
||||
`goOnline()` | nothing | This method puts the layer in online mode. When in online mode, the layer will behave as regular layers, fetching all tiles from the remote server. If there is no internet connectivity the tiles may appear thanks to the browsers cache, but no attempt will be made to look up tiles in the local database.
|
||||
`getLevelEstimation(extent,` `level, tileSize)` | {level, tileCount, sizeBytes} | Returns an object that contains the number of tiles that would need to be downloaded for the specified extent and zoom level, and the estimated byte size of such tiles. This method is useful to give the user an indication of the required time and space before launching the actual download operation. The byte size estimation is very rough.
|
||||
@ -38,3 +33,44 @@ Methods | Returns | Description
|
||||
`estimateTileSize(callback)` | `callback(number)` | Retrieves one tile from a layer and then returns its size.
|
||||
`prepareForOffline(` `minLevel, maxLevel, extent, ` `reportProgress)` | `callback(number)` | Retrieves tiles and stores them in the local cache. See the "How To Use" section below to learn more about customizing the use of this method.
|
||||
|
||||
###Properties
|
||||
Property | Description
|
||||
--- | ---
|
||||
`layer.offline.proxyPath`| For CORS enabled servers this can be set to `null`. The default is to use the internal resource-proxy path: `libs/offline-editor-js/resource-proxy/proxy.php.` Don't forget to check your proxy configuration to allow connections for all possible services that you might be using. More information on using proxies with ArcGIS can be found here: [https://developers.arcgis.com/javascript/jshelp/ags_proxy.html](https://developers.arcgis.com/javascript/jshelp/ags_proxy.html).
|
||||
|
||||
|
||||
|
||||
##OfflineTilesEnablerLayer
|
||||
Extends and overrides a tiled map service. This library creates a custom tiled map layer. It can be used in situations where a browser restart or reload is required.
|
||||
|
||||
###Constructor
|
||||
Constructor | Description
|
||||
--- | ---
|
||||
`OfflineTilesEnablerLayer(url,callback,state)` | Creates an instance of the offlineTilesEnabler class. This library allows you to extend an ArcGISTiledMapServiceLayer with offline capability as well as manage the online/offline resynchronization process. Any Esri basemap REST endpoint should work. The state property is a boolean for specifying if the application is intializing the layer online (true) or offline (false). When you first load the map you should set this property to `true`.
|
||||
|
||||
|
||||
###Methods
|
||||
Methods | Returns | Description
|
||||
--- | --- | ---
|
||||
`goOffline()` | nothing | This method puts the layer in offline mode. When in offline mode, the layer will not fetch any tile from the remote server. It will look up the tiles in the indexed db database and display them in the layer. If the tile can't be found in the local database it will show up blank (even if there is actual connectivity). The pair of methods `goOffline()` and `goOnline() `allows the developer to manually control the behaviour of the layer. Used in conjunction with the offline dectection library, you can put the layer in the appropriate mode when the offline condition changes.
|
||||
`goOnline()` | nothing | This method puts the layer in online mode. When in online mode, the layer will behave as regular layers, fetching all tiles from the remote server. If there is no internet connectivity the tiles may appear thanks to the browsers cache, but no attempt will be made to look up tiles in the local database.
|
||||
`getLevelEstimation(extent,` `level, tileSize)` | {level, tileCount, sizeBytes} | Returns an object that contains the number of tiles that would need to be downloaded for the specified extent and zoom level, and the estimated byte size of such tiles. This method is useful to give the user an indication of the required time and space before launching the actual download operation. The byte size estimation is very rough.
|
||||
`getExtentBuffer(buffer,extent)`| Extent | Returns a new extent buffered by a given measurement that's based on map units. For example, if you are using mercator map projection then the buffer property would be in meters and the new extent would be returned in mercactor.
|
||||
`getTileUrlsByExtent(extent, level)` | Array | Returns an array of tile urls within a given map extent and zoom level.
|
||||
`deleteAllTiles(callback)` | `callback(boolean, errors)` | Clears the local cache of tiles.
|
||||
`getOfflineUsage(callback)` | `callback(size, error)` | Gets the size in bytes of the local tile cache.
|
||||
`getTilePolygons(callback)` | `callback(polygon, error)` | Gets polygons representing all cached tiles. This is helpful to give users a visual feedback of the current content of the tile cache.
|
||||
`saveToFile(filename, callback)` | `callback( boolean, error)` | Saves tile cache into a portable csv format.
|
||||
`loadFromFile(filename, callback)` | `callback( boolean, error)` | Reads a csv file into local tile cache.
|
||||
`estimateTileSize(callback)` | `callback(number)` | Retrieves one tile from a layer and then returns its size.
|
||||
`prepareForOffline(` `minLevel, maxLevel, extent, ` `reportProgress)` | `callback(number)` | Retrieves tiles and stores them in the local cache. See the "How To Use" section below to learn more about customizing the use of this method.
|
||||
`getMaxZoom(callback)` | `callback(number)` | Returns the maximum zoom level of the layer.
|
||||
`getMinZoom(callback)` | `callback(number)` | Returns the minimum zoom level of the layer.
|
||||
|
||||
|
||||
###Properties
|
||||
Property | Description
|
||||
--- | ---
|
||||
`layer.offline.proxyPath`| For CORS enabled servers this can be set to `null`. The default is to use the internal resource-proxy path: `libs/offline-editor-js/resource-proxy/proxy.php.` Don't forget to check your proxy configuration to allow connections for all possible services that you might be using. More information on using proxies with ArcGIS can be found here: [https://developers.arcgis.com/javascript/jshelp/ags_proxy.html](https://developers.arcgis.com/javascript/jshelp/ags_proxy.html).
|
||||
|
||||
|
||||
|
||||
48
doc/tpklayer.md
Normal file
48
doc/tpklayer.md
Normal file
@ -0,0 +1,48 @@
|
||||
API Doc for TPKLayer
|
||||
====================
|
||||
|
||||
##TPKLayer
|
||||
|
||||
Extends a tiled map service and provides the ability to display tiles from a .tpk (ArcGIS Tile Page).
|
||||
|
||||
###Constructor
|
||||
|
||||
Constructor | Description
|
||||
--- | ---
|
||||
`TPKLayer()` | Creates an instance of the TPKLayer class. This library allows you to extend a TiledMapServiceLayer for the purpose of displaying a TPK file as a map.
|
||||
|
||||
###Methods
|
||||
Methods | Returns | Description
|
||||
--- | --- | ---
|
||||
`extend(files)`| nothing | Overrides a TiledMapServiceLayer. Files is an array of Entry Objects derived from a zip (tpk) file parsed via zip.js. As soon as this method is called it will extract all the necessary information from the zip file and display the TPK as a map.
|
||||
`setMaxDBSize(size)`| nothing | (Optional) Let's you specify a maximum size in MBs for the local database. The default is 75MBs. Recommended maximum is 100MBs. Important: Making the database too large can result in browser crashes and slow application performance.
|
||||
`getDBSize(callback)`| `callback(size,err)` | Returns the size of local database in bytes or an error message. Calling this too often during parsing operations can affect application performance.
|
||||
`isDBWriteable(value)`| boolean | Default is true. Let's you programmatically allow or not allow the storing of tiles in the local database. This method can help you manage the size of the database. Use this in conjunction with `getDBSize()` on a map pan or zoom event listener. Tile retrieval times from images stored in the database are significantly faster than pulling images from the TPK.
|
||||
|
||||
###Properties
|
||||
Property | Value | Description
|
||||
--- | --- | ---
|
||||
`map` | Object | Refers to the main applications Esri.Map object.
|
||||
`store` | Object | Refers to the local database and hooks directly to its [functionality](offlinetilesenabler.md).
|
||||
`RECENTER_DELAY` | 350 | Default is 350ms. Millisecond delay before attempting to recenter the map after orientation changes. Note: adjusting this too high will cause annoying delays. Adjusting this to short and it may not fire properly within the application life cycle.
|
||||
`PROGRESS_START` | "start" | An event property indicated parsing has begun. Important for control of UX elements that provide user feedback during parsing.
|
||||
`PROGRESS_END` | "end" | An event property indicated parsing has finished. Important for control of UX elements that provide user feedback during parsing.
|
||||
`WINDOW_VALIDATED` | "windowValidated" | An event property indicated all window related functionality has been checked. Example: Window.File and Window.FileReader.
|
||||
`DB_VALIDATED` | "dbValidated" | An event property indicating all database checks have passed.
|
||||
`PARSING_ERROR` | "parsingError" | An event property indicating an error occured while parsing the TPK file.
|
||||
`DB_INIT_ERROR` | "dbInitError"| An event property indicating an error occurred while initializing the database.
|
||||
`NO_SUPPORT_ERROR` | "libNotSupportedError"| An event property indicating the library won't work on this browser.
|
||||
|
||||
###Events
|
||||
Event | Value | Description
|
||||
--- | --- | ---
|
||||
`DATABASE_ERROR_EVENT` | "databaseErrorEvent" | An error occured while reading or writing to the local database.
|
||||
`VALIDATION_EVENT` | "validationEvent" | An event related to various checks to insure library functionality is supported.
|
||||
`PROGRESS_EVENT` | "progress" | Event indicated progress status while parsing a TPK file. Parsing can take a while depending on how large the TPK is.
|
||||
|
||||
###TiledMapServiceLayer Override
|
||||
|
||||
Methods | Returns | Description
|
||||
--- | --- | ---
|
||||
`getTileUrl(level, row, col)` | String | Use the url's level, row and column to retrieve tiles as requested by the ArcGIS API for JavaScript. If a tile is in the local database it is returned. If it is not then the library parsing the TPK file for the appropriate tile image. If `isDBWriteable()` is set to true (default), then an image retrieved from the TPK will be written to the database. Tile retrieval times from images stored in the database are significantly faster than pulling images from the TPK.
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
"use strict";
|
||||
|
||||
/*global IDBKeyRange,indexedDB */
|
||||
define([], function()
|
||||
{
|
||||
"use strict";
|
||||
var AttachmentsStore = function()
|
||||
{
|
||||
this._db = null;
|
||||
@ -322,7 +322,7 @@ define([], function()
|
||||
|
||||
this._createLocalURL = function(attachmentFile)
|
||||
{
|
||||
return window.URL.createObjectURL(attachmentFile)
|
||||
return window.URL.createObjectURL(attachmentFile);
|
||||
};
|
||||
|
||||
this._revokeLocalURL = function(attachment)
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
"use strict";
|
||||
|
||||
define(["esri/graphic"], function(Graphic)
|
||||
{
|
||||
"use strict";
|
||||
/* private consts */
|
||||
var EDITS_QUEUE_KEY = "esriEditsQueue";
|
||||
var SEPARATOR = "|@|";
|
||||
@ -24,15 +23,15 @@ define(["esri/graphic"], function(Graphic)
|
||||
isSupported: function()
|
||||
{
|
||||
// http://stackoverflow.com/questions/11214404/how-to-detect-if-browser-supports-html5-local-storage
|
||||
var mod = 'esriLocalStorageTest';
|
||||
var mod = "esriLocalStorageTest";
|
||||
try {
|
||||
window.localStorage.setItem(mod, mod);
|
||||
window.localStorage.removeItem(mod);
|
||||
return true;
|
||||
} catch(e) {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
pushEdit: function(operation,layer,graphic)
|
||||
{
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
"use strict";
|
||||
|
||||
define([
|
||||
"edit/editsStore",
|
||||
@ -12,7 +11,7 @@ define([
|
||||
"dojo/dom-attr",
|
||||
"dojo/dom-style",
|
||||
"dojo/query",
|
||||
|
||||
"esri/config",
|
||||
"esri/layers/GraphicsLayer",
|
||||
"esri/graphic",
|
||||
"esri/symbols/SimpleMarkerSymbol",
|
||||
@ -21,24 +20,25 @@ define([
|
||||
"esri/urlUtils"],
|
||||
function(editsStore, AttachmentsStore,
|
||||
Evented,Deferred,all,declare,array,domAttr,domStyle,query,
|
||||
GraphicsLayer,Graphic,SimpleMarkerSymbol,SimpleLineSymbol,SimpleFillSymbol,urlUtils)
|
||||
esriConfig,GraphicsLayer,Graphic,SimpleMarkerSymbol,SimpleLineSymbol,SimpleFillSymbol,urlUtils)
|
||||
{
|
||||
"use strict";
|
||||
return declare([Evented],
|
||||
{
|
||||
_onlineStatus: "online",
|
||||
_featureLayers: {},
|
||||
|
||||
ONLINE: "online", // all edits will directly go to the server
|
||||
OFFLINE: "offline", // edits will be enqueued
|
||||
RECONNECTING: "reconnecting", // sending stored edits to the server
|
||||
OFFLINE: "offline", // edits will be enqueued
|
||||
RECONNECTING: "reconnecting", // sending stored edits to the server
|
||||
|
||||
// manager emits event when...
|
||||
events: {
|
||||
EDITS_SENT: 'edits-sent', // ...whenever any edit is actually sent to the server
|
||||
EDITS_ENQUEUED: 'edits-enqueued', // ...when an edit is enqueued (and not sent to the server)
|
||||
ALL_EDITS_SENT: 'all-edits-sent', // ...after going online and there are no pending edits in the queue
|
||||
ATTACHMENT_ENQUEUED: 'attachment-enqueued',
|
||||
ATTACHMENTS_SENT: 'attachments-sent'
|
||||
EDITS_SENT: "edits-sent", // ...whenever any edit is actually sent to the server
|
||||
EDITS_ENQUEUED: "edits-enqueued", // ...when an edit is enqueued (and not sent to the server)
|
||||
ALL_EDITS_SENT: "all-edits-sent", // ...after going online and there are no pending edits in the queue
|
||||
ATTACHMENT_ENQUEUED: "attachment-enqueued",
|
||||
ATTACHMENTS_SENT: "attachments-sent"
|
||||
},
|
||||
|
||||
/**
|
||||
@ -101,7 +101,7 @@ define([
|
||||
}
|
||||
return true;
|
||||
}
|
||||
alert('The File APIs are not fully supported in this browser.');
|
||||
alert("The File APIs are not fully supported in this browser.");
|
||||
return false;
|
||||
},
|
||||
|
||||
@ -487,7 +487,7 @@ define([
|
||||
*/
|
||||
goOffline: function()
|
||||
{
|
||||
console.log('going offline');
|
||||
console.log("offlineFeatureManager going offline");
|
||||
this._onlineStatus = this.OFFLINE;
|
||||
},
|
||||
|
||||
@ -498,7 +498,7 @@ define([
|
||||
*/
|
||||
goOnline: function(callback)
|
||||
{
|
||||
console.log('going online');
|
||||
console.log("offlineFeaturesManager going online");
|
||||
this._onlineStatus = this.RECONNECTING;
|
||||
this._replayStoredEdits(function(success,responses)
|
||||
{
|
||||
@ -540,7 +540,7 @@ define([
|
||||
var layer = this._featureLayers[ edit.layer ];
|
||||
var graphic = editsStore._deserialize(edit.graphic);
|
||||
var readableGraphic = graphic.geometry.type;
|
||||
var layerId = edit.layer.substring(edit.layer.lastIndexOf('/')+1);
|
||||
var layerId = edit.layer.substring(edit.layer.lastIndexOf("/")+1);
|
||||
if(layer)
|
||||
{
|
||||
readableGraphic += " [id=" + graphic.attributes[layer.objectIdField] + "]";
|
||||
@ -664,8 +664,9 @@ define([
|
||||
dfd.reject(err);
|
||||
};
|
||||
var proxy = esriConfig.defaults.io.proxyUrl || "";
|
||||
if(proxy !== "")
|
||||
proxy += "?"
|
||||
if(proxy !== ""){
|
||||
proxy += "?";
|
||||
}
|
||||
console.log("proxy:", proxy);
|
||||
oAjaxReq.open("post", proxy + attachment.featureId + "/addAttachment", true);
|
||||
var sBoundary = "---------------------------" + Date.now().toString(16);
|
||||
@ -777,7 +778,6 @@ define([
|
||||
case editsStore.ADD:
|
||||
/* impossible!! */
|
||||
throw("can't add the same feature twice!");
|
||||
break;
|
||||
case editsStore.UPDATE:
|
||||
layerEdits[ objectId ].graphic = edit.graphic;
|
||||
break;
|
||||
|
||||
58
lib/edit/restartOfflineFeaturesManager.js
Normal file
58
lib/edit/restartOfflineFeaturesManager.js
Normal file
@ -0,0 +1,58 @@
|
||||
/**
|
||||
* Helper library for handling features during browser restarts or reloads.
|
||||
*/
|
||||
define(["esri/graphic"], function(Graphic) {
|
||||
"use strict";
|
||||
|
||||
return {
|
||||
|
||||
/**
|
||||
* Converts an array of graphics/features into JSON
|
||||
* @param features
|
||||
* @param updateEndEvent
|
||||
* @param callback
|
||||
*/
|
||||
convertGraphicLayerToJSON: function(features,updateEndEvent,callback){
|
||||
var layerDefinition = {};
|
||||
layerDefinition.objectIdFieldName = updateEndEvent.target.objectIdField;
|
||||
layerDefinition.globalIdFieldName = updateEndEvent.target.globalIdField;
|
||||
layerDefinition.geometryType = updateEndEvent.target.geometryType;
|
||||
layerDefinition.spatialReference = updateEndEvent.target.spatialReference;
|
||||
layerDefinition.fields = updateEndEvent.target.fields;
|
||||
|
||||
var length = features.length;
|
||||
var jsonArray = [];
|
||||
for(var i=0; i < length; i++){
|
||||
var jsonGraphic = features[i].toJson();
|
||||
jsonArray.push(jsonGraphic);
|
||||
if(i == (length - 1)) {
|
||||
var featureJSON = JSON.stringify(jsonArray);
|
||||
var layerDefJSON = JSON.stringify(layerDefinition);
|
||||
callback(featureJSON,layerDefJSON);
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Create a featureDefinition
|
||||
* @param featureLayer
|
||||
* @param featuresArr
|
||||
* @param geometryType
|
||||
* @param callback
|
||||
*/
|
||||
getFeatureDefinition: function(/* Object */ featureLayer,/* Array */ featuresArr,/* String */ geometryType,callback){
|
||||
|
||||
var featureDefinition = {
|
||||
"layerDefinition":featureLayer,
|
||||
"featureSet":{
|
||||
"features": featuresArr,
|
||||
"geometryType": geometryType
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
callback(featureDefinition);
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -20,6 +20,8 @@
|
||||
<serverUrl url="http://sampleserver1b.arcgisonline.com/arcgisoutput/" matchAll="true"/>),
|
||||
<serverUrl url="http://sampleserver1c.arcgisonline.com/arcgisoutput/" matchAll="true"/>),
|
||||
<serverUrl url="http://services2.arcgis.com/" matchAll="true"/>)
|
||||
<serverUrl url="http://andygup.github.io" matchAll="true"/>
|
||||
<serverUrl url="http://esri.github.io" matchAll="true"/>
|
||||
</serverUrls>
|
||||
</ProxyConfig>
|
||||
|
||||
|
||||
699
lib/tiles/OfflineTilesEnablerLayer.js
Normal file
699
lib/tiles/OfflineTilesEnablerLayer.js
Normal file
File diff suppressed because one or more lines are too long
@ -1,5 +1,5 @@
|
||||
"use strict";
|
||||
|
||||
/*global indexedDB */
|
||||
/**
|
||||
* Library for handling the storing of map tiles in IndexedDB.
|
||||
*
|
||||
@ -8,6 +8,7 @@
|
||||
*/
|
||||
define([],function()
|
||||
{
|
||||
"use strict";
|
||||
var TilesStore = function()
|
||||
{
|
||||
/**
|
||||
@ -25,7 +26,7 @@ define([],function()
|
||||
*/
|
||||
this.isSupported = function(){
|
||||
|
||||
if(!window.indexedDB){
|
||||
if(!window.indexedDB && !window.openDatabase){
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -81,7 +82,7 @@ define([],function()
|
||||
request.onsuccess = function(event)
|
||||
{
|
||||
var result = event.target.result;
|
||||
if(!result)
|
||||
if(result == undefined)
|
||||
{
|
||||
callback(false,"not found");
|
||||
}
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
"use strict";
|
||||
|
||||
define([],function()
|
||||
{
|
||||
"use strict";
|
||||
var Base64Utils={};
|
||||
Base64Utils.outputTypes={
|
||||
// summary:
|
||||
@ -43,7 +42,7 @@ define([],function()
|
||||
s.push(String.fromCharCode((wa[i>>5]>>>(i%32))&mask));
|
||||
}
|
||||
return s.join(""); // string
|
||||
}
|
||||
};
|
||||
Base64Utils.wordToHex=function(/* word[] */wa){
|
||||
// summary:
|
||||
// convert an array of words to a hex tab
|
||||
@ -52,7 +51,7 @@ define([],function()
|
||||
s.push(h.charAt((wa[i>>2]>>((i%4)*8+4))&0xF)+h.charAt((wa[i>>2]>>((i%4)*8))&0xF));
|
||||
}
|
||||
return s.join(""); // string
|
||||
}
|
||||
};
|
||||
Base64Utils.wordToBase64=function(/* word[] */wa){
|
||||
// summary:
|
||||
// convert an array of words to base64 encoding, should be more efficient
|
||||
|
||||
File diff suppressed because one or more lines are too long
@ -1,70 +1,60 @@
|
||||
"use strict";
|
||||
|
||||
define([
|
||||
"esri/geometry"
|
||||
], function(geometry)
|
||||
{
|
||||
"esri/geometry/Polygon"
|
||||
], function (Polygon) {
|
||||
"use strict";
|
||||
var TilingScheme = function (layer) {
|
||||
this.tileInfo = layer.tileInfo;
|
||||
};
|
||||
|
||||
var TilingScheme = function(layer)
|
||||
{
|
||||
this.tileInfo = layer.tileInfo;
|
||||
};
|
||||
TilingScheme.prototype = {
|
||||
getCellIdFromXy: function (x, y, level) {
|
||||
var col = Math.floor((x - this.tileInfo.origin.x) / (this.tileInfo.cols * this.tileInfo.lods[level].resolution));
|
||||
var row = Math.floor((this.tileInfo.origin.y - y) / (this.tileInfo.rows * this.tileInfo.lods[level].resolution));
|
||||
return [col, row];
|
||||
},
|
||||
|
||||
TilingScheme.prototype =
|
||||
{
|
||||
getCellIdFromXy: function(x,y,level)
|
||||
{
|
||||
var col = Math.floor((x-this.tileInfo.origin.x) / (this.tileInfo.cols*this.tileInfo.lods[level].resolution));
|
||||
var row = Math.floor((this.tileInfo.origin.y-y) / (this.tileInfo.rows*this.tileInfo.lods[level].resolution));
|
||||
return [col,row];
|
||||
},
|
||||
getCellPolygonFromCellId: function (cellId, level) {
|
||||
var col1 = cellId[0];
|
||||
var row1 = cellId[1];
|
||||
var col2 = col1 + 1;
|
||||
var row2 = row1 + 1;
|
||||
|
||||
getCellPolygonFromCellId: function(cellId,level)
|
||||
{
|
||||
var col1 = cellId[0];
|
||||
var row1 = cellId[1];
|
||||
var col2 = col1+1;
|
||||
var row2 = row1+1;
|
||||
var x1 = this.tileInfo.origin.x + (col1 * this.tileInfo.cols * this.tileInfo.lods[level].resolution);
|
||||
var y1 = this.tileInfo.origin.y - (row1 * this.tileInfo.rows * this.tileInfo.lods[level].resolution);
|
||||
var x2 = this.tileInfo.origin.x + (col2 * this.tileInfo.cols * this.tileInfo.lods[level].resolution);
|
||||
var y2 = this.tileInfo.origin.y - (row2 * this.tileInfo.rows * this.tileInfo.lods[level].resolution);
|
||||
|
||||
var x1 = this.tileInfo.origin.x + (col1 * this.tileInfo.cols * this.tileInfo.lods[level].resolution);
|
||||
var y1 = this.tileInfo.origin.y - (row1 * this.tileInfo.rows * this.tileInfo.lods[level].resolution);
|
||||
var x2 = this.tileInfo.origin.x + (col2 * this.tileInfo.cols * this.tileInfo.lods[level].resolution);
|
||||
var y2 = this.tileInfo.origin.y - (row2 * this.tileInfo.rows * this.tileInfo.lods[level].resolution);
|
||||
var polygon = new Polygon(this.tileInfo.spatialReference);
|
||||
polygon.addRing([
|
||||
[x1, y1], // clockwise
|
||||
[x2, y1],
|
||||
[x2, y2],
|
||||
[x1, y2],
|
||||
[x1, y1]
|
||||
]);
|
||||
return polygon;
|
||||
},
|
||||
|
||||
var polygon = new geometry.Polygon(this.tileInfo.spatialReference);
|
||||
polygon.addRing([
|
||||
[x1,y1], // clockwise
|
||||
[x2,y1],
|
||||
[x2,y2],
|
||||
[x1,y2],
|
||||
[x1,y1]
|
||||
]);
|
||||
return polygon;
|
||||
},
|
||||
getAllCellIdsInExtent: function (extent, gridLevel) {
|
||||
var cellId0 = this.getCellIdFromXy(extent.xmin, extent.ymin, gridLevel);
|
||||
var cellId1 = this.getCellIdFromXy(extent.xmax, extent.ymax, gridLevel);
|
||||
|
||||
getAllCellIdsInExtent : function(extent, gridLevel)
|
||||
{
|
||||
var cellId0 = this.getCellIdFromXy(extent.xmin, extent.ymin, gridLevel);
|
||||
var cellId1 = this.getCellIdFromXy(extent.xmax, extent.ymax, gridLevel);
|
||||
var i, j;
|
||||
var i0 = Math.max(Math.min(cellId0[0], cellId1[0]), this.tileInfo.lods[gridLevel].startTileCol);
|
||||
var i1 = Math.min(Math.max(cellId0[0], cellId1[0]), this.tileInfo.lods[gridLevel].endTileCol);
|
||||
var j0 = Math.max(Math.min(cellId0[1], cellId1[1]), this.tileInfo.lods[gridLevel].startTileRow);
|
||||
var j1 = Math.min(Math.max(cellId0[1], cellId1[1]), this.tileInfo.lods[gridLevel].endTileRow);
|
||||
|
||||
var i,j;
|
||||
var i0 = Math.max(Math.min(cellId0[0], cellId1[0]), this.tileInfo.lods[gridLevel].startTileCol);
|
||||
var i1 = Math.min(Math.max(cellId0[0], cellId1[0]), this.tileInfo.lods[gridLevel].endTileCol);
|
||||
var j0 = Math.max(Math.min(cellId0[1], cellId1[1]), this.tileInfo.lods[gridLevel].startTileRow);
|
||||
var j1 = Math.min(Math.max(cellId0[1], cellId1[1]), this.tileInfo.lods[gridLevel].endTileRow);
|
||||
var cellIds = [];
|
||||
|
||||
var cellIds = [];
|
||||
for (i = i0; i <= i1; i++) {
|
||||
for (j = j0; j <= j1; j++) {
|
||||
cellIds.push([i, j]);
|
||||
}
|
||||
}
|
||||
return cellIds;
|
||||
}
|
||||
};
|
||||
|
||||
for(i=i0; i<=i1; i++)
|
||||
{
|
||||
for(j=j0; j<=j1; j++)
|
||||
{
|
||||
cellIds.push([i,j]);
|
||||
}
|
||||
}
|
||||
return cellIds;
|
||||
}
|
||||
};
|
||||
|
||||
return TilingScheme;
|
||||
return TilingScheme;
|
||||
});
|
||||
7
lib/tpk/README.md
Normal file
7
lib/tpk/README.md
Normal file
@ -0,0 +1,7 @@
|
||||
3rd Party Library Licensing
|
||||
===========================
|
||||
|
||||
|
||||
[xml2json](https://code.google.com/p/x2js/) A library for converting XML to JSON. Seems to handle complex XML is licensed under an Apache2 license.
|
||||
|
||||
[zip](http://gildas-lormeau.github.io/zip.js/) A lset of libraries for zipping and unzipping files is licensed as BSD [here](https://github.com/gildas-lormeau/zip.js).
|
||||
631
lib/tpk/TPKLayer.js
Normal file
631
lib/tpk/TPKLayer.js
Normal file
File diff suppressed because one or more lines are too long
105
lib/tpk/autoCenterMap.js
Normal file
105
lib/tpk/autoCenterMap.js
Normal file
@ -0,0 +1,105 @@
|
||||
define(["dojo/_base/declare"],function(declare){
|
||||
return declare(null,{
|
||||
|
||||
constructor:function(/* Map */ map,/* int */ delay){
|
||||
this.map = map;
|
||||
this._setPanListener();
|
||||
this._setZoomListener();
|
||||
this._setOrientationListener(delay);
|
||||
var centerPt = map.extent.getCenter();
|
||||
this._setCenterPt(centerPt.x,centerPt.y,map.spatialReference.wkid);
|
||||
},
|
||||
|
||||
/**
|
||||
* Activates the orientation listener and listens for native events.
|
||||
*/
|
||||
_setOrientationListener: function(delay){
|
||||
var supportsOrientationChange = "onorientationchange" in window,
|
||||
orientationEvent = supportsOrientationChange ? "orientationchange" : "resize";
|
||||
|
||||
window.addEventListener(orientationEvent, function(evt){
|
||||
this._centerMap(this,delay);
|
||||
}.bind(this), false);
|
||||
},
|
||||
|
||||
/**
|
||||
* Center the map based on locations pulled from local storage
|
||||
* @param context
|
||||
* @param delay
|
||||
* @private
|
||||
*/
|
||||
_centerMap: function(context,delay){
|
||||
|
||||
setTimeout(
|
||||
function(){
|
||||
var locationStr = context._getCenterPt().split(",");
|
||||
var wkid = context.map.spatialReference.wkid;
|
||||
var mapPt = null;
|
||||
|
||||
if(wkid == 4326){
|
||||
mapPt = new esri.geometry.Point(locationStr[1],locationStr[0]);
|
||||
}
|
||||
else if(wkid = 102100){
|
||||
mapPt = new esri.geometry.Point(locationStr[0],locationStr[1], new esri.SpatialReference({ wkid: wkid }));
|
||||
}
|
||||
context.map.centerAt(mapPt);
|
||||
}
|
||||
,delay);
|
||||
},
|
||||
|
||||
/**
|
||||
* Automatically sets new center point in local storage.
|
||||
*/
|
||||
_setPanListener: function(){
|
||||
this.map.on("pan-end",function(){
|
||||
var center = this.map.extent.getCenter();
|
||||
this._setCenterPt(center.x,center.y,this.map.spatialReference.wkid);
|
||||
}.bind(this))
|
||||
},
|
||||
|
||||
/**
|
||||
* Automatically sets new center point and zoom level in
|
||||
* local storage.
|
||||
*/
|
||||
_setZoomListener: function(){
|
||||
this.map.on("zoom-end",function(){
|
||||
var center = this.map.extent.getCenter();
|
||||
this._setCenterPt(center.x,center.y,this.map.spatialReference.wkid);
|
||||
this.map.setZoom(this.map.getZoom());
|
||||
}.bind(this))
|
||||
},
|
||||
|
||||
/**
|
||||
* Uses localStorage to save a location.
|
||||
* @param lat
|
||||
* @param lon
|
||||
* @param spatialReference
|
||||
*/
|
||||
_setCenterPt: function(lat,lon,spatialReference){
|
||||
localStorage.setItem("_centerPtX", lat);
|
||||
localStorage.setItem("_centerPtY", lon);
|
||||
localStorage.setItem("_spatialReference", spatialReference);
|
||||
},
|
||||
|
||||
/**
|
||||
* Pulls a saved location from localStorage
|
||||
* Requires that setCenterPt() has been set.
|
||||
* @returns String x,y,spatialReference
|
||||
*/
|
||||
_getCenterPt: function(){
|
||||
var value = null;
|
||||
|
||||
try{
|
||||
value = localStorage.getItem("_centerPtX") + "," + localStorage.getItem("_centerPtY") + "," +
|
||||
localStorage.getItem("_spatialReference");
|
||||
}
|
||||
catch(err)
|
||||
{
|
||||
console.log("getCenterFromLocalStorage: " + err.message);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
})
|
||||
})
|
||||
2163
lib/tpk/inflate.js
Executable file
2163
lib/tpk/inflate.js
Executable file
File diff suppressed because it is too large
Load Diff
557
lib/tpk/xml2json.js
Normal file
557
lib/tpk/xml2json.js
Normal file
@ -0,0 +1,557 @@
|
||||
/*
|
||||
Copyright 2011-2013 Abdulla Abdurakhmanov
|
||||
Original sources are available at https://code.google.com/p/x2js/
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
define([],function() {
|
||||
return function X2JS(config) {
|
||||
'use strict';
|
||||
|
||||
var VERSION = "1.1.5";
|
||||
|
||||
config = config || {};
|
||||
initConfigDefaults();
|
||||
initRequiredPolyfills();
|
||||
|
||||
function initConfigDefaults() {
|
||||
if (config.escapeMode === undefined) {
|
||||
config.escapeMode = true;
|
||||
}
|
||||
config.attributePrefix = config.attributePrefix || "_";
|
||||
config.arrayAccessForm = config.arrayAccessForm || "none";
|
||||
config.emptyNodeForm = config.emptyNodeForm || "text";
|
||||
if (config.enableToStringFunc === undefined) {
|
||||
config.enableToStringFunc = true;
|
||||
}
|
||||
config.arrayAccessFormPaths = config.arrayAccessFormPaths || [];
|
||||
if (config.skipEmptyTextNodesForObj === undefined) {
|
||||
config.skipEmptyTextNodesForObj = true;
|
||||
}
|
||||
if (config.stripWhitespaces === undefined) {
|
||||
config.stripWhitespaces = true;
|
||||
}
|
||||
config.datetimeAccessFormPaths = config.datetimeAccessFormPaths || [];
|
||||
}
|
||||
|
||||
var DOMNodeTypes = {
|
||||
ELEMENT_NODE: 1,
|
||||
TEXT_NODE: 3,
|
||||
CDATA_SECTION_NODE: 4,
|
||||
COMMENT_NODE: 8,
|
||||
DOCUMENT_NODE: 9
|
||||
};
|
||||
|
||||
function initRequiredPolyfills() {
|
||||
function pad(number) {
|
||||
var r = String(number);
|
||||
if (r.length === 1) {
|
||||
r = '0' + r;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
// Hello IE8-
|
||||
if (typeof String.prototype.trim !== 'function') {
|
||||
String.prototype.trim = function () {
|
||||
return this.replace(/^\s+|^\n+|(\s|\n)+$/g, '');
|
||||
}
|
||||
}
|
||||
if (typeof Date.prototype.toISOString !== 'function') {
|
||||
// Implementation from http://stackoverflow.com/questions/2573521/how-do-i-output-an-iso-8601-formatted-string-in-javascript
|
||||
Date.prototype.toISOString = function () {
|
||||
return this.getUTCFullYear()
|
||||
+ '-' + pad(this.getUTCMonth() + 1)
|
||||
+ '-' + pad(this.getUTCDate())
|
||||
+ 'T' + pad(this.getUTCHours())
|
||||
+ ':' + pad(this.getUTCMinutes())
|
||||
+ ':' + pad(this.getUTCSeconds())
|
||||
+ '.' + String((this.getUTCMilliseconds() / 1000).toFixed(3)).slice(2, 5)
|
||||
+ 'Z';
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function getNodeLocalName(node) {
|
||||
var nodeLocalName = node.localName;
|
||||
if (nodeLocalName == null) // Yeah, this is IE!!
|
||||
nodeLocalName = node.baseName;
|
||||
if (nodeLocalName == null || nodeLocalName == "") // =="" is IE too
|
||||
nodeLocalName = node.nodeName;
|
||||
return nodeLocalName;
|
||||
}
|
||||
|
||||
function getNodePrefix(node) {
|
||||
return node.prefix;
|
||||
}
|
||||
|
||||
function escapeXmlChars(str) {
|
||||
if (typeof(str) == "string")
|
||||
return str.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, ''').replace(/\//g, '/');
|
||||
else
|
||||
return str;
|
||||
}
|
||||
|
||||
function unescapeXmlChars(str) {
|
||||
return str.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, "'").replace(///g, '\/');
|
||||
}
|
||||
|
||||
function toArrayAccessForm(obj, childName, path) {
|
||||
switch (config.arrayAccessForm) {
|
||||
case "property":
|
||||
if (!(obj[childName] instanceof Array))
|
||||
obj[childName + "_asArray"] = [obj[childName]];
|
||||
else
|
||||
obj[childName + "_asArray"] = obj[childName];
|
||||
break;
|
||||
/*case "none":
|
||||
break;*/
|
||||
}
|
||||
|
||||
if (!(obj[childName] instanceof Array) && config.arrayAccessFormPaths.length > 0) {
|
||||
var idx = 0;
|
||||
for (; idx < config.arrayAccessFormPaths.length; idx++) {
|
||||
var arrayPath = config.arrayAccessFormPaths[idx];
|
||||
if (typeof arrayPath === "string") {
|
||||
if (arrayPath == path)
|
||||
break;
|
||||
}
|
||||
else if (arrayPath instanceof RegExp) {
|
||||
if (arrayPath.test(path))
|
||||
break;
|
||||
}
|
||||
else if (typeof arrayPath === "function") {
|
||||
if (arrayPath(obj, childName, path))
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (idx != config.arrayAccessFormPaths.length) {
|
||||
obj[childName] = [obj[childName]];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function fromXmlDateTime(prop) {
|
||||
// Implementation based up on http://stackoverflow.com/questions/8178598/xml-datetime-to-javascript-date-object
|
||||
// Improved to support full spec and optional parts
|
||||
var bits = prop.split(/[-T:+Z]/g);
|
||||
|
||||
var d = new Date(bits[0], bits[1] - 1, bits[2]);
|
||||
var secondBits = bits[5].split("\.");
|
||||
d.setHours(bits[3], bits[4], secondBits[0]);
|
||||
if (secondBits.length > 1)
|
||||
d.setMilliseconds(secondBits[1]);
|
||||
|
||||
// Get supplied time zone offset in minutes
|
||||
if (bits[6] && bits[7]) {
|
||||
var offsetMinutes = bits[6] * 60 + Number(bits[7]);
|
||||
var sign = /\d\d-\d\d:\d\d$/.test(prop) ? '-' : '+';
|
||||
|
||||
// Apply the sign
|
||||
offsetMinutes = 0 + (sign == '-' ? -1 * offsetMinutes : offsetMinutes);
|
||||
|
||||
// Apply offset and local timezone
|
||||
d.setMinutes(d.getMinutes() - offsetMinutes - d.getTimezoneOffset())
|
||||
}
|
||||
else if (prop.indexOf("Z", prop.length - 1) !== -1) {
|
||||
d = new Date(Date.UTC(d.getFullYear(), d.getMonth(), d.getDate(), d.getHours(), d.getMinutes(), d.getSeconds(), d.getMilliseconds()));
|
||||
}
|
||||
|
||||
// d is now a local time equivalent to the supplied time
|
||||
return d;
|
||||
}
|
||||
|
||||
function checkFromXmlDateTimePaths(value, childName, fullPath) {
|
||||
if (config.datetimeAccessFormPaths.length > 0) {
|
||||
var path = fullPath.split("\.#")[0];
|
||||
var idx = 0;
|
||||
for (; idx < config.datetimeAccessFormPaths.length; idx++) {
|
||||
var dtPath = config.datetimeAccessFormPaths[idx];
|
||||
if (typeof dtPath === "string") {
|
||||
if (dtPath == path)
|
||||
break;
|
||||
}
|
||||
else if (dtPath instanceof RegExp) {
|
||||
if (dtPath.test(path))
|
||||
break;
|
||||
}
|
||||
else if (typeof dtPath === "function") {
|
||||
if (dtPath(obj, childName, path))
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (idx != config.datetimeAccessFormPaths.length) {
|
||||
return fromXmlDateTime(value);
|
||||
}
|
||||
else
|
||||
return value;
|
||||
}
|
||||
else
|
||||
return value;
|
||||
}
|
||||
|
||||
function parseDOMChildren(node, path) {
|
||||
if (node.nodeType == DOMNodeTypes.DOCUMENT_NODE) {
|
||||
var result = new Object;
|
||||
var nodeChildren = node.childNodes;
|
||||
// Alternative for firstElementChild which is not supported in some environments
|
||||
for (var cidx = 0; cidx < nodeChildren.length; cidx++) {
|
||||
var child = nodeChildren.item(cidx);
|
||||
if (child.nodeType == DOMNodeTypes.ELEMENT_NODE) {
|
||||
var childName = getNodeLocalName(child);
|
||||
result[childName] = parseDOMChildren(child, childName);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
else if (node.nodeType == DOMNodeTypes.ELEMENT_NODE) {
|
||||
var result = new Object;
|
||||
result.__cnt = 0;
|
||||
|
||||
var nodeChildren = node.childNodes;
|
||||
|
||||
// Children nodes
|
||||
for (var cidx = 0; cidx < nodeChildren.length; cidx++) {
|
||||
var child = nodeChildren.item(cidx); // nodeChildren[cidx];
|
||||
var childName = getNodeLocalName(child);
|
||||
|
||||
if (child.nodeType != DOMNodeTypes.COMMENT_NODE) {
|
||||
result.__cnt++;
|
||||
if (result[childName] == null) {
|
||||
result[childName] = parseDOMChildren(child, path + "." + childName);
|
||||
toArrayAccessForm(result, childName, path + "." + childName);
|
||||
}
|
||||
else {
|
||||
if (result[childName] != null) {
|
||||
if (!(result[childName] instanceof Array)) {
|
||||
result[childName] = [result[childName]];
|
||||
toArrayAccessForm(result, childName, path + "." + childName);
|
||||
}
|
||||
}
|
||||
(result[childName])[result[childName].length] = parseDOMChildren(child, path + "." + childName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Attributes
|
||||
for (var aidx = 0; aidx < node.attributes.length; aidx++) {
|
||||
var attr = node.attributes.item(aidx); // [aidx];
|
||||
result.__cnt++;
|
||||
result[config.attributePrefix + attr.name] = attr.value;
|
||||
}
|
||||
|
||||
// Node namespace prefix
|
||||
var nodePrefix = getNodePrefix(node);
|
||||
if (nodePrefix != null && nodePrefix != "") {
|
||||
result.__cnt++;
|
||||
result.__prefix = nodePrefix;
|
||||
}
|
||||
|
||||
if (result["#text"] != null) {
|
||||
result.__text = result["#text"];
|
||||
if (result.__text instanceof Array) {
|
||||
result.__text = result.__text.join("\n");
|
||||
}
|
||||
if (config.escapeMode)
|
||||
result.__text = unescapeXmlChars(result.__text);
|
||||
if (config.stripWhitespaces)
|
||||
result.__text = result.__text.trim();
|
||||
delete result["#text"];
|
||||
if (config.arrayAccessForm == "property")
|
||||
delete result["#text_asArray"];
|
||||
result.__text = checkFromXmlDateTimePaths(result.__text, childName, path + "." + childName);
|
||||
}
|
||||
if (result["#cdata-section"] != null) {
|
||||
result.__cdata = result["#cdata-section"];
|
||||
delete result["#cdata-section"];
|
||||
if (config.arrayAccessForm == "property")
|
||||
delete result["#cdata-section_asArray"];
|
||||
}
|
||||
|
||||
if (result.__cnt == 1 && result.__text != null) {
|
||||
result = result.__text;
|
||||
}
|
||||
else if (result.__cnt == 0 && config.emptyNodeForm == "text") {
|
||||
result = '';
|
||||
}
|
||||
else if (result.__cnt > 1 && result.__text != null && config.skipEmptyTextNodesForObj) {
|
||||
if ((config.stripWhitespaces && result.__text == "") || (result.__text.trim() == "")) {
|
||||
delete result.__text;
|
||||
}
|
||||
}
|
||||
delete result.__cnt;
|
||||
|
||||
if (config.enableToStringFunc && (result.__text != null || result.__cdata != null )) {
|
||||
result.toString = function () {
|
||||
return (this.__text != null ? this.__text : '') + ( this.__cdata != null ? this.__cdata : '');
|
||||
};
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
else if (node.nodeType == DOMNodeTypes.TEXT_NODE || node.nodeType == DOMNodeTypes.CDATA_SECTION_NODE) {
|
||||
return node.nodeValue;
|
||||
}
|
||||
}
|
||||
|
||||
function startTag(jsonObj, element, attrList, closed) {
|
||||
var resultStr = "<" + ( (jsonObj != null && jsonObj.__prefix != null) ? (jsonObj.__prefix + ":") : "") + element;
|
||||
if (attrList != null) {
|
||||
for (var aidx = 0; aidx < attrList.length; aidx++) {
|
||||
var attrName = attrList[aidx];
|
||||
var attrVal = jsonObj[attrName];
|
||||
if (config.escapeMode)
|
||||
attrVal = escapeXmlChars(attrVal);
|
||||
resultStr += " " + attrName.substr(config.attributePrefix.length) + "='" + attrVal + "'";
|
||||
}
|
||||
}
|
||||
if (!closed)
|
||||
resultStr += ">";
|
||||
else
|
||||
resultStr += "/>";
|
||||
return resultStr;
|
||||
}
|
||||
|
||||
function endTag(jsonObj, elementName) {
|
||||
return "</" + (jsonObj.__prefix != null ? (jsonObj.__prefix + ":") : "") + elementName + ">";
|
||||
}
|
||||
|
||||
function endsWith(str, suffix) {
|
||||
return str.indexOf(suffix, str.length - suffix.length) !== -1;
|
||||
}
|
||||
|
||||
function jsonXmlSpecialElem(jsonObj, jsonObjField) {
|
||||
if ((config.arrayAccessForm == "property" && endsWith(jsonObjField.toString(), ("_asArray")))
|
||||
|| jsonObjField.toString().indexOf(config.attributePrefix) == 0
|
||||
|| jsonObjField.toString().indexOf("__") == 0
|
||||
|| (jsonObj[jsonObjField] instanceof Function))
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
function jsonXmlElemCount(jsonObj) {
|
||||
var elementsCnt = 0;
|
||||
if (jsonObj instanceof Object) {
|
||||
for (var it in jsonObj) {
|
||||
if (jsonXmlSpecialElem(jsonObj, it))
|
||||
continue;
|
||||
elementsCnt++;
|
||||
}
|
||||
}
|
||||
return elementsCnt;
|
||||
}
|
||||
|
||||
function parseJSONAttributes(jsonObj) {
|
||||
var attrList = [];
|
||||
if (jsonObj instanceof Object) {
|
||||
for (var ait in jsonObj) {
|
||||
if (ait.toString().indexOf("__") == -1 && ait.toString().indexOf(config.attributePrefix) == 0) {
|
||||
attrList.push(ait);
|
||||
}
|
||||
}
|
||||
}
|
||||
return attrList;
|
||||
}
|
||||
|
||||
function parseJSONTextAttrs(jsonTxtObj) {
|
||||
var result = "";
|
||||
|
||||
if (jsonTxtObj.__cdata != null) {
|
||||
result += "<![CDATA[" + jsonTxtObj.__cdata + "]]>";
|
||||
}
|
||||
|
||||
if (jsonTxtObj.__text != null) {
|
||||
if (config.escapeMode)
|
||||
result += escapeXmlChars(jsonTxtObj.__text);
|
||||
else
|
||||
result += jsonTxtObj.__text;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function parseJSONTextObject(jsonTxtObj) {
|
||||
var result = "";
|
||||
|
||||
if (jsonTxtObj instanceof Object) {
|
||||
result += parseJSONTextAttrs(jsonTxtObj);
|
||||
}
|
||||
else if (jsonTxtObj != null) {
|
||||
if (config.escapeMode)
|
||||
result += escapeXmlChars(jsonTxtObj);
|
||||
else
|
||||
result += jsonTxtObj;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function parseJSONArray(jsonArrRoot, jsonArrObj, attrList) {
|
||||
var result = "";
|
||||
if (jsonArrRoot.length == 0) {
|
||||
result += startTag(jsonArrRoot, jsonArrObj, attrList, true);
|
||||
}
|
||||
else {
|
||||
for (var arIdx = 0; arIdx < jsonArrRoot.length; arIdx++) {
|
||||
result += startTag(jsonArrRoot[arIdx], jsonArrObj, parseJSONAttributes(jsonArrRoot[arIdx]), false);
|
||||
result += parseJSONObject(jsonArrRoot[arIdx]);
|
||||
result += endTag(jsonArrRoot[arIdx], jsonArrObj);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function parseJSONObject(jsonObj) {
|
||||
var result = "";
|
||||
|
||||
var elementsCnt = jsonXmlElemCount(jsonObj);
|
||||
|
||||
if (elementsCnt > 0) {
|
||||
for (var it in jsonObj) {
|
||||
|
||||
if (jsonXmlSpecialElem(jsonObj, it))
|
||||
continue;
|
||||
|
||||
var subObj = jsonObj[it];
|
||||
|
||||
var attrList = parseJSONAttributes(subObj)
|
||||
|
||||
if (subObj == null || subObj == undefined) {
|
||||
result += startTag(subObj, it, attrList, true);
|
||||
}
|
||||
else if (subObj instanceof Object) {
|
||||
|
||||
if (subObj instanceof Array) {
|
||||
result += parseJSONArray(subObj, it, attrList);
|
||||
}
|
||||
else if (subObj instanceof Date) {
|
||||
result += startTag(subObj, it, attrList, false);
|
||||
result += subObj.toISOString();
|
||||
result += endTag(subObj, it);
|
||||
}
|
||||
else {
|
||||
var subObjElementsCnt = jsonXmlElemCount(subObj);
|
||||
if (subObjElementsCnt > 0 || subObj.__text != null || subObj.__cdata != null) {
|
||||
result += startTag(subObj, it, attrList, false);
|
||||
result += parseJSONObject(subObj);
|
||||
result += endTag(subObj, it);
|
||||
}
|
||||
else {
|
||||
result += startTag(subObj, it, attrList, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
result += startTag(subObj, it, attrList, false);
|
||||
result += parseJSONTextObject(subObj);
|
||||
result += endTag(subObj, it);
|
||||
}
|
||||
}
|
||||
}
|
||||
result += parseJSONTextObject(jsonObj);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
this.parseXmlString = function (xmlDocStr) {
|
||||
var isIEParser = window.ActiveXObject || "ActiveXObject" in window;
|
||||
if (xmlDocStr === undefined) {
|
||||
return null;
|
||||
}
|
||||
var xmlDoc;
|
||||
if (window.DOMParser) {
|
||||
var parser = new window.DOMParser();
|
||||
var parsererrorNS = null;
|
||||
// IE9+ now is here
|
||||
if (!isIEParser) {
|
||||
try {
|
||||
parsererrorNS = parser.parseFromString("INVALID", "text/xml").childNodes[0].namespaceURI;
|
||||
}
|
||||
catch (err) {
|
||||
parsererrorNS = null;
|
||||
}
|
||||
}
|
||||
try {
|
||||
xmlDoc = parser.parseFromString(xmlDocStr, "text/xml");
|
||||
if (parsererrorNS != null && xmlDoc.getElementsByTagNameNS(parsererrorNS, "parsererror").length > 0) {
|
||||
//throw new Error('Error parsing XML: '+xmlDocStr);
|
||||
xmlDoc = null;
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
xmlDoc = null;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// IE :(
|
||||
if (xmlDocStr.indexOf("<?") == 0) {
|
||||
xmlDocStr = xmlDocStr.substr(xmlDocStr.indexOf("?>") + 2);
|
||||
}
|
||||
xmlDoc = new ActiveXObject("Microsoft.XMLDOM");
|
||||
xmlDoc.async = "false";
|
||||
xmlDoc.loadXML(xmlDocStr);
|
||||
}
|
||||
return xmlDoc;
|
||||
};
|
||||
|
||||
this.asArray = function (prop) {
|
||||
if (prop instanceof Array)
|
||||
return prop;
|
||||
else
|
||||
return [prop];
|
||||
};
|
||||
|
||||
this.toXmlDateTime = function (dt) {
|
||||
if (dt instanceof Date)
|
||||
return dt.toISOString();
|
||||
else if (typeof(dt) === 'number')
|
||||
return new Date(dt).toISOString();
|
||||
else
|
||||
return null;
|
||||
};
|
||||
|
||||
this.asDateTime = function (prop) {
|
||||
if (typeof(prop) == "string") {
|
||||
return fromXmlDateTime(prop);
|
||||
}
|
||||
else
|
||||
return prop;
|
||||
};
|
||||
|
||||
this.xml2json = function (xmlDoc) {
|
||||
return parseDOMChildren(xmlDoc);
|
||||
};
|
||||
|
||||
this.xml_str2json = function (xmlDocStr) {
|
||||
var xmlDoc = this.parseXmlString(xmlDocStr);
|
||||
if (xmlDoc != null)
|
||||
return this.xml2json(xmlDoc);
|
||||
else
|
||||
return null;
|
||||
};
|
||||
|
||||
this.json2xml_str = function (jsonObj) {
|
||||
return parseJSONObject(jsonObj);
|
||||
};
|
||||
|
||||
this.json2xml = function (jsonObj) {
|
||||
var xmlDocStr = this.json2xml_str(jsonObj);
|
||||
return this.parseXmlString(xmlDocStr);
|
||||
};
|
||||
|
||||
this.getVersion = function () {
|
||||
return VERSION;
|
||||
};
|
||||
|
||||
}
|
||||
})
|
||||
814
lib/tpk/zip.js
Executable file
814
lib/tpk/zip.js
Executable file
@ -0,0 +1,814 @@
|
||||
/*
|
||||
Copyright (c) 2013 Gildas Lormeau. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in
|
||||
the documentation and/or other materials provided with the distribution.
|
||||
|
||||
3. The names of the authors may not be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
|
||||
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT,
|
||||
INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
|
||||
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
|
||||
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
define([],function() {
|
||||
var obj = this;
|
||||
|
||||
var ERR_BAD_FORMAT = "File format is not recognized.";
|
||||
var ERR_ENCRYPTED = "File contains encrypted entry.";
|
||||
var ERR_ZIP64 = "File is using Zip64 (4gb+ file size).";
|
||||
var ERR_READ = "Error while reading zip file.";
|
||||
var ERR_WRITE = "Error while writing zip file.";
|
||||
var ERR_WRITE_DATA = "Error while writing file data.";
|
||||
var ERR_READ_DATA = "Error while reading file data.";
|
||||
var ERR_DUPLICATED_NAME = "File already exists.";
|
||||
var CHUNK_SIZE = 512 * 1024;
|
||||
|
||||
var INFLATE_JS = "inflate.js";
|
||||
var DEFLATE_JS = "deflate.js";
|
||||
|
||||
var TEXT_PLAIN = "text/plain";
|
||||
|
||||
var MESSAGE_EVENT = "message";
|
||||
|
||||
var appendABViewSupported;
|
||||
try {
|
||||
appendABViewSupported = new Blob([ new DataView(new ArrayBuffer(0)) ]).size === 0;
|
||||
} catch (e) {
|
||||
}
|
||||
|
||||
function Crc32() {
|
||||
var crc = -1, that = this;
|
||||
that.append = function (data) {
|
||||
var offset, table = that.table;
|
||||
for (offset = 0; offset < data.length; offset++)
|
||||
crc = (crc >>> 8) ^ table[(crc ^ data[offset]) & 0xFF];
|
||||
};
|
||||
that.get = function () {
|
||||
return ~crc;
|
||||
};
|
||||
}
|
||||
|
||||
Crc32.prototype.table = (function () {
|
||||
var i, j, t, table = [];
|
||||
for (i = 0; i < 256; i++) {
|
||||
t = i;
|
||||
for (j = 0; j < 8; j++)
|
||||
if (t & 1)
|
||||
t = (t >>> 1) ^ 0xEDB88320;
|
||||
else
|
||||
t = t >>> 1;
|
||||
table[i] = t;
|
||||
}
|
||||
return table;
|
||||
})();
|
||||
|
||||
function blobSlice(blob, index, length) {
|
||||
if (blob.slice)
|
||||
return blob.slice(index, index + length);
|
||||
else if (blob.webkitSlice)
|
||||
return blob.webkitSlice(index, index + length);
|
||||
else if (blob.mozSlice)
|
||||
return blob.mozSlice(index, index + length);
|
||||
else if (blob.msSlice)
|
||||
return blob.msSlice(index, index + length);
|
||||
}
|
||||
|
||||
function getDataHelper(byteLength, bytes) {
|
||||
var dataBuffer, dataArray;
|
||||
dataBuffer = new ArrayBuffer(byteLength);
|
||||
dataArray = new Uint8Array(dataBuffer);
|
||||
if (bytes)
|
||||
dataArray.set(bytes, 0);
|
||||
return {
|
||||
buffer: dataBuffer,
|
||||
array: dataArray,
|
||||
view: new DataView(dataBuffer)
|
||||
};
|
||||
}
|
||||
|
||||
// Readers
|
||||
function Reader() {
|
||||
}
|
||||
|
||||
function TextReader(text) {
|
||||
var that = this, blobReader;
|
||||
|
||||
function init(callback, onerror) {
|
||||
var blob = new Blob([ text ], {
|
||||
type: TEXT_PLAIN
|
||||
});
|
||||
blobReader = new BlobReader(blob);
|
||||
blobReader.init(function () {
|
||||
that.size = blobReader.size;
|
||||
callback();
|
||||
}, onerror);
|
||||
}
|
||||
|
||||
function readUint8Array(index, length, callback, onerror) {
|
||||
blobReader.readUint8Array(index, length, callback, onerror);
|
||||
}
|
||||
|
||||
that.size = 0;
|
||||
that.init = init;
|
||||
that.readUint8Array = readUint8Array;
|
||||
}
|
||||
|
||||
TextReader.prototype = new Reader();
|
||||
TextReader.prototype.constructor = TextReader;
|
||||
|
||||
function Data64URIReader(dataURI) {
|
||||
var that = this, dataStart;
|
||||
|
||||
function init(callback) {
|
||||
var dataEnd = dataURI.length;
|
||||
while (dataURI.charAt(dataEnd - 1) == "=")
|
||||
dataEnd--;
|
||||
dataStart = dataURI.indexOf(",") + 1;
|
||||
that.size = Math.floor((dataEnd - dataStart) * 0.75);
|
||||
callback();
|
||||
}
|
||||
|
||||
function readUint8Array(index, length, callback) {
|
||||
var i, data = getDataHelper(length);
|
||||
var start = Math.floor(index / 3) * 4;
|
||||
var end = Math.ceil((index + length) / 3) * 4;
|
||||
var bytes = obj.atob(dataURI.substring(start + dataStart, end + dataStart));
|
||||
var delta = index - Math.floor(start / 4) * 3;
|
||||
for (i = delta; i < delta + length; i++)
|
||||
data.array[i - delta] = bytes.charCodeAt(i);
|
||||
callback(data.array);
|
||||
}
|
||||
|
||||
that.size = 0;
|
||||
that.init = init;
|
||||
that.readUint8Array = readUint8Array;
|
||||
}
|
||||
|
||||
Data64URIReader.prototype = new Reader();
|
||||
Data64URIReader.prototype.constructor = Data64URIReader;
|
||||
|
||||
function BlobReader(blob) {
|
||||
var that = this;
|
||||
|
||||
function init(callback) {
|
||||
this.size = blob.size;
|
||||
callback();
|
||||
}
|
||||
|
||||
function readUint8Array(index, length, callback, onerror) {
|
||||
var reader = new FileReader();
|
||||
reader.onload = function (e) {
|
||||
callback(new Uint8Array(e.target.result));
|
||||
};
|
||||
reader.onerror = onerror;
|
||||
reader.readAsArrayBuffer(blobSlice(blob, index, length));
|
||||
}
|
||||
|
||||
that.size = 0;
|
||||
that.init = init;
|
||||
that.readUint8Array = readUint8Array;
|
||||
}
|
||||
|
||||
BlobReader.prototype = new Reader();
|
||||
BlobReader.prototype.constructor = BlobReader;
|
||||
|
||||
// Writers
|
||||
|
||||
function Writer() {
|
||||
}
|
||||
|
||||
Writer.prototype.getData = function (callback) {
|
||||
callback(this.data);
|
||||
};
|
||||
|
||||
//Added by Andy G. Tracking token
|
||||
function TextWriter(token,encoding) {
|
||||
var that = this, blob;
|
||||
|
||||
function init(callback) {
|
||||
blob = new Blob([], {
|
||||
type: TEXT_PLAIN
|
||||
});
|
||||
callback();
|
||||
}
|
||||
|
||||
function writeUint8Array(array, callback) {
|
||||
blob = new Blob([ blob, appendABViewSupported ? array : array.buffer ], {
|
||||
type: TEXT_PLAIN
|
||||
});
|
||||
callback();
|
||||
}
|
||||
|
||||
function getData(callback, onerror) {
|
||||
var reader = new FileReader();
|
||||
reader.onload = function (e) {
|
||||
var obj = {string: e.target.result,token:token};
|
||||
callback(obj);
|
||||
};
|
||||
reader.onerror = onerror;
|
||||
reader.readAsText(blob, encoding);
|
||||
}
|
||||
|
||||
that.init = init;
|
||||
that.writeUint8Array = writeUint8Array;
|
||||
that.getData = getData;
|
||||
}
|
||||
|
||||
TextWriter.prototype = new Writer();
|
||||
TextWriter.prototype.constructor = TextWriter;
|
||||
|
||||
function Data64URIWriter(contentType) {
|
||||
var that = this, data = "", pending = "";
|
||||
|
||||
function init(callback) {
|
||||
data += "data:" + (contentType || "") + ";base64,";
|
||||
callback();
|
||||
}
|
||||
|
||||
function writeUint8Array(array, callback) {
|
||||
var i, delta = pending.length, dataString = pending;
|
||||
pending = "";
|
||||
for (i = 0; i < (Math.floor((delta + array.length) / 3) * 3) - delta; i++)
|
||||
dataString += String.fromCharCode(array[i]);
|
||||
for (; i < array.length; i++)
|
||||
pending += String.fromCharCode(array[i]);
|
||||
if (dataString.length > 2)
|
||||
data += obj.btoa(dataString);
|
||||
else
|
||||
pending = dataString;
|
||||
callback();
|
||||
}
|
||||
|
||||
function getData(callback) {
|
||||
callback(data + obj.btoa(pending));
|
||||
}
|
||||
|
||||
that.init = init;
|
||||
that.writeUint8Array = writeUint8Array;
|
||||
that.getData = getData;
|
||||
}
|
||||
|
||||
Data64URIWriter.prototype = new Writer();
|
||||
Data64URIWriter.prototype.constructor = Data64URIWriter;
|
||||
|
||||
//Added by Andy G. Tracking token
|
||||
function BlobWriter(token,contentType) {
|
||||
var blob, that = this ;
|
||||
|
||||
function init(callback) {
|
||||
blob = new Blob([], {
|
||||
type: contentType
|
||||
});
|
||||
callback();
|
||||
}
|
||||
|
||||
function writeUint8Array(array, callback) {
|
||||
blob = new Blob([ blob, appendABViewSupported ? array : array.buffer ], {
|
||||
type: contentType
|
||||
});
|
||||
blob.token = token;
|
||||
callback();
|
||||
}
|
||||
|
||||
function getData(callback) {
|
||||
callback(blob);
|
||||
}
|
||||
|
||||
that.init = init;
|
||||
that.writeUint8Array = writeUint8Array;
|
||||
that.getData = getData;
|
||||
}
|
||||
|
||||
BlobWriter.prototype = new Writer();
|
||||
BlobWriter.prototype.constructor = BlobWriter;
|
||||
|
||||
// inflate/deflate core functions
|
||||
|
||||
function launchWorkerProcess(worker, reader, writer, offset, size, onappend, onprogress, onend, onreaderror, onwriteerror) {
|
||||
var chunkIndex = 0, index, outputSize;
|
||||
|
||||
function onflush() {
|
||||
worker.removeEventListener(MESSAGE_EVENT, onmessage, false);
|
||||
onend(outputSize);
|
||||
}
|
||||
|
||||
function onmessage(event) {
|
||||
var message = event.data, data = message.data;
|
||||
|
||||
if (message.onappend) {
|
||||
outputSize += data.length;
|
||||
writer.writeUint8Array(data, function () {
|
||||
onappend(false, data);
|
||||
step();
|
||||
}, onwriteerror);
|
||||
}
|
||||
if (message.onflush)
|
||||
if (data) {
|
||||
outputSize += data.length;
|
||||
writer.writeUint8Array(data, function () {
|
||||
onappend(false, data);
|
||||
onflush();
|
||||
}, onwriteerror);
|
||||
} else
|
||||
onflush();
|
||||
if (message.progress && onprogress)
|
||||
onprogress(index + message.current, size);
|
||||
}
|
||||
|
||||
function step() {
|
||||
index = chunkIndex * CHUNK_SIZE;
|
||||
if (index < size)
|
||||
reader.readUint8Array(offset + index, Math.min(CHUNK_SIZE, size - index), function (array) {
|
||||
worker.postMessage({
|
||||
append: true,
|
||||
data: array
|
||||
});
|
||||
chunkIndex++;
|
||||
if (onprogress)
|
||||
onprogress(index, size);
|
||||
onappend(true, array);
|
||||
}, onreaderror);
|
||||
else
|
||||
worker.postMessage({
|
||||
flush: true
|
||||
});
|
||||
}
|
||||
|
||||
outputSize = 0;
|
||||
worker.addEventListener(MESSAGE_EVENT, onmessage, false);
|
||||
step();
|
||||
}
|
||||
|
||||
function launchProcess(process, reader, writer, offset, size, onappend, onprogress, onend, onreaderror, onwriteerror) {
|
||||
var chunkIndex = 0, index, outputSize = 0;
|
||||
|
||||
function step() {
|
||||
var outputData;
|
||||
index = chunkIndex * CHUNK_SIZE;
|
||||
if (index < size)
|
||||
reader.readUint8Array(offset + index, Math.min(CHUNK_SIZE, size - index), function (inputData) {
|
||||
var outputData = process.append(inputData, function () {
|
||||
if (onprogress)
|
||||
onprogress(offset + index, size);
|
||||
});
|
||||
outputSize += outputData.length;
|
||||
onappend(true, inputData);
|
||||
writer.writeUint8Array(outputData, function () {
|
||||
onappend(false, outputData);
|
||||
chunkIndex++;
|
||||
setTimeout(step, 1);
|
||||
}, onwriteerror);
|
||||
if (onprogress)
|
||||
onprogress(index, size);
|
||||
}, onreaderror);
|
||||
else {
|
||||
outputData = process.flush();
|
||||
if (outputData) {
|
||||
outputSize += outputData.length;
|
||||
writer.writeUint8Array(outputData, function () {
|
||||
onappend(false, outputData);
|
||||
onend(outputSize);
|
||||
}, onwriteerror);
|
||||
} else
|
||||
onend(outputSize);
|
||||
}
|
||||
}
|
||||
|
||||
step();
|
||||
}
|
||||
|
||||
function inflate(reader, writer, offset, size, computeCrc32, onend, onprogress, onreaderror, onwriteerror) {
|
||||
var worker, crc32 = new Crc32();
|
||||
|
||||
function oninflateappend(sending, array) {
|
||||
if (computeCrc32 && !sending)
|
||||
crc32.append(array);
|
||||
}
|
||||
|
||||
function oninflateend(outputSize) {
|
||||
onend(outputSize, crc32.get());
|
||||
}
|
||||
|
||||
if (obj.zip.useWebWorkers) {
|
||||
worker = new Worker(obj.zip.workerScriptsPath + INFLATE_JS);
|
||||
launchWorkerProcess(worker, reader, writer, offset, size, oninflateappend, onprogress, oninflateend, onreaderror, onwriteerror);
|
||||
} else
|
||||
launchProcess(new obj.zip.Inflater(), reader, writer, offset, size, oninflateappend, onprogress, oninflateend, onreaderror, onwriteerror);
|
||||
return worker;
|
||||
}
|
||||
|
||||
function deflate(reader, writer, level, onend, onprogress, onreaderror, onwriteerror) {
|
||||
var worker, crc32 = new Crc32();
|
||||
|
||||
function ondeflateappend(sending, array) {
|
||||
if (sending)
|
||||
crc32.append(array);
|
||||
}
|
||||
|
||||
function ondeflateend(outputSize) {
|
||||
onend(outputSize, crc32.get());
|
||||
}
|
||||
|
||||
function onmessage() {
|
||||
worker.removeEventListener(MESSAGE_EVENT, onmessage, false);
|
||||
launchWorkerProcess(worker, reader, writer, 0, reader.size, ondeflateappend, onprogress, ondeflateend, onreaderror, onwriteerror);
|
||||
}
|
||||
|
||||
if (obj.zip.useWebWorkers) {
|
||||
worker = new Worker(obj.zip.workerScriptsPath + DEFLATE_JS);
|
||||
worker.addEventListener(MESSAGE_EVENT, onmessage, false);
|
||||
worker.postMessage({
|
||||
init: true,
|
||||
level: level
|
||||
});
|
||||
} else
|
||||
launchProcess(new obj.zip.Deflater(), reader, writer, 0, reader.size, ondeflateappend, onprogress, ondeflateend, onreaderror, onwriteerror);
|
||||
return worker;
|
||||
}
|
||||
|
||||
function copy(reader, writer, offset, size, computeCrc32, onend, onprogress, onreaderror, onwriteerror) {
|
||||
var chunkIndex = 0, crc32 = new Crc32();
|
||||
|
||||
function step() {
|
||||
var index = chunkIndex * CHUNK_SIZE;
|
||||
if (index < size)
|
||||
reader.readUint8Array(offset + index, Math.min(CHUNK_SIZE, size - index), function (array) {
|
||||
if (computeCrc32)
|
||||
crc32.append(array);
|
||||
if (onprogress)
|
||||
onprogress(index, size, array);
|
||||
writer.writeUint8Array(array, function () {
|
||||
chunkIndex++;
|
||||
step();
|
||||
}, onwriteerror);
|
||||
}, onreaderror);
|
||||
else
|
||||
onend(size, crc32.get());
|
||||
}
|
||||
|
||||
step();
|
||||
}
|
||||
|
||||
// ZipReader
|
||||
|
||||
function decodeASCII(str) {
|
||||
var i, out = "", charCode, extendedASCII = [ '\u00C7', '\u00FC', '\u00E9', '\u00E2', '\u00E4', '\u00E0', '\u00E5', '\u00E7', '\u00EA', '\u00EB',
|
||||
'\u00E8', '\u00EF', '\u00EE', '\u00EC', '\u00C4', '\u00C5', '\u00C9', '\u00E6', '\u00C6', '\u00F4', '\u00F6', '\u00F2', '\u00FB', '\u00F9',
|
||||
'\u00FF', '\u00D6', '\u00DC', '\u00F8', '\u00A3', '\u00D8', '\u00D7', '\u0192', '\u00E1', '\u00ED', '\u00F3', '\u00FA', '\u00F1', '\u00D1',
|
||||
'\u00AA', '\u00BA', '\u00BF', '\u00AE', '\u00AC', '\u00BD', '\u00BC', '\u00A1', '\u00AB', '\u00BB', '_', '_', '_', '\u00A6', '\u00A6',
|
||||
'\u00C1', '\u00C2', '\u00C0', '\u00A9', '\u00A6', '\u00A6', '+', '+', '\u00A2', '\u00A5', '+', '+', '-', '-', '+', '-', '+', '\u00E3',
|
||||
'\u00C3', '+', '+', '-', '-', '\u00A6', '-', '+', '\u00A4', '\u00F0', '\u00D0', '\u00CA', '\u00CB', '\u00C8', 'i', '\u00CD', '\u00CE',
|
||||
'\u00CF', '+', '+', '_', '_', '\u00A6', '\u00CC', '_', '\u00D3', '\u00DF', '\u00D4', '\u00D2', '\u00F5', '\u00D5', '\u00B5', '\u00FE',
|
||||
'\u00DE', '\u00DA', '\u00DB', '\u00D9', '\u00FD', '\u00DD', '\u00AF', '\u00B4', '\u00AD', '\u00B1', '_', '\u00BE', '\u00B6', '\u00A7',
|
||||
'\u00F7', '\u00B8', '\u00B0', '\u00A8', '\u00B7', '\u00B9', '\u00B3', '\u00B2', '_', ' ' ];
|
||||
for (i = 0; i < str.length; i++) {
|
||||
charCode = str.charCodeAt(i) & 0xFF;
|
||||
if (charCode > 127)
|
||||
out += extendedASCII[charCode - 128];
|
||||
else
|
||||
out += String.fromCharCode(charCode);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function decodeUTF8(string) {
|
||||
return decodeURIComponent(escape(string));
|
||||
}
|
||||
|
||||
function getString(bytes) {
|
||||
var i, str = "";
|
||||
for (i = 0; i < bytes.length; i++)
|
||||
str += String.fromCharCode(bytes[i]);
|
||||
return str;
|
||||
}
|
||||
|
||||
function getDate(timeRaw) {
|
||||
var date = (timeRaw & 0xffff0000) >> 16, time = timeRaw & 0x0000ffff;
|
||||
try {
|
||||
return new Date(1980 + ((date & 0xFE00) >> 9), ((date & 0x01E0) >> 5) - 1, date & 0x001F, (time & 0xF800) >> 11, (time & 0x07E0) >> 5,
|
||||
(time & 0x001F) * 2, 0);
|
||||
} catch (e) {
|
||||
}
|
||||
}
|
||||
|
||||
function readCommonHeader(entry, data, index, centralDirectory, onerror) {
|
||||
entry.version = data.view.getUint16(index, true);
|
||||
entry.bitFlag = data.view.getUint16(index + 2, true);
|
||||
entry.compressionMethod = data.view.getUint16(index + 4, true);
|
||||
entry.lastModDateRaw = data.view.getUint32(index + 6, true);
|
||||
entry.lastModDate = getDate(entry.lastModDateRaw);
|
||||
if ((entry.bitFlag & 0x01) === 0x01) {
|
||||
onerror(ERR_ENCRYPTED);
|
||||
return;
|
||||
}
|
||||
if (centralDirectory || (entry.bitFlag & 0x0008) != 0x0008) {
|
||||
entry.crc32 = data.view.getUint32(index + 10, true);
|
||||
entry.compressedSize = data.view.getUint32(index + 14, true);
|
||||
entry.uncompressedSize = data.view.getUint32(index + 18, true);
|
||||
}
|
||||
if (entry.compressedSize === 0xFFFFFFFF || entry.uncompressedSize === 0xFFFFFFFF) {
|
||||
onerror(ERR_ZIP64);
|
||||
return;
|
||||
}
|
||||
entry.filenameLength = data.view.getUint16(index + 22, true);
|
||||
entry.extraFieldLength = data.view.getUint16(index + 24, true);
|
||||
}
|
||||
|
||||
function createZipReader(reader, onerror) {
|
||||
function Entry() {
|
||||
}
|
||||
|
||||
Entry.prototype.getData = function (writer, onend, onprogress, checkCrc32) {
|
||||
var that = this, worker;
|
||||
|
||||
function terminate(callback, param) {
|
||||
if (worker)
|
||||
worker.terminate();
|
||||
worker = null;
|
||||
if (callback)
|
||||
callback(param);
|
||||
}
|
||||
|
||||
function testCrc32(crc32) {
|
||||
var dataCrc32 = getDataHelper(4);
|
||||
dataCrc32.view.setUint32(0, crc32);
|
||||
return that.crc32 == dataCrc32.view.getUint32(0);
|
||||
}
|
||||
|
||||
function getWriterData(uncompressedSize, crc32) {
|
||||
if (checkCrc32 && !testCrc32(crc32))
|
||||
onreaderror();
|
||||
else
|
||||
writer.getData(function (data) {
|
||||
terminate(onend, data);
|
||||
});
|
||||
}
|
||||
|
||||
function onreaderror() {
|
||||
terminate(onerror, ERR_READ_DATA);
|
||||
}
|
||||
|
||||
function onwriteerror() {
|
||||
terminate(onerror, ERR_WRITE_DATA);
|
||||
}
|
||||
|
||||
reader.readUint8Array(that.offset, 30, function (bytes) {
|
||||
var data = getDataHelper(bytes.length, bytes), dataOffset;
|
||||
if (data.view.getUint32(0) != 0x504b0304) {
|
||||
onerror(ERR_BAD_FORMAT);
|
||||
return;
|
||||
}
|
||||
readCommonHeader(that, data, 4, false, onerror);
|
||||
dataOffset = that.offset + 30 + that.filenameLength + that.extraFieldLength;
|
||||
writer.init(function () {
|
||||
if (that.compressionMethod === 0)
|
||||
copy(reader, writer, dataOffset, that.compressedSize, checkCrc32, getWriterData, onprogress, onreaderror, onwriteerror);
|
||||
else
|
||||
worker = inflate(reader, writer, dataOffset, that.compressedSize, checkCrc32, getWriterData, onprogress, onreaderror, onwriteerror);
|
||||
}, onwriteerror);
|
||||
}, onreaderror);
|
||||
};
|
||||
|
||||
function seekEOCDR(offset, entriesCallback) {
|
||||
reader.readUint8Array(reader.size - offset, offset, function (bytes) {
|
||||
var dataView = getDataHelper(bytes.length, bytes).view;
|
||||
if (dataView.getUint32(0) != 0x504b0506) {
|
||||
seekEOCDR(offset + 1, entriesCallback);
|
||||
} else {
|
||||
entriesCallback(dataView);
|
||||
}
|
||||
}, function () {
|
||||
onerror(ERR_READ);
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
getEntries: function (callback) {
|
||||
if (reader.size < 22) {
|
||||
onerror(ERR_BAD_FORMAT);
|
||||
return;
|
||||
}
|
||||
// look for End of central directory record
|
||||
seekEOCDR(22, function (dataView) {
|
||||
var datalength, fileslength;
|
||||
datalength = dataView.getUint32(16, true);
|
||||
fileslength = dataView.getUint16(8, true);
|
||||
reader.readUint8Array(datalength, reader.size - datalength, function (bytes) {
|
||||
var i, index = 0, entries = [], entry, filename, comment, data = getDataHelper(bytes.length, bytes);
|
||||
for (i = 0; i < fileslength; i++) {
|
||||
entry = new Entry();
|
||||
if (data.view.getUint32(index) != 0x504b0102) {
|
||||
onerror(ERR_BAD_FORMAT);
|
||||
return;
|
||||
}
|
||||
readCommonHeader(entry, data, index + 6, true, onerror);
|
||||
entry.commentLength = data.view.getUint16(index + 32, true);
|
||||
entry.directory = ((data.view.getUint8(index + 38) & 0x10) == 0x10);
|
||||
entry.offset = data.view.getUint32(index + 42, true);
|
||||
filename = getString(data.array.subarray(index + 46, index + 46 + entry.filenameLength));
|
||||
entry.filename = ((entry.bitFlag & 0x0800) === 0x0800) ? decodeUTF8(filename) : decodeASCII(filename);
|
||||
if (!entry.directory && entry.filename.charAt(entry.filename.length - 1) == "/")
|
||||
entry.directory = true;
|
||||
comment = getString(data.array.subarray(index + 46 + entry.filenameLength + entry.extraFieldLength, index + 46
|
||||
+ entry.filenameLength + entry.extraFieldLength + entry.commentLength));
|
||||
entry.comment = ((entry.bitFlag & 0x0800) === 0x0800) ? decodeUTF8(comment) : decodeASCII(comment);
|
||||
entries.push(entry);
|
||||
index += 46 + entry.filenameLength + entry.extraFieldLength + entry.commentLength;
|
||||
}
|
||||
callback(entries);
|
||||
}, function () {
|
||||
onerror(ERR_READ);
|
||||
});
|
||||
});
|
||||
},
|
||||
close: function (callback) {
|
||||
if (callback)
|
||||
callback();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// ZipWriter
|
||||
|
||||
function encodeUTF8(string) {
|
||||
return unescape(encodeURIComponent(string));
|
||||
}
|
||||
|
||||
function getBytes(str) {
|
||||
var i, array = [];
|
||||
for (i = 0; i < str.length; i++)
|
||||
array.push(str.charCodeAt(i));
|
||||
return array;
|
||||
}
|
||||
|
||||
function createZipWriter(writer, onerror, dontDeflate) {
|
||||
var worker, files = {}, filenames = [], datalength = 0;
|
||||
|
||||
function terminate(callback, message) {
|
||||
if (worker)
|
||||
worker.terminate();
|
||||
worker = null;
|
||||
if (callback)
|
||||
callback(message);
|
||||
}
|
||||
|
||||
function onwriteerror() {
|
||||
terminate(onerror, ERR_WRITE);
|
||||
}
|
||||
|
||||
function onreaderror() {
|
||||
terminate(onerror, ERR_READ_DATA);
|
||||
}
|
||||
|
||||
return {
|
||||
add: function (name, reader, onend, onprogress, options) {
|
||||
var header, filename, date;
|
||||
|
||||
function writeHeader(callback) {
|
||||
var data;
|
||||
date = options.lastModDate || new Date();
|
||||
header = getDataHelper(26);
|
||||
files[name] = {
|
||||
headerArray: header.array,
|
||||
directory: options.directory,
|
||||
filename: filename,
|
||||
offset: datalength,
|
||||
comment: getBytes(encodeUTF8(options.comment || ""))
|
||||
};
|
||||
header.view.setUint32(0, 0x14000808);
|
||||
if (options.version)
|
||||
header.view.setUint8(0, options.version);
|
||||
if (!dontDeflate && options.level !== 0 && !options.directory)
|
||||
header.view.setUint16(4, 0x0800);
|
||||
header.view.setUint16(6, (((date.getHours() << 6) | date.getMinutes()) << 5) | date.getSeconds() / 2, true);
|
||||
header.view.setUint16(8, ((((date.getFullYear() - 1980) << 4) | (date.getMonth() + 1)) << 5) | date.getDate(), true);
|
||||
header.view.setUint16(22, filename.length, true);
|
||||
data = getDataHelper(30 + filename.length);
|
||||
data.view.setUint32(0, 0x504b0304);
|
||||
data.array.set(header.array, 4);
|
||||
data.array.set(filename, 30);
|
||||
datalength += data.array.length;
|
||||
writer.writeUint8Array(data.array, callback, onwriteerror);
|
||||
}
|
||||
|
||||
function writeFooter(compressedLength, crc32) {
|
||||
var footer = getDataHelper(16);
|
||||
datalength += compressedLength || 0;
|
||||
footer.view.setUint32(0, 0x504b0708);
|
||||
if (typeof crc32 != "undefined") {
|
||||
header.view.setUint32(10, crc32, true);
|
||||
footer.view.setUint32(4, crc32, true);
|
||||
}
|
||||
if (reader) {
|
||||
footer.view.setUint32(8, compressedLength, true);
|
||||
header.view.setUint32(14, compressedLength, true);
|
||||
footer.view.setUint32(12, reader.size, true);
|
||||
header.view.setUint32(18, reader.size, true);
|
||||
}
|
||||
writer.writeUint8Array(footer.array, function () {
|
||||
datalength += 16;
|
||||
terminate(onend);
|
||||
}, onwriteerror);
|
||||
}
|
||||
|
||||
function writeFile() {
|
||||
options = options || {};
|
||||
name = name.trim();
|
||||
if (options.directory && name.charAt(name.length - 1) != "/")
|
||||
name += "/";
|
||||
if (files.hasOwnProperty(name)) {
|
||||
onerror(ERR_DUPLICATED_NAME);
|
||||
return;
|
||||
}
|
||||
filename = getBytes(encodeUTF8(name));
|
||||
filenames.push(name);
|
||||
writeHeader(function () {
|
||||
if (reader)
|
||||
if (dontDeflate || options.level === 0)
|
||||
copy(reader, writer, 0, reader.size, true, writeFooter, onprogress, onreaderror, onwriteerror);
|
||||
else
|
||||
worker = deflate(reader, writer, options.level, writeFooter, onprogress, onreaderror, onwriteerror);
|
||||
else
|
||||
writeFooter();
|
||||
}, onwriteerror);
|
||||
}
|
||||
|
||||
if (reader)
|
||||
reader.init(writeFile, onreaderror);
|
||||
else
|
||||
writeFile();
|
||||
},
|
||||
close: function (callback) {
|
||||
var data, length = 0, index = 0, indexFilename, file;
|
||||
for (indexFilename = 0; indexFilename < filenames.length; indexFilename++) {
|
||||
file = files[filenames[indexFilename]];
|
||||
length += 46 + file.filename.length + file.comment.length;
|
||||
}
|
||||
data = getDataHelper(length + 22);
|
||||
for (indexFilename = 0; indexFilename < filenames.length; indexFilename++) {
|
||||
file = files[filenames[indexFilename]];
|
||||
data.view.setUint32(index, 0x504b0102);
|
||||
data.view.setUint16(index + 4, 0x1400);
|
||||
data.array.set(file.headerArray, index + 6);
|
||||
data.view.setUint16(index + 32, file.comment.length, true);
|
||||
if (file.directory)
|
||||
data.view.setUint8(index + 38, 0x10);
|
||||
data.view.setUint32(index + 42, file.offset, true);
|
||||
data.array.set(file.filename, index + 46);
|
||||
data.array.set(file.comment, index + 46 + file.filename.length);
|
||||
index += 46 + file.filename.length + file.comment.length;
|
||||
}
|
||||
data.view.setUint32(index, 0x504b0506);
|
||||
data.view.setUint16(index + 8, filenames.length, true);
|
||||
data.view.setUint16(index + 10, filenames.length, true);
|
||||
data.view.setUint32(index + 12, length, true);
|
||||
data.view.setUint32(index + 16, datalength, true);
|
||||
writer.writeUint8Array(data.array, function () {
|
||||
terminate(function () {
|
||||
writer.getData(callback);
|
||||
});
|
||||
}, onwriteerror);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return obj.zip = {
|
||||
Reader: Reader,
|
||||
Writer: Writer,
|
||||
BlobReader: BlobReader,
|
||||
Data64URIReader: Data64URIReader,
|
||||
TextReader: TextReader,
|
||||
BlobWriter: BlobWriter,
|
||||
Data64URIWriter: Data64URIWriter,
|
||||
TextWriter: TextWriter,
|
||||
createReader: function (reader, callback, onerror) {
|
||||
reader.init(function () {
|
||||
callback(createZipReader(reader, onerror));
|
||||
}, onerror);
|
||||
},
|
||||
createWriter: function (writer, callback, onerror, dontDeflate) {
|
||||
writer.init(function () {
|
||||
callback(createZipWriter(writer, onerror, dontDeflate));
|
||||
}, onerror);
|
||||
},
|
||||
workerScriptsPath: "",
|
||||
useWebWorkers: true
|
||||
};
|
||||
|
||||
|
||||
});
|
||||
@ -20,42 +20,34 @@ module.exports = function(grunt) {
|
||||
options: {
|
||||
basePath: "./",
|
||||
cache: ["# <%= pkg.name %>, version: <%= pkg.version %>",
|
||||
"#",
|
||||
"# Home Page",
|
||||
"<%= pkg.appHomePage %>",
|
||||
"#",
|
||||
"# ArcGIS API for JavaScript files",
|
||||
"<%= pkg.optimizedApiURL %>/dojo/dojo.js",
|
||||
"<%= pkg.optimizedApiURL %>/dojo/main.js",
|
||||
"<%= pkg.optimizedApiURL %>/dojox/gfx/svg.js",
|
||||
"<%= pkg.optimizedApiURL %>/dojo/_base/browser.js",
|
||||
"<%= pkg.optimizedApiURL %>/dojox/gfx/shape.js",
|
||||
"<%= pkg.optimizedApiURL %>/dojo/nls/dojo_en-us.js",
|
||||
"<%= pkg.optimizedApiURL %>/dojox/gfx/shape.js",
|
||||
"<%= pkg.optimizedApiURL %>/dojox/gfx/path.js",
|
||||
"<%= pkg.optimizedApiURL %>/dojo/request.js",
|
||||
"<%= pkg.optimizedApiURL %>/dojo/_base/NodeList.js",
|
||||
"<%= pkg.optimizedApiURL %>/dojo/selector/acme.js",
|
||||
"<%= pkg.optimizedApiURL %>/dojo/_base/loader.js",
|
||||
"<%= pkg.optimizedApiURL %>/dojo/request/default.js",
|
||||
"<%= pkg.optimizedApiURL %>/esri/geometry.js",
|
||||
"<%= pkg.optimizedApiURL %>/esri/geometry/geodesicUtils.js",
|
||||
"<%= pkg.optimizedApiURL %>/esri/geometry/normalizeUtils.js",
|
||||
"<%= pkg.optimizedApiURL %>/esri/dijit/Attribution.js",
|
||||
"<%= pkg.optimizedApiURL %>/esri/units.js",
|
||||
"<%= pkg.optimizedApiURL %>/dojo/fx/Toggler.js",
|
||||
"#",
|
||||
"<%= pkg.arcGISBaseURL %>/js/esri/dijit/images/popup-sprite.png",
|
||||
"<%= pkg.arcGISBaseURL %>/js/esri/dijit/images/attribute_inspector_sprite.png",
|
||||
"<%= pkg.arcGISBaseURL %>/js/dojo/dojox/gfx/svg.js",
|
||||
"<%= pkg.arcGISBaseURL %>/js/dojo/dojo/resources/blank.gif",
|
||||
"<%= pkg.arcGISBaseURL %>/js/esri/dijit/images/ajax-loader.gif",
|
||||
"<%= pkg.arcGISBaseURL %>/js/esri/images/map/logo-sm.png",
|
||||
"<%= pkg.arcGISBaseURL %>/js/esri/images/map/logo-med.png",
|
||||
"<%= pkg.arcGISBaseURL %>/js/esri/css/esri.css",
|
||||
"<%= pkg.arcGISBaseURL %>/js/dojo/dijit/themes/claro/claro.css",
|
||||
"<%= pkg.arcGISBaseURL %>/js/esri/nls/jsapi_en-us.js",
|
||||
"#",
|
||||
"//services.arcgisonline.com/ArcGIS/rest/info?f=json",
|
||||
"//static.arcgis.com/attribution/World_Topo_Map?f=json",
|
||||
"//services.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer?f=json&callback=dojo.io.script.jsonp_dojoIoScript1._jsonpCallback",
|
||||
"#",
|
||||
"# required for web maps",
|
||||
"<%= pkg.arcGISBaseURL %>/js/esri/dijit/images/ajax-loader.gif",
|
||||
"<%= pkg.arcGISBaseURL %>/js/esri/dijit/images/popup.png",
|
||||
"# required custom libs",
|
||||
"#",
|
||||
"# required local html",
|
||||
"# /xyz/style.css",
|
||||
"# /img/1.png"],
|
||||
network: [
|
||||
@ -69,18 +61,19 @@ module.exports = function(grunt) {
|
||||
},
|
||||
src: [
|
||||
"*.html",
|
||||
/*"js/*.min.js",*/
|
||||
"samples/images/*.png",
|
||||
"vendor/IndexedDBShim/dist/*.min.js",
|
||||
"vendor/offline/offline.min.js",
|
||||
"lib/tiles/*.js",
|
||||
"lib/tiles/*.png",
|
||||
"lib/tiles/*.psd",
|
||||
"utils/*.js"
|
||||
/*
|
||||
"images/*",
|
||||
"css/*.css"
|
||||
*/
|
||||
"../samples/images/*.png",
|
||||
"../samples/css/*.css",
|
||||
"../vendor/IndexedDBShim/dist/*.js",
|
||||
"../vendor/offline/offline.min.js",
|
||||
"../lib/tiles/*.js",
|
||||
"../lib/tiles/*.png",
|
||||
"../lib/tiles/*.psd",
|
||||
"../lib/edit/*.js",
|
||||
"../utils/*.js"
|
||||
/*
|
||||
"images/*",
|
||||
"css/*.css"
|
||||
*/
|
||||
],
|
||||
dest: "<%= pkg.manifestName %>"
|
||||
}
|
||||
|
||||
@ -3,4 +3,19 @@
|
||||
|
||||
This library is used to help generate manifest files and it is licensed under the MIT/X11 license.
|
||||
|
||||
Visit [here](https://github.com/gunta/grunt-manifest/blob/master/LICENSE-MIT) for a copy of the license. Or visit @guntas repo at: [https://github.com/gunta/grunt-manifest](https://github.com/gunta/grunt-manifest)
|
||||
Visit [here](https://github.com/gunta/grunt-manifest/blob/master/LICENSE-MIT) for a copy of the license. Or visit @guntas repo at: [https://github.com/gunta/grunt-manifest](https://github.com/gunta/grunt-manifest)
|
||||
|
||||
|
||||
##zip.js
|
||||
========
|
||||
|
||||
A library for zipping and unzipping files.
|
||||
|
||||
Visit [here](https://github.com/gildas-lormeau/zip.js) for BSD licensing information.
|
||||
|
||||
##DataStream.js
|
||||
===============
|
||||
|
||||
A library for working with binary data.
|
||||
|
||||
No license information provided on the associated github [repo](https://github.com/kig/DataStream.js).
|
||||
@ -1,45 +1,37 @@
|
||||
CACHE MANIFEST
|
||||
# This manifest was generated by grunt-manifest HTML5 Cache Manifest Generator
|
||||
# Time: Tue Apr 29 2014 17:39:26 GMT-0600 (MDT)
|
||||
# Time: Wed Jun 11 2014 13:42:14 GMT-0600 (MDT)
|
||||
|
||||
CACHE:
|
||||
# manifest-generator, version: 0.0.1
|
||||
#
|
||||
# Home Page
|
||||
appcache-features.html
|
||||
#
|
||||
# ArcGIS API for JavaScript files
|
||||
http://jsdev.arcgis.com/o/andygup/appcache1/dojo/dojo.js
|
||||
http://jsdev.arcgis.com/o/andygup/appcache1/dojo/main.js
|
||||
http://jsdev.arcgis.com/o/andygup/appcache1/dojox/gfx/svg.js
|
||||
http://jsdev.arcgis.com/o/andygup/appcache1/dojo/_base/browser.js
|
||||
http://jsdev.arcgis.com/o/andygup/appcache1/dojox/gfx/shape.js
|
||||
http://jsdev.arcgis.com/o/andygup/appcache1/dojo/nls/dojo_en-us.js
|
||||
http://jsdev.arcgis.com/o/andygup/appcache1/dojox/gfx/shape.js
|
||||
http://jsdev.arcgis.com/o/andygup/appcache1/dojox/gfx/path.js
|
||||
http://jsdev.arcgis.com/o/andygup/appcache1/dojo/request.js
|
||||
http://jsdev.arcgis.com/o/andygup/appcache1/dojo/_base/NodeList.js
|
||||
http://jsdev.arcgis.com/o/andygup/appcache1/dojo/selector/acme.js
|
||||
http://jsdev.arcgis.com/o/andygup/appcache1/dojo/_base/loader.js
|
||||
http://jsdev.arcgis.com/o/andygup/appcache1/dojo/request/default.js
|
||||
http://jsdev.arcgis.com/o/andygup/appcache1/esri/geometry.js
|
||||
http://jsdev.arcgis.com/o/andygup/appcache1/esri/geometry/geodesicUtils.js
|
||||
http://jsdev.arcgis.com/o/andygup/appcache1/esri/geometry/normalizeUtils.js
|
||||
http://jsdev.arcgis.com/o/andygup/appcache1/esri/dijit/Attribution.js
|
||||
http://jsdev.arcgis.com/o/andygup/appcache1/esri/units.js
|
||||
http://jsdev.arcgis.com/o/andygup/appcache1/dojo/fx/Toggler.js
|
||||
http://js.arcgis.com/3.8/js/dojo/dojox/gfx/svg.js
|
||||
http://js.arcgis.com/3.8/js/dojo/dojo/resources/blank.gif
|
||||
http://js.arcgis.com/3.8/js/esri/dijit/images/ajax-loader.gif
|
||||
http://js.arcgis.com/3.8/js/esri/images/map/logo-sm.png
|
||||
http://js.arcgis.com/3.8/js/esri/images/map/logo-med.png
|
||||
http://js.arcgis.com/3.8/js/esri/css/esri.css
|
||||
http://js.arcgis.com/3.8/js/esri/nls/jsapi_en-us.js
|
||||
http://js.arcgis.com/o/agup_hack4co/appcacheFeatures/dojo/dojo.js
|
||||
http://js.arcgis.com/o/agup_hack4co/appcacheFeatures/dojo/nls/dojo_en-us.js
|
||||
http://js.arcgis.com/o/agup_hack4co/appcacheFeatures/dojo/selector/acme.js
|
||||
#
|
||||
http://js.arcgis.com/3.9/js/esri/dijit/images/popup-sprite.png
|
||||
http://js.arcgis.com/3.9/js/esri/dijit/images/attribute_inspector_sprite.png
|
||||
http://js.arcgis.com/3.9/js/dojo/dojox/gfx/svg.js
|
||||
http://js.arcgis.com/3.9/js/dojo/dojo/resources/blank.gif
|
||||
http://js.arcgis.com/3.9/js/esri/dijit/images/ajax-loader.gif
|
||||
http://js.arcgis.com/3.9/js/esri/images/map/logo-sm.png
|
||||
http://js.arcgis.com/3.9/js/esri/images/map/logo-med.png
|
||||
http://js.arcgis.com/3.9/js/esri/css/esri.css
|
||||
http://js.arcgis.com/3.9/js/dojo/dijit/themes/claro/claro.css
|
||||
http://js.arcgis.com/3.9/js/esri/nls/jsapi_en-us.js
|
||||
#
|
||||
//services.arcgisonline.com/ArcGIS/rest/info?f=json
|
||||
//static.arcgis.com/attribution/World_Topo_Map?f=json
|
||||
//services.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer?f=json&callback=dojo.io.script.jsonp_dojoIoScript1._jsonpCallback
|
||||
#
|
||||
# required for web maps
|
||||
http://js.arcgis.com/3.8/js/esri/dijit/images/ajax-loader.gif
|
||||
http://js.arcgis.com/3.8/js/esri/dijit/images/popup.png
|
||||
# required custom libs
|
||||
http://js.arcgis.com/3.9/js/esri/dijit/images/ajax-loader.gif
|
||||
#
|
||||
# required local html
|
||||
# /xyz/style.css
|
||||
# /img/1.png
|
||||
appcache-features.html
|
||||
@ -49,6 +41,27 @@ attachments-editor.html
|
||||
military-offline.html
|
||||
service-inspector.html
|
||||
tiles-indexed-db.html
|
||||
tpk-layer.html
|
||||
../samples/images/blue-pin.png
|
||||
../samples/images/red-pin.png
|
||||
../samples/css/modular-popup.css
|
||||
../vendor/IndexedDBShim/dist/IndexedDBShim.js
|
||||
../vendor/IndexedDBShim/dist/IndexedDBShim.min.js
|
||||
../vendor/offline/offline.min.js
|
||||
../lib/tiles/FileSaver.js
|
||||
../lib/tiles/OfflineTilesEnablerLayer.js
|
||||
../lib/tiles/TilesStore.js
|
||||
../lib/tiles/base64utils.js
|
||||
../lib/tiles/offlineTilesEnabler.js
|
||||
../lib/tiles/tilingScheme.js
|
||||
../lib/tiles/notile.png
|
||||
../lib/tiles/notile.psd
|
||||
../lib/edit/attachmentsStore.js
|
||||
../lib/edit/editsStore.js
|
||||
../lib/edit/offlineFeaturesManager.js
|
||||
../lib/edit/restartOfflineFeaturesManager.js
|
||||
../utils/appCacheManager.js
|
||||
../utils/debouncer.js
|
||||
|
||||
NETWORK:
|
||||
*
|
||||
|
||||
@ -1,34 +1,17 @@
|
||||
<!DOCTYPE html>
|
||||
<!--<html>-->
|
||||
<html manifest="appcache-features.appcache">
|
||||
<!--
|
||||
This sample demonstrates using an application manifest along with storing feature edits locally.
|
||||
Included in this sample is the ability to work directly with the AttributeInspector widget.
|
||||
Features are typically points, lines and polygons along with associated attributes.
|
||||
|
||||
The use cases for using this sample are to ensure you can reload your application
|
||||
offline as well as gracefully load after a browser restart. Any edits made by the user will still
|
||||
be available and any .js libraries stored in the application cache will also continue to work.
|
||||
|
||||
It is recommended to also store map tiles locally. See the appache-tiles.html sample and the
|
||||
tiles-indexed-db.html sample for more hints on how to do that.
|
||||
|
||||
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.
|
||||
|
||||
-->
|
||||
<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>
|
||||
<link rel="stylesheet" href="http://js.arcgis.com/3.8/js/dojo/dijit/themes/claro/claro.css">
|
||||
<link rel="stylesheet" href="http://js.arcgis.com/3.8/js/esri/css/esri.css">
|
||||
<!--The viewport meta tag is used to improve the presentation and behavior of the samples
|
||||
on iOS devices-->
|
||||
<meta name="viewport" content="initial-scale=1, maximum-scale=1,user-scalable=no">
|
||||
<title>AppCache Tiles and Features</title>
|
||||
|
||||
<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%;
|
||||
@ -41,61 +24,16 @@ ask if you want to reload the application.
|
||||
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;
|
||||
#map {
|
||||
z-index: 1;
|
||||
position: absolute;
|
||||
top: 50px;
|
||||
left: 0;
|
||||
}
|
||||
#button-div1{
|
||||
position: relative;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
z-index: 2;
|
||||
background: #000000;
|
||||
color: #ffffff;
|
||||
width: 100%;
|
||||
@ -106,15 +44,55 @@ ask if you want to reload the application.
|
||||
position: relative; float: right;
|
||||
}
|
||||
#img-offline-indicator{
|
||||
padding: 8px;
|
||||
/*padding: 8px;*/
|
||||
position: relative; float: right;
|
||||
position: relative; float: right;
|
||||
|
||||
}
|
||||
#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;
|
||||
}
|
||||
#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 = {
|
||||
@ -132,148 +110,264 @@ ask if you want to reload the application.
|
||||
var dojoConfig = {
|
||||
paths: {
|
||||
edit: locationPath + "/../lib/edit",
|
||||
vendor: locationPath + "/../vendor"
|
||||
vendor: locationPath + "/../vendor",
|
||||
utils: locationPath + "/../utils",
|
||||
tiles: locationPath + "/../lib/tiles"
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<script src="http://js.arcgis.com/3.8/"></script>
|
||||
<script>
|
||||
<!-- Required when using the offline-editor-js library with Safari -->
|
||||
<script src="../vendor/IndexedDBShim/dist/IndexedDBShim.js"></script>
|
||||
<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" id="btn-get-tiles">1. Download Tiles</button>
|
||||
<button class="basic-btn" data-dojo-type="dijit/form/ToggleButton" id="btn-online-offline">2. Go Offline</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>
|
||||
|
||||
require([
|
||||
"esri/map",
|
||||
"esri/tasks/query",
|
||||
"esri/layers/FeatureLayer",
|
||||
"edit/offlineFeaturesManager",
|
||||
"edit/editsStore",
|
||||
"esri/dijit/AttributeInspector",
|
||||
"dojo/dom-construct",
|
||||
"dojo/on",
|
||||
"dojo/dom",
|
||||
"dijit/form/Button",
|
||||
"dijit/form/SimpleTextarea",
|
||||
"dojo/domReady!"],
|
||||
function(Map,Query,FeatureLayer,OfflineFeaturesManager,editsStore,AttributeInspector,domConstruct,on,dom,Button,SimpleTextArea) {
|
||||
<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>
|
||||
|
||||
var textTimer;
|
||||
<div id="map"></div>
|
||||
|
||||
<script>
|
||||
require(["esri/map","esri/layers/FeatureLayer","tiles/OfflineTilesEnablerLayer",
|
||||
"edit/restartOfflineFeaturesManager", "utils/appCacheManager",
|
||||
"esri/renderers/SimpleRenderer","esri/symbols/SimpleMarkerSymbol","esri/Color","esri/tasks/query",
|
||||
"edit/offlineFeaturesManager", "edit/editsStore",
|
||||
"dojo/on",
|
||||
"dojo/domReady!"],
|
||||
function(Map,FeatureLayer,OfflineTilesEnablerLayer,restartOfflineFeaturesMgr,AppCacheManager,
|
||||
SimpleRenderer,SimpleMarkerSymbol,Color,Query,
|
||||
OfflineFeaturesManager,editsStore,on) {
|
||||
|
||||
initAppCacheManager();
|
||||
|
||||
var _isOnline = true;
|
||||
var defaultSymbol;
|
||||
|
||||
// Variables for editing handling
|
||||
var currentFeature;
|
||||
var busStopFeatureLayer;
|
||||
var offlineFeaturesManager;
|
||||
var map,busStopsFeatureLayer,currentFeature;
|
||||
var imgOfflineIndicator,btnOnlineOffline;
|
||||
var modPopup = document.getElementById("popup");
|
||||
var pendingEdits = document.getElementById("pending-edits");
|
||||
var imgOfflineIndicator = document.getElementById("img-offline-indicator");
|
||||
|
||||
// Variables for handling tiles
|
||||
var tileLayer;
|
||||
var _currentExtent = null;
|
||||
var _wantToCancel;
|
||||
var globalState = {};
|
||||
var btnGetTiles = document.getElementById("btn-get-tiles");
|
||||
// Important settings for determining which tile layers gets stored for offline use.
|
||||
var minZoomAdjust = -1, maxZoomAdjust = 1, resetZoom = 18;
|
||||
var EXTENT_BUFFER = 0; //buffers the map extent in meters
|
||||
|
||||
// Misc.
|
||||
var appCacheManager;
|
||||
var btnOnlineOffline = document.getElementById("btn-online-offline");
|
||||
|
||||
// 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");
|
||||
|
||||
// Symbols and images
|
||||
var redPinPath = "../samples/images/red-pin.png";
|
||||
var bluePinPath = "../samples/images/blue-pin.png"
|
||||
var redPinImage = new Image().src = redPinPath;
|
||||
var bluePinPath = "../samples/images/blue-pin.png";
|
||||
|
||||
initOffline();
|
||||
// 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);
|
||||
}
|
||||
|
||||
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"]
|
||||
/**
|
||||
* 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();
|
||||
})
|
||||
|
||||
map.on("layers-add-result",initEditing);
|
||||
function startMap(){
|
||||
|
||||
map.addLayers([busStopsFeatureLayer]);
|
||||
//Make sure map shows up after a browser refresh
|
||||
Offline.check();
|
||||
Offline.state === 'up' ? resetZoom = 18 : resetZoom = 17;
|
||||
|
||||
function initOffline(){
|
||||
tileLayer = new OfflineTilesEnablerLayer("http://server.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer",function(evt){
|
||||
console.log("Tile Layer Loaded.");
|
||||
},_isOnline);
|
||||
|
||||
var map = new Map("map",{
|
||||
center: [-104.98,39.74], // long, lat
|
||||
zoom: 8,
|
||||
sliderStyle: "small"
|
||||
});
|
||||
|
||||
map.addLayer(tileLayer);
|
||||
|
||||
busStopFeatureLayer = 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.
|
||||
busStopFeatureLayer.setRenderer(new SimpleRenderer(defaultSymbol));
|
||||
|
||||
busStopFeatureLayer.on("update-end",function(evt){
|
||||
|
||||
on(btnGetTiles,"click",downloadTiles);
|
||||
on(btnOnlineOffline, 'click', goOnlineOffline);
|
||||
|
||||
initOfflineFeaturesMgr();
|
||||
setFeatureLayerClickHandler();
|
||||
setModalPopupClickListeners();
|
||||
|
||||
var features = evt.target.graphics;
|
||||
|
||||
// Convert existing feature graphics into JSON.
|
||||
// These are then stored in localStorage.
|
||||
// If you want you can store multiple feature layers.
|
||||
// Just be aware of the localStorage limitations.
|
||||
restartOfflineFeaturesMgr.convertGraphicLayerToJSON(features,evt,function(features,layerDef){
|
||||
|
||||
if(typeof(Storage) !== "undefined") {
|
||||
localStorage.offlineLayerDef = layerDef;
|
||||
localStorage.offlineFeature = features;
|
||||
console.log("Done pushing layerDef and features to localStorage.")
|
||||
} else {
|
||||
alert("The offline library is not supported on this browser.")
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
map.on("zoom-end",function(evt){
|
||||
_currentExtent = evt.extent;
|
||||
});
|
||||
|
||||
map.on("pan-end",function(evt){
|
||||
_currentExtent = evt.extent;
|
||||
});
|
||||
|
||||
map.on("load",function(evt){
|
||||
|
||||
_currentExtent = evt.map.extent;
|
||||
updateOfflineUsage();
|
||||
|
||||
if(_isOnline == false){
|
||||
|
||||
var featureLayer = JSON.parse(localStorage.offlineLayerDef);
|
||||
var featuresArray = JSON.parse(localStorage.offlineFeature);
|
||||
var geometryType = "esriGeometryPoint";
|
||||
|
||||
restartOfflineFeaturesMgr.getFeatureDefinition(featureLayer,featuresArray,geometryType,function(featureDef){
|
||||
|
||||
busStopFeatureLayer = new FeatureLayer(featureDef,{
|
||||
mode: FeatureLayer.MODE_SNAPSHOT,
|
||||
outFields: ["OBJECTID","BSID","ROUTES","STOPNAME"]
|
||||
});
|
||||
|
||||
// Set the graphic symbols to red diamond to make it easy to click on them
|
||||
// on a mobile device.
|
||||
busStopFeatureLayer.setRenderer(new SimpleRenderer(defaultSymbol));
|
||||
var mapListen = map.on("update-end",function(evt){
|
||||
console.log("Feature has been added back to the map while offline.");
|
||||
on(btnGetTiles,"click",downloadTiles);
|
||||
on(btnOnlineOffline, 'click', goOnlineOffline);
|
||||
|
||||
initOfflineFeaturesMgr();
|
||||
setFeatureLayerClickHandler();
|
||||
setModalPopupClickListeners();
|
||||
mapListen.remove();
|
||||
})
|
||||
map.addLayer(busStopFeatureLayer);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
map.addLayer(busStopFeatureLayer);
|
||||
}
|
||||
|
||||
/**
|
||||
* **********************************************
|
||||
* FEATURE LAYER MANAGEMENT CODE
|
||||
* **********************************************
|
||||
*/
|
||||
|
||||
function initOfflineFeaturesMgr(){
|
||||
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";
|
||||
offlineFeaturesManager.extend(busStopFeatureLayer);
|
||||
console.log("offlineFeaturesManager initialized.");
|
||||
|
||||
Offline.check();
|
||||
Offline.on('up', goOnline);
|
||||
Offline.on('down', goOffline);
|
||||
|
||||
on(dom.byId('btn-online-offline'), 'click', goOnlineOffline);
|
||||
}
|
||||
|
||||
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));
|
||||
});
|
||||
|
||||
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(){
|
||||
@ -288,27 +382,189 @@ ask if you want to reload the application.
|
||||
}
|
||||
}
|
||||
|
||||
function goOnline(){
|
||||
/**
|
||||
* Display modal popup when someone clicks on a feature
|
||||
*/
|
||||
function setFeatureLayerClickHandler(){
|
||||
busStopFeatureLayer.on("click", function(evt) {
|
||||
currentFeature = evt.graphic
|
||||
|
||||
var query = new Query();
|
||||
query.objectIds = [evt.graphic.attributes.OBJECTID];
|
||||
busStopFeatureLayer.selectFeatures(query,FeatureLayer.SELECTION_NEW,
|
||||
function(evt){
|
||||
console.log("Success: " + JSON.stringify(evt[0].attributes));
|
||||
showModalPopup(evt[0]);
|
||||
var atts = evt[0].attributes
|
||||
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 setModalPopupClickListeners(){
|
||||
closeBtn.onclick = function(evt){
|
||||
hideModalPopup();
|
||||
}
|
||||
|
||||
saveBtn.onclick = function(evt){
|
||||
|
||||
modPopup.graphic.attributes.ROUTES = stopRoutes.value;
|
||||
modPopup.graphic.attributes.STOPNAME = stopNames.value;
|
||||
|
||||
busStopFeatureLayer.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){
|
||||
busStopFeatureLayer.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 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
|
||||
* ************************************
|
||||
*/
|
||||
|
||||
/**
|
||||
* Manually starts the process to download and store tiles
|
||||
* in the local database
|
||||
*/
|
||||
function downloadTiles(){
|
||||
tileLayer.deleteAllTiles(function(success,err){
|
||||
if(success == false){
|
||||
alert("There was a problem deleting the tile cache");
|
||||
}
|
||||
else{
|
||||
console.log("success deleting tile cache");
|
||||
|
||||
if( globalState.downloadState == 'downloading')
|
||||
{
|
||||
console.log("cancel!");
|
||||
_wantToCancel = true;
|
||||
btnGetTiles.innerHTML = "cancelling..";
|
||||
}
|
||||
else
|
||||
{
|
||||
var zoom = getMinMaxZoom();
|
||||
|
||||
var extent = tileLayer.getExtentBuffer(EXTENT_BUFFER,_currentExtent);
|
||||
_wantToCancel = false;
|
||||
tileLayer.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 = '1. Download Tiles';
|
||||
}
|
||||
return _wantToCancel; //determines if a cancel request has been issued
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility function to validate min and max zoom settings of the map
|
||||
*/
|
||||
function getMinMaxZoom(){
|
||||
|
||||
var zoom = {};
|
||||
var min = tileLayer.getLevel() + minZoomAdjust;
|
||||
var max = tileLayer.getLevel() + maxZoomAdjust;
|
||||
var mMaxZoom;
|
||||
tileLayer.getMaxZoom(function(result){
|
||||
mMaxZoom = result;
|
||||
});
|
||||
var mMinZoom;
|
||||
tileLayer.getMinZoom(function(result){
|
||||
mMinZoom = result;
|
||||
});
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* ************************************
|
||||
* MISC. CODE
|
||||
* ************************************
|
||||
*/
|
||||
|
||||
function goOnline(){
|
||||
console.log("Going online...");
|
||||
offlineFeaturesManager.goOnline(function(success,error){
|
||||
if(success == true){
|
||||
btnOnlineOffline.innerHTML = "Go Offline";
|
||||
if(error === undefined){
|
||||
btnOnlineOffline.innerHTML = "2. Go Offline";
|
||||
imgOfflineIndicator.src = bluePinPath;
|
||||
imgOfflineIndicator.offlineColor = "blue";
|
||||
console.log("Online.");
|
||||
console.log("offlineFeatureManager is online.");
|
||||
}
|
||||
else{
|
||||
alert("There was a problem syncing offline edits: " + JSON.stringify(error));
|
||||
}
|
||||
});
|
||||
|
||||
updateOfflineUsage();
|
||||
if(typeof baseMapLayer != "undefined") baseMapLayer.goOnline();
|
||||
}
|
||||
|
||||
function goOffline(){
|
||||
btnOnlineOffline.innerHTML = "Go Online";
|
||||
console.log("Going offline...");
|
||||
btnOnlineOffline.innerHTML = "2. Go Online";
|
||||
imgOfflineIndicator.src = redPinPath;
|
||||
imgOfflineIndicator.offlineColor = "red";
|
||||
offlineFeaturesManager.goOffline();
|
||||
|
||||
if(typeof tileLayer != "undefined") tileLayer.goOffline();
|
||||
}
|
||||
|
||||
function goOnlineOffline(){
|
||||
@ -319,21 +575,71 @@ ask if you want to reload the application.
|
||||
goOnline();
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
</script>
|
||||
</head>
|
||||
/**
|
||||
* For internal use - detecting amount of storage used and number of tiles stored.
|
||||
*/
|
||||
function updateOfflineUsage()
|
||||
{
|
||||
tileLayer.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");
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
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 cacheLoadedHandler(evt){
|
||||
if(evt == appCacheManager.CACHE_LOADED) console.log("Application cache successfully loaded!")
|
||||
}
|
||||
|
||||
function cacheEventHandler(evt){
|
||||
if(evt.hasOwnProperty("total")) console.log("CACHE EVENT loaded #:" + evt.loaded + ", out of " + evt.total);
|
||||
}
|
||||
|
||||
function cacheErrorHandler(evt){
|
||||
console.log("CACHE ERROR: " + JSON.stringify(evt));
|
||||
alert("There was a problem loading the cache. Try reloading the app. ")
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<body>
|
||||
<div id="button-div1">
|
||||
<button class="basic-btn" data-dojo-type="dijit/form/ToggleButton" id="btn-online-offline">Go Offline</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="test"></div>
|
||||
<div id="map"></div>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,45 +1,35 @@
|
||||
CACHE MANIFEST
|
||||
# This manifest was generated by grunt-manifest HTML5 Cache Manifest Generator
|
||||
# Time: Tue Apr 29 2014 17:40:03 GMT-0600 (MDT)
|
||||
# Time: Wed Jun 04 2014 17:49:41 GMT-0600 (MDT)
|
||||
|
||||
CACHE:
|
||||
# manifest-generator, version: 0.0.1
|
||||
#
|
||||
# Home Page
|
||||
appcache-tiles.html
|
||||
#
|
||||
# ArcGIS API for JavaScript files
|
||||
http://jsdev.arcgis.com/o/andygup/appcache1/dojo/dojo.js
|
||||
http://jsdev.arcgis.com/o/andygup/appcache1/dojo/main.js
|
||||
http://jsdev.arcgis.com/o/andygup/appcache1/dojox/gfx/svg.js
|
||||
http://jsdev.arcgis.com/o/andygup/appcache1/dojo/_base/browser.js
|
||||
http://jsdev.arcgis.com/o/andygup/appcache1/dojox/gfx/shape.js
|
||||
http://jsdev.arcgis.com/o/andygup/appcache1/dojo/nls/dojo_en-us.js
|
||||
http://jsdev.arcgis.com/o/andygup/appcache1/dojox/gfx/shape.js
|
||||
http://jsdev.arcgis.com/o/andygup/appcache1/dojox/gfx/path.js
|
||||
http://jsdev.arcgis.com/o/andygup/appcache1/dojo/request.js
|
||||
http://jsdev.arcgis.com/o/andygup/appcache1/dojo/_base/NodeList.js
|
||||
http://jsdev.arcgis.com/o/andygup/appcache1/dojo/selector/acme.js
|
||||
http://jsdev.arcgis.com/o/andygup/appcache1/dojo/_base/loader.js
|
||||
http://jsdev.arcgis.com/o/andygup/appcache1/dojo/request/default.js
|
||||
http://jsdev.arcgis.com/o/andygup/appcache1/esri/geometry.js
|
||||
http://jsdev.arcgis.com/o/andygup/appcache1/esri/geometry/geodesicUtils.js
|
||||
http://jsdev.arcgis.com/o/andygup/appcache1/esri/geometry/normalizeUtils.js
|
||||
http://jsdev.arcgis.com/o/andygup/appcache1/esri/dijit/Attribution.js
|
||||
http://jsdev.arcgis.com/o/andygup/appcache1/esri/units.js
|
||||
http://jsdev.arcgis.com/o/andygup/appcache1/dojo/fx/Toggler.js
|
||||
http://js.arcgis.com/3.8/js/dojo/dojox/gfx/svg.js
|
||||
http://js.arcgis.com/3.8/js/dojo/dojo/resources/blank.gif
|
||||
http://js.arcgis.com/3.8/js/esri/dijit/images/ajax-loader.gif
|
||||
http://js.arcgis.com/3.8/js/esri/images/map/logo-sm.png
|
||||
http://js.arcgis.com/3.8/js/esri/images/map/logo-med.png
|
||||
http://js.arcgis.com/3.8/js/esri/css/esri.css
|
||||
http://js.arcgis.com/3.8/js/esri/nls/jsapi_en-us.js
|
||||
http://js.arcgis.com/o/agup_hack4co/appcache2/dojo/dojo.js
|
||||
http://js.arcgis.com/o/agup_hack4co/appcache2/dojo/nls/dojo_en-us.js
|
||||
http://js.arcgis.com/o/agup_hack4co/appcache2/dojo/selector/acme.js
|
||||
#
|
||||
http://js.arcgis.com/3.9/js/esri/dijit/images/popup-sprite.png
|
||||
http://js.arcgis.com/3.9/js/dojo/dojox/gfx/svg.js
|
||||
http://js.arcgis.com/3.9/js/dojo/dojo/resources/blank.gif
|
||||
http://js.arcgis.com/3.9/js/esri/dijit/images/ajax-loader.gif
|
||||
http://js.arcgis.com/3.9/js/esri/images/map/logo-sm.png
|
||||
http://js.arcgis.com/3.9/js/esri/images/map/logo-med.png
|
||||
http://js.arcgis.com/3.9/js/esri/css/esri.css
|
||||
http://js.arcgis.com/3.9/js/esri/nls/jsapi_en-us.js
|
||||
#
|
||||
//services.arcgisonline.com/ArcGIS/rest/info?f=json
|
||||
//static.arcgis.com/attribution/World_Topo_Map?f=json
|
||||
//services.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer?f=json&callback=dojo.io.script.jsonp_dojoIoScript1._jsonpCallback
|
||||
#
|
||||
# required for web maps
|
||||
http://js.arcgis.com/3.8/js/esri/dijit/images/ajax-loader.gif
|
||||
http://js.arcgis.com/3.8/js/esri/dijit/images/popup.png
|
||||
# required custom libs
|
||||
http://js.arcgis.com/3.9/js/esri/dijit/images/ajax-loader.gif
|
||||
#
|
||||
# required local html
|
||||
# /xyz/style.css
|
||||
# /img/1.png
|
||||
appcache-features.html
|
||||
@ -49,6 +39,20 @@ attachments-editor.html
|
||||
military-offline.html
|
||||
service-inspector.html
|
||||
tiles-indexed-db.html
|
||||
tpk-layer.html
|
||||
../samples/images/blue-pin.png
|
||||
../samples/images/red-pin.png
|
||||
../vendor/IndexedDBShim/dist/IndexedDBShim.min.js
|
||||
../vendor/offline/offline.min.js
|
||||
../lib/tiles/FileSaver.js
|
||||
../lib/tiles/TilesStore.js
|
||||
../lib/tiles/base64utils.js
|
||||
../lib/tiles/offlineTilesEnabler.js
|
||||
../lib/tiles/tilingScheme.js
|
||||
../lib/tiles/notile.png
|
||||
../lib/tiles/notile.psd
|
||||
../utils/appCacheManager.js
|
||||
../utils/debouncer.js
|
||||
|
||||
NETWORK:
|
||||
*
|
||||
|
||||
@ -1,6 +1,9 @@
|
||||
<!DOCTYPE html>
|
||||
<html manifest="appcache-tiles.appcache">
|
||||
<!--<html>-->
|
||||
<html manifest="appcache-tiles.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"/>
|
||||
|
||||
<!--
|
||||
|
||||
This sample demonstrates using an application manifest to store tiles locally.
|
||||
@ -8,6 +11,10 @@ This sample demonstrates using an application manifest to store tiles locally.
|
||||
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.
|
||||
@ -19,324 +26,352 @@ A few things to know about manifest files:
|
||||
ask if you want to reload the application.
|
||||
|
||||
-->
|
||||
<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>Simple Map</title>
|
||||
<link rel="stylesheet" href="http://js.arcgis.com/3.8/js/esri/css/esri.css">
|
||||
<style>
|
||||
html, body, #map {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
<title>Cache Tiles</title>
|
||||
<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";
|
||||
}
|
||||
#button-div1{
|
||||
position: relative;
|
||||
background: #000000;
|
||||
color: #ffffff;
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
}
|
||||
.basic-btn{
|
||||
background-color: #000000;
|
||||
border-color: #ffffff 1px;
|
||||
color: #ffffff;
|
||||
padding: 8px;
|
||||
position: relative; float: left;
|
||||
}
|
||||
#img-offline-indicator{
|
||||
padding: 8px;
|
||||
position: relative; float: right;
|
||||
}
|
||||
#tile-info{
|
||||
background-color: #000000;
|
||||
color: white;
|
||||
padding: 8px;
|
||||
position: relative; float: right;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
var locationPath = location.pathname.replace(/\/[^/]+$/, "");
|
||||
var dojoConfig = {
|
||||
paths: {
|
||||
tiles: locationPath + "/../lib/tiles",
|
||||
vendor: locationPath + "/../vendor",
|
||||
utils: locationPath + "/../utils"
|
||||
}
|
||||
body {
|
||||
background-color: #FFF;
|
||||
overflow: hidden;
|
||||
font-family: "Trebuchet MS";
|
||||
}
|
||||
#button-div1{
|
||||
position: relative;
|
||||
background: #000000;
|
||||
color: #ffffff;
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
}
|
||||
.basic-btn{
|
||||
background-color: #000000;
|
||||
border-color: #ffffff 1px;
|
||||
color: #ffffff;
|
||||
padding: 8px;
|
||||
position: relative; float: left;
|
||||
}
|
||||
#img-offline-indicator{
|
||||
padding: 8px;
|
||||
position: relative; float: right;
|
||||
}
|
||||
#tile-info{
|
||||
background-color: #000000;
|
||||
color: white;
|
||||
padding: 8px;
|
||||
position: relative; float: right;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
var locationPath = location.pathname.replace(/\/[^/]+$/, "");
|
||||
var dojoConfig = {
|
||||
paths: {
|
||||
tiles: locationPath + "/../lib/tiles",
|
||||
vendor: locationPath + "/../vendor",
|
||||
utils: locationPath + "/../utils"
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<!-- This is a custom build of the ArcGIS API for JavaScript using the new Web Optimizer Tool -->
|
||||
<script src="http://jsdev.arcgis.com/o/andygup/appcache1/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>-->
|
||||
<script src="../vendor/IndexedDBShim/dist/IndexedDBShim.min.js"></script>
|
||||
<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'
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<script>
|
||||
var map,offlineTileEnabler,baseMapLayer,zoom = 18;
|
||||
var globalState = {};
|
||||
var _wantToCancel;
|
||||
var minZoomAdjust = -1, maxZoomAdjust = 1, mMinZoom, mMaxZoom;
|
||||
var imgOfflineIndicator,btnGetTiles,btnOnlineOffline,btnZoom;
|
||||
var tiles,appCacheManager,tileInfo;
|
||||
var redPinPath = "../samples/images/red-pin.png";
|
||||
var bluePinPath = "../samples/images/blue-pin.png"
|
||||
var EXTENT_BUFFER = 0; //buffers the map extent in meters
|
||||
|
||||
require(["esri/map","utils/appCacheManager","tiles/offlineTilesEnabler","esri/geometry/Extent","dojo/on","dojo/domReady!"],
|
||||
function(Map,AppCacheManager,OfflineTileEnabler,Extent,on) {
|
||||
|
||||
imgOfflineIndicator = document.getElementById("img-offline-indicator");
|
||||
imgOfflineIndicator.offlineColor = "blue";
|
||||
|
||||
Offline.check();
|
||||
Offline.on('up down', updateState );
|
||||
|
||||
//Make sure map shows up after a browser refresh
|
||||
Offline.state === 'up' ? zoom = 18 : zoom = 17;
|
||||
|
||||
map = new Map("map", {
|
||||
basemap: "topo",
|
||||
center: [-122.45,37.75], // long, lat
|
||||
zoom: zoom,
|
||||
sliderStyle: "small"
|
||||
});
|
||||
|
||||
map.on("load",function(evt){
|
||||
init();
|
||||
initOfflineTileEnabler();
|
||||
console.log("level: " + map.getLevel() + ", maxZoom: " + map.getMaxZoom());
|
||||
})
|
||||
|
||||
function init(){
|
||||
map.on("extent-change",function(evt){
|
||||
updateOfflineUsage();
|
||||
console.log("Zoom level = " + map.getLevel())
|
||||
})
|
||||
appCacheManager = new AppCacheManager(true,true);
|
||||
appCacheManager.on(appCacheManager.CACHE_EVENT,cacheEventHandler);
|
||||
appCacheManager.on(appCacheManager.CACHE_ERROR,cacheErrorHandler);
|
||||
|
||||
btnGetTiles = document.getElementById("btn-get-tiles");
|
||||
btnOnlineOffline = document.getElementById("btn-online-offline");
|
||||
btnZoom = document.getElementById("btn-zoom-out");
|
||||
|
||||
tileInfo = document.getElementById("tile-info")
|
||||
tileInfo.innerHTML = "Tile count: 0\r\nBytes: 0";
|
||||
|
||||
map.reposition();
|
||||
map.resize();
|
||||
}
|
||||
|
||||
function initOfflineTileEnabler(){
|
||||
|
||||
mMaxZoom = map.getMaxZoom();
|
||||
mMinZoom = map.getMinZoom();
|
||||
|
||||
offlineTileEnabler = new OfflineTileEnabler();
|
||||
baseMapLayer = offlineTileEnabler.getBasemapLayer(map);
|
||||
|
||||
offlineTileEnabler.extend(baseMapLayer,function(success)
|
||||
{
|
||||
if( success )
|
||||
{
|
||||
console.log("Offline tile lib is enabled");
|
||||
|
||||
//using null sets this for CORS
|
||||
baseMapLayer.offline.proxyPath = null;
|
||||
|
||||
updateOfflineUsage();
|
||||
}
|
||||
else
|
||||
{
|
||||
alert("error initializing storage - browser doesn't support indexeddb or websql")
|
||||
}
|
||||
}.bind(this));
|
||||
}
|
||||
|
||||
function cacheEventHandler(evt){
|
||||
console.log("CACHE EVENT: " + JSON.stringify(evt));
|
||||
}
|
||||
|
||||
function cacheErrorHandler(evt){
|
||||
console.log("CACHE ERROR: " + JSON.stringify(evt));
|
||||
}
|
||||
|
||||
function updateState(){
|
||||
if(Offline.state === 'up'){
|
||||
updateOfflineUsage();
|
||||
imgOfflineIndicator.src = bluePinPath;
|
||||
baseMapLayer.goOnline();
|
||||
}
|
||||
else{
|
||||
imgOfflineIndicator.src = redPinPath;
|
||||
baseMapLayer.goOffline();
|
||||
}
|
||||
}
|
||||
|
||||
function updateOfflineUsage()
|
||||
{
|
||||
var count = getEstimateTileCount(function(info,err){
|
||||
if(info != null){
|
||||
console.log("COUNT " + info)
|
||||
tileInfo.innerHTML = "Tile count: " + info.tileCount + "\r\nBytes: " + info.sizeBytes;
|
||||
}
|
||||
else{
|
||||
console.log("ERROR updateOfflineUsage(): " + JSON.stringify(err));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets tile count and size estimates. Not perfect.
|
||||
* @param callback
|
||||
*/
|
||||
function getEstimateTileCount(callback)
|
||||
{
|
||||
var extent = baseMapLayer.getExtentBuffer(EXTENT_BUFFER,map.extent);
|
||||
var level = map.getLevel();
|
||||
var url = baseMapLayer.getTileUrlsByExtent(extent,level)[0];
|
||||
baseMapLayer._lastTileUrl = url;
|
||||
baseMapLayer.estimateTileSize(function(tileSize,err){
|
||||
|
||||
if(tileSize != null){
|
||||
var totalEstimation = {tileCount:0,sizeBytes:0};
|
||||
|
||||
var zoom = getMinMaxZoom();
|
||||
|
||||
for(var level = zoom.min; level<= zoom.max; level++)
|
||||
{
|
||||
var levelEstimation = baseMapLayer.getLevelEstimation(baseMapLayer.getExtentBuffer(EXTENT_BUFFER,map.extent),level,tileSize);
|
||||
|
||||
totalEstimation.tileCount += levelEstimation.tileCount;
|
||||
totalEstimation.sizeBytes += levelEstimation.sizeBytes;
|
||||
}
|
||||
|
||||
console.log("Size estimate: " + totalEstimation.sizeBytes + ", tile count: " + totalEstimation.tileCount)
|
||||
callback(totalEstimation,null);
|
||||
}
|
||||
else{
|
||||
callback(null,err);
|
||||
}
|
||||
|
||||
}.bind(this))
|
||||
}
|
||||
}
|
||||
</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/appcache2/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>-->
|
||||
<script src="../vendor/IndexedDBShim/dist/IndexedDBShim.min.js"></script>
|
||||
<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));
|
||||
}
|
||||
);
|
||||
|
||||
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;
|
||||
},
|
||||
active: 'image'
|
||||
}
|
||||
|
||||
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";
|
||||
|
||||
if( progress.cancelRequested )
|
||||
{
|
||||
globalState.downloadState = 'cancelled';
|
||||
alert("Tile download was cancelled");
|
||||
}
|
||||
else
|
||||
{
|
||||
globalState.downloadState = 'downloaded';
|
||||
alert("Tile download complete");
|
||||
}
|
||||
|
||||
btnGetTiles.innerHTML = '1. Download Tiles';
|
||||
}
|
||||
return _wantToCancel; //determines if a cancel request has been issued
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
|
||||
function goOnlineOffline(){
|
||||
if(imgOfflineIndicator.offlineColor == "blue"){
|
||||
btnOnlineOffline.innerHTML = "2. Go Online";
|
||||
imgOfflineIndicator.src = redPinPath;
|
||||
imgOfflineIndicator.offlineColor = "red";
|
||||
baseMapLayer.goOffline();
|
||||
}
|
||||
else{
|
||||
btnOnlineOffline.innerHTML = "2. Go Offline";
|
||||
imgOfflineIndicator.src = bluePinPath;
|
||||
imgOfflineIndicator.offlineColor = "blue";
|
||||
baseMapLayer.goOnline();
|
||||
}
|
||||
}
|
||||
|
||||
function panLeft(){
|
||||
map.panLeft();
|
||||
}
|
||||
</script>
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="button-div1">
|
||||
<img id="img-offline-indicator" onclick="goOnlineOffline()" src="../samples/images/blue-pin.png"/>
|
||||
<img id="img-offline-indicator" src="../samples/images/blue-pin.png"/>
|
||||
<textarea contenteditable="false" id="tile-info"></textarea>
|
||||
<button class="basic-btn" id="btn-get-tiles" onclick="downloadTiles()">1. Download Tiles</button>
|
||||
<button class="basic-btn" onclick="goOnlineOffline()" id="btn-online-offline">2. Go Offline</button>
|
||||
<button class="basic-btn" id="btn-get-tiles">1. Download Tiles</button>
|
||||
<button class="basic-btn" id="btn-online-offline">2. Go Offline</button>
|
||||
<button class="basic-btn" id="btn-zoom-out" onclick="panLeft()">3. Pan left</button>
|
||||
</div>
|
||||
<div id="map"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
<script>
|
||||
|
||||
var map;
|
||||
|
||||
require(["esri/map","utils/appCacheManager","tiles/OfflineTilesEnablerLayer","dojo/on","dojo/domReady!"],
|
||||
function(Map,AppCacheManager,OfflineTileEnablerLayer,on) {
|
||||
|
||||
var tileLayer = null;
|
||||
|
||||
var zoom = 18;
|
||||
var globalState = {};
|
||||
var _wantToCancel;
|
||||
var _isOnline = true;
|
||||
var minZoomAdjust = -1, maxZoomAdjust = 1, mMinZoom, mMaxZoom;
|
||||
var imgOfflineIndicator,btnGetTiles,btnOnlineOffline,btnZoom;
|
||||
var tiles,appCacheManager,tileInfo;
|
||||
var redPinPath = "../samples/images/red-pin.png";
|
||||
var bluePinPath = "../samples/images/blue-pin.png"
|
||||
var EXTENT_BUFFER = 0; //buffers the map extent in meters
|
||||
|
||||
imgOfflineIndicator = document.getElementById("img-offline-indicator");
|
||||
imgOfflineIndicator.offlineColor = "blue";
|
||||
|
||||
Offline.check();
|
||||
Offline.on('up down', updateState );
|
||||
|
||||
/**
|
||||
* 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.state === 'up' ? zoom = 18 : zoom = 17;
|
||||
|
||||
map = new Map("map", {
|
||||
center: [-122.45,37.75], // long, lat
|
||||
zoom: zoom,
|
||||
sliderStyle: "small"
|
||||
});
|
||||
|
||||
tileLayer = new OfflineTileEnablerLayer("http://server.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer",function(evt){
|
||||
console.log("Offline tile lib is enabled. Application state is: " + Offline.state);
|
||||
},_isOnline);
|
||||
|
||||
map.on("load",function(evt){
|
||||
init();
|
||||
|
||||
Offline.check();
|
||||
|
||||
//using null sets this for CORS
|
||||
tileLayer.offline.proxyPath = null;
|
||||
|
||||
on(btnOnlineOffline,"click",goOnlineOffline);
|
||||
on(btnGetTiles,"click",downloadTiles);
|
||||
|
||||
updateOfflineUsage();
|
||||
|
||||
tileLayer.getMaxZoom(function(result){
|
||||
mMaxZoom = result;
|
||||
});
|
||||
tileLayer.getMinZoom(function(result){
|
||||
mMinZoom = result;
|
||||
});
|
||||
|
||||
console.log("level: " + tileLayer.getLevel() + ", maxZoom: " + mMaxZoom);
|
||||
})
|
||||
|
||||
map.addLayer(tileLayer);
|
||||
}
|
||||
|
||||
function init(){
|
||||
map.on("extent-change",function(evt){
|
||||
updateOfflineUsage();
|
||||
console.log("Zoom level = " + tileLayer.getLevel())
|
||||
});
|
||||
|
||||
appCacheManager = new AppCacheManager(true,true);
|
||||
appCacheManager.on(appCacheManager.CACHE_EVENT,cacheEventHandler);
|
||||
appCacheManager.on(appCacheManager.CACHE_ERROR,cacheErrorHandler);
|
||||
appCacheManager.on(appCacheManager.CACHE_LOADED,cacheLoaderHandler);
|
||||
|
||||
btnGetTiles = document.getElementById("btn-get-tiles");
|
||||
btnOnlineOffline = document.getElementById("btn-online-offline");
|
||||
|
||||
btnZoom = document.getElementById("btn-zoom-out");
|
||||
|
||||
tileInfo = document.getElementById("tile-info")
|
||||
tileInfo.innerHTML = "Tile count: 0\r\nBytes: 0";
|
||||
|
||||
map.reposition();
|
||||
map.resize();
|
||||
}
|
||||
|
||||
function updateState(){
|
||||
if(Offline.state === 'up'){
|
||||
updateOfflineUsage();
|
||||
imgOfflineIndicator.src = bluePinPath;
|
||||
if(typeof tileLayer != "undefined") tileLayer.goOnline();
|
||||
}
|
||||
else{
|
||||
imgOfflineIndicator.src = redPinPath;
|
||||
if(typeof tileLayer != "undefined") tileLayer.goOffline();
|
||||
}
|
||||
}
|
||||
|
||||
function updateOfflineUsage()
|
||||
{
|
||||
tileLayer.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 = tileLayer.getLevel() + minZoomAdjust;
|
||||
var max = tileLayer.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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 = '1. Download Tiles';
|
||||
}
|
||||
return _wantToCancel; //determines if a cancel request has been issued
|
||||
}
|
||||
|
||||
/**
|
||||
* Forces offlineTileEnabler to go online or offline.
|
||||
* If it is offline it will try to find a tile in the local database.
|
||||
*/
|
||||
function goOnlineOffline(){
|
||||
if(imgOfflineIndicator.offlineColor == "blue"){
|
||||
btnOnlineOffline.innerHTML = "2. Go Online";
|
||||
imgOfflineIndicator.src = redPinPath;
|
||||
imgOfflineIndicator.offlineColor = "red";
|
||||
tileLayer.goOffline();
|
||||
}
|
||||
else{
|
||||
btnOnlineOffline.innerHTML = "2. Go Offline";
|
||||
imgOfflineIndicator.src = bluePinPath;
|
||||
imgOfflineIndicator.offlineColor = "blue";
|
||||
tileLayer.goOnline();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Manually starts the process to download and store tiles
|
||||
* in the local database
|
||||
*/
|
||||
function downloadTiles(){
|
||||
tileLayer.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 = tileLayer.getExtentBuffer(EXTENT_BUFFER,map.extent);
|
||||
_wantToCancel = false;
|
||||
tileLayer.prepareForOffline(zoom.min, zoom.max, extent, reportProgress.bind(this));
|
||||
globalState.downloadState = 'downloading';
|
||||
}
|
||||
}
|
||||
}.bind(this))
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 cacheLoaderHandler(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));
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
function panLeft(){
|
||||
map.panLeft();
|
||||
}
|
||||
</script>
|
||||
|
||||
</body>
|
||||
138
samples/css/modular-popup.css
Normal file
138
samples/css/modular-popup.css
Normal file
@ -0,0 +1,138 @@
|
||||
.mod-popup-modal-background {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
opacity: 0.5;
|
||||
background-color: black;
|
||||
z-index: 99;
|
||||
display: table;
|
||||
}
|
||||
.mod-popup-input {
|
||||
border-bottom: solid #ffffff 1px;
|
||||
width: 80%;
|
||||
display: table-cell;
|
||||
vertical-align: middle;
|
||||
padding: 12px;
|
||||
}
|
||||
.mod-popup-label {
|
||||
padding: 12px;
|
||||
width: 20%;
|
||||
display: table-cell;
|
||||
vertical-align: middle;
|
||||
background-color: dimgrey;
|
||||
}
|
||||
.mod-popup-label-top-left {
|
||||
padding: 12px;
|
||||
width: 20%;
|
||||
display: table-cell;
|
||||
vertical-align: middle;
|
||||
background-color: dimgray;
|
||||
border-top-left-radius: 10px;
|
||||
}
|
||||
.mod-popup-table-row {
|
||||
color: white;
|
||||
border-radius: 10px;
|
||||
display: table-row;
|
||||
}
|
||||
.mod-popup-stop-input {
|
||||
border-radius: 5px;
|
||||
font-size: x-large;
|
||||
width: 90%;
|
||||
height: 75%;
|
||||
padding: 5px;
|
||||
}
|
||||
.mod-popup-stop-input-disabled {
|
||||
color: white;
|
||||
border-radius: 5px;
|
||||
font-size: x-large;
|
||||
background-color: dimgray;
|
||||
opacity: 1.0; /* For safari display bug */
|
||||
padding: 5px;
|
||||
width: 90%;
|
||||
height: 75%;
|
||||
}
|
||||
.mod-popup-button {
|
||||
position: relative;
|
||||
float: left;
|
||||
color: #ffffff;
|
||||
font-size: x-large;
|
||||
border-radius: 10px;
|
||||
width: 45%;
|
||||
padding: 8px;
|
||||
background-color: #000000;
|
||||
border: solid #ffffff 2px;
|
||||
}
|
||||
.mod-popup-button:active {
|
||||
position: relative;
|
||||
float: left;
|
||||
color: #000000;
|
||||
font-size: x-large;
|
||||
border-radius: 10px;
|
||||
width: 45%;
|
||||
padding: 8px;
|
||||
background-color: lightyellow;
|
||||
border: solid #ffffff 2px;
|
||||
}
|
||||
.mod-popup-button-cancel {
|
||||
position: relative;
|
||||
float: left;
|
||||
color: lightgray;
|
||||
font-size: x-large;
|
||||
border-radius: 10px;
|
||||
width: 100%;
|
||||
padding: 8px;
|
||||
background-color: #000000;
|
||||
border: solid lightgray 1px;
|
||||
}
|
||||
.mod-popup-button-cancel:active {
|
||||
position: relative;
|
||||
float: left;
|
||||
color: #000000;
|
||||
font-size: x-large;
|
||||
border-radius: 10px;
|
||||
width: 100%;
|
||||
padding: 8px;
|
||||
background-color: lightyellow;
|
||||
border: solid lightgray 1px;
|
||||
}
|
||||
.mod-popup-button-div {
|
||||
padding: 12px;
|
||||
display: table-cell;
|
||||
background-color: #000000;
|
||||
}
|
||||
.mod-popup-button-div-bottom-left {
|
||||
padding: 12px;
|
||||
display: table-cell;
|
||||
background-color: #000000;
|
||||
border-bottom-left-radius: 8px;
|
||||
}
|
||||
@media (max-width: 500px) {
|
||||
.mod-popup-button {
|
||||
font-size: small;
|
||||
}
|
||||
.mod-popup-button:active{
|
||||
font-size: small;
|
||||
}
|
||||
.mod-popup-button-cancel {
|
||||
font-size: small;
|
||||
}
|
||||
.mod-popup-button-cancel:active {
|
||||
font-size: small;
|
||||
}
|
||||
}
|
||||
@media (max-width: 450px) {
|
||||
.mod-popup-button {
|
||||
font-size: x-small;
|
||||
}
|
||||
.mod-popup-button:active{
|
||||
font-size: x-small;
|
||||
}
|
||||
.mod-popup-button-cancel {
|
||||
font-size: x-small;
|
||||
}
|
||||
.mod-popup-button-cancel:active {
|
||||
font-size: x-small;
|
||||
}
|
||||
}
|
||||
BIN
samples/images/loading.gif
Executable file
BIN
samples/images/loading.gif
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 3.8 KiB |
@ -5,10 +5,10 @@
|
||||
"https://www.npmjs.org/doc/cli/npm-init.html"
|
||||
],
|
||||
"name": "manifest-generator",
|
||||
"manifestName": "appcache-tiles.appcache",
|
||||
"appHomePage": "appcache-tiles.html",
|
||||
"optimizedApiURL": "http://jsdev.arcgis.com/o/andygup/appcache1",
|
||||
"arcGISBaseURL": "http://js.arcgis.com/3.8",
|
||||
"manifestName": "appcache-features.appcache",
|
||||
"appHomePage": "appcache-features.html",
|
||||
"optimizedApiURL": "http://js.arcgis.com/o/agup_hack4co/appcacheFeatures",
|
||||
"arcGISBaseURL": "http://js.arcgis.com/3.9",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"description": "manifest generator project",
|
||||
@ -16,7 +16,7 @@
|
||||
"type": "git",
|
||||
"url": "https://github.com/Esri/offline-editor-js.git"
|
||||
},
|
||||
"author": "Lloyd Heberlie",
|
||||
"author": "Andy Gup",
|
||||
"license": "Apache 2",
|
||||
"bugs": {
|
||||
"url": "https://github.com/Esri/offline-editor-js/issues"
|
||||
|
||||
221
samples/tpk-layer.html
Normal file
221
samples/tpk-layer.html
Normal file
@ -0,0 +1,221 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<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>TPKLayer</title>
|
||||
<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";
|
||||
}
|
||||
#header-div{
|
||||
font-family: helvetica, serif;
|
||||
background: #000000;
|
||||
color: #ffffff;
|
||||
width: 100%;
|
||||
height: 90px;
|
||||
display:inline-block;
|
||||
vertical-align:middle;
|
||||
line-height: 50px;
|
||||
padding-left: 8px;
|
||||
}
|
||||
#input-container{
|
||||
position: absolute;
|
||||
}
|
||||
#file-input{
|
||||
margin-top: 10px;
|
||||
margin-left: 10px;
|
||||
position: absolute;
|
||||
float: left;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
#url-input{
|
||||
position: relative;
|
||||
/* float: left; */
|
||||
padding-left: 10px;
|
||||
margin-left: 10px;
|
||||
margin-top: 40px;
|
||||
width: 250px;
|
||||
}
|
||||
#url-btn{
|
||||
position: relative;
|
||||
/*float: left;*/
|
||||
}
|
||||
#header-title{
|
||||
position: relative;
|
||||
float: right;
|
||||
padding-right: 15px;
|
||||
}
|
||||
#loader-gif{
|
||||
display: block;
|
||||
visibility: hidden;
|
||||
position: relative;
|
||||
float: left;
|
||||
padding-left: 50%;
|
||||
padding-top: 25%;
|
||||
z-index: 10;
|
||||
}
|
||||
#map{
|
||||
position: absolute;
|
||||
left: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
/* Portrait */
|
||||
@media screen and (orientation:portrait) {
|
||||
#loader-gif{
|
||||
display: block;
|
||||
visibility: hidden;
|
||||
position: relative;
|
||||
float: left;
|
||||
padding-left: 50%;
|
||||
padding-top: 25%;
|
||||
z-index: 10;
|
||||
}
|
||||
}
|
||||
|
||||
/* Landscape */
|
||||
@media screen and (orientation:landscape) {
|
||||
#loader-gif{
|
||||
display: block;
|
||||
visibility: hidden;
|
||||
position: relative;
|
||||
float: left;
|
||||
padding-left: 50%;
|
||||
padding-top: 25%;
|
||||
z-index: 10;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="header-div">
|
||||
<div id="input-container">
|
||||
<input type="file" id="file-input" name="files[]" accept="application/zip"/>
|
||||
<input type="text" id="url-input" value="tpks/Beirut.zip" />
|
||||
<button id="url-btn">Get file via url</button>
|
||||
</div>
|
||||
<div id="header-title">TPKLayer demo</div>
|
||||
</div>
|
||||
<img id="loader-gif" src="images/loading.gif"/>
|
||||
|
||||
<div id="map"></div>
|
||||
|
||||
<script>
|
||||
var locationPath = location.pathname.replace(/\/[^/]+$/, "");
|
||||
var dojoConfig = {
|
||||
paths: {
|
||||
tpk: locationPath + "/../lib/tpk",
|
||||
tiles: locationPath + "/../lib/tiles"
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<script src="../vendor/IndexedDBShim/dist/IndexedDBShim.min.js"></script>
|
||||
<script src="http://js.arcgis.com/3.9/"></script>
|
||||
<script>
|
||||
require(["esri/map","tpk/TPKLayer","tpk/zip","dojo/on","dojo/_base/window","dojo/domReady!"],
|
||||
function(Map,TPKLayer,zip,on,win) {
|
||||
var map;
|
||||
var fileInput,tpkLayer, urlInputBtn;
|
||||
|
||||
var loading = dojo.byId("loader-gif");
|
||||
var getFileBtn = dojo.byId("url-btn");
|
||||
|
||||
initChooseLocalFile();
|
||||
initGetRemoteFile();
|
||||
|
||||
/**
|
||||
* Parse zipped TPK file and get the individual files.
|
||||
*/
|
||||
function zipParser(blob){
|
||||
|
||||
//IMPORTANT!
|
||||
zip.workerScriptsPath = locationPath + "/../lib/tpk/"; //tell zip.js where to find it's associated scripts
|
||||
|
||||
zip.createReader(new zip.BlobReader(blob), function (zipReader) {
|
||||
zipReader.getEntries(function (entries) {
|
||||
initMap(entries);
|
||||
zipReader.close(function(evt){
|
||||
console.log("Done reading zip file.")
|
||||
})
|
||||
}, function (err) {
|
||||
alert("There was a problem reading the file!: " + err);
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the Map and the TPKLayer
|
||||
*/
|
||||
function initMap(entries){
|
||||
tpkLayer = new TPKLayer();
|
||||
tpkLayer.on("progress", function (evt) {
|
||||
evt == "start" ? loading.style.visibility = "visible" : loading.style.visibility = "hidden";
|
||||
})
|
||||
tpkLayer.extend(entries);
|
||||
|
||||
map = new Map("map");
|
||||
map.addLayer(tpkLayer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Choose a TPK file from your local filesystem
|
||||
*/
|
||||
function initChooseLocalFile(){
|
||||
fileInput = document.getElementById("file-input");
|
||||
fileInput.addEventListener('change', function() {
|
||||
console.log("File success");
|
||||
zipParser(fileInput.files[0]);
|
||||
},false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a TPK file from a remote server via a URL
|
||||
*/
|
||||
function initGetRemoteFile(){
|
||||
getFileBtn.innerHTML = "Get file via url";
|
||||
var url = document.getElementById("url-input");
|
||||
urlInputBtn = document.getElementById("url-btn");
|
||||
urlInputBtn.onclick = function(){
|
||||
var xhrRequest = new XMLHttpRequest();
|
||||
xhrRequest.open("GET", url.value, true);
|
||||
xhrRequest.responseType = "blob";
|
||||
xhrRequest.onprogress = function(evt){
|
||||
loading.style.visibility = "visible";
|
||||
var percent = (parseFloat(evt.loaded / evt.totalSize) * 100).toFixed(0);
|
||||
getFileBtn.innerHTML = "Get file via url " + percent + "%";
|
||||
console.log("Begin downloading remote tpk file...")
|
||||
}
|
||||
|
||||
xhrRequest.error = function(err){console.log("ERRROR")}
|
||||
|
||||
xhrRequest.onload = function(oEvent) {
|
||||
if(this.status == 200) {
|
||||
loading.style.visibility = "hidden";
|
||||
console.log("Remote tpk download finished.")
|
||||
zipParser(this.response)
|
||||
}
|
||||
else{
|
||||
loading.style.visibility = "hidden";
|
||||
alert("There was a problem loading the file. " + this.status + ": " + this.statusText )
|
||||
}
|
||||
};
|
||||
|
||||
xhrRequest.send();
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
BIN
samples/tpks/Beirut.zip
Normal file
BIN
samples/tpks/Beirut.zip
Normal file
Binary file not shown.
122
test/SpecRunner.TPKLayer.html
Normal file
122
test/SpecRunner.TPKLayer.html
Normal file
@ -0,0 +1,122 @@
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
|
||||
"http://www.w3.org/TR/html4/loose.dtd">
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<title>Jasmine Spec Runner - TPKLayer</title>
|
||||
|
||||
<link rel="shortcut icon" type="image/png" href="../vendor/jasmine-1.3.1/jasmine_favicon.png">
|
||||
<link rel="stylesheet" type="text/css" href="../vendor/jasmine-1.3.1/jasmine.css">
|
||||
<script type="text/javascript" src="../vendor/jasmine-1.3.1/jasmine.js"></script>
|
||||
<script type="text/javascript" src="../vendor/jasmine-1.3.1/jasmine-html.js"></script>
|
||||
<script type="text/javascript" src="../vendor/jasmine.async/lib/jasmine.async.js"></script>
|
||||
<style type="text/css">
|
||||
#loader-gif{
|
||||
display: block;
|
||||
visibility: hidden;
|
||||
position: relative;
|
||||
float: left;
|
||||
z-index: 10;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
var locationPath = location.pathname.replace(/\/[^/]+$/, "");
|
||||
var dojoConfig = {
|
||||
paths: {
|
||||
edit: locationPath + "../../lib/edit",
|
||||
tpk: locationPath + "/../lib/tpk",
|
||||
tiles: locationPath + "/../lib/tiles"
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<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">
|
||||
<script src="http://js.arcgis.com/3.9/"></script>
|
||||
|
||||
<!-- include spec files here... -->
|
||||
<script type="text/javascript" src="spec/tpkLayerSpec.js"></script>
|
||||
<script type="text/javascript">
|
||||
"use strict"
|
||||
|
||||
var getFileBtn, fileInput, FILE;
|
||||
var loading = dojo.byId("loader-gif");
|
||||
var tilesEntries = null;
|
||||
var tpkLayer = null;
|
||||
|
||||
require(["esri/map",
|
||||
"esri/layers/GraphicsLayer", "esri/graphic", "esri/symbols/SimpleFillSymbol", "esri/symbols/SimpleMarkerSymbol", "esri/symbols/SimpleLineSymbol",
|
||||
"esri/SpatialReference","esri/geometry",
|
||||
"edit/attachmentsStore",
|
||||
"dojo/dom", "dojo/on",
|
||||
"tpk/TPKLayer","tpk/zip",
|
||||
"dojo/domReady!"],
|
||||
function(Map,
|
||||
GraphicsLayer, Graphic, SimpleFillSymbol, SimpleMarkerSymbol, SimpleLineSymbol,
|
||||
SpatialReference, geometry,
|
||||
AttachmentsStore,
|
||||
dom, on, TPKLayer,zip)
|
||||
{
|
||||
var jasmineEnv;
|
||||
loading = dojo.byId("loader-gif");
|
||||
getFileBtn = dojo.byId("url-btn");
|
||||
//IMPORTANT!
|
||||
zip.workerScriptsPath = locationPath + "/../lib/tpk/"; //tell zip.js where to find it's associated scripts
|
||||
|
||||
tpkLayer = new TPKLayer();
|
||||
|
||||
initChooseLocalFile();
|
||||
|
||||
/**
|
||||
* Choose a TPK file from your local filesystem
|
||||
*/
|
||||
function initChooseLocalFile(){
|
||||
fileInput = document.getElementById("file-input");
|
||||
fileInput.addEventListener('change', function() {
|
||||
console.log("File success");
|
||||
FILE = fileInput.files[0];
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
console.log("everything ok!");
|
||||
|
||||
jasmineEnv = jasmine.getEnv();
|
||||
jasmineEnv.updateInterval = 1000;
|
||||
jasmineEnv.defaultTimeoutInterval = 10000; // 1 sec
|
||||
var htmlReporter = new jasmine.HtmlReporter();
|
||||
|
||||
jasmineEnv.addReporter(htmlReporter);
|
||||
|
||||
jasmineEnv.specFilter = function(spec) {
|
||||
return htmlReporter.specFilter(spec);
|
||||
};
|
||||
|
||||
jasmineEnv.execute();
|
||||
|
||||
}
|
||||
catch(err)
|
||||
{
|
||||
alert(err);
|
||||
}
|
||||
|
||||
},false);
|
||||
}
|
||||
|
||||
}); // require()
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<span>Select a tpk file to launch unit testing. You may have to change it to .zip</span>
|
||||
<br>
|
||||
<br>
|
||||
<div id="input-container">
|
||||
<input type="file" id="file-input" name="files[]" accept="application/zip"/>
|
||||
</div>
|
||||
|
||||
<img id="loader-gif" src="../samples/images/loading.gif"/>
|
||||
</body>
|
||||
</html>
|
||||
87
test/SpecRunner.offlineTilesEnablerLayer.html
Normal file
87
test/SpecRunner.offlineTilesEnablerLayer.html
Normal file
@ -0,0 +1,87 @@
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
|
||||
"http://www.w3.org/TR/html4/loose.dtd">
|
||||
<html>
|
||||
<head>
|
||||
<title>Jasmine Spec Runner - Tiles</title>
|
||||
|
||||
<link rel="shortcut icon" type="image/png" href="../vendor/jasmine-1.3.1/jasmine_favicon.png">
|
||||
<link rel="stylesheet" type="text/css" href="../vendor/jasmine-1.3.1/jasmine.css">
|
||||
<script type="text/javascript" src="../vendor/jasmine-1.3.1/jasmine.js"></script>
|
||||
<script type="text/javascript" src="../vendor/jasmine-1.3.1/jasmine-html.js"></script>
|
||||
<script type="text/javascript" src="../vendor/jasmine.async/lib/jasmine.async.js"></script>
|
||||
|
||||
<script>
|
||||
var dojoConfig = {
|
||||
paths: { tiles: location.pathname.replace(/\/[^/]+$/, "") + "../../lib/tiles" }
|
||||
}
|
||||
</script>
|
||||
|
||||
<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">
|
||||
<script src="http://js.arcgis.com/3.9/"></script>
|
||||
|
||||
<!-- include spec files here... -->
|
||||
<script type="text/javascript" src="spec/offlineTilesEnablerLayerSpec.js"></script>
|
||||
|
||||
<script type="text/javascript">
|
||||
|
||||
var g_map;
|
||||
var g_basemapLayer;
|
||||
|
||||
require(["esri/map",
|
||||
"esri/layers/GraphicsLayer", "esri/graphic", "esri/symbols/SimpleFillSymbol",
|
||||
"esri/dijit/Scalebar", "esri/arcgis/utils", "esri/geometry",
|
||||
"dojo/dom", "dojo/on", "dojo/query",
|
||||
"esri/urlUtils", "esri/geometry/webMercatorUtils",
|
||||
"tiles/OfflineTilesEnablerLayer",
|
||||
"dojo/dom-construct", "dojo/domReady!"],
|
||||
function(Map,
|
||||
GraphicsLayer, Graphic, SimpleFillSymbol,
|
||||
Scalebar, esriUtils, geometry,
|
||||
dom, on, query,
|
||||
urlUtils, webMercatorUtils,
|
||||
OfflineTilesEnablerLayer,
|
||||
domConstruct)
|
||||
{
|
||||
g_basemapLayer = new OfflineTilesEnablerLayer("http://server.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer",function(evt){
|
||||
console.log("Tile Layer Loaded.");
|
||||
},true);
|
||||
|
||||
g_map = new Map("map", {
|
||||
center: [-3.695, 40.412], // Madrid center
|
||||
zoom: 14,
|
||||
sliderStyle: "small"
|
||||
});
|
||||
|
||||
g_map.on('load', test);
|
||||
|
||||
g_map.addLayer(g_basemapLayer);
|
||||
|
||||
function test()
|
||||
{
|
||||
var jasmineEnv = jasmine.getEnv();
|
||||
jasmineEnv.updateInterval = 1000;
|
||||
jasmineEnv.defaultTimeoutInterval = 20000; // 20 sec
|
||||
var htmlReporter = new jasmine.HtmlReporter();
|
||||
|
||||
jasmineEnv.addReporter(htmlReporter);
|
||||
|
||||
jasmineEnv.specFilter = function(spec) {
|
||||
return htmlReporter.specFilter(spec);
|
||||
};
|
||||
|
||||
var currentWindowOnload = window.onload;
|
||||
|
||||
jasmineEnv.execute();
|
||||
|
||||
}; // test()
|
||||
|
||||
}); // require()
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="map" style="position: absolute; bottom: 0; right: 0; height:200px; width: 200px;"></div>
|
||||
<img src="" alt="" id="fakeTile" style="display:none;">
|
||||
</body>
|
||||
</html>
|
||||
239
test/spec/offlineTilesEnablerLayerSpec.js
Normal file
239
test/spec/offlineTilesEnablerLayerSpec.js
Normal file
@ -0,0 +1,239 @@
|
||||
"use strict"
|
||||
|
||||
describe("offline enabler custom layer library", function()
|
||||
{
|
||||
var async = new AsyncSpec(this);
|
||||
|
||||
async.it("validate map", function(done)
|
||||
{ console.log("VALIDATE !!!!!")
|
||||
expect(g_map).toEqual(jasmine.any(Object));
|
||||
expect(g_map.id).toEqual("map");
|
||||
done();
|
||||
});
|
||||
|
||||
async.it("validate tiled layer", function(done)
|
||||
{
|
||||
expect(g_basemapLayer).toEqual(jasmine.any(Object));
|
||||
expect(g_basemapLayer.tileInfo).toEqual(jasmine.any(Object));
|
||||
done();
|
||||
});
|
||||
|
||||
// async.it("extends the tiled layer object", function(done)
|
||||
// {
|
||||
// expect(g_basemapLayer.goOffline).toBeUndefined();
|
||||
// g_offlineTilesEnabler.extend(g_basemapLayer,function(success)
|
||||
// {
|
||||
// expect(success).toEqual(true);
|
||||
// expect(g_basemapLayer.goOffline).toEqual(jasmine.any(Function));
|
||||
// expect(g_basemapLayer.goOnline).toEqual(jasmine.any(Function));
|
||||
// expect(g_basemapLayer.getTileUrl).toEqual(jasmine.any(Function));
|
||||
// expect(g_basemapLayer._getTileUrl).toEqual(jasmine.any(Function));
|
||||
// expect(g_basemapLayer.prepareForOffline).toEqual(jasmine.any(Function));
|
||||
// expect(g_basemapLayer._storeTile).toEqual(jasmine.any(Function));
|
||||
// expect(g_basemapLayer.deleteAllTiles).toEqual(jasmine.any(Function));
|
||||
// expect(g_basemapLayer.offline).toEqual(jasmine.any(Object));
|
||||
// expect(g_basemapLayer.offline.store).toEqual(jasmine.any(Object));
|
||||
//
|
||||
// g_basemapLayer.offline.proxyPath = "../lib/resource-proxy/proxy.php";
|
||||
// done();
|
||||
// });
|
||||
// });
|
||||
//
|
||||
async.it("can go offline", function(done)
|
||||
{
|
||||
expect(g_basemapLayer.goOffline).toEqual(jasmine.any(Function));
|
||||
expect(g_basemapLayer.offline.online).toEqual(true);
|
||||
g_basemapLayer.goOffline();
|
||||
expect(g_basemapLayer.offline.online).toEqual(false);
|
||||
done();
|
||||
});
|
||||
|
||||
async.it("can go online", function(done)
|
||||
{
|
||||
expect(g_basemapLayer.goOffline).toEqual(jasmine.any(Function));
|
||||
expect(g_basemapLayer.offline.online).toEqual(false);
|
||||
g_basemapLayer.goOnline();
|
||||
expect(g_basemapLayer.offline.online).toEqual(true);
|
||||
done();
|
||||
})
|
||||
|
||||
async.it("delete all tiles", function(done)
|
||||
{
|
||||
g_basemapLayer.deleteAllTiles(function(success)
|
||||
{
|
||||
expect(success).toEqual(true);
|
||||
setTimeout(function()
|
||||
{
|
||||
g_basemapLayer.getOfflineUsage(function(usage)
|
||||
{
|
||||
expect(usage.tileCount).toEqual(0);
|
||||
done();
|
||||
});
|
||||
},1);
|
||||
});
|
||||
});
|
||||
|
||||
async.it("stores one tile", function(done)
|
||||
{
|
||||
g_basemapLayer.getOfflineUsage(function(usage)
|
||||
{
|
||||
expect(usage.tileCount).toEqual(0);
|
||||
g_basemapLayer._storeTile(14,6177,8023, function(success)
|
||||
{
|
||||
expect(success).toEqual(true);
|
||||
g_basemapLayer.getOfflineUsage(function(usage)
|
||||
{
|
||||
expect(usage.tileCount).toEqual(1);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
async.it("stores one tile again", function(done)
|
||||
{
|
||||
g_basemapLayer.getOfflineUsage(function(usage)
|
||||
{
|
||||
expect(usage.tileCount).toEqual(1);
|
||||
g_basemapLayer._storeTile(14,6177,8023, function(success)
|
||||
{
|
||||
expect(success).toEqual(true);
|
||||
g_basemapLayer.getOfflineUsage(function(usage)
|
||||
{
|
||||
expect(usage.tileCount).toEqual(1);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
async.it("gets level estimation", function(done)
|
||||
{
|
||||
require(["esri/geometry/Extent"],function(Extent)
|
||||
{
|
||||
var extent = new Extent({"xmin":-822542.2830377579,"ymin":4580841.761960262,"xmax":94702.05638410954,"ymax":5131188.365613382,"spatialReference":{"wkid":102100}});
|
||||
g_basemapLayer.estimateTileSize(function(tileSize){
|
||||
var estimation = g_basemapLayer.getLevelEstimation(extent,10,tileSize);
|
||||
expect(estimation.tileCount).toEqual(375);
|
||||
expect(estimation.sizeBytes).toEqual(estimation.tileCount * tileSize);
|
||||
|
||||
var estimation = g_basemapLayer.getLevelEstimation(extent,8,tileSize);
|
||||
expect(estimation.tileCount).toEqual(28);
|
||||
expect(estimation.sizeBytes).toEqual(estimation.tileCount * tileSize);
|
||||
var estimation = g_basemapLayer.getLevelEstimation(extent,2,tileSize);
|
||||
expect(estimation.tileCount).toEqual(2);
|
||||
expect(estimation.sizeBytes).toEqual(estimation.tileCount * tileSize);
|
||||
done();
|
||||
}.bind(this));
|
||||
});
|
||||
});
|
||||
|
||||
async.it("prepares the layer for offline usage", function(done)
|
||||
{
|
||||
require(["esri/geometry/Extent"], function(Extent)
|
||||
{
|
||||
g_basemapLayer.deleteAllTiles(function(success)
|
||||
{
|
||||
expect(success).toEqual(true);
|
||||
var extent = new Extent({"xmin":-822542.2830377579,"ymin":4580841.761960262,"xmax":94702.05638410954,"ymax":5131188.365613382,"spatialReference":{"wkid":102100}});
|
||||
var callCount = 0;
|
||||
var reportProgress = function(progress)
|
||||
{
|
||||
callCount += 1;
|
||||
expect(progress.error).not.toBeDefined();
|
||||
|
||||
if( progress.finishedDownloading )
|
||||
{
|
||||
g_basemapLayer.getOfflineUsage(function(usage)
|
||||
{
|
||||
expect(usage.tileCount).toEqual(28);
|
||||
expect(callCount).toEqual(29);
|
||||
done();
|
||||
});
|
||||
}
|
||||
|
||||
return false; // cancelRequested = false;
|
||||
}
|
||||
|
||||
g_basemapLayer.prepareForOffline(8,8,extent,reportProgress);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
async.it("get tile urls",function(done)
|
||||
{
|
||||
require(["esri/geometry/Extent"],function(Extent){
|
||||
var extent = new Extent({"xmin":-822542.2830377579,"ymin":4580841.761960262,"xmax":94702.05638410954,"ymax":5131188.365613382,"spatialReference":{"wkid":102100}});
|
||||
var cells = g_basemapLayer.getTileUrlsByExtent(extent,3);
|
||||
var regexp = /(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?/;
|
||||
for(var i = 0; i < cells.length; i++){
|
||||
var isUrl = regexp.test(cells[i]);
|
||||
expect(isUrl).toBe(true);
|
||||
}
|
||||
expect(cells.length).toBeGreaterThan(0);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
async.it("get extent buffer",function(done)
|
||||
{
|
||||
require(["esri/geometry/Extent"],function(Extent){
|
||||
var extent = new Extent({"xmin":-822542.2830377579,"ymin":4580841.761960262,"xmax":94702.05638410954,"ymax":5131188.365613382,"spatialReference":{"wkid":102100}});
|
||||
var newExtent = g_basemapLayer.getExtentBuffer(1000,extent);
|
||||
expect(newExtent.xmin).toBe(-823542.2830377579);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
async.it("returns placeholder urls when offline", function(done)
|
||||
{
|
||||
require(["dojo/dom"], function(dom)
|
||||
{
|
||||
var fakeTile = dom.byId('fakeTile');
|
||||
|
||||
g_basemapLayer.goOnline();
|
||||
var onlineUrl = g_basemapLayer.getTileUrl(14,6178,8023);
|
||||
|
||||
//NOTE: We are getting new attributes at ArcGIS JS API v3.8 : blankTile=false&_ts=1393031666639 <last part is a random number>
|
||||
// http://services.arcgisonline.com/ArcGIS/rest/services/Canvas/World_Light_Gray_Base/MapServer/tile/14/6178/8023?blankTile=false&_ts=1393031666639"
|
||||
var tempUrl = onlineUrl.slice( 0, onlineUrl.indexOf('?'))
|
||||
expect(tempUrl).toEqual('http://server.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer/tile/14/6178/802');
|
||||
|
||||
g_basemapLayer.goOffline();
|
||||
var offlineUrl = fakeTile.src = g_basemapLayer.getTileUrl(14,6178,8023);
|
||||
expect(offlineUrl).toEqual('void:/14/6178/8023');
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
async.it("getMaxZoom", function(done){
|
||||
g_basemapLayer.getMaxZoom(function(result){
|
||||
expect(result).toBe(19);
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
async.it("getMinZoom", function(done){
|
||||
g_basemapLayer.getMinZoom(function(result){
|
||||
expect(result).toBe(0);
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
it("verifies ability to retrieve layer info",function(done){
|
||||
g_basemapLayer._getTileInfoPrivate("http://server.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer",function(result){
|
||||
var fixedResponse = result.replace(/\\'/g, "'");
|
||||
var resultObj = JSON.parse(fixedResponse);
|
||||
expect(resultObj).toEqual(jasmine.any(Object));
|
||||
})
|
||||
});
|
||||
|
||||
it("verifies ability to parse layer info",function(done){
|
||||
g_basemapLayer._getTileInfoPrivate("http://server.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer",function(result){
|
||||
g_basemapLayer.parseGetTileInfo(result,function(result){
|
||||
expect(result).toEqual(jasmine.any(Object));
|
||||
})
|
||||
})
|
||||
});
|
||||
|
||||
});
|
||||
288
test/spec/tpkLayerSpec.js
Normal file
288
test/spec/tpkLayerSpec.js
Normal file
File diff suppressed because one or more lines are too long
@ -1,5 +1,12 @@
|
||||
/**
|
||||
* Helper Class for working with the application cache.
|
||||
*
|
||||
* Listen for the following events:
|
||||
* UPDATE_READY - an update to the cache is ready
|
||||
* UPDATE_NONE - the cache hasn't changed since the last app load.
|
||||
* CACHE_LOADED - the cache has finished loading.
|
||||
* CACHE_ERROR - an error was thrown by the browser while attempting to load the cache.
|
||||
*
|
||||
* For more information on application cache:
|
||||
* https://developer.mozilla.org/en-US/docs/HTML/Using_the_application_cache
|
||||
* Many thanks and all kudos go to the following blog posts:
|
||||
@ -14,6 +21,7 @@ define([
|
||||
UPDATE_READY: "update-ready",
|
||||
UPDATE_NONE: "no-update",
|
||||
CACHE_EVENT: "cache-event",
|
||||
CACHE_LOADED: "cache-loaded",
|
||||
CACHE_ERROR: "cache-error",
|
||||
appCache: window.applicationCache,
|
||||
|
||||
@ -101,6 +109,12 @@ define([
|
||||
},
|
||||
|
||||
_handleCacheEvents:function(evt){
|
||||
if(evt.hasOwnProperty("total") && evt.hasOwnProperty("loaded")){
|
||||
if(evt.total == evt.loaded){
|
||||
console.log("appCacheManager: cache has finished loading.")
|
||||
this.emit(this.CACHE_LOADED,"cache-loaded");
|
||||
}
|
||||
}
|
||||
this.emit(this.CACHE_EVENT,evt);
|
||||
},
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user