123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595 |
- /**
- * jQuery Geocoding and Places Autocomplete Plugin - V 1.7.0
- *
- * @author Martin Kleppe <kleppe@ubilabs.net>, 2016
- * @author Ubilabs http://ubilabs.net, 2016
- * @license MIT License <http://www.opensource.org/licenses/mit-license.php>
- */
-
- // # $.geocomplete()
- // ## jQuery Geocoding and Places Autocomplete Plugin
- //
- // * https://github.com/ubilabs/geocomplete/
- // * by Martin Kleppe <kleppe@ubilabs.net>
-
- (function ($, window, document, undefined) {
-
- // ## Options
- // The default options for this plugin.
- //
- // * `map` - Might be a selector, an jQuery object or a DOM element. Default is `false` which shows no map.
- // * `details` - The container that should be populated with data. Defaults to `false` which ignores the setting.
- // * 'detailsScope' - Allows you to scope the 'details' container and have multiple geocomplete fields on one page. Must be a parent of the input. Default is 'null'
- // * `location` - Location to initialize the map on. Might be an address `string` or an `array` with [latitude, longitude] or a `google.maps.LatLng`object. Default is `false` which shows a blank map.
- // * `bounds` - Whether to snap geocode search to map bounds. Default: `true` if false search globally. Alternatively pass a custom `LatLngBounds object.
- // * `autoselect` - Automatically selects the highlighted item or the first item from the suggestions list on Enter.
- // * `detailsAttribute` - The attribute's name to use as an indicator. Default: `"name"`
- // * `mapOptions` - Options to pass to the `google.maps.Map` constructor. See the full list [here](http://code.google.com/apis/maps/documentation/javascript/reference.html#MapOptions).
- // * `mapOptions.zoom` - The inital zoom level. Default: `14`
- // * `mapOptions.scrollwheel` - Whether to enable the scrollwheel to zoom the map. Default: `false`
- // * `mapOptions.mapTypeId` - The map type. Default: `"roadmap"`
- // * `markerOptions` - The options to pass to the `google.maps.Marker` constructor. See the full list [here](http://code.google.com/apis/maps/documentation/javascript/reference.html#MarkerOptions).
- // * `markerOptions.draggable` - If the marker is draggable. Default: `false`. Set to true to enable dragging.
- // * `markerOptions.disabled` - Do not show marker. Default: `false`. Set to true to disable marker.
- // * `maxZoom` - The maximum zoom level too zoom in after a geocoding response. Default: `16`
- // * `types` - An array containing one or more of the supported types for the places request. Default: `['geocode']` See the full list [here](http://code.google.com/apis/maps/documentation/javascript/places.html#place_search_requests).
- // * `blur` - Trigger geocode when input loses focus.
- // * `geocodeAfterResult` - If blur is set to true, choose whether to geocode if user has explicitly selected a result before blur.
- // * `restoreValueAfterBlur` - Restores the input's value upon blurring. Default is `false` which ignores the setting.
-
- var defaults = {
- bounds: true,
- country: null,
- map: false,
- details: false,
- detailsAttribute: "name",
- detailsScope: null,
- autoselect: true,
- location: false,
-
- mapOptions: {
- zoom: 14,
- scrollwheel: true,
- mapTypeId: "roadmap"
- },
-
- markerOptions: {
- draggable: false
- },
-
- maxZoom: 16,
- types: ['geocode'],
- blur: false,
- geocodeAfterResult: false,
- restoreValueAfterBlur: false
- };
-
- // See: [Geocoding Types](https://developers.google.com/maps/documentation/geocoding/#Types)
- // on Google Developers.
- var componentTypes = ("street_address route intersection political " +
- "country administrative_area_level_1 administrative_area_level_2 " +
- "administrative_area_level_3 colloquial_area locality sublocality " +
- "neighborhood premise subpremise postal_code natural_feature airport " +
- "park point_of_interest post_box street_number floor room " +
- "lat lng viewport location " +
- "formatted_address location_type bounds").split(" ");
-
- // See: [Places Details Responses](https://developers.google.com/maps/documentation/javascript/places#place_details_responses)
- // on Google Developers.
- var placesDetails = ("id place_id url website vicinity reference name rating " +
- "international_phone_number icon formatted_phone_number").split(" ");
-
- // The actual plugin constructor.
- function GeoComplete(input, options) {
-
- this.options = $.extend(true, {}, defaults, options);
-
- // This is a fix to allow types:[] not to be overridden by defaults
- // so search results includes everything
- if (options && options.types) {
- this.options.types = options.types;
- }
-
- this.input = input;
- this.$input = $(input);
-
- this._defaults = defaults;
- this._name = 'geocomplete';
-
- this.init();
- }
-
- // Initialize all parts of the plugin.
- $.extend(GeoComplete.prototype, {
- init: function () {
- this.initMap();
- this.initMarker();
- this.initGeocoder();
- this.initDetails();
- this.initLocation();
- },
-
- // Initialize the map but only if the option `map` was set.
- // This will create a `map` within the given container
- // using the provided `mapOptions` or link to the existing map instance.
- initMap: function () {
- if (!this.options.map) { return; }
-
- if (typeof this.options.map.setCenter == "function") {
- this.map = this.options.map;
- return;
- }
-
- this.map = new google.maps.Map(
- $(this.options.map)[0],
- this.options.mapOptions
- );
-
- // add click event listener on the map
- google.maps.event.addListener(
- this.map,
- 'click',
- $.proxy(this.mapClicked, this)
- );
-
- // add dragend even listener on the map
- google.maps.event.addListener(
- this.map,
- 'dragend',
- $.proxy(this.mapDragged, this)
- );
-
- // add idle even listener on the map
- google.maps.event.addListener(
- this.map,
- 'idle',
- $.proxy(this.mapIdle, this)
- );
-
- google.maps.event.addListener(
- this.map,
- 'zoom_changed',
- $.proxy(this.mapZoomed, this)
- );
- },
-
- // Add a marker with the provided `markerOptions` but only
- // if the option was set. Additionally it listens for the `dragend` event
- // to notify the plugin about changes.
- initMarker: function () {
- if (!this.map) { return; }
- var options = $.extend(this.options.markerOptions, { map: this.map });
-
- if (options.disabled) { return; }
-
- this.marker = new google.maps.Marker(options);
-
- google.maps.event.addListener(
- this.marker,
- 'dragend',
- $.proxy(this.markerDragged, this)
- );
- },
-
- // Associate the input with the autocompleter and create a geocoder
- // to fall back when the autocompleter does not return a value.
- initGeocoder: function () {
-
- // Indicates is user did select a result from the dropdown.
- var selected = false;
-
- var options = {
- types: this.options.types,
- bounds: this.options.bounds === true ? null : this.options.bounds,
- componentRestrictions: this.options.componentRestrictions
- };
-
- if (this.options.country) {
- options.componentRestrictions = { country: this.options.country };
- }
-
- this.autocomplete = new google.maps.places.Autocomplete(
- this.input, options
- );
-
- this.geocoder = new google.maps.Geocoder();
-
- // Bind autocomplete to map bounds but only if there is a map
- // and `options.bindToMap` is set to true.
- if (this.map && this.options.bounds === true) {
- this.autocomplete.bindTo('bounds', this.map);
- }
-
- // Watch `place_changed` events on the autocomplete input field.
- google.maps.event.addListener(
- this.autocomplete,
- 'place_changed',
- $.proxy(this.placeChanged, this)
- );
-
- // Prevent parent form from being submitted if user hit enter.
- this.$input.on('keypress.' + this._name, function (event) {
- if (event.keyCode === 13) { return false; }
- });
-
- // Assume that if user types anything after having selected a result,
- // the selected location is not valid any more.
- if (this.options.geocodeAfterResult === true) {
- this.$input.bind('keypress.' + this._name, $.proxy(function () {
- if (event.keyCode != 9 && this.selected === true) {
- this.selected = false;
- }
- }, this));
- }
-
- // Listen for "geocode" events and trigger find action.
- this.$input.bind('geocode.' + this._name, $.proxy(function () {
- this.find();
- }, this));
-
- // Saves the previous input value
- this.$input.bind('geocode:result.' + this._name, $.proxy(function () {
- this.lastInputVal = this.$input.val();
- }, this));
-
- // Trigger find action when input element is blurred out and user has
- // not explicitly selected a result.
- // (Useful for typing partial location and tabbing to the next field
- // or clicking somewhere else.)
- if (this.options.blur === true) {
- this.$input.on('blur.' + this._name, $.proxy(function () {
- if (this.options.geocodeAfterResult === true && this.selected === true) { return; }
-
- if (this.options.restoreValueAfterBlur === true && this.selected === true) {
- setTimeout($.proxy(this.restoreLastValue, this), 0);
- } else {
- this.find();
- }
- }, this));
- }
- },
-
- // Prepare a given DOM structure to be populated when we got some data.
- // This will cycle through the list of component types and map the
- // corresponding elements.
- initDetails: function () {
- if (!this.options.details) { return; }
-
- if (this.options.detailsScope) {
- var $details = $(this.input).parents(this.options.detailsScope).find(this.options.details);
- } else {
- var $details = $(this.options.details);
- }
-
- var attribute = this.options.detailsAttribute,
- details = {};
-
- function setDetail(value) {
- details[value] = $details.find("[" + attribute + "=" + value + "]");
- }
-
- $.each(componentTypes, function (index, key) {
- setDetail(key);
- setDetail(key + "_short");
- });
-
- $.each(placesDetails, function (index, key) {
- setDetail(key);
- });
-
- this.$details = $details;
- this.details = details;
- },
-
- // Set the initial location of the plugin if the `location` options was set.
- // This method will care about converting the value into the right format.
- initLocation: function () {
-
- var location = this.options.location, latLng;
-
- if (!location) { return; }
-
- if (typeof location == 'string') {
- this.find(location);
- return;
- }
-
- if (location instanceof Array) {
- latLng = new google.maps.LatLng(location[0], location[1]);
- }
-
- if (location instanceof google.maps.LatLng) {
- latLng = location;
- }
-
- if (latLng) {
- if (this.map) { this.map.setCenter(latLng); }
- if (this.marker) { this.marker.setPosition(latLng); }
- }
- },
-
- destroy: function () {
- if (this.map) {
- google.maps.event.clearInstanceListeners(this.map);
- google.maps.event.clearInstanceListeners(this.marker);
- }
-
- this.autocomplete.unbindAll();
- google.maps.event.clearInstanceListeners(this.autocomplete);
- google.maps.event.clearInstanceListeners(this.input);
- this.$input.removeData();
- this.$input.off(this._name);
- this.$input.unbind('.' + this._name);
- },
-
- // Look up a given address. If no `address` was specified it uses
- // the current value of the input.
- find: function (address) {
- this.geocode({
- address: address || this.$input.val()
- });
- },
-
- // Requests details about a given location.
- // Additionally it will bias the requests to the provided bounds.
- geocode: function (request) {
- // Don't geocode if the requested address is empty
- if (!request.address) {
- return;
- }
- if (this.options.bounds && !request.bounds) {
- if (this.options.bounds === true) {
- request.bounds = this.map && this.map.getBounds();
- } else {
- request.bounds = this.options.bounds;
- }
- }
-
- if (this.options.country) {
- request.region = this.options.country;
- }
-
- this.geocoder.geocode(request, $.proxy(this.handleGeocode, this));
- },
-
- // Get the selected result. If no result is selected on the list, then get
- // the first result from the list.
- selectFirstResult: function () {
- //$(".pac-container").hide();
-
- var selected = '';
- // Check if any result is selected.
- if ($(".pac-item-selected")[0]) {
- selected = '-selected';
- }
-
- // Get the first suggestion's text.
- var $span1 = $(".pac-container:visible .pac-item" + selected + ":first span:nth-child(2)").text();
- var $span2 = $(".pac-container:visible .pac-item" + selected + ":first span:nth-child(3)").text();
-
- // Adds the additional information, if available.
- var firstResult = $span1;
- if ($span2) {
- firstResult += " - " + $span2;
- }
-
- this.$input.val(firstResult);
-
- return firstResult;
- },
-
- // Restores the input value using the previous value if it exists
- restoreLastValue: function () {
- if (this.lastInputVal) { this.$input.val(this.lastInputVal); }
- },
-
- // Handles the geocode response. If more than one results was found
- // it triggers the "geocode:multiple" events. If there was an error
- // the "geocode:error" event is fired.
- handleGeocode: function (results, status) {
- if (status === google.maps.GeocoderStatus.OK) {
- var result = results[0];
- this.$input.val(result.formatted_address);
- this.update(result);
-
- if (results.length > 1) {
- this.trigger("geocode:multiple", results);
- }
-
- } else {
- this.trigger("geocode:error", status);
- }
- },
-
- // Triggers a given `event` with optional `arguments` on the input.
- trigger: function (event, argument) {
- this.$input.trigger(event, [argument]);
- },
-
- // Set the map to a new center by passing a `geometry`.
- // If the geometry has a viewport, the map zooms out to fit the bounds.
- // Additionally it updates the marker position.
- center: function (geometry) {
- if (geometry.viewport) {
- this.map.fitBounds(geometry.viewport);
- if (this.map.getZoom() > this.options.maxZoom) {
- this.map.setZoom(this.options.maxZoom);
- }
- } else {
- this.map.setZoom(this.options.maxZoom);
- this.map.setCenter(geometry.location);
- }
-
- if (this.marker) {
- this.marker.setPosition(geometry.location);
- this.marker.setAnimation(this.options.markerOptions.animation);
- }
- },
-
- // Update the elements based on a single places or geocoding response
- // and trigger the "geocode:result" event on the input.
- update: function (result) {
-
- if (this.map) {
- this.center(result.geometry);
- }
-
- if (this.$details) {
- this.fillDetails(result);
- }
-
- this.trigger("geocode:result", result);
- },
-
- // Populate the provided elements with new `result` data.
- // This will lookup all elements that has an attribute with the given
- // component type.
- fillDetails: function (result) {
-
- var data = {},
- geometry = result.geometry,
- viewport = geometry.viewport,
- bounds = geometry.bounds;
-
- // Create a simplified version of the address components.
- $.each(result.address_components, function (index, object) {
- var name = object.types[0];
-
- $.each(object.types, function (index, name) {
- data[name] = object.long_name;
- data[name + "_short"] = object.short_name;
- });
- });
-
- // Add properties of the places details.
- $.each(placesDetails, function (index, key) {
- data[key] = result[key];
- });
-
- // Add infos about the address and geometry.
- $.extend(data, {
- formatted_address: result.formatted_address,
- location_type: geometry.location_type || "PLACES",
- viewport: viewport,
- bounds: bounds,
- location: geometry.location,
- lat: geometry.location.lat(),
- lng: geometry.location.lng()
- });
-
- // Set the values for all details.
- $.each(this.details, $.proxy(function (key, $detail) {
- var value = data[key];
- this.setDetail($detail, value);
- }, this));
-
- this.data = data;
- },
-
- // Assign a given `value` to a single `$element`.
- // If the element is an input, the value is set, otherwise it updates
- // the text content.
- setDetail: function ($element, value) {
-
- if (value === undefined) {
- value = "";
- } else if (typeof value.toUrlValue == "function") {
- value = value.toUrlValue();
- }
-
- if ($element.is(":input")) {
- $element.val(value);
- } else {
- $element.text(value);
- }
- },
-
- // Fire the "geocode:dragged" event and pass the new position.
- markerDragged: function (event) {
- this.trigger("geocode:dragged", event.latLng);
- },
- markerPositionChanged: function (event) {
- this.trigger("geocode:dragged", event.latLng);
- },
- mapClicked: function (event) {
- this.trigger("geocode:click", event.latLng);
- },
-
- // Fire the "geocode:mapdragged" event and pass the current position of the map center.
- mapDragged: function (event) {
- this.trigger("geocode:mapdragged", this.map.getCenter());
- },
-
- // Fire the "geocode:idle" event and pass the current position of the map center.
- mapIdle: function (event) {
- this.trigger("geocode:idle", this.map.getCenter());
- },
-
- mapZoomed: function (event) {
- this.trigger("geocode:zoom", this.map.getZoom());
- },
-
- // Restore the old position of the marker to the last knwon location.
- resetMarker: function () {
- this.marker.setPosition(this.data.location);
- this.setDetail(this.details.lat, this.data.location.lat());
- this.setDetail(this.details.lng, this.data.location.lng());
- },
-
- // Update the plugin after the user has selected an autocomplete entry.
- // If the place has no geometry it passes it to the geocoder.
- placeChanged: function () {
- var place = this.autocomplete.getPlace();
- this.selected = true;
-
- if (!place.geometry) {
- if (this.options.autoselect) {
- // Automatically selects the highlighted item or the first item from the
- // suggestions list.
- var autoSelection = this.selectFirstResult();
- this.find(autoSelection);
- }
- } else {
- // Use the input text if it already gives geometry.
- this.update(place);
- }
- }
- });
-
- // A plugin wrapper around the constructor.
- // Pass `options` with all settings that are different from the default.
- // The attribute is used to prevent multiple instantiations of the plugin.
- $.fn.geocomplete = function (options) {
-
- var attribute = 'plugin_geocomplete';
-
- // If you call `.geocomplete()` with a string as the first parameter
- // it returns the corresponding property or calls the method with the
- // following arguments.
- if (typeof options == "string") {
-
- var instance = $(this).data(attribute) || $(this).geocomplete().data(attribute),
- prop = instance[options];
-
- if (typeof prop == "function") {
- prop.apply(instance, Array.prototype.slice.call(arguments, 1));
- return $(this);
- } else {
- if (arguments.length == 2) {
- prop = arguments[1];
- }
- return prop;
- }
- } else {
- return this.each(function () {
- // Prevent against multiple instantiations.
- var instance = $.data(this, attribute);
- if (!instance) {
- instance = new GeoComplete(this, options);
- $.data(this, attribute, instance);
- }
- });
- }
- };
-
- })(jQuery, window, document);
|