├── .github
├── FUNDING.yml
└── workflows
│ ├── build.yml
│ └── npmpublish.yml
├── doc
└── images
│ └── Example.png
├── .vscode
└── settings.json
├── postVersion.js
├── .eslintrc
├── LICENSE
├── package.json
├── .gitignore
├── src
└── QualtricsGoogleMap.ts
├── tsconfig.json
└── README.md
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: pkmnct
2 | patreon: pkmnct
3 | custom: ['https://paypal.me/pkmnct']
4 |
--------------------------------------------------------------------------------
/doc/images/Example.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pkmnct/qualtrics-google-map-lat-long/HEAD/doc/images/Example.png
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "editor.formatOnSave": true,
3 | "eslint.format.enable": true,
4 | "editor.codeActionsOnSave": {
5 | "source.fixAll.eslint": true
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: Build and Lint
2 |
3 | on: [push, pull_request]
4 |
5 | jobs:
6 | build:
7 | runs-on: ubuntu-latest
8 |
9 | strategy:
10 | matrix:
11 | # the Node.js versions to build on
12 | node-version: [13.x]
13 |
14 | steps:
15 | - uses: actions/checkout@v2
16 |
17 | - name: Use Node.js ${{ matrix.node-version }}
18 | uses: actions/setup-node@v1
19 | with:
20 | node-version: ${{ matrix.node-version }}
21 |
22 | - name: Install dependencies
23 | run: npm install
24 |
25 | - name: Lint the project
26 | run: npm run lint
27 |
28 | - name: Build the project
29 | run: npm run build
30 | env:
31 | CI: true
32 |
--------------------------------------------------------------------------------
/postVersion.js:
--------------------------------------------------------------------------------
1 | // This script ensures that the Readme includes the up-to-date script src
2 |
3 | /* eslint-disable */
4 | const fs = require("fs");
5 |
6 | const filename = "README.md";
7 | const version = process.argv[2];
8 | const regex = /qualtrics-google-map-lat-long@(.*)\/dist/g;
9 | const replace = `qualtrics-google-map-lat-long@${version}/dist`;
10 |
11 | if (process.argv.length !== 3) {
12 | return console.error("Invalid Arguments");
13 | }
14 |
15 | fs.readFile(filename, "utf8", (err, data) => {
16 | if (err) {
17 | return console.error(err);
18 | }
19 | const result = data.replace(regex, replace);
20 |
21 | fs.writeFile(filename, result, "utf8", (err) => {
22 | if (err) {
23 | return console.error(err);
24 | }
25 | });
26 | });
27 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "parser": "@typescript-eslint/parser",
3 | "extends": [
4 | "eslint:recommended",
5 | "plugin:@typescript-eslint/eslint-recommended",
6 | "plugin:@typescript-eslint/recommended"
7 | ],
8 | "parserOptions": {
9 | "ecmaVersion": 2018,
10 | "sourceType": "module"
11 | },
12 | "ignorePatterns": [
13 | "dist"
14 | ],
15 | "rules": {
16 | "quotes": ["warn", "single"],
17 | "indent": ["warn", 2, { "SwitchCase": 1 }],
18 | "linebreak-style": ["warn", "unix"],
19 | "semi": ["warn", "always"],
20 | "comma-dangle": ["warn", "always-multiline"],
21 | "eqeqeq": "warn",
22 | "curly": ["warn", "all"],
23 | "brace-style": ["warn"],
24 | "prefer-arrow-callback": ["warn"],
25 | "lines-between-class-members": ["warn", "always", {"exceptAfterSingleLine": true}],
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/.github/workflows/npmpublish.yml:
--------------------------------------------------------------------------------
1 | # This workflow will run tests using node and then publish a package to GitHub Packages when a release is created
2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/publishing-nodejs-packages
3 |
4 | name: Node.js Package
5 |
6 | on:
7 | release:
8 | types: [created]
9 |
10 | jobs:
11 | build:
12 | runs-on: ubuntu-latest
13 | steps:
14 | - uses: actions/checkout@v2
15 | - uses: actions/setup-node@v1
16 | with:
17 | node-version: 12
18 | - run: npm ci
19 | - run: npm test
20 |
21 | publish-npm:
22 | needs: build
23 | runs-on: ubuntu-latest
24 | steps:
25 | - uses: actions/checkout@v2
26 | - uses: actions/setup-node@v1
27 | with:
28 | node-version: 12
29 | registry-url: https://registry.npmjs.org/
30 | - run: npm ci
31 | - run: npm publish
32 | env:
33 | NODE_AUTH_TOKEN: ${{secrets.npm_token}}
34 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 George W. Walker
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "qualtrics-google-map-lat-long",
3 | "version": "2.0.1",
4 | "description": "Embed a customizable Google Map in your Qualtrics survey to collect location data",
5 | "scripts": {
6 | "lint": "eslint src/**.ts",
7 | "lint:fix": "eslint src/**.ts --fix",
8 | "build": "rimraf ./dist && tsc && npm run uglify",
9 | "uglify": "uglifyjs ./dist/QualtricsGoogleMap.js -o ./dist/QualtricsGoogleMap.min.js --compress --mangle --source-map \"content='./dist/QualtricsGoogleMap.js.map',url='QualtricsGoogleMap.min.js.map'\"",
10 | "prepublishOnly": "npm run lint && npm run postversion && npm run build",
11 | "postversion": "cross-var node postVersion.js $npm_package_version"
12 | },
13 | "repository": {
14 | "type": "git",
15 | "url": "git+https://github.com/pkmnct/qualtrics-google-map-lat-long.git"
16 | },
17 | "keywords": [
18 | "Qualtrics",
19 | "Google Maps",
20 | "Maps",
21 | "Survey",
22 | "Lat",
23 | "Long",
24 | "Latitude",
25 | "Longitude",
26 | "Map"
27 | ],
28 | "author": "George W. Walker",
29 | "license": "MIT",
30 | "bugs": {
31 | "url": "https://github.com/pkmnct/qualtrics-google-map-lat-long/issues"
32 | },
33 | "homepage": "https://github.com/pkmnct/qualtrics-google-map-lat-long#readme",
34 | "devDependencies": {
35 | "@types/googlemaps": "^3.39.11",
36 | "@typescript-eslint/eslint-plugin": "^3.7.0",
37 | "@typescript-eslint/parser": "^3.7.0",
38 | "cross-var": "^1.1.0",
39 | "eslint": "^7.5.0",
40 | "rimraf": "^3.0.2",
41 | "typescript": "^3.9.7",
42 | "uglify-js": "^3.10.0"
43 | },
44 | "files": [
45 | "dist/*",
46 | "src/*"
47 | ]
48 | }
49 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Ignore macOS files
2 | .DS_Store
3 |
4 | # Ignore compiled code
5 | dist
6 |
7 | # ------------- Defaults ------------- #
8 |
9 | # Logs
10 | logs
11 | *.log
12 | npm-debug.log*
13 | yarn-debug.log*
14 | yarn-error.log*
15 | lerna-debug.log*
16 |
17 | # Diagnostic reports (https://nodejs.org/api/report.html)
18 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
19 |
20 | # Runtime data
21 | pids
22 | *.pid
23 | *.seed
24 | *.pid.lock
25 |
26 | # Directory for instrumented libs generated by jscoverage/JSCover
27 | lib-cov
28 |
29 | # Coverage directory used by tools like istanbul
30 | coverage
31 | *.lcov
32 |
33 | # nyc test coverage
34 | .nyc_output
35 |
36 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
37 | .grunt
38 |
39 | # Bower dependency directory (https://bower.io/)
40 | bower_components
41 |
42 | # node-waf configuration
43 | .lock-wscript
44 |
45 | # Compiled binary addons (https://nodejs.org/api/addons.html)
46 | build/Release
47 |
48 | # Dependency directories
49 | node_modules/
50 | jspm_packages/
51 |
52 | # Snowpack dependency directory (https://snowpack.dev/)
53 | web_modules/
54 |
55 | # TypeScript cache
56 | *.tsbuildinfo
57 |
58 | # Optional npm cache directory
59 | .npm
60 |
61 | # Optional eslint cache
62 | .eslintcache
63 |
64 | # Microbundle cache
65 | .rpt2_cache/
66 | .rts2_cache_cjs/
67 | .rts2_cache_es/
68 | .rts2_cache_umd/
69 |
70 | # Optional REPL history
71 | .node_repl_history
72 |
73 | # Output of 'npm pack'
74 | *.tgz
75 |
76 | # Yarn Integrity file
77 | .yarn-integrity
78 |
79 | # dotenv environment variables file
80 | .env
81 | .env.test
82 |
83 | # parcel-bundler cache (https://parceljs.org/)
84 | .cache
85 | .parcel-cache
86 |
87 | # Next.js build output
88 | .next
89 |
90 | # Nuxt.js build / generate output
91 | .nuxt
92 | dist
93 |
94 | # Gatsby files
95 | .cache/
96 | # Comment in the public line in if your project uses Gatsby and not Next.js
97 | # https://nextjs.org/blog/next-9-1#public-directory-support
98 | # public
99 |
100 | # vuepress build output
101 | .vuepress/dist
102 |
103 | # Serverless directories
104 | .serverless/
105 |
106 | # FuseBox cache
107 | .fusebox/
108 |
109 | # DynamoDB Local files
110 | .dynamodb/
111 |
112 | # TernJS port file
113 | .tern-port
114 |
115 | # Stores VSCode versions used for testing VSCode extensions
116 | .vscode-test
117 |
118 | # yarn v2
119 |
120 | .yarn/cache
121 | .yarn/unplugged
122 | .yarn/build-state.yml
123 | .pnp.*
--------------------------------------------------------------------------------
/src/QualtricsGoogleMap.ts:
--------------------------------------------------------------------------------
1 | interface Map {
2 | css?: string;
3 | options: google.maps.MapOptions;
4 | markers?: {
5 | autocomplete?: {
6 | enabled: boolean;
7 | label: string;
8 | css?: string;
9 | labelCss?: string;
10 | invalidLocationAlertText: string;
11 | },
12 | options: google.maps.MarkerOptions;
13 | }[];
14 | }
15 |
16 | interface Question {
17 | id: string;
18 | container: Element;
19 | map: Map;
20 | }
21 |
22 | const initGoogleMapsQuestion = (
23 | id: Question['id'],
24 | container: Question['container'],
25 | map: Question['map'],
26 | ): void | Error => {
27 | // Find the dataBox and hide it
28 | const dataBox = document.getElementById(`QR~${id}`) as HTMLInputElement | null;
29 | if (!dataBox) {
30 | return new Error(`Could not find input for question with id ${id}.`);
31 | }
32 | dataBox.style.display = 'none';
33 |
34 | // Find the QuestionBody to append to
35 | const questionBody = container.querySelector('.QuestionBody') || container;
36 |
37 | // Initialize data storage or load from existing data in field
38 | const value: { [key: number]: google.maps.LatLng } = dataBox.value !== '' ? JSON.parse(dataBox.value) : {};
39 |
40 | // Function to set the dataBox to a lat/lng
41 | const setLatLng = (key: number, latLng: google.maps.LatLng) => {
42 | value[key] = latLng;
43 | dataBox.value = JSON.stringify(value);
44 | };
45 |
46 | const styles = document.createElement('style');
47 | document.head.appendChild(styles);
48 |
49 | // Create the map node
50 | const mapObject = document.createElement('div');
51 | mapObject.setAttribute('id', `${id}-map`);
52 | if (map.css) {
53 | styles.innerText += `#${id}-map {${map.css}}`;
54 | mapObject.setAttribute('style', map.css);
55 | } else {
56 | styles.innerText += `#${id}-map {height: 300px;}`;
57 | }
58 | questionBody.appendChild(mapObject);
59 |
60 | // Initialize the Google Map
61 | const googleMap = new google.maps.Map(mapObject, map.options);
62 |
63 | // Initialize the Markers
64 | map.markers?.forEach((marker, index) => {
65 | // Create the marker
66 | const mapMarker = new google.maps.Marker({
67 | ...marker.options,
68 | map: googleMap,
69 | position: index in value ? value[index] : marker.options.position || map.options.center,
70 | });
71 |
72 | if (marker.autocomplete?.enabled) {
73 | const inputId = `${id}-${index}-locationInput`;
74 |
75 | // Make the label for the autocomplete
76 | const locationLabel = document.createElement('label');
77 | locationLabel.setAttribute('for', inputId);
78 | locationLabel.setAttribute('id', `${inputId}-label`);
79 | locationLabel.setAttribute('class', 'QuestionText');
80 | if (marker.autocomplete.labelCss) {
81 | styles.innerText += `#${inputId}-label {${marker.autocomplete.labelCss}}`;
82 | }
83 | locationLabel.innerText = marker.autocomplete.label || marker.options.title || `Marker ${marker.options.label ? marker.options.label : index}`;
84 | questionBody.appendChild(locationLabel);
85 |
86 | // Make the autocomplete
87 | const locationInput = document.createElement('input');
88 | locationInput.setAttribute('id', inputId);
89 | locationInput.setAttribute('class', 'InputText');
90 | if (marker.autocomplete.css) {
91 | styles.innerText += `#${id}-${index}-locationInput {${marker.autocomplete.css}}`;
92 | }
93 | questionBody.appendChild(locationInput);
94 |
95 | // Load the places API
96 | const locationAutocomplete = new google.maps.places.Autocomplete(locationInput);
97 |
98 | // Whenever the inputs change, set the locationLatLong and pan the map to the location
99 | google.maps.event.addListener(locationAutocomplete, 'place_changed', () => {
100 | const place = locationAutocomplete.getPlace();
101 |
102 | if (place.geometry) {
103 | mapMarker.setPosition(place.geometry.location);
104 | googleMap.panTo(place.geometry.location);
105 | setLatLng(index, place.geometry.location);
106 | } else {
107 | alert(marker.autocomplete?.invalidLocationAlertText || 'Invalid Location');
108 | }
109 | });
110 | }
111 |
112 | // If there is only one marker, allow setting its position by clicking the map
113 | const draggableMarkerCount = map.markers?.filter(marker => marker.options.draggable).length;
114 | if (draggableMarkerCount === 1) {
115 | // When the map is clicked, move the marker and update stored position
116 | google.maps.event.addListener(googleMap, 'click', event => {
117 | setLatLng(index, event.latLng);
118 | mapMarker.setPosition(event.latLng);
119 | });
120 | }
121 |
122 | // When the marker is dragged, store the lat/lng where it ends
123 | google.maps.event.addListener(mapMarker, 'dragend', event => {
124 | setLatLng(index, event.latLng);
125 | });
126 | });
127 | };
128 |
129 | // Typescript doesn't allow augmentation of the global scope except in modules, but we need to expose this to the global scope
130 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment
131 | // @ts-ignore
132 | window.initGoogleMapsQuestion = initGoogleMapsQuestion;
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | /* Visit https://aka.ms/tsconfig.json to read more about this file */
4 |
5 | /* Basic Options */
6 | // "incremental": true, /* Enable incremental compilation */
7 | "target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */
8 | "module": "none", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
9 | // "lib": [], /* Specify library files to be included in the compilation. */
10 | // "allowJs": true, /* Allow javascript files to be compiled. */
11 | // "checkJs": true, /* Report errors in .js files. */
12 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
13 | // "declaration": true, /* Generates corresponding '.d.ts' file. */
14 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
15 | "sourceMap": true, /* Generates corresponding '.map' file. */
16 | // "outFile": "./", /* Concatenate and emit output to single file. */
17 | "outDir": "./dist", /* Redirect output structure to the directory. */
18 | "rootDir": "./src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
19 | // "composite": true, /* Enable project compilation */
20 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
21 | // "removeComments": true, /* Do not emit comments to output. */
22 | // "noEmit": true, /* Do not emit outputs. */
23 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */
24 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
25 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
26 |
27 | /* Strict Type-Checking Options */
28 | "strict": true, /* Enable all strict type-checking options. */
29 | "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
30 | // "strictNullChecks": true, /* Enable strict null checks. */
31 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */
32 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
33 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
34 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
35 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
36 |
37 | /* Additional Checks */
38 | // "noUnusedLocals": true, /* Report errors on unused locals. */
39 | // "noUnusedParameters": true, /* Report errors on unused parameters. */
40 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
41 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
42 |
43 | /* Module Resolution Options */
44 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
45 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
46 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
47 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
48 | // "typeRoots": [], /* List of folders to include type definitions from. */
49 | // "types": [], /* Type declaration files to be included in compilation. */
50 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
51 | "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
52 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
53 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
54 |
55 | /* Source Map Options */
56 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
57 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
58 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
59 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
60 |
61 | /* Experimental Options */
62 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
63 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
64 |
65 | /* Advanced Options */
66 | "skipLibCheck": true, /* Skip type checking of declaration files. */
67 | "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Qualtrics Google Map
2 |
3 | Embed a customizable Google Map in your Qualtrics survey to collect location data
4 |
5 | 
6 |
7 | ## Prerequisites
8 |
9 | ### Qualtrics
10 |
11 | Unfortunately Qualtrics does not allow adding JavaScript to questions on free accounts. In order to use Google Maps in your question, you must have a full account. See _[Trial Accounts](https://www.qualtrics.com/support/survey-platform/managing-your-account/trial-accounts/)_ on Qualtrics' support site for more information.
12 |
13 | ### Google Maps
14 |
15 | You must have a valid Google Maps JavaScript API key. If you want to use the autocomplete functionality, the API key must have access to the Places API as well. See _[Get Maps JavaScript API Key](https://developers.google.com/maps/documentation/javascript/get-api-key)_ and _[Get Places API Key](https://developers.google.com/places/web-service/get-api-key)_ on Google's developer documentation.
16 |
17 | ## Getting Started
18 |
19 | ### Header Script
20 |
21 | The first step is to add the Google Maps API and this script to your survey's header. See _[Adding a Survey Header/Footer](https://www.qualtrics.com/support/survey-platform/survey-module/look-feel/general-look-feel-settings/#AddFooterHeader)_ on Qualtrics' support site. When you get to the Rich Text Editor, click the
icon in the toolbar to display HTML. Paste the following at the top of the header:
22 |
23 | ```html
24 |
25 |
26 | ```
27 |
28 | Make sure to replace the `{YOURKEYHERE}` with your Google Maps API key.
29 |
30 | ### Adding a Map Question
31 |
32 | Once you have the Header Script added, you can create map questions. Start by making a new _Text Entry_ question. Ensure that the text type used is single line. You can treat this question like you would any other (ex. require a response, change the title, etc.). It is recommended that you provide clear instructions to the responder in the question.
33 |
34 | On the left side of the question, click the gear and select _Add JavaScript..._
35 |
36 | Copy the code from the example below and modify the options to adjust how the map will render. Paste this below the `/*Place your JavaScript here to run when the page loads*/` text in the _addOnload_ section of the _Edit Question JavaScript_ dialog.
37 |
38 | See _[Add JavaScript](https://www.qualtrics.com/support/survey-platform/survey-module/question-options/add-javascript/)_ on Qualtrics' support site for more information.
39 |
40 | #### Option Documentation
41 |
42 | - Map Options are documented on Google's developer documentation. [Center and zoom options are required](https://developers.google.com/maps/documentation/javascript/overview#MapOptions), and there are [many other options you can configure](https://developers.google.com/maps/documentation/javascript/reference/map#MapOptions).
43 | - [Marker Options are documented on Google's developer documentation](https://developers.google.com/maps/documentation/javascript/reference/marker#MarkerOptions)
44 | - Marker Autocomplete Options
45 | - You can enable an autocomplete field to assist responders in finding a location. Responders can enter a location into this field and the map will snap its marker to that location. They can then fine-tune the response by dragging the marker to a specific location (such as a door to a building).
46 | - See the example markers below
47 | - If there is only one marker, you can click on the map to set its location.
48 |
49 | #### Example
50 |
51 | ```js
52 | initGoogleMapsQuestion(this.questionId, this.getQuestionContainer(), {
53 | // Map Options, set these! See Map Options in Option Documentation Section
54 | options: {
55 | center: {
56 | lat: 39.1836,
57 | lng: -96.5717,
58 | },
59 | zoom: 16,
60 | },
61 | // Marker Options, set these!
62 | markers: [
63 | // First Marker
64 | {
65 | // See Marker Options in Option Documentation Section
66 | options: {
67 | title: "Marker 1",
68 | draggable: true,
69 | label: "1",
70 | },
71 | autocomplete: {
72 | // If true, an autocomplete will show.
73 | enabled: true,
74 | // The label shown for the autocomplete field
75 | label: "Location for Marker 1",
76 | // Styles for the label
77 | labelCss: "padding-left: 0; padding-right: 0;",
78 | // Text to show if an invalid location is selected
79 | invalidLocationAlertText:
80 | "Please choose a location from the search dropdown. If your location doesn't appear in the search, enter a nearby location and move the marker to the correct location.",
81 | },
82 | },
83 | // Second Marker
84 | {
85 | // See Marker Options in Option Documentation Section
86 | options: {
87 | title: "This is an example second marker. Rename or delete me.",
88 | draggable: true,
89 | position: {
90 | lat: 39.184,
91 | lng: -96.572,
92 | },
93 | label: "2",
94 | },
95 | autocomplete: {
96 | // If true, an autocomplete will show.
97 | enabled: true,
98 | // The label shown for the autocomplete field
99 | label: "Location for Marker 2",
100 | // Styles for the label
101 | labelCss: "padding-left: 0; padding-right: 0;",
102 | // Text to show if an invalid location is selected
103 | invalidLocationAlertText:
104 | "Please choose a location from the search dropdown. If your location doesn't appear in the search, enter a nearby location and move the marker to the correct location.",
105 | },
106 | },
107 | // You can add more markers as well
108 | ],
109 | });
110 | ```
111 |
112 | Click Save. Test the question before sending out the survey. If you have any issues, see _Troubleshooting_ below.
113 |
114 | ## Data Collected
115 |
116 | The result is collected in the Qualtrics question field as a string representation of a JSON object. Each marker is represented by its index as the key in the object and the latitude and longitude as the value.
117 |
118 | ### Example Single-Marker Question Data
119 |
120 | `{0:{"lat":38.8951,"long":-77.0364}}`
121 |
122 | ### Example Multi-Marker Question Data
123 |
124 | `{0:{"lat":38.8951,"long":-77.0364},1:{"lat":38.8951,"long":-77.0364},2:{"lat":38.8951,"long":-77.0364}}`
125 |
126 | ## Troubleshooting
127 |
128 | ### The map doesn't show after adding it to the question
129 |
130 | The map will only show up in the actual survey, not in the back-end of Qualtrics. Try to preview or take the survey.
131 |
132 | ### The map or autocomplete field search shows "_This page can't load Google Maps correctly_" or "_For development purposes only_"
133 |
134 | This usually indicates an issue with your API key. Make sure you set the API key in the Google Maps script placed in the header. Check that the API key has access to both the Maps JavaScript API, and if you are using the Autocomplete Field, the Places API. If you are still having trouble, follow [Google's API key troubleshooting steps](https://developers.google.com/maps/documentation/javascript/error-messages).
135 |
136 | ### Responses are not saving
137 |
138 | Ensure that the text type used on your form is single line. [See Issue #6](https://github.com/pkmnct/qualtrics-google-map-lat-long/issues/6).
139 |
140 | ### I'm still having problems
141 |
142 | Make sure you are using the latest version of the code. If that doesn't help, see if an [issue](https://github.com/pkmnct/qualtrics-google-map-lat-long/issues) has been created for the problem you are facing already. If not, you can [create a new issue](https://github.com/pkmnct/qualtrics-google-map-lat-long/issues).
143 |
144 | ## Migrating from 1.x
145 |
146 | If you have used older versions of this script in your survey, you have a few options to migrate. Version 2.0 changes the way the data is stored in the text field to support multiple map markers. If you have started collecting survey responses, it may be beneficial for all of the data collected to be in the same format. If that is the case, follow the _I have already collected survey responses_ section below. If you do not mind the survey results mixing data types, or have not started collecting data, see the _I have not started collecting survey responses_ section below.
147 |
148 | ### I have already collected survey responses
149 |
150 | In this case, it is recommended that you continue using [the older version of the script](https://github.com/pkmnct/qualtrics-google-map-lat-long/blob/4e9ab1288e6a030431b0e9eab6db56ba5b5062a2/README.md). This will ensure that all of the data collected is in the same format. If you do not mind the survey results mixing data types, see the _I have not started collecting survey responses_ section below.
151 |
152 | ### I have not started collecting survey responses
153 |
154 | In this case, it is recommended that you update all older questions to use the new script. You can also mix 1.x and 2.x questions, but you must remove the code that loads the Google Maps API in all 1.x questions. This is the last code block pasted, below the _Load the Google Maps API if it is not already loaded_ comment. If you do not want to update questions, you can still use [the older version of the script](https://github.com/pkmnct/qualtrics-google-map-lat-long/blob/4e9ab1288e6a030431b0e9eab6db56ba5b5062a2/README.md).
155 |
--------------------------------------------------------------------------------