Flexible Map - Version 1.17.0

Version Description

Download this release

Release Info

Developer webaware
Plugin Icon 128x128 Flexible Map
Version 1.17.0
Comparing to
See all releases

Code changes from version 1.16.0 to 1.17.0

changelog.md CHANGED
@@ -2,6 +2,15 @@
2
 
3
  ## Changelog
4
 
 
 
 
 
 
 
 
 
 
5
  ### 1.16.0
6
 
7
  Released 2018-09-07
2
 
3
  ## Changelog
4
 
5
+ ### 1.17.0
6
+
7
+ Released 2018-11-19
8
+
9
+ * fixed: map tiles don't redraw for KML maps with zoom when hidden in a tab / accordion
10
+ * changed: use the [current quarterly (stable) version of the Google Maps API](https://developers.google.com/maps/documentation/javascript/versions)
11
+ * changed: remove support for ancient browsers (Opera 12, IE < 11)
12
+ * tested: WordPress 5.0 (no Gutenberg block yet; maybe next release!)
13
+
14
  ### 1.16.0
15
 
16
  Released 2018-09-07
flexible-map.php CHANGED
@@ -3,7 +3,7 @@
3
  Plugin Name: Flexible Map
4
  Plugin URI: https://flexible-map.webaware.net.au/
5
  Description: Embed Google Maps shortcodes in pages and posts, either by centre coordinates or street address, or by URL to a Google Earth KML file. <a href="https://flexible-map.webaware.net.au/manual/getting-started/">Get started</a> with a simple shortcode. See the <a href="https://flexible-map.webaware.net.au/manual/attribute-reference/">complete attribute reference</a> for more details.
6
- Version: 1.16.0
7
  Author: WebAware
8
  Author URI: https://shop.webaware.com.au/
9
  Text Domain: wp-flexible-map
@@ -37,7 +37,7 @@ define('FLXMAP_PLUGIN_FILE', __FILE__);
37
  define('FLXMAP_PLUGIN_ROOT', dirname(__FILE__) . '/');
38
  define('FLXMAP_PLUGIN_NAME', basename(dirname(__FILE__)) . '/' . basename(__FILE__));
39
  define('FLXMAP_PLUGIN_OPTIONS', 'flexible_map');
40
- define('FLXMAP_PLUGIN_VERSION', '1.16.0');
41
 
42
  // shortcode tags
43
  define('FLXMAP_PLUGIN_TAG_MAP', 'flexiblemap');
3
  Plugin Name: Flexible Map
4
  Plugin URI: https://flexible-map.webaware.net.au/
5
  Description: Embed Google Maps shortcodes in pages and posts, either by centre coordinates or street address, or by URL to a Google Earth KML file. <a href="https://flexible-map.webaware.net.au/manual/getting-started/">Get started</a> with a simple shortcode. See the <a href="https://flexible-map.webaware.net.au/manual/attribute-reference/">complete attribute reference</a> for more details.
6
+ Version: 1.17.0
7
  Author: WebAware
8
  Author URI: https://shop.webaware.com.au/
9
  Text Domain: wp-flexible-map
37
  define('FLXMAP_PLUGIN_ROOT', dirname(__FILE__) . '/');
38
  define('FLXMAP_PLUGIN_NAME', basename(dirname(__FILE__)) . '/' . basename(__FILE__));
39
  define('FLXMAP_PLUGIN_OPTIONS', 'flexible_map');
40
+ define('FLXMAP_PLUGIN_VERSION', '1.17.0');
41
 
42
  // shortcode tags
43
  define('FLXMAP_PLUGIN_TAG_MAP', 'flexiblemap');
includes/class.FlxMapPlugin.php CHANGED
@@ -87,7 +87,7 @@ class FlxMapPlugin {
87
  $options = get_option(FLXMAP_PLUGIN_OPTIONS, array());
88
 
89
  if (empty($options['noAPI'])) {
90
- $args = array('v' => '3.32');
91
  if (!empty($options['apiKey'])) {
92
  $args['key'] = $options['apiKey'];
93
  }
@@ -573,8 +573,11 @@ HTML;
573
  throw new Exception("error decoding JSON\n" . $response['body']);
574
  }
575
 
576
- if ($result->status != 'OK') {
577
- throw new Exception(sprintf("error retrieving address: %s; %s", $result->status, $result->error_message));
 
 
 
578
  }
579
 
580
  // success, return array with latitude and longitude
87
  $options = get_option(FLXMAP_PLUGIN_OPTIONS, array());
88
 
89
  if (empty($options['noAPI'])) {
90
+ $args = array('v' => 'quarterly');
91
  if (!empty($options['apiKey'])) {
92
  $args['key'] = $options['apiKey'];
93
  }
573
  throw new Exception("error decoding JSON\n" . $response['body']);
574
  }
575
 
576
+ if ($result->status !== 'OK') {
577
+ if (!empty($result->error_message)) {
578
+ throw new Exception(sprintf("error retrieving address: %s; %s", $result->status, $result->error_message));
579
+ }
580
+ throw new Exception(sprintf("error retrieving address: %s", $result->status));
581
  }
582
 
583
  // success, return array with latitude and longitude
js/flexible-map.js CHANGED
@@ -1,993 +1,986 @@
 
 
1
  /*
2
  JavaScript for the WordPress plugin wp-flexible-map
3
  https://flexible-map.webaware.net.au/
4
  */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5
 
6
- window.FlexibleMap = function() {
7
- "use strict";
8
-
9
- // instance-private members with accessors
10
- var map, // google.maps.Map object
11
- centre, // google.maps.LatLng object for map centre
12
- markerLocation, // google.maps.LatLng object for single marker, when using showMarker()
13
- markerPoint, // google.maps.Marker object for single marker, when using showMarker()
14
- markerInfowin, // google.maps.InfoWindow object for single marker, when using showMarker()
15
- kmlLayer, // if map has a KML layer, this is the layer object
16
- hasRedrawn = false; // boolean, whether map has been asked to redrawOnce() already
17
-
18
- /**
19
- * set gesture handling options, with legacy support for draggable, dblclickZoom, scrollwheel
20
- * set to "cooperative" if none of the settings are given
21
- * @link https://developers.google.com/maps/documentation/javascript/reference/map#MapOptions.gestureHandling
22
- * @param {Object} mapOptions
23
- * @param {FlexibleMap} flexibleMap
24
- * @return {Object}
25
- */
26
- function getGestureHandling(mapOptions, flexibleMap) {
27
- if (flexibleMap.gestureHandling !== undefined) {
28
- mapOptions.gestureHandling = flexibleMap.gestureHandling;
29
- }
30
- else if (flexibleMap.draggable === undefined && flexibleMap.dblclickZoom === undefined && flexibleMap.scrollwheel === undefined) {
31
- mapOptions.gestureHandling = "cooperative";
32
- }
33
- else {
34
- // legacy support; deprecated
35
- var draggable = (flexibleMap.draggable === undefined) ? true : flexibleMap.draggable; // default true, i.e. enable draggable
36
- var disableDblclickZoom = (flexibleMap.dblclickZoom === undefined) ? false : !flexibleMap.draggable; // default true, i.e. enable double-click zoom
37
- var scrollwheel = (flexibleMap.scrollwheel === undefined) ? false : flexibleMap.scrollwheel; // default false, i.e. dinable scrollwheel
38
-
39
- // ----------------+-----------+------------------------+------------
40
- // gestureHandling | draggable | disableDoubleClickZoom | scrollwheel
41
- // ----------------+-----------+------------------------+------------
42
- // cooperative | true | false | ctrl+ |
43
- // greedy | true | false | true |
44
- // auto | ???? | false | ???? |
45
- // none | false | true | false |
46
- // ----------------+-----------+------------------------+------------
47
-
48
- if (!draggable && disableDblclickZoom && !scrollwheel) {
49
- mapOptions.gestureHandling = "none";
50
- }
51
- else if (draggable && !disableDblclickZoom && scrollwheel) {
52
- mapOptions.gestureHandling = "greedy";
53
- }
54
- else {
55
- mapOptions.draggable = draggable;
56
- mapOptions.disableDoubleClickZoom = disableDblclickZoom;
57
- mapOptions.scrollwheel = scrollwheel;
58
- }
59
- }
60
-
61
- return mapOptions;
62
- }
63
-
64
- /**
65
- * get the Google Maps API Map object
66
- * @return {google.maps.Map}
67
- */
68
- this.getMap = function() {
69
- return map;
70
- };
71
-
72
- /**
73
- * get the centrepoint of the map at creation, or via setCenter()
74
- * @return {google.maps.LatLng}
75
- */
76
- this.getCenter = function() {
77
- return centre;
78
- };
79
-
80
- /**
81
- * set the centrepoint of the map
82
- * @param {google.maps.LatLng} latLng
83
- */
84
- this.setCenter = function(latLng) {
85
- centre = latLng;
86
- map.setCenter(centre);
87
- };
88
-
89
- /**
90
- * record the single marker location
91
- * @param {google.maps.LatLng} latLng
92
- */
93
- this.setMarkerLocation = function(latLng) {
94
- markerLocation = latLng;
95
- };
96
-
97
- /**
98
- * get the single marker location
99
- * @return {google.maps.LatLng}
100
- */
101
- this.getMarkerLocation = function() {
102
- return markerLocation;
103
- };
104
-
105
- /**
106
- * record the single marker point
107
- * @param {google.maps.Marker} point
108
- */
109
- this.setMarkerPoint = function(point) {
110
- markerPoint = point;
111
- };
112
-
113
- /**
114
- * get the single marker point
115
- * @return {google.maps.Marker}
116
- */
117
- this.getMarkerPoint = function() {
118
- return markerPoint;
119
- };
120
-
121
- /**
122
- * record the single marker infowindow
123
- * @param {google.maps.InfoWindow} infowin
124
- */
125
- this.setMarkerInfowin = function(infowin) {
126
- markerInfowin = infowin;
127
- };
128
-
129
- /**
130
- * get the single marker infowindow
131
- * @return {google.maps.InfoWindow}
132
- */
133
- this.getMarkerInfowin = function() {
134
- return markerInfowin;
135
- };
136
-
137
- /**
138
- * get the map's KML layer if set
139
- * @return {google.maps.KmlLayer}
140
- */
141
- this.getKmlLayer = function() {
142
- return kmlLayer;
143
- };
144
-
145
- /**
146
- * if map starts life hidden and needs to be redrawn when revealed, this function will perform that redraw *once*
147
- */
148
- this.redrawOnce = function() {
149
- if (!hasRedrawn) {
150
- hasRedrawn = true;
151
- this.redraw();
152
- }
153
- };
154
-
155
- /**
156
- * show a map at specified centre latitude / longitude
157
- * @param {String} divID the ID of the div that will contain the map
158
- * @param {Array} latLng the map centre, an array of two integers: [ latitude, longitude ]
159
- * @return {google.maps.Map} the Google Maps map created
160
- */
161
- this.showMap = function(divID, latLng) {
162
- centre = new google.maps.LatLng(latLng[0], latLng[1]);
163
-
164
- var mapOptions,
165
- zoomControlStyles = {
166
- "small" : google.maps.ZoomControlStyle.SMALL,
167
- "large" : google.maps.ZoomControlStyle.LARGE,
168
- "default" : google.maps.ZoomControlStyle.DEFAULT
169
- },
170
- zoomControlStyle = zoomControlStyles.small;
171
-
172
- // style the zoom control
173
- if (this.zoomControlStyle in zoomControlStyles) {
174
- zoomControlStyle = zoomControlStyles[this.zoomControlStyle];
175
- }
176
-
177
- // basic options
178
- mapOptions = {
179
- mapTypeId: this.mapTypeId,
180
- mapTypeControl: this.mapTypeControl,
181
- scaleControl: this.scaleControl,
182
- panControl: this.panControl,
183
- streetViewControl: this.streetViewControl,
184
- zoomControl: this.zoomControl,
185
- zoomControlOptions: { style: zoomControlStyle },
186
- fullscreenControl: this.fullscreen,
187
- center: centre,
188
- zoom: this.zoom
189
- };
190
- mapOptions = getGestureHandling(mapOptions, this);
191
-
192
- // select which map types for map type control, if specified as comma-separated list of map type IDs
193
- if (this.mapTypeIds) {
194
- mapOptions.mapTypeControlOptions = {
195
- mapTypeIds: this.mapTypeIds.split(",")
196
- };
197
- }
198
-
199
- // create a map
200
- map = new google.maps.Map(document.getElementById(divID), mapOptions);
201
-
202
- // set custom map type if specified
203
- if (this.mapTypeId in this.mapTypes) {
204
- map.mapTypes.set(this.mapTypeId, this.mapTypes[this.mapTypeId]._styled_map);
205
- }
206
-
207
- return map;
208
- };
209
-
210
- /**
211
- * load a map from a KML file and add as a layer on the Google Maps map
212
- * @param {String} kmlFileURL
213
- * @return {google.maps.KmlLayer}
214
- */
215
- this.loadKmlMap = function(kmlFileURL) {
216
- // load KML file as a layer and add to map
217
- var self = this;
218
- var options = {
219
- map: map,
220
- url: kmlFileURL,
221
- };
222
-
223
- /**
224
- * reset the map back to the recorded centre coordinates -- only used when centre is set for map
225
- */
226
- function resetCentre() {
227
- self.setCenter(new google.maps.LatLng(self.kmlCentre[0], self.kmlCentre[1]));
228
- }
229
-
230
- /**
231
- * update centre of map from bounds on KML layer -- not called when centre is set for map
232
- */
233
- function recordKmlCentre() {
234
- centre = kmlLayer.getDefaultViewport().getCenter();
235
- }
236
-
237
- // if a centre has been set, stop the viewport from changing and add the centre to map options
238
- if (this.kmlCentre) {
239
- options.preserveViewport = true;
240
- options.center = new google.maps.LatLng(this.kmlCentre[0], this.kmlCentre[1]);
241
- }
242
-
243
- kmlLayer = new google.maps.KmlLayer(options);
244
-
245
- // listen for KML layer loaded event
246
- google.maps.event.addListenerOnce(kmlLayer, "defaultviewport_changed", this.kmlCentre ? resetCentre : recordKmlCentre);
247
-
248
- return kmlLayer;
249
- };
250
-
251
- // load localisations if they haven't already been loaded
252
- if (!this.localised && "flxmap" in window) {
253
- this.localise();
254
- }
255
-
256
- // set map defaults
257
- this.mapTypeId = google.maps.MapTypeId.ROADMAP;
258
- this.mapTypeControl = true; // no control for changing map type
259
- this.scaleControl = false; // no control for changing scale
260
- this.panControl = false; // no control for panning
261
- this.zoomControl = true; // show control for zooming
262
- this.zoomControlStyle = "small"; // from "small", "large", "default"
263
- this.streetViewControl = false; // no control for street view
264
- this.fullscreen = true; // show control for full-screen view
265
- this.gestureHandling = undefined; // defaults to cooperative
266
- this.scrollwheel = undefined; // deprecated; use gestureHandling instead
267
- this.draggable = undefined; // deprecated; use gestureHandling instead
268
- this.dblclickZoom = undefined; // deprecated; use gestureHandling instead
269
- this.zoom = 16; // zoom level, smaller is closer
270
- this.markerTitle = ""; // title for marker info window
271
- this.markerDescription = ""; // description for marker info window
272
- this.markerHTML = ""; // HTML for marker info window (overrides title and description)
273
- this.markerLink = ""; // link for marker title
274
- this.markerLinkTarget = ""; // link target for marker link, e.g. _blank
275
- this.markerLinkText = false; // link text for marker link, overriding default text
276
- this.markerIcon = ""; // link for marker icon, leave blank for default
277
- this.markerShowInfo = true; // if have infowin for marker, show it immediately
278
- this.markerAnimation = "drop"; // marker animation - bounce, drop, none
279
- this.markerDirections = false; // show directions link in info window
280
- this.markerDirectionsShow = false; // show directions as soon as page loads
281
- this.markerDirectionsDefault = ""; // default from: location for directions
282
- this.markerAddress = ""; // address of marker, if given
283
- this.targetFix = true; // remove target="_blank" from links on KML map
284
- this.dirService = false;
285
- this.dirRenderer = false;
286
- this.dirDraggable = false; // directions route is draggable (for alternate routing)
287
- this.dirSuppressMarkers = false; // suppress A/B markers on directions route
288
- this.dirShowSteps = true; // show the directions steps (turn-by-turn)
289
- this.dirShowSearch = true; // show the directions form for searching directions
290
- this.dirTravelMode = "driving"; // can be bicycling, driving, transit, walking
291
- this.dirUnitSystem = undefined; // can be imperial or metric
292
- this.region = "";
293
- this.locale = "en";
294
- this.localeActive = false;
295
- this.kmlCentre = false;
296
- this.kmlcache = "none";
297
- }
298
-
299
- FlexibleMap.prototype = (function() {
300
- "use strict";
301
-
302
- var addEventListener, stopEvent, handleHiddenMap;
303
-
304
- // detect standard event model
305
- if (document.addEventListener) {
306
- addEventListener = function(element, eventName, hook) {
307
- element.addEventListener(eventName, hook, false);
308
- };
309
-
310
- stopEvent = function(event) {
311
- event.stopPropagation();
312
- event.preventDefault();
313
- };
314
- }
315
- else
316
- // detect IE event model
317
- if (document.attachEvent) {
318
- addEventListener = function(element, event, hook) {
319
- element.attachEvent("on" + event, function() { hook.call(element, window.event); });
320
- };
321
-
322
- stopEvent = function(event) {
323
- event.cancelBubble = true;
324
- event.returnValue = 0;
325
- };
326
- }
327
-
328
- // handle hidden maps, trigger a resize on first display
329
- if (typeof MutationObserver !== "undefined") {
330
- handleHiddenMap = function(flxmap, divID) {
331
- var mapDiv = document.getElementById(divID),
332
- container = mapDiv.parentNode,
333
- observer;
334
-
335
- function isHidden(element) {
336
- var style = window.getComputedStyle(element);
337
- return style.display === "none" || style.visibility === "hidden";
338
- }
339
-
340
- // only need to watch and act if the parent container is hidden from display
341
- if (isHidden(container)) {
342
- observer = new MutationObserver(function(mutations, self) {
343
- // only proceed if map is visible now
344
- if (!isHidden(container)) {
345
- flxmap.redrawOnce();
346
-
347
- // stop observing, we're done
348
- self.disconnect();
349
- }
350
- });
351
-
352
- observer.observe(container, {
353
- attributes: true,
354
- attributeFilter: ["style"]
355
- });
356
- }
357
- };
358
- }
359
- else {
360
- handleHiddenMap = function() { };
361
- }
362
-
363
- /**
364
- * encode special JavaScript characters, so text is safe when building JavaScript/HTML dynamically
365
- * NB: conservatively assumes that HTML special characters are unsafe, and encodes them too
366
- * @param {String} text
367
- * @return {String}
368
- */
369
- var encodeJS = (function() {
370
-
371
- /**
372
- * encode character as Unicode hexadecimal escape sequence
373
- * @param {String} ch character to encode
374
- * @return {String}
375
- */
376
- function toUnicodeHex(ch) {
377
- var c = ch.charCodeAt(0),
378
- s = c.toString(16);
379
-
380
- // see if we can use 2-digit hex code
381
- if (c < 0x100) {
382
- return "\\x" + ("00" + s).slice(-2);
383
- }
384
-
385
- // must use 4-digit hex code
386
- return "\\u" + ("0000" + s).slice(-4);
387
- }
388
-
389
- return function(text) {
390
- // search for JavaScript and HTML special characters, convert to Unicode hex
391
- return text.replace(/[\\/"'&<>\x00-\x1f\x7f-\xa0\u2000-\u200f\u2028-\u202f]/g, toUnicodeHex); // eslint-disable-line no-control-regex
392
- };
393
-
394
- })();
395
-
396
- /**
397
- * add cache buster to KML source link
398
- * @param {String} url
399
- * @param {String} caching
400
- * @return {String}
401
- */
402
- function kmlCacheBuster(url, caching) {
403
- var milliseconds, buster, multiplier, matches = /^(\d+)\s*(minute|hour|day)s?$/.exec(caching);
404
-
405
- if (matches) {
406
- milliseconds = (new Date()).getTime();
407
- multiplier = +matches[1];
408
-
409
- switch(matches[2]) {
410
- case "minute":
411
- // can't be less than 5 minutes
412
- if (multiplier < 5) {
413
- multiplier = 5;
414
- }
415
- buster = milliseconds / (60000 * multiplier);
416
- break;
417
-
418
- case "hour":
419
- buster = milliseconds / (3600000 * multiplier);
420
- break;
421
-
422
- case "day":
423
- buster = milliseconds / (86400000 * multiplier);
424
- break;
425
-
426
- default:
427
- buster = false;
428
- break;
429
- }
430
-
431
- if (buster) {
432
- buster = Math.floor(buster);
433
- url += (url.indexOf("?") > -1 ? "&" : "?") + "nocache=" + buster; // eslint-disable-line no-param-reassign
434
- }
435
- }
436
-
437
- return url;
438
- }
439
-
440
- return {
441
- constructor: FlexibleMap,
442
-
443
- /**
444
- * collection of locale / phrase mapping for internationalisation of messages
445
- */
446
- i18n: { },
447
-
448
- /**
449
- * collection of custom Google Maps map types for styling maps
450
- */
451
- mapTypes: { },
452
-
453
- localised: false, // set to true once localisations have been loaded
454
-
455
- /**
456
- * load localisations into class prototype
457
- */
458
- localise: function() {
459
- var key, mapTypes;
460
-
461
- // load translations
462
- if ("i18n" in flxmap) {
463
- FlexibleMap.prototype.i18n = flxmap.i18n;
464
- }
465
-
466
- // load custom map types
467
- if ("mapTypes" in flxmap) {
468
- mapTypes = flxmap.mapTypes;
469
-
470
- for (key in mapTypes) {
471
- mapTypes[key]._styled_map = new google.maps.StyledMapType(mapTypes[key].styles, mapTypes[key].options);
472
- }
473
-
474
- FlexibleMap.prototype.mapTypes = mapTypes;
475
- }
476
-
477
- FlexibleMap.prototype.localised = true;
478
- },
479
-
480
- /**
481
- * set the locale used for i18n phrase lookup, picking the best match
482
- * @param {String} localeWanted the locale wanted, e.g. en-AU, da-DK, sv
483
- * @return {String} the locale that will be used (nearest match, or default if none)
484
- */
485
- setlocale: function(localeWanted) {
486
- this.locale = localeWanted;
487
-
488
- // attempt to set this locale as active
489
- if (localeWanted in this.i18n) {
490
- this.localeActive = localeWanted;
491
- }
492
- else {
493
- // not found, so try simplified locale
494
- if (localeWanted.substr(0, 2) in this.i18n) {
495
- this.localeActive = localeWanted;
496
- }
497
- else {
498
- // still not found
499
- this.localeActive = false;
500
- }
501
- }
502
-
503
- return this.localeActive;
504
- },
505
-
506
- /**
507
- * get phrase from the current locale domain, or the default domain (en) if not found
508
- * @param {String} key the key for the desired phrase
509
- * @return {String}
510
- */
511
- gettext: function(key) {
512
- var locale = this.localeActive;
513
-
514
- if (locale && key in this.i18n[locale]) {
515
- return this.i18n[locale][key];
516
- }
517
-
518
- return key;
519
- },
520
-
521
- /**
522
- * show a map based on a KML file
523
- * @param {String} divID the ID of the div that will contain the map
524
- * @param {String} kmlFileURL path to the KML file to load
525
- * @param {Number} zoom [optional] zoom level
526
- */
527
- showKML: function(divID, kmlFileURL, zoom) {
528
- if (typeof zoom != "undefined")
529
- this.zoom = zoom;
530
-
531
- var self = this,
532
- mapDiv = document.getElementById(divID),
533
- varName = mapDiv.getAttribute("data-flxmap"),
534
- map = this.showMap(divID, [0, 0]),
535
- kmlLayer = this.loadKmlMap(kmlCacheBuster(kmlFileURL, this.kmlcache));
536
-
537
- handleHiddenMap(this, divID);
538
-
539
- // set zoom if specified
540
- if (typeof zoom != "undefined") {
541
- // listen for zoom changing to fit markers on KML layer, force it back to what we asked for
542
- google.maps.event.addListenerOnce(map, "zoom_changed", function() {
543
- map.setZoom(zoom);
544
- self.zoom = zoom;
545
- });
546
- }
547
-
548
- // add a directions service if needed
549
- if (this.markerDirections || this.markerDirectionsShow) {
550
- this.startDirService(map);
551
- }
552
-
553
- // customise the infowindow as required; can do this on click event on KML layer (thanks, Stack Overflow!)
554
- google.maps.event.addListener(kmlLayer, 'click', function(kmlEvent) {
555
- var featureData = kmlEvent.featureData;
556
-
557
- // NB: since Google Maps API v3.9 the info window HTML is precomposed before this event occurs,
558
- // so just changing the description won't change infowindow
559
-
560
- if (!featureData._flxmapOnce) {
561
- // add a flag to stop doing this on every click; once is enough
562
- featureData._flxmapOnce = true;
563
-
564
- // stop links opening in a new window
565
- if (self.targetFix && featureData.description) {
566
- var reTargetFix = / target="_blank"/ig;
567
- featureData.description = featureData.description.replace(reTargetFix, "");
568
- featureData.infoWindowHtml = featureData.infoWindowHtml.replace(reTargetFix, "");
569
- }
570
-
571
- // if we're showing directions, add directions link to marker description
572
- if (self.markerDirections) {
573
- var latLng = kmlEvent.latLng,
574
- params = latLng.lat() + ',' + latLng.lng() + ",'" + encodeJS(featureData.name) + "',true",
575
- a = '<br /><a href="#" data-flxmap-fix-opera="1" onclick="' + varName + '.showDirections(' + params + '); return false;">' + self.gettext("Directions") + '</a>';
576
-
577
- featureData.infoWindowHtml = featureData.infoWindowHtml.replace(/<\/div><\/div>$/i, a + "</div></div>");
578
- }
579
- }
580
- });
581
-
582
- // hack for directions links on Opera, which fails to ignore events when onclick returns false
583
- if (window.opera && this.markerDirections) {
584
- addEventListener(mapDiv, "click", function(event) {
585
- if (event.target.getAttribute("data-flxmap-fix-opera")) {
586
- stopEvent(event);
587
- }
588
- });
589
- }
590
-
591
- },
592
-
593
- /**
594
- * show a map centred at latitude / longitude and with marker at latitude / longitude
595
- * @param {String} divID the ID of the div that will contain the map
596
- * @param {Array} centre an array of two integers: [ latitude, longitude ]
597
- * @param {Array} marker an array of two integers: [ latitude, longitude ]
598
- */
599
- showMarker: function(divID, centre, marker) {
600
- var map = this.showMap(divID, centre);
601
- var markerLocation = new google.maps.LatLng(marker[0], marker[1]);
602
- var options = {
603
- map: map,
604
- position: markerLocation,
605
- icon: this.markerIcon
606
- };
607
- var Animation = google.maps.Animation;
608
-
609
- switch (this.markerAnimation) {
610
-
611
- case "drop":
612
- options.animation = Animation.DROP;
613
- break;
614
-
615
- case "bounce":
616
- options.animation = Animation.BOUNCE;
617
- break;
618
-
619
- }
620
-
621
- var point = new google.maps.Marker(options);
622
-
623
- this.setMarkerPoint(point);
624
- this.setMarkerLocation(markerLocation);
625
-
626
- handleHiddenMap(this, divID);
627
-
628
- if (!this.markerTitle) {
629
- this.markerTitle = this.markerAddress;
630
- }
631
-
632
- if (this.markerTitle || this.markerHTML || this.markerDescription || this.markerLink || this.markerDirections) {
633
- var i, len, lines, infowin, element, a,
634
- self = this,
635
- container = document.createElement("DIV");
636
-
637
- container.className = "flxmap-infowin";
638
-
639
- // heading for info window
640
- element = document.createElement("DIV");
641
- element.className = "flxmap-marker-title";
642
- if (this.markerTitle) {
643
- element.appendChild(document.createTextNode(this.markerTitle));
644
-
645
- // add tooltip title for marker
646
- point.setTitle(this.markerTitle);
647
- }
648
- container.appendChild(element);
649
-
650
- // add precomposed HTML for infowindow
651
- if (this.markerHTML) {
652
- element = document.createElement("DIV");
653
- element.innerHTML = this.markerHTML;
654
- container.appendChild(element);
655
- }
656
-
657
- // body of info window, with link
658
- if (this.markerDescription || this.markerLink) {
659
- element = document.createElement("DIV");
660
- element.className = "flxmap-marker-link";
661
- if (this.markerDescription) {
662
- lines = this.markerDescription.split("\n");
663
- for (i = 0, len = lines.length; i < len; i++) {
664
- if (i > 0)
665
- element.appendChild(document.createElement("BR"));
666
- element.appendChild(document.createTextNode(lines[i]));
667
- }
668
- if (this.markerLink) {
669
- element.appendChild(document.createElement("BR"));
670
- }
671
- }
672
- if (this.markerLink) {
673
- a = document.createElement("A");
674
- a.href = this.markerLink;
675
- if (this.markerLinkTarget) {
676
- a.target = this.markerLinkTarget;
677
- }
678
- a.appendChild(document.createTextNode(this.markerLinkText || this.gettext("Click for details")));
679
- element.appendChild(a);
680
- }
681
- container.appendChild(element);
682
- }
683
-
684
- // add a link for directions if wanted
685
- if (this.markerDirections) {
686
- element = document.createElement("DIV");
687
- element.className = "flxmap-directions-link";
688
- a = document.createElement("A");
689
- a.href = "#";
690
- a.dataLatitude = marker[0];
691
- a.dataLongitude = marker[1];
692
- addEventListener(a, "click", function(event) {
693
- stopEvent(event);
694
- self.showDirections(this.dataLatitude, this.dataLongitude, true);
695
- });
696
- a.appendChild(document.createTextNode(this.gettext("Directions")));
697
- element.appendChild(a);
698
- container.appendChild(element);
699
- }
700
-
701
- infowin = new google.maps.InfoWindow({content: container});
702
- this.setMarkerInfowin(infowin);
703
-
704
- if (this.markerShowInfo) {
705
- // open after map is loaded, so that infowindow will auto-pan and won't be cropped at top
706
- google.maps.event.addListenerOnce(map, "tilesloaded", function() {
707
- infowin.open(map, point);
708
- });
709
- }
710
-
711
- google.maps.event.addListener(point, "click", function() {
712
- infowin.open(map, point);
713
- });
714
-
715
- // find Google link and append marker info, modern browsers only!
716
- // NB: Google link is set before initial map idle event, and reset each time the map centre changes
717
- var googleLink = function() { self.updateGoogleLink(); };
718
- google.maps.event.addListener(map, "idle", googleLink);
719
- google.maps.event.addListener(map, "center_changed", googleLink);
720
- google.maps.event.addListenerOnce(map, "tilesloaded", googleLink);
721
- }
722
-
723
- // add a directions service if needed
724
- if (this.markerDirections || this.markerDirectionsShow) {
725
- this.startDirService(map);
726
-
727
- // show directions immediately if required
728
- if (this.markerDirectionsShow) {
729
- this.showDirections(marker[0], marker[1], false);
730
- }
731
- }
732
-
733
- },
734
-
735
- /**
736
- * show a map centred at address
737
- * @param {String} divID the ID of the div that will contain the map
738
- * @param {String} address the address (should return a unique location in Google Maps!)
739
- */
740
- showAddress: function(divID, address) {
741
- var self = this,
742
- geocoder = new google.maps.Geocoder();
743
-
744
- this.markerAddress = address;
745
-
746
- if (this.markerTitle === "") {
747
- this.markerTitle = address;
748
- }
749
-
750
- geocoder.geocode({address: address, region: this.region}, function(results, status) {
751
- if (status === google.maps.GeocoderStatus.OK) {
752
- var location = results[0].geometry.location,
753
- centre = [ location.lat(), location.lng() ];
754
- self.showMarker(divID, centre, centre);
755
- }
756
- else {
757
- window.alert("Map address returns error: " + status);
758
- }
759
- });
760
- },
761
-
762
- /**
763
- * set query parameters on Google link to maps -- modern browsers only
764
- * NB: will only set the query parameters when Google link doesn't have them already;
765
- * Google link is set before initial map idle event, and reset each time the map centre changes
766
- */
767
- updateGoogleLink: function() {
768
- if ("querySelectorAll" in document) {
769
- try {
770
- var flxmap = this.getMap().getDiv(),
771
- location = this.getMarkerLocation(),
772
- googleLinks = flxmap.querySelectorAll("a[href*='maps.google.com/maps']:not([href*='mps_dialog']):not([href*='&q='])"),
773
- i = 0, len = googleLinks.length,
774
- query = encodeURIComponent((this.markerAddress ? this.markerAddress : this.markerTitle) +
775
- " @" + location.lat() + "," + location.lng());
776
-
777
- for (; i < len; i++) {
778
- googleLinks[i].href += "&mrt=loc&iwloc=A&q=" + query;
779
- }
780
- }
781
- catch (e) {
782
- // we don't care about IE8 and earlier...
783
- }
784
- }
785
- },
786
-
787
- /**
788
- * tell Google Maps to redraw the map, and centre it back where it started with default zoom
789
- */
790
- redraw: function() {
791
- var map = this.getMap(),
792
- kmlLayer = this.getKmlLayer();
793
-
794
- google.maps.event.trigger(map, "resize");
795
-
796
- // if map is KML, must refit to computed bounds, else use centre and zoom setting
797
- if (kmlLayer) {
798
- map.fitBounds(kmlLayer.getDefaultViewport());
799
- }
800
- else {
801
- map.setCenter(this.getCenter());
802
- map.setZoom(this.zoom);
803
-
804
- // redraw the marker's infowindow if it has one
805
- var infowin = this.getMarkerInfowin();
806
- if (infowin) {
807
- infowin.open(map, this.getMarkerPoint());
808
- }
809
- }
810
- },
811
-
812
- /**
813
- * create directions service
814
- */
815
- startDirService: function(map) {
816
- // make sure we have a directions service
817
- if (!this.dirService) {
818
- this.dirService = new google.maps.DirectionsService();
819
- }
820
-
821
- // make sure we have a directions renderer
822
- if (!this.dirRenderer) {
823
- this.dirRenderer = new google.maps.DirectionsRenderer({
824
- map: map,
825
- draggable: this.dirDraggable,
826
- suppressMarkers: this.dirSuppressMarkers,
827
- panel: this.dirShowSteps ? document.getElementById(this.markerDirectionsDiv) : null
828
- });
829
- }
830
- },
831
-
832
- /**
833
- * show directions for specified latitude / longitude and title
834
- * @param {Number} latitude
835
- * @param {Number} longitude
836
- * @param {bool} focus [optional]
837
- */
838
- showDirections: function(latitude, longitude, focus) {
839
- var self = this;
840
-
841
- /**
842
- * show the directions form to allow directions searches
843
- */
844
- function showDirectionsForm() {
845
- var panel = document.getElementById(self.markerDirectionsDiv),
846
- form = document.createElement("form"),
847
- input, p, from;
848
-
849
- // remove all from panel
850
- for (p = panel.lastChild; p; p = panel.lastChild) {
851
- panel.removeChild(p);
852
- }
853
-
854
- // populate form and add to panel
855
- p = document.createElement("p");
856
- p.appendChild(document.createTextNode(self.gettext("From") + ": "));
857
- from = document.createElement("input");
858
- from.type = "text";
859
- from.name = "from";
860
- from.value = self.markerDirectionsDefault;
861
- p.appendChild(from);
862
- input = document.createElement("input");
863
- input.type = "submit";
864
- input.value = self.gettext("Get directions");
865
- p.appendChild(input);
866
- form.appendChild(p);
867
- panel.appendChild(form);
868
-
869
- // hack to fix IE<=7 name weirdness for dynamically created form elements;
870
- // see https://msdn.microsoft.com/en-us/library/ms534184.aspx but have a hanky ready
871
- if (typeof form.elements.from == "undefined") {
872
- form.elements.from = from;
873
- }
874
-
875
- // only focus when asked, to prevent problems autofocusing on elements and scrolling the page!
876
- if (focus) {
877
- from.focus();
878
- }
879
-
880
- // handle the form submit
881
- addEventListener(form, "submit", function(event) {
882
- stopEvent(event);
883
-
884
- var from = this.elements.from.value;
885
-
886
- // only process if something was entered to search on
887
- if (/\S/.test(from)) {
888
- requestDirections(from);
889
- }
890
-
891
- });
892
- }
893
-
894
- /**
895
- * request directions
896
- * @param {String} from
897
- */
898
- function requestDirections(from) {
899
- var dest = (self.markerAddress === "") ? new google.maps.LatLng(latitude, longitude) : self.markerAddress,
900
- request = {
901
- origin: from,
902
- destination: dest
903
- };
904
-
905
- if (self.region) {
906
- request.region = self.region;
907
- }
908
-
909
- switch (self.dirTravelMode) {
910
- case "bicycling":
911
- request.travelMode = google.maps.TravelMode.BICYCLING;
912
- break;
913
-
914
- case "driving":
915
- request.travelMode = google.maps.TravelMode.DRIVING;
916
- break;
917
-
918
- case "transit":
919
- request.travelMode = google.maps.TravelMode.TRANSIT;
920
- break;
921
-
922
- case "walking":
923
- request.travelMode = google.maps.TravelMode.WALKING;
924
- break;
925
- }
926
-
927
- switch (self.dirUnitSystem) {
928
- case "imperial":
929
- request.unitSystem = google.maps.UnitSystem.IMPERIAL;
930
- break;
931
-
932
- case "metric":
933
- request.unitSystem = google.maps.UnitSystem.METRIC;
934
- break;
935
- }
936
-
937
- self.dirService.route(request, dirResponseHander);
938
- }
939
-
940
- /**
941
- * handle the response for Google directions service
942
- * @param {google.maps.DirectionsResult} response
943
- * @param {Number} status
944
- */
945
- function dirResponseHander(response, status) {
946
- var DirectionsStatus = google.maps.DirectionsStatus;
947
-
948
- switch (status) {
949
- case DirectionsStatus.OK:
950
- self.dirRenderer.setDirections(response);
951
- break;
952
-
953
- case DirectionsStatus.ZERO_RESULTS:
954
- window.alert("No route could be found between the origin and destination.");
955
- break;
956
-
957
- case DirectionsStatus.OVER_QUERY_LIMIT:
958
- window.alert("The webpage has gone over the requests limit in too short a period of time.");
959
- break;
960
-
961
- case DirectionsStatus.REQUEST_DENIED:
962
- window.alert("The webpage is not allowed to use the directions service.");
963
- break;
964
-
965
- case DirectionsStatus.INVALID_REQUEST:
966
- window.alert("Invalid directions request.");
967
- break;
968
-
969
- case DirectionsStatus.NOT_FOUND:
970
- window.alert("Origin or destination was not found.");
971
- break;
972
-
973
- default:
974
- window.alert("A directions request could not be processed due to a server error. The request may succeed if you try again.");
975
- break;
976
- }
977
- }
978
-
979
- // if we have a directions div, show the form for searching it
980
- if (this.markerDirectionsDiv && this.dirShowSearch) {
981
- showDirectionsForm();
982
- }
983
-
984
- // if the from: location is already set, trigger the directions query
985
- if (this.markerDirectionsDefault) {
986
- requestDirections(this.markerDirectionsDefault);
987
- }
988
-
989
- }
990
-
991
- };
992
-
993
- })();
1
+ "use strict";
2
+
3
  /*
4
  JavaScript for the WordPress plugin wp-flexible-map
5
  https://flexible-map.webaware.net.au/
6
  */
7
+ window.FlexibleMap = function () {
8
+ "use strict"; // instance-private members with accessors
9
+
10
+ var map; // google.maps.Map object
11
+
12
+ var centre; // google.maps.LatLng object for map centre
13
+
14
+ var markerLocation; // google.maps.LatLng object for single marker, when using showMarker()
15
+
16
+ var markerPoint; // google.maps.Marker object for single marker, when using showMarker()
17
+
18
+ var markerInfowin; // google.maps.InfoWindow object for single marker, when using showMarker()
19
+
20
+ var kmlLayer; // if map has a KML layer, this is the layer object
21
+
22
+ var hasRedrawn = false; // boolean, whether map has been asked to redrawOnce() already
23
+
24
+ /**
25
+ * set gesture handling options, with legacy support for draggable, dblclickZoom, scrollwheel
26
+ * set to "cooperative" if none of the settings are given
27
+ * @link https://developers.google.com/maps/documentation/javascript/reference/map#MapOptions.gestureHandling
28
+ * @param {Object} mapOptions
29
+ * @param {FlexibleMap} flexibleMap
30
+ * @return {Object}
31
+ */
32
+
33
+ function getGestureHandling(mapOptions, flexibleMap) {
34
+ if (flexibleMap.gestureHandling !== undefined) {
35
+ mapOptions.gestureHandling = flexibleMap.gestureHandling;
36
+ } else if (flexibleMap.draggable === undefined && flexibleMap.dblclickZoom === undefined && flexibleMap.scrollwheel === undefined) {
37
+ mapOptions.gestureHandling = "cooperative";
38
+ } else {
39
+ // legacy support; deprecated
40
+ var draggable = flexibleMap.draggable === undefined ? true : flexibleMap.draggable; // default true, i.e. enable draggable
41
+
42
+ var disableDblclickZoom = flexibleMap.dblclickZoom === undefined ? false : !flexibleMap.draggable; // default true, i.e. enable double-click zoom
43
+
44
+ var scrollwheel = flexibleMap.scrollwheel === undefined ? false : flexibleMap.scrollwheel; // default false, i.e. dinable scrollwheel
45
+ // ----------------+-----------+------------------------+------------
46
+ // gestureHandling | draggable | disableDoubleClickZoom | scrollwheel
47
+ // ----------------+-----------+------------------------+------------
48
+ // cooperative | true | false | ctrl+ |
49
+ // greedy | true | false | true |
50
+ // auto | ???? | false | ???? |
51
+ // none | false | true | false |
52
+ // ----------------+-----------+------------------------+------------
53
+
54
+ if (!draggable && disableDblclickZoom && !scrollwheel) {
55
+ mapOptions.gestureHandling = "none";
56
+ } else if (draggable && !disableDblclickZoom && scrollwheel) {
57
+ mapOptions.gestureHandling = "greedy";
58
+ } else {
59
+ mapOptions.draggable = draggable;
60
+ mapOptions.disableDoubleClickZoom = disableDblclickZoom;
61
+ mapOptions.scrollwheel = scrollwheel;
62
+ }
63
+ }
64
+
65
+ return mapOptions;
66
+ }
67
+ /**
68
+ * get the Google Maps API Map object
69
+ * @return {google.maps.Map}
70
+ */
71
+
72
+
73
+ this.getMap = function () {
74
+ return map;
75
+ };
76
+ /**
77
+ * get the centrepoint of the map at creation, or via setCenter()
78
+ * @return {google.maps.LatLng}
79
+ */
80
+
81
+
82
+ this.getCenter = function () {
83
+ return centre;
84
+ };
85
+ /**
86
+ * set the centrepoint of the map
87
+ * @param {google.maps.LatLng} latLng
88
+ */
89
+
90
+
91
+ this.setCenter = function (latLng) {
92
+ centre = latLng;
93
+ map.setCenter(centre);
94
+ };
95
+ /**
96
+ * record the single marker location
97
+ * @param {google.maps.LatLng} latLng
98
+ */
99
+
100
+
101
+ this.setMarkerLocation = function (latLng) {
102
+ markerLocation = latLng;
103
+ };
104
+ /**
105
+ * get the single marker location
106
+ * @return {google.maps.LatLng}
107
+ */
108
+
109
+
110
+ this.getMarkerLocation = function () {
111
+ return markerLocation;
112
+ };
113
+ /**
114
+ * record the single marker point
115
+ * @param {google.maps.Marker} point
116
+ */
117
+
118
+
119
+ this.setMarkerPoint = function (point) {
120
+ markerPoint = point;
121
+ };
122
+ /**
123
+ * get the single marker point
124
+ * @return {google.maps.Marker}
125
+ */
126
+
127
+
128
+ this.getMarkerPoint = function () {
129
+ return markerPoint;
130
+ };
131
+ /**
132
+ * record the single marker infowindow
133
+ * @param {google.maps.InfoWindow} infowin
134
+ */
135
+
136
+
137
+ this.setMarkerInfowin = function (infowin) {
138
+ markerInfowin = infowin;
139
+ };
140
+ /**
141
+ * get the single marker infowindow
142
+ * @return {google.maps.InfoWindow}
143
+ */
144
+
145
+
146
+ this.getMarkerInfowin = function () {
147
+ return markerInfowin;
148
+ };
149
+ /**
150
+ * get the map's KML layer if set
151
+ * @return {google.maps.KmlLayer}
152
+ */
153
+
154
+
155
+ this.getKmlLayer = function () {
156
+ return kmlLayer;
157
+ };
158
+ /**
159
+ * if map starts life hidden and needs to be redrawn when revealed, this function will perform that redraw *once*
160
+ */
161
+
162
+
163
+ this.redrawOnce = function () {
164
+ if (!hasRedrawn) {
165
+ hasRedrawn = true;
166
+ this.redraw();
167
+ }
168
+ };
169
+ /**
170
+ * show a map at specified centre latitude / longitude
171
+ * @param {String} divID the ID of the div that will contain the map
172
+ * @param {Array} latLng the map centre, an array of two integers: [ latitude, longitude ]
173
+ * @return {google.maps.Map} the Google Maps map created
174
+ */
175
+
176
+
177
+ this.showMap = function (divID, latLng) {
178
+ centre = new google.maps.LatLng(latLng[0], latLng[1]);
179
+ var zoomControlStyles = {
180
+ "small": google.maps.ZoomControlStyle.SMALL,
181
+ "large": google.maps.ZoomControlStyle.LARGE,
182
+ "default": google.maps.ZoomControlStyle.DEFAULT
183
+ };
184
+ var zoomControlStyle = zoomControlStyles.small; // style the zoom control
185
+
186
+ if (this.zoomControlStyle in zoomControlStyles) {
187
+ zoomControlStyle = zoomControlStyles[this.zoomControlStyle];
188
+ } // basic options
189
+
190
+
191
+ var mapOptions = {
192
+ mapTypeId: this.mapTypeId,
193
+ mapTypeControl: this.mapTypeControl,
194
+ scaleControl: this.scaleControl,
195
+ panControl: this.panControl,
196
+ streetViewControl: this.streetViewControl,
197
+ zoomControl: this.zoomControl,
198
+ zoomControlOptions: {
199
+ style: zoomControlStyle
200
+ },
201
+ fullscreenControl: this.fullscreen,
202
+ center: centre,
203
+ zoom: this.zoom
204
+ };
205
+ mapOptions = getGestureHandling(mapOptions, this); // select which map types for map type control, if specified as comma-separated list of map type IDs
206
+
207
+ if (this.mapTypeIds) {
208
+ mapOptions.mapTypeControlOptions = {
209
+ mapTypeIds: this.mapTypeIds.split(",")
210
+ };
211
+ } // create a map
212
+
213
+
214
+ map = new google.maps.Map(document.getElementById(divID), mapOptions); // set custom map type if specified
215
+
216
+ if (this.mapTypeId in this.mapTypes) {
217
+ map.mapTypes.set(this.mapTypeId, this.mapTypes[this.mapTypeId]._styled_map);
218
+ }
219
+
220
+ return map;
221
+ };
222
+ /**
223
+ * load a map from a KML file and add as a layer on the Google Maps map
224
+ * @param {String} kmlFileURL
225
+ * @return {google.maps.KmlLayer}
226
+ */
227
+
228
+
229
+ this.loadKmlMap = function (kmlFileURL) {
230
+ // load KML file as a layer and add to map
231
+ var self = this;
232
+ var options = {
233
+ map: map,
234
+ url: kmlFileURL
235
+ };
236
+ /**
237
+ * reset the map back to the recorded centre coordinates -- only used when centre is set for map
238
+ */
239
+
240
+ function resetCentre() {
241
+ self.setCenter(new google.maps.LatLng(self.kmlCentre[0], self.kmlCentre[1]));
242
+ }
243
+ /**
244
+ * update centre of map from bounds on KML layer -- not called when centre is set for map
245
+ */
246
+
247
+
248
+ function recordKmlCentre() {
249
+ centre = kmlLayer.getDefaultViewport().getCenter();
250
+ } // if a centre has been set, stop the viewport from changing and add the centre to map options
251
+
252
+
253
+ if (this.kmlCentre) {
254
+ options.preserveViewport = true;
255
+ options.center = new google.maps.LatLng(this.kmlCentre[0], this.kmlCentre[1]);
256
+ }
257
+
258
+ kmlLayer = new google.maps.KmlLayer(options); // listen for KML layer loaded event
259
+
260
+ google.maps.event.addListenerOnce(kmlLayer, "defaultviewport_changed", this.kmlCentre ? resetCentre : recordKmlCentre);
261
+ return kmlLayer;
262
+ }; // load localisations if they haven't already been loaded
263
+
264
+
265
+ if (!this.localised && "flxmap" in window) {
266
+ this.localise();
267
+ } // set map defaults
268
+
269
+
270
+ this.mapTypeId = google.maps.MapTypeId.ROADMAP;
271
+ this.mapTypeControl = true; // no control for changing map type
272
+
273
+ this.scaleControl = false; // no control for changing scale
274
+
275
+ this.panControl = false; // no control for panning
276
+
277
+ this.zoomControl = true; // show control for zooming
278
+
279
+ this.zoomControlStyle = "small"; // from "small", "large", "default"
280
+
281
+ this.streetViewControl = false; // no control for street view
282
+
283
+ this.fullscreen = true; // show control for full-screen view
284
+
285
+ this.gestureHandling = undefined; // defaults to cooperative
286
+
287
+ this.scrollwheel = undefined; // deprecated; use gestureHandling instead
288
+
289
+ this.draggable = undefined; // deprecated; use gestureHandling instead
290
+
291
+ this.dblclickZoom = undefined; // deprecated; use gestureHandling instead
292
+
293
+ this.zoom = 16; // zoom level, smaller is closer
294
+
295
+ this.markerTitle = ""; // title for marker info window
296
+
297
+ this.markerDescription = ""; // description for marker info window
298
+
299
+ this.markerHTML = ""; // HTML for marker info window (overrides title and description)
300
+
301
+ this.markerLink = ""; // link for marker title
302
+
303
+ this.markerLinkTarget = ""; // link target for marker link, e.g. _blank
304
+
305
+ this.markerLinkText = false; // link text for marker link, overriding default text
306
+
307
+ this.markerIcon = ""; // link for marker icon, leave blank for default
308
+
309
+ this.markerShowInfo = true; // if have infowin for marker, show it immediately
310
+
311
+ this.markerAnimation = "drop"; // marker animation - bounce, drop, none
312
+
313
+ this.markerDirections = false; // show directions link in info window
314
+
315
+ this.markerDirectionsShow = false; // show directions as soon as page loads
316
+
317
+ this.markerDirectionsDefault = ""; // default from: location for directions
318
+
319
+ this.markerAddress = ""; // address of marker, if given
320
+
321
+ this.targetFix = true; // remove target="_blank" from links on KML map
322
+
323
+ this.dirService = false;
324
+ this.dirRenderer = false;
325
+ this.dirDraggable = false; // directions route is draggable (for alternate routing)
326
+
327
+ this.dirSuppressMarkers = false; // suppress A/B markers on directions route
328
+
329
+ this.dirShowSteps = true; // show the directions steps (turn-by-turn)
330
+
331
+ this.dirShowSearch = true; // show the directions form for searching directions
332
+
333
+ this.dirTravelMode = "driving"; // can be bicycling, driving, transit, walking
334
+
335
+ this.dirUnitSystem = undefined; // can be imperial or metric
336
+
337
+ this.region = "";
338
+ this.locale = "en";
339
+ this.localeActive = false;
340
+ this.kmlCentre = false;
341
+ this.kmlcache = "none";
342
+ };
343
+
344
+ FlexibleMap.prototype = function () {
345
+ "use strict";
346
+
347
+ function stopEvent(event) {
348
+ event.stopPropagation();
349
+ event.preventDefault();
350
+ } // handle hidden maps, trigger a resize on first display
351
+
352
+
353
+ var handleHiddenMap = function () {
354
+ if (typeof MutationObserver === "undefined") {
355
+ return function () {};
356
+ }
357
+
358
+ return function (flxmap, divID) {
359
+ var container = document.getElementById(divID).parentNode;
360
+
361
+ function isHidden(element) {
362
+ var style = window.getComputedStyle(element);
363
+ return style.display === "none" || style.visibility === "hidden";
364
+ } // only need to watch and act if the parent container is hidden from display
365
+
366
+
367
+ if (isHidden(container)) {
368
+ var observer = new MutationObserver(function (mutations, self) {
369
+ // only proceed if map is visible now
370
+ if (!isHidden(container)) {
371
+ flxmap.redrawOnce(); // stop observing, we're done
372
+
373
+ self.disconnect();
374
+ }
375
+ });
376
+ observer.observe(container, {
377
+ attributes: true,
378
+ attributeFilter: ["style"]
379
+ });
380
+ }
381
+ };
382
+ }();
383
+ /**
384
+ * encode special JavaScript characters, so text is safe when building JavaScript/HTML dynamically
385
+ * NB: conservatively assumes that HTML special characters are unsafe, and encodes them too
386
+ * @param {String} text
387
+ * @return {String}
388
+ */
389
+
390
+
391
+ function encodeJS() {
392
+ /**
393
+ * encode character as Unicode hexadecimal escape sequence
394
+ * @param {String} ch character to encode
395
+ * @return {String}
396
+ */
397
+ function toUnicodeHex(ch) {
398
+ var c = ch.charCodeAt(0);
399
+ var s = c.toString(16); // see if we can use 2-digit hex code
400
+
401
+ if (c < 0x100) {
402
+ return "\\x" + ("00" + s).slice(-2);
403
+ } // must use 4-digit hex code
404
+
405
+
406
+ return "\\u" + ("0000" + s).slice(-4);
407
+ }
408
 
409
+ return function (text) {
410
+ // search for JavaScript and HTML special characters, convert to Unicode hex
411
+ return text.replace(/[\\/"'&<>\x00-\x1f\x7f-\xa0\u2000-\u200f\u2028-\u202f]/g, toUnicodeHex); // eslint-disable-line no-control-regex
412
+ };
413
+ }
414
+ /**
415
+ * add cache buster to KML source link
416
+ * @param {String} url
417
+ * @param {String} caching
418
+ * @return {String}
419
+ */
420
+
421
+
422
+ function kmlCacheBuster(url, caching) {
423
+ var matches = /^(\d+)\s*(minute|hour|day)s?$/.exec(caching);
424
+ var buster;
425
+
426
+ if (matches) {
427
+ var milliseconds = new Date().getTime();
428
+ var multiplier = +matches[1];
429
+
430
+ switch (matches[2]) {
431
+ case "minute":
432
+ // can't be less than 5 minutes
433
+ if (multiplier < 5) {
434
+ multiplier = 5;
435
+ }
436
+
437
+ buster = milliseconds / (60000 * multiplier);
438
+ break;
439
+
440
+ case "hour":
441
+ buster = milliseconds / (3600000 * multiplier);
442
+ break;
443
+
444
+ case "day":
445
+ buster = milliseconds / (86400000 * multiplier);
446
+ break;
447
+
448
+ default:
449
+ buster = false;
450
+ break;
451
+ }
452
+
453
+ if (buster) {
454
+ buster = Math.floor(buster);
455
+ url += (url.indexOf("?") > -1 ? "&" : "?") + "nocache=" + buster; // eslint-disable-line no-param-reassign
456
+ }
457
+ }
458
+
459
+ return url;
460
+ }
461
+
462
+ return {
463
+ constructor: FlexibleMap,
464
+
465
+ /**
466
+ * collection of locale / phrase mapping for internationalisation of messages
467
+ */
468
+ i18n: {},
469
+
470
+ /**
471
+ * collection of custom Google Maps map types for styling maps
472
+ */
473
+ mapTypes: {},
474
+ localised: false,
475
+ // set to true once localisations have been loaded
476
+
477
+ /**
478
+ * load localisations into class prototype
479
+ */
480
+ localise: function localise() {
481
+ // load translations
482
+ if ("i18n" in flxmap) {
483
+ FlexibleMap.prototype.i18n = flxmap.i18n;
484
+ } // load custom map types
485
+
486
+
487
+ if ("mapTypes" in flxmap) {
488
+ var mapTypes = flxmap.mapTypes;
489
+
490
+ for (var key in mapTypes) {
491
+ mapTypes[key]._styled_map = new google.maps.StyledMapType(mapTypes[key].styles, mapTypes[key].options);
492
+ }
493
+
494
+ FlexibleMap.prototype.mapTypes = mapTypes;
495
+ }
496
+
497
+ FlexibleMap.prototype.localised = true;
498
+ },
499
+
500
+ /**
501
+ * set the locale used for i18n phrase lookup, picking the best match
502
+ * @param {String} localeWanted the locale wanted, e.g. en-AU, da-DK, sv
503
+ * @return {String} the locale that will be used (nearest match, or default if none)
504
+ */
505
+ setlocale: function setlocale(localeWanted) {
506
+ this.locale = localeWanted; // attempt to set this locale as active
507
+
508
+ if (localeWanted in this.i18n) {
509
+ this.localeActive = localeWanted;
510
+ } else {
511
+ // not found, so try simplified locale
512
+ if (localeWanted.substr(0, 2) in this.i18n) {
513
+ this.localeActive = localeWanted;
514
+ } else {
515
+ // still not found
516
+ this.localeActive = false;
517
+ }
518
+ }
519
+
520
+ return this.localeActive;
521
+ },
522
+
523
+ /**
524
+ * get phrase from the current locale domain, or the default domain (en) if not found
525
+ * @param {String} key the key for the desired phrase
526
+ * @return {String}
527
+ */
528
+ gettext: function gettext(key) {
529
+ var locale = this.localeActive;
530
+
531
+ if (locale && key in this.i18n[locale]) {
532
+ return this.i18n[locale][key];
533
+ }
534
+
535
+ return key;
536
+ },
537
+
538
+ /**
539
+ * show a map based on a KML file
540
+ * @param {String} divID the ID of the div that will contain the map
541
+ * @param {String} kmlFileURL path to the KML file to load
542
+ * @param {Number} zoom [optional] zoom level
543
+ */
544
+ showKML: function showKML(divID, kmlFileURL, zoom) {
545
+ this.zoom = zoom; // will be falsey if zoom argument is undefined
546
+
547
+ var self = this;
548
+ var varName = document.getElementById(divID).getAttribute("data-flxmap");
549
+ var map = this.showMap(divID, [0, 0]);
550
+ var kmlLayer = this.loadKmlMap(kmlCacheBuster(kmlFileURL, this.kmlcache));
551
+ handleHiddenMap(this, divID); // set zoom if specified
552
+
553
+ if (typeof zoom != "undefined") {
554
+ // listen for zoom changing to fit markers on KML layer, force it back to what we asked for
555
+ google.maps.event.addListenerOnce(map, "zoom_changed", function () {
556
+ map.setZoom(zoom);
557
+ self.zoom = zoom;
558
+ });
559
+ } // add a directions service if needed
560
+
561
+
562
+ if (this.markerDirections || this.markerDirectionsShow) {
563
+ this.startDirService(map);
564
+ } // customise the infowindow as required; can do this on click event on KML layer (thanks, Stack Overflow!)
565
+
566
+
567
+ google.maps.event.addListener(kmlLayer, 'click', function (kmlEvent) {
568
+ var featureData = kmlEvent.featureData; // NB: since Google Maps API v3.9 the info window HTML is precomposed before this event occurs,
569
+ // so just changing the description won't change infowindow
570
+
571
+ if (!featureData._flxmapOnce) {
572
+ // add a flag to stop doing this on every click; once is enough
573
+ featureData._flxmapOnce = true; // stop links opening in a new window
574
+
575
+ if (self.targetFix && featureData.description) {
576
+ var reTargetFix = / target="_blank"/ig;
577
+ featureData.description = featureData.description.replace(reTargetFix, "");
578
+ featureData.infoWindowHtml = featureData.infoWindowHtml.replace(reTargetFix, "");
579
+ } // if we're showing directions, add directions link to marker description
580
+
581
+
582
+ if (self.markerDirections) {
583
+ var latLng = kmlEvent.latLng;
584
+ var params = latLng.lat() + ',' + latLng.lng() + ",'" + encodeJS(featureData.name) + "',true";
585
+ var a = '<br /><a href="#" onclick="' + varName + '.showDirections(' + params + '); return false;">' + self.gettext("Directions") + '</a>';
586
+ featureData.infoWindowHtml = featureData.infoWindowHtml.replace(/<\/div><\/div>$/i, a + "</div></div>");
587
+ }
588
+ }
589
+ });
590
+ },
591
+
592
+ /**
593
+ * show a map centred at latitude / longitude and with marker at latitude / longitude
594
+ * @param {String} divID the ID of the div that will contain the map
595
+ * @param {Array} centre an array of two integers: [ latitude, longitude ]
596
+ * @param {Array} marker an array of two integers: [ latitude, longitude ]
597
+ */
598
+ showMarker: function showMarker(divID, centre, marker) {
599
+ var map = this.showMap(divID, centre);
600
+ var markerLocation = new google.maps.LatLng(marker[0], marker[1]);
601
+ var options = {
602
+ map: map,
603
+ position: markerLocation,
604
+ icon: this.markerIcon
605
+ };
606
+ var Animation = google.maps.Animation;
607
+
608
+ switch (this.markerAnimation) {
609
+ case "drop":
610
+ options.animation = Animation.DROP;
611
+ break;
612
+
613
+ case "bounce":
614
+ options.animation = Animation.BOUNCE;
615
+ break;
616
+ }
617
+
618
+ var point = new google.maps.Marker(options);
619
+ this.setMarkerPoint(point);
620
+ this.setMarkerLocation(markerLocation);
621
+ handleHiddenMap(this, divID);
622
+
623
+ if (!this.markerTitle) {
624
+ this.markerTitle = this.markerAddress;
625
+ }
626
+
627
+ if (this.markerTitle || this.markerHTML || this.markerDescription || this.markerLink || this.markerDirections) {
628
+ var self = this;
629
+ var container = document.createElement("DIV");
630
+ var element, a;
631
+ container.className = "flxmap-infowin"; // heading for info window
632
+
633
+ element = document.createElement("DIV");
634
+ element.className = "flxmap-marker-title";
635
+
636
+ if (this.markerTitle) {
637
+ element.appendChild(document.createTextNode(this.markerTitle)); // add tooltip title for marker
638
+
639
+ point.setTitle(this.markerTitle);
640
+ }
641
+
642
+ container.appendChild(element); // add precomposed HTML for infowindow
643
+
644
+ if (this.markerHTML) {
645
+ element = document.createElement("DIV");
646
+ element.innerHTML = this.markerHTML;
647
+ container.appendChild(element);
648
+ } // body of info window, with link
649
+
650
+
651
+ if (this.markerDescription || this.markerLink) {
652
+ element = document.createElement("DIV");
653
+ element.className = "flxmap-marker-link";
654
+
655
+ if (this.markerDescription) {
656
+ var lines = this.markerDescription.split("\n");
657
+
658
+ for (var i = 0, len = lines.length; i < len; i++) {
659
+ if (i > 0) {
660
+ element.appendChild(document.createElement("BR"));
661
+ }
662
+
663
+ element.appendChild(document.createTextNode(lines[i]));
664
+ }
665
+
666
+ if (this.markerLink) {
667
+ element.appendChild(document.createElement("BR"));
668
+ }
669
+ }
670
+
671
+ if (this.markerLink) {
672
+ a = document.createElement("A");
673
+ a.href = this.markerLink;
674
+
675
+ if (this.markerLinkTarget) {
676
+ a.target = this.markerLinkTarget;
677
+ }
678
+
679
+ a.appendChild(document.createTextNode(this.markerLinkText || this.gettext("Click for details")));
680
+ element.appendChild(a);
681
+ }
682
+
683
+ container.appendChild(element);
684
+ } // add a link for directions if wanted
685
+
686
+
687
+ if (this.markerDirections) {
688
+ element = document.createElement("DIV");
689
+ element.className = "flxmap-directions-link";
690
+ a = document.createElement("A");
691
+ a.href = "#";
692
+ a.dataLatitude = marker[0];
693
+ a.dataLongitude = marker[1];
694
+ a.addEventListener("click", function (event) {
695
+ stopEvent(event);
696
+ self.showDirections(this.dataLatitude, this.dataLongitude, true);
697
+ });
698
+ a.appendChild(document.createTextNode(this.gettext("Directions")));
699
+ element.appendChild(a);
700
+ container.appendChild(element);
701
+ }
702
+
703
+ var infowin = new google.maps.InfoWindow({
704
+ content: container
705
+ });
706
+ this.setMarkerInfowin(infowin);
707
+
708
+ if (this.markerShowInfo) {
709
+ // open after map is loaded, so that infowindow will auto-pan and won't be cropped at top
710
+ google.maps.event.addListenerOnce(map, "tilesloaded", function () {
711
+ infowin.open(map, point);
712
+ });
713
+ }
714
+
715
+ google.maps.event.addListener(point, "click", function () {
716
+ infowin.open(map, point);
717
+ }); // find Google link and append marker info, modern browsers only!
718
+ // NB: Google link is set before initial map idle event, and reset each time the map centre changes
719
+
720
+ var googleLink = function googleLink() {
721
+ self.updateGoogleLink();
722
+ };
723
+
724
+ google.maps.event.addListener(map, "idle", googleLink);
725
+ google.maps.event.addListener(map, "center_changed", googleLink);
726
+ google.maps.event.addListenerOnce(map, "tilesloaded", googleLink);
727
+ } // add a directions service if needed
728
+
729
+
730
+ if (this.markerDirections || this.markerDirectionsShow) {
731
+ this.startDirService(map); // show directions immediately if required
732
+
733
+ if (this.markerDirectionsShow) {
734
+ this.showDirections(marker[0], marker[1], false);
735
+ }
736
+ }
737
+ },
738
+
739
+ /**
740
+ * show a map centred at address
741
+ * @param {String} divID the ID of the div that will contain the map
742
+ * @param {String} address the address (should return a unique location in Google Maps!)
743
+ */
744
+ showAddress: function showAddress(divID, address) {
745
+ var self = this;
746
+ var geocoder = new google.maps.Geocoder();
747
+ this.markerAddress = address;
748
+
749
+ if (this.markerTitle === "") {
750
+ this.markerTitle = address;
751
+ }
752
+
753
+ geocoder.geocode({
754
+ address: address,
755
+ region: this.region
756
+ }, function (results, status) {
757
+ if (status === google.maps.GeocoderStatus.OK) {
758
+ var location = results[0].geometry.location;
759
+ var centre = [location.lat(), location.lng()];
760
+ self.showMarker(divID, centre, centre);
761
+ } else {
762
+ window.alert("Map address returns error: " + status);
763
+ }
764
+ });
765
+ },
766
+
767
+ /**
768
+ * set query parameters on Google link to maps -- modern browsers only
769
+ * NB: will only set the query parameters when Google link doesn't have them already;
770
+ * Google link is set before initial map idle event, and reset each time the map centre changes
771
+ */
772
+ updateGoogleLink: function updateGoogleLink() {
773
+ if ("querySelectorAll" in document) {
774
+ try {
775
+ var _flxmap = this.getMap().getDiv();
776
+
777
+ var location = this.getMarkerLocation();
778
+
779
+ var googleLinks = _flxmap.querySelectorAll("a[href*='maps.google.com/maps']:not([href*='mps_dialog']):not([href*='&q='])");
780
+
781
+ var query = encodeURIComponent((this.markerAddress ? this.markerAddress : this.markerTitle) + " @" + location.lat() + "," + location.lng());
782
+
783
+ for (var i = 0, len = googleLinks.length; i < len; i++) {
784
+ googleLinks[i].href += "&mrt=loc&iwloc=A&q=" + query;
785
+ }
786
+ } catch (e) {// we don't care about IE8 and earlier...
787
+ }
788
+ }
789
+ },
790
+
791
+ /**
792
+ * tell Google Maps to redraw the map, and centre it back where it started with default zoom
793
+ */
794
+ redraw: function redraw() {
795
+ var map = this.getMap();
796
+ var kmlLayer = this.getKmlLayer();
797
+ google.maps.event.trigger(map, "resize"); // if map is KML, must refit to computed bounds, else use centre and zoom setting
798
+
799
+ if (kmlLayer) {
800
+ map.fitBounds(kmlLayer.getDefaultViewport());
801
+
802
+ if (this.zoom) {
803
+ map.setZoom(this.zoom);
804
+ }
805
+ } else {
806
+ map.setCenter(this.getCenter());
807
+ map.setZoom(this.zoom); // redraw the marker's infowindow if it has one
808
+
809
+ var infowin = this.getMarkerInfowin();
810
+
811
+ if (infowin) {
812
+ infowin.open(map, this.getMarkerPoint());
813
+ }
814
+ }
815
+ },
816
+
817
+ /**
818
+ * create directions service
819
+ */
820
+ startDirService: function startDirService(map) {
821
+ // make sure we have a directions service
822
+ if (!this.dirService) {
823
+ this.dirService = new google.maps.DirectionsService();
824
+ } // make sure we have a directions renderer
825
+
826
+
827
+ if (!this.dirRenderer) {
828
+ this.dirRenderer = new google.maps.DirectionsRenderer({
829
+ map: map,
830
+ draggable: this.dirDraggable,
831
+ suppressMarkers: this.dirSuppressMarkers,
832
+ panel: this.dirShowSteps ? document.getElementById(this.markerDirectionsDiv) : null
833
+ });
834
+ }
835
+ },
836
+
837
+ /**
838
+ * show directions for specified latitude / longitude and title
839
+ * @param {Number} latitude
840
+ * @param {Number} longitude
841
+ * @param {bool} focus [optional]
842
+ */
843
+ showDirections: function showDirections(latitude, longitude, focus) {
844
+ var self = this;
845
+ /**
846
+ * show the directions form to allow directions searches
847
+ */
848
+
849
+ function showDirectionsForm() {
850
+ var panel = document.getElementById(self.markerDirectionsDiv);
851
+ var form = document.createElement("form");
852
+ var p, from; // remove all from panel
853
+
854
+ for (p = panel.lastChild; p; p = panel.lastChild) {
855
+ panel.removeChild(p);
856
+ } // populate form and add to panel
857
+
858
+
859
+ p = document.createElement("p");
860
+ p.appendChild(document.createTextNode(self.gettext("From") + ": "));
861
+ from = document.createElement("input");
862
+ from.type = "text";
863
+ from.name = "from";
864
+ from.value = self.markerDirectionsDefault;
865
+ p.appendChild(from);
866
+ var input = document.createElement("input");
867
+ input.type = "submit";
868
+ input.value = self.gettext("Get directions");
869
+ p.appendChild(input);
870
+ form.appendChild(p);
871
+ panel.appendChild(form); // only focus when asked, to prevent problems autofocusing on elements and scrolling the page!
872
+
873
+ if (focus) {
874
+ from.focus();
875
+ } // handle the form submit
876
+
877
+
878
+ form.addEventListener("submit", function (event) {
879
+ stopEvent(event);
880
+ var from = this.elements.from.value; // only process if something was entered to search on
881
+
882
+ if (/\S/.test(from)) {
883
+ requestDirections(from);
884
+ }
885
+ });
886
+ }
887
+ /**
888
+ * request directions
889
+ * @param {String} from
890
+ */
891
+
892
+
893
+ function requestDirections(from) {
894
+ var dest = self.markerAddress === "" ? new google.maps.LatLng(latitude, longitude) : self.markerAddress;
895
+ var request = {
896
+ origin: from,
897
+ destination: dest
898
+ };
899
+
900
+ if (self.region) {
901
+ request.region = self.region;
902
+ }
903
+
904
+ switch (self.dirTravelMode) {
905
+ case "bicycling":
906
+ request.travelMode = google.maps.TravelMode.BICYCLING;
907
+ break;
908
+
909
+ case "driving":
910
+ request.travelMode = google.maps.TravelMode.DRIVING;
911
+ break;
912
+
913
+ case "transit":
914
+ request.travelMode = google.maps.TravelMode.TRANSIT;
915
+ break;
916
+
917
+ case "walking":
918
+ request.travelMode = google.maps.TravelMode.WALKING;
919
+ break;
920
+ }
921
+
922
+ switch (self.dirUnitSystem) {
923
+ case "imperial":
924
+ request.unitSystem = google.maps.UnitSystem.IMPERIAL;
925
+ break;
926
+
927
+ case "metric":
928
+ request.unitSystem = google.maps.UnitSystem.METRIC;
929
+ break;
930
+ }
931
+
932
+ self.dirService.route(request, dirResponseHander);
933
+ }
934
+ /**
935
+ * handle the response for Google directions service
936
+ * @param {google.maps.DirectionsResult} response
937
+ * @param {Number} status
938
+ */
939
+
940
+
941
+ function dirResponseHander(response, status) {
942
+ var DirectionsStatus = google.maps.DirectionsStatus;
943
+
944
+ switch (status) {
945
+ case DirectionsStatus.OK:
946
+ self.dirRenderer.setDirections(response);
947
+ break;
948
+
949
+ case DirectionsStatus.ZERO_RESULTS:
950
+ window.alert("No route could be found between the origin and destination.");
951
+ break;
952
+
953
+ case DirectionsStatus.OVER_QUERY_LIMIT:
954
+ window.alert("The webpage has gone over the requests limit in too short a period of time.");
955
+ break;
956
+
957
+ case DirectionsStatus.REQUEST_DENIED:
958
+ window.alert("The webpage is not allowed to use the directions service.");
959
+ break;
960
+
961
+ case DirectionsStatus.INVALID_REQUEST:
962
+ window.alert("Invalid directions request.");
963
+ break;
964
+
965
+ case DirectionsStatus.NOT_FOUND:
966
+ window.alert("Origin or destination was not found.");
967
+ break;
968
+
969
+ default:
970
+ window.alert("A directions request could not be processed due to a server error. The request may succeed if you try again.");
971
+ break;
972
+ }
973
+ } // if we have a directions div, show the form for searching it
974
+
975
+
976
+ if (this.markerDirectionsDiv && this.dirShowSearch) {
977
+ showDirectionsForm();
978
+ } // if the from: location is already set, trigger the directions query
979
+
980
+
981
+ if (this.markerDirectionsDefault) {
982
+ requestDirections(this.markerDirectionsDefault);
983
+ }
984
+ }
985
+ };
986
+ }();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
js/flexible-map.min.js CHANGED
@@ -1,4 +1,4 @@
1
  // Flexible Map
2
  // https://flexible-map.webaware.net.au/
3
 
4
- window.FlexibleMap=function(){"use strict";var n,a,t,i,r,o,e=!1;this.getMap=function(){return n},this.getCenter=function(){return a},this.setCenter=function(e){a=e,n.setCenter(a)},this.setMarkerLocation=function(e){t=e},this.getMarkerLocation=function(){return t},this.setMarkerPoint=function(e){i=e},this.getMarkerPoint=function(){return i},this.setMarkerInfowin=function(e){r=e},this.getMarkerInfowin=function(){return r},this.getKmlLayer=function(){return o},this.redrawOnce=function(){e||(e=!0,this.redraw())},this.showMap=function(e,t){a=new google.maps.LatLng(t[0],t[1]);var i,r={small:google.maps.ZoomControlStyle.SMALL,large:google.maps.ZoomControlStyle.LARGE,default:google.maps.ZoomControlStyle.DEFAULT},o=r.small;return this.zoomControlStyle in r&&(o=r[this.zoomControlStyle]),i=function(e,t){if(void 0!==t.gestureHandling)e.gestureHandling=t.gestureHandling;else if(void 0===t.draggable&&void 0===t.dblclickZoom&&void 0===t.scrollwheel)e.gestureHandling="cooperative";else{var i=void 0===t.draggable||t.draggable,r=void 0!==t.dblclickZoom&&!t.draggable,o=void 0!==t.scrollwheel&&t.scrollwheel;i||!r||o?i&&!r&&o?e.gestureHandling="greedy":(e.draggable=i,e.disableDoubleClickZoom=r,e.scrollwheel=o):e.gestureHandling="none"}return e}(i={mapTypeId:this.mapTypeId,mapTypeControl:this.mapTypeControl,scaleControl:this.scaleControl,panControl:this.panControl,streetViewControl:this.streetViewControl,zoomControl:this.zoomControl,zoomControlOptions:{style:o},fullscreenControl:this.fullscreen,center:a,zoom:this.zoom},this),this.mapTypeIds&&(i.mapTypeControlOptions={mapTypeIds:this.mapTypeIds.split(",")}),n=new google.maps.Map(document.getElementById(e),i),this.mapTypeId in this.mapTypes&&n.mapTypes.set(this.mapTypeId,this.mapTypes[this.mapTypeId]._styled_map),n},this.loadKmlMap=function(e){var t=this,i={map:n,url:e};return this.kmlCentre&&(i.preserveViewport=!0,i.center=new google.maps.LatLng(this.kmlCentre[0],this.kmlCentre[1])),o=new google.maps.KmlLayer(i),google.maps.event.addListenerOnce(o,"defaultviewport_changed",this.kmlCentre?function(){t.setCenter(new google.maps.LatLng(t.kmlCentre[0],t.kmlCentre[1]))}:function(){a=o.getDefaultViewport().getCenter()}),o},!this.localised&&"flxmap"in window&&this.localise(),this.mapTypeId=google.maps.MapTypeId.ROADMAP,this.mapTypeControl=!0,this.scaleControl=!1,this.panControl=!1,this.zoomControl=!0,this.zoomControlStyle="small",this.streetViewControl=!1,this.fullscreen=!0,this.gestureHandling=void 0,this.scrollwheel=void 0,this.draggable=void 0,this.dblclickZoom=void 0,this.zoom=16,this.markerTitle="",this.markerDescription="",this.markerHTML="",this.markerLink="",this.markerLinkTarget="",this.markerLinkText=!1,this.markerIcon="",this.markerShowInfo=!0,this.markerAnimation="drop",this.markerDirections=!1,this.markerDirectionsShow=!1,this.markerDirectionsDefault="",this.markerAddress="",this.targetFix=!0,this.dirService=!1,this.dirRenderer=!1,this.dirDraggable=!1,this.dirSuppressMarkers=!1,this.dirShowSteps=!0,this.dirShowSearch=!0,this.dirTravelMode="driving",this.dirUnitSystem=void 0,this.region="",this.locale="en",this.localeActive=!1,this.kmlCentre=!1,this.kmlcache="none"},FlexibleMap.prototype=function(){"use strict";var k,v,w;document.addEventListener?(k=function(e,t,i){e.addEventListener(t,i,!1)},v=function(e){e.stopPropagation(),e.preventDefault()}):document.attachEvent&&(k=function(e,t,i){e.attachEvent("on"+t,function(){i.call(e,window.event)})},v=function(e){e.cancelBubble=!0,e.returnValue=0}),w="undefined"!=typeof MutationObserver?function(i,e){var r=document.getElementById(e).parentNode;function o(e){var t=window.getComputedStyle(e);return"none"===t.display||"hidden"===t.visibility}o(r)&&new MutationObserver(function(e,t){o(r)||(i.redrawOnce(),t.disconnect())}).observe(r,{attributes:!0,attributeFilter:["style"]})}:function(){};var l=function(){function t(e){var t=e.charCodeAt(0),i=t.toString(16);return t<256?"\\x"+("00"+i).slice(-2):"\\u"+("0000"+i).slice(-4)}return function(e){return e.replace(/[\\/"'&<>\x00-\x1f\x7f-\xa0\u2000-\u200f\u2028-\u202f]/g,t)}}();return{constructor:FlexibleMap,i18n:{},mapTypes:{},localised:!1,localise:function(){var e,t;if("i18n"in flxmap&&(FlexibleMap.prototype.i18n=flxmap.i18n),"mapTypes"in flxmap){for(e in t=flxmap.mapTypes)t[e]._styled_map=new google.maps.StyledMapType(t[e].styles,t[e].options);FlexibleMap.prototype.mapTypes=t}FlexibleMap.prototype.localised=!0},setlocale:function(e){return(this.locale=e)in this.i18n?this.localeActive=e:e.substr(0,2)in this.i18n?this.localeActive=e:this.localeActive=!1,this.localeActive},gettext:function(e){var t=this.localeActive;return t&&e in this.i18n[t]?this.i18n[t][e]:e},showKML:function(e,t,i){void 0!==i&&(this.zoom=i);var a=this,r=document.getElementById(e),s=r.getAttribute("data-flxmap"),o=this.showMap(e,[0,0]),n=this.loadKmlMap(function(e,t){var i,r,o,n=/^(\d+)\s*(minute|hour|day)s?$/.exec(t);if(n){switch(i=(new Date).getTime(),o=+n[1],n[2]){case"minute":o<5&&(o=5),r=i/(6e4*o);break;case"hour":r=i/(36e5*o);break;case"day":r=i/(864e5*o);break;default:r=!1}r&&(r=Math.floor(r),e+=(-1<e.indexOf("?")?"&":"?")+"nocache="+r)}return e}(t,this.kmlcache));w(this,e),void 0!==i&&google.maps.event.addListenerOnce(o,"zoom_changed",function(){o.setZoom(i),a.zoom=i}),(this.markerDirections||this.markerDirectionsShow)&&this.startDirService(o),google.maps.event.addListener(n,"click",function(e){var t=e.featureData;if(!t._flxmapOnce){if(t._flxmapOnce=!0,a.targetFix&&t.description){var i=/ target="_blank"/gi;t.description=t.description.replace(i,""),t.infoWindowHtml=t.infoWindowHtml.replace(i,"")}if(a.markerDirections){var r=e.latLng,o=r.lat()+","+r.lng()+",'"+l(t.name)+"',true",n='<br /><a href="#" data-flxmap-fix-opera="1" onclick="'+s+".showDirections("+o+'); return false;">'+a.gettext("Directions")+"</a>";t.infoWindowHtml=t.infoWindowHtml.replace(/<\/div><\/div>$/i,n+"</div></div>")}}}),window.opera&&this.markerDirections&&k(r,"click",function(e){e.target.getAttribute("data-flxmap-fix-opera")&&v(e)})},showMarker:function(e,t,i){var r=this.showMap(e,t),o=new google.maps.LatLng(i[0],i[1]),n={map:r,position:o,icon:this.markerIcon},a=google.maps.Animation;switch(this.markerAnimation){case"drop":n.animation=a.DROP;break;case"bounce":n.animation=a.BOUNCE}var s=new google.maps.Marker(n);if(this.setMarkerPoint(s),this.setMarkerLocation(o),w(this,e),this.markerTitle||(this.markerTitle=this.markerAddress),this.markerTitle||this.markerHTML||this.markerDescription||this.markerLink||this.markerDirections){var l,d,c,m,h,p,g=this,u=document.createElement("DIV");if(u.className="flxmap-infowin",(h=document.createElement("DIV")).className="flxmap-marker-title",this.markerTitle&&(h.appendChild(document.createTextNode(this.markerTitle)),s.setTitle(this.markerTitle)),u.appendChild(h),this.markerHTML&&((h=document.createElement("DIV")).innerHTML=this.markerHTML,u.appendChild(h)),this.markerDescription||this.markerLink){if((h=document.createElement("DIV")).className="flxmap-marker-link",this.markerDescription){for(l=0,d=(c=this.markerDescription.split("\n")).length;l<d;l++)0<l&&h.appendChild(document.createElement("BR")),h.appendChild(document.createTextNode(c[l]));this.markerLink&&h.appendChild(document.createElement("BR"))}this.markerLink&&((p=document.createElement("A")).href=this.markerLink,this.markerLinkTarget&&(p.target=this.markerLinkTarget),p.appendChild(document.createTextNode(this.markerLinkText||this.gettext("Click for details"))),h.appendChild(p)),u.appendChild(h)}this.markerDirections&&((h=document.createElement("DIV")).className="flxmap-directions-link",(p=document.createElement("A")).href="#",p.dataLatitude=i[0],p.dataLongitude=i[1],k(p,"click",function(e){v(e),g.showDirections(this.dataLatitude,this.dataLongitude,!0)}),p.appendChild(document.createTextNode(this.gettext("Directions"))),h.appendChild(p),u.appendChild(h)),m=new google.maps.InfoWindow({content:u}),this.setMarkerInfowin(m),this.markerShowInfo&&google.maps.event.addListenerOnce(r,"tilesloaded",function(){m.open(r,s)}),google.maps.event.addListener(s,"click",function(){m.open(r,s)});var f=function(){g.updateGoogleLink()};google.maps.event.addListener(r,"idle",f),google.maps.event.addListener(r,"center_changed",f),google.maps.event.addListenerOnce(r,"tilesloaded",f)}(this.markerDirections||this.markerDirectionsShow)&&(this.startDirService(r),this.markerDirectionsShow&&this.showDirections(i[0],i[1],!1))},showAddress:function(o,e){var n=this,t=new google.maps.Geocoder;this.markerAddress=e,""===this.markerTitle&&(this.markerTitle=e),t.geocode({address:e,region:this.region},function(e,t){if(t===google.maps.GeocoderStatus.OK){var i=e[0].geometry.location,r=[i.lat(),i.lng()];n.showMarker(o,r,r)}else window.alert("Map address returns error: "+t)})},updateGoogleLink:function(){if("querySelectorAll"in document)try{for(var e=this.getMap().getDiv(),t=this.getMarkerLocation(),i=e.querySelectorAll("a[href*='maps.google.com/maps']:not([href*='mps_dialog']):not([href*='&q='])"),r=0,o=i.length,n=encodeURIComponent((this.markerAddress?this.markerAddress:this.markerTitle)+" @"+t.lat()+","+t.lng());r<o;r++)i[r].href+="&mrt=loc&iwloc=A&q="+n}catch(e){}},redraw:function(){var e=this.getMap(),t=this.getKmlLayer();if(google.maps.event.trigger(e,"resize"),t)e.fitBounds(t.getDefaultViewport());else{e.setCenter(this.getCenter()),e.setZoom(this.zoom);var i=this.getMarkerInfowin();i&&i.open(e,this.getMarkerPoint())}},startDirService:function(e){this.dirService||(this.dirService=new google.maps.DirectionsService),this.dirRenderer||(this.dirRenderer=new google.maps.DirectionsRenderer({map:e,draggable:this.dirDraggable,suppressMarkers:this.dirSuppressMarkers,panel:this.dirShowSteps?document.getElementById(this.markerDirectionsDiv):null}))},showDirections:function(i,r,n){var a=this;function s(e){var t={origin:e,destination:""===a.markerAddress?new google.maps.LatLng(i,r):a.markerAddress};switch(a.region&&(t.region=a.region),a.dirTravelMode){case"bicycling":t.travelMode=google.maps.TravelMode.BICYCLING;break;case"driving":t.travelMode=google.maps.TravelMode.DRIVING;break;case"transit":t.travelMode=google.maps.TravelMode.TRANSIT;break;case"walking":t.travelMode=google.maps.TravelMode.WALKING}switch(a.dirUnitSystem){case"imperial":t.unitSystem=google.maps.UnitSystem.IMPERIAL;break;case"metric":t.unitSystem=google.maps.UnitSystem.METRIC}a.dirService.route(t,o)}function o(e,t){var i=google.maps.DirectionsStatus;switch(t){case i.OK:a.dirRenderer.setDirections(e);break;case i.ZERO_RESULTS:window.alert("No route could be found between the origin and destination.");break;case i.OVER_QUERY_LIMIT:window.alert("The webpage has gone over the requests limit in too short a period of time.");break;case i.REQUEST_DENIED:window.alert("The webpage is not allowed to use the directions service.");break;case i.INVALID_REQUEST:window.alert("Invalid directions request.");break;case i.NOT_FOUND:window.alert("Origin or destination was not found.");break;default:window.alert("A directions request could not be processed due to a server error. The request may succeed if you try again.")}}this.markerDirectionsDiv&&this.dirShowSearch&&function(){var e,t,i,r=document.getElementById(a.markerDirectionsDiv),o=document.createElement("form");for(t=r.lastChild;t;t=r.lastChild)r.removeChild(t);(t=document.createElement("p")).appendChild(document.createTextNode(a.gettext("From")+": ")),(i=document.createElement("input")).type="text",i.name="from",i.value=a.markerDirectionsDefault,t.appendChild(i),(e=document.createElement("input")).type="submit",e.value=a.gettext("Get directions"),t.appendChild(e),o.appendChild(t),r.appendChild(o),void 0===o.elements.from&&(o.elements.from=i),n&&i.focus(),k(o,"submit",function(e){v(e);var t=this.elements.from.value;/\S/.test(t)&&s(t)})}(),this.markerDirectionsDefault&&s(this.markerDirectionsDefault)}}}();
1
  // Flexible Map
2
  // https://flexible-map.webaware.net.au/
3
 
4
+ "use strict";window.FlexibleMap=function(){var e,t,i,r,o,n,a=!1;this.getMap=function(){return e},this.getCenter=function(){return t},this.setCenter=function(i){t=i,e.setCenter(t)},this.setMarkerLocation=function(e){i=e},this.getMarkerLocation=function(){return i},this.setMarkerPoint=function(e){r=e},this.getMarkerPoint=function(){return r},this.setMarkerInfowin=function(e){o=e},this.getMarkerInfowin=function(){return o},this.getKmlLayer=function(){return n},this.redrawOnce=function(){a||(a=!0,this.redraw())},this.showMap=function(i,r){t=new google.maps.LatLng(r[0],r[1]);var o={small:google.maps.ZoomControlStyle.SMALL,large:google.maps.ZoomControlStyle.LARGE,default:google.maps.ZoomControlStyle.DEFAULT},n=o.small;this.zoomControlStyle in o&&(n=o[this.zoomControlStyle]);var a={mapTypeId:this.mapTypeId,mapTypeControl:this.mapTypeControl,scaleControl:this.scaleControl,panControl:this.panControl,streetViewControl:this.streetViewControl,zoomControl:this.zoomControl,zoomControlOptions:{style:n},fullscreenControl:this.fullscreen,center:t,zoom:this.zoom};return a=function(e,t){if(void 0!==t.gestureHandling)e.gestureHandling=t.gestureHandling;else if(void 0===t.draggable&&void 0===t.dblclickZoom&&void 0===t.scrollwheel)e.gestureHandling="cooperative";else{var i=void 0===t.draggable||t.draggable,r=void 0!==t.dblclickZoom&&!t.draggable,o=void 0!==t.scrollwheel&&t.scrollwheel;i||!r||o?i&&!r&&o?e.gestureHandling="greedy":(e.draggable=i,e.disableDoubleClickZoom=r,e.scrollwheel=o):e.gestureHandling="none"}return e}(a,this),this.mapTypeIds&&(a.mapTypeControlOptions={mapTypeIds:this.mapTypeIds.split(",")}),e=new google.maps.Map(document.getElementById(i),a),this.mapTypeId in this.mapTypes&&e.mapTypes.set(this.mapTypeId,this.mapTypes[this.mapTypeId]._styled_map),e},this.loadKmlMap=function(i){var r=this,o={map:e,url:i};return this.kmlCentre&&(o.preserveViewport=!0,o.center=new google.maps.LatLng(this.kmlCentre[0],this.kmlCentre[1])),n=new google.maps.KmlLayer(o),google.maps.event.addListenerOnce(n,"defaultviewport_changed",this.kmlCentre?function(){r.setCenter(new google.maps.LatLng(r.kmlCentre[0],r.kmlCentre[1]))}:function(){t=n.getDefaultViewport().getCenter()}),n},!this.localised&&"flxmap"in window&&this.localise(),this.mapTypeId=google.maps.MapTypeId.ROADMAP,this.mapTypeControl=!0,this.scaleControl=!1,this.panControl=!1,this.zoomControl=!0,this.zoomControlStyle="small",this.streetViewControl=!1,this.fullscreen=!0,this.gestureHandling=void 0,this.scrollwheel=void 0,this.draggable=void 0,this.dblclickZoom=void 0,this.zoom=16,this.markerTitle="",this.markerDescription="",this.markerHTML="",this.markerLink="",this.markerLinkTarget="",this.markerLinkText=!1,this.markerIcon="",this.markerShowInfo=!0,this.markerAnimation="drop",this.markerDirections=!1,this.markerDirectionsShow=!1,this.markerDirectionsDefault="",this.markerAddress="",this.targetFix=!0,this.dirService=!1,this.dirRenderer=!1,this.dirDraggable=!1,this.dirSuppressMarkers=!1,this.dirShowSteps=!0,this.dirShowSearch=!0,this.dirTravelMode="driving",this.dirUnitSystem=void 0,this.region="",this.locale="en",this.localeActive=!1,this.kmlCentre=!1,this.kmlcache="none"},FlexibleMap.prototype=function(){function e(e){e.stopPropagation(),e.preventDefault()}var t="undefined"==typeof MutationObserver?function(){}:function(e,t){var i=document.getElementById(t).parentNode;function r(e){var t=window.getComputedStyle(e);return"none"===t.display||"hidden"===t.visibility}r(i)&&new MutationObserver(function(t,o){r(i)||(e.redrawOnce(),o.disconnect())}).observe(i,{attributes:!0,attributeFilter:["style"]})};return{constructor:FlexibleMap,i18n:{},mapTypes:{},localised:!1,localise:function(){if("i18n"in flxmap&&(FlexibleMap.prototype.i18n=flxmap.i18n),"mapTypes"in flxmap){var e=flxmap.mapTypes;for(var t in e)e[t]._styled_map=new google.maps.StyledMapType(e[t].styles,e[t].options);FlexibleMap.prototype.mapTypes=e}FlexibleMap.prototype.localised=!0},setlocale:function(e){return this.locale=e,e in this.i18n?this.localeActive=e:e.substr(0,2)in this.i18n?this.localeActive=e:this.localeActive=!1,this.localeActive},gettext:function(e){var t=this.localeActive;return t&&e in this.i18n[t]?this.i18n[t][e]:e},showKML:function(e,i,r){this.zoom=r;var o=this,n=document.getElementById(e).getAttribute("data-flxmap"),a=this.showMap(e,[0,0]),s=this.loadKmlMap(function(e,t){var i,r=/^(\d+)\s*(minute|hour|day)s?$/.exec(t);if(r){var o=(new Date).getTime(),n=+r[1];switch(r[2]){case"minute":n<5&&(n=5),i=o/(6e4*n);break;case"hour":i=o/(36e5*n);break;case"day":i=o/(864e5*n);break;default:i=!1}i&&(i=Math.floor(i),e+=(e.indexOf("?")>-1?"&":"?")+"nocache="+i)}return e}(i,this.kmlcache));t(this,e),void 0!==r&&google.maps.event.addListenerOnce(a,"zoom_changed",function(){a.setZoom(r),o.zoom=r}),(this.markerDirections||this.markerDirectionsShow)&&this.startDirService(a),google.maps.event.addListener(s,"click",function(e){var t=e.featureData;if(!t._flxmapOnce){if(t._flxmapOnce=!0,o.targetFix&&t.description){var i=/ target="_blank"/gi;t.description=t.description.replace(i,""),t.infoWindowHtml=t.infoWindowHtml.replace(i,"")}if(o.markerDirections){var r=e.latLng,a=r.lat()+","+r.lng()+",'"+function(){function e(e){var t=e.charCodeAt(0),i=t.toString(16);return t<256?"\\x"+("00"+i).slice(-2):"\\u"+("0000"+i).slice(-4)}return function(t){return t.replace(/[\\/"'&<>\x00-\x1f\x7f-\xa0\u2000-\u200f\u2028-\u202f]/g,e)}}(t.name)+"',true",s='<br /><a href="#" onclick="'+n+".showDirections("+a+'); return false;">'+o.gettext("Directions")+"</a>";t.infoWindowHtml=t.infoWindowHtml.replace(/<\/div><\/div>$/i,s+"</div></div>")}}})},showMarker:function(i,r,o){var n=this.showMap(i,r),a=new google.maps.LatLng(o[0],o[1]),s={map:n,position:a,icon:this.markerIcon},l=google.maps.Animation;switch(this.markerAnimation){case"drop":s.animation=l.DROP;break;case"bounce":s.animation=l.BOUNCE}var d=new google.maps.Marker(s);if(this.setMarkerPoint(d),this.setMarkerLocation(a),t(this,i),this.markerTitle||(this.markerTitle=this.markerAddress),this.markerTitle||this.markerHTML||this.markerDescription||this.markerLink||this.markerDirections){var c,m,h=this,p=document.createElement("DIV");if(p.className="flxmap-infowin",(c=document.createElement("DIV")).className="flxmap-marker-title",this.markerTitle&&(c.appendChild(document.createTextNode(this.markerTitle)),d.setTitle(this.markerTitle)),p.appendChild(c),this.markerHTML&&((c=document.createElement("DIV")).innerHTML=this.markerHTML,p.appendChild(c)),this.markerDescription||this.markerLink){if((c=document.createElement("DIV")).className="flxmap-marker-link",this.markerDescription){for(var g=this.markerDescription.split("\n"),u=0,f=g.length;u<f;u++)u>0&&c.appendChild(document.createElement("BR")),c.appendChild(document.createTextNode(g[u]));this.markerLink&&c.appendChild(document.createElement("BR"))}this.markerLink&&((m=document.createElement("A")).href=this.markerLink,this.markerLinkTarget&&(m.target=this.markerLinkTarget),m.appendChild(document.createTextNode(this.markerLinkText||this.gettext("Click for details"))),c.appendChild(m)),p.appendChild(c)}this.markerDirections&&((c=document.createElement("DIV")).className="flxmap-directions-link",(m=document.createElement("A")).href="#",m.dataLatitude=o[0],m.dataLongitude=o[1],m.addEventListener("click",function(t){e(t),h.showDirections(this.dataLatitude,this.dataLongitude,!0)}),m.appendChild(document.createTextNode(this.gettext("Directions"))),c.appendChild(m),p.appendChild(c));var k=new google.maps.InfoWindow({content:p});this.setMarkerInfowin(k),this.markerShowInfo&&google.maps.event.addListenerOnce(n,"tilesloaded",function(){k.open(n,d)}),google.maps.event.addListener(d,"click",function(){k.open(n,d)});var v=function(){h.updateGoogleLink()};google.maps.event.addListener(n,"idle",v),google.maps.event.addListener(n,"center_changed",v),google.maps.event.addListenerOnce(n,"tilesloaded",v)}(this.markerDirections||this.markerDirectionsShow)&&(this.startDirService(n),this.markerDirectionsShow&&this.showDirections(o[0],o[1],!1))},showAddress:function(e,t){var i=this,r=new google.maps.Geocoder;this.markerAddress=t,""===this.markerTitle&&(this.markerTitle=t),r.geocode({address:t,region:this.region},function(t,r){if(r===google.maps.GeocoderStatus.OK){var o=t[0].geometry.location,n=[o.lat(),o.lng()];i.showMarker(e,n,n)}else window.alert("Map address returns error: "+r)})},updateGoogleLink:function(){if("querySelectorAll"in document)try{for(var e=this.getMap().getDiv(),t=this.getMarkerLocation(),i=e.querySelectorAll("a[href*='maps.google.com/maps']:not([href*='mps_dialog']):not([href*='&q='])"),r=encodeURIComponent((this.markerAddress?this.markerAddress:this.markerTitle)+" @"+t.lat()+","+t.lng()),o=0,n=i.length;o<n;o++)i[o].href+="&mrt=loc&iwloc=A&q="+r}catch(e){}},redraw:function(){var e=this.getMap(),t=this.getKmlLayer();if(google.maps.event.trigger(e,"resize"),t)e.fitBounds(t.getDefaultViewport()),this.zoom&&e.setZoom(this.zoom);else{e.setCenter(this.getCenter()),e.setZoom(this.zoom);var i=this.getMarkerInfowin();i&&i.open(e,this.getMarkerPoint())}},startDirService:function(e){this.dirService||(this.dirService=new google.maps.DirectionsService),this.dirRenderer||(this.dirRenderer=new google.maps.DirectionsRenderer({map:e,draggable:this.dirDraggable,suppressMarkers:this.dirSuppressMarkers,panel:this.dirShowSteps?document.getElementById(this.markerDirectionsDiv):null}))},showDirections:function(t,i,r){var o=this;function n(e){var r={origin:e,destination:""===o.markerAddress?new google.maps.LatLng(t,i):o.markerAddress};switch(o.region&&(r.region=o.region),o.dirTravelMode){case"bicycling":r.travelMode=google.maps.TravelMode.BICYCLING;break;case"driving":r.travelMode=google.maps.TravelMode.DRIVING;break;case"transit":r.travelMode=google.maps.TravelMode.TRANSIT;break;case"walking":r.travelMode=google.maps.TravelMode.WALKING}switch(o.dirUnitSystem){case"imperial":r.unitSystem=google.maps.UnitSystem.IMPERIAL;break;case"metric":r.unitSystem=google.maps.UnitSystem.METRIC}o.dirService.route(r,a)}function a(e,t){var i=google.maps.DirectionsStatus;switch(t){case i.OK:o.dirRenderer.setDirections(e);break;case i.ZERO_RESULTS:window.alert("No route could be found between the origin and destination.");break;case i.OVER_QUERY_LIMIT:window.alert("The webpage has gone over the requests limit in too short a period of time.");break;case i.REQUEST_DENIED:window.alert("The webpage is not allowed to use the directions service.");break;case i.INVALID_REQUEST:window.alert("Invalid directions request.");break;case i.NOT_FOUND:window.alert("Origin or destination was not found.");break;default:window.alert("A directions request could not be processed due to a server error. The request may succeed if you try again.")}}this.markerDirectionsDiv&&this.dirShowSearch&&function(){var t,i,a=document.getElementById(o.markerDirectionsDiv),s=document.createElement("form");for(t=a.lastChild;t;t=a.lastChild)a.removeChild(t);(t=document.createElement("p")).appendChild(document.createTextNode(o.gettext("From")+": ")),(i=document.createElement("input")).type="text",i.name="from",i.value=o.markerDirectionsDefault,t.appendChild(i);var l=document.createElement("input");l.type="submit",l.value=o.gettext("Get directions"),t.appendChild(l),s.appendChild(t),a.appendChild(s),r&&i.focus(),s.addEventListener("submit",function(t){e(t);var i=this.elements.from.value;/\S/.test(i)&&n(i)})}(),this.markerDirectionsDefault&&n(this.markerDirectionsDefault)}}}();
readme.txt CHANGED
@@ -6,8 +6,8 @@ Author URI: https://shop.webaware.com.au/
6
  Donate link: https://shop.webaware.com.au/donations/?donation_for=Flexible+Map
7
  Tags: google, map, maps, google maps, kml
8
  Requires at least: 4.0
9
- Tested up to: 4.9
10
- Stable tag: 1.16.0
11
  Requires PHP: 5.3
12
  License: GPLv2 or later
13
  License URI: https://www.gnu.org/licenses/gpl-2.0.html
@@ -228,18 +228,19 @@ Either turn off CloudFlare Rocketscript :) or install the [Flxmap No Rocketscrip
228
 
229
  ## Upgrade Notice
230
 
231
- ### 1.16.0
232
 
233
- added setting that prevents the plugin from loading the Google Maps API; deprecated attributes `scrollwheel`, `draggable`, `dblclickzoom`, please use `gesturehandling` instead
234
 
235
  ## Changelog
236
 
237
  The full changelog can be found [on GitHub](https://github.com/webaware/flexible-map/blob/master/changelog.md). Recent entries:
238
 
239
- ### 1.16.0
240
 
241
- Released 2018-09-07
242
 
243
- * added: setting that prevents the plugin from loading the Google Maps API; useful for preventing conflicts
244
- * added: attribute [gesturehandling](https://flexible-map.webaware.net.au/manual/attribute-reference/#attr-gesturehandling) for smarter handling of the scroll wheel, drag to pan, double-click to zoom; default = cooperative
245
- * deprecated: attributes `scrollwheel`, `draggable`, `dblclickzoom` are supported but not recommended; please use `gesturehandling` instead
 
6
  Donate link: https://shop.webaware.com.au/donations/?donation_for=Flexible+Map
7
  Tags: google, map, maps, google maps, kml
8
  Requires at least: 4.0
9
+ Tested up to: 5.0
10
+ Stable tag: 1.17.0
11
  Requires PHP: 5.3
12
  License: GPLv2 or later
13
  License URI: https://www.gnu.org/licenses/gpl-2.0.html
228
 
229
  ## Upgrade Notice
230
 
231
+ ### 1.17.0
232
 
233
+ track the latest stable version of the Google Maps API; remove support for ancient browsers (Opera 12, IE < 11); fix KML maps with zoom when hidden in a tab
234
 
235
  ## Changelog
236
 
237
  The full changelog can be found [on GitHub](https://github.com/webaware/flexible-map/blob/master/changelog.md). Recent entries:
238
 
239
+ ### 1.17.0
240
 
241
+ Released 2018-11-19
242
 
243
+ * fixed: map tiles don't redraw for KML maps with zoom when hidden in a tab / accordion
244
+ * changed: use the [current quarterly (stable) version of the Google Maps API](https://developers.google.com/maps/documentation/javascript/versions)
245
+ * changed: remove support for ancient browsers (Opera 12, IE < 11)
246
+ * tested: WordPress 5.0 (no Gutenberg block yet; maybe next release!)