├── .gitignore ├── .gitmodules ├── .jshintrc ├── .npmignore ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Gruntfile.js ├── ISSUE_TEMPLATE.md ├── README.md ├── demo ├── api-doc.html ├── css │ └── style.css ├── header.html ├── images │ ├── appcachefeatures-demo-thumb.png │ ├── appcachetiles-demo-thumb.png │ ├── attachments-demo-thumb.png │ ├── cop-demo-thumb.png │ ├── favicon.ico │ ├── offline_arch.png │ ├── simple-edit-thumb.png │ ├── tiles-demo-thumb.png │ └── tpk-demo-thumb.png └── index.html ├── dist ├── offline-edit-advanced-min.js ├── offline-edit-advanced-src.js ├── offline-edit-basic-min.js ├── offline-edit-basic-src.js ├── offline-tiles-advanced-min.js ├── offline-tiles-advanced-src.js ├── offline-tiles-basic-min.js ├── offline-tiles-basic-src.js ├── offline-tpk-min.js └── offline-tpk-src.js ├── doc ├── attachments.md ├── dependencies.md ├── gettingstartedfulloffline.md ├── howtouseappcache.md ├── howtouseofmadvancedlibrary.md ├── howtouseofmbasic.md ├── howtousetiles.md ├── howtousetpklibrary.md ├── migratefromv1tov2.md ├── offlineeditadvanced.md ├── offlineeditbasic.md ├── offlinetilesadvanced.md ├── offlinetilesbasic.md └── tpklayer.md ├── lib ├── edit │ ├── OfflineEditAdvanced.js │ ├── OfflineEditBasic.js │ ├── OfflineEditNS.js │ ├── attachmentsStore.js │ ├── editStorePOLS.js │ ├── editsStore.js │ └── offlineJSOptions.js ├── tiles │ ├── FileSaver.js │ ├── LICENSE.md │ ├── OfflineTilesAdvanced.js │ ├── OfflineTilesBasic.js │ ├── OfflineTilesNS.js │ ├── README.md │ ├── TilesCore.js │ ├── TilesStore.js │ ├── base64string.js │ ├── base64utils.js │ ├── blank_map_tile.png │ ├── lzString.js │ ├── notile.png │ ├── notile.psd │ └── tilingScheme.js └── tpk │ ├── OfflineTpkNS.js │ ├── README.md │ ├── TPKLayer.js │ ├── autoCenterMap.js │ ├── inflate.js │ ├── xml2json.js │ └── zip.js ├── license.txt ├── package.json ├── samples ├── Gruntfile.js ├── README.md ├── appcache-features.appcache ├── appcache-features.html ├── appcache-tiles.appcache ├── appcache-tiles.html ├── appcache-twofeatureslayer-noedit.html ├── attachments-editor-secure.html ├── attachments-editor.html ├── css │ └── modular-popup.css ├── draw-pointlinepoly-offline.html ├── images │ ├── blue-pin.png │ ├── loading.gif │ └── red-pin.png ├── jsolib │ ├── dojo.js │ ├── nls │ │ └── dojo_en.js │ ├── resources │ │ └── blank.gif │ └── selector │ │ ├── acme.js │ │ └── lite.js ├── lib │ └── CleanFeatureService.js ├── package.json ├── service-inspector.html ├── simple-edit.html ├── simple-tiles.html ├── tpk-layer.html ├── tpks │ └── Beirut.zip └── widgets │ └── modal │ ├── css │ └── modal-popup.css │ ├── popup.js │ └── template │ └── popup.html ├── scripts ├── preprocessor.sh ├── pull.sh └── push.sh ├── test ├── SpecRunner.TPKLayer.html ├── SpecRunner.attachmentsStore.html ├── SpecRunner.editsStore.html ├── SpecRunner.offlineAttachments.html ├── SpecRunner.offlineEditAdvanced.TokenBased.html ├── SpecRunner.offlineEditAdvanced.html ├── SpecRunner.offlineEditAdvanced.oAuth.html ├── SpecRunner.offlineEditBasic.html ├── SpecRunner.offlineTilesAdvanced.TokenBased.html ├── SpecRunner.offlineTilesAdvanced.html ├── SpecRunner.offlineTilesBasic.html ├── images │ ├── blue-pin.png │ └── red-pin.png ├── secure_tiles_basic_test.html ├── spec │ ├── attachmentsStoreSpec.js │ ├── editsStoreSpec.js │ ├── editsStoreSpec2.js │ ├── offlineAttachmentsSpec.js │ ├── offlineEditingAdvancedSpec.js │ ├── offlineEditingBasicSpec.js │ ├── offlineTilesAdvancedSpec.js │ ├── offlineTilesAdvancedTokenSpec.js │ ├── offlineTilesBasicSpec.js │ └── tpkLayerSpec.js └── test-localStorage.html ├── tiny-image.png ├── utils ├── appCacheManager.js └── debouncer.js └── vendor └── jasmine-1.3.1 ├── MIT.LICENSE ├── jasmine-html.js ├── jasmine.css └── jasmine.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Mac 2 | .DS_Store 3 | Icon 4 | 5 | # Filesystem 6 | *.zip 7 | 8 | # Node 9 | node_modules 10 | 11 | # Grunt 12 | .grunt/ 13 | 14 | # npm 15 | 16 | *.log -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "vendor/jasmine.async"] 2 | path = vendor/jasmine.async 3 | url = https://github.com/derickbailey/jasmine.async.git 4 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | 3 | 4 | "maxerr" : 5000, 5 | "bitwise" : true, 6 | "camelcase" : false, 7 | "curly" : true, 8 | "eqeqeq" : false, 9 | "forin" : true, 10 | "immed" : true, 11 | "latedef" : false, 12 | "newcap" : true, 13 | "noarg" : true, 14 | "noempty" : true, 15 | "nonew" : true, 16 | "plusplus" : false, 17 | "quotmark" : true, 18 | 19 | 20 | 21 | 22 | "undef" : true, 23 | "unused" : "vars", 24 | "strict" : false, 25 | "trailing" : false, 26 | 27 | 28 | "asi" : false, 29 | "boss" : false, 30 | "debug" : false, 31 | "eqnull" : true, 32 | "es5" : false, 33 | "esnext" : false, 34 | "moz" : false, 35 | 36 | "evil" : false, 37 | "expr" : false, 38 | "funcscope" : true, 39 | "globalstrict" : false, 40 | "iterator" : false, 41 | "lastsemic" : false, 42 | "laxbreak" : false, 43 | "laxcomma" : false, 44 | "loopfunc" : true, 45 | "multistr" : false, 46 | "proto" : false, 47 | "scripturl" : true, 48 | "smarttabs" : false, 49 | "shadow" : false, 50 | "sub" : false, 51 | "supernew" : false, 52 | "validthis" : true, 53 | 54 | 55 | "browser" : true, 56 | "devel" : true, 57 | "couch" : false, 58 | "dojo" : false, 59 | "jquery" : false, 60 | "mootools" : false, 61 | "node" : false, 62 | "nonstandard" : false, 63 | "prototypejs" : false, 64 | "rhino" : false, 65 | "worker" : false, 66 | "wsh" : false, 67 | "yui" : false, 68 | 69 | "predef" : [ 70 | "define", 71 | "require" 72 | ], 73 | "globals" : { 74 | "define": false, 75 | "require": false, 76 | "O":false, 77 | "module": false 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # Mac 2 | .DS_Store 3 | Icon 4 | ._* 5 | .Spotlight-V100 6 | 7 | # SublimeText 8 | /*.sublime-project 9 | *.sublime-workspace 10 | *.sublime-project 11 | .idea/* -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Esri welcomes contributions from anyone and everyone. Please see our [guidelines for contributing](https://github.com/esri/contributing). 2 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | module.exports = function(grunt) { 4 | grunt.initConfig({ 5 | pkg: grunt.file.readJSON('package.json'), 6 | watch: { 7 | js: { 8 | files: [ 9 | 'Gruntfile.js', 10 | 'lib/*.js', 11 | 'lib/edit/*.js', 12 | 'lib/tiles/*.js', 13 | 'lib/tpk/*.js' 14 | ], 15 | 16 | tasks: ['jshint','concat', 'uglify'], 17 | options: { 18 | spawn: false 19 | } 20 | } 21 | }, 22 | jshint: { 23 | options: { 24 | jshintrc: '.jshintrc' 25 | }, 26 | files: { 27 | src: [ 28 | 'Gruntfile.js', 29 | 'lib/edit/*.js', 30 | 'lib/tiles/base64utils.js', 31 | 'lib/tiles/OfflineTilesBasic.js', 32 | 'lib/tiles/OfflineTilesAdvanced.js', 33 | 'lib/tiles/OfflineTilesNS.js', 34 | 'lib/tiles/TilesCore.js', 35 | 'lib/tiles/TilesStore.js', 36 | 'lib/tiles/base64string.js', 37 | 'lib/stiles/lzString.js', 38 | 'lib/tiles/tilingScheme.js', 39 | 'lib/tpk/autoCenterMap.js', 40 | 'lib/tpk/OfflineTpkNS.js', 41 | 'lib/tpk/TPKLayer.js' 42 | ] 43 | } 44 | }, 45 | concat: { 46 | options: { 47 | separator: '\n', 48 | banner: '/*! <%= pkg.name %> - v<%= pkg.version %> - <%= grunt.template.today("yyyy-mm-dd") %>\n' + 49 | '* Copyright (c) <%= grunt.template.today("yyyy") %> Environmental Systems Research Institute, Inc.\n' + 50 | '* Apache License' + 51 | '*/\n' 52 | }, 53 | /* All feature editing capabilities: adds, updates and deletes */ 54 | editAdvanced: { 55 | src: [ 56 | 'lib/edit/offlineJSOptions.js', 57 | 'lib/edit/OfflineEditAdvanced.js', 58 | 'lib/edit/OfflineEditNS.js', 59 | 'lib/edit/editsStore.js', 60 | 'lib/edit/attachmentsStore.js' 61 | ], 62 | dest: 'dist/offline-edit-advanced-src.js' 63 | }, 64 | editBasic: { 65 | src: [ 66 | 'lib/edit/offlineJSOptions.js', 67 | 'lib/edit/OfflineEditBasic.js', 68 | 'lib/edit/OfflineEditNS.js', 69 | 'lib/edit/editStorePOLS.js' 70 | ], 71 | dest: 'dist/offline-edit-basic-src.js' 72 | }, 73 | /* Tiles basic is for use with WebMaps. Cannot be reloaded or restarted while offline */ 74 | tilesBasic: { 75 | src: [ 76 | 'lib/tiles/OfflineTilesBasic.js', 77 | 'lib/tiles/OfflineTilesNS.js', 78 | 'lib/tiles/base64utils.js', 79 | 'lib/tiles/base64string.js', 80 | 'lib/tiles/lzString.js', 81 | 'lib/tiles/FileSaver.js', 82 | 'lib/tiles/TilesCore.js', 83 | 'lib/tiles/TilesStore.js', 84 | 'lib/tiles/tilingScheme.js' 85 | ], 86 | dest: 'dist/offline-tiles-basic-src.js' 87 | }, 88 | /* Tiles advanced is for use with tiled map services. Works with reload or restart while offline */ 89 | tilesAdvanced: { 90 | src: [ 91 | 'lib/tiles/OfflineTilesAdvanced.js', 92 | 'lib/tiles/OfflineTilesNS.js', 93 | 'lib/tiles/base64utils.js', 94 | 'lib/tiles/base64string.js', 95 | 'lib/tiles/lzString.js', 96 | 'lib/tiles/FileSaver.js', 97 | 'lib/tiles/TilesCore.js', 98 | 'lib/tiles/TilesStore.js', 99 | 'lib/tiles/tilingScheme.js' 100 | ], 101 | dest: 'dist/offline-tiles-advanced-src.js' 102 | }, 103 | /* TPKLayer - for working directly with tile packages (.tpk files) */ 104 | tpk: { 105 | src: [ 106 | 'lib/tpk/TPKLayer.js', 107 | 'lib/tpk/OfflineTpkNS.js', 108 | 'lib/tiles/TilesStore.js', 109 | 'lib/tiles/lzString.js', 110 | 'lib/tiles/base64String.js', 111 | 'lib/tpk/zip.js', 112 | 'lib/tpk/autoCenterMap.js', 113 | 'lib/tpk/inflate.js', 114 | 'lib/tpk/xml2json.js' 115 | ], 116 | dest: 'dist/offline-tpk-src.js' 117 | } 118 | }, 119 | 120 | uglify: { 121 | options: { 122 | compress: { 123 | drop_console: true //remove console.log statements :) 124 | }, 125 | beautify: { 126 | semicolons: false //Required: prevents dojo parser errors w/ minified files in this project 127 | }, 128 | preserveComments: /^!/, 129 | wrap: false 130 | // mangle: { 131 | // except: ['O'] 132 | // } 133 | }, 134 | dist: { 135 | files: { 136 | 'dist/offline-edit-advanced-min.js': ['dist/offline-edit-advanced-src.js'], 137 | 'dist/offline-edit-basic-min.js': ['dist/offline-edit-basic-src.js'], 138 | 'dist/offline-tiles-basic-min.js': ['dist/offline-tiles-basic-src.js'], 139 | 'dist/offline-tiles-advanced-min.js': ['dist/offline-tiles-advanced-src.js'], 140 | 'dist/offline-tpk-min.js': ['dist/offline-tpk-src.js'] 141 | } 142 | } 143 | } 144 | }); 145 | 146 | // Load required modules 147 | grunt.loadNpmTasks('grunt-contrib-concat'); 148 | grunt.loadNpmTasks('grunt-contrib-uglify'); 149 | grunt.loadNpmTasks('grunt-contrib-compress'); 150 | grunt.loadNpmTasks('grunt-contrib-watch'); 151 | grunt.loadNpmTasks('grunt-contrib-jshint'); 152 | 153 | grunt.registerTask('build',['jshint','concat','uglify']); 154 | grunt.registerTask('test',['jshint']); 155 | }; -------------------------------------------------------------------------------- /ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | #### Expected behavior 2 | 3 | - Which offline library are you using (e.g. OfflineEditBasic.js, OfflineTilesAdvanced, etc)? 4 | - What's the offline library version number? 5 | - Describe what you expected or wanted to happen. 6 | - What you are trying to achieve? 7 | 8 | #### Actual behavior 9 | 10 | - Describe what occurs in your code. 11 | - Specifically, what seems to work differently than you intended? 12 | - Provide any error messages you see in the console. 13 | 14 | #### Steps to reproduce the behavior 15 | 16 | **We can only help you if we're able to reproduce the behavior you describe above.** Please provide: 17 | 18 | 0. What operating system, browser and browser version are you using? 19 | 1. Steps to reproduce the behavior 20 | 2. A link to an app where we can carry out those steps (either our example pages, your publicly facing app, JSFiddle, JSBin, etc) 21 | 3. Relevant code snippet(s) (only if not easily obtained from the above link) -------------------------------------------------------------------------------- /demo/api-doc.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Offline-editor-js 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 23 | 24 | 49 | 50 | 51 | 52 | 53 | 56 | 57 | 58 | 59 | 60 |
61 |
62 |

API Docs

63 |

Documentation for the offline-editor-js JavaScript libraries.

64 |
65 |
66 | 67 |
68 | 69 |
70 |
71 | 72 |
73 |
74 |

Offline Editing - Basic

75 |

The offline-edit-basic-min.js library is for working with ArcGIS JS API points, lines and polygons in intermittent offline use cases, only. 76 |

77 |
78 |
79 | 87 |
88 |
89 | 90 |
91 |
92 |

Offline Editing - Advanced

93 |

The offline-edit-advanced-min.js library is for working with ArcGIS JS API points, lines, polygons and attachments in intermittent and full offline use cases. 94 | This library has limited support for attachments. 95 |

96 |
97 |
98 | 113 |
114 |
115 | 116 |
117 |
118 |

Offline Tiles - Basic

119 |

The offline-tiles-basic-min.js library is for use with ArcGIS.com web maps and partial/intermittently offline use cases. 120 | You won't be able to restart or reload your app when using this library offline. 121 |

122 |
123 |
124 | 132 |
133 |
134 | 135 |
136 |
137 |

Offline Tiles - Advanced

138 |

The offline-tiles-advanced-min.js library is for use with tiled map services in partial or fully offline use cases. 139 | If you have a requirement to reload or restart your app while offline you should use this library. 140 |

141 |
142 |
143 | 151 |
152 |
153 | 154 |
155 |
156 |

Offline TPK

157 |

The offline-tpk-min.js library is for working with TPK files in partial or fully offline use cases. 158 | TPK files are binary tile image packages. They can be used stand-alone or alongside tiled base maps. 159 |

160 |
161 |
162 | 170 |
171 |
172 | 173 |
174 |
175 |
176 | 177 | 183 | 184 | 186 | 187 | 188 | 189 | 190 | 191 | -------------------------------------------------------------------------------- /demo/css/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding-top: 50px; 3 | padding-bottom: 20px; 4 | } 5 | .thumbnail { 6 | width: 200px; 7 | } 8 | .navbar-brand-title { 9 | float: left; 10 | padding: 15px; 11 | height: 50px; 12 | line-height: 20px; 13 | font-size: large; 14 | color: #000000; 15 | } 16 | .navbar { 17 | background-color: #6699cc !important; 18 | } 19 | 20 | .navbar-nav:hover { 21 | color: black; 22 | } 23 | 24 | .navbar-brand-title:hover { 25 | color: lightgray; 26 | text-decoration: none; 27 | } -------------------------------------------------------------------------------- /demo/header.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /demo/images/appcachefeatures-demo-thumb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Esri/offline-editor-js/f4d324b01ab5d3912088509c001d7dbbde4746ae/demo/images/appcachefeatures-demo-thumb.png -------------------------------------------------------------------------------- /demo/images/appcachetiles-demo-thumb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Esri/offline-editor-js/f4d324b01ab5d3912088509c001d7dbbde4746ae/demo/images/appcachetiles-demo-thumb.png -------------------------------------------------------------------------------- /demo/images/attachments-demo-thumb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Esri/offline-editor-js/f4d324b01ab5d3912088509c001d7dbbde4746ae/demo/images/attachments-demo-thumb.png -------------------------------------------------------------------------------- /demo/images/cop-demo-thumb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Esri/offline-editor-js/f4d324b01ab5d3912088509c001d7dbbde4746ae/demo/images/cop-demo-thumb.png -------------------------------------------------------------------------------- /demo/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Esri/offline-editor-js/f4d324b01ab5d3912088509c001d7dbbde4746ae/demo/images/favicon.ico -------------------------------------------------------------------------------- /demo/images/offline_arch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Esri/offline-editor-js/f4d324b01ab5d3912088509c001d7dbbde4746ae/demo/images/offline_arch.png -------------------------------------------------------------------------------- /demo/images/simple-edit-thumb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Esri/offline-editor-js/f4d324b01ab5d3912088509c001d7dbbde4746ae/demo/images/simple-edit-thumb.png -------------------------------------------------------------------------------- /demo/images/tiles-demo-thumb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Esri/offline-editor-js/f4d324b01ab5d3912088509c001d7dbbde4746ae/demo/images/tiles-demo-thumb.png -------------------------------------------------------------------------------- /demo/images/tpk-demo-thumb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Esri/offline-editor-js/f4d324b01ab5d3912088509c001d7dbbde4746ae/demo/images/tpk-demo-thumb.png -------------------------------------------------------------------------------- /doc/attachments.md: -------------------------------------------------------------------------------- 1 | # Attachment Support 2 | The __offline-edit-advanced-min.js__ has support for attachments in offline mode. See [attachments-editor.html](../samples/attachments-editor.html) sample. 3 | 4 | ## What you can do: 5 | While your application is in `OFFLINE` mode, you can: 6 | 7 | * add attachments to any feature, either a feature that already exists in the server or a newly added feature. 8 | * delete attachments from features if you have pre-cached the attachments or if you have added a feature while offline you can delete it from the local database. 9 | * query for attachments of a particular feature. It will only return attachments that have been added while offline. 10 | * view the attached files (see __limitations__ below) 11 | * when the app goes to `ONLINE` mode, all attachments are sent back to the server and removed from the local database. 12 | 13 | ## How you do use it: 14 | You can either use the ArcGIS FeatureLayer API _(esri.layers.FeatureLayer)_ directly or use the [AttachmentEditor](https://developers.arcgis.com/javascript/jsapi/attachmenteditor-amd.html) widget that supports feature attachment editing. Both approaches work well, and the code you write works the same either if you are on `ONLINE` or `OFFLINE` modes. 15 | 16 | The only differences in your code are: 17 | 18 | * create an OfflineEditAdvanced instance that is enabled for attachment support. Make sure you initialize the attachments database: 19 | 20 | var offlineEdit = new O.esri.Edit.OfflineEditAdvanced(); 21 | offlineEdit.initAttachments(function(success, error){ . . . }); 22 | 23 | * extend your featureLayers with offline editing functionality: 24 | 25 | offlineEdit.extend(featureLayer, function(success, error) 26 | { 27 | console.log("layer extended", success? "success" : "failed"); 28 | }); 29 | 30 | You can also modified the database's name and object store name. This functionality is typically used for advanced 31 | users that have a requirement to run multiple databases: 32 | 33 | var offlineEdit = new O.esri.Edit.OfflineEditAdvanced(); 34 | offlineEdit.ATTACHMENTS_DB_NAME = "attachment-store-two"; 35 | offlineEdit.ATTACHMENTS_DB_OBJECTSTORE_NAME = "attachments-two"; 36 | 37 | offlineEdit.initAttachments(function(success, error){ . . . }); 38 | 39 | ### Using the FeatureLayer API 40 | The FeatureLayer API for handling attachments consists primarily of four methods. In general you should let `OfflineEditAdvanced` 41 | handle interactions with attachments and it's not recommended to interact with the attachments database directly. 42 | 43 | * `layer.queryAttachmentInfos(objectId,callback,errback)` [doc](https://developers.arcgis.com/javascript/jsapi/featurelayer.html#queryattachmentinfos) 44 | * `layer.addAttachment(objectId, formNode, callback, errback)` [doc](https://developers.arcgis.com/javascript/jsapi/featurelayer.html#addattachment) 45 | * `layer.updateAttachment(objectId, attachmentId, formNode, callback, errback)` - as of April 2015 the ArcGIS API for JavaScript document has this functionality but it's not documented. That should hopefully be fixed in the next release of the JS API. 46 | * `layer.deleteAttachments(objectId, attachmentIds, callback, errback)` [doc](https://developers.arcgis.com/javascript/jsapi/featurelayer.html#deleteattachments) 47 | 48 | They work the same both in ONLINE and OFFLINE mode. In OFFLINE mode, attachments will be kept in the local database (indexeddb) and sent back to the server when you call `offlineEdit.goOnline()` 49 | 50 | ## Getting database usage 51 | Once a feature layer is extended you can find out how big the database and how many attachments are stored by using the following pattern: 52 | 53 | layer.getAttachmentsUsage(function(usage, error) { 54 | console.log("Size: " + usage.sizeBytes + ", attachmentCount: " + usage.attachmentCount); 55 | }); 56 | 57 | ## Resetting the database 58 | Under certain circumstances you may want to force the database to delete everything. 59 | 60 | layer.resetAttachmentsDatabase(function(result, error) { 61 | console.log("Reset succes: " + result); // result is a boolean 62 | }); 63 | 64 | ### Using the AttachmentEditor widget 65 | The [AttachmentEditor](https://developers.arcgis.com/javascript/jsapi/attachmenteditor-amd.html) is not very fancy, but it's easy to work with: 66 | 67 | map.infoWindow.setContent("
"); 68 | map.infoWindow.resize(350,200); 69 | var attachmentEditor = new AttachmentEditor({}, dom.byId("content")); 70 | attachmentEditor.startup(); 71 | 72 | featureLayer.on("click", function(evt) 73 | { 74 | var event = evt; 75 | var objectId = evt.graphic.attributes[featureLayer.objectIdField]; 76 | map.infoWindow.setTitle(objectId); 77 | attachmentEditor.showAttachments(event.graphic,featureLayer); 78 | map.infoWindow.show(evt.screenPoint, map.getInfoWindowAnchor(evt.screenPoint)); 79 | }); 80 | 81 | The widget internally uses the FeatureLayer API, and it works well in OFFLINE mode. 82 | 83 | 84 | ## Limitations 85 | Attachment support in OFFLINE mode has some limitations: 86 | 87 | * While in OFFLINE mode, features in a featureLayer don't know whether they have any attachments on the server or any other information about attachments unless you specifically build out that functionality. Therefore `queryAttachmentInfos()` and `deleteAttachments()` won't take their respective attachments into account. Calling `queryAttachmentInfos()` will only return locally stored attachments and `deleteAttachments()` can also only remove local attachments. -------------------------------------------------------------------------------- /doc/dependencies.md: -------------------------------------------------------------------------------- 1 | # Technical Dependencies 2 | 3 | The offline-editor-js projects includes but is not limited to the following dependencies: 4 | 5 | * indexedDB. Storage limits for indexedDB are not necessarily consistent across browsers. Here is a Mozilla [document](https://developer.mozilla.org/en-US/docs/IndexedDB#Storage_limits) discussing limits across different browsers. 6 | * Advanced users of the library should be aware that JavaScript stores some strings as UTF-16. More information can be found in this Mozilla [article](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/length). 7 | * If a user completely flushes their browser cache all queued edits, tiles as well as anything stored in local storage, IndexedDB, SQLite or even the appcache will most likely be lost. 8 | * Data stored by the library should persist if the browser is shutdown and restarted. However, this does not mean that your application will be returned to its correct state. A client web application's state has to be programmatically managed. This includes the state of items including but not limited to feature layers within your application. 9 | * Browser support for all the functionality in this library isn't gauranteed, of course. 10 | 11 | -------------------------------------------------------------------------------- /doc/gettingstartedfulloffline.md: -------------------------------------------------------------------------------- 1 | Getting started with full offline editing 2 | ========================================= 3 | 4 | Full offline editing involves setting up your application to survive a browser restart while the mobile device is offline. This short document outlines the various pieces you need to help ensure your web application will work successfully in a full offline environment. 5 | 6 | While this document focuses on editing, some of the patterns recommended here also apply to handling of map tiles while offline. 7 | 8 | **Workflows** 9 | 10 | Here are the top-level application workflows you'll encounter in offline apps: 11 | 12 | - Online + feature layer not extended for offline use = edits always sync with feature service. 13 | - Online + feature layer extended = edits always immediately sync with feature service. Edits pass-thru completely. 14 | - Offline + feature layer extended = edits stored in database and the dataStore is updated by default each time there is a new edit registered. 15 | - Returning online with pending edits + feature layer extended = edits are synced with feature service from local database. 16 | - Returning online with pending edits + feature layer not extended = no edits will be synced. 17 | - Always offline + feature layer extended = you can retrieve the edits programmatically and transfer them to a thumbdrive or equivalent to sync them manually. 18 | 19 | **Overview of the Pieces** 20 | 21 | Restarting a web app offline involves a number of critical pieces that have to be in place. The vast majority of web apps are not designed to survive an offline restart so you have to take extra steps to insure the app reinitializes just like it would if it was online. 22 | 23 | 1. When working with offline apps, you take full responsbility for having a thorough understanding of your application's initialization life cycle. The order in which the various HTML, CSS, .js libraries and image files are parsed by the browser is very important. As you'll find out, if there are any inter-dependencies between libraries, then an offline restart will expose them. 24 | 2. Offline apps will require an Application Cache or Service Workers to pull down all HTML, CSS, .js files and images that an app uses while it is online. 25 | 3. Offline apps require a way to determine if there is an internet connection or not. 26 | 4. Not all JavaScript code will work after an offline restart, such as the Editor Widget. It's safe to assume that the vast majority of JavaScript code ever written was written for fully-online usage and you'll need to take extra care with application initialization. 27 | 5. You'll need to make some decisions on how to manage certain aspects of the feature layer while it is fully offline. OfflineEditAdvanced has a dataStore which you can manually access if needed. 28 | 29 | **Application Life-cycle** Study the full offline samples in this repo carefully. In most cases, they are slightly overengineered. However, they are built that way for good reasons. The order in which things intializes is critical. In many cases don't be surprised if you'll need to force initialization sequences in your code to be sequential. Asynchronous loading of JavaScript libraries typically only works for full offline if you want to wait until all libraries have finished loading. 30 | 31 | **Application Cache.** You will have to set up an application cache file. This cache is also sometimes called Cache Manifest, AppCache and Manifest. The application cache mehanism is broadly supported across [browser vendors](http://caniuse.com/#search=application). It instructs the browser to store any HTML, CSS, .js or image that is required for fully reconstituting the web page while offline. It can be a pain to set these up, it requires a lot of trail and error, but once they are set up they function fairly well. 32 | 33 | Examples of application caches can be found in this repositories `/samples` directory, along with a grunt-based application cache helper tool. 34 | 35 | You may notice warnings that Application Cache has gone away, for example [here on MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Using_the_application_cache) but the warnings are [greatly exaggerated](http://www.andygup.net/application-cache-is-not-gone-oh-my-or-is-it/). 36 | 37 | **Offline detection for browsers** You need a mechanism for checking if the browser is online or offline. If the browser is offline you can set the `OfflineEditAdvanced` library to offline mode using `OfflineEditAdvanced.goOffline()` and then all edits will be saved to the database. When you want to return online, you can use `OfflineEditAdvanced.goOnline(callback)` and the library will push any pending edits to the Feature Service, and it will also simply pass-thru any and not store anything as long as the library remains in online mode. 38 | 39 | * **[Offline.js](http://caniuse.com/#search=application)** is one library for helping detect if a browser is offline or not. It does have its flaws, for example we've noticed that Chrome on Windows laptops can fail to quickly detect when the browser has gone offline. Detecting when a browser's state shifts from online to offline and back again is tricky business. It's much easier with native SDKs or with plugins for PhoneGap/Cordova. 40 | 41 | * **Simple HTTP request** Another fairly straightforward pattern is to simply try an HTTP request and check if it fails. This isn't completely bulletproof, but it works across all modern browsers. Here's one example: 42 | 43 | ```javascript 44 | 45 | function validateOnline(callback) { 46 | var req = new XMLHttpRequest(); 47 | req.open("GET", "http://mywebsite.com/blue-pin.png?" + 48 | (Math.floor(Math.random() * 1000000000)), true); 49 | req.onload = function() { 50 | if( req.status === 200 && req.responseText !== "") { 51 | req = null; 52 | callback(true); 53 | } 54 | else { 55 | console.log("validateOnline failed"); 56 | req = null; 57 | callback(false); 58 | } 59 | }; 60 | req.onerror = function(e) { 61 | console.log("validateOnline failed: " + e); 62 | callback(false); 63 | }; 64 | req.timeout = 10000; //milliseconds 65 | req.send(null); 66 | } 67 | 68 | ``` 69 | 70 | **Editing Widgets** The ArcGIS API for JavaScript v3.x Editor Widget does not work in full offline mode for ADDS, UPDATES or DELETES because it doesn't store changes directly in the feature layer. 71 | 72 | You'll need to build your own custom functionality for now. There is an effort underway to create a [light-weight editing template](https://github.com/Esri/offline-editor-js/issues/434) but that is a work in progress and the sample has been temporarily moved to this [gist](https://gist.github.com/andygup/1e768216177dd8a77a73). 73 | 74 | The [appcache-features.html](https://github.com/Esri/offline-editor-js/blob/master/samples/appcache-features.html) sample shows a custom modal widget for doing only UPDATE's on existing data. 75 | 76 | **About the dataStore** The dataStore is a database mechanism for storing feature collections and any other information or objects needed to rebuild an application when it starts up while offline. 77 | 78 | You can either let the library automatically handle the dataStore (default) or you can manually manage it yourself if there's additional items you need to store and maintain between offline browser resarts. In default mode, the library automatically updates the dataStore every time you make an edit. It's sole purpose is to ensure the library has an accurate snapshot of feature layer and its features so that they can be reconstituted correctly. 79 | 80 | More information on how the use the dataStore can be found [here](https://github.com/Esri/offline-editor-js/blob/master/doc/howtouseofmadvancedlibrary.md). 81 | 82 | 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /doc/howtousetpklibrary.md: -------------------------------------------------------------------------------- 1 | How to use the TPKLayer library 2 | =============================== 3 | 4 | ## `TPKLayer` Library 5 | 6 | The `TPKLayer` Library allows you to display at TPK file as a map. 7 | 8 | **Step 1** Include the `offline-tpk-min.js` library in your app. 9 | 10 | ```js 11 | require([ 12 | "esri/map", 13 | "..dist/offline-tpk-min.js"], 14 | function(Map) 15 | { 16 | ... 17 | }); 18 | ``` 19 | 20 | **Step 2** 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. 21 | 22 | ```js 23 | 24 | O.esri.zip.createReader(new O.esri.zip.BlobReader(blob), function (zipReader) { 25 | zipReader.getEntries(function (entries) { 26 | initMap(entries); 27 | zipReader.close(function(evt){ 28 | console.log("Done reading zip file.") 29 | }) 30 | }, function (err) { 31 | alert("There was a problem reading the file!: " + err); 32 | }) 33 | }) 34 | 35 | 36 | ``` 37 | **Step 3** 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. 38 | 39 | 40 | ```js 41 | 42 | tpkLayer = new O.esri.TPK.TPKLayer(); 43 | 44 | //Listen for progress events to provide UX feedback 45 | tpkLayer.on("progress", function (evt) { 46 | evt == "start" ? loading.style.visibility = "visible" : loading.style.visibility = "hidden"; 47 | }) 48 | 49 | tpkLayer.extend(entries); 50 | 51 | map = new Map("map"); 52 | map.addLayer(tpkLayer); 53 | 54 | ``` 55 | 56 | 57 | 58 | **Listen for errors** 59 | 60 | It is a best practice to listen for the following events and handle them appropriately in the user interface. 61 | 62 | ```js 63 | 64 | tpkLayer.on("validationEvent", function(evt){ 65 | //evt.msg is the string message 66 | //evt.err is the error 67 | if(evt.msg == tpkLayer.NO_SUPPORT_ERROR){ 68 | //Let the user know the library isn't supported. 69 | } 70 | }) 71 | 72 | tpkLayer.on("databaseErrorEvent", function(evt){ 73 | //evt.msg is the string message 74 | //evt.err is the error 75 | if(evt.msg == tpkLayer.DB_INIT_ERROR){ 76 | //Let the user know there was a db problem. 77 | } 78 | }) 79 | 80 | ``` 81 | 82 | 83 | **To clear the database** 84 | 85 | When you need to delete all tiles from the existing data use the following pattern. 86 | 87 | ```js 88 | 89 | tpkLayer.store.deleteAll(function(success,error){ 90 | if(success){ 91 | //do something 92 | } 93 | else{ 94 | //let user know something went wrong 95 | } 96 | }) 97 | 98 | ``` 99 | 100 | **Can I use the TPKLayer with a tiled basemap?** 101 | 102 | Yes for ArcGIS API for JavaScript v3.8+ and ONLY if the TPKs Levels of Detail (LODs) match the tiled map services LODs exactly. 103 | 104 | The basemap (base tiled layer) defines the LODs that the map can display. Any other operational tiled layers on the map will not display if they don’t match the basemap’s LODs. Esri.Map doesn’t union LODs of all tiled layers on the map. 105 | 106 | You can also use the `TPKLayer.loadFromURL()` method to add tiled map service tiles directly to the database. In order to get the tile information, use this method with `OfflineTilesBasic.saveToFile()` and `OfflineTilesAdvanced.saveToFile()`. That will create a CSV file that contains all the tiles within the extent that you define. The tiles-indexed-db.html sample demonstrates this pattern. Each tile within the CSV is defined by a URL as a String, and the tile image as a base64 String: 107 | 108 | ```js 109 | 110 | var tile = { 111 | url: "http://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/16/24710/32091", 112 | img: "data:image/png;base64,iVBORw0KGgoAAA...pQAAAABJRU5ErkJggg==" 113 | }; 114 | 115 | ``` 116 | 117 | See the [TPKLayer API doc](tpklayer.md) for more info. 118 | 119 | For more information on creating TPKs go [here](http://resources.arcgis.com/en/help/main/10.1/index.html#//006600000457000000). 120 | 121 | **Can I use the TPKLayer with `OfflineEditAdvanced`?** 122 | 123 | Yes, you can use the TPKLayer library alongside the `OfflineEditAdvanced` library. Spatial references must be exactly the same for both the TPK and the feature service. 124 | 125 | **Additional Considerations** 126 | 127 | There are a few things to keep in mind when working with TPK files and JavaScript. 128 | 129 | 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. 130 | 131 | * Number of layers 132 | * Size of the extent 133 | * Minimum and maximum zoom level 134 | 135 | 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. 136 | 137 | The library helps where it can by providing 2.7x compression of the tile imagery and about 50% compression of the tile URLs. 138 | 139 | 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. 140 | 141 | 142 | 143 | -------------------------------------------------------------------------------- /doc/migratefromv1tov2.md: -------------------------------------------------------------------------------- 1 | # Migrating from v1 to v2 2 | 3 | This doc is to provide pointers for migrating from offline-editor-js v1 to v2. Migration should be fairly straightforward as you are simply going to be changing library names and method namespaces. Check the CHANGELOG doc for specifics as well as any deprecations. 4 | 5 | 6 | ## Importing the libraries 7 | 8 | In your main html application you can use generic script injection to import the offline-editor-js libraries into your project. Don't create any aliases for the offline-editor-js libraries within the function statement and add them to the end of the module array, but before domReady. As you can see in the example below, the only alias is for `Map`. 9 | 10 | ```html 11 | 12 | 110 | 111 | 256 | 257 | 258 | -------------------------------------------------------------------------------- /samples/simple-edit.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Simple Edit 9 | 10 | 11 | 12 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 |
44 | 51 |
52 |
53 | 54 | 198 | 199 | 200 | -------------------------------------------------------------------------------- /samples/tpks/Beirut.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Esri/offline-editor-js/f4d324b01ab5d3912088509c001d7dbbde4746ae/samples/tpks/Beirut.zip -------------------------------------------------------------------------------- /samples/widgets/modal/css/modal-popup.css: -------------------------------------------------------------------------------- 1 | .mod-popup-div { 2 | opacity: 0; 3 | height: 0; 4 | width: 0; 5 | z-index: 98; 6 | font-size: x-large; 7 | position: absolute; 8 | top: 0px; 9 | left: -1000px; 10 | } 11 | .mod-popup-body { 12 | position: relative; 13 | top: 10%; 14 | left: 10%; 15 | z-index: 100; 16 | height: 80%; 17 | width: 80%; 18 | border-radius: 10px; 19 | background-color: black; 20 | display: table; 21 | opacity: 0.7; 22 | } 23 | .mod-popup-modal-background { 24 | position: absolute; 25 | top: 0; 26 | left: 0; 27 | height: 100%; 28 | width: 100%; 29 | opacity: 0.5; 30 | background-color: black; 31 | z-index: 99; 32 | overflow: visible; 33 | } 34 | .mod-popup-input { 35 | border-bottom: solid #ffffff 1px; 36 | width: 80%; 37 | display: table-cell; 38 | vertical-align: middle; 39 | padding-left: 12px; 40 | } 41 | .mod-popup-label { 42 | padding: 12px; 43 | width: 20%; 44 | display: table-cell; 45 | vertical-align: middle; 46 | background-color: dimgrey; 47 | } 48 | .mod-popup-label-top-left { 49 | padding: 12px; 50 | width: 20%; 51 | display: table-cell; 52 | vertical-align: middle; 53 | background-color: dimgray; 54 | border-top-left-radius: 10px; 55 | } 56 | .mod-popup-table-row { 57 | color: white; 58 | border-radius: 10px; 59 | display: table-row; 60 | } 61 | .mod-popup-stop-input { 62 | border-radius: 5px; 63 | font-size: x-large; 64 | width: 90%; 65 | height: 75%; 66 | padding: 5px; 67 | } 68 | .mod-popup-stop-input-disabled { 69 | color: white; 70 | border-radius: 5px; 71 | font-size: x-large; 72 | background-color: dimgray; 73 | opacity: 1.0; /* For safari display bug */ 74 | padding: 5px; 75 | width: 90%; 76 | height: 75%; 77 | } 78 | .mod-popup-button { 79 | position: relative; 80 | float: left; 81 | color: #ffffff; 82 | font-size: x-large; 83 | border-radius: 10px; 84 | width: 45%; 85 | padding: 8px; 86 | background-color: #000000; 87 | border: solid #ffffff 2px; 88 | } 89 | .mod-popup-button:active { 90 | position: relative; 91 | float: left; 92 | color: #000000; 93 | font-size: x-large; 94 | border-radius: 10px; 95 | width: 45%; 96 | padding: 8px; 97 | background-color: lightyellow; 98 | border: solid #ffffff 2px; 99 | } 100 | .mod-popup-button-cancel { 101 | position: relative; 102 | float: left; 103 | color: lightgray; 104 | font-size: x-large; 105 | border-radius: 10px; 106 | width: 100%; 107 | padding: 8px; 108 | background-color: #000000; 109 | border: solid lightgray 1px; 110 | } 111 | .mod-popup-button-cancel:active { 112 | position: relative; 113 | float: left; 114 | color: #000000; 115 | font-size: x-large; 116 | border-radius: 10px; 117 | width: 100%; 118 | padding: 8px; 119 | background-color: lightyellow; 120 | border: solid lightgray 1px; 121 | } 122 | .mod-popup-button-div { 123 | padding: 12px; 124 | display: table-cell; 125 | vertical-align: middle; 126 | background-color: #000000; 127 | } 128 | .mod-popup-button-div-bottom-left { 129 | padding: 12px; 130 | display: table-cell; 131 | vertical-align: middle; 132 | background-color: #000000; 133 | border-bottom-left-radius: 8px; 134 | } 135 | @media (max-width: 500px) { 136 | .mod-popup-button { 137 | font-size: small; 138 | } 139 | .mod-popup-button:active{ 140 | font-size: small; 141 | } 142 | .mod-popup-button-cancel { 143 | font-size: small; 144 | } 145 | .mod-popup-button-cancel:active { 146 | font-size: small; 147 | } 148 | .mod-popup-stop-input { 149 | font-size: small; 150 | } 151 | .mod-popup-stop-input-disabled { 152 | font-size: small; 153 | } 154 | } 155 | @media (max-width: 450px) { 156 | .mod-popup-button { 157 | font-size: x-small; 158 | } 159 | .mod-popup-button:active{ 160 | font-size: x-small; 161 | } 162 | .mod-popup-button-cancel { 163 | font-size: x-small; 164 | } 165 | .mod-popup-button-cancel:active { 166 | font-size: x-small; 167 | } 168 | .mod-popup-stop-input { 169 | font-size: small; 170 | } 171 | .mod-popup-stop-input-disabled { 172 | font-size: small; 173 | } 174 | } 175 | @media (max-height: 500px) { 176 | 177 | .mod-popup-button { 178 | font-size: small; 179 | } 180 | .mod-popup-button:active{ 181 | font-size: small; 182 | } 183 | .mod-popup-button-cancel { 184 | font-size: small; 185 | } 186 | .mod-popup-button-cancel:active { 187 | font-size: small; 188 | } 189 | .mod-popup-stop-input { 190 | font-size: small; 191 | } 192 | .mod-popup-stop-input-disabled { 193 | font-size: small; 194 | } 195 | .mod-popup-modal-background { 196 | 197 | } 198 | } -------------------------------------------------------------------------------- /samples/widgets/modal/popup.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Modal Popup Widget 3 | * This widget provides a basic framework for building modal popups 4 | * for mobile GIS web applications. 5 | * @author @agup 6 | */ 7 | define([ 8 | "dojo/_base/declare", "dojo/parser", "dojo/ready", 9 | "dijit/_WidgetBase", "dijit/_TemplatedMixin","dojo/query", 10 | "dojo/text!../modal/template/popup.html","dojo/NodeList-manipulate" 11 | ], function(declare, parser, ready, _WidgetBase, _TemplatedMixin,query,template){ 12 | 13 | return declare("ModalPopup", [_WidgetBase, _TemplatedMixin], { 14 | 15 | options: { 16 | animation: false, 17 | animationDuration: 1 18 | }, 19 | 20 | templateString: template, 21 | 22 | constructor: function (options, srcRefNode) { 23 | // mix in settings and defaults 24 | declare.safeMixin(this.options, options); 25 | 26 | // widget node 27 | this.domNode = srcRefNode; 28 | 29 | // Set properties 30 | this.set("animation", this.options.animation); 31 | this.set("animationDuration", this.options.animationDuration); 32 | }, 33 | 34 | show: function () { 35 | 36 | if(this.animation){ 37 | // You can design any animation you want! 38 | this.domNode.style.opacity = 1; 39 | this.domNode.style.left = 0; 40 | this.domNode.style.top = 0; 41 | this.domNode.style.width = "100%"; 42 | this.domNode.style.height = "100%"; 43 | this.domNode.style.transition = "all " + this.animationDuration + "s linear 0s"; 44 | } 45 | else{ 46 | this.domNode.style.position = "static"; 47 | } 48 | }, 49 | 50 | hide: function () { 51 | 52 | if(this.animation){ 53 | // You can design any animation you want! 54 | this.domNode.style.height = 0; 55 | this.domNode.style.width = 0; 56 | this.domNode.style.opacity = 0; 57 | this.domNode.style.top = "0px"; 58 | this.domNode.style.left = -1000 + "px"; 59 | this.domNode.style.transition = "all " + this.animationDuration + "s ease-in-out 0s"; 60 | } 61 | else{ 62 | this.domNode.style.position = "absolute"; 63 | } 64 | }, 65 | 66 | // connections/subscriptions will be cleaned up during the destroy() lifecycle phase 67 | destroy: function () { 68 | this.inherited(arguments); 69 | } 70 | }); 71 | }); 72 | -------------------------------------------------------------------------------- /samples/widgets/modal/template/popup.html: -------------------------------------------------------------------------------- 1 |
2 | 10 |
11 |
12 |
ID
13 |
14 | 15 |
16 |
17 |
18 |
Bustop ID
19 |
20 | 21 |
22 |
23 |
24 |
Routes
25 |
26 | 27 |
28 |
29 |
30 |
Stopnames
31 |
32 | 33 |
34 |
35 |
36 |
37 | 38 |
39 |
40 | 41 | 42 |
43 |
44 |
45 | 46 |
-------------------------------------------------------------------------------- /scripts/preprocessor.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # For the log 4 | echo "$(date)" 5 | echo "$NAME" 6 | echo "v$VERSION" 7 | echo "" 8 | 9 | # Lint the dist files 10 | echo "run lint" 11 | npm run lint || exit 1 12 | 13 | # Concat the dist files 14 | echo "concat files" 15 | npm run concat || exit 1 16 | 17 | # Uglify the dist files 18 | echo "uglify dist" 19 | npm run uglify || exit 1 -------------------------------------------------------------------------------- /scripts/pull.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ########################################################## 4 | # ONLY use this script after using the push script. 5 | # This script helps sync up your local git with master 6 | # and origin. 7 | # 8 | # To use: npm run pull 9 | ########################################################## 10 | 11 | # SET VARS 12 | VERSION=$(node --eval "console.log(require('./package.json').version);") 13 | 14 | # Sync up local master with origin/master 15 | echo "checkout master" 16 | git checkout master 17 | 18 | echo "pull upstream master" 19 | git pull upstream master 20 | 21 | read -p "Press [Enter] to push origin master..." 22 | git push origin master 23 | 24 | # Delete the version branch 25 | echo "delete the version branch" 26 | git branch -D $VERSION 27 | 28 | # Sync and merge gh-pages 29 | echo "checkout gh-pages" 30 | git checkout gh-pages 31 | 32 | read -p "Press [Enter] to merge gh-pages with master..." 33 | git merge master 34 | 35 | echo "push origin gh-pages" 36 | read -p "Press [Enter] to push gh-pages to origin..." 37 | git push origin gh-pages 38 | 39 | # publish release on NPM 40 | read -p "Press [Enter] to npm publish..." 41 | npm publish 42 | 43 | echo "Now create a pull request for gh-pages manually on github." 44 | echo "This gives you a final chance to review your changes." 45 | echo "Once pull request has been merged, don't forget to run 'git pull upstream gh-pages' and 'git push origin gh-pages'" -------------------------------------------------------------------------------- /scripts/push.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ########################################################## 4 | # This script provides final build automation for the repo. 5 | # It's responsible for pushing the build. Once your 6 | # branch has been merged by master then use the pull script 7 | # to finalize syncing up your git and github repos. 8 | # 9 | # PRE-REQS: 10 | # 1. Make sure you have node, npm and grunt installed 11 | # 2. Before you can use this repo: npm init 12 | # 13 | # 14 | # INSTRUCTIONS: 15 | # 16 | # 0. MAKE SURE you look thru the steps here and that everything is in order before you hit the button. The only file 17 | # that should be pending for commit is CHANGELOG and/or package.json. 18 | # 1. Make sure in package.json to name your releases after the version number, such as v2.0.0, v2.0.1, v2.1.1, etc. 19 | # 2. To submit changes to your github branch: npm run push. You can still make changes on this branch if you need to. 20 | # 3. After those changes have been merged on esri/master then sync up gh-pages: npm run pull 21 | # 4. After those changes have been merged on esri/gh-pages: 'git pull upstream gh-pages' then 'git push origin gh-pages' 22 | # 23 | ########################################################## 24 | 25 | # SET VARS 26 | VERSION=$(node --eval "console.log(require('./package.json').version);") 27 | NAME=$(node --eval "console.log(require('./package.json').name);") 28 | 29 | # Checkout temp branch for release 30 | git checkout v$VERSION 31 | 32 | # Add files, get ready to commit. 33 | # CHANGELOG should have pending changes 34 | read -p "Press [Enter] to add git files..." 35 | git add CHANGELOG.md 36 | git add package.json 37 | git add dist 38 | 39 | # Create the release zip file 40 | echo "creating zip of /dist" 41 | zip -r $NAME-v$VERSION.zip dist 42 | 43 | # Run gh-release to create the tag and push release to github 44 | read -p "Press [Enter] to push a release file..." 45 | gh-release --assets $NAME-v$VERSION.zip 46 | 47 | # Remove the temp zip file 48 | rm $NAME-v$VERSION.zip 49 | 50 | # Commit changes + commit message 51 | git commit -m "$VERSION" 52 | 53 | # Push to origin 54 | read -p "Press [Enter] to push commits to origin..." 55 | git push origin v$VERSION 56 | 57 | echo "zip file deleted" 58 | echo "push script: done" 59 | 60 | echo "Go to your github branch $VERSION, review changes then create pull request to esri/master" 61 | echo "Once the PR is accepted and merged then run the pull script" -------------------------------------------------------------------------------- /test/SpecRunner.TPKLayer.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | Jasmine Spec Runner - TPKLayer 7 | 8 | 9 | 10 | 11 | 12 | 13 | 22 | 23 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 101 | 102 | 103 | 104 | 105 | Select a tpk file to launch unit testing. You may have to change it to .zip 106 |
107 |
108 |
109 | 110 |
111 | 112 | 113 | 114 | 115 | -------------------------------------------------------------------------------- /test/SpecRunner.attachmentsStore.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | Jasmine Spec Runner - Graphics Store 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | -------------------------------------------------------------------------------- /test/SpecRunner.editsStore.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | Jasmine Spec Runner - Graphics Store 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 143 | 144 | 145 | 146 |
147 | 148 | 149 | 150 | -------------------------------------------------------------------------------- /test/SpecRunner.offlineAttachments.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | Jasmine Spec Runner - Offline Attachments 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 193 | 194 | 195 | 196 |
197 |
198 | Select any file (.png, .jpg,...) to launch unit testing 199 |
200 |
201 | 202 | 203 | 204 | -------------------------------------------------------------------------------- /test/SpecRunner.offlineEditAdvanced.TokenBased.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | Jasmine Spec Runner - OfflineEditAdvanced 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 158 | 159 | 160 | 161 |
162 | 163 | 164 | 165 | -------------------------------------------------------------------------------- /test/SpecRunner.offlineEditAdvanced.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | Jasmine Spec Runner - Offline Edit Advanced 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 160 | 161 | 162 | 163 |
164 | 165 | 166 | 167 | -------------------------------------------------------------------------------- /test/SpecRunner.offlineEditAdvanced.oAuth.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | Jasmine Spec Runner - Offline Edit Advanced OAuth 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 187 | 188 | 189 | 190 |
191 | 192 |
193 | 194 | 196 | 197 | 198 | -------------------------------------------------------------------------------- /test/SpecRunner.offlineEditBasic.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | Jasmine Spec Runner - OfflineEditBasic 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 150 | 151 | 152 | 153 |
154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | -------------------------------------------------------------------------------- /test/SpecRunner.offlineTilesAdvanced.TokenBased.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | Jasmine Spec Runner - Tiles 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 94 | 95 | 96 | 97 |
98 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /test/SpecRunner.offlineTilesAdvanced.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | Jasmine Spec Runner - Tiles 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 92 | 93 | 94 | 95 |
96 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /test/SpecRunner.offlineTilesBasic.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | Jasmine Spec Runner - Tiles 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 93 | 94 | 95 | 96 |
97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /test/images/blue-pin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Esri/offline-editor-js/f4d324b01ab5d3912088509c001d7dbbde4746ae/test/images/blue-pin.png -------------------------------------------------------------------------------- /test/images/red-pin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Esri/offline-editor-js/f4d324b01ab5d3912088509c001d7dbbde4746ae/test/images/red-pin.png -------------------------------------------------------------------------------- /test/test-localStorage.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | localStorage test 6 | 12 | 13 | 14 |
15 |
use this tool to test localStorage capacity exhaustion
16 |
first, use button below to fill up localStorage
17 |
then, test your application to see how it behaves under localStorage full condition
18 |
and finally, clear localStorage
19 |
show console (F12) to see progress
20 |
-
21 |
22 | 23 |
24 |
25 | 26 | 118 | 119 | 120 | -------------------------------------------------------------------------------- /tiny-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Esri/offline-editor-js/f4d324b01ab5d3912088509c001d7dbbde4746ae/tiny-image.png -------------------------------------------------------------------------------- /utils/appCacheManager.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Helper Class for working with the application cache. 3 | * 4 | * Listen for the following events: 5 | * UPDATE_READY - an update to the cache is ready 6 | * UPDATE_NONE - the cache hasn't changed since the last app load. 7 | * CACHE_LOADED - the cache has finished loading. 8 | * CACHE_ERROR - an error was thrown by the browser while attempting to load the cache. 9 | * 10 | * For more information on application cache: 11 | * https://developer.mozilla.org/en-US/docs/HTML/Using_the_application_cache 12 | * Many thanks and all kudos go to the following blog posts: 13 | * http://www.html5rocks.com/en/tutorials/this.appCache/beginner/ 14 | */ 15 | define([ 16 | "dojo/_base/declare", 17 | "dojo/Evented", 18 | "dojo/_base/lang"], 19 | function(declare,Evented,lang){ 20 | return declare([Evented], { 21 | UPDATE_READY: "update-ready", 22 | UPDATE_NONE: "no-update", 23 | CACHE_EVENT: "cache-event", 24 | CACHE_LOADED: "cache-loaded", 25 | CACHE_ERROR: "cache-error", 26 | appCache: window.applicationCache, 27 | 28 | constructor: function(/* boolean */autoUpdate, /* boolean */ setListeners) 29 | { 30 | if(autoUpdate)this.setUpdateCache(); 31 | if(setListeners)this.setCacheListeners(); 32 | console.log("appCacheManager.js enabled"); 33 | }, 34 | 35 | setUpdateCache:function(){ 36 | // Check if a new cache is available on page load. 37 | window.addEventListener('load', function(evt) { 38 | 39 | window.applicationCache.addEventListener('updateready', function(evt) { 40 | if (window.applicationCache.status == window.applicationCache.UPDATEREADY) { 41 | // Browser downloaded a new app cache. 42 | if (confirm('A new version of this cache is available.')) { 43 | window.location.reload(); 44 | console.log("App cache reloaded"); 45 | this.emit(this.UPDATE_READY,null); 46 | } 47 | } else { 48 | // Manifest didn't changed. Nothing new to server. 49 | console.log("App cache no change"); 50 | this.emit(this.UPDATE_NONE,null); 51 | } 52 | }, false); 53 | 54 | }, false); 55 | }, 56 | 57 | setCacheListeners:function(){ 58 | this.appCache.addEventListener('cached', this._handleCacheEvents.bind(this), false); 59 | 60 | // Checking for an update. Always the first event fired in the sequence. 61 | this.appCache.addEventListener('checking', this._handleCacheEvents.bind(this), false); 62 | 63 | // An update was found. The browser is fetching resources. 64 | this.appCache.addEventListener('downloading', this._handleCacheEvents.bind(this), false); 65 | 66 | // The manifest returns 404 or 410, the download failed, 67 | // or the manifest changed while the download was in progress. 68 | this.appCache.addEventListener('error', this._handleCacheErrors.bind(this), false); 69 | 70 | // Fired after the first download of the manifest. 71 | this.appCache.addEventListener('noupdate', this._handleCacheEvents.bind(this), false); 72 | 73 | // Fired if the manifest file returns a 404 or 410. 74 | // This results in the application cache being deleted. 75 | this.appCache.addEventListener('obsolete', this._handleCacheEvents.bind(this), false); 76 | 77 | // Fired for each resource listed in the manifest as it is being fetched. 78 | this.appCache.addEventListener('progress', this._handleCacheEvents.bind(this), false); 79 | 80 | // Fired when the manifest resources have been newly redownloaded. 81 | this.appCache.addEventListener('updateready', this._handleCacheEvents.bind(this), false); 82 | }, 83 | 84 | getCacheStatus:function(){ 85 | 86 | switch (this.appCache.status) { 87 | case this.appCache.UNCACHED: // UNCACHED == 0 88 | return 'UNCACHED'; 89 | break; 90 | case this.appCache.IDLE: // IDLE == 1 91 | return 'IDLE'; 92 | break; 93 | case this.appCache.CHECKING: // CHECKING == 2 94 | return 'CHECKING'; 95 | break; 96 | case this.appCache.DOWNLOADING: // DOWNLOADING == 3 97 | return 'DOWNLOADING'; 98 | break; 99 | case this.appCache.UPDATEREADY: // UPDATEREADY == 4 100 | return 'UPDATEREADY'; 101 | break; 102 | case this.appCache.OBSOLETE: // OBSOLETE == 5 103 | return 'OBSOLETE'; 104 | break; 105 | default: 106 | return 'UKNOWN CACHE STATUS'; 107 | break; 108 | }; 109 | }, 110 | 111 | _handleCacheEvents:function(evt){ 112 | if(evt.hasOwnProperty("total") && evt.hasOwnProperty("loaded")){ 113 | if(evt.total == evt.loaded){ 114 | console.log("appCacheManager: cache has finished loading.") 115 | this.emit(this.CACHE_LOADED,"cache-loaded"); 116 | } 117 | } 118 | this.emit(this.CACHE_EVENT,evt); 119 | }, 120 | 121 | _handleCacheErrors:function(evt){ 122 | this.emit(this.CACHE_ERROR,evt); 123 | } 124 | }) 125 | } 126 | ) 127 | 128 | -------------------------------------------------------------------------------- /utils/debouncer.js: -------------------------------------------------------------------------------- 1 | define([],function(){ 2 | return { 3 | 4 | /** 5 | * Activates the orientation listener and listens for native events. 6 | * Handle orientation events to allow for resizing the map and working around 7 | * 3rd party library bugs related to how and when the view settles after such an event 8 | */ 9 | setOrientationListener: function(delay,callback){ 10 | var supportsOrientationChange = "onorientationchange" in window, 11 | orientationEvent = supportsOrientationChange ? "orientationchange" : "resize"; 12 | 13 | window.addEventListener(orientationEvent, this.debounceMap(function(){ 14 | callback(); 15 | },delay).bind(this), false); 16 | }, 17 | 18 | /** 19 | * Minimize the number of times window readjustment fires a function 20 | * http://davidwalsh.name/javascript-debounce-function 21 | * @param func 22 | * @param wait 23 | * @param immediate 24 | * @returns {Function} 25 | */ 26 | debounceMap: function (func, wait, immediate) { 27 | var timeout; 28 | return function() { 29 | var context = this, args = arguments; 30 | clearTimeout(timeout); 31 | timeout = setTimeout(function() { 32 | timeout = null; 33 | if (!immediate) func.apply(context, args); 34 | }, wait); 35 | if (immediate && !timeout) func.apply(context, args); 36 | }; 37 | } 38 | } 39 | }) -------------------------------------------------------------------------------- /vendor/jasmine-1.3.1/MIT.LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2008-2011 Pivotal Labs 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /vendor/jasmine-1.3.1/jasmine.css: -------------------------------------------------------------------------------- 1 | body { background-color: #eeeeee; padding: 0; margin: 5px; overflow-y: scroll; } 2 | 3 | #HTMLReporter { font-size: 11px; font-family: Monaco, "Lucida Console", monospace; line-height: 14px; color: #333333; } 4 | #HTMLReporter a { text-decoration: none; } 5 | #HTMLReporter a:hover { text-decoration: underline; } 6 | #HTMLReporter p, #HTMLReporter h1, #HTMLReporter h2, #HTMLReporter h3, #HTMLReporter h4, #HTMLReporter h5, #HTMLReporter h6 { margin: 0; line-height: 14px; } 7 | #HTMLReporter .banner, #HTMLReporter .symbolSummary, #HTMLReporter .summary, #HTMLReporter .resultMessage, #HTMLReporter .specDetail .description, #HTMLReporter .alert .bar, #HTMLReporter .stackTrace { padding-left: 9px; padding-right: 9px; } 8 | #HTMLReporter #jasmine_content { position: fixed; right: 100%; } 9 | #HTMLReporter .version { color: #aaaaaa; } 10 | #HTMLReporter .banner { margin-top: 14px; } 11 | #HTMLReporter .duration { color: #aaaaaa; float: right; } 12 | #HTMLReporter .symbolSummary { overflow: hidden; *zoom: 1; margin: 14px 0; } 13 | #HTMLReporter .symbolSummary li { display: block; float: left; height: 7px; width: 14px; margin-bottom: 7px; font-size: 16px; } 14 | #HTMLReporter .symbolSummary li.passed { font-size: 14px; } 15 | #HTMLReporter .symbolSummary li.passed:before { color: #5e7d00; content: "\02022"; } 16 | #HTMLReporter .symbolSummary li.failed { line-height: 9px; } 17 | #HTMLReporter .symbolSummary li.failed:before { color: #b03911; content: "x"; font-weight: bold; margin-left: -1px; } 18 | #HTMLReporter .symbolSummary li.skipped { font-size: 14px; } 19 | #HTMLReporter .symbolSummary li.skipped:before { color: #bababa; content: "\02022"; } 20 | #HTMLReporter .symbolSummary li.pending { line-height: 11px; } 21 | #HTMLReporter .symbolSummary li.pending:before { color: #aaaaaa; content: "-"; } 22 | #HTMLReporter .exceptions { color: #fff; float: right; margin-top: 5px; margin-right: 5px; } 23 | #HTMLReporter .bar { line-height: 28px; font-size: 14px; display: block; color: #eee; } 24 | #HTMLReporter .runningAlert { background-color: #666666; } 25 | #HTMLReporter .skippedAlert { background-color: #aaaaaa; } 26 | #HTMLReporter .skippedAlert:first-child { background-color: #333333; } 27 | #HTMLReporter .skippedAlert:hover { text-decoration: none; color: white; text-decoration: underline; } 28 | #HTMLReporter .passingAlert { background-color: #a6b779; } 29 | #HTMLReporter .passingAlert:first-child { background-color: #5e7d00; } 30 | #HTMLReporter .failingAlert { background-color: #cf867e; } 31 | #HTMLReporter .failingAlert:first-child { background-color: #b03911; } 32 | #HTMLReporter .results { margin-top: 14px; } 33 | #HTMLReporter #details { display: none; } 34 | #HTMLReporter .resultsMenu, #HTMLReporter .resultsMenu a { background-color: #fff; color: #333333; } 35 | #HTMLReporter.showDetails .summaryMenuItem { font-weight: normal; text-decoration: inherit; } 36 | #HTMLReporter.showDetails .summaryMenuItem:hover { text-decoration: underline; } 37 | #HTMLReporter.showDetails .detailsMenuItem { font-weight: bold; text-decoration: underline; } 38 | #HTMLReporter.showDetails .summary { display: none; } 39 | #HTMLReporter.showDetails #details { display: block; } 40 | #HTMLReporter .summaryMenuItem { font-weight: bold; text-decoration: underline; } 41 | #HTMLReporter .summary { margin-top: 14px; } 42 | #HTMLReporter .summary .suite .suite, #HTMLReporter .summary .specSummary { margin-left: 14px; } 43 | #HTMLReporter .summary .specSummary.passed a { color: #5e7d00; } 44 | #HTMLReporter .summary .specSummary.failed a { color: #b03911; } 45 | #HTMLReporter .description + .suite { margin-top: 0; } 46 | #HTMLReporter .suite { margin-top: 14px; } 47 | #HTMLReporter .suite a { color: #333333; } 48 | #HTMLReporter #details .specDetail { margin-bottom: 28px; } 49 | #HTMLReporter #details .specDetail .description { display: block; color: white; background-color: #b03911; } 50 | #HTMLReporter .resultMessage { padding-top: 14px; color: #333333; } 51 | #HTMLReporter .resultMessage span.result { display: block; } 52 | #HTMLReporter .stackTrace { margin: 5px 0 0 0; max-height: 224px; overflow: auto; line-height: 18px; color: #666666; border: 1px solid #ddd; background: white; white-space: pre; } 53 | 54 | #TrivialReporter { padding: 8px 13px; position: absolute; top: 0; bottom: 0; left: 0; right: 0; overflow-y: scroll; background-color: white; font-family: "Helvetica Neue Light", "Lucida Grande", "Calibri", "Arial", sans-serif; /*.resultMessage {*/ /*white-space: pre;*/ /*}*/ } 55 | #TrivialReporter a:visited, #TrivialReporter a { color: #303; } 56 | #TrivialReporter a:hover, #TrivialReporter a:active { color: blue; } 57 | #TrivialReporter .run_spec { float: right; padding-right: 5px; font-size: .8em; text-decoration: none; } 58 | #TrivialReporter .banner { color: #303; background-color: #fef; padding: 5px; } 59 | #TrivialReporter .logo { float: left; font-size: 1.1em; padding-left: 5px; } 60 | #TrivialReporter .logo .version { font-size: .6em; padding-left: 1em; } 61 | #TrivialReporter .runner.running { background-color: yellow; } 62 | #TrivialReporter .options { text-align: right; font-size: .8em; } 63 | #TrivialReporter .suite { border: 1px outset gray; margin: 5px 0; padding-left: 1em; } 64 | #TrivialReporter .suite .suite { margin: 5px; } 65 | #TrivialReporter .suite.passed { background-color: #dfd; } 66 | #TrivialReporter .suite.failed { background-color: #fdd; } 67 | #TrivialReporter .spec { margin: 5px; padding-left: 1em; clear: both; } 68 | #TrivialReporter .spec.failed, #TrivialReporter .spec.passed, #TrivialReporter .spec.skipped { padding-bottom: 5px; border: 1px solid gray; } 69 | #TrivialReporter .spec.failed { background-color: #fbb; border-color: red; } 70 | #TrivialReporter .spec.passed { background-color: #bfb; border-color: green; } 71 | #TrivialReporter .spec.skipped { background-color: #bbb; } 72 | #TrivialReporter .messages { border-left: 1px dashed gray; padding-left: 1em; padding-right: 1em; } 73 | #TrivialReporter .passed { background-color: #cfc; display: none; } 74 | #TrivialReporter .failed { background-color: #fbb; } 75 | #TrivialReporter .skipped { color: #777; background-color: #eee; display: none; } 76 | #TrivialReporter .resultMessage span.result { display: block; line-height: 2em; color: black; } 77 | #TrivialReporter .resultMessage .mismatch { color: black; } 78 | #TrivialReporter .stackTrace { white-space: pre; font-size: .8em; margin-left: 10px; max-height: 5em; overflow: auto; border: 1px inset red; padding: 1em; background: #eef; } 79 | #TrivialReporter .finished-at { padding-left: 1em; font-size: .6em; } 80 | #TrivialReporter.show-passed .passed, #TrivialReporter.show-skipped .skipped { display: block; } 81 | #TrivialReporter #jasmine_content { position: fixed; right: 100%; } 82 | #TrivialReporter .runner { border: 1px solid gray; display: block; margin: 5px 0; padding: 2px 0 2px 10px; } 83 | --------------------------------------------------------------------------------