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