├── .gitignore ├── examples ├── medicare.png ├── medicare-shadow.png ├── panel.js ├── places.html ├── gme.js ├── gme.html ├── panel.html ├── dynamic.html ├── custom.js ├── custom.html ├── places.js ├── medicare-static-ds.js ├── medicare-dynamic-ds.js └── infobubble-compiled.js ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── AUTHORS ├── CONTRIBUTORS ├── CONTRIBUTING.md ├── js ├── common.js ├── feature.js ├── static-datasource.js ├── featureset.js ├── gme-datasource.js ├── store.js ├── view.js └── panel.js ├── css └── storelocator.css ├── gradlew.bat ├── exports.js ├── README.md ├── gradlew ├── LICENSE ├── dist └── store-locator.min.js └── reference.html /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | build 3 | -------------------------------------------------------------------------------- /examples/medicare.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timbertinker/js-store-locator/HEAD/examples/medicare.png -------------------------------------------------------------------------------- /examples/medicare-shadow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timbertinker/js-store-locator/HEAD/examples/medicare-shadow.png -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timbertinker/js-store-locator/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Tue Nov 04 15:07:27 EST 2014 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=http\://services.gradle.org/distributions/gradle-2.1-bin.zip 7 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | # This is the official list of js-store-locator authors for copyright purposes. 2 | # This file is distinct from the CONTRIBUTORS files. 3 | # See the latter for an explanation. 4 | 5 | # Names should be added to this file as: 6 | # Name or Organization 7 | # The email address is not required for organizations. 8 | 9 | Google Inc. 10 | -------------------------------------------------------------------------------- /CONTRIBUTORS: -------------------------------------------------------------------------------- 1 | # People who have agreed to one of the CLAs and can contribute patches. 2 | # The AUTHORS file lists the copyright holders; this file 3 | # lists people. For example, Google employees are listed here 4 | # but not in AUTHORS, because Google holds the copyright. 5 | # 6 | # https://developers.google.com/open-source/cla/individual 7 | # https://developers.google.com/open-source/cla/corporate 8 | # 9 | # Names should be added to this file as: 10 | # Name 11 | 12 | Chris Broadfoot 13 | Brendan Kenny 14 | -------------------------------------------------------------------------------- /examples/panel.js: -------------------------------------------------------------------------------- 1 | google.maps.event.addDomListener(window, 'load', function() { 2 | var map = new google.maps.Map(document.getElementById('map-canvas'), { 3 | center: new google.maps.LatLng(-28, 135), 4 | zoom: 4, 5 | mapTypeId: google.maps.MapTypeId.ROADMAP 6 | }); 7 | 8 | var panelDiv = document.getElementById('panel'); 9 | 10 | var data = new MedicareDataSource; 11 | 12 | var view = new storeLocator.View(map, data, { 13 | geolocation: false, 14 | features: data.getFeatures() 15 | }); 16 | 17 | new storeLocator.Panel(panelDiv, { 18 | view: view 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Contributing 2 | 3 | Want to help out? That's awesome! 4 | ![Analytics](https://ga-beacon.appspot.com/UA-12846745-20/js-store-locator/contributing?pixel) 5 | 6 | The library is open source and lives on GitHub at: 7 | https://github.com/googlemaps/js-store-locator 8 | Open an issue or fork the library and submit a pull request. 9 | 10 | Keep in mind that before we can accept any pull requests we have to jump 11 | through a couple of legal hurdles, primarily a Contributor License Agreement 12 | (CLA): 13 | 14 | - **If you are an individual writing original source code** 15 | and you're sure you own the intellectual property, 16 | then you'll need to sign an 17 | [individual CLA](https://developers.google.com/open-source/cla/individual). 18 | - **If you work for a company that wants to allow you to contribute your work**, 19 | then you'll need to sign a 20 | [corporate CLA](https://developers.google.com/open-source/cla/corporate) 21 | 22 | Follow either of the two links above to access the appropriate CLA and 23 | instructions for how to sign and return it. 24 | 25 | When preparing your code, make sure to update the AUTHORS and CONTRIBUTORS files 26 | to reflect your contribtion. 27 | 28 | Once we receive your CLA, we'll be able to review and accept your pull requests. 29 | -------------------------------------------------------------------------------- /js/common.js: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Google Inc. 2 | 3 | /** 4 | * @name Store Locator for Google Maps API V3 5 | * @version 0.1 6 | * @author Chris Broadfoot (Google) 7 | * @fileoverview 8 | * This library makes it easy to create a fully-featured Store Locator for 9 | * your business's website. 10 | */ 11 | 12 | /** 13 | * @license 14 | * 15 | * Copyright 2012 Google Inc. 16 | * 17 | * Licensed under the Apache License, Version 2.0 (the "License"); 18 | * you may not use this file except in compliance with the License. 19 | * You may obtain a copy of the License at 20 | * 21 | * http://www.apache.org/licenses/LICENSE-2.0 22 | * 23 | * Unless required by applicable law or agreed to in writing, software 24 | * distributed under the License is distributed on an "AS IS" BASIS, 25 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 26 | * See the License for the specific language governing permissions and 27 | * limitations under the License. 28 | */ 29 | 30 | /** 31 | * Namespace for Store Locator. 32 | * @constructor 33 | */ 34 | var storeLocator = function() {}; 35 | window['storeLocator'] = storeLocator; 36 | 37 | /** 38 | * Convert from degrees to radians. 39 | * @private 40 | * @param {number} degrees the number in degrees. 41 | * @return {number} the number in radians. 42 | */ 43 | storeLocator.toRad_ = function(degrees) { 44 | return degrees * Math.PI / 180; 45 | }; 46 | -------------------------------------------------------------------------------- /examples/places.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Store locator with Panel 6 | 8 | 11 | 12 | 13 | 14 | 21 | 32 | 33 | 34 |

Places API

35 |
36 |
37 | 38 | 39 | -------------------------------------------------------------------------------- /examples/gme.js: -------------------------------------------------------------------------------- 1 | google.maps.event.addDomListener(window, 'load', function() { 2 | var map = new google.maps.Map(document.getElementById('map-canvas'), { 3 | center: new google.maps.LatLng(-28, 135), 4 | zoom: 4, 5 | mapTypeId: google.maps.MapTypeId.ROADMAP 6 | }); 7 | 8 | var panelDiv = document.getElementById('panel'); 9 | 10 | var data = new storeLocator.GMEDataFeed({ 11 | tableId: '12421761926155747447-06672618218968397709', 12 | apiKey: 'AIzaSyAtunhRg0VTElV-P7n4Agpm9tYlABQDCAM', 13 | propertiesModifier: function(props) { 14 | var shop = join([props.Shp_num_an, props.Shp_centre], ', '); 15 | var locality = join([props.Locality, props.Postcode], ', '); 16 | 17 | return { 18 | id: props.uuid, 19 | title: props.Fcilty_nam, 20 | address: join([shop, props.Street_add, locality], '
'), 21 | hours: props.Hrs_of_bus 22 | }; 23 | } 24 | }); 25 | 26 | var view = new storeLocator.View(map, data, { 27 | geolocation: false 28 | }); 29 | 30 | new storeLocator.Panel(panelDiv, { 31 | view: view 32 | }); 33 | }); 34 | 35 | /** 36 | * Joins elements of an array that are non-empty and non-null. 37 | * @private 38 | * @param {!Array} arr array of elements to join. 39 | * @param {string} sep the separator. 40 | * @return {string} 41 | */ 42 | function join(arr, sep) { 43 | var parts = []; 44 | for (var i = 0, ii = arr.length; i < ii; i++) { 45 | arr[i] && parts.push(arr[i]); 46 | } 47 | return parts.join(sep); 48 | } 49 | -------------------------------------------------------------------------------- /examples/gme.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Store locator with Panel 6 | 8 | 11 | 12 | 13 | 14 | 20 | 31 | 32 | 33 |

Medicare offices

34 |
35 |
36 |

Medicare location data from data.gov.au, licensed under CC-BY 2.5

37 | 38 | 39 | -------------------------------------------------------------------------------- /examples/panel.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Store locator with Panel 6 | 8 | 11 | 12 | 13 | 14 | 15 | 22 | 33 | 34 | 35 |

Medicare offices

36 |
37 |
38 |

Medicare location data from data.gov.au, licensed under CC-BY 2.5

39 | 40 | 41 | -------------------------------------------------------------------------------- /examples/dynamic.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Store locator with Panel 6 | 8 | 11 | 12 | 13 | 14 | 15 | 22 | 33 | 34 | 35 |

Medicare offices

36 |
37 |
38 |

Medicare location data from data.gov.au, licensed under CC-BY 2.5

39 | 40 | 41 | -------------------------------------------------------------------------------- /examples/custom.js: -------------------------------------------------------------------------------- 1 | // Store locator with customisations 2 | // - custom marker 3 | // - custom info window (using Info Bubble) 4 | // - custom info window content (+ store hours) 5 | 6 | var ICON = new google.maps.MarkerImage('medicare.png', null, null, 7 | new google.maps.Point(14, 13)); 8 | 9 | var SHADOW = new google.maps.MarkerImage('medicare-shadow.png', null, null, 10 | new google.maps.Point(14, 13)); 11 | 12 | google.maps.event.addDomListener(window, 'load', function() { 13 | var map = new google.maps.Map(document.getElementById('map-canvas'), { 14 | center: new google.maps.LatLng(-28, 135), 15 | zoom: 4, 16 | mapTypeId: google.maps.MapTypeId.ROADMAP 17 | }); 18 | 19 | var panelDiv = document.getElementById('panel'); 20 | 21 | var data = new MedicareDataSource; 22 | 23 | var view = new storeLocator.View(map, data, { 24 | geolocation: false, 25 | features: data.getFeatures() 26 | }); 27 | 28 | view.createMarker = function(store) { 29 | var markerOptions = { 30 | position: store.getLocation(), 31 | icon: ICON, 32 | shadow: SHADOW, 33 | title: store.getDetails().title 34 | }; 35 | return new google.maps.Marker(markerOptions); 36 | } 37 | 38 | var infoBubble = new InfoBubble; 39 | view.getInfoWindow = function(store) { 40 | if (!store) { 41 | return infoBubble; 42 | } 43 | 44 | var details = store.getDetails(); 45 | 46 | var html = ['
', details.title, 47 | '
', details.address, '
', 48 | '
', details.hours, '
'].join(''); 49 | 50 | infoBubble.setContent($(html)[0]); 51 | return infoBubble; 52 | }; 53 | 54 | new storeLocator.Panel(panelDiv, { 55 | view: view 56 | }); 57 | }); 58 | -------------------------------------------------------------------------------- /js/feature.js: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Google Inc. 2 | 3 | /** 4 | * @author Chris Broadfoot (Google) 5 | * @fileoverview 6 | * Feature model class for Store Locator library. 7 | */ 8 | 9 | /** 10 | * Licensed under the Apache License, Version 2.0 (the "License"); 11 | * you may not use this file except in compliance with the License. 12 | * You may obtain a copy of the License at 13 | * 14 | * http://www.apache.org/licenses/LICENSE-2.0 15 | * 16 | * Unless required by applicable law or agreed to in writing, software 17 | * distributed under the License is distributed on an "AS IS" BASIS, 18 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 | * See the License for the specific language governing permissions and 20 | * limitations under the License. 21 | */ 22 | 23 | /** 24 | * Representation of a feature of a store. (e.g. 24 hours, BYO, etc). 25 | * @example
26 |  * var feature = new storeLocator.Feature('24hour', 'Open 24 Hours');
27 |  * 
28 | * @param {string} id unique identifier for this feature. 29 | * @param {string} name display name of this feature. 30 | * @constructor 31 | * @implements storeLocator_Feature 32 | */ 33 | storeLocator.Feature = function(id, name) { 34 | this.id_ = id; 35 | this.name_ = name; 36 | }; 37 | storeLocator['Feature'] = storeLocator.Feature; 38 | 39 | /** 40 | * Gets this Feature's ID. 41 | * @return {string} this feature's ID. 42 | */ 43 | storeLocator.Feature.prototype.getId = function() { 44 | return this.id_; 45 | }; 46 | 47 | /** 48 | * Gets this Feature's display name. 49 | * @return {string} this feature's display name. 50 | */ 51 | storeLocator.Feature.prototype.getDisplayName = function() { 52 | return this.name_; 53 | }; 54 | 55 | storeLocator.Feature.prototype.toString = function() { 56 | return this.getDisplayName(); 57 | }; 58 | -------------------------------------------------------------------------------- /examples/custom.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Store locator with customisations 6 | 8 | 11 | 12 | 13 | 14 | 15 | 16 | 24 | 35 | 36 | 37 |

Medicare offices

38 |
39 |
40 |

Medicare location data from data.gov.au, licensed under CC-BY 2.5

41 | 42 | 43 | -------------------------------------------------------------------------------- /examples/places.js: -------------------------------------------------------------------------------- 1 | // A simple demo showing how to grab places using the Maps API Places Library. 2 | // Useful extensions may include using "name" filtering or "keyword" search. 3 | 4 | google.maps.event.addDomListener(window, 'load', function() { 5 | var map = new google.maps.Map(document.getElementById('map-canvas'), { 6 | center: new google.maps.LatLng(-28, 135), 7 | zoom: 4, 8 | mapTypeId: google.maps.MapTypeId.ROADMAP 9 | }); 10 | 11 | var panelDiv = document.getElementById('panel'); 12 | 13 | var data = new PlacesDataSource(map); 14 | 15 | var view = new storeLocator.View(map, data); 16 | 17 | var markerSize = new google.maps.Size(24, 24); 18 | view.createMarker = function(store) { 19 | return new google.maps.Marker({ 20 | position: store.getLocation(), 21 | icon: new google.maps.MarkerImage(store.getDetails().icon, null, null, 22 | null, markerSize) 23 | }); 24 | }; 25 | 26 | new storeLocator.Panel(panelDiv, { 27 | view: view 28 | }); 29 | }); 30 | 31 | /** 32 | * Creates a new PlacesDataSource. 33 | * @param {google.maps.Map} map 34 | * @constructor 35 | */ 36 | function PlacesDataSource(map) { 37 | this.service_ = new google.maps.places.PlacesService(map); 38 | } 39 | 40 | /** 41 | * @inheritDoc 42 | */ 43 | PlacesDataSource.prototype.getStores = function(bounds, features, callback) { 44 | this.service_.search({ 45 | bounds: bounds 46 | }, function(results, status) { 47 | var stores = []; 48 | 49 | for (var i = 0, result; result = results[i]; i++) { 50 | var latLng = result.geometry.location; 51 | var store = new storeLocator.Store(result.id, latLng, null, { 52 | title: result.name, 53 | address: result.types.join(', '), 54 | icon: result.icon 55 | }); 56 | stores.push(store); 57 | } 58 | 59 | callback(stores); 60 | }); 61 | }; 62 | -------------------------------------------------------------------------------- /css/storelocator.css: -------------------------------------------------------------------------------- 1 | .storelocator-panel { 2 | border: 1px solid #ccc; 3 | overflow: auto; 4 | } 5 | 6 | .storelocator-panel .store-list { 7 | margin: 0; 8 | padding: 0; 9 | } 10 | 11 | .storelocator-panel .store-list li, 12 | .storelocator-panel .directions-panel { 13 | padding: 5px; 14 | } 15 | .storelocator-panel .directions-panel { 16 | font-size: 0.8em; 17 | } 18 | 19 | .storelocator-panel .store-list li.store:hover { 20 | cursor: pointer; 21 | background: #eee; 22 | } 23 | 24 | .storelocator-panel .store-list li.highlighted, 25 | .storelocator-panel .store-list li.highlighted:hover { 26 | background: #ccf; 27 | } 28 | 29 | .storelocator-panel .directions { 30 | display: none; 31 | } 32 | 33 | .storelocator-panel .location-search { 34 | padding: 5px; 35 | } 36 | 37 | .storelocator-panel .location-search input { 38 | width: 95%; 39 | } 40 | 41 | .storelocator-panel .location-search h4 { 42 | font-size: 0.8em; 43 | margin: 0; 44 | padding: 0; 45 | } 46 | 47 | .storelocator-panel .store-list .no-stores { 48 | color: grey; 49 | } 50 | 51 | .storelocator-panel .store .features { 52 | display: none; 53 | } 54 | 55 | .storelocator-panel .feature-filter { 56 | overflow: hidden; 57 | } 58 | 59 | .storelocator-panel .feature-filter label { 60 | display: block; 61 | font-size: 0.8em; 62 | margin: 0 0.4em; 63 | float: left; 64 | } 65 | 66 | .store .title { 67 | font-weight: bold; 68 | } 69 | 70 | .store .address, 71 | .store .phone, 72 | .store .web, 73 | .store .misc { 74 | font-size: 80%; 75 | margin-top: 0.2em; 76 | margin-bottom: 0.5em; 77 | display: block; 78 | } 79 | 80 | .store .features { 81 | overflow: hidden; 82 | color: grey; 83 | margin: 0; 84 | padding: 0; 85 | font-size: 70%; 86 | max-width: 250px; 87 | } 88 | 89 | .store .features li { 90 | display: inline; 91 | float: left; 92 | padding-right: 0.8em; 93 | } 94 | 95 | .store .web a { 96 | color: green 97 | } 98 | 99 | .store .action { 100 | font-size: 0.8em; 101 | color: green; 102 | margin-right: 0.5em; 103 | } 104 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /examples/medicare-static-ds.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @extends storeLocator.StaticDataFeed 3 | * @constructor 4 | */ 5 | function MedicareDataSource() { 6 | $.extend(this, new storeLocator.StaticDataFeed); 7 | 8 | var that = this; 9 | $.get('medicare.csv', function(data) { 10 | that.setStores(that.parse_(data)); 11 | }); 12 | } 13 | 14 | /** 15 | * @const 16 | * @type {!storeLocator.FeatureSet} 17 | * @private 18 | */ 19 | MedicareDataSource.prototype.FEATURES_ = new storeLocator.FeatureSet( 20 | new storeLocator.Feature('Wheelchair-YES', 'Wheelchair access'), 21 | new storeLocator.Feature('Audio-YES', 'Audio') 22 | ); 23 | 24 | /** 25 | * @return {!storeLocator.FeatureSet} 26 | */ 27 | MedicareDataSource.prototype.getFeatures = function() { 28 | return this.FEATURES_; 29 | }; 30 | 31 | /** 32 | * @private 33 | * @param {string} csv 34 | * @return {!Array.} 35 | */ 36 | MedicareDataSource.prototype.parse_ = function(csv) { 37 | var stores = []; 38 | var rows = csv.split('\n'); 39 | var headings = this.parseRow_(rows[0]); 40 | 41 | for (var i = 1, row; row = rows[i]; i++) { 42 | row = this.toObject_(headings, this.parseRow_(row)); 43 | var features = new storeLocator.FeatureSet; 44 | features.add(this.FEATURES_.getById('Wheelchair-' + row.Wheelchair)); 45 | features.add(this.FEATURES_.getById('Audio-' + row.Audio)); 46 | 47 | var position = new google.maps.LatLng(row.Ycoord, row.Xcoord); 48 | 49 | var shop = this.join_([row.Shp_num_an, row.Shp_centre], ', '); 50 | var locality = this.join_([row.Locality, row.Postcode], ', '); 51 | 52 | var store = new storeLocator.Store(row.uuid, position, features, { 53 | title: row.Fcilty_nam, 54 | address: this.join_([shop, row.Street_add, locality], '
'), 55 | hours: row.Hrs_of_bus 56 | }); 57 | stores.push(store); 58 | } 59 | return stores; 60 | }; 61 | 62 | /** 63 | * Joins elements of an array that are non-empty and non-null. 64 | * @private 65 | * @param {!Array} arr array of elements to join. 66 | * @param {string} sep the separator. 67 | * @return {string} 68 | */ 69 | MedicareDataSource.prototype.join_ = function(arr, sep) { 70 | var parts = []; 71 | for (var i = 0, ii = arr.length; i < ii; i++) { 72 | arr[i] && parts.push(arr[i]); 73 | } 74 | return parts.join(sep); 75 | }; 76 | 77 | /** 78 | * Very rudimentary CSV parsing - we know how this particular CSV is formatted. 79 | * IMPORTANT: Don't use this for general CSV parsing! 80 | * @private 81 | * @param {string} row 82 | * @return {Array.} 83 | */ 84 | MedicareDataSource.prototype.parseRow_ = function(row) { 85 | // Strip leading quote. 86 | if (row.charAt(0) == '"') { 87 | row = row.substring(1); 88 | } 89 | // Strip trailing quote. There seems to be a character between the last quote 90 | // and the line ending, hence 2 instead of 1. 91 | if (row.charAt(row.length - 2) == '"') { 92 | row = row.substring(0, row.length - 2); 93 | } 94 | 95 | row = row.split('","'); 96 | 97 | return row; 98 | }; 99 | 100 | /** 101 | * Creates an object mapping headings to row elements. 102 | * @private 103 | * @param {Array.} headings 104 | * @param {Array.} row 105 | * @return {Object} 106 | */ 107 | MedicareDataSource.prototype.toObject_ = function(headings, row) { 108 | var result = {}; 109 | for (var i = 0, ii = row.length; i < ii; i++) { 110 | result[headings[i]] = row[i]; 111 | } 112 | return result; 113 | }; 114 | -------------------------------------------------------------------------------- /exports.js: -------------------------------------------------------------------------------- 1 | /** @interface */ 2 | function storeLocator_Feature() {}; 3 | storeLocator_Feature.prototype.getId = function(var_args) {}; 4 | storeLocator_Feature.prototype.getDisplayName = function(var_args) {}; 5 | 6 | /** @interface */ 7 | function storeLocator_FeatureSet() {}; 8 | storeLocator_FeatureSet.prototype.toggle = function(var_args) {}; 9 | storeLocator_FeatureSet.prototype.contains = function(var_args) {}; 10 | storeLocator_FeatureSet.prototype.getById = function(var_args) {}; 11 | storeLocator_FeatureSet.prototype.add = function(var_args) {}; 12 | storeLocator_FeatureSet.prototype.remove = function(var_args) {}; 13 | storeLocator_FeatureSet.prototype.asList = function(var_args) {}; 14 | 15 | /** @interface */ 16 | function storeLocator_GMEDataFeed() {}; 17 | 18 | /** @interface */ 19 | function storeLocator_Panel() {}; 20 | storeLocator_Panel.prototype.searchPosition = function(var_args) {}; 21 | storeLocator_Panel.prototype.setView = function(var_args) {}; 22 | storeLocator_Panel.prototype.view_changed = function(var_args) {}; 23 | storeLocator_Panel.prototype.stores_changed = function(var_args) {}; 24 | storeLocator_Panel.prototype.selectedStore_changed = function(var_args) {}; 25 | storeLocator_Panel.prototype.hideDirections = function(var_args) {}; 26 | storeLocator_Panel.prototype.showDirections = function(var_args) {}; 27 | storeLocator_Panel.prototype.featureFilter_changed = function(var_args) {}; 28 | 29 | /** @interface */ 30 | function storeLocator_StaticDataFeed() {}; 31 | storeLocator_StaticDataFeed.prototype.setStores = function(var_args) {}; 32 | storeLocator_StaticDataFeed.prototype.getStores = function(var_args) {}; 33 | 34 | /** @interface */ 35 | function storeLocator_Store() {}; 36 | storeLocator_Store.prototype.setMarker = function(var_args) {}; 37 | storeLocator_Store.prototype.getMarker = function(var_args) {}; 38 | storeLocator_Store.prototype.getId = function(var_args) {}; 39 | storeLocator_Store.prototype.getLocation = function(var_args) {}; 40 | storeLocator_Store.prototype.getFeatures = function(var_args) {}; 41 | storeLocator_Store.prototype.hasFeature = function(var_args) {}; 42 | storeLocator_Store.prototype.hasAllFeatures = function(var_args) {}; 43 | storeLocator_Store.prototype.getDetails = function(var_args) {}; 44 | storeLocator_Store.prototype.getInfoWindowContent = function(var_args) {}; 45 | storeLocator_Store.prototype.getInfoPanelContent = function(var_args) {}; 46 | storeLocator_Store.prototype.getInfoPanelItem = function(var_args) {}; 47 | storeLocator_Store.prototype.distanceTo = function(var_args) {}; 48 | 49 | /** @interface */ 50 | function storeLocator_View() {}; 51 | storeLocator_View.prototype.updateOnPan_changed = function(var_args) {}; 52 | storeLocator_View.prototype.addStoreToMap = function(var_args) {}; 53 | storeLocator_View.prototype.createMarker = function(var_args) {}; 54 | storeLocator_View.prototype.getMarker = function(var_args) {}; 55 | storeLocator_View.prototype.getInfoWindow = function(var_args) {}; 56 | storeLocator_View.prototype.getFeatures = function(var_args) {}; 57 | storeLocator_View.prototype.getFeatureById = function(var_args) {}; 58 | storeLocator_View.prototype.featureFilter_changed = function(var_args) {}; 59 | storeLocator_View.prototype.clearMarkers = function(var_args) {}; 60 | storeLocator_View.prototype.refreshView = function(var_args) {}; 61 | storeLocator_View.prototype.stores_changed = function(var_args) {}; 62 | storeLocator_View.prototype.getMap = function(var_args) {}; 63 | storeLocator_View.prototype.highlight = function(var_args) {}; 64 | storeLocator_View.prototype.selectedStore_changed = function(var_args) {}; 65 | 66 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Store Locator Library for the Google Maps JavaScript API v3 2 | ============== 3 | 4 | This library enables developers to easily build store locator-type applications using the Google Maps JavaScript API v3. 5 | ![Analytics](https://ga-beacon.appspot.com/UA-12846745-20/js-store-locator/readme?pixel) 6 | 7 | The library provides the following features: 8 | 9 | * Pluggable data source – display stores from a data source of your choosing 10 | * HTML5 Geolocation – determines a good initial viewport for the user 11 | * Info window – shows details about a store 12 | * Street View 13 | * Extensible – customise your own markers, info windows, etc. 14 | * Fully-featured side panel, providing: 15 | * Feature filtering 16 | * Autocomplete search 17 | * List of nearby stores 18 | * Directions 19 | 20 | ## Examples/Demos 21 | 22 | The best way to become acquainted with the library is to see some of the examples: 23 | 24 | 1. [panel.html](https://googlemaps.github.io/js-store-locator/examples/panel.html) – A simple store locator, including panel. Data is fetched from a static CSV file. 25 | 2. [dynamic.html](https://googlemaps.github.io/js-store-locator/examples/dynamic.html) – Same as above, except stores are fetched dynamically from a JSONP web service. 26 | 3. [gme.html](https://googlemaps.github.io/js-store-locator/examples/gme.html) – Same as above, except stores are fetched dynamically from Google Maps Engine. 27 | 4. [custom.html](https://googlemaps.github.io/js-store-locator/examples/custom.html) – Various customisations to the default UI including custom markers and info window. 28 | 5. [places.html](https://googlemaps.github.io/js-store-locator/examples/places.html) – Places are searched using the Google Places API, and displayed as a store locator. 29 | 30 | ## Reference documentation 31 | 32 | For detailed documentation on the library, including classes, events and sample usage, please see the [reference documentation](https://googlemaps.github.io/js-store-locator/reference.html). 33 | 34 | ## Quick Start 35 | 36 | To get started, include the [store-locator.min.js](https://github.com/googlemaps/js-store-locator/blob/gh-pages/dist/store-locator.min.js) file on your HTML page. A set of [CSS styles](https://github.com/googlemaps/js-store-locator/blob/gh-pages/css/storelocator.css) are also available, which provide styling for store details and side panel. 37 | 38 | The library's functions are contained under the `storeLocator` namespace. The main class is called `storeLocator.View`. A `View` has two required parameters, a `google.maps.Map`, and a `storeLocator.DataFeed`. 39 | 40 | A `DataFeed` is an object with the function `getStores`. This function is called each time the map needs an update. A simple example of a `DataFeed` can be found in the [reference](https://googlemaps.github.io/js-store-locator/reference.html#storeLocator.DataFeed). 41 | 42 | ## Contributing 43 | 44 | Want to contribute? Check out the [contributing guide](CONTRIBUTING.md)! 45 | 46 | ## Compiling the project 47 | 48 | $ ./gradlew assemble 49 | 50 | > gradlew.bat assemble 51 | 52 | ## License 53 | 54 | Copyright 2014 Google Inc. All rights reserved. 55 | 56 | Licensed under the Apache License, Version 2.0 (the "License"); 57 | you may not use this file except in compliance with the License. 58 | You may obtain a copy of the License at 59 | 60 | http://www.apache.org/licenses/LICENSE-2.0 61 | 62 | Unless required by applicable law or agreed to in writing, software 63 | distributed under the License is distributed on an "AS IS" BASIS, 64 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 65 | See the License for the specific language governing permissions and 66 | limitations under the License. 67 | -------------------------------------------------------------------------------- /js/static-datasource.js: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Google Inc. 2 | 3 | /** 4 | * @author Chris Broadfoot (Google) 5 | * @fileoverview 6 | * Allows developers to specify a static set of stores to be used in the 7 | * storelocator. 8 | */ 9 | 10 | /** 11 | * Licensed under the Apache License, Version 2.0 (the "License"); 12 | * you may not use this file except in compliance with the License. 13 | * You may obtain a copy of the License at 14 | * 15 | * http://www.apache.org/licenses/LICENSE-2.0 16 | * 17 | * Unless required by applicable law or agreed to in writing, software 18 | * distributed under the License is distributed on an "AS IS" BASIS, 19 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 20 | * See the License for the specific language governing permissions and 21 | * limitations under the License. 22 | */ 23 | 24 | 25 | /** 26 | * A DataFeed with a static set of stores. Provides sorting of stores by 27 | * proximity and feature filtering (store must have all features from 28 | * the filter). 29 | * @example
 30 |  * var dataFeed = new storeLocator.StaticDataFeed();
 31 |  * jQuery.getJSON('stores.json', function(json) {
 32 |  *   var stores = parseStores(json);
 33 |  *   dataFeed.setStores(stores);
 34 |  * });
 35 |  * new storeLocator.View(map, dataFeed);
 36 |  * 
37 | * @implements {storeLocator.DataFeed} 38 | * @constructor 39 | * @implements storeLocator_StaticDataFeed 40 | */ 41 | storeLocator.StaticDataFeed = function() { 42 | /** 43 | * The static list of stores. 44 | * @private 45 | * @type {Array.} 46 | */ 47 | this.stores_ = []; 48 | }; 49 | storeLocator['StaticDataFeed'] = storeLocator.StaticDataFeed; 50 | 51 | /** 52 | * This will contain a callback to be called if getStores was called before 53 | * setStores (i.e. if the map is waiting for data from the data source). 54 | * @private 55 | * @type {Function} 56 | */ 57 | storeLocator.StaticDataFeed.prototype.firstCallback_; 58 | 59 | /** 60 | * Set the stores for this data feed. 61 | * @param {!Array.} stores the stores for this data feed. 62 | */ 63 | storeLocator.StaticDataFeed.prototype.setStores = function(stores) { 64 | this.stores_ = stores; 65 | if (this.firstCallback_) { 66 | this.firstCallback_(); 67 | } else { 68 | delete this.firstCallback_; 69 | } 70 | }; 71 | 72 | /** 73 | * @inheritDoc 74 | */ 75 | storeLocator.StaticDataFeed.prototype.getStores = function(bounds, features, 76 | callback) { 77 | 78 | // Prevent race condition - if getStores is called before stores are loaded. 79 | if (!this.stores_.length) { 80 | var that = this; 81 | this.firstCallback_ = function() { 82 | that.getStores(bounds, features, callback); 83 | }; 84 | return; 85 | } 86 | 87 | // Filter stores for features. 88 | var stores = []; 89 | for (var i = 0, store; store = this.stores_[i]; i++) { 90 | if (store.hasAllFeatures(features)) { 91 | stores.push(store); 92 | } 93 | } 94 | this.sortByDistance_(bounds.getCenter(), stores); 95 | callback(stores); 96 | }; 97 | 98 | /** 99 | * Sorts a list of given stores by distance from a point in ascending order. 100 | * Directly manipulates the given array (has side effects). 101 | * @private 102 | * @param {google.maps.LatLng} latLng the point to sort from. 103 | * @param {!Array.} stores the stores to sort. 104 | */ 105 | storeLocator.StaticDataFeed.prototype.sortByDistance_ = function(latLng, 106 | stores) { 107 | stores.sort(function(a, b) { 108 | return a.distanceTo(latLng) - b.distanceTo(latLng); 109 | }); 110 | }; 111 | -------------------------------------------------------------------------------- /examples/medicare-dynamic-ds.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @implements storeLocator.DataFeed 3 | * @constructor 4 | */ 5 | function MedicareDataSource() { 6 | } 7 | 8 | MedicareDataSource.prototype.getStores = function(bounds, features, callback) { 9 | var that = this; 10 | var center = bounds.getCenter(); 11 | var audioFeature = this.FEATURES_.getById('Audio-YES'); 12 | var wheelchairFeature = this.FEATURES_.getById('Wheelchair-YES'); 13 | 14 | var where = '(ST_INTERSECTS(geometry, ' + this.boundsToWkt_(bounds) + ')' + 15 | ' OR ST_DISTANCE(geometry, ' + this.latLngToWkt_(center) + ') < 20000)'; 16 | 17 | if (features.contains(audioFeature)) { 18 | where += " AND Audio='YES'"; 19 | } 20 | if (features.contains(wheelchairFeature)) { 21 | where += " AND Wheelchair='YES'"; 22 | } 23 | 24 | var tableId = '12421761926155747447-06672618218968397709'; 25 | var url = 'https://www.googleapis.com/mapsengine/v1/tables/' + tableId + 26 | '/features?callback=?'; 27 | 28 | $.getJSON(url, { 29 | key: 'AIzaSyAtunhRg0VTElV-P7n4Agpm9tYlABQDCAM', 30 | where: where, 31 | version: 'published', 32 | maxResults: 300 33 | }, function(resp) { 34 | var stores = that.parse_(resp); 35 | that.sortByDistance_(center, stores); 36 | callback(stores); 37 | }); 38 | }; 39 | 40 | MedicareDataSource.prototype.latLngToWkt_ = function(point) { 41 | return 'ST_POINT(' + point.lng() + ', ' + point.lat() + ')'; 42 | }; 43 | 44 | MedicareDataSource.prototype.boundsToWkt_ = function(bounds) { 45 | var ne = bounds.getNorthEast(); 46 | var sw = bounds.getSouthWest(); 47 | return [ 48 | "ST_GEOMFROMTEXT('POLYGON ((", 49 | sw.lng(), ' ', sw.lat(), ', ', 50 | ne.lng(), ' ', sw.lat(), ', ', 51 | ne.lng(), ' ', ne.lat(), ', ', 52 | sw.lng(), ' ', ne.lat(), ', ', 53 | sw.lng(), ' ', sw.lat(), 54 | "))')" 55 | ].join(''); 56 | }; 57 | 58 | MedicareDataSource.prototype.parse_ = function(data) { 59 | var stores = []; 60 | for (var i = 0, row; row = data.features[i]; i++) { 61 | var props = row.properties; 62 | var features = new storeLocator.FeatureSet; 63 | features.add(this.FEATURES_.getById('Wheelchair-' + props.Wheelchair)); 64 | features.add(this.FEATURES_.getById('Audio-' + props.Audio)); 65 | 66 | var position = new google.maps.LatLng(row.geometry.coordinates[1], 67 | row.geometry.coordinates[0]); 68 | 69 | var shop = this.join_([props.Shp_num_an, props.Shp_centre], ', '); 70 | var locality = this.join_([props.Locality, props.Postcode], ', '); 71 | 72 | var store = new storeLocator.Store(props.uuid, position, features, { 73 | title: props.Fcilty_nam, 74 | address: this.join_([shop, props.Street_add, locality], '
'), 75 | hours: props.Hrs_of_bus 76 | }); 77 | stores.push(store); 78 | } 79 | return stores; 80 | }; 81 | 82 | /** 83 | * @const 84 | * @type {!storeLocator.FeatureSet} 85 | * @private 86 | */ 87 | MedicareDataSource.prototype.FEATURES_ = new storeLocator.FeatureSet( 88 | new storeLocator.Feature('Wheelchair-YES', 'Wheelchair access'), 89 | new storeLocator.Feature('Audio-YES', 'Audio') 90 | ); 91 | 92 | /** 93 | * @return {!storeLocator.FeatureSet} 94 | */ 95 | MedicareDataSource.prototype.getFeatures = function() { 96 | return this.FEATURES_; 97 | }; 98 | 99 | 100 | /** 101 | * Joins elements of an array that are non-empty and non-null. 102 | * @private 103 | * @param {!Array} arr array of elements to join. 104 | * @param {string} sep the separator. 105 | * @return {string} 106 | */ 107 | MedicareDataSource.prototype.join_ = function(arr, sep) { 108 | var parts = []; 109 | for (var i = 0, ii = arr.length; i < ii; i++) { 110 | arr[i] && parts.push(arr[i]); 111 | } 112 | return parts.join(sep); 113 | }; 114 | 115 | /** 116 | * Sorts a list of given stores by distance from a point in ascending order. 117 | * Directly manipulates the given array (has side effects). 118 | * @private 119 | * @param {google.maps.LatLng} latLng the point to sort from. 120 | * @param {!Array.} stores the stores to sort. 121 | */ 122 | MedicareDataSource.prototype.sortByDistance_ = function(latLng, stores) { 123 | stores.sort(function(a, b) { 124 | return a.distanceTo(latLng) - b.distanceTo(latLng); 125 | }); 126 | }; 127 | -------------------------------------------------------------------------------- /js/featureset.js: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Google Inc. 2 | 3 | /** 4 | * @author Chris Broadfoot (Google) 5 | * @fileoverview 6 | * FeatureSet class for Store Locator library. A mutable, ordered set of 7 | * storeLocator.Features. 8 | */ 9 | 10 | /** 11 | * Licensed under the Apache License, Version 2.0 (the "License"); 12 | * you may not use this file except in compliance with the License. 13 | * You may obtain a copy of the License at 14 | * 15 | * http://www.apache.org/licenses/LICENSE-2.0 16 | * 17 | * Unless required by applicable law or agreed to in writing, software 18 | * distributed under the License is distributed on an "AS IS" BASIS, 19 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 20 | * See the License for the specific language governing permissions and 21 | * limitations under the License. 22 | */ 23 | 24 | /** 25 | * A mutable, ordered set of storeLocator.Features. 26 | * @example
 27 |  * var feature1 = new storeLocator.Feature('1', 'Feature One');
 28 |  * var feature2 = new storeLocator.Feature('2', 'Feature Two');
 29 |  * var feature3 = new storeLocator.Feature('3', 'Feature Three');
 30 |  *
 31 |  * var featureSet = new storeLocator.FeatureSet(feature1, feature2, feature3);
 32 |  * 
33 | * @param {...storeLocator.Feature} var_args the initial features to add to 34 | * the set. 35 | * @constructor 36 | * @implements storeLocator_FeatureSet 37 | */ 38 | storeLocator.FeatureSet = function(var_args) { 39 | /** 40 | * Stores references to the actual Feature. 41 | * @private 42 | * @type {!Array.} 43 | */ 44 | this.array_ = []; 45 | 46 | /** 47 | * Maps from a Feature's id to its array index. 48 | * @private 49 | * @type {Object.} 50 | */ 51 | this.hash_ = {}; 52 | 53 | for (var i = 0, feature; feature = arguments[i]; i++) { 54 | this.add(feature); 55 | } 56 | }; 57 | storeLocator['FeatureSet'] = storeLocator.FeatureSet; 58 | 59 | /** 60 | * Adds the given feature to the set, if it doesn't exist in the set already. 61 | * Else, removes the feature from the set. 62 | * @param {!storeLocator.Feature} feature the feature to toggle. 63 | */ 64 | storeLocator.FeatureSet.prototype.toggle = function(feature) { 65 | if (this.contains(feature)) { 66 | this.remove(feature); 67 | } else { 68 | this.add(feature); 69 | } 70 | }; 71 | 72 | /** 73 | * Check if a feature exists within this set. 74 | * @param {!storeLocator.Feature} feature the feature. 75 | * @return {boolean} true if the set contains the given feature. 76 | */ 77 | storeLocator.FeatureSet.prototype.contains = function(feature) { 78 | return feature.getId() in this.hash_; 79 | }; 80 | 81 | /** 82 | * Gets a Feature object from the set, by the feature id. 83 | * @param {string} featureId the feature's id. 84 | * @return {storeLocator.Feature} the feature, if the set contains it. 85 | */ 86 | storeLocator.FeatureSet.prototype.getById = function(featureId) { 87 | if (featureId in this.hash_) { 88 | return this.array_[this.hash_[featureId]]; 89 | } 90 | return null; 91 | }; 92 | 93 | /** 94 | * Adds a feature to the set. 95 | * @param {storeLocator.Feature} feature the feature to add. 96 | */ 97 | storeLocator.FeatureSet.prototype.add = function(feature) { 98 | if (!feature) { 99 | return; 100 | } 101 | this.array_.push(feature); 102 | this.hash_[feature.getId()] = this.array_.length - 1; 103 | }; 104 | 105 | /** 106 | * Removes a feature from the set, if it already exists in the set. If it does 107 | * not already exist in the set, this function is a no op. 108 | * @param {!storeLocator.Feature} feature the feature to remove. 109 | */ 110 | storeLocator.FeatureSet.prototype.remove = function(feature) { 111 | if (!this.contains(feature)) { 112 | return; 113 | } 114 | this.array_[this.hash_[feature.getId()]] = null; 115 | delete this.hash_[feature.getId()]; 116 | }; 117 | 118 | /** 119 | * Get the contents of this set as an Array. 120 | * @return {Array.} the features in the set, in the order 121 | * they were inserted. 122 | */ 123 | storeLocator.FeatureSet.prototype.asList = function() { 124 | var filtered = []; 125 | for (var i = 0, ii = this.array_.length; i < ii; i++) { 126 | var elem = this.array_[i]; 127 | if (elem !== null) { 128 | filtered.push(elem); 129 | } 130 | } 131 | return filtered; 132 | }; 133 | 134 | /** 135 | * Empty feature set. 136 | * @type storeLocator.FeatureSet 137 | * @const 138 | */ 139 | storeLocator.FeatureSet.NONE = new storeLocator.FeatureSet; 140 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 46 | if $cygwin ; then 47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 48 | fi 49 | 50 | # Attempt to set APP_HOME 51 | # Resolve links: $0 may be a link 52 | PRG="$0" 53 | # Need this for relative symlinks. 54 | while [ -h "$PRG" ] ; do 55 | ls=`ls -ld "$PRG"` 56 | link=`expr "$ls" : '.*-> \(.*\)$'` 57 | if expr "$link" : '/.*' > /dev/null; then 58 | PRG="$link" 59 | else 60 | PRG=`dirname "$PRG"`"/$link" 61 | fi 62 | done 63 | SAVED="`pwd`" 64 | cd "`dirname \"$PRG\"`/" >&- 65 | APP_HOME="`pwd -P`" 66 | cd "$SAVED" >&- 67 | 68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 69 | 70 | # Determine the Java command to use to start the JVM. 71 | if [ -n "$JAVA_HOME" ] ; then 72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 73 | # IBM's JDK on AIX uses strange locations for the executables 74 | JAVACMD="$JAVA_HOME/jre/sh/java" 75 | else 76 | JAVACMD="$JAVA_HOME/bin/java" 77 | fi 78 | if [ ! -x "$JAVACMD" ] ; then 79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 80 | 81 | Please set the JAVA_HOME variable in your environment to match the 82 | location of your Java installation." 83 | fi 84 | else 85 | JAVACMD="java" 86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 87 | 88 | Please set the JAVA_HOME variable in your environment to match the 89 | location of your Java installation." 90 | fi 91 | 92 | # Increase the maximum file descriptors if we can. 93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 94 | MAX_FD_LIMIT=`ulimit -H -n` 95 | if [ $? -eq 0 ] ; then 96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 97 | MAX_FD="$MAX_FD_LIMIT" 98 | fi 99 | ulimit -n $MAX_FD 100 | if [ $? -ne 0 ] ; then 101 | warn "Could not set maximum file descriptor limit: $MAX_FD" 102 | fi 103 | else 104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 105 | fi 106 | fi 107 | 108 | # For Darwin, add options to specify how the application appears in the dock 109 | if $darwin; then 110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 111 | fi 112 | 113 | # For Cygwin, switch paths to Windows format before running java 114 | if $cygwin ; then 115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /js/gme-datasource.js: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Google Inc. 2 | 3 | /** 4 | * @author Chris Broadfoot (Google) 5 | * @fileoverview 6 | * Provides access to store data through Google Maps Engine. 7 | */ 8 | 9 | /** 10 | * Licensed under the Apache License, Version 2.0 (the "License"); 11 | * you may not use this file except in compliance with the License. 12 | * You may obtain a copy of the License at 13 | * 14 | * http://www.apache.org/licenses/LICENSE-2.0 15 | * 16 | * Unless required by applicable law or agreed to in writing, software 17 | * distributed under the License is distributed on an "AS IS" BASIS, 18 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 | * See the License for the specific language governing permissions and 20 | * limitations under the License. 21 | */ 22 | 23 | 24 | /** 25 | * A DataFeed where stores are provided by Google Maps Engine. 26 | *

27 | * Note: the table that contains the stores needs to be publicly accessible. 28 | * @example

 29 |  * var dataFeed = new storeLocator.GMEDataFeed({
 30 |  *   tableId: '12421761926155747447-06672618218968397709',
 31 |  *   apiKey: 'AIzaSyAtunhRg0VTElV-P7n4Agpm9tYlABQDCAM',
 32 |  *   propertiesModifier: function(props) {
 33 |  *     return {
 34 |  *       id: transformId(props.store_id),
 35 |  *       title: props.store_title
 36 |  *     };
 37 |  *   }
 38 |  * });
 39 |  * new storeLocator.View(map, dataFeed);
 40 |  * 
41 | * @implements storeLocator.DataFeed 42 | * @param {!storeLocator.GMEDataFeedOptions} opts the table ID, API key and 43 | * a transformation function for feature/store properties. 44 | * @constructor 45 | * @implements storeLocator_GMEDataFeed 46 | */ 47 | storeLocator.GMEDataFeed = function(opts) { 48 | this.tableId_ = opts['tableId']; 49 | this.apiKey_ = opts['apiKey']; 50 | if (opts['propertiesModifier']) { 51 | this.propertiesModifier_ = opts['propertiesModifier']; 52 | } 53 | }; 54 | storeLocator['GMEDataFeed'] = storeLocator.GMEDataFeed; 55 | 56 | storeLocator.GMEDataFeed.prototype.getStores = 57 | function(bounds, features, callback) { 58 | 59 | // TODO: use features. 60 | 61 | var that = this; 62 | var center = bounds.getCenter(); 63 | 64 | var where = '(ST_INTERSECTS(geometry, ' + this.boundsToWkt_(bounds) + ')' + 65 | ' OR ST_DISTANCE(geometry, ' + this.latLngToWkt_(center) + ') < 20000)'; 66 | 67 | var url = 'https://www.googleapis.com/mapsengine/v1/tables/' + this.tableId_ + 68 | '/features?callback=?'; 69 | 70 | $.getJSON(url, { 71 | 'key': this.apiKey_, 72 | 'where': where, 73 | 'version': 'published', 74 | 'maxResults': 300 75 | }, function(resp) { 76 | var stores = that.parse_(resp); 77 | that.sortByDistance_(center, stores); 78 | callback(stores); 79 | }); 80 | }; 81 | 82 | /** 83 | * @private 84 | * @param {!google.maps.LatLng} point 85 | * @return {string} 86 | */ 87 | storeLocator.GMEDataFeed.prototype.latLngToWkt_ = function(point) { 88 | return 'ST_POINT(' + point.lng() + ', ' + point.lat() + ')'; 89 | }; 90 | 91 | /** 92 | * @private 93 | * @param {!google.maps.LatLngBounds} bounds 94 | * @return {string} 95 | */ 96 | storeLocator.GMEDataFeed.prototype.boundsToWkt_ = function(bounds) { 97 | var ne = bounds.getNorthEast(); 98 | var sw = bounds.getSouthWest(); 99 | return [ 100 | "ST_GEOMFROMTEXT('POLYGON ((", 101 | sw.lng(), ' ', sw.lat(), ', ', 102 | ne.lng(), ' ', sw.lat(), ', ', 103 | ne.lng(), ' ', ne.lat(), ', ', 104 | sw.lng(), ' ', ne.lat(), ', ', 105 | sw.lng(), ' ', sw.lat(), 106 | "))')" 107 | ].join(''); 108 | }; 109 | 110 | /** 111 | * @private 112 | * @param {*} data GeoJSON feature set. 113 | * @return {!Array.} 114 | */ 115 | storeLocator.GMEDataFeed.prototype.parse_ = function(data) { 116 | if (data['error']) { 117 | window.alert(data['error']['message']); 118 | return []; 119 | } 120 | var features = data['features']; 121 | if (!features) { 122 | return []; 123 | } 124 | var stores = []; 125 | for (var i = 0, row; row = features[i]; i++) { 126 | var coordinates = row['geometry']['coordinates']; 127 | var position = new google.maps.LatLng(coordinates[1], coordinates[0]); 128 | 129 | var props = this.propertiesModifier_(row['properties']); 130 | var store = new storeLocator.Store(props['id'], position, null, props); 131 | stores.push(store); 132 | } 133 | return stores; 134 | }; 135 | 136 | /** 137 | * Default properties modifier. Just returns the same properties passed into 138 | * it. Useful if the columns in the GME table are already appropriate. 139 | * @private 140 | * @param {Object} props 141 | * @return {Object} an Object to be passed into the "props" argument in the 142 | * Store constructor. 143 | */ 144 | storeLocator.GMEDataFeed.prototype.propertiesModifier_ = function(props) { 145 | return props; 146 | }; 147 | 148 | /** 149 | * Sorts a list of given stores by distance from a point in ascending order. 150 | * Directly manipulates the given array (has side effects). 151 | * @private 152 | * @param {google.maps.LatLng} latLng the point to sort from. 153 | * @param {!Array.} stores the stores to sort. 154 | */ 155 | storeLocator.GMEDataFeed.prototype.sortByDistance_ = 156 | function(latLng, stores) { 157 | stores.sort(function(a, b) { 158 | return a.distanceTo(latLng) - b.distanceTo(latLng); 159 | }); 160 | }; 161 | 162 | /** 163 | * @example see storeLocator.GMEDataFeed 164 | * @interface 165 | */ 166 | storeLocator.GMEDataFeedOptions = function() {}; 167 | 168 | /** 169 | * The table's asset ID. 170 | * @type string 171 | */ 172 | storeLocator.GMEDataFeedOptions.prototype.tableId; 173 | 174 | /** 175 | * The API key to use for all requests. 176 | * @type string 177 | */ 178 | storeLocator.GMEDataFeedOptions.prototype.apiKey; 179 | 180 | /** 181 | * A transformation function. The first argument is the feature's properties. 182 | * Return an object useful for the "props" argument in the 183 | * storeLocator.Store constructor. The default properties modifier 184 | * function passes the feature straight through. 185 | *

186 | * Note: storeLocator.GMEDataFeed expects an "id" property. 187 | * @type ?(function(Object): Object) 188 | */ 189 | storeLocator.GMEDataFeedOptions.prototype.propertiesModifier; 190 | -------------------------------------------------------------------------------- /js/store.js: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Google Inc. 2 | 3 | /** 4 | * @author Chris Broadfoot (Google) 5 | * @fileoverview 6 | * Store model class for Store Locator library. 7 | */ 8 | 9 | /** 10 | * Licensed under the Apache License, Version 2.0 (the "License"); 11 | * you may not use this file except in compliance with the License. 12 | * You may obtain a copy of the License at 13 | * 14 | * http://www.apache.org/licenses/LICENSE-2.0 15 | * 16 | * Unless required by applicable law or agreed to in writing, software 17 | * distributed under the License is distributed on an "AS IS" BASIS, 18 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 | * See the License for the specific language governing permissions and 20 | * limitations under the License. 21 | */ 22 | 23 | /** 24 | * Represents a store. 25 | * @example

 26 |  * var latLng = new google.maps.LatLng(40.7585, -73.9861);
 27 |  * var store = new storeLocator.Store('times_square', latLng, null);
 28 |  * 
29 | *
 30 |  * var features = new storeLocator.FeatureSet(
 31 |  *     view.getFeatureById('24hour'),
 32 |  *     view.getFeatureById('express'),
 33 |  *     view.getFeatureById('wheelchair_access'));
 34 |  *
 35 |  * var store = new storeLocator.Store('times_square', latLng, features, {
 36 |  *   title: 'Times Square',
 37 |  *   address: '1 Times Square<br>Manhattan, NY 10036'
 38 |  * });
 39 |  * 
40 | *
 41 |  * store.distanceTo(map.getCenter());
 42 |  *
 43 |  * // override default info window
 44 |  * store.getInfoWindowContent = function() {
 45 |  *   var details = this.getDetails();
 46 |  *   return '<h1>' + details.title + '<h1>' + details.address;
 47 |  * };
 48 |  * 
49 | * @param {string} id globally unique id of the store - should be suitable to 50 | * use as a HTML id. 51 | * @param {!google.maps.LatLng} location location of the store. 52 | * @param {storeLocator.FeatureSet} features the features of this store. 53 | * @param {Object.=} props any additional properties. 54 | *

Recommended fields are: 55 | * 'title', 'address', 'phone', 'misc', 'web'.

56 | * @constructor 57 | * @implements storeLocator_Store 58 | */ 59 | storeLocator.Store = function(id, location, features, props) { 60 | this.id_ = id; 61 | this.location_ = location; 62 | this.features_ = features || storeLocator.FeatureSet.NONE; 63 | this.props_ = props || {}; 64 | }; 65 | storeLocator['Store'] = storeLocator.Store; 66 | 67 | /** 68 | * Sets this store's Marker. 69 | * @param {google.maps.Marker} marker the marker to set on this store. 70 | */ 71 | storeLocator.Store.prototype.setMarker = function(marker) { 72 | this.marker_ = marker; 73 | google.maps.event.trigger(this, 'marker_changed', marker); 74 | }; 75 | 76 | /** 77 | * Gets this store's Marker 78 | * @return {google.maps.Marker} the store's marker. 79 | */ 80 | storeLocator.Store.prototype.getMarker = function() { 81 | return this.marker_; 82 | }; 83 | 84 | /** 85 | * Gets this store's ID. 86 | * @return {string} this store's ID. 87 | */ 88 | storeLocator.Store.prototype.getId = function() { 89 | return this.id_; 90 | }; 91 | 92 | /** 93 | * Gets this store's location. 94 | * @return {google.maps.LatLng} this store's location. 95 | */ 96 | storeLocator.Store.prototype.getLocation = function() { 97 | return this.location_; 98 | }; 99 | 100 | /** 101 | * Gets this store's features. 102 | * @return {storeLocator.FeatureSet} this store's features. 103 | */ 104 | storeLocator.Store.prototype.getFeatures = function() { 105 | return this.features_; 106 | }; 107 | 108 | /** 109 | * Checks whether this store has a particular feature. 110 | * @param {!storeLocator.Feature} feature the feature to check for. 111 | * @return {boolean} true if the store has the feature, false otherwise. 112 | */ 113 | storeLocator.Store.prototype.hasFeature = function(feature) { 114 | return this.features_.contains(feature); 115 | }; 116 | 117 | /** 118 | * Checks whether this store has all the given features. 119 | * @param {storeLocator.FeatureSet} features the features to check for. 120 | * @return {boolean} true if the store has all features, false otherwise. 121 | */ 122 | storeLocator.Store.prototype.hasAllFeatures = function(features) { 123 | if (!features) { 124 | return true; 125 | } 126 | var featureList = features.asList(); 127 | for (var i = 0, ii = featureList.length; i < ii; i++) { 128 | if (!this.hasFeature(featureList[i])) { 129 | return false; 130 | } 131 | } 132 | return true; 133 | }; 134 | 135 | /** 136 | * Gets additional details about this store. 137 | * @return {Object} additional properties of this store. 138 | */ 139 | storeLocator.Store.prototype.getDetails = function() { 140 | return this.props_; 141 | }; 142 | 143 | /** 144 | * Generates HTML for additional details about this store. 145 | * @private 146 | * @param {Array.} fields the optional fields of this store to output. 147 | * @return {string} html version of additional fields of this store. 148 | */ 149 | storeLocator.Store.prototype.generateFieldsHTML_ = function(fields) { 150 | var html = []; 151 | for (var i = 0, ii = fields.length; i < ii; i++) { 152 | var prop = fields[i]; 153 | if (this.props_[prop]) { 154 | html.push('
'); 157 | html.push(this.props_[prop]); 158 | html.push('
'); 159 | } 160 | } 161 | return html.join(''); 162 | }; 163 | 164 | /** 165 | * Generates a HTML list of this store's features. 166 | * @private 167 | * @return {string} html list of this store's features. 168 | */ 169 | storeLocator.Store.prototype.generateFeaturesHTML_ = function() { 170 | var html = []; 171 | html.push('
    '); 172 | var featureList = this.features_.asList(); 173 | for (var i = 0, feature; feature = featureList[i]; i++) { 174 | html.push('
  • '); 175 | html.push(feature.getDisplayName()); 176 | html.push('
  • '); 177 | } 178 | html.push('
'); 179 | return html.join(''); 180 | }; 181 | 182 | /** 183 | * Gets the HTML content for this Store, suitable for use in an InfoWindow. 184 | * @return {string} a HTML version of this store. 185 | */ 186 | storeLocator.Store.prototype.getInfoWindowContent = function() { 187 | if (!this.content_) { 188 | // TODO(cbro): make this a setting? 189 | var fields = ['title', 'address', 'phone', 'misc', 'web']; 190 | var html = ['
']; 191 | html.push(this.generateFieldsHTML_(fields)); 192 | html.push(this.generateFeaturesHTML_()); 193 | html.push('
'); 194 | 195 | this.content_ = html.join(''); 196 | } 197 | return this.content_; 198 | }; 199 | 200 | /** 201 | * Gets the HTML content for this Store, suitable for use in suitable for use 202 | * in the sidebar info panel. 203 | * @this storeLocator.Store 204 | * @return {string} a HTML version of this store. 205 | */ 206 | storeLocator.Store.prototype.getInfoPanelContent = function() { 207 | return this.getInfoWindowContent(); 208 | }; 209 | 210 | /** 211 | * Keep a cache of InfoPanel items (DOM Node), keyed by the store ID. 212 | * @private 213 | * @type {Object} 214 | */ 215 | storeLocator.Store.infoPanelCache_ = {}; 216 | 217 | /** 218 | * Gets a HTML element suitable for use in the InfoPanel. 219 | * @return {Node} a HTML element. 220 | */ 221 | storeLocator.Store.prototype.getInfoPanelItem = function() { 222 | var cache = storeLocator.Store.infoPanelCache_; 223 | var store = this; 224 | var key = store.getId(); 225 | if (!cache[key]) { 226 | var content = store.getInfoPanelContent(); 227 | cache[key] = $('
  • ' + content + '
  • ')[0]; 229 | } 230 | return cache[key]; 231 | }; 232 | 233 | /** 234 | * Gets the distance between this Store and a certain location. 235 | * @param {google.maps.LatLng} point the point to calculate distance to/from. 236 | * @return {number} the distance from this store to a given point. 237 | * @license 238 | * Latitude/longitude spherical geodesy formulae & scripts 239 | * (c) Chris Veness 2002-2010 240 | * www.movable-type.co.uk/scripts/latlong.html 241 | */ 242 | storeLocator.Store.prototype.distanceTo = function(point) { 243 | var R = 6371; // mean radius of earth 244 | var location = this.getLocation(); 245 | var lat1 = storeLocator.toRad_(location.lat()); 246 | var lon1 = storeLocator.toRad_(location.lng()); 247 | var lat2 = storeLocator.toRad_(point.lat()); 248 | var lon2 = storeLocator.toRad_(point.lng()); 249 | var dLat = lat2 - lat1; 250 | var dLon = lon2 - lon1; 251 | 252 | var a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + 253 | Math.cos(lat1) * Math.cos(lat2) * 254 | Math.sin(dLon / 2) * Math.sin(dLon / 2); 255 | var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); 256 | return R * c; 257 | }; 258 | 259 | /** 260 | * Fired when the Store's marker property changes. 261 | * @name storeLocator.Store#event:marker_changed 262 | * @param {google.maps.Marker} marker 263 | * @event 264 | */ 265 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /js/view.js: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Google Inc. 2 | 3 | /** 4 | * @author Chris Broadfoot (Google) 5 | * @fileoverview 6 | * This library makes it easy to create a fully-featured Store Locator for 7 | * your business's website. 8 | */ 9 | 10 | /** 11 | * Licensed under the Apache License, Version 2.0 (the "License"); 12 | * you may not use this file except in compliance with the License. 13 | * You may obtain a copy of the License at 14 | * 15 | * http://www.apache.org/licenses/LICENSE-2.0 16 | * 17 | * Unless required by applicable law or agreed to in writing, software 18 | * distributed under the License is distributed on an "AS IS" BASIS, 19 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 20 | * See the License for the specific language governing permissions and 21 | * limitations under the License. 22 | */ 23 | 24 | /** 25 | * Data feed that returns stores based on a given bounds and a set of features. 26 | * @example
     27 |  * // always returns the same stores
     28 |  * function SimpleStaticFeed(stores) {
     29 |  *   this.stores = stores;
     30 |  * }
     31 |  * SimpleStaticFeed.prototype.getStores = function(bounds, features, callback) {
     32 |  *   callback(this.stores);
     33 |  * };
     34 |  * new storeLocator.View(map, new SimpleStaticFeed());
     35 |  * 
    36 | * @interface 37 | */ 38 | storeLocator.DataFeed = function() {}; 39 | storeLocator['DataFeed'] = storeLocator.DataFeed; 40 | 41 | /** 42 | * Fetch stores, based on bounds to search within, and features to filter on. 43 | * @param {google.maps.LatLngBounds} bounds the bounds to search within. 44 | * @param {storeLocator.FeatureSet} features the features to filter on. 45 | * @param {function(Array.)} callback the callback 46 | * function. 47 | */ 48 | storeLocator.DataFeed.prototype.getStores = 49 | function(bounds, features, callback) {}; 50 | 51 | /** 52 | * The main store locator object. 53 | * @example
     54 |  * new storeLocator.View(map, dataFeed);
     55 |  * 
    56 | *
     57 |  * var features = new storeLocator.FeatureSet(feature1, feature2, feature3);
     58 |  * new storeLocator.View(map, dataFeed, {
     59 |  *   markerIcon: 'icon.png',
     60 |  *   features: features,
     61 |  *   geolocation: false
     62 |  * });
     63 |  * 
    64 | *
     65 |  * // refresh stores every 10 seconds, regardless of interaction on the map.
     66 |  * var view = new storeLocator.View(map, dataFeed, {
     67 |  *   updateOnPan: false
     68 |  * });
     69 |  * setTimeout(function() {
     70 |  *   view.refreshView();
     71 |  * }, 10000);
     72 |  * 
    73 | *
     74 |  * // custom MarkerOptions, by overriding the createMarker method.
     75 |  * view.createMarker = function(store) {
     76 |  *   return new google.maps.Marker({
     77 |  *     position: store.getLocation(),
     78 |  *     icon: store.getDetails().icon,
     79 |  *     title: store.getDetails().title
     80 |  *   });
     81 |  * };
     82 |  * 
    83 | * @extends {google.maps.MVCObject} 84 | * @param {google.maps.Map} map the map to operate upon. 85 | * @param {storeLocator.DataFeed} data the data feed to fetch stores from. 86 | * @param {storeLocator.ViewOptions} opt_options 87 | * @constructor 88 | * @implements storeLocator_View 89 | */ 90 | storeLocator.View = function(map, data, opt_options) { 91 | this.map_ = map; 92 | this.data_ = data; 93 | this.settings_ = $.extend({ 94 | 'updateOnPan': true, 95 | 'geolocation': true, 96 | 'features': new storeLocator.FeatureSet 97 | }, opt_options); 98 | 99 | this.init_(); 100 | google.maps.event.trigger(this, 'load'); 101 | this.set('featureFilter', new storeLocator.FeatureSet); 102 | }; 103 | storeLocator['View'] = storeLocator.View; 104 | 105 | storeLocator.View.prototype = new google.maps.MVCObject; 106 | 107 | /** 108 | * Attempt to perform geolocation and pan to the given location 109 | * @private 110 | */ 111 | storeLocator.View.prototype.geolocate_ = function() { 112 | var that = this; 113 | if (window.navigator && navigator.geolocation) { 114 | navigator.geolocation.getCurrentPosition(function(pos) { 115 | var loc = new google.maps.LatLng( 116 | pos.coords.latitude, pos.coords.longitude); 117 | 118 | that.getMap().setCenter(loc); 119 | that.getMap().setZoom(11); 120 | google.maps.event.trigger(that, 'load'); 121 | }, undefined, /** @type GeolocationPositionOptions */({ 122 | maximumAge: 60 * 1000, 123 | timeout: 10 * 1000 124 | })); 125 | } 126 | }; 127 | 128 | /** 129 | * Initialise the View object 130 | * @private 131 | */ 132 | storeLocator.View.prototype.init_ = function() { 133 | if (this.settings_['geolocation']) { 134 | this.geolocate_(); 135 | } 136 | this.markerCache_ = {}; 137 | this.infoWindow_ = new google.maps.InfoWindow; 138 | 139 | var that = this; 140 | var map = this.getMap(); 141 | 142 | this.set('updateOnPan', this.settings_['updateOnPan']); 143 | 144 | google.maps.event.addListener(this.infoWindow_, 'closeclick', function() { 145 | that.highlight(null); 146 | }); 147 | 148 | google.maps.event.addListener(map, 'click', function() { 149 | that.highlight(null); 150 | that.infoWindow_.close(); 151 | }); 152 | }; 153 | 154 | /** 155 | * Adds/remove hooks as appropriate. 156 | */ 157 | storeLocator.View.prototype.updateOnPan_changed = function() { 158 | if (this.updateOnPanListener_) { 159 | google.maps.event.removeListener(this.updateOnPanListener_); 160 | } 161 | 162 | if (this.get('updateOnPan') && this.getMap()) { 163 | var that = this; 164 | var map = this.getMap(); 165 | this.updateOnPanListener_ = google.maps.event.addListener(map, 'idle', 166 | function() { 167 | that.refreshView(); 168 | }); 169 | } 170 | }; 171 | 172 | /** 173 | * Add a store to the map. 174 | * @param {storeLocator.Store} store the store to add. 175 | */ 176 | storeLocator.View.prototype.addStoreToMap = function(store) { 177 | var marker = this.getMarker(store); 178 | store.setMarker(marker); 179 | var that = this; 180 | 181 | marker.clickListener_ = google.maps.event.addListener(marker, 'click', 182 | function() { 183 | that.highlight(store, false); 184 | }); 185 | 186 | if (marker.getMap() != this.getMap()) { 187 | marker.setMap(this.getMap()); 188 | } 189 | }; 190 | 191 | /** 192 | * Create a marker for a store. 193 | * @param {storeLocator.Store} store the store to produce a marker for. 194 | * @this storeLocator.View 195 | * @return {google.maps.Marker} a new marker. 196 | * @export 197 | */ 198 | storeLocator.View.prototype.createMarker = function(store) { 199 | var markerOptions = { 200 | position: store.getLocation() 201 | }; 202 | var opt_icon = this.settings_['markerIcon']; 203 | if (opt_icon) { 204 | markerOptions.icon = opt_icon; 205 | } 206 | return new google.maps.Marker(markerOptions); 207 | }; 208 | 209 | /** 210 | * Get a marker for a store. By default, this caches the value from 211 | * createMarker(store) 212 | * @param {storeLocator.Store} store the store to get the marker from. 213 | * @return {google.maps.Marker} the marker. 214 | */ 215 | storeLocator.View.prototype.getMarker = function(store) { 216 | var cache = this.markerCache_; 217 | var key = store.getId(); 218 | if (!cache[key]) { 219 | cache[key] = this['createMarker'](store); 220 | } 221 | return cache[key]; 222 | }; 223 | 224 | /** 225 | * Get a InfoWindow for a particular store. 226 | * @param {storeLocator.Store} store the store. 227 | * @return {google.maps.InfoWindow} the store's InfoWindow. 228 | */ 229 | storeLocator.View.prototype.getInfoWindow = function(store) { 230 | if (!store) { 231 | return this.infoWindow_; 232 | } 233 | 234 | var div = $(store.getInfoWindowContent()); 235 | this.infoWindow_.setContent(div[0]); 236 | return this.infoWindow_; 237 | }; 238 | 239 | /** 240 | * Gets all possible features for this View. 241 | * @return {storeLocator.FeatureSet} All possible features. 242 | */ 243 | storeLocator.View.prototype.getFeatures = function() { 244 | return this.settings_['features']; 245 | }; 246 | 247 | /** 248 | * Gets a feature by its id. Convenience method. 249 | * @param {string} id the feature's id. 250 | * @return {storeLocator.Feature|undefined} The feature, if the id is valid. 251 | * undefined if not. 252 | */ 253 | storeLocator.View.prototype.getFeatureById = function(id) { 254 | if (!this.featureById_) { 255 | this.featureById_ = {}; 256 | for (var i = 0, feature; feature = this.settings_['features'][i]; i++) { 257 | this.featureById_[feature.getId()] = feature; 258 | } 259 | } 260 | return this.featureById_[id]; 261 | }; 262 | 263 | /** 264 | * featureFilter_changed event handler. 265 | */ 266 | storeLocator.View.prototype.featureFilter_changed = function() { 267 | google.maps.event.trigger(this, 'featureFilter_changed', 268 | this.get('featureFilter')); 269 | 270 | if (this.get('stores')) { 271 | this.clearMarkers(); 272 | } 273 | }; 274 | 275 | /** 276 | * Clears the visible markers on the map. 277 | */ 278 | storeLocator.View.prototype.clearMarkers = function() { 279 | for (var marker in this.markerCache_) { 280 | this.markerCache_[marker].setMap(null); 281 | var listener = this.markerCache_[marker].clickListener_; 282 | if (listener) { 283 | google.maps.event.removeListener(listener); 284 | } 285 | } 286 | }; 287 | 288 | /** 289 | * Refresh the map's view. This will fetch new data based on the map's bounds. 290 | */ 291 | storeLocator.View.prototype.refreshView = function() { 292 | var that = this; 293 | 294 | this.data_.getStores(this.getMap().getBounds(), 295 | /** @type {storeLocator.FeatureSet} */ (this.get('featureFilter')), 296 | function(stores) { 297 | var oldStores = that.get('stores'); 298 | if (oldStores) { 299 | for (var i = 0, ii = oldStores.length; i < ii; i++) { 300 | google.maps.event.removeListener( 301 | oldStores[i].getMarker().clickListener_); 302 | } 303 | } 304 | that.set('stores', stores); 305 | }); 306 | }; 307 | 308 | /** 309 | * stores_changed event handler. 310 | * This will display all new stores on the map. 311 | * @this storeLocator.View 312 | */ 313 | storeLocator.View.prototype.stores_changed = function() { 314 | var stores = this.get('stores'); 315 | for (var i = 0, store; store = stores[i]; i++) { 316 | this.addStoreToMap(store); 317 | } 318 | }; 319 | 320 | /** 321 | * Gets the view's Map. 322 | * @return {google.maps.Map} the view's Map. 323 | */ 324 | storeLocator.View.prototype.getMap = function() { 325 | return this.map_; 326 | }; 327 | 328 | /** 329 | * Select a particular store. 330 | * @param {storeLocator.Store} store the store to highlight. 331 | * @param {boolean=} opt_pan if panning to the store on the map is desired. 332 | */ 333 | storeLocator.View.prototype.highlight = function(store, opt_pan) { 334 | var infoWindow = this.getInfoWindow(store); 335 | if (store) { 336 | var infoWindow = this.getInfoWindow(store); 337 | if (store.getMarker()) { 338 | infoWindow.open(this.getMap(), store.getMarker()); 339 | } else { 340 | infoWindow.setPosition(store.getLocation()); 341 | infoWindow.open(this.getMap()); 342 | } 343 | if (opt_pan) { 344 | this.getMap().panTo(store.getLocation()); 345 | } 346 | if (this.getMap().getStreetView().getVisible()) { 347 | this.getMap().getStreetView().setPosition(store.getLocation()); 348 | } 349 | } else { 350 | infoWindow.close(); 351 | } 352 | 353 | this.set('selectedStore', store); 354 | }; 355 | 356 | /** 357 | * Re-triggers the selectedStore_changed event with the store as a parameter. 358 | * @this storeLocator.View 359 | */ 360 | storeLocator.View.prototype.selectedStore_changed = function() { 361 | google.maps.event.trigger(this, 'selectedStore_changed', 362 | this.get('selectedStore')); 363 | }; 364 | 365 | /** 366 | * Fired when the View is loaded. This happens once immediately, 367 | * then once more if geolocation is successful. 368 | * @name storeLocator.View#event:load 369 | * @event 370 | */ 371 | 372 | /** 373 | * Fired when the View's featureFilter property 374 | * changes. 375 | * @name storeLocator.View#event:featureFilter_changed 376 | * @event 377 | */ 378 | 379 | /** 380 | * Fired when the View's updateOnPan property changes. 381 | * @name storeLocator.View#event:updateOnPan_changed 382 | * @event 383 | */ 384 | 385 | /** 386 | * Fired when the View's stores property changes. 387 | * @name storeLocator.View#event:stores_changed 388 | * @event 389 | */ 390 | 391 | /** 392 | * Fired when the View's selectedStore property 393 | * changes. This happens after highlight() is called. 394 | * @name storeLocator.View#event:selectedStore_changed 395 | * @param {storeLocator.Store} store 396 | * @event 397 | */ 398 | 399 | /** 400 | * @example see storeLocator.View 401 | * @interface 402 | */ 403 | storeLocator.ViewOptions = function() {}; 404 | 405 | /** 406 | * Whether the map should update stores in the visible area when the visible 407 | * area changes. refreshView() will need to be called 408 | * programatically. Defaults to true. 409 | * @type boolean 410 | */ 411 | storeLocator.ViewOptions.prototype.updateOnPan; 412 | 413 | /** 414 | * Whether the store locator should attempt to determine the user's location 415 | * for the initial view. Defaults to true. 416 | * @type boolean 417 | */ 418 | storeLocator.ViewOptions.prototype.geolocation; 419 | 420 | /** 421 | * All available store features. Defaults to empty FeatureSet. 422 | * @type storeLocator.FeatureSet 423 | */ 424 | storeLocator.ViewOptions.prototype.features; 425 | 426 | /** 427 | * The icon to use for markers representing stores. 428 | * @type string|google.maps.MarkerImage 429 | */ 430 | storeLocator.ViewOptions.prototype.markerIcon; 431 | -------------------------------------------------------------------------------- /examples/infobubble-compiled.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | /** 3 | * Copyright 2014 Google Inc. All Rights Reserved. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | var b=void 0,g; 18 | function k(a){this.extend(k,google.maps.OverlayView);this.b=[];this.d=null;this.g=100;this.m=!1;a=a||{};if(a.backgroundColor==b)a.backgroundColor=this.z;if(a.borderColor==b)a.borderColor=this.A;if(a.borderRadius==b)a.borderRadius=this.B;if(a.borderWidth==b)a.borderWidth=this.C;if(a.padding==b)a.padding=this.F;if(a.arrowPosition==b)a.arrowPosition=this.u;a.disableAutoPan==b&&(a.disableAutoPan=!1);a.disableAnimation==b&&(a.disableAnimation=!1);if(a.minWidth==b)a.minWidth=this.D;if(a.shadowStyle==b)a.shadowStyle= 19 | this.G;if(a.arrowSize==b)a.arrowSize=this.v;if(a.arrowStyle==b)a.arrowStyle=this.w;l(this);this.setValues(a)}window.InfoBubble=k;g=k.prototype;g.v=15;g.w=0;g.G=1;g.D=50;g.u=50;g.F=10;g.C=1;g.A="#ccc";g.B=10;g.z="#fff";g.extend=function(a,c){return function(a){for(var c in a.prototype)this.prototype[c]=a.prototype[c];return this}.apply(a,[c])}; 20 | function l(a){var c=a.c=document.createElement("DIV");c.style.position="absolute";c.style.zIndex=a.g;(a.i=document.createElement("DIV")).style.position="relative";var d=a.l=document.createElement("IMG");d.style.position="absolute";d.style.width=n(12);d.style.height=n(12);d.style.border=0;d.style.zIndex=a.g+1;d.style.cursor="pointer";d.src="https://maps.gstatic.com/intl/en_us/mapfiles/iw_close.gif";google.maps.event.addDomListener(d,"click",function(){a.close();google.maps.event.trigger(a,"closeclick")}); 21 | var e=a.e=document.createElement("DIV");e.style.overflowX="auto";e.style.overflowY="auto";e.style.cursor="default";e.style.clear="both";e.style.position="relative";var f=a.j=document.createElement("DIV");e.appendChild(f);f=a.L=document.createElement("DIV");f.style.position="relative";var i=a.n=document.createElement("DIV"),h=a.k=document.createElement("DIV"),j=q(a);i.style.position=h.style.position="absolute";i.style.left=h.style.left="50%";i.style.height=h.style.height="0";i.style.width=h.style.width= 22 | "0";i.style.marginLeft=n(-j);i.style.borderWidth=n(j);i.style.borderBottomWidth=0;j=a.a=document.createElement("DIV");j.style.position="absolute";c.style.display=j.style.display="none";c.appendChild(a.i);c.appendChild(d);c.appendChild(e);f.appendChild(i);f.appendChild(h);c.appendChild(f);c=document.createElement("style");c.setAttribute("type","text/css");a.h="_ibani_"+Math.round(1E4*Math.random());c.textContent="."+a.h+"{-webkit-animation-name:"+a.h+";-webkit-animation-duration:0.5s;-webkit-animation-iteration-count:1;}@-webkit-keyframes "+ 23 | a.h+" {from {-webkit-transform: scale(0)}50% {-webkit-transform: scale(1.2)}90% {-webkit-transform: scale(0.95)}to {-webkit-transform: scale(1)}}";document.getElementsByTagName("head")[0].appendChild(c)}g.ca=function(a){this.set("backgroundClassName",a)};k.prototype.setBackgroundClassName=k.prototype.ca;k.prototype.M=function(){this.j.className=this.get("backgroundClassName")};k.prototype.backgroundClassName_changed=k.prototype.M;k.prototype.oa=function(a){this.set("tabClassName",a)}; 24 | k.prototype.setTabClassName=k.prototype.oa;k.prototype.ra=function(){t(this)};k.prototype.tabClassName_changed=k.prototype.ra;k.prototype.ba=function(a){this.set("arrowStyle",a)};k.prototype.setArrowStyle=k.prototype.ba;k.prototype.K=function(){this.p()};k.prototype.arrowStyle_changed=k.prototype.K;function q(a){return parseInt(a.get("arrowSize"),10)||0}k.prototype.aa=function(a){this.set("arrowSize",a)};k.prototype.setArrowSize=k.prototype.aa;k.prototype.p=function(){this.r()}; 25 | k.prototype.arrowSize_changed=k.prototype.p;k.prototype.$=function(a){this.set("arrowPosition",a)};k.prototype.setArrowPosition=k.prototype.$;k.prototype.J=function(){this.n.style.left=this.k.style.left=(parseInt(this.get("arrowPosition"),10)||0)+"%";u(this)};k.prototype.arrowPosition_changed=k.prototype.J;k.prototype.setZIndex=function(a){this.set("zIndex",a)};k.prototype.setZIndex=k.prototype.setZIndex;k.prototype.getZIndex=function(){return parseInt(this.get("zIndex"),10)||this.g}; 26 | k.prototype.ta=function(){var a=this.getZIndex();this.c.style.zIndex=this.g=a;this.l.style.zIndex=a+1};k.prototype.zIndex_changed=k.prototype.ta;k.prototype.ma=function(a){this.set("shadowStyle",a)};k.prototype.setShadowStyle=k.prototype.ma; 27 | k.prototype.pa=function(){var a="",c="",d="";switch(parseInt(this.get("shadowStyle"),10)||0){case 0:a="none";break;case 1:c="40px 15px 10px rgba(33,33,33,0.3)";d="transparent";break;case 2:c="0 0 2px rgba(33,33,33,0.3)",d="rgba(33,33,33,0.35)"}this.a.style.boxShadow=this.a.style.webkitBoxShadow=this.a.style.MozBoxShadow=c;this.a.style.backgroundColor=d;if(this.m)this.a.style.display=a,this.draw()};k.prototype.shadowStyle_changed=k.prototype.pa; 28 | k.prototype.qa=function(){this.set("hideCloseButton",!1)};k.prototype.showCloseButton=k.prototype.qa;k.prototype.P=function(){this.set("hideCloseButton",!0)};k.prototype.hideCloseButton=k.prototype.P;k.prototype.Q=function(){this.l.style.display=this.get("hideCloseButton")?"none":""};k.prototype.hideCloseButton_changed=k.prototype.Q;k.prototype.da=function(a){a&&this.set("backgroundColor",a)};k.prototype.setBackgroundColor=k.prototype.da; 29 | k.prototype.N=function(){var a=this.get("backgroundColor");this.e.style.backgroundColor=a;this.k.style.borderColor=a+" transparent transparent";t(this)};k.prototype.backgroundColor_changed=k.prototype.N;k.prototype.ea=function(a){a&&this.set("borderColor",a)};k.prototype.setBorderColor=k.prototype.ea; 30 | k.prototype.O=function(){var a=this.get("borderColor"),c=this.e,d=this.n;c.style.borderColor=a;d.style.borderColor=a+" transparent transparent";c.style.borderStyle=d.style.borderStyle=this.k.style.borderStyle="solid";t(this)};k.prototype.borderColor_changed=k.prototype.O;k.prototype.fa=function(a){this.set("borderRadius",a)};k.prototype.setBorderRadius=k.prototype.fa;function w(a){return parseInt(a.get("borderRadius"),10)||0} 31 | k.prototype.q=function(){var a=w(this),c=x(this);this.e.style.borderRadius=this.e.style.MozBorderRadius=this.e.style.webkitBorderRadius=this.a.style.borderRadius=this.a.style.MozBorderRadius=this.a.style.webkitBorderRadius=n(a);this.i.style.paddingLeft=this.i.style.paddingRight=n(a+c);u(this)};k.prototype.borderRadius_changed=k.prototype.q;function x(a){return parseInt(a.get("borderWidth"),10)||0}k.prototype.ga=function(a){this.set("borderWidth",a)};k.prototype.setBorderWidth=k.prototype.ga; 32 | k.prototype.r=function(){var a=x(this);this.e.style.borderWidth=n(a);this.i.style.top=n(a);var a=x(this),c=q(this),d=parseInt(this.get("arrowStyle"),10)||0,e=n(c),f=n(Math.max(0,c-a)),i=this.n,h=this.k;this.L.style.marginTop=n(-a);i.style.borderTopWidth=e;h.style.borderTopWidth=f;0==d||1==d?(i.style.borderLeftWidth=e,h.style.borderLeftWidth=f):i.style.borderLeftWidth=h.style.borderLeftWidth=0;0==d||2==d?(i.style.borderRightWidth=e,h.style.borderRightWidth=f):i.style.borderRightWidth=h.style.borderRightWidth= 33 | 0;2>d?(i.style.marginLeft=n(-c),h.style.marginLeft=n(-(c-a))):i.style.marginLeft=h.style.marginLeft=0;i.style.display=0==a?"none":"";t(this);this.q();u(this)};k.prototype.borderWidth_changed=k.prototype.r;k.prototype.la=function(a){this.set("padding",a)};k.prototype.setPadding=k.prototype.la;function y(a){return parseInt(a.get("padding"),10)||0}k.prototype.X=function(){this.e.style.padding=n(y(this));t(this);u(this)};k.prototype.padding_changed=k.prototype.X;function n(a){return a?a+"px":a} 34 | function z(a){var c="mousedown,mousemove,mouseover,mouseout,mouseup,mousewheel,DOMMouseScroll,touchstart,touchend,touchmove,dblclick,contextmenu,click".split(","),d=a.c;a.s=[];for(var e=0,f;f=c[e];e++)a.s.push(google.maps.event.addDomListener(d,f,function(a){a.cancelBubble=!0;a.stopPropagation&&a.stopPropagation()}))}k.prototype.onAdd=function(){this.c||l(this);z(this);var a=this.getPanes();a&&(a.floatPane.appendChild(this.c),a.floatShadow.appendChild(this.a))};k.prototype.onAdd=k.prototype.onAdd; 35 | k.prototype.draw=function(){var a=this.getProjection();if(a){var c=this.get("position");if(c){var d=0;if(this.d)d=this.d.offsetHeight;var e=A(this),f=q(this),i=parseInt(this.get("arrowPosition"),10)||0,i=i/100,a=a.fromLatLngToDivPixel(c);if(c=this.e.offsetWidth){var h=a.y-(this.c.offsetHeight+f);e&&(h-=e);var j=a.x-c*i;this.c.style.top=n(h);this.c.style.left=n(j);switch(parseInt(this.get("shadowStyle"),10)){case 1:this.a.style.top=n(h+d-1);this.a.style.left=n(j);this.a.style.width=n(c);this.a.style.height= 36 | n(this.e.offsetHeight-f);break;case 2:c*=0.8,this.a.style.top=e?n(a.y):n(a.y+f),this.a.style.left=n(a.x-c*i),this.a.style.width=n(c),this.a.style.height=n(2)}}}else this.close()}};k.prototype.draw=k.prototype.draw;k.prototype.onRemove=function(){this.c&&this.c.parentNode&&this.c.parentNode.removeChild(this.c);this.a&&this.a.parentNode&&this.a.parentNode.removeChild(this.a);for(var a=0,c;c=this.s[a];a++)google.maps.event.removeListener(c)};k.prototype.onRemove=k.prototype.onRemove;k.prototype.R=function(){return this.m}; 37 | k.prototype.isOpen=k.prototype.R;k.prototype.close=function(){if(this.c)this.c.style.display="none",this.c.className=this.c.className.replace(this.h,"");if(this.a)this.a.style.display="none",this.a.className=this.a.className.replace(this.h,"");this.m=!1};k.prototype.close=k.prototype.close;k.prototype.open=function(a,c){var d=this;window.setTimeout(function(){B(d,a,c)},0)}; 38 | function B(a,c,d){C(a);c&&a.setMap(c);d&&(a.set("anchor",d),a.bindTo("anchorPoint",d),a.bindTo("position",d));a.c.style.display=a.a.style.display="";a.get("disableAnimation")||(a.c.className+=" "+a.h,a.a.className+=" "+a.h);u(a);a.m=!0;a.get("disableAutoPan")||window.setTimeout(function(){a.o()},200)}k.prototype.open=k.prototype.open;k.prototype.setPosition=function(a){a&&this.set("position",a)};k.prototype.setPosition=k.prototype.setPosition;k.prototype.getPosition=function(){return this.get("position")}; 39 | k.prototype.getPosition=k.prototype.getPosition;k.prototype.Y=function(){this.draw()};k.prototype.position_changed=k.prototype.Y;k.prototype.o=function(){var a=this.getProjection();if(a&&this.c){var c=this.c.offsetHeight+A(this),d=this.get("map"),e=d.getDiv().offsetHeight,f=this.getPosition(),i=a.fromLatLngToContainerPixel(d.getCenter()),f=a.fromLatLngToContainerPixel(f),c=i.y-c,e=e-i.y,i=0;0>c&&(i=(-1*c+e)/2);f.y-=i;f=a.fromContainerPixelToLatLng(f);d.getCenter()!=f&&d.panTo(f)}}; 40 | k.prototype.panToView=k.prototype.o;function D(a){var a=a.replace(/^\s*([\S\s]*)\b\s*$/,"$1"),c=document.createElement("DIV");c.innerHTML=a;if(1==c.childNodes.length)return c.removeChild(c.firstChild);for(a=document.createDocumentFragment();c.firstChild;)a.appendChild(c.firstChild);return a}function E(a){if(a)for(var c;c=a.firstChild;)a.removeChild(c)}k.prototype.setContent=function(a){this.set("content",a)};k.prototype.setContent=k.prototype.setContent;k.prototype.getContent=function(){return this.get("content")}; 41 | k.prototype.getContent=k.prototype.getContent;function C(a){if(a.j){E(a.j);var c=a.getContent();if(c){"string"==typeof c&&(c=D(c));a.j.appendChild(c);for(var c=a.j.getElementsByTagName("IMG"),d=0,e;e=c[d];d++)google.maps.event.addDomListener(e,"load",function(){var c=!a.get("disableAutoPan");u(a);c&&(0==a.b.length||0==a.d.index)&&a.o()});google.maps.event.trigger(a,"domready")}u(a)}} 42 | function t(a){if(a.b&&a.b.length){for(var c=0,d;d=a.b[c];c++)F(a,d.f);a.d.style.zIndex=a.g;c=x(a);d=y(a)/2;a.d.style.borderBottomWidth=0;a.d.style.paddingBottom=n(d+c)}} 43 | function F(a,c){var d=a.get("backgroundColor"),e=a.get("borderColor"),f=w(a),i=x(a),h=y(a),j=n(-Math.max(h,f)),f=n(f),p=a.g;c.index&&(p-=c.index);var d={cssFloat:"left",position:"relative",cursor:"pointer",backgroundColor:d,border:n(i)+" solid "+e,padding:n(h/2)+" "+n(h),marginRight:j,whiteSpace:"nowrap",borderRadiusTopLeft:f,MozBorderRadiusTopleft:f,webkitBorderTopLeftRadius:f,borderRadiusTopRight:f,MozBorderRadiusTopright:f,webkitBorderTopRightRadius:f,zIndex:p,display:"inline"},m;for(m in d)c.style[m]= 44 | d[m];m=a.get("tabClassName");m!=b&&(c.className+=" "+m)}function G(a,c){c.S=google.maps.event.addDomListener(c,"click",function(){H(a,this)})}k.prototype.na=function(a){(a=this.b[a-1])&&H(this,a.f)};k.prototype.setTabActive=k.prototype.na; 45 | function H(a,c){if(c){var d=y(a)/2,e=x(a);if(a.d){var f=a.d;f.style.zIndex=a.g-f.index;f.style.paddingBottom=n(d);f.style.borderBottomWidth=n(e)}c.style.zIndex=a.g;c.style.borderBottomWidth=0;c.style.marginBottomWidth="-10px";c.style.paddingBottom=n(d+e);a.setContent(a.b[c.index].content);C(a);a.d=c;u(a)}else a.setContent(""),C(a)}k.prototype.ia=function(a){this.set("maxWidth",a)};k.prototype.setMaxWidth=k.prototype.ia;k.prototype.U=function(){u(this)};k.prototype.maxWidth_changed=k.prototype.U; 46 | k.prototype.ha=function(a){this.set("maxHeight",a)};k.prototype.setMaxHeight=k.prototype.ha;k.prototype.T=function(){u(this)};k.prototype.maxHeight_changed=k.prototype.T;k.prototype.ka=function(a){this.set("minWidth",a)};k.prototype.setMinWidth=k.prototype.ka;k.prototype.W=function(){u(this)};k.prototype.minWidth_changed=k.prototype.W;k.prototype.ja=function(a){this.set("minHeight",a)};k.prototype.setMinHeight=k.prototype.ja;k.prototype.V=function(){u(this)};k.prototype.minHeight_changed=k.prototype.V; 47 | k.prototype.H=function(a,c){var d=document.createElement("DIV");d.innerHTML=a;F(this,d);G(this,d);this.i.appendChild(d);this.b.push({label:a,content:c,f:d});d.index=this.b.length-1;d.style.zIndex=this.g-d.index;this.d||H(this,d);d.className=d.className+" "+this.h;u(this)};k.prototype.addTab=k.prototype.H;k.prototype.sa=function(a,c,d){if(this.b.length&&!(0>a||a>=this.b.length)){a=this.b[a];if(c!=b)a.f.innerHTML=a.label=c;if(d!=b)a.content=d;this.d==a.f&&(this.setContent(a.content),C(this));u(this)}}; 48 | k.prototype.updateTab=k.prototype.sa;k.prototype.Z=function(a){if(this.b.length&&!(0>a||a>=this.b.length)){var c=this.b[a];c.f.parentNode.removeChild(c.f);google.maps.event.removeListener(c.f.S);this.b.splice(a,1);delete c;for(var d=0,e;e=this.b[d];d++)e.f.index=d;if(c.f==this.d)this.d=this.b[a]?this.b[a].f:this.b[a-1]?this.b[a-1].f:b,H(this,this.d);u(this)}};k.prototype.removeTab=k.prototype.Z; 49 | function I(a,c,d){var e=document.createElement("DIV");e.style.display="inline";e.style.position="absolute";e.style.visibility="hidden";"string"==typeof a?e.innerHTML=a:e.appendChild(a.cloneNode(!0));document.body.appendChild(e);a=new google.maps.Size(e.offsetWidth,e.offsetHeight);if(c&&a.width>c)e.style.width=n(c),a=new google.maps.Size(e.offsetWidth,e.offsetHeight);if(d&&a.height>d)e.style.height=n(d),a=new google.maps.Size(e.offsetWidth,e.offsetHeight);document.body.removeChild(e);delete e;return a} 50 | function u(a){var c=a.get("map");if(c){var d=y(a);x(a);w(a);var e=q(a),f=c.getDiv(),i=2*e,c=f.offsetWidth-i,f=f.offsetHeight-i-A(a),i=0,h=a.get("minWidth")||0,j=a.get("minHeight")||0,p=a.get("maxWidth")||0,m=a.get("maxHeight")||0,p=Math.min(c,p),m=Math.min(f,m),v=0;if(a.b.length)for(var r=0,o;o=a.b[r];r++){var s=I(o.f,p,m);o=I(o.content,p,m);if(hi)i=s.height;if(hc&&(h=c);j>f&&(j=f-i);if(a.i)a.t=i,a.i.style.width=n(v);a.e.style.width=n(h);a.e.style.height=n(j)}w(a);d=x(a);c=2;a.b.length&&a.t&&(c+=a.t);e=2+d;(f=a.e)&&f.clientHeight 28 | * var container = document.getElementById('panel'); 29 | * var panel = new storeLocator.Panel(container, { 30 | * view: view, 31 | * locationSearchLabel: 'Location:' 32 | * }); 33 | * google.maps.event.addListener(panel, 'geocode', function(result) { 34 | * geocodeMarker.setPosition(result.geometry.location); 35 | * }); 36 | * 37 | * @extends {google.maps.MVCObject} 38 | * @param {!Node} el the element to contain this panel. 39 | * @param {storeLocator.PanelOptions} opt_options 40 | * @constructor 41 | * @implements storeLocator_Panel 42 | */ 43 | storeLocator.Panel = function(el, opt_options) { 44 | this.el_ = $(el); 45 | this.el_.addClass('storelocator-panel'); 46 | this.settings_ = $.extend({ 47 | 'locationSearch': true, 48 | 'locationSearchLabel': 'Where are you?', 49 | 'featureFilter': true, 50 | 'directions': true, 51 | 'view': null 52 | }, opt_options); 53 | 54 | this.directionsRenderer_ = new google.maps.DirectionsRenderer({ 55 | draggable: true 56 | }); 57 | this.directionsService_ = new google.maps.DirectionsService; 58 | 59 | this.init_(); 60 | }; 61 | storeLocator['Panel'] = storeLocator.Panel; 62 | 63 | storeLocator.Panel.prototype = new google.maps.MVCObject; 64 | 65 | /** 66 | * Initialise the info panel 67 | * @private 68 | */ 69 | storeLocator.Panel.prototype.init_ = function() { 70 | var that = this; 71 | this.itemCache_ = {}; 72 | 73 | if (this.settings_['view']) { 74 | this.set('view', this.settings_['view']); 75 | } 76 | 77 | this.filter_ = $('
    '); 78 | this.el_.append(this.filter_); 79 | 80 | if (this.settings_['locationSearch']) { 81 | this.locationSearch_ = $(''); 83 | this.filter_.append(this.locationSearch_); 84 | 85 | if (typeof google.maps.places != 'undefined') { 86 | this.initAutocomplete_(); 87 | } else { 88 | this.filter_.submit(function() { 89 | var search = $('input', that.locationSearch_).val(); 90 | that.searchPosition(/** @type {string} */(search)); 91 | }); 92 | } 93 | this.filter_.submit(function() { 94 | return false; 95 | }); 96 | 97 | google.maps.event.addListener(this, 'geocode', function(place) { 98 | if (!place.geometry) { 99 | that.searchPosition(place.name); 100 | return; 101 | } 102 | 103 | this.directionsFrom_ = place.geometry.location; 104 | 105 | if (that.directionsVisible_) { 106 | that.renderDirections_(); 107 | } 108 | var sl = that.get('view'); 109 | sl.highlight(null); 110 | var map = sl.getMap(); 111 | if (place.geometry.viewport) { 112 | map.fitBounds(place.geometry.viewport); 113 | } else { 114 | map.setCenter(place.geometry.location); 115 | map.setZoom(13); 116 | } 117 | sl.refreshView(); 118 | that.listenForStoresUpdate_(); 119 | }); 120 | } 121 | 122 | if (this.settings_['featureFilter']) { 123 | // TODO(cbro): update this on view_changed 124 | this.featureFilter_ = $('
    '); 125 | var allFeatures = this.get('view').getFeatures().asList(); 126 | for (var i = 0, ii = allFeatures.length; i < ii; i++) { 127 | var feature = allFeatures[i]; 128 | var checkbox = $(''); 129 | checkbox.data('feature', feature); 130 | $('