Commit | Line | Data |
---|---|---|
e368b357 AE |
1 | "use strict"; |
2 | ||
80e131ce MS |
3 | /** |
4 | * Location-related classes for WCF | |
5 | * | |
6 | * @author Matthias Schmidt | |
7b7b9764 | 7 | * @copyright 2001-2019 WoltLab GmbH |
80e131ce | 8 | * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> |
5cc1d63e | 9 | * @deprecated 6.0 use `<woltlab-core-google-maps>` instead |
80e131ce MS |
10 | */ |
11 | WCF.Location = { }; | |
12 | ||
13 | /** | |
14 | * Provides location-related utility functions. | |
15 | */ | |
16 | WCF.Location.Util = { | |
17 | /** | |
18 | * Passes the user's current latitude and longitude to the given function | |
19 | * as parameters. If the user's current position cannot be determined, | |
20 | * undefined will be passed as both parameters. | |
21 | * | |
22 | * @param function callback | |
23 | * @param integer timeout | |
24 | */ | |
25 | getLocation: function(callback, timeout) { | |
6ae5ec03 MS |
26 | var $accessUserLocation = WCF.Location.GoogleMaps.Settings.get('accessUserLocation'); |
27 | if (navigator.geolocation && $accessUserLocation !== null && $accessUserLocation) { | |
80e131ce MS |
28 | navigator.geolocation.getCurrentPosition(function(position) { |
29 | callback(position.coords.latitude, position.coords.longitude); | |
30 | }, function() { | |
31 | callback(undefined, undefined); | |
32 | }, { | |
33 | timeout: timeout || 5000 | |
34 | }); | |
35 | } | |
36 | else { | |
37 | callback(undefined, undefined); | |
38 | } | |
39 | } | |
40 | }; | |
41 | ||
42 | /** | |
43 | * Namespace for Google Maps-related classes. | |
44 | */ | |
45 | WCF.Location.GoogleMaps = { }; | |
46 | ||
513029ce MS |
47 | /** |
48 | * After an authentican error, Google Maps tries to call the `gm_authFailure` function. | |
49 | * (see https://developers.google.com/maps/documentation/javascript/events#auth-errors) | |
50 | */ | |
51 | function gm_authFailure() { | |
52 | WCF.System.Event.fireEvent('com.woltlab.wcf.googleMaps', 'authenticationFailure'); | |
53 | }; | |
54 | ||
80e131ce MS |
55 | /** |
56 | * Handles the global Google Maps settings. | |
57 | */ | |
58 | WCF.Location.GoogleMaps.Settings = { | |
59 | /** | |
60 | * Google Maps settings | |
61 | * @var object | |
62 | */ | |
63 | _settings: { }, | |
64 | ||
65 | /** | |
66 | * Returns the value of a certain setting or null if it doesn't exist. | |
67 | * | |
68 | * If no parameter is given, all settings are returned. | |
69 | * | |
70 | * @param string setting | |
71 | * @return mixed | |
72 | */ | |
73 | get: function(setting) { | |
74 | if (setting === undefined) { | |
75 | return this._settings; | |
76 | } | |
77 | ||
78 | if (this._settings[setting] !== undefined) { | |
79 | return this._settings[setting]; | |
80 | } | |
81 | ||
82 | return null; | |
83 | }, | |
84 | ||
85 | /** | |
86 | * Sets the value of a certain setting. | |
87 | * | |
88 | * @param mixed setting | |
89 | * @param mixed value | |
90 | */ | |
91 | set: function(setting, value) { | |
92 | if ($.isPlainObject(setting)) { | |
93 | for (var index in setting) { | |
94 | this._settings[index] = setting[index]; | |
95 | } | |
96 | } | |
97 | else { | |
98 | this._settings[setting] = value; | |
99 | } | |
100 | } | |
101 | }; | |
102 | ||
103 | /** | |
104 | * Handles a Google Maps map. | |
105 | */ | |
106 | WCF.Location.GoogleMaps.Map = Class.extend({ | |
107 | /** | |
108 | * map object for the displayed map | |
109 | * @var google.maps.Map | |
110 | */ | |
111 | _map: null, | |
112 | ||
113 | /** | |
114 | * list of markers on the map | |
115 | * @var array<google.maps.Marker> | |
116 | */ | |
117 | _markers: [ ], | |
118 | ||
119 | /** | |
1615fc2e | 120 | * Initializes a new WCF.Location.Map object. |
80e131ce MS |
121 | * |
122 | * @param string mapContainerID | |
123 | * @param object mapOptions | |
124 | */ | |
125 | init: function(mapContainerID, mapOptions) { | |
126 | this._mapContainer = $('#' + mapContainerID); | |
127 | this._mapOptions = $.extend(true, this._getDefaultMapOptions(), mapOptions); | |
128 | ||
129 | this._map = new google.maps.Map(this._mapContainer[0], this._mapOptions); | |
130 | this._markers = [ ]; | |
131 | ||
17994b04 MS |
132 | // fix maps in mobile sidebars by refreshing the map when displaying |
133 | // the map | |
134 | if (this._mapContainer.parents('.sidebar').length) { | |
632c9d0c TD |
135 | require(['Ui/Screen'], function(UiScreen) { |
136 | UiScreen.on('screen-sm-down', { | |
137 | setup: $.proxy(this._addSidebarMapListener, this) | |
138 | }); | |
139 | }.bind(this)); | |
17994b04 MS |
140 | } |
141 | ||
80e131ce MS |
142 | this.refresh(); |
143 | }, | |
144 | ||
0a2c8393 MS |
145 | /** |
146 | * Adds the event listener to a marker to show the associated info window. | |
147 | * | |
148 | * @param google.maps.Marker marker | |
149 | * @param google.maps.InfoWindow infoWindow | |
150 | */ | |
151 | _addInfoWindowEventListener: function(marker, infoWindow) { | |
152 | google.maps.event.addListener(marker, 'click', $.proxy(function() { | |
153 | infoWindow.open(this._map, marker); | |
154 | }, this)); | |
155 | }, | |
156 | ||
17994b04 MS |
157 | /** |
158 | * Adds click listener to mobile sidebar toggle button to refresh map. | |
159 | */ | |
160 | _addSidebarMapListener: function() { | |
161 | $('.content > .mobileSidebarToggleButton').click($.proxy(this.refresh, this)); | |
162 | }, | |
163 | ||
80e131ce MS |
164 | /** |
165 | * Returns the default map options. | |
166 | * | |
167 | * @return object | |
168 | */ | |
169 | _getDefaultMapOptions: function() { | |
170 | var $defaultMapOptions = { }; | |
171 | ||
172 | // dummy center value | |
ceaa4d38 | 173 | $defaultMapOptions.center = new google.maps.LatLng(WCF.Location.GoogleMaps.Settings.get('defaultLatitude'), WCF.Location.GoogleMaps.Settings.get('defaultLongitude')); |
80e131ce MS |
174 | |
175 | // double click to zoom | |
176 | $defaultMapOptions.disableDoubleClickZoom = WCF.Location.GoogleMaps.Settings.get('disableDoubleClickZoom'); | |
177 | ||
178 | // draggable | |
179 | $defaultMapOptions.draggable = WCF.Location.GoogleMaps.Settings.get('draggable'); | |
180 | ||
181 | // map type | |
182 | switch (WCF.Location.GoogleMaps.Settings.get('mapType')) { | |
183 | case 'map': | |
184 | $defaultMapOptions.mapTypeId = google.maps.MapTypeId.ROADMAP; | |
185 | break; | |
186 | ||
187 | case 'satellite': | |
188 | $defaultMapOptions.mapTypeId = google.maps.MapTypeId.SATELLITE; | |
189 | break; | |
190 | ||
191 | case 'physical': | |
192 | $defaultMapOptions.mapTypeId = google.maps.MapTypeId.TERRAIN; | |
193 | break; | |
194 | ||
195 | case 'hybrid': | |
196 | default: | |
197 | $defaultMapOptions.mapTypeId = google.maps.MapTypeId.HYBRID; | |
198 | break; | |
199 | } | |
200 | ||
201 | /// map type controls | |
202 | $defaultMapOptions.mapTypeControl = WCF.Location.GoogleMaps.Settings.get('mapTypeControl') != 'off'; | |
203 | if ($defaultMapOptions.mapTypeControl) { | |
204 | switch (WCF.Location.GoogleMaps.Settings.get('mapTypeControl')) { | |
205 | case 'dropdown': | |
206 | $defaultMapOptions.mapTypeControlOptions = { | |
207 | style: google.maps.MapTypeControlStyle.DROPDOWN_MENU | |
208 | }; | |
209 | break; | |
210 | ||
211 | case 'horizontalBar': | |
212 | $defaultMapOptions.mapTypeControlOptions = { | |
213 | style: google.maps.MapTypeControlStyle.HORIZONTAL_BAR | |
214 | }; | |
215 | break; | |
216 | ||
217 | default: | |
218 | $defaultMapOptions.mapTypeControlOptions = { | |
219 | style: google.maps.MapTypeControlStyle.DEFAULT | |
220 | }; | |
221 | break; | |
222 | } | |
223 | } | |
224 | ||
225 | // scale control | |
226 | $defaultMapOptions.scaleControl = WCF.Location.GoogleMaps.Settings.get('scaleControl'); | |
227 | $defaultMapOptions.scrollwheel = WCF.Location.GoogleMaps.Settings.get('scrollwheel'); | |
228 | ||
229 | // zoom | |
230 | $defaultMapOptions.zoom = WCF.Location.GoogleMaps.Settings.get('zoom'); | |
231 | ||
232 | return $defaultMapOptions; | |
233 | }, | |
234 | ||
235 | /** | |
236 | * Adds a draggable marker at the given position to the map and returns | |
237 | * the created marker object. | |
238 | * | |
239 | * @param float latitude | |
240 | * @param float longitude | |
241 | * @return google.maps.Marker | |
242 | */ | |
243 | addDraggableMarker: function(latitude, longitude) { | |
244 | var $marker = new google.maps.Marker({ | |
245 | clickable: false, | |
246 | draggable: true, | |
247 | map: this._map, | |
4c1010d9 MS |
248 | position: new google.maps.LatLng(latitude, longitude), |
249 | zIndex: 1 | |
80e131ce MS |
250 | }); |
251 | ||
252 | this._markers.push($marker); | |
253 | ||
254 | return $marker; | |
255 | }, | |
256 | ||
257 | /** | |
258 | * Adds a marker with the given data to the map and returns the created | |
259 | * marker object. | |
260 | * | |
261 | * @param float latitude | |
262 | * @param float longitude | |
263 | * @param string title | |
264 | * @param mixed icon | |
265 | * @param string information | |
266 | * @return google.maps.Marker | |
267 | */ | |
268 | addMarker: function(latitude, longitude, title, icon, information) { | |
269 | var $marker = new google.maps.Marker({ | |
270 | map: this._map, | |
271 | position: new google.maps.LatLng(latitude, longitude), | |
272 | title: title | |
273 | }); | |
274 | ||
275 | // add icon | |
276 | if (icon) { | |
277 | $marker.setIcon(icon); | |
278 | } | |
279 | ||
280 | // add info window for marker information | |
281 | if (information) { | |
282 | var $infoWindow = new google.maps.InfoWindow({ | |
283 | content: information | |
284 | }); | |
0a2c8393 | 285 | this._addInfoWindowEventListener($marker, $infoWindow); |
6cf7dace MS |
286 | |
287 | // add info window object to marker object | |
288 | $marker.infoWindow = $infoWindow; | |
80e131ce MS |
289 | } |
290 | ||
291 | this._markers.push($marker); | |
292 | ||
293 | return $marker; | |
294 | }, | |
295 | ||
296 | /** | |
297 | * Returns all markers on the map. | |
298 | * | |
299 | * @return array<google.maps.Marker> | |
300 | */ | |
301 | getMarkers: function() { | |
302 | return this._markers; | |
303 | }, | |
304 | ||
305 | /** | |
306 | * Returns the Google Maps map object. | |
307 | * | |
308 | * @return google.maps.Map | |
309 | */ | |
310 | getMap: function() { | |
311 | return this._map; | |
312 | }, | |
313 | ||
314 | /** | |
315 | * Refreshes the map. | |
316 | */ | |
317 | refresh: function() { | |
f6b34a5b MS |
318 | // save current center since resize does not preserve it |
319 | var $center = this._map.getCenter(); | |
320 | ||
80e131ce | 321 | google.maps.event.trigger(this._map, 'resize'); |
f6b34a5b MS |
322 | |
323 | // set center to old value again | |
324 | this._map.setCenter($center); | |
80e131ce MS |
325 | }, |
326 | ||
327 | /** | |
328 | * Refreshes the boundaries of the map to show all markers. | |
329 | */ | |
330 | refreshBounds: function() { | |
331 | var $minLatitude = null; | |
332 | var $maxLatitude = null; | |
333 | var $minLongitude = null; | |
334 | var $maxLongitude = null; | |
335 | ||
336 | for (var $index in this._markers) { | |
337 | var $marker = this._markers[$index]; | |
338 | var $latitude = $marker.getPosition().lat(); | |
339 | var $longitude = $marker.getPosition().lng(); | |
340 | ||
341 | if ($minLatitude === null) { | |
342 | $minLatitude = $maxLatitude = $latitude; | |
343 | $minLongitude = $maxLongitude = $longitude; | |
344 | } | |
345 | else { | |
346 | if ($minLatitude > $latitude) { | |
347 | $minLatitude = $latitude; | |
348 | } | |
349 | else if ($maxLatitude < $latitude) { | |
350 | $maxLatitude = $latitude; | |
351 | } | |
352 | ||
353 | if ($minLongitude > $latitude) { | |
354 | $minLongitude = $latitude; | |
355 | } | |
356 | else if ($maxLongitude < $longitude) { | |
357 | $maxLongitude = $longitude; | |
358 | } | |
359 | } | |
360 | } | |
361 | ||
362 | this._map.fitBounds(new google.maps.LatLngBounds( | |
363 | new google.maps.LatLng($minLatitude, $minLongitude), | |
364 | new google.maps.LatLng($maxLatitude, $maxLongitude) | |
365 | )); | |
366 | }, | |
367 | ||
368 | /** | |
369 | * Removes all markers from the map. | |
370 | */ | |
371 | removeMarkers: function() { | |
372 | for (var $index in this._markers) { | |
373 | this._markers[$index].setMap(null); | |
374 | } | |
375 | ||
376 | this._markers = [ ]; | |
377 | }, | |
378 | ||
42eb214d MS |
379 | /** |
380 | * Changes the bounds of the map. | |
381 | * | |
382 | * @param object northEast | |
383 | * @param object southWest | |
384 | */ | |
385 | setBounds: function(northEast, southWest) { | |
386 | this._map.fitBounds(new google.maps.LatLngBounds( | |
387 | new google.maps.LatLng(southWest.latitude, southWest.longitude), | |
388 | new google.maps.LatLng(northEast.latitude, northEast.longitude) | |
389 | )); | |
390 | }, | |
391 | ||
80e131ce MS |
392 | /** |
393 | * Sets the center of the map to the given position. | |
394 | * | |
395 | * @param float latitude | |
396 | * @param float longitude | |
397 | */ | |
398 | setCenter: function(latitude, longitude) { | |
399 | this._map.setCenter(new google.maps.LatLng(latitude, longitude)); | |
400 | } | |
401 | }); | |
402 | ||
6cf7dace MS |
403 | /** |
404 | * Handles a large map with many markers where (new) markers are loaded via AJAX. | |
405 | */ | |
406 | WCF.Location.GoogleMaps.LargeMap = WCF.Location.GoogleMaps.Map.extend({ | |
407 | /** | |
408 | * name of the PHP class executing the 'getMapMarkers' action | |
409 | * @var string | |
410 | */ | |
411 | _actionClassName: null, | |
412 | ||
eaf90fa4 MW |
413 | /** |
414 | * additional parameters for executing the 'getMapMarkers' action | |
415 | * @var object | |
416 | */ | |
417 | _additionalParameters: { }, | |
418 | ||
6cf7dace MS |
419 | /** |
420 | * indicates if the maps center can be set by location search | |
421 | * @var WCF.Location.GoogleMaps.LocationSearch | |
422 | */ | |
423 | _locationSearch: null, | |
424 | ||
425 | /** | |
426 | * selector for the location search input | |
427 | * @var string | |
428 | */ | |
429 | _locationSearchInputSelector: null, | |
430 | ||
431 | /** | |
432 | * cluster handling the markers on the map | |
433 | * @var MarkerClusterer | |
434 | */ | |
435 | _markerClusterer: null, | |
436 | ||
437 | /** | |
438 | * ids of the objects which are already displayed | |
439 | * @var array<integer> | |
440 | */ | |
441 | _objectIDs: [ ], | |
442 | ||
443 | /** | |
444 | * previous coordinates of the north east map boundary | |
445 | * @var google.maps.LatLng | |
446 | */ | |
447 | _previousNorthEast: null, | |
448 | ||
449 | /** | |
450 | * previous coordinates of the south west map boundary | |
451 | * @var google.maps.LatLng | |
452 | */ | |
453 | _previousSouthWest: null, | |
454 | ||
76d16d7f MS |
455 | /** |
456 | * if `true`, the `exludedObjectIds` array will be sent as a JSON string, | |
457 | * otherwise as an array (default) | |
458 | * note: be prepared that in the future, only JSON strings might be supported | |
459 | * @var boolean | |
460 | */ | |
461 | _stringifyExcludedObjectIds: false, | |
462 | ||
6cf7dace MS |
463 | /** |
464 | * @see WCF.Location.GoogleMaps.Map.init() | |
465 | */ | |
eaf90fa4 | 466 | init: function(mapContainerID, mapOptions, actionClassName, locationSearchInputSelector, additionalParameters) { |
76d16d7f MS |
467 | this._stringifyExcludedObjectIds = false; |
468 | if (mapOptions && mapOptions.stringifyExcludedObjectIds) { | |
469 | this._stringifyExcludedObjectIds = mapOptions.stringifyExcludedObjectIds; | |
470 | delete mapOptions.stringifyExcludedObjectIds; | |
471 | } | |
472 | ||
6cf7dace MS |
473 | this._super(mapContainerID, mapOptions); |
474 | ||
475 | this._actionClassName = actionClassName; | |
476 | this._locationSearchInputSelector = locationSearchInputSelector || ''; | |
eaf90fa4 | 477 | this._additionalParameters = additionalParameters || { }; |
6cf7dace MS |
478 | this._objectIDs = [ ]; |
479 | ||
480 | if (this._locationSearchInputSelector) { | |
481 | this._locationSearch = new WCF.Location.GoogleMaps.LocationSearch(locationSearchInputSelector, $.proxy(this._centerMap, this)); | |
482 | } | |
483 | ||
f9c4ea7c | 484 | this._markerClusterer = new MarkerClusterer(this._map, this._markers, { |
ab587abc | 485 | maxZoom: 17, |
73c4ffca | 486 | imagePath: WCF.Location.GoogleMaps.Settings.get('markerClustererImagePath') + 'm' |
f9c4ea7c | 487 | }); |
6cf7dace | 488 | |
0a2c8393 MS |
489 | this._markerSpiderfier = new OverlappingMarkerSpiderfier(this._map, { |
490 | keepSpiderfied: true, | |
491 | markersWontHide: true, | |
492 | markersWontMove: true | |
493 | }); | |
494 | this._markerSpiderfier.addListener('click', $.proxy(function(marker) { | |
495 | if (marker.infoWindow) { | |
496 | marker.infoWindow.open(this._map, marker); | |
497 | } | |
498 | }, this)); | |
499 | ||
6cf7dace MS |
500 | this._proxy = new WCF.Action.Proxy({ |
501 | showLoadingOverlay: false, | |
502 | success: $.proxy(this._success, this) | |
503 | }); | |
504 | ||
505 | this._previousNorthEast = null; | |
506 | this._previousSouthWest = null; | |
507 | google.maps.event.addListener(this._map, 'idle', $.proxy(this._loadMarkers, this)); | |
508 | }, | |
509 | ||
0a2c8393 MS |
510 | /** |
511 | * @see WCF.Location.GoogleMaps.Map.addMarker() | |
512 | */ | |
513 | _addInfoWindowEventListener: function(marker, infoWindow) { | |
514 | // does nothing, is handled by the event listener of the marker | |
515 | // spiderfier | |
516 | }, | |
517 | ||
6cf7dace MS |
518 | /** |
519 | * Centers the map based on a location search result. | |
520 | * | |
521 | * @param object data | |
522 | */ | |
523 | _centerMap: function(data) { | |
524 | this.setCenter(data.location.lat(), data.location.lng()); | |
525 | ||
526 | $(this._locationSearchInputSelector).val(data.label); | |
527 | }, | |
528 | ||
529 | /** | |
2957e71a MS |
530 | * Loads markers if the map is reloaded. Returns true if new markers will |
531 | * be loaded and false if for the current map bounds, the markers have already | |
532 | * been loaded. | |
533 | * | |
534 | * @return boolean | |
6cf7dace MS |
535 | */ |
536 | _loadMarkers: function() { | |
537 | var $northEast = this._map.getBounds().getNorthEast(); | |
538 | var $southWest = this._map.getBounds().getSouthWest(); | |
539 | ||
540 | // check if the user has zoomed in, then all markers are already | |
541 | // displayed | |
542 | if (this._previousNorthEast && this._previousNorthEast.lat() >= $northEast.lat() && this._previousNorthEast.lng() >= $northEast.lng() && this._previousSouthWest.lat() <= $southWest.lat() && this._previousSouthWest.lng() <= $southWest.lng()) { | |
2957e71a | 543 | return false; |
6cf7dace MS |
544 | } |
545 | ||
546 | this._previousNorthEast = $northEast; | |
547 | this._previousSouthWest = $southWest; | |
548 | ||
549 | this._proxy.setOption('data', { | |
550 | actionName: 'getMapMarkers', | |
551 | className: this._actionClassName, | |
eaf90fa4 | 552 | parameters: $.extend(this._additionalParameters, { |
76d16d7f | 553 | excludedObjectIDs: this._stringifyExcludedObjectIds ? JSON.stringify(this._objectIDs) : this._objectIDs, |
6cf7dace MS |
554 | eastLongitude: $northEast.lng(), |
555 | northLatitude: $northEast.lat(), | |
556 | southLatitude: $southWest.lat(), | |
557 | westLongitude: $southWest.lng() | |
eaf90fa4 | 558 | }) |
6cf7dace MS |
559 | }); |
560 | this._proxy.sendRequest(); | |
2957e71a MS |
561 | |
562 | return true; | |
6cf7dace MS |
563 | }, |
564 | ||
565 | /** | |
566 | * Handles a successful AJAX request. | |
567 | * | |
568 | * @param object data | |
569 | * @param string textStatus | |
570 | * @param jQuery jqXHR | |
571 | */ | |
572 | _success: function(data, textStatus, jqXHR) { | |
573 | if (data.returnValues && data.returnValues.markers) { | |
574 | for (var $index in data.returnValues.markers) { | |
575 | var $markerInfo = data.returnValues.markers[$index]; | |
576 | ||
577 | this.addMarker($markerInfo.latitude, $markerInfo.longitude, $markerInfo.title, null, $markerInfo.infoWindow); | |
578 | ||
579 | if ($markerInfo.objectID) { | |
580 | this._objectIDs.push($markerInfo.objectID); | |
581 | } | |
582 | else if ($markerInfo.objectIDs) { | |
583 | this._objectIDs = this._objectIDs.concat($markerInfo.objectIDs); | |
584 | } | |
585 | } | |
586 | } | |
587 | }, | |
588 | ||
589 | /** | |
590 | * @see WCF.Location.GoogleMaps.Map.addMarker() | |
591 | */ | |
592 | addMarker: function(latitude, longitude, title, icon, information) { | |
593 | var $marker = this._super(latitude, longitude, title, icon, information); | |
594 | this._markerClusterer.addMarker($marker); | |
0a2c8393 | 595 | this._markerSpiderfier.addMarker($marker); |
6cf7dace MS |
596 | |
597 | return $marker; | |
598 | } | |
599 | }); | |
600 | ||
2957e71a MS |
601 | /** |
602 | * Extends the large map implementation by treating non-draggable markers as location | |
603 | * suggestions. | |
604 | */ | |
605 | WCF.Location.GoogleMaps.SuggestionMap = WCF.Location.GoogleMaps.LargeMap.extend({ | |
606 | /** | |
607 | * maps control showing/hiding location suggestions | |
608 | * @var jQuery | |
609 | */ | |
610 | _locationSuggestionsButton: null, | |
611 | ||
612 | /** | |
613 | * function called when a location is selected | |
614 | * @var function | |
615 | */ | |
616 | _suggestionSelectionCallback: null, | |
617 | ||
618 | /** | |
619 | * @see WCF.Location.GoogleMaps.LargeMap.init() | |
620 | */ | |
621 | init: function(mapContainerID, mapOptions, actionClassName, locationSearchInputSelector, additionalParameters) { | |
622 | this._super(mapContainerID, mapOptions, actionClassName, locationSearchInputSelector, additionalParameters); | |
623 | ||
1a6e8c52 | 624 | var $locationSuggestionDiv = $('<div class="gmnoprint googleMapsCustomControlContainer"><div class="gm-style-mtc"><div class="googleMapsCustomControl">' + WCF.Language.get('wcf.map.showLocationSuggestions') + '</div></div></div>'); |
2957e71a MS |
625 | this._locationSuggestionsButton = $locationSuggestionDiv.find('.googleMapsCustomControl').click($.proxy(this._toggleLocationSuggestions, this)); |
626 | ||
627 | this._map.controls[google.maps.ControlPosition.TOP_RIGHT].push($locationSuggestionDiv.get(0)); | |
628 | }, | |
629 | ||
630 | /** | |
631 | * @see WCF.Location.GoogleMaps.LargeMap._loadMarkers() | |
632 | */ | |
633 | _loadMarkers: function() { | |
634 | if (!this._locationSuggestionsButton.hasClass('active')) return; | |
635 | ||
636 | if (!this._super()) { | |
637 | this._loadSuggestions = false; | |
638 | } | |
639 | }, | |
640 | ||
641 | /** | |
642 | * @see WCF.Location.GoogleMaps.LargeMap._loadMarkers() | |
643 | */ | |
644 | _success: function(data, textStatus, jqXHR) { | |
645 | var $oldLength = this._markers.length; | |
646 | this._super(data, textStatus, jqXHR); | |
647 | ||
648 | if (this._loadSuggestions && $oldLength == this._markers.length) { | |
649 | this._loadSuggestions = false; | |
650 | new WCF.System.Notification(WCF.Language.get('wcf.map.noLocationSuggestions'), 'info').show(); | |
651 | } | |
652 | }, | |
653 | ||
654 | /** | |
655 | * Handles clicks on the location suggestions button. | |
656 | */ | |
657 | _toggleLocationSuggestions: function() { | |
658 | var $showSuggestions = !this._locationSuggestionsButton.hasClass('active'); | |
659 | if ($showSuggestions) { | |
660 | this._loadSuggestions = true; | |
661 | } | |
662 | ||
663 | this.showSuggestions($showSuggestions); | |
664 | }, | |
665 | ||
666 | /** | |
667 | * @see WCF.Location.GoogleMaps.Map.addMarker() | |
668 | */ | |
669 | addMarker: function(latitude, longitude, title, icon, information) { | |
670 | var $infoWindow = $(information); | |
671 | var $useLocation = $('<a class="googleMapsUseLocationSuggestionLink" />').text(WCF.Language.get('wcf.map.useLocationSuggestion')).click(this._suggestionSelectionCallback); | |
95961bdf | 672 | $infoWindow.append($('<p />').append($useLocation)); |
2957e71a MS |
673 | |
674 | var $marker = this._super(latitude, longitude, title, '//mt.google.com/vt/icon/name=icons/spotlight/spotlight-waypoint-a.png', $infoWindow.get(0)); | |
675 | ||
676 | $useLocation.data('marker', $marker); | |
677 | ||
678 | return $marker; | |
679 | }, | |
680 | ||
681 | /** | |
682 | * Sets the function called when a location is selected. | |
683 | * | |
684 | * @param function callback | |
685 | */ | |
686 | setSuggestionSelectionCallback: function(callback) { | |
687 | this._suggestionSelectionCallback = callback; | |
688 | }, | |
689 | ||
690 | /** | |
691 | * Shows or hides the location suggestions. | |
692 | * | |
693 | * @param boolean showSuggestions | |
694 | */ | |
695 | showSuggestions: function(showSuggestions) { | |
696 | // missing argument means showing the suggestions | |
697 | if (showSuggestions === undefined) showSuggestions = true; | |
698 | ||
699 | this._locationSuggestionsButton.toggleClass('active', showSuggestions); | |
700 | ||
701 | var $clusterMarkers = [ ]; | |
702 | for (var $i = 0, $length = this._markers.length; $i < $length; $i++) { | |
703 | var $marker = this._markers[$i]; | |
704 | ||
705 | // ignore draggable markers | |
706 | if (!$marker.draggable) { | |
707 | $marker.setVisible(showSuggestions); | |
708 | if (showSuggestions) { | |
709 | $clusterMarkers.push($marker); | |
710 | } | |
711 | } | |
712 | } | |
713 | ||
714 | this._markerClusterer.clearMarkers(); | |
715 | if (showSuggestions) { | |
716 | this._markerClusterer.addMarkers($clusterMarkers); | |
717 | } | |
718 | ||
719 | this._loadMarkers(); | |
720 | } | |
721 | }); | |
722 | ||
80e131ce MS |
723 | /** |
724 | * Provides location searches based on google.maps.Geocoder. | |
725 | */ | |
726 | WCF.Location.GoogleMaps.LocationSearch = WCF.Search.Base.extend({ | |
727 | /** | |
728 | * Google Maps geocoder object | |
729 | * @var google.maps.Geocoder | |
730 | */ | |
731 | _geocoder: null, | |
732 | ||
733 | /** | |
734 | * @see WCF.Search.Base.init() | |
735 | */ | |
736 | init: function(searchInput, callback, excludedSearchValues, commaSeperated, showLoadingOverlay) { | |
737 | this._super(searchInput, callback, excludedSearchValues, commaSeperated, showLoadingOverlay); | |
738 | ||
f67fb4e0 | 739 | this.setDelay(500); |
80e131ce MS |
740 | this._geocoder = new google.maps.Geocoder(); |
741 | }, | |
742 | ||
743 | /** | |
744 | * @see WCF.Search.Base._createListItem() | |
745 | */ | |
746 | _createListItem: function(geocoderResult) { | |
747 | var $listItem = $('<li><span>' + WCF.String.escapeHTML(geocoderResult.formatted_address) + '</span></li>').appendTo(this._list); | |
748 | $listItem.data('location', geocoderResult.geometry.location).data('label', geocoderResult.formatted_address).click($.proxy(this._executeCallback, this)); | |
749 | ||
750 | this._itemCount++; | |
751 | ||
752 | return $listItem; | |
753 | }, | |
754 | ||
755 | /** | |
756 | * @see WCF.Search.Base._keyUp() | |
757 | */ | |
758 | _keyUp: function(event) { | |
759 | // handle arrow keys and return key | |
760 | switch (event.which) { | |
3e597f7d AE |
761 | case $.ui.keyCode.LEFT: |
762 | case $.ui.keyCode.RIGHT: | |
80e131ce | 763 | return; |
80e131ce | 764 | |
3e597f7d | 765 | case $.ui.keyCode.UP: |
80e131ce MS |
766 | this._selectPreviousItem(); |
767 | return; | |
80e131ce | 768 | |
3e597f7d | 769 | case $.ui.keyCode.DOWN: |
80e131ce MS |
770 | this._selectNextItem(); |
771 | return; | |
80e131ce | 772 | |
3e597f7d | 773 | case $.ui.keyCode.ENTER: |
80e131ce | 774 | return this._selectElement(event); |
80e131ce MS |
775 | } |
776 | ||
777 | var $content = this._getSearchString(event); | |
778 | if ($content === '') { | |
779 | this._clearList(true); | |
780 | } | |
781 | else if ($content.length >= this._triggerLength) { | |
f67fb4e0 MS |
782 | if (this._delay) { |
783 | if (this._timer !== null) { | |
784 | this._timer.stop(); | |
785 | } | |
786 | ||
787 | this._timer = new WCF.PeriodicalExecuter($.proxy(function() { | |
788 | this._geocoder.geocode({ | |
789 | address: $content | |
790 | }, $.proxy(this._success, this)); | |
791 | ||
792 | this._timer.stop(); | |
793 | this._timer = null; | |
794 | }, this), this._delay); | |
795 | } | |
796 | else { | |
797 | this._geocoder.geocode({ | |
798 | address: $content | |
799 | }, $.proxy(this._success, this)); | |
800 | } | |
80e131ce MS |
801 | } |
802 | else { | |
803 | // input below trigger length | |
804 | this._clearList(false); | |
805 | } | |
806 | }, | |
807 | ||
808 | /** | |
1615fc2e | 809 | * Handles a successful geocoder request. |
80e131ce MS |
810 | * |
811 | * @param array results | |
812 | * @param integer status | |
813 | */ | |
814 | _success: function(results, status) { | |
cb34315a MS |
815 | this._clearList(false); |
816 | ||
80e131ce MS |
817 | if (status != google.maps.GeocoderStatus.OK) { |
818 | return; | |
819 | } | |
820 | ||
821 | if ($.getLength(results)) { | |
822 | var $count = 0; | |
823 | for (var $index in results) { | |
824 | this._createListItem(results[$index]); | |
825 | ||
826 | if (++$count == 10) { | |
827 | break; | |
828 | } | |
829 | } | |
830 | } | |
831 | else if (!this._handleEmptyResult()) { | |
832 | return; | |
833 | } | |
834 | ||
835 | WCF.CloseOverlayHandler.addCallback('WCF.Search.Base', $.proxy(function() { this._clearList(); }, this)); | |
836 | ||
837 | var $containerID = this._searchInput.parents('.dropdown').wcfIdentify(); | |
838 | if (!WCF.Dropdown.getDropdownMenu($containerID).hasClass('dropdownOpen')) { | |
f010ddce | 839 | WCF.Dropdown.toggleDropdown($containerID, true); |
80e131ce MS |
840 | } |
841 | ||
842 | // pre-select first item | |
843 | this._itemIndex = -1; | |
844 | if (!WCF.Dropdown.getDropdown($containerID).data('disableAutoFocus')) { | |
845 | this._selectNextItem(); | |
846 | } | |
847 | } | |
848 | }); | |
849 | ||
850 | /** | |
851 | * Handles setting a single location on a Google Map. | |
852 | */ | |
853 | WCF.Location.GoogleMaps.LocationInput = Class.extend({ | |
854 | /** | |
855 | * location search object | |
856 | * @var WCF.Location.GoogleMaps.LocationSearch | |
857 | */ | |
858 | _locationSearch: null, | |
859 | ||
860 | /** | |
861 | * related map object | |
862 | * @var WCF.Location.GoogleMaps.Map | |
863 | */ | |
864 | _map: null, | |
865 | ||
866 | /** | |
867 | * draggable marker to set the location | |
868 | * @var google.maps.Marker | |
869 | */ | |
870 | _marker: null, | |
871 | ||
872 | /** | |
873 | * Initializes a new WCF.Location.GoogleMaps.LocationInput object. | |
874 | * | |
875 | * @param string mapContainerID | |
876 | * @param object mapOptions | |
877 | * @param string searchInput | |
73f5239b MS |
878 | * @param float latitude |
879 | * @param float longitude | |
2957e71a | 880 | * @param string actionClassName |
80e131ce | 881 | */ |
2957e71a | 882 | init: function(mapContainerID, mapOptions, searchInput, latitude, longitude, actionClassName) { |
80e131ce | 883 | this._searchInput = searchInput; |
2957e71a MS |
884 | |
885 | if (actionClassName) { | |
886 | this._map = new WCF.Location.GoogleMaps.SuggestionMap(mapContainerID, mapOptions, actionClassName); | |
887 | this._map.setSuggestionSelectionCallback($.proxy(this._useSuggestion, this)); | |
888 | } | |
889 | else { | |
890 | this._map = new WCF.Location.GoogleMaps.Map(mapContainerID, mapOptions); | |
891 | } | |
892 | ||
80e131ce MS |
893 | this._locationSearch = new WCF.Location.GoogleMaps.LocationSearch(searchInput, $.proxy(this._setMarkerByLocation, this)); |
894 | ||
895 | if (latitude && longitude) { | |
896 | this._marker = this._map.addDraggableMarker(latitude, longitude); | |
897 | } | |
898 | else { | |
d6693d81 | 899 | this._marker = this._map.addDraggableMarker(WCF.Location.GoogleMaps.Settings.get('defaultLatitude'), WCF.Location.GoogleMaps.Settings.get('defaultLongitude')); |
80e131ce MS |
900 | |
901 | WCF.Location.Util.getLocation($.proxy(function(latitude, longitude) { | |
2cf378ae MS |
902 | if (latitude !== undefined && longitude !== undefined) { |
903 | WCF.Location.GoogleMaps.Util.moveMarker(this._marker, latitude, longitude); | |
fba1ceef | 904 | WCF.Location.GoogleMaps.Util.focusMarker(this._marker); |
2cf378ae | 905 | } |
80e131ce MS |
906 | }, this)); |
907 | } | |
bcf17845 AE |
908 | |
909 | this._marker.addListener('dragend', $.proxy(this._updateLocation, this)); | |
80e131ce MS |
910 | }, |
911 | ||
912 | /** | |
2957e71a MS |
913 | * Uses a suggestion by clicking on the "Use suggestion" link in the marker's |
914 | * info window. | |
80e131ce | 915 | * |
2957e71a | 916 | * @param Event event |
80e131ce | 917 | */ |
2957e71a MS |
918 | _useSuggestion: function(event) { |
919 | var $marker = $(event.currentTarget).data('marker'); | |
920 | ||
921 | this._marker.setPosition($marker.getPosition()); | |
922 | this._updateLocation(); | |
923 | ||
924 | // hide suggestions | |
925 | this._map.showSuggestions(false); | |
80e131ce MS |
926 | }, |
927 | ||
bcf17845 AE |
928 | /** |
929 | * Updates location on marker position change. | |
930 | */ | |
931 | _updateLocation: function() { | |
932 | WCF.Location.GoogleMaps.Util.reverseGeocoding($.proxy(function(result) { | |
933 | if (result !== null) { | |
934 | $(this._searchInput).val(result); | |
935 | } | |
936 | }, this), this._marker); | |
937 | }, | |
938 | ||
80e131ce MS |
939 | /** |
940 | * Sets the marker based on an entered location. | |
941 | * | |
942 | * @param object data | |
943 | */ | |
944 | _setMarkerByLocation: function(data) { | |
945 | this._marker.setPosition(data.location); | |
946 | WCF.Location.GoogleMaps.Util.focusMarker(this._marker); | |
947 | ||
3e597f7d | 948 | $(this._searchInput).val(data.label); |
2957e71a MS |
949 | }, |
950 | ||
951 | /** | |
952 | * Returns the related map. | |
953 | * | |
954 | * @return WCF.Location.GoogleMaps.Map | |
955 | */ | |
956 | getMap: function() { | |
957 | return this._map; | |
958 | }, | |
959 | ||
960 | /** | |
961 | * Returns the draggable marker used to set the location. | |
962 | * | |
963 | * @return google.maps.Marker | |
964 | */ | |
965 | getMarker: function() { | |
966 | return this._marker; | |
80e131ce MS |
967 | } |
968 | }); | |
969 | ||
970 | /** | |
971 | * Provides utility functions for Google Maps maps. | |
972 | */ | |
973 | WCF.Location.GoogleMaps.Util = { | |
7013ec88 AE |
974 | /** |
975 | * geocoder instance | |
976 | * @var google.maps.Geocoder | |
977 | */ | |
978 | _geocoder: null, | |
979 | ||
80e131ce MS |
980 | /** |
981 | * Focuses the given marker's map on the marker. | |
982 | * | |
983 | * @param google.maps.Marker marker | |
984 | */ | |
985 | focusMarker: function(marker) { | |
986 | marker.getMap().setCenter(marker.getPosition()); | |
987 | }, | |
988 | ||
989 | /** | |
990 | * Returns the latitude and longitude of the given marker. | |
991 | * | |
992 | * @return object | |
993 | */ | |
994 | getMarkerPosition: function(marker) { | |
995 | return { | |
996 | latitude: marker.getPosition().lat(), | |
997 | longitude: marker.getPosition().lng() | |
998 | }; | |
999 | }, | |
1000 | ||
1001 | /** | |
1002 | * Moves the given marker to the given position. | |
1003 | * | |
1004 | * @param google.maps.Marker marker | |
1005 | * @param float latitude | |
1006 | * @param float longitude | |
ab6d3626 | 1007 | * @param boolean dragend indicates if "dragend" event is fired |
80e131ce | 1008 | */ |
ab6d3626 | 1009 | moveMarker: function(marker, latitude, longitude, triggerDragend) { |
80e131ce | 1010 | marker.setPosition(new google.maps.LatLng(latitude, longitude)); |
ab6d3626 MS |
1011 | |
1012 | if (triggerDragend) { | |
1013 | google.maps.event.trigger(marker, 'dragend'); | |
1014 | } | |
7013ec88 AE |
1015 | }, |
1016 | ||
1017 | /** | |
1018 | * Performs a reverse geocoding request. | |
1019 | * | |
1020 | * @param object callback | |
1021 | * @param google.maps.Marker marker | |
1022 | * @param string latitude | |
1023 | * @param string longitude | |
1024 | * @param boolean fullResult | |
1025 | */ | |
1026 | reverseGeocoding: function(callback, marker, latitude, longitude, fullResult) { | |
1027 | if (marker) { | |
1028 | latitude = marker.getPosition().lat(); | |
1029 | longitude = marker.getPosition().lng(); | |
1030 | } | |
1031 | ||
1032 | if (this._geocoder === null) { | |
1033 | this._geocoder = new google.maps.Geocoder(); | |
1034 | } | |
1035 | ||
1036 | var $latLng = new google.maps.LatLng(latitude, longitude); | |
1037 | this._geocoder.geocode({ latLng: $latLng }, function(results, status) { | |
1038 | if (status == google.maps.GeocoderStatus.OK) { | |
1039 | callback((fullResult ? results : results[0].formatted_address)); | |
1040 | } | |
1041 | else { | |
1042 | callback(null); | |
1043 | } | |
1044 | }); | |
2cf378ae | 1045 | } |
80e131ce | 1046 | }; |