├── .eslintrc ├── .gitignore ├── CONTRIBUTING.md ├── Gruntfile.js ├── Jenkinsfile ├── License.txt ├── README.md ├── enterprise-build-sample-js.png ├── karma.conf.js ├── package-lock.json ├── package.json ├── spec ├── ToolbarSpec.js └── main.js ├── test-reports └── HeadlessChrome_64.0.3282_(Windows_10.0.0) │ └── test-results.xml ├── web ├── buildProject.css ├── index.html ├── index.js └── js │ ├── buildProject │ ├── ChartingPane.js │ ├── InfoGrid.js │ ├── SiteManager.js │ ├── Toolbar.js │ ├── ToolbarSkeleton.js │ ├── TravelRingDialog.js │ ├── TravelRingTool.js │ ├── _buildProject.profile.js │ ├── buildProject.js │ ├── package.json │ └── templates │ │ ├── ChartingPane.html │ │ ├── InfoGrid.html │ │ ├── Toolbar.html │ │ └── _TravelRingDialog.html │ ├── data │ ├── config.json │ └── sampleData.json │ └── images │ └── logo.jpg └── webpack.config.js /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "eslint:recommended", 3 | "env": { 4 | "browser": true 5 | }, 6 | "rules": { 7 | "no-console": "off", 8 | "semi": ["error", "always"], 9 | "quotes": "off", 10 | "no-unused-vars": "off", 11 | "no-mixed-spaces-and-tabs":"off" 12 | } 13 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | node_modules/ 3 | arcgis-js-api/ 4 | enterprise-build-sample-js.sublime-project 5 | enterprise-build-sample-js.sublime-workspace 6 | build-src/ 7 | build-temp/ 8 | Chrome_*/ 9 | coverage/ 10 | test-reports/ 11 | test-results.xml -------------------------------------------------------------------------------- /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 | /* eslint-disable */ 2 | 3 | module.exports = function (grunt) { 4 | // Build customizations would be left up to developer to implement. 5 | grunt.loadNpmTasks('grunt-karma'); 6 | grunt.loadNpmTasks('grunt-contrib-watch'); 7 | grunt.loadNpmTasks('grunt-eslint'); 8 | grunt.loadNpmTasks('grunt-tomcat-deploy'); 9 | grunt.loadNpmTasks('grunt-zip'); 10 | 11 | var servePort = grunt.option('servePort') || 8000; 12 | var port = grunt.option('port') || 8080; 13 | var host = grunt.option('host') || 'localhost'; 14 | var path = grunt.option('path') || 'sample'; 15 | 16 | grunt.initConfig({ 17 | karma: { 18 | options: { 19 | // get defaults from karma config 20 | configFile: 'karma.conf.js', 21 | // run all tests once then exit 22 | singleRun: true, 23 | // only show error messages 24 | logLevel: 'ERROR', 25 | }, 26 | unit: { 27 | options:{ 28 | singleRun: true, 29 | captureTimeout:30000, 30 | reporters : ['junit', 'progress'], 31 | junitReporter: { 32 | outputDir: 'test-reports', // results will be saved as $outputDir/$browserName.xml 33 | outputFile: undefined, // if included, results will be saved as $outputDir/$browserName/$outputFile 34 | suite: '', // suite will become the package name attribute in xml testsuite element 35 | useBrowserName: true // add browser name to report and classes names 36 | } 37 | } 38 | } 39 | }, 40 | eslint: { 41 | files: [ 'web/js/buildProject/**/*.js' ] 42 | }, 43 | watch: { 44 | options: { 45 | livereload: true 46 | }, 47 | js: { 48 | files: [ 'Gruntfile.js', 'web/js/buildProject/**/*.js', 'spec/**/*.js' ], 49 | tasks: ['test'] 50 | }, 51 | html: { 52 | files: [ 'web/index.html'] 53 | } 54 | }, 55 | 56 | // Tomcat redeploy task still gets its config from tomcat_deploy 57 | // config item 58 | tomcat_deploy: { 59 | host: host, 60 | login: 'tomcat', 61 | password: 'tomcat', 62 | path: '/' + path, 63 | port: port, 64 | war: 'sample.war', 65 | deploy: '/manager/text/deploy', 66 | undeploy: '/manager/text/undeploy' 67 | }, 68 | 69 | zip: { 70 | war: { 71 | cwd: 'dist', 72 | dest: 'sample.war', 73 | src: ['dist/**'] 74 | } 75 | } 76 | }); 77 | 78 | // Serve dev app locally 79 | grunt.registerTask('serve', [ 'watch' ]); 80 | 81 | grunt.registerTask('deploy', ['tomcat_redeploy']); 82 | 83 | grunt.registerTask('test', ['eslint', 'karma']); 84 | 85 | // JS task 86 | grunt.registerTask('js', [ 'eslint']); 87 | 88 | }; -------------------------------------------------------------------------------- /Jenkinsfile: -------------------------------------------------------------------------------- 1 | pipeline { 2 | agent { label 'windows' } 3 | stages { 4 | stage('test') { 5 | steps { 6 | bat 'npm install' 7 | bat 'grunt test -v' 8 | } 9 | post { 10 | success { 11 | junit 'test-reports/**/*.xml' 12 | } 13 | } 14 | } 15 | stage('build') { 16 | steps { 17 | bat 'npm install' 18 | bat 'bower install' 19 | bat 'grunt build zip -v' 20 | } 21 | post { 22 | success { 23 | stash includes: '*.war', name: 'app' 24 | } 25 | } 26 | } 27 | stage('deploy QA') { 28 | steps { 29 | unstash 'app' 30 | bat 'grunt deploy' 31 | } 32 | } 33 | stage('deploy Prod') { 34 | steps { 35 | milestone(3) 36 | input message: 'Deploy?' 37 | unstash 'app' 38 | bat 'grunt deploy --port=8081' 39 | milestone(4) 40 | } 41 | } 42 | } 43 | post { 44 | failure { 45 | mail to: 'randy_jones@esri.com', subject: 'The Pipeline failed', body: 'The Pipeline failed' 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /License.txt: -------------------------------------------------------------------------------- 1 | Apache License - 2.0 2 | 3 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 4 | 5 | 1. Definitions. 6 | 7 | "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. 8 | 9 | "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. 10 | 11 | "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control 12 | with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management 13 | of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial 14 | ownership of such entity. 15 | 16 | "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. 17 | 18 | "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, 19 | and configuration files. 20 | 21 | "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to 22 | compiled object code, generated documentation, and conversions to other media types. 23 | 24 | "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice 25 | that is included in or attached to the work (an example is provided in the Appendix below). 26 | 27 | "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the 28 | editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes 29 | of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, 30 | the Work and Derivative Works thereof. 31 | 32 | "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work 33 | or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual 34 | or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of 35 | electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on 36 | electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for 37 | the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing 38 | by the copyright owner as "Not a Contribution." 39 | 40 | "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and 41 | subsequently incorporated within the Work. 42 | 43 | 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, 44 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, 45 | publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 46 | 47 | 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, 48 | non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, 49 | sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are 50 | necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was 51 | submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work 52 | or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You 53 | under this License for that Work shall terminate as of the date such litigation is filed. 54 | 55 | 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, 56 | and in Source or Object form, provided that You meet the following conditions: 57 | 58 | 1. You must give any other recipients of the Work or Derivative Works a copy of this License; and 59 | 60 | 2. You must cause any modified files to carry prominent notices stating that You changed the files; and 61 | 62 | 3. You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices 63 | from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and 64 | 65 | 4. If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a 66 | readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the 67 | Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the 68 | Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever 69 | such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. 70 | You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, 71 | provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to 72 | Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your 73 | modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with 74 | the conditions stated in this License. 75 | 76 | 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You 77 | to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, 78 | nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 79 | 80 | 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except 81 | as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 82 | 83 | 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides 84 | its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, 85 | any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for 86 | determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under 87 | this License. 88 | 89 | 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required 90 | by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, 91 | including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the 92 | use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or 93 | any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 94 | 95 | 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a 96 | fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting 97 | such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree 98 | to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your 99 | accepting any such warranty or additional liability. 100 | 101 | END OF TERMS AND CONDITIONS 102 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # enterprise-build-sample-js 2 | 3 | This sample application shows how to apply webpack on a legacy 4.x ArcGIS API for JavaScript application to get it ready for production. This sample is using the 4.10 version of the ArcGIS API for JavaScript. 4 | 5 | ![App](enterprise-build-sample-js.png?raw=true) 6 | 7 | ## Features 8 | * Sample Enterprise Application - Formatted and developed to work with Automated Webpack Builds 9 | * Sample Webpack Config - Sample webpack files to be adapted to your own applications 10 | 11 | ## Instructions 12 | 13 | 1. install [git](https://git-scm.com/) 14 | 2. install [Node.js](https://nodejs.org/) 15 | 3. Fork and then clone this repository. 16 | 4. Navigate to the folder project was cloned to. 17 | 5. Install local node packages into your project by running `npm install` 18 | 8. Run the webpack build using grunt by running `npm run build` 19 | 9. When the build is complete the dist directory should be a production ready version of the application 20 | 21 | ## Requirements 22 | 23 | * Notepad or your favorite HTML editor 24 | * [Node.js](https://nodejs.org/) 25 | * Web browser with access to the Internet 26 | 27 | ## Resources 28 | 29 | * [ArcGIS for JavaScript API Resource Center](https://js.arcgis.com/) 30 | * [ArcGIS WebPack Plugin Documentation](https://developers.arcgis.com/javascript/latest/guide/using-webpack/) 31 | * [ArcGIS WebPack Plugin Source](https://github.com/esri/arcgis-webpack-plugin) 32 | * [ArcGIS Blog](http://blogs.esri.com/esri/arcgis/) 33 | * [twitter@esri](http://twitter.com/esri) 34 | * [Dev Summit 2018 Build Presentation](https://www.youtube.com/watch?v=KGJs4au30Zk) 35 | * [Dev Summit 2017 Build Presentation](https://www.youtube.com/watch?v=3H_de4pAs70) 36 | * [Dev Summit 2017 Testing Presentation](https://www.youtube.com/watch?v=QJIQxx79MhE) 37 | 38 | ## Issues 39 | 40 | Find a bug or want to request a new feature? Please let us know by submitting an issue. 41 | 42 | ## Contributing 43 | 44 | Esri welcomes contributions from anyone and everyone. Please see our [guidelines for contributing](https://github.com/esri/contributing). 45 | 46 | ## Licensing 47 | Copyright 2015 Esri 48 | 49 | Licensed under the Apache License, Version 2.0 (the "License"); 50 | you may not use this file except in compliance with the License. 51 | You may obtain a copy of the License at 52 | 53 | http://www.apache.org/licenses/LICENSE-2.0 54 | 55 | Unless required by applicable law or agreed to in writing, software 56 | distributed under the License is distributed on an "AS IS" BASIS, 57 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 58 | See the License for the specific language governing permissions and 59 | limitations under the License. 60 | 61 | A copy of the license is available in the repository's [license.txt](License.txt?raw=true) file. 62 | -------------------------------------------------------------------------------- /enterprise-build-sample-js.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Esri/enterprise-build-sample-js/d3ded8d09e4cbf18cbcde9ae4ecde56027fe77db/enterprise-build-sample-js.png -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | 3 | module.exports = function(config) { 4 | config.set({ 5 | // base path, that will be used to resolve files and exclude 6 | basePath: '', 7 | 8 | frameworks: ['jasmine', 'dojo'], 9 | 10 | // list of files / patterns to load in the browser 11 | files: [ 12 | 'spec/main.js', 13 | 14 | // all the sources, tests 15 | {pattern: 'web/js/buildProject/**/*', included: false}, 16 | {pattern: 'spec/*.js', included: false} 17 | ], 18 | 19 | 20 | // uncomment the following for code coverage 21 | // preprocessors: { 22 | // // source files, that you wanna generate coverage for 23 | // // do not include tests or libraries 24 | // // (these files will be instrumented by Istanbul) 25 | // 'src/**/*.js': ['coverage'] 26 | // }, 27 | 28 | 29 | // test results reporter to use 30 | // possible values: dots || progress 31 | reporters: [ 32 | 'dots', 'junit' 33 | // uncomment the following line for code coverage 34 | // , 'coverage' 35 | ], 36 | 37 | junitReporter : { 38 | outputFile: 'test-results.xml' 39 | }, 40 | 41 | 42 | // web server port 43 | port: 9876, 44 | 45 | // proxy for cross domain requests 46 | proxies: { 47 | '/arcgis/': 'http://imagery.arcgisonline.com/arcgis/' 48 | }, 49 | 50 | 51 | // cli runner port 52 | runnerPort: 9100, 53 | 54 | 55 | // enable / disable colors in the output (reporters and logs) 56 | colors: true, 57 | 58 | 59 | // level of logging 60 | // possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG 61 | logLevel: config.LOG_INFO, 62 | 63 | 64 | // enable / disable watching file and executing tests whenever any file changes 65 | autoWatch: false, 66 | 67 | // Start these browsers, currently available: 68 | // - Chrome 69 | // - ChromeCanary 70 | // - Firefox 71 | // - Opera 72 | // - Safari 73 | // - PhantomJS 74 | // NOTE: you may need to first set it's path in an environment vairable, for example: 75 | // set FIREFOX_BIN="c:\Program Files (x86)\Mozilla Firefox\firefox.exe" 76 | // see: http://karma-runner.github.io/0.10/config/browsers.html 77 | browsers: [ 78 | 'ChromeHeadless' 79 | // add the name (from above) for each additional 80 | // browser you want to test below 81 | // , 'Firefox' 82 | ], 83 | 84 | 85 | // uncomment the following for coverage options 86 | // see: https://github.com/karma-runner/karma-coverage#options 87 | // coverageReporter: { 88 | // type : 'text', 89 | // dir: 'coverage/' 90 | // }, 91 | 92 | 93 | // Continuous Integration mode 94 | // if true, it capture browsers, run tests and exit 95 | singleRun: true 96 | }); 97 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "enterprise-test-sample", 3 | "version": "2.0.0", 4 | "description": "Here is a sample application, bundled with current versions of the patched Dojo source and ESRI JavaScript API AMD Build. The sample uses ArcGIS Online basemaps and services. Learn more [here](http://www.arcgis.com/about/).", 5 | "main": "Gruntfile.js", 6 | "scripts": { 7 | "test": "grunt test", 8 | "test:watch": "grunt watch", 9 | "start": "npm-run-all --parallel serve:dev test test:watch", 10 | "build": "webpack --mode production", 11 | "serve": "webpack-dev-server --mode production --open --https --compress", 12 | "serve:dev": "webpack-dev-server --mode development --open" 13 | }, 14 | "devDependencies": { 15 | "clean-webpack-plugin": "^2.0.0", 16 | "copy-webpack-plugin": "^5.0.0", 17 | "css-loader": "^2.1.0", 18 | "dojo-themes": "^1.13.0", 19 | "grunt": "^1.0.3", 20 | "grunt-contrib-watch": "^1.1.0", 21 | "grunt-eslint": "^20.1.0", 22 | "grunt-karma": "^3.0.1", 23 | "grunt-tomcat-deploy": "git://github.com/rsjones/grunt-tomcat-deploy.git", 24 | "grunt-zip": "^0.10.2", 25 | "html-loader": "^0.5.5", 26 | "html-webpack-plugin": "^3.2.0", 27 | "jasmine-core": "^3.3.0", 28 | "karma": "^4.0.1", 29 | "karma-chrome-launcher": "^2.2.0", 30 | "karma-coverage": "^1.1.2", 31 | "karma-dojo": "0.0.1", 32 | "karma-jasmine": "^2.0.1", 33 | "karma-junit-reporter": "^1.2.0", 34 | "mini-css-extract-plugin": "^0.5.0", 35 | "npm-run-all": "^4.1.5", 36 | "raw-loader": "^1.0.0", 37 | "requirejs": "^2.3.6", 38 | "uglifyjs-webpack-plugin": "^2.1.2", 39 | "webpack": "^4.29.6", 40 | "webpack-cli": "^3.2.3", 41 | "webpack-dev-server": "^3.2.1" 42 | }, 43 | "dependencies": { 44 | "@arcgis/webpack-plugin": "^4.10.5" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /spec/ToolbarSpec.js: -------------------------------------------------------------------------------- 1 | define([ 2 | 'buildProject/Toolbar', 3 | 'esri/layers/TileLayer' 4 | ], function( 5 | Toolbar, 6 | TileLayer 7 | ) { 8 | 9 | describe('components: Toolbar ', function() { 10 | var testArgs = { 11 | "view": { 12 | get:function(){}, 13 | watch:function(){} 14 | }, 15 | "config": {}, 16 | "map": {} 17 | }; 18 | 19 | // create the map 20 | beforeEach(function() { 21 | toolbar = new Toolbar(testArgs); 22 | }); 23 | 24 | // destroy the map 25 | afterEach(function() { 26 | toolbar.destroy(); 27 | }); 28 | 29 | it('clears graphics and info window', function(){ 30 | 31 | toolbar.view.graphics = jasmine.createSpyObj('toolbar.view.graphics',["removeAll"]); 32 | spyOn(toolbar,"hidePopup"); 33 | 34 | toolbar.clearGraphics(); 35 | 36 | expect(toolbar.view.graphics.removeAll).toHaveBeenCalled(); 37 | expect(toolbar.hidePopup).toHaveBeenCalled(); 38 | 39 | }); 40 | 41 | it('toggles street map', function(){ 42 | expect(toolbar.viewingStreets).toBe(false); 43 | 44 | var hideStreetMapCounter = 0; 45 | var showStreetMapCounter = 0; 46 | 47 | 48 | spyOn(toolbar,"_createStreetMapLayer"); 49 | spyOn(toolbar,"_hideStreetMapLayer"); 50 | spyOn(toolbar,"_showStreetMapLayer"); 51 | 52 | toolbar.toggleStreetMap(); 53 | 54 | expect(toolbar._createStreetMapLayer).toHaveBeenCalled(); 55 | expect(toolbar._showStreetMapLayer).toHaveBeenCalled(); 56 | 57 | toolbar.toggleStreetMap(); 58 | 59 | expect(toolbar._hideStreetMapLayer).toHaveBeenCalled(); 60 | 61 | toolbar.toggleStreetMap(); 62 | expect(toolbar._showStreetMapLayer).toHaveBeenCalled(); 63 | 64 | }); 65 | 66 | it('enables assets button', function(){ 67 | // 1. Toolbar should be disabled for zoom levels < 6 68 | 69 | 70 | // 2. Toolbar should be enabled for zoom levels >= 6 71 | 72 | 73 | //expect(toolbar.toggleAssetsButton.get("disabled")).toBeTruthy(); 74 | }); 75 | 76 | it('created Street Map Layer', function(){ 77 | 78 | toolbar.map = jasmine.createSpyObj('toolbar.map',['add']); 79 | toolbar._createStreetMapLayer(); 80 | 81 | expect(toolbar.streetMapLayer instanceof TileLayer).toBe(true); 82 | expect(toolbar.map.add).toHaveBeenCalled(); 83 | }); 84 | 85 | }); 86 | }); -------------------------------------------------------------------------------- /spec/main.js: -------------------------------------------------------------------------------- 1 | (function(window) { 2 | 'use strict'; 3 | 4 | var allTestFiles = []; 5 | var TEST_REGEXP = /.*Spec\.js$/; 6 | 7 | for (var file in window.__karma__.files) { 8 | if (TEST_REGEXP.test(file)) { 9 | allTestFiles.push(file); 10 | } 11 | } 12 | 13 | window.dojoConfig = { 14 | packages: [ 15 | // local pacakges to test 16 | { 17 | name:"buildProject", 18 | location:"../../../web/js/buildProject" 19 | }, 20 | 21 | // esri/dojo packages 22 | { 23 | name: 'dgrid', 24 | location: 'http://js.arcgis.com/4.6/dgrid' 25 | }, { 26 | name: 'dijit', 27 | location: 'http://js.arcgis.com/4.6/dijit' 28 | }, { 29 | name: 'esri', 30 | location: 'http://js.arcgis.com/4.6/esri' 31 | }, { 32 | name: 'dojo', 33 | location: 'http://js.arcgis.com/4.6/dojo' 34 | }, { 35 | name: 'dojox', 36 | location: 'http://js.arcgis.com/4.6/dojox' 37 | }, { 38 | name: 'moment', 39 | location: 'http://js.arcgis.com/4.6/moment' 40 | } 41 | ], 42 | }; 43 | 44 | 45 | /** 46 | * This function must be defined and is called back by the dojo adapter 47 | * @returns {string} a list of dojo spec/test modules to register with your testing framework 48 | */ 49 | window.__karma__.dojoStart = function() { 50 | return allTestFiles; 51 | }; 52 | })(window); -------------------------------------------------------------------------------- /test-reports/HeadlessChrome_64.0.3282_(Windows_10.0.0)/test-results.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /web/buildProject.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | height: 100%; 3 | width: 100%; 4 | margin: 0; 5 | padding: 0; 6 | overflow: hidden; 7 | } 8 | 9 | 10 | #map{ 11 | padding:0; 12 | } 13 | 14 | .toolbar { 15 | background:white; 16 | border-style:solid; 17 | border-width:1px; 18 | position:absolute; 19 | top:5px; 20 | right:5px; 21 | z-index:999; 22 | padding: 0px 2px 5px; 23 | width: 185px; 24 | } 25 | 26 | .toolbar .header { 27 | cursor: move; 28 | border-bottom-style:solid; 29 | border-bottom-color:black; 30 | border-bottom-width:1px; 31 | } 32 | 33 | .assetDialog { 34 | height: 500px; 35 | width: 500px; 36 | } 37 | 38 | .areaDialog { 39 | height: 500px; 40 | width: 500px; 41 | } 42 | 43 | .toolbar .dijitButtonNode { 44 | width: 170px; 45 | } 46 | 47 | .chartingPane { 48 | 49 | width:360px; 50 | background:white; 51 | border-style:solid; 52 | border-width:1px; 53 | position:absolute; 54 | top:5px; 55 | right:205px; 56 | z-index:999; 57 | } 58 | .chart { 59 | height:153px; 60 | width:200px; 61 | } 62 | 63 | .titleNode { 64 | font-weight:bold; 65 | font-face:arial; 66 | font-size:15px; 67 | border-bottom-style: solid; 68 | border-bottom-color: black; 69 | border-bottom-width: 1px; 70 | padding-left: 10px; 71 | } 72 | 73 | .flat .dijitTextBox { 74 | width: 60px !important; 75 | } -------------------------------------------------------------------------------- /web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 27 | 28 | 29 | 30 |
31 |
32 |
33 |
34 | 35 | -------------------------------------------------------------------------------- /web/index.js: -------------------------------------------------------------------------------- 1 | require([ 2 | "dojo/_base/kernel", 3 | "./js/buildProject/SiteManager" 4 | ], function (kernel, SiteManager) { 5 | kernel.global.site = new SiteManager(); 6 | kernel.global.site.init(); 7 | }); -------------------------------------------------------------------------------- /web/js/buildProject/ChartingPane.js: -------------------------------------------------------------------------------- 1 | /*global define,dojo,dijit,dojox,buildProject,esri*/ 2 | 3 | define([ 4 | "dojo/_base/declare", 5 | "dojo/_base/lang", 6 | "dojo/on", 7 | "dojo/dnd/Moveable", 8 | "dojox/charting/Chart2D", 9 | "dojox/charting/action2d/Highlight", 10 | "dojox/charting/action2d/MoveSlice", 11 | "dojox/charting/action2d/Tooltip", 12 | "dojox/charting/themes/MiamiNice", 13 | "esri/tasks/support/Query", 14 | "esri/tasks/QueryTask", 15 | // Our Project's classes --------------------------------------------- 16 | "./buildProject", 17 | // Widget base classes ----------------------------------------------- 18 | "dijit/_WidgetBase", 19 | "dijit/_TemplatedMixin", 20 | "dijit/_WidgetsInTemplateMixin", 21 | // Widget template --------------------------------------------------- 22 | "dojo/text!./templates/ChartingPane.html" 23 | ], function ( 24 | declare, 25 | lang, 26 | on, 27 | Moveable, 28 | Chart2D, 29 | Highlight, 30 | MoveSlice, 31 | Tooltip, 32 | MiamiNice, 33 | Query, 34 | QueryTask, 35 | buildProject, 36 | _WidgetBase, 37 | _TemplatedMixin, 38 | _WidgetsInTemplateMixin, 39 | template) { 40 | return declare([_WidgetBase, _TemplatedMixin, _WidgetsInTemplateMixin], { 41 | // summary: 42 | // Floating pane that displays a pie chart when users click on 43 | // an info grid row. 44 | // templateString: String 45 | // HTML template of the widget (imported using dojo/text) 46 | // chart: dojox.charting.Chart2D 47 | // Pie chart object 48 | // queryTask: esri.tasks.QueryTask 49 | // Query Task used to fetch geometry from a given district id. This is used on slice clicks. 50 | // data: Object 51 | // Data object loaded to populate pie chart. 52 | // map: esri.Map 53 | // The application's map object. 54 | // config: Object 55 | // The application's configuration object. 56 | // infoGrid: buildProject.InfoGrid 57 | // InfoGrid object that will interact with this pane. 58 | // (This should probably be changed to use subscribe/publish functionality in the future) 59 | templateString : template, 60 | chart: null, 61 | queryTask: null, 62 | data: null, 63 | map: null, 64 | config: null, 65 | infoGrid: null, 66 | postCreate: function(){ 67 | // summary: 68 | // Runs after the widget is completely formed. 69 | // description: 70 | // Runs after the widget is completely formed. Sets up the floating behavior and creates the query task. 71 | 72 | var moveable = new Moveable(this.domNode,{handle:this.header}); 73 | 74 | // Note: The 1 is appended to the entityDistrictUrl to run the query on Districts. 75 | // This can be modified to change the layer. To run query tasks on multiple layers, 76 | // new query tasks will have to be constructed. 77 | this.queryTask = new QueryTask(this.config.entityDistrictUrl + "/" + this.config.entityDistrictLayer); 78 | }, 79 | show: function(/*String*/field,/*String*/title,/*Object*/data,/*int*/layerId){ 80 | // summary: 81 | // Displays the charting pane and sets up the pie chart. 82 | // field: String 83 | // Statistic field clicked on from the Info Grid. 84 | // title: String 85 | // Label for the feature being described in the pie chart. 86 | // data: Object 87 | // Data object orginally used by the Info Grid. This should probably be changed to be fetched dynamically from a servlet. 88 | // layerId: int 89 | // Map layer that was identified to for the Info Grid that called this function. 90 | 91 | // Show the pane and set its title. 92 | this.domNode.style.display = ""; 93 | this.titleDiv.innerHTML = title + " " + field + " Results"; 94 | 95 | var dc = dojox.charting; 96 | // dojox.charting can be unpredictable, so it is usually cleaner to completely destroy 97 | // the object and re-instantiate then adjusting the values. 98 | if (this.chart){ 99 | this.chart.destroy(); 100 | } 101 | this.chart = new Chart2D(this.chartPane); 102 | var seriesData = []; 103 | 104 | // populating random data for each district as listed in the data file. 105 | // TODO: This needs to be changed to use actual data. 106 | for (var i=0;i 0) || identifyResults.length === undefined) { 66 | if (identifyResults[0]){ 67 | this.identifyResults = identifyResults[0]; 68 | } else { 69 | this.identifyResults = identifyResults; 70 | } 71 | this.view.graphics.removeAll(); 72 | this.view.get("popup").set("visible",false); 73 | 74 | // Destroy the grid and store since dojox is strange. 75 | if (this.grid){ 76 | this.grid.destroyRecursive(); 77 | delete this.store; 78 | } 79 | 80 | // Call to retrieve sample data. xhr.get cannot be used on different domains. 81 | // If you are using a servlet or data on another server, look into switching to dojo.io.script. 82 | 83 | xhr.get({ 84 | url: require.toUrl("js/data/sampleData.json"), 85 | handleAs: "json", 86 | load: lang.partial(dojo.hitch(this,this._finalPopulate),point), 87 | error: buildProject.displayError 88 | }, true); 89 | } else { 90 | buildProject.displayError("No results were returned from identify."); 91 | } 92 | } catch(e){ 93 | console.debug("ERROR",e); 94 | } 95 | }, 96 | _finalPopulate: function(/*esri.geometry.Point*/point,/*Object*/jsonResults){ 97 | // summary: 98 | // Finishes creating the chart. Called when the data object is returned from the server. 99 | // point: esri.geometry.Point 100 | // Point of map click event 101 | // jsonResults: Object 102 | // Data returned from the server. 103 | 104 | var data,attr = this.identifyResults.feature.attributes; 105 | 106 | // check to see if the layer being identified is an entity. 107 | // if it is, set the data to results attached to districts. 108 | // otherwise, assign arbitrary data. 109 | // TODO: Expand for actual data collected from server. 110 | 111 | if (this.identifyResults.layerId === 0){ 112 | data = jsonResults.entities[attr.dist_region_entity_ENTITY_ID].stats; 113 | this.data = jsonResults.entities[attr.dist_region_entity_ENTITY_ID]; 114 | } else { 115 | data = jsonResults.entities[9110].stats; 116 | this.data = jsonResults.entities[9110]; 117 | } 118 | var obj = {"label":"id", 119 | "identifier":"id", 120 | "items":data}; 121 | 122 | this.store = new ItemFileReadStore({data:obj}); 123 | var layout = [{"field":"id", 124 | "title":"Field", 125 | "width":"auto" }, 126 | { "field":"value", 127 | "title":"Value", 128 | "width":"auto" }]; 129 | 130 | var feature = this.identifyResults.feature; 131 | var graphic = new Graphic({ 132 | "geometry": feature.geometry, 133 | "symbol": new SimpleFillSymbol(), 134 | "attributes": feature.attributes 135 | }); 136 | this.view.graphics.add(graphic); 137 | 138 | // Setup the popup so that it can hold the datagrid. 139 | var popup = this.view.get("popup"); 140 | 141 | popup.set("title",graphic.attributes[this.identifyResults.displayFieldName]); 142 | popup.set("content",this.domNode); 143 | 144 | popup.open({ 145 | "location": point 146 | }); 147 | 148 | this.grid = new DataGrid({ 149 | store: this.store, 150 | structure: layout, 151 | canSort: function(){return false;} 152 | },domConstruct.create("div")); 153 | 154 | domConstruct.place(this.gridDomNode,this.domNode); 155 | this.gridDomNode.appendChild(this.grid.domNode); 156 | this.grid.startup(); 157 | 158 | window.setTimeout(lang.hitch(this.grid,this.grid.resize),500); 159 | 160 | // Setup click event behavior. 161 | this.grid.own(this.grid.on("RowClick", lang.hitch(this, function (evt) { 162 | var item = this.store._arrayOfAllItems[evt.rowIndex]; 163 | if (!this.chartingPane){ 164 | this.chartingPane = new ChartingPane({"id":"chartingPane","map":this.map,"config":this.config,"infoGrid":this},domConstruct.create('div')); 165 | document.body.appendChild(this.chartingPane.domNode); 166 | } 167 | this.chartingPane.show(this.store.getValue(item,"id"),graphic.attributes[this.identifyResults.displayFieldName],this.data,this.identifyResults.layerId); 168 | }))); 169 | } 170 | }); 171 | }); -------------------------------------------------------------------------------- /web/js/buildProject/SiteManager.js: -------------------------------------------------------------------------------- 1 | /*global define, require, dojo,dijit,esri,buildProject*/ 2 | define([ 3 | "dojo/_base/declare", 4 | "dojo/_base/kernel", 5 | "dojo/_base/lang", 6 | "dojo/_base/xhr", 7 | "dojo/dom", 8 | "dojo/on", 9 | "esri/Map", 10 | "esri/views/MapView", 11 | "esri/geometry/Extent", 12 | "esri/tasks/support/IdentifyParameters", 13 | "esri/tasks/IdentifyTask", 14 | // Our Project's classes --------------------------------------------- 15 | "./buildProject", 16 | "./InfoGrid", 17 | "./Toolbar" 18 | ], function ( 19 | declare, 20 | kernel, 21 | lang, 22 | xhr, 23 | dom, 24 | on, 25 | Map, 26 | MapView, 27 | Extent, 28 | IdentifyParameters, 29 | IdentifyTask, 30 | buildProject, 31 | InfoGrid, 32 | Toolbar) { 33 | return declare (null, { 34 | // summary: 35 | // Loader class. Initializes the site and several essential widgets. 36 | // toolbar: String | buildProject.Toolbar 37 | // Initially the id of a DOM Node to create the toolbar in. 38 | // infoGrid: buildProject.InfoGrid 39 | // Chart to be displayed inside info window on identify 40 | // map: esri.Map 41 | // The application's map object 42 | // identifyTask: esri.tasks.IdentifyTask 43 | // Task used to identify entities, districts, and zip codes. 44 | // identifyParams: esri.tasks.support.IdentifyParameters 45 | // Parameters used by the Identify Task object 46 | // config: Object 47 | // Configuration object loaded from data/config.json 48 | // identifyConnect: dojo.connect 49 | // Keeps track of the on click behavior for entities, districts, and zip codes. 50 | 51 | toolbar: "toolbarNode", 52 | infoGrid: null, 53 | map: null, 54 | identifyTask: null, 55 | identifyParams: null, 56 | config: null, 57 | identifyConnect: null, 58 | init: function(){ 59 | // summary: 60 | // Loades the flat config file and initializes several widgets. 61 | 62 | 63 | xhr.get({ 64 | "url": require.toUrl("js/data/config.json"), 65 | "handleAs":"json", 66 | "load": lang.hitch(this,function(config){ 67 | this.config = config; 68 | 69 | this.setupMap(); 70 | 71 | this.setupToolbar(); 72 | 73 | this.setupInfoGrid(); 74 | }), 75 | "error": buildProject.displayError 76 | }); 77 | }, 78 | setupMap: function(){ 79 | // summary: 80 | // Sets up the map starting pararmeters and loads all standard layers onto the map. 81 | 82 | var initExtent = new Extent({ 83 | "xmin":-8585309.048629632, 84 | "ymin":4702297.046680435, 85 | "xmax":-8560849.19957841, 86 | "ymax":4714259.441607048, 87 | "spatialReference":{"wkid":102100} 88 | }); 89 | 90 | this.map = new Map({ 91 | "basemap": "dark-gray" 92 | }); 93 | 94 | 95 | var lods = this.config.mapLods; 96 | this.view = new MapView({ 97 | "container": "map", 98 | "map": this.map, 99 | "extent": initExtent, 100 | "lods": lods 101 | }); 102 | //resize the map when the browser resizes - view the 'Resizing and repositioning the map' section in 103 | //the following help topic for more details http://help.esri.com/EN/webapi/javascript/arcgis/help/jshelp_start.htm#jshelp/inside_guidelines.htm 104 | var resizeTimer; 105 | this.map.on('load', lang.hitch(this, function(theMap) { 106 | on(kernel.global, "onresize", lang.hitch(this, function(){ //resize the map if the div is resized 107 | clearTimeout(resizeTimer); 108 | resizeTimer = setTimeout( lang.hitch(this,function(){ 109 | this.map.resize(); 110 | this.map.reposition(); 111 | }), 250); 112 | })); 113 | })); 114 | 115 | }, 116 | setupToolbar: function(){ 117 | // summary: 118 | // Initializes the toolbar widget. 119 | 120 | this.toolbar = dom.byId(this.toolbar); 121 | if (this.toolbar){ 122 | this.toolbar = new Toolbar({ 123 | "id":"toolbar", 124 | "map":this.map, 125 | "view": this.view, 126 | "config":this.config 127 | },this.toolbar); 128 | } 129 | }, 130 | setupInfoGrid: function(){ 131 | // summary: 132 | // Initializes the InfoGrid widget and sets up the Identify Task. 133 | 134 | this.identifyTask = new IdentifyTask(this.config.entityDistrictUrl); 135 | this.identifyParams = new IdentifyParameters(); 136 | 137 | this.identifyParams.tolerance = 1; 138 | this.identifyParams.returnGeometry = true; 139 | 140 | this.infoGrid = new InfoGrid({ 141 | "id":"infoGrid", 142 | "map":this.map, 143 | "config":this.config, 144 | "view": this.view 145 | }); 146 | 147 | this.enableIdentifyConnect(); 148 | }, 149 | disableIdentifyConnect: function(){ 150 | // summary: 151 | // Disables the click to identify behavior for Entities, Districts, and Zip Codes. 152 | // description: 153 | // Disables the click to identify behavior for Entities, Districts, and Zip Codes. 154 | // This is used by the travel ring tool, and asset connections to allow us to enable 155 | // other map click actions. 156 | 157 | this.identifyConnect.remove(); 158 | }, 159 | enableIdentifyConnect: function(){ 160 | // summary: 161 | // Enables the click to identify behavior for Entities, Districts, and Zip Codes. 162 | // description: 163 | // Enables the click to identify behavior for Entities, Districts, and Zip Codes. 164 | // This is used by the travel ring tool, and asset connections to allow us to restore 165 | // default functionality when they are done with the map. 166 | 167 | this.identifyConnect = on(this.view,"click",lang.hitch(this,this.identify)); 168 | }, 169 | identify: function(evt){ 170 | // summary: 171 | // Runs an identify task on the given point. 172 | // description: 173 | // Runs an identify task on the given point.This is run by the identify 174 | // connect handler whenever a user clicks on the map. 175 | // evt: Event 176 | // Mouse click event. 177 | 178 | this.identifyParams.geometry = evt.mapPoint; 179 | this.identifyParams.mapExtent = this.view.get("extent"); 180 | 181 | // This is used to determine which map layer we are running the identify on 182 | // and is based on the zoom level of the map. 183 | // 0: Entities 184 | // 1: Districts 185 | // 2: Zip Codes 186 | 187 | var level = this.view.get("zoom"); 188 | this.identifyParams.layerIds = [this.config.entityDistrictLayer]; 189 | 190 | this.identifyTask.execute(this.identifyParams).then( 191 | lang.partial(dojo.hitch(this.infoGrid,this.infoGrid.populate),evt.mapPoint), 192 | buildProject.displayError 193 | ); 194 | } 195 | }); 196 | }); -------------------------------------------------------------------------------- /web/js/buildProject/Toolbar.js: -------------------------------------------------------------------------------- 1 | /*global define, dojo,dijit,esri,buildProject,site*/ 2 | 3 | define([ 4 | "dojo/_base/declare", 5 | "dojo/_base/kernel", 6 | "dojo/_base/lang", 7 | "dojo/dom", 8 | "dojo/on", 9 | "dojo/dnd/Moveable", 10 | "esri/PopupTemplate", 11 | "esri/layers/MapImageLayer", 12 | "esri/layers/TileLayer", 13 | "esri/symbols/SimpleMarkerSymbol", 14 | "esri/tasks/support/IdentifyParameters", 15 | "esri/tasks/IdentifyTask", 16 | // Our Project's classes --------------------------------------------- 17 | "./buildProject", 18 | "./TravelRingTool", 19 | // Widget base classes ----------------------------------------------- 20 | "dijit/_WidgetBase", 21 | "dijit/_TemplatedMixin", 22 | "dijit/_WidgetsInTemplateMixin", 23 | // Widget template --------------------------------------------------- 24 | "dojo/text!./templates/Toolbar.html", 25 | // Widgets in template (do not need to be included in function () ---- 26 | "dijit/form/Button" 27 | ], function ( 28 | declare, 29 | kernel, 30 | lang, 31 | dom, 32 | on, 33 | Moveable, 34 | PopupTemplate, 35 | MapImageLayer, 36 | TileLayer, 37 | SimpleMarkerSymbol, 38 | IdentifyParameters, 39 | IdentifyTask, 40 | buildProject, 41 | TravelRingTool, 42 | _WidgetBase, 43 | _TemplatedMixin, 44 | _WidgetsInTemplateMixin, 45 | template) { 46 | return declare([_WidgetBase, _TemplatedMixin, _WidgetsInTemplateMixin], { 47 | // summary: 48 | // Toolbar widget. Also handles the activation of several tools. 49 | // templateString: String 50 | // HTML template of the widget (imported using dojo/text) 51 | // streetMapLayer: esri.layers.TileLayer 52 | // Transportation layer to be displayed on Toggle Streets 53 | // viewingStreets: boolean 54 | // Flag to keep track of which layers should be on the map 55 | // viewingAssets: boolean 56 | // Flag to keep track of which layers should be on the map 57 | // travelRingTool: buildProject.TravelRingTool 58 | // Tool for adding a travel ring to the map. 59 | // identifyTask: esri.tasks.IdentifyTasks 60 | // Identify task used for the assets layer. 61 | // identifyParams: esri.tasks.IdentifyParameters 62 | // Parameters used for the assets layer identify task. 63 | // assetConnect: dojo.connect 64 | // Keeps track of the onclick event for the assets layer. 65 | templateString : template, 66 | streetMapLayer: null, 67 | viewingStreets: false, 68 | viewingAssets: false, 69 | travelRingTool: null, 70 | identifyTask: null, 71 | identifyParams: null, 72 | assetConnect: null, 73 | postCreate: function(){ 74 | // summary: 75 | // Runs after the widget is completely formed. 76 | // description: 77 | // Runs after the widget is completely formed. Sets up the floating behavior. 78 | 79 | var moveable = new Moveable(this.domNode,{handle:this.header}); 80 | 81 | var setAssetButtonState = lang.hitch(this,function(zoom){ 82 | if (zoom <= 6){ 83 | this.toggleAssetsButton.set("disabled",true); 84 | } else { 85 | this.toggleAssetsButton.set("disabled",false); 86 | } 87 | }); 88 | 89 | // Set initial assets Button state 90 | setAssetButtonState(this.view.get("zoom")); 91 | //reset assets 92 | this.view.watch("zoom", setAssetButtonState); 93 | }, 94 | toggleStreetMap: function(){ 95 | // summary: 96 | // Turns the street map on and off when the appropriate button is clicked. 97 | // Also makes sure the zip code layer is hidden when the street map is not. 98 | 99 | 100 | this._createStreetMapLayer(); 101 | 102 | if (this.viewingStreets){ 103 | this._hideStreetMapLayer(); 104 | } else { 105 | this._showStreetMapLayer(); 106 | } 107 | 108 | this.viewingStreets = !this.viewingStreets; 109 | }, 110 | _createStreetMapLayer: function(){ 111 | if (!this.streetMapLayer){ 112 | this.streetMapLayer = new TileLayer({ 113 | "url": this.config.transportationUrl, 114 | "visible": false 115 | }); 116 | 117 | this.map.add(this.streetMapLayer); 118 | } 119 | }, 120 | _hideStreetMapLayer: function(){ 121 | this.streetMapLayer.set("visible",false); 122 | this.toggleStreetsButton.set("label","Show Transportation"); 123 | }, 124 | _showStreetMapLayer: function(){ 125 | this.streetMapLayer.set("visible",true); 126 | this.toggleStreetsButton.set("label","Hide Transportation"); 127 | }, 128 | toggleAssetLayer: function(){ 129 | // summary: 130 | // Turns the asset layer on and off when the appropriate button is clicked. 131 | // description: 132 | // Turns the asset layer on and off when the appropriate button is clicked. 133 | // There is a click to identify behavior associated with the assets layer. This 134 | // function also handles the toggling of that and ensures the right layer is being 135 | // used for click to identify. 136 | 137 | if (!this.assetLayer){ 138 | this.assetLayer = new MapImageLayer({ 139 | "url": this.config.assetsUrl, 140 | "sublayers": [ 141 | { 142 | id: 0, 143 | visible: true 144 | } 145 | ] 146 | }); 147 | 148 | this.map.add(this.assetLayer); 149 | } 150 | if (this.viewingAssets){ 151 | 152 | this.assetLayer.set("visible",false); 153 | this.toggleAssetsButton.set("label","Show Assets"); 154 | this.disableAssetConnect(); 155 | kernel.global.site.enableIdentifyConnect(); 156 | 157 | } else { 158 | this.assetLayer.set("visible",true); 159 | this.toggleAssetsButton.set("label","Hide Assets"); 160 | 161 | if (!this.identifyTask){ 162 | this.identifyTask = new IdentifyTask(this.config.assetsUrl); 163 | 164 | this.identifyParams = new IdentifyParameters(); 165 | 166 | this.identifyParams.tolerance = 2; 167 | this.identifyParams.layerIds = [0]; 168 | this.identifyParams.returnGeometry = true; 169 | this.identifyParams.layerOption = IdentifyParameters.LAYER_OPTION_ALL; 170 | } 171 | this.enableAssetConnect(); 172 | kernel.global.site.disableIdentifyConnect(); 173 | 174 | } 175 | 176 | 177 | 178 | this.viewingAssets = !this.viewingAssets; 179 | }, 180 | enableAssetConnect: function(){ 181 | // summary: 182 | // Enables the identify connect behavior for asset layers. 183 | 184 | // Make sure the sites identify connect is turned off. 185 | kernel.global.site.disableIdentifyConnect(); 186 | 187 | this.assetConnect = this.map.on("click", lang.hitch(this,function(evt){ 188 | this.identifyParams.geometry = evt.mapPoint; 189 | this.identifyParams.mapExtent = this.map.extent; 190 | 191 | // when the identify task is completed we create a poopup for the given asset. 192 | this.identifyTask.execute(this.identifyParams,lang.hitch(this,function(identifyResults){ 193 | if (identifyResults.length === 0){ 194 | buildProject.displayError("No assets found at click location"); 195 | } else { 196 | var feature = identifyResults[0].feature; 197 | feature.set("symbol",new SimpleMarkerSymbol()); 198 | feature.set("popupTemplate",new PopupTemplate({"title":"Asset","content":"${*}"})); 199 | 200 | this.view.graphics.add(feature); 201 | var screenPt = this.map.toScreen(feature.geometry); 202 | 203 | var popup = this.view.get("popup"); 204 | popup.set("title","Asset"); 205 | popup.set("content",feature.getContent()); 206 | popup.show(screenPt,this.map.getInfoWindowAnchor(screenPt)); 207 | } 208 | }),buildProject.displayError); 209 | })); 210 | 211 | 212 | }, 213 | disableAssetConnect: function(){ 214 | // summary: 215 | // Disables the identify connect behavior for asset layers. 216 | 217 | this.view.get("popup").set("visible",false); 218 | this.assetConnect.remove(); 219 | }, 220 | toggleTravelRingTool: function(){ 221 | // summary: 222 | // Turns the travel ring tool on and off when the appropriate button is clicked. 223 | 224 | if (!this.travelRingTool){ 225 | this.travelRingTool = new TravelRingTool({ 226 | map:this.map, 227 | config:this.config, 228 | toolbar:this, 229 | "view": this.view}); 230 | } 231 | if (this.travelRingTool.isActive){ 232 | this.toggleTravelRingButton.containerNode.style.fontWeight = ""; 233 | this.toggleTravelRingButton.domNode.firstChild.style.backgroundColor = ""; 234 | this.travelRingTool.deactivate(); 235 | } else { 236 | this.toggleTravelRingButton.containerNode.style.fontWeight = "bold"; 237 | this.toggleTravelRingButton.domNode.firstChild.style.backgroundColor = "#AFD9FF"; 238 | this.travelRingTool.activate(); 239 | } 240 | }, 241 | clearGraphics: function(){ 242 | // summary: 243 | // Clears all graphics from the map. 244 | 245 | this.view.graphics.removeAll(); 246 | this.hidePopup(); 247 | }, 248 | hidePopup: function(){ 249 | this.view.get("popup").set("visible",false); 250 | } 251 | }); 252 | }); -------------------------------------------------------------------------------- /web/js/buildProject/ToolbarSkeleton.js: -------------------------------------------------------------------------------- 1 | /*global define*/ 2 | define([ 3 | "dojo/_base/declare", 4 | "dojo/on", 5 | "dojo/_base/lang" 6 | ], 7 | function(declare, on, lang) { 8 | return { 9 | postCreate: function(){ 10 | 11 | this.makeToolbarDraggable(); 12 | 13 | on(this.toggleTransportationButton,"click",lang.hitch(this,this.toggleTransportation)); 14 | 15 | on(this.toggleAssetsButton,"click",lang.hitch(this,this.toggleAssets)); 16 | 17 | on(this.createTravelRingButton,"click",lang.hitch(this,this.toggleTravelRingTool)); 18 | 19 | on(this.clearGraphicsButton,"click",lang.hitch(this,this.clearGraphics)); 20 | }, 21 | makeToolbarDraggable: function(){ 22 | }, 23 | toggleTransportation: function(){ 24 | if(this.isTransportationVisible()){ 25 | this.hideTransportation(); 26 | } else { 27 | this.showTransportation(); 28 | } 29 | }, 30 | isTransportationVisible: function(){ 31 | }, 32 | hideTransportation: function(){ 33 | }, 34 | showTransportation: function(){ 35 | }, 36 | toggleAssetsButton: function(){ 37 | if (this.areAssetsVisible()){ 38 | this.hideAssets(); 39 | } else { 40 | this.showAssets(); 41 | } 42 | }, 43 | areAssetsVisible: function(){ 44 | }, 45 | hideAssets: function(){ 46 | }, 47 | showAssets: function(){ 48 | }, 49 | toggleTravelRingTool: function(){ 50 | if (this.isTravelRingToolActive()){ 51 | this.activateTravelRingTool(); 52 | } else { 53 | this.deactivateTravelRingTool(); 54 | } 55 | }, 56 | isTravelRingToolActive: function(){ 57 | }, 58 | activateTravelRingTool: function(){ 59 | }, 60 | deactivateTravelRingTool: function(){ 61 | }, 62 | clearGraphics: function() { 63 | } 64 | }; 65 | } 66 | ); -------------------------------------------------------------------------------- /web/js/buildProject/TravelRingDialog.js: -------------------------------------------------------------------------------- 1 | /*global define*/ 2 | 3 | define([ 4 | "dojo/_base/Color", 5 | "dojo/_base/declare", 6 | "dojo/_base/lang", 7 | "esri/Graphic", 8 | "esri/symbols/SimpleFillSymbol", 9 | "esri/symbols/SimpleLineSymbol", 10 | "esri/symbols/SimpleMarkerSymbol", 11 | "esri/tasks/support/FeatureSet", 12 | // Our Project's classes --------------------------------------------- 13 | "./buildProject", 14 | // Widget base classes ----------------------------------------------- 15 | "dijit/_WidgetBase", 16 | "dijit/_TemplatedMixin", 17 | "dijit/_WidgetsInTemplateMixin", 18 | // Widget template --------------------------------------------------- 19 | "dojo/text!./templates/_TravelRingDialog.html", 20 | // Widgets in template (do not need to be included in function () ---- 21 | "dijit/Dialog", 22 | "dijit/form/NumberSpinner", 23 | "dojox/form/BusyButton" 24 | ], function ( 25 | Color, 26 | declare, 27 | lang, 28 | Graphic, 29 | SimpleFillSymbol, 30 | SimpleLineSymbol, 31 | SimpleMarkerSymbol, 32 | FeatureSet, 33 | buildProject, 34 | _WidgetBase, 35 | _TemplatedMixin, 36 | _WidgetsInTemplateMixin, 37 | template) { 38 | return declare([_WidgetBase, _TemplatedMixin, _WidgetsInTemplateMixin], { 39 | // summary: 40 | // Internal dialog used by the Travel Ring Tool. 41 | // description: 42 | // This dialog handles all the major functionality of the Travel Ring 43 | // and accepts user input. 44 | // templateString: String 45 | // HTML template of the widget (imported using dojo/text) 46 | // geometry: esri.geometry.Point 47 | // Point to draw the travel ring around. 48 | templateString : template, 49 | geometry: null, 50 | 51 | show: function(/*esri.geometry.Point*/geometry){ 52 | // summary: 53 | // Opens the dialog. 54 | // geometry: esri.geometry.Point 55 | // Point to draw the travel ring around. 56 | 57 | this.innerDialog.show(); 58 | this.geometry = geometry; 59 | this.submitButton.cancel(); 60 | }, 61 | getRing: function(/*Event*/evt){ 62 | // summary: 63 | // Once the user has submitted the form, this function creates the travel ring. 64 | // evt: Event 65 | // Click event on the submit button. 66 | 67 | // First use dijit to make sure the form is valid 68 | if (this.timeInput.isValid()){ 69 | var featureSet = new FeatureSet(); 70 | featureSet.geometryType = "point"; 71 | featureSet.features = [new Graphic({ 72 | "geometry":this.geometry, 73 | "symbol": new SimpleMarkerSymbol(), 74 | "attributes": {} 75 | })]; 76 | 77 | var inputParams = { 78 | "Input_Location": featureSet, 79 | "Drive_Times": this.timeInput.get("value") 80 | }; 81 | 82 | // Submit the job and check on its results once the tool returns a completion message. 83 | this.driveTimesTask.execute(inputParams).then(lang.hitch(this,function(response){ 84 | 85 | console.debug("FINISHED WITH EXECUTE"); 86 | console.debug(response); 87 | // Add the input points and result polygons to the map. 88 | // Note: This function loops through even though there will only be one polygon. 89 | // This will make it easier to add multiple points or the option to enter 90 | // multiple time values in the future. 91 | 92 | var results = response.results; 93 | try{ 94 | var polygons = results[0]; 95 | for (var i=0;i 2 |
3 |
4 |
5 |
6 |
7 | -------------------------------------------------------------------------------- /web/js/buildProject/templates/InfoGrid.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
-------------------------------------------------------------------------------- /web/js/buildProject/templates/Toolbar.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 |
6 |
7 |
8 | 9 | 10 |
11 | -------------------------------------------------------------------------------- /web/js/buildProject/templates/_TravelRingDialog.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | Travel Time (in minutes): 4 |

10 | 11 |
16 |
17 |
-------------------------------------------------------------------------------- /web/js/data/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "transportationUrl": "http://server.arcgisonline.com/ArcGIS/rest/services/Reference/World_Transportation/MapServer", 3 | "boundariesUrl": "http://server.arcgisonline.com/ArcGIS/rest/services/Reference/World_Boundaries_and_Places_Alternate/MapServer", 4 | "entityDistrictUrl": "https://maps2.dcgis.dc.gov/dcgis/rest/services/DCGIS_DATA/Demographic_WebMercator/MapServer/", 5 | "entityDistrictLayer": "8", 6 | "assetsUrl": "https://maps2.dcgis.dc.gov/dcgis/rest/services/DCGIS_DATA/Demographic_WebMercator/MapServer/", 7 | "driveTimesUrl": "http://sampleserver1.arcgisonline.com/ArcGIS/rest/services/Network/ESRI_DriveTime_US/GPServer/CreateDriveTimePolygons", 8 | "mapLods": [ 9 | {"level" : 0, "resolution" : 9783.93962049996, "scale" : 36978595.474472}, 10 | {"level" : 1, "resolution" : 4891.96981024998, "scale" : 18489297.737236}, 11 | {"level" : 2, "resolution" : 2445.98490512499, "scale" : 9244648.868618}, 12 | {"level" : 3, "resolution" : 1222.99245256249, "scale" : 4622324.434309}, 13 | {"level" : 4, "resolution" : 611.49622628138, "scale" : 2311162.217155}, 14 | {"level" : 5, "resolution" : 305.748113140558, "scale" : 1155581.108577}, 15 | {"level" : 6, "resolution" : 152.874056570411, "scale" : 577790.554289}, 16 | {"level" : 7, "resolution" : 76.4370282850732, "scale" : 288895.277144}, 17 | {"level" : 8, "resolution" : 38.2185141425366, "scale" : 144447.638572}, 18 | {"level" : 9, "resolution" : 19.1092570712683, "scale" : 72223.819286} 19 | ] 20 | } -------------------------------------------------------------------------------- /web/js/data/sampleData.json: -------------------------------------------------------------------------------- 1 | {"entities": { 2 | "9110": { 3 | "districts": [ 4 | { 5 | "id": "1150", 6 | "value": 34563 7 | }, 8 | { 9 | "id": "1190", 10 | "value": 409812 11 | }, 12 | { 13 | "id": "1280", 14 | "value": 53999 15 | }, 16 | { 17 | "id": "1530", 18 | "value": 438124 19 | }, 20 | { 21 | "id": "1070", 22 | "value": 2457274 23 | }, 24 | { 25 | "id": "1060", 26 | "value": 20971 27 | } 28 | ], 29 | "stats": [ 30 | { 31 | "id": "Stat1", 32 | "value": 3354563 33 | }, 34 | { 35 | "id": "Stat2", 36 | "value": 3423 37 | }, 38 | { 39 | "id": "Stat3", 40 | "value": 3999 41 | }, 42 | { 43 | "id": "Stat4", 44 | "value": 2098 45 | }, 46 | { 47 | "id": "Stat5", 48 | "value": 983 49 | }, 50 | { 51 | "id": "Stat6", 52 | "value": 235 53 | } 54 | ] 55 | }, 56 | "9120": { 57 | "districts": 58 | [ 59 | { 60 | "id": "4160", 61 | "value": 34563 62 | }, 63 | { 64 | "id": "4200", 65 | "value": 409812 66 | }, 67 | { 68 | "id": "4180", 69 | "value": 53999 70 | }, 71 | { 72 | "id": "4130", 73 | "value": 438124 74 | }, 75 | { 76 | "id": "4600", 77 | "value": 2457274 78 | }, 79 | { 80 | "id": "4080", 81 | "value": 20971 82 | }, 83 | { 84 | "id": "3180", 85 | "value": 438124 86 | }, 87 | { 88 | "id": "4220", 89 | "value": 2457274 90 | }, 91 | { 92 | "id": "3150", 93 | "value": 20971 94 | } 95 | ], 96 | "stats": [ 97 | { 98 | "id": "Stat1", 99 | "value": 3354563 100 | }, 101 | { 102 | "id": "Stat2", 103 | "value": 3423 104 | }, 105 | { 106 | "id": "Stat3", 107 | "value": 3999 108 | }, 109 | { 110 | "id": "Stat4", 111 | "value": 2098 112 | }, 113 | { 114 | "id": "Stat5", 115 | "value": 983 116 | }, 117 | { 118 | "id": "Stat6", 119 | "value": 235 120 | } 121 | ] 122 | }, 123 | "9130": { 124 | "districts": [ 125 | { 126 | "id": "3810", 127 | "value": 34563 128 | }, 129 | { 130 | "id": "5050", 131 | "value": 409812 132 | }, 133 | { 134 | "id": "5560", 135 | "value": 53999 136 | }, 137 | { 138 | "id": "5070", 139 | "value": 438124 140 | }, 141 | { 142 | "id": "5100", 143 | "value": 2457274 144 | }, 145 | { 146 | "id": "5650", 147 | "value": 20971 148 | }, 149 | { 150 | "id": "5010", 151 | "value": 53999 152 | }, 153 | { 154 | "id": "5360", 155 | "value": 438124 156 | }, 157 | { 158 | "id": "5230", 159 | "value": 2457274 160 | }, 161 | { 162 | "id": "5140", 163 | "value": 20971 164 | } 165 | ], 166 | "stats": [ 167 | { 168 | "id": "Stat1", 169 | "value": 3354563 170 | }, 171 | { 172 | "id": "Stat2", 173 | "value": 3423 174 | }, 175 | { 176 | "id": "Stat3", 177 | "value": 3999 178 | }, 179 | { 180 | "id": "Stat4", 181 | "value": 2098 182 | }, 183 | { 184 | "id": "Stat5", 185 | "value": 983 186 | }, 187 | { 188 | "id": "Stat6", 189 | "value": 235 190 | } 191 | ] 192 | }, 193 | "9170": { 194 | "districts":[ 195 | { 196 | "id": "1420", 197 | "value": 34563 198 | }, 199 | { 200 | "id": "1500", 201 | "value": 409812 202 | }, 203 | { 204 | "id": "1760", 205 | "value": 53999 206 | }, 207 | { 208 | "id": "2580", 209 | "value": 438124 210 | }, 211 | { 212 | "id": "2200", 213 | "value": 2457274 214 | }, 215 | { 216 | "id": "2100", 217 | "value": 20971 218 | }, 219 | { 220 | "id": "2160", 221 | "value": 20971 222 | }, 223 | { 224 | "id": "2040", 225 | "value": 438124 226 | }, 227 | { 228 | "id": "2510", 229 | "value": 2457274 230 | } 231 | ], 232 | "stats": [ 233 | { 234 | "id": "Stat1", 235 | "value": 3354563 236 | }, 237 | { 238 | "id": "Stat2", 239 | "value": 3423 240 | }, 241 | { 242 | "id": "Stat3", 243 | "value": 3999 244 | }, 245 | { 246 | "id": "Stat4", 247 | "value": 2098 248 | }, 249 | { 250 | "id": "Stat5", 251 | "value": 983 252 | }, 253 | { 254 | "id": "Stat6", 255 | "value": 235 256 | } 257 | ] 258 | }, 259 | "9180":{ 260 | "districts": [ 261 | { 262 | "id": "3240", 263 | "value": 34563 264 | }, 265 | { 266 | "id": "2640", 267 | "value": 409812 268 | }, 269 | { 270 | "id": "3320", 271 | "value": 53999 272 | }, 273 | { 274 | "id": "3050", 275 | "value": 438124 276 | }, 277 | { 278 | "id": "3250", 279 | "value": 2457274 280 | }, 281 | { 282 | "id": "3130", 283 | "value": 20971 284 | }, 285 | { 286 | "id": "3660", 287 | "value": 438124 288 | }, 289 | { 290 | "id": "3170", 291 | "value": 2457274 292 | }, 293 | { 294 | "id": "5470", 295 | "value": 20971 296 | } 297 | ], 298 | "stats": [ 299 | { 300 | "id": "Stat1", 301 | "value": 3354563 302 | }, 303 | { 304 | "id": "Stat2", 305 | "value": 3423 306 | }, 307 | { 308 | "id": "Stat3", 309 | "value": 3999 310 | }, 311 | { 312 | "id": "Stat4", 313 | "value": 2098 314 | }, 315 | { 316 | "id": "Stat5", 317 | "value": 983 318 | }, 319 | { 320 | "id": "Stat6", 321 | "value": 235 322 | } 323 | ] 324 | } 325 | }} -------------------------------------------------------------------------------- /web/js/images/logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Esri/enterprise-build-sample-js/d3ded8d09e4cbf18cbcde9ae4ecde56027fe77db/web/js/images/logo.jpg -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack') 2 | 3 | const ArcGISPlugin = require("@arcgis/webpack-plugin"); 4 | const CleanWebpackPlugin = require("clean-webpack-plugin"); 5 | const MiniCssExtractPlugin = require("mini-css-extract-plugin"); 6 | const UglifyJsPlugin = require("uglifyjs-webpack-plugin"); 7 | const HtmlWebPackPlugin = require("html-webpack-plugin"); 8 | const CopyWebpackPlugin = require("copy-webpack-plugin"); 9 | 10 | const path = require("path"); 11 | 12 | module.exports = { 13 | entry: { 14 | index: ["./web/buildProject.css", "./web/index.js"] 15 | }, 16 | output: { 17 | filename: "[name].[chunkhash].js", 18 | publicPath: "" 19 | }, 20 | optimization: { 21 | minimizer: [ 22 | new UglifyJsPlugin({ 23 | cache: true, 24 | parallel: true, 25 | sourceMap: false 26 | }) 27 | ] 28 | }, 29 | module: { 30 | rules: [ 31 | { 32 | test: /\.html$/, 33 | use: [ 34 | { 35 | loader: "html-loader" 36 | } 37 | ], 38 | exclude: /node_modules/ 39 | }, 40 | { 41 | test: /\.css$/, 42 | use: [MiniCssExtractPlugin.loader, "css-loader"] 43 | } 44 | ] 45 | }, 46 | plugins: [ 47 | new CleanWebpackPlugin(), 48 | new CopyWebpackPlugin([ 49 | { 50 | from: 'web/js/data', 51 | to: "js/data" }, 52 | { 53 | from: "web/js/images", 54 | to: "js/images" 55 | } 56 | ]), 57 | new webpack.NormalModuleReplacementPlugin(/^dojo\/text!/, function(data) { 58 | data.request = data.request.replace(/^dojo\/text!/, "!!raw-loader!"); 59 | }), 60 | new ArcGISPlugin(), 61 | 62 | new HtmlWebPackPlugin({ 63 | title: "Build Project", 64 | template: "./web/index.html", 65 | filename: "./index.html", 66 | chunksSortMode: "none", 67 | inlineSource: ".(css)$" 68 | }), 69 | 70 | new MiniCssExtractPlugin({ 71 | filename: "[name].[chunkhash].css", 72 | chunkFilename: "[id].css" 73 | }) 74 | ], 75 | resolve: { 76 | modules: [ 77 | path.resolve(__dirname, "/web"), 78 | path.resolve(__dirname, "node_modules/") 79 | ], 80 | extensions: [ ".js", ".css"] 81 | }, 82 | node: { 83 | process: false, 84 | global: false, 85 | fs: "empty" 86 | } 87 | }; --------------------------------------------------------------------------------