Created
June 8, 2017 14:36
-
-
Save markdboyd/c0d2410a683cc32e4754b16ab6623e4c to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
diff --git a/geolocation.libraries.yml b/geolocation.libraries.yml | |
index 71c2fcf..b5dfaaf 100644 | |
--- a/geolocation.libraries.yml | |
+++ b/geolocation.libraries.yml | |
@@ -22,6 +22,12 @@ geolocation.views.filter.geocoder: | |
dependencies: | |
- geolocation/geolocation.geocoder | |
+# HTML5 Geolocation API. | |
+geolocation.html5: | |
+ version: 1.x | |
+ js: | |
+ js/geolocation-html5.js: { scope: footer } | |
+ | |
# HTML5 widget library. | |
geolocation.widgets.html5: | |
version: 1.x | |
@@ -31,6 +37,17 @@ geolocation.widgets.html5: | |
js: | |
js/geolocation-widget-html5.js: { scope: footer } | |
+# HTML5 proximity field. | |
+geolocation.proximity.html5: | |
+ version: 1.x | |
+ js: | |
+ js/geolocation-proximity-html5.js: { scope: footer } | |
+ dependencies: | |
+ - core/drupal.ajax | |
+ - views/views.ajax | |
+ - geolocation/geolocation.googlemapsapi | |
+ - geolocation/geolocation.html5 | |
+ | |
# | |
# | |
# Google Maps API | |
diff --git a/js/geolocation-common-map.js b/js/geolocation-common-map.js | |
index 0b14d3f..961b19f 100644 | |
--- a/js/geolocation-common-map.js | |
+++ b/js/geolocation-common-map.js | |
@@ -495,7 +495,11 @@ | |
markerClustererStyles = commonMapSettings.markerClusterer.styles; | |
} | |
- new MarkerClusterer( | |
+ if (geolocationMap.markerClusterer) { | |
+ geolocationMap.markerClusterer.clearMarkers(); | |
+ } | |
+ | |
+ geolocationMap.markerClusterer = new MarkerClusterer( | |
geolocationMap.googleMap, | |
geolocationMap.mapMarkers, | |
{ | |
diff --git a/js/geolocation-google-maps-api.js b/js/geolocation-google-maps-api.js | |
index 69e0be4..a63d66a 100644 | |
--- a/js/geolocation-google-maps-api.js | |
+++ b/js/geolocation-google-maps-api.js | |
@@ -434,7 +434,6 @@ | |
*/ | |
Drupal.geolocation.removeMapMarker = function (map) { | |
map.mapMarkers = map.mapMarkers || []; | |
- | |
$.each( | |
map.mapMarkers, | |
diff --git a/js/geolocation-html5.js b/js/geolocation-html5.js | |
new file mode 100644 | |
index 0000000..b9fdf67 | |
--- /dev/null | |
+++ b/js/geolocation-html5.js | |
@@ -0,0 +1,106 @@ | |
+/** | |
+ * @file | |
+ * Javascript for integrating the W3C Geolocation API. | |
+ */ | |
+ | |
+(function ($, Drupal, drupalSettings, navigator) { | |
+ | |
+ 'use strict'; | |
+ | |
+ /** | |
+ * @namespace | |
+ */ | |
+ Drupal.geolocation = Drupal.geolocation || {}; | |
+ Drupal.geolocation.html5 = Drupal.geolocation.html5 || {}; | |
+ | |
+ drupalSettings.geolocation.html5 = drupalSettings.geolocation.html5 || {}; | |
+ | |
+ /** | |
+ * Adds a callback that will be called when client location is retrieved. | |
+ * | |
+ * @callback {geolocationHTML5ResultCallback} callback - The callback | |
+ */ | |
+ Drupal.geolocation.html5.addResultCallback = function (callback) { | |
+ Drupal.geolocation.html5.resultCallbacks = Drupal.geolocation.html5.resultCallbacks || []; | |
+ Drupal.geolocation.html5.resultCallbacks.push({callback: callback}); | |
+ }; | |
+ | |
+ /** | |
+ * Get the client location using HTML5 Geolocation API. | |
+ * | |
+ * @callback success - The success callback | |
+ * @callback error - The error callback | |
+ * @param {object} options - The options for the HTML5 geolocation | |
+ */ | |
+ Drupal.geolocation.html5.getClientLocation = function(success, error, options) { | |
+ if (typeof success === 'undefined' || !success) { | |
+ alert(Drupal.t('You must provide a success handler for the W3C Geolocation API.')); | |
+ return; | |
+ } | |
+ | |
+ var error = (error && typeof error === 'function') ? error : Drupal.geolocation.html5.getClientLocationError; | |
+ var options = options || { | |
+ enableHighAccuracy: true, | |
+ timeout: 5000, | |
+ maximumAge: 6000 | |
+ }; | |
+ | |
+ // If the browser supports W3C Geolocation API. | |
+ if (navigator.geolocation) { | |
+ // Get the geolocation from the browser. | |
+ navigator.geolocation.getCurrentPosition(success, error, options); | |
+ } | |
+ else { | |
+ alert(Drupal.t('No location data found. Your browser does not support the W3C Geolocation API.')); | |
+ } | |
+ }; | |
+ | |
+ /** | |
+ * Default error handler for Geolocation API. | |
+ * | |
+ * @param {object} error - The error object. | |
+ */ | |
+ Drupal.geolocation.html5.getClientLocationError = function(error) { | |
+ // Alert with error message. | |
+ switch (error.code) { | |
+ case error.PERMISSION_DENIED: | |
+ console.log(Drupal.t('No location data found. Reason: PERMISSION_DENIED.')); | |
+ drupalSettings.geolocation.html5.permissionDenied = true; | |
+ break; | |
+ case error.POSITION_UNAVAILABLE: | |
+ console.log(Drupal.t('No location data found. Reason: POSITION_UNAVAILABLE.')); | |
+ break; | |
+ case error.TIMEOUT: | |
+ console.log(Drupal.t('No location data found. Reason: TIMEOUT.')); | |
+ break; | |
+ default: | |
+ console.log(Drupal.t('No location data found. Reason: Unknown error.')); | |
+ break; | |
+ } | |
+ }; | |
+ | |
+ /** | |
+ * Attach HTML5 geolocation functionality. | |
+ * | |
+ * @type {Drupal~behavior} | |
+ */ | |
+ Drupal.behaviors.geolocationHTML5 = { | |
+ attach: function (context) { | |
+ if (!drupalSettings.geolocation.html5.retrieved && | |
+ !drupalSettings.geolocation.html5.permissionDenied) { | |
+ // Get the client location using the Geolocation API. | |
+ Drupal.geolocation.html5.getClientLocation(function(position) { | |
+ // Save the state of client location retrieval. | |
+ drupalSettings.geolocation.html5.retrieved = true; | |
+ | |
+ // Iterate over callbacks and call them with the geolocation result. | |
+ Drupal.geolocation.html5.resultCallbacks = Drupal.geolocation.html5.resultCallbacks || []; | |
+ $.each(Drupal.geolocation.html5.resultCallbacks, function (index, callbackContainer) { | |
+ callbackContainer.callback(position); | |
+ }); | |
+ }); | |
+ } | |
+ }, | |
+ }; | |
+ | |
+})(jQuery, Drupal, drupalSettings, navigator); | |
diff --git a/js/geolocation-proximity-html5.js b/js/geolocation-proximity-html5.js | |
new file mode 100644 | |
index 0000000..c8b5323 | |
--- /dev/null | |
+++ b/js/geolocation-proximity-html5.js | |
@@ -0,0 +1,67 @@ | |
+(function ($, Drupal, drupalSettings) { | |
+ 'use strict'; | |
+ | |
+ /** | |
+ * @namespace | |
+ */ | |
+ drupalSettings.geolocation.html5 = drupalSettings.geolocation.html5 || {}; | |
+ drupalSettings.geolocation.html5.proximity_view_ids = drupalSettings.geolocation.html5.proximity_view_ids || []; | |
+ | |
+ Drupal.geolocation = Drupal.geolocation || {}; | |
+ Drupal.geolocation.proximityHTML5 = Drupal.geolocation.proximityHTML5 || {}; | |
+ | |
+ Drupal.geolocation.proximityHTML5.refreshView = function(mapId, map, coordinates, context) { | |
+ // Get the AJAX settings for this view. | |
+ var viewSettings = Drupal.views.instances['views_dom_id:' + mapId]; | |
+ var geolocationAjaxSettings = viewSettings.element_settings; | |
+ | |
+ // Change the progress indicator. | |
+ geolocationAjaxSettings.progress.type = 'throbber'; | |
+ | |
+ // Add the coordinates to the data to be submitted with the | |
+ // request. | |
+ geolocationAjaxSettings.submit['proximity_lat'] = coordinates.latitude; | |
+ geolocationAjaxSettings.submit['proximity_lng'] = coordinates.longitude; | |
+ | |
+ // Use AJAX to refresh the view. | |
+ $('.js-view-dom-id-' + mapId, context).trigger('RefreshView'); | |
+ }; | |
+ | |
+ Drupal.behaviors.geolocationProximityHTML5 = { | |
+ attach: function(context) { | |
+ if (!drupalSettings.geolocation.html5.proximity_view_ids.length || | |
+ drupalSettings.geolocation.html5.permissionDenied || | |
+ drupalSettings.geolocation.html5.hasCoordinates) { | |
+ return; | |
+ } | |
+ | |
+ $.each(drupalSettings.geolocation.html5.proximity_view_ids, function(key, dom_id) { | |
+ Drupal.geolocation.proximityHTML5[dom_id] = Drupal.geolocation.proximityHTML5[dom_id] || {}; | |
+ Drupal.geolocation.proximityHTML5[dom_id].mapLoaded = $.Deferred(); | |
+ Drupal.geolocation.proximityHTML5[dom_id].receivedCoordinates = $.Deferred(); | |
+ | |
+ Drupal.geolocation.addMapLoadedCallback(function(map) { | |
+ Drupal.geolocation.proximityHTML5[dom_id].mapLoaded.resolve(map); | |
+ }, dom_id); | |
+ | |
+ Drupal.geolocation.html5.addResultCallback(function (position) { | |
+ // If specified, auto refresh the view with the received | |
+ // HTML5 coordinates. | |
+ if (drupalSettings.geolocation.html5[dom_id] && | |
+ drupalSettings.geolocation.html5[dom_id].auto_refresh) { | |
+ Drupal.geolocation.proximityHTML5[dom_id].receivedCoordinates.resolve(position); | |
+ } | |
+ }); | |
+ | |
+ // Wait for promises to be resolved for the map to be loaded and | |
+ // coordinates to be received before acting on the map. | |
+ $.when( | |
+ Drupal.geolocation.proximityHTML5[dom_id].mapLoaded, | |
+ Drupal.geolocation.proximityHTML5[dom_id].receivedCoordinates | |
+ ).then(function(map, position) { | |
+ Drupal.geolocation.proximityHTML5.refreshView(dom_id, map, position.coords, context); | |
+ }); | |
+ }); | |
+ } | |
+ }; | |
+})(jQuery, Drupal, drupalSettings); | |
\ No newline at end of file | |
diff --git a/js/geolocation-widget-html5.js b/js/geolocation-widget-html5.js | |
index 32f3272..42442d3 100644 | |
--- a/js/geolocation-widget-html5.js | |
+++ b/js/geolocation-widget-html5.js | |
@@ -15,7 +15,7 @@ | |
* @prop {Drupal~behaviorAttach} attach | |
* Attaches html5 widget functionality to relevant elements. | |
*/ | |
- Drupal.behaviors.geolocationHTML5 = { | |
+ Drupal.behaviors.geolocationWidgetHTML5 = { | |
attach: function (context, settings) { | |
$('.geolocation-html5-button:not(.disabled)').each(function (index) { | |
// The parent element. | |
diff --git a/src/Plugin/views/field/ProximityField.php b/src/Plugin/views/field/ProximityField.php | |
index d473cf7..526ef2e 100644 | |
--- a/src/Plugin/views/field/ProximityField.php | |
+++ b/src/Plugin/views/field/ProximityField.php | |
@@ -3,6 +3,7 @@ | |
namespace Drupal\geolocation\Plugin\views\field; | |
use Drupal\geolocation\GeolocationCore; | |
+use Drupal\views\Plugin\views\exposed_form\InputRequired; | |
use Drupal\views\ResultRow; | |
use Drupal\views\Plugin\views\field\NumericField; | |
use Drupal\Core\Render\Element; | |
@@ -11,7 +12,7 @@ use Drupal\Core\Plugin\ContainerFactoryPluginInterface; | |
use Symfony\Component\DependencyInjection\ContainerInterface; | |
/** | |
- * Field handler for geolocaiton field. | |
+ * Field handler for geolocation field. | |
* | |
* @ingroup views_field_handlers | |
* | |
@@ -102,6 +103,7 @@ class ProximityField extends NumericField implements ContainerFactoryPluginInter | |
'#options' => [ | |
'direct_input' => $this->t('Static Values'), | |
'user_input' => $this->t('User input'), | |
+ 'client_location' => $this->t('Client location (HTML5)'), | |
], | |
]; | |
@@ -154,6 +156,8 @@ class ProximityField extends NumericField implements ContainerFactoryPluginInter | |
['select[name="options[proximity_source]"]' => ['value' => 'entity_id_argument']], | |
'or', | |
['select[name="options[proximity_source]"]' => ['value' => 'user_input']], | |
+ 'or', | |
+ ['select[name="options[proximity_source]"]' => ['value' => 'client_location']], | |
], | |
], | |
], | |
@@ -378,6 +382,21 @@ class ProximityField extends NumericField implements ContainerFactoryPluginInter | |
$units = $this->options['proximity_units']; | |
break; | |
+ case 'client_location': | |
+ $geolocation = $this->getStoredUserGeolocation(); | |
+ if ($geolocation) { | |
+ list($latitude, $longitude) = explode(':', $geolocation); | |
+ } | |
+ else { | |
+ $latitude = $this->view->getRequest()->get('proximity_lat', ''); | |
+ $longitude = $this->view->getRequest()->get('proximity_lng', ''); | |
+ // Set the geolocation coordinates in a cookie so subsequent requests | |
+ // don't have to retrieve the geolocation from the client. | |
+ $this->storeUserGeolocation($latitude, $longitude); | |
+ } | |
+ $units = $this->options['proximity_units']; | |
+ break; | |
+ | |
case 'filter': | |
/** @var \Drupal\geolocation\Plugin\views\filter\ProximityFilter $filter */ | |
$filter = $this->view->filter[$this->options['proximity_filter']]; | |
@@ -473,29 +492,35 @@ class ProximityField extends NumericField implements ContainerFactoryPluginInter | |
* The current state of the form. | |
*/ | |
public function viewsForm(array &$form, FormStateInterface $form_state) { | |
- if ($this->options['proximity_source'] != 'user_input') { | |
+ if (!in_array($this->options['proximity_source'], ['user_input', 'client_location'])) { | |
unset($form['actions']); | |
return; | |
} | |
+ | |
+ $proximity_source = $this->options['proximity_source']; | |
+ | |
$form['#cache']['max-age'] = 0; | |
$form['#method'] = 'GET'; | |
$form['#attributes']['class'][] = 'geolocation-views-proximity-field'; | |
+ $proximity_lat = $this->view->getRequest()->get('proximity_lat', ''); | |
$form['proximity_lat'] = [ | |
'#type' => 'textfield', | |
'#title' => $this->t('Latitude'), | |
'#empty_value' => '', | |
- '#default_value' => $this->view->getRequest()->get('proximity_lat', ''), | |
+ '#default_value' => $proximity_lat, | |
'#maxlength' => 255, | |
'#weight' => -1, | |
]; | |
+ | |
+ $proximity_lng = $this->view->getRequest()->get('proximity_lng', ''); | |
$form['proximity_lng'] = [ | |
'#type' => 'textfield', | |
'#title' => $this->t('Longitude'), | |
'#empty_value' => '', | |
- '#default_value' => $this->view->getRequest()->get('proximity_lng', ''), | |
+ '#default_value' => $proximity_lng, | |
'#maxlength' => 255, | |
'#weight' => -1, | |
]; | |
@@ -503,6 +528,7 @@ class ProximityField extends NumericField implements ContainerFactoryPluginInter | |
if ( | |
$this->options['proximity_geocoder'] | |
&& !empty($this->options['proximity_geocoder_plugin_settings']) | |
+ && $proximity_source !== 'client_location' | |
) { | |
$geocoder_configuration = $this->options['proximity_geocoder_plugin_settings']['settings']; | |
$geocoder_configuration['label'] = $this->t('Address'); | |
@@ -533,6 +559,64 @@ class ProximityField extends NumericField implements ContainerFactoryPluginInter | |
$form['actions']['submit']['#value'] = $this->t('Calculate proximity'); | |
+ // Add functionality for retrieving location from client | |
+ // using HTML5 geolocation API. | |
+ if ($proximity_source === 'client_location') { | |
+ // Determine if we already have coordinate data, either retrieved from the | |
+ // current request or stored in a cookie. | |
+ $has_coordinates = ((!empty($proximity_lat) && !empty($proximity_lng)) || | |
+ $this->getStoredUserGeolocation()); | |
+ | |
+ // If AJAX is not enabled on this view, then enable it and run | |
+ // Views pre-render code to generate the AJAX settings for the | |
+ // view. These AJAX settings are used by the HTML5 geolocation | |
+ // code to auto-update the view after the HTML5 client location | |
+ // is retrieved. | |
+ if (!$this->view->ajaxEnabled() && !$has_coordinates) { | |
+ $this->view->setAjaxEnabled(TRUE); | |
+ views_views_pre_render($this->view); | |
+ } | |
+ | |
+ // By default, the view will auto-refresh using AJAX once HTML5 | |
+ // coordinates are received. | |
+ $auto_refresh = true; | |
+ | |
+ // Determine if this view requires exposed form input and if any | |
+ // exposed input exists. If the exposed form requires input and | |
+ // none exists, then the view will not be auto-refreshed via | |
+ // AJAX. | |
+ $exposed_form_plugin = $this->view->display_handler->getPlugin('exposed_form'); | |
+ if ($exposed_form_plugin instanceof InputRequired) { | |
+ $auto_refresh = (!empty($this->view->getExposedInput())); | |
+ } | |
+ | |
+ // Add assets and drupalSettings for HTML5 geolocation. | |
+ $form = array_merge_recursive($form, [ | |
+ '#attached' => [ | |
+ 'library' => [ | |
+ 'geolocation/geolocation.proximity.html5', | |
+ ], | |
+ 'drupalSettings' => [ | |
+ 'geolocation' => [ | |
+ 'html5' => [ | |
+ 'hasCoordinates' => $has_coordinates, | |
+ 'proximity_view_ids' => [$this->view->dom_id], | |
+ $this->view->dom_id => [ | |
+ 'auto_refresh' => $auto_refresh, | |
+ ], | |
+ ], | |
+ ], | |
+ ], | |
+ ], | |
+ ]); | |
+ | |
+ // Remove the form inputs, as the view will be dynamically | |
+ // refreshed if HTML5 geolocation succeeds. | |
+ unset($form['proximity_lat']); | |
+ unset($form['proximity_lng']); | |
+ unset($form['actions']); | |
+ } | |
+ | |
// #weight will be stripped from 'output' in preRender callback. | |
// Offset negatively to compensate. | |
foreach (Element::children($form) as $key) { | |
@@ -561,4 +645,34 @@ class ProximityField extends NumericField implements ContainerFactoryPluginInter | |
return parent::render($row); | |
} | |
+ /** | |
+ * Store the client location in temporary storage. | |
+ * | |
+ * @param $latitude | |
+ * The latitude coordinate. | |
+ * @param $longitude | |
+ * The longitude coordinate. | |
+ */ | |
+ protected function storeUserGeolocation($latitude, $longitude) { | |
+ if (empty($latitude) || empty($longitude) || | |
+ $this->view->getRequest()->cookies->has('client_location')) { | |
+ return; | |
+ } | |
+ setCookie('client_location', "{$latitude}:{$longitude}", 0, '/', $this->view->getRequest()->getHost(), FALSE, TRUE); | |
+ } | |
+ | |
+ /** | |
+ * Retrieve the client location from temporary storage. | |
+ * | |
+ * @return bool|mixed | |
+ * Return FALSE if no client location cookie exists, else return the cookie | |
+ * value. | |
+ */ | |
+ protected function getStoredUserGeolocation() { | |
+ if (!$this->view->getRequest()->cookies->has('client_location')) { | |
+ return FALSE; | |
+ } | |
+ return $this->view->getRequest()->cookies->get('client_location'); | |
+ } | |
+ | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment