Set default captcha type to none
[GitHub/WoltLab/WCF.git] / wcfsetup / install / files / js / WCF.Location.js
CommitLineData
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 */
11WCF.Location = { };
12
13/**
14 * Provides location-related utility functions.
15 */
16WCF.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 */
45WCF.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 */
51function 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 */
58WCF.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 */
106WCF.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 */
406WCF.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 */
605WCF.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 */
726WCF.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 */
853WCF.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 */
973WCF.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};