Merge pull request #199 from andygup/gh-pages

Update gh-pages. Closes #198.
This commit is contained in:
Andy 2014-06-16 14:22:31 -06:00
commit 44e6f46066
39 changed files with 7778 additions and 815 deletions

98
.jshintrc Normal file
View 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
}
}

View File

@ -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
View 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)

View File

@ -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
View 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.

View File

@ -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
View 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.

View File

@ -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)

View File

@ -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)
{

View File

@ -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;

View 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);
}
}
})

View File

@ -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>

File diff suppressed because one or more lines are too long

View File

@ -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");
}

View File

@ -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

View File

@ -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
View 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

File diff suppressed because one or more lines are too long

105
lib/tpk/autoCenterMap.js Normal file
View 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

File diff suppressed because it is too large Load Diff

557
lib/tpk/xml2json.js Normal file
View 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, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#x27;').replace(/\//g, '&#x2F;');
else
return str;
}
function unescapeXmlChars(str) {
return str.replace(/&amp;/g, '&').replace(/&lt;/g, '<').replace(/&gt;/g, '>').replace(/&quot;/g, '"').replace(/&#x27;/g, "'").replace(/&#x2F;/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
View 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
};
});

View File

@ -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 %>"
}

View File

@ -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).

View File

@ -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:
*

View File

@ -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>

View File

@ -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:
*

View File

@ -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>

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@ -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
View 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

Binary file not shown.

View 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>

View 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>

View 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

File diff suppressed because one or more lines are too long

View File

@ -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);
},