PageSourceSearch

https://www.thetruckersreport.com/jobs/js/jquery.geocomplete.min.js

js thetruckersreport.com collected 2026-09-24 09:34:47 UTC 18,991 bytes, 605 lines download raw bytes

1/**
2 * jQuery Geocoding and Places Autocomplete Plugin - V 1.7.0
3 *
4 * @author Martin Kleppe <[email protected]>, 2016
5 * @author Ubilabs http://ubilabs.net, 2016
6 * @license MIT License <http://www.opensource.org/licenses/mit-license.php>
7 */
8
9// # $.geocomplete()
10// ## jQuery Geocoding and Places Autocomplete Plugin
11//
12// * https://github.com/ubilabs/geocomplete/
13// * by Martin Kleppe <[email protected]>
14
15(function($, window, document, undefined){
16
17	// ## Options
18	// The default options for this plugin.
19	//
20	// * `map` - Might be a selector, an jQuery object or a DOM element. Default is `false` which shows no map.
21	// * `details` - The container that should be populated with data. Defaults to `false` which ignores the setting.
22	// * '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'
23	// * `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.
24	// * `bounds` - Whether to snap geocode search to map bounds. Default: `true` if false search globally. Alternatively pass a custom `LatLngBounds object.
25	// * `autoselect` - Automatically selects the highlighted item or the first item from the suggestions list on Enter.
26	// * `detailsAttribute` - The attribute's name to use as an indicator. Default: `"name"`
27	// * `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).
28	// * `mapOptions.zoom` - The inital zoom level. Default: `14`
29	// * `mapOptions.scrollwheel` - Whether to enable the scrollwheel to zoom the map. Default: `false`
30	// * `mapOptions.mapTypeId` - The map type. Default: `"roadmap"`
31	// * `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).
32	// * `markerOptions.draggable` - If the marker is draggable. Default: `false`. Set to true to enable dragging.
33	// * `markerOptions.disabled` - Do not show marker. Default: `false`. Set to true to disable marker.
34	// * `maxZoom` - The maximum zoom level too zoom in after a geocoding response. Default: `16`
35	// * `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).
36	// * `blur` - Trigger geocode when input loses focus.
37	// * `geocodeAfterResult` - If blur is set to true, choose whether to geocode if user has explicitly selected a result before blur.
38	// * `restoreValueAfterBlur` - Restores the input's value upon blurring. Default is `false` which ignores the setting.
39
40	var defaults = {
41		bounds: true,
42		country: 'US',
43		map: false,
44		details: false,
45		detailsAttribute: "name",
46		detailsScope: null,
47		autoselect: true,
48		location: false,
49
50		mapOptions: {
51			zoom: 14,
52			scrollwheel: false,
53			mapTypeId: "roadmap"
54		},
55
56		markerOptions: {
57			draggable: false
58		},
59
60		maxZoom: 16,
61		types: ['geocode'],
62		blur: false,
63		geocodeAfterResult: false,
64		restoreValueAfterBlur: false
65	};
66
67	// See: [Geocoding Types](https://developers.google.com/maps/documentation/geocoding/#Types)
68	// on Google Developers.
69	var componentTypes = ("street_address route intersection political " +
70	"country administrative_area_level_1 administrative_area_level_2 " +
71	"administrative_area_level_3 colloquial_area locality sublocality " +
72	"neighborhood premise subpremise postal_code natural_feature airport " +
73	"park point_of_interest post_box street_number floor room " +
74	"lat lng viewport location " +
75	"formatted_address location_type bounds").split(" ");
76
77	// See: [Places Details Responses](https://developers.google.com/maps/documentation/javascript/places#place_details_responses)
78	// on Google Developers.
79	var placesDetails = ("id place_id url website vicinity reference name rating " +
80	"international_phone_number icon formatted_phone_number").split(" ");
81
82	// The actual plugin constructor.
83	function GeoComplete(input, options) {
84
85		this.options = $.extend(true, {}, defaults, options);
86
87		// This is a fix to allow types:[] not to be overridden by defaults
88		// so search results includes everything
89		if (options && options.types) {
90			this.options.types = options.types;
91		}
92
93		this.input = input;
94		this.$input = $(input);
95
96		this._defaults = defaults;
97		this._name = 'geocomplete';
98
99		this.init();
100	}
101
102	// Initialize all parts of the plugin.
103	$.extend(GeoComplete.prototype, {
104		init: function(){
105			this.initMap();
106			this.initMarker();
107			this.initGeocoder();
108			this.initDetails();
109			this.initLocation();
110		},
111
112		// Initialize the map but only if the option `map` was set.
113		// This will create a `map` within the given container
114		// using the provided `mapOptions` or link to the existing map instance.
115		initMap: function(){
116			if (!this.options.map){ return; }
117
118			if (typeof this.options.map.setCenter == "function"){
119				this.map = this.options.map;
120				return;
121			}
122
123			this.map = new google.maps.Map(
124				$(this.options.map)[0],
125				this.options.mapOptions
126			);
127
128			// add click event listener on the map
129			google.maps.event.addListener(
130				this.map,
131				'click',
132				$.proxy(this.mapClicked, this)
133			);
134
135			// add dragend even listener on the map
136			google.maps.event.addListener(
137				this.map,
138				'dragend',
139				$.proxy(this.mapDragged, this)
140			);
141
142			// add idle even listener on the map
143			google.maps.event.addListener(
144				this.map,
145				'idle',
146				$.proxy(this.mapIdle, this)
147			);
148
149			google.maps.event.addListener(
150				this.map,
151				'zoom_changed',
152				$.proxy(this.mapZoomed, this)
153			);
154		},
155
156		// Add a marker with the provided `markerOptions` but only
157		// if the option was set. Additionally it listens for the `dragend` event
158		// to notify the plugin about changes.
159		initMarker: function(){
160			if (!this.map){ return; }
161			var options = $.extend(this.options.markerOptions, { map: this.map });
162
163			if (options.disabled){ return; }
164
165			this.marker = new google.maps.Marker(options);
166
167			google.maps.event.addListener(
168				this.marker,
169				'dragend',
170				$.proxy(this.markerDragged, this)
171			);
172		},
173
174		// Associate the input with the autocompleter and create a geocoder
175		// to fall back when the autocompleter does not return a value.
176		initGeocoder: function(){
177
178			// Indicates is user did select a result from the dropdown.
179			var selected = false;
180
181			var options = {
182				types: this.options.types,
183				bounds: this.options.bounds === true ? null : this.options.bounds,
184				componentRestrictions: this.options.componentRestrictions
185			};
186
187			if (this.options.country){
188				options.componentRestrictions = {country: this.options.country};
189			}
190
191			this.autocomplete = new google.maps.places.Autocomplete(
192				this.input, options
193			);
194
195			// Hack
196			document.addEventListener('DOMNodeInserted', function(event) {
197				var target = $(event.target);
198				if (target.hasClass('pac-item')) {
199					target.html(target.html().replace(/, USA<\/span>$/, "</span>"));
200				}
201			});
202
203			this.geocoder = new google.maps.Geocoder();
204
205			// Bind autocomplete to map bounds but only if there is a map
206			// and `options.bindToMap` is set to true.
207			if (this.map && this.options.bounds === true){
208				this.autocomplete.bindTo('bounds', this.map);
209			}
210
211			// Watch `place_changed` events on the autocomplete input field.
212			google.maps.event.addListener(
213				this.autocomplete,
214				'place_changed',
215				$.proxy(this.placeChanged, this)
216			);
217
218			// Prevent parent form from being submitted if user hit enter.
219			this.$input.on('keypress.' + this._name, function(event){
220				if (event.keyCode === 13){ return false; }
221			});
222
223			// Assume that if user types anything after having selected a result,
224			// the selected location is not valid any more.
225			if (this.options.geocodeAfterResult === true){
226				this.$input.bind('keypress.' + this._name, $.proxy(function(){
227					if (event.keyCode != 9 && this.selected === true){
228						this.selected = false;
229					}
230				}, this));
231			}
232
233			// Listen for "geocode" events and trigger find action.
234			this.$input.bind('geocode.' + this._name, $.proxy(function(){
235				this.find();
236			}, this));
237
238			// Saves the previous input value
239			this.$input.bind('geocode:result.' + this._name, $.proxy(function(){
240				this.lastInputVal = this.$input.val();
241			}, this));
242
243			// Trigger find action when input element is blurred out and user has
244			// not explicitly selected a result.
245			// (Useful for typing partial location and tabbing to the next field
246			// or clicking somewhere else.)
247			if (this.options.blur === true){
248				this.$input.on('blur.' + this._name, $.proxy(function(){
249					if (this.options.geocodeAfterResult === true && this.selected === true) { return; }
250
251					if (this.options.restoreValueAfterBlur === true && this.selected === true) {
252						setTimeout($.proxy(this.restoreLastValue, this), 0);
253					} else {
254						this.find();
255					}
256				}, this));
257			}
258		},
259
260		// Prepare a given DOM structure to be populated when we got some data.
261		// This will cycle through the list of component types and map the
262		// corresponding elements.
263		initDetails: function(){
264			if (!this.options.details){ return; }
265
266			if(this.options.detailsScope) {
267				var $details = $(this.input).parents(this.options.detailsScope).find(this.options.details);
268			} else {
269				var $details = $(this.options.details);
270			}
271
272			var attribute = this.options.detailsAttribute,
273				details = {};
274
275			function setDetail(value){
276				details[value] = $details.find("[" +  attribute + "=" + value + "]");
277			}
278
279			$.each(componentTypes, function(index, key){
280				setDetail(key);
281				setDetail(key + "_short");
282			});
283
284			$.each(placesDetails, function(index, key){
285				setDetail(key);
286			});
287
288			this.$details = $details;
289			this.details = details;
290		},
291
292		// Set the initial location of the plugin if the `location` options was set.
293		// This method will care about converting the value into the right format.
294		initLocation: function() {
295
296			var location = this.options.location, latLng;
297
298			if (!location) { return; }
299
300			if (typeof location == 'string') {
301				this.find(location);
302				return;
303			}
304
305			if (location instanceof Array) {
306				latLng = new google.maps.LatLng(location[0], location[1]);
307			}
308
309			if (location instanceof google.maps.LatLng){
310				latLng = location;
311			}
312
313			if (latLng){
314				if (this.map){ this.map.setCenter(latLng); }
315				if (this.marker){ this.marker.setPosition(latLng); }
316			}
317		},
318
319		destroy: function(){
320			if (this.map) {
321				google.maps.event.clearInstanceListeners(this.map);
322				google.maps.event.clearInstanceListeners(this.marker);
323			}
324
325			this.autocomplete.unbindAll();
326			google.maps.event.clearInstanceListeners(this.autocomplete);
327			google.maps.event.clearInstanceListeners(this.input);
328			this.$input.removeData();
329			this.$input.off(this._name);
330			this.$input.unbind('.' + this._name);
331		},
332
333		// Look up a given address. If no `address` was specified it uses
334		// the current value of the input.
335		find: function(address){
336			this.geocode({
337				address: address || this.$input.val()
338			});
339		},
340
341		// Requests details about a given location.
342		// Additionally it will bias the requests to the provided boun
342ds.
343		geocode: function(request){
344			// Don't geocode if the requested address is empty
345			if (!request.address) {
346				return;
347			}
348			if (this.options.bounds && !request.bounds){
349				if (this.options.bounds === true){
350					request.bounds = this.map && this.map.getBounds();
351				} else {
352					request.bounds = this.options.bounds;
353				}
354			}
355
356			if (this.options.country){
357				request.region = this.options.country;
358			}
359
360			this.geocoder.geocode(request, $.proxy(this.handleGeocode, this));
361		},
362
363		// Get the selected result. If no result is selected on the list, then get
364		// the first result from the list.
365		selectFirstResult: function() {
366			//$(".pac-container").hide();
367
368			var selected = '';
369			// Check if any result is selected.
370			if ($(".pac-item-selected")[0]) {
371				selected = '-selected';
372			}
373
374			// Get the first suggestion's text.
375			var $span1 = $(".pac-container:visible .pac-item" + selected + ":first span:nth-child(2)").text();
376			var $span2 = $(".pac-container:visible .pac-item" + selected + ":first span:nth-child(3)").text();
377
378			// Adds the additional information, if available.
379			var firstResult = $span1;
380			if ($span2) {
381				firstResult += " - " + $span2;
382			}
383
384			this.$input.val(firstResult);
385
386			return firstResult;
387		},
388
389		// Restores the input value using the previous value if it exists
390		restoreLastValue: function() {
391			if (this.lastInputVal){ this.$input.val(this.lastInputVal); }
392		},
393
394		// Handles the geocode response. If more than one results was found
395		// it triggers the "geocode:multiple" events. If there was an error
396		// the "geocode:error" event is fired.
397		handleGeocode: function(results, status){
398			if (status === google.maps.GeocoderStatus.OK) {
399				var result = results[0];
400				this.$input.val(result.formatted_address);
401				this.update(result);
402
403				if (results.length > 1){
404					this.trigger("geocode:multiple", results);
405				}
406
407			} else {
408				this.trigger("geocode:error", status);
409			}
410		},
411
412		// Triggers a given `event` with optional `arguments` on the input.
413		trigger: function(event, argument){
414			this.$input.trigger(event, [argument]);
415		},
416
417		// Set the map to a new center by passing a `geometry`.
418		// If the geometry has a viewport, the map zooms out to fit the bounds.
419		// Additionally it updates the marker position.
420		center: function(geometry){
421			if (geometry.viewport){
422				this.map.fitBounds(geometry.viewport);
423				if (this.map.getZoom() > this.options.maxZoom){
424					this.map.setZoom(this.options.maxZoom);
425				}
426			} else {
427				this.map.setZoom(this.options.maxZoom);
428				this.map.setCenter(geometry.location);
429			}
430
431			if (this.marker){
432				this.marker.setPosition(geometry.location);
433				this.marker.setAnimation(this.options.markerOptions.animation);
434			}
435		},
436
437		// Update the elements based on a single places or geocoding response
438		// and trigger the "geocode:result" event on the input.
439		update: function(result){
440
441			if (this.map){
442				this.center(result.geometry);
443			}
444
445			if (this.$details){
446				this.fillDetails(result);
447			}
448
449			this.trigger("geocode:result", result);
450		},
451
452		// Populate the provided elements with new `result` data.
453		// This will lookup all elements that has an attribute with the given
454		// component type.
455		fillDetails: function(result){
456
457			var data = {},
458				geometry = result.geometry,
459				viewport = geometry.viewport,
460				bounds = geometry.bounds;
461
462			// Create a simplified version of the address components.
463			$.each(result.address_components, function(index, object){
464				var name = object.types[0];
465
466				$.each(object.types, function(index, name){
467					data[name] = object.long_name;
468					data[name + "_short"] = object.short_name;
469				});
470			});
471
472			// Add properties of the places details.
473			$.each(placesDetails, function(index, key){
474				data[key] = result[key];
475			});
476
477			// Add infos about the address and geometry.
478			$.extend(data, {
479				formatted_address: result.formatted_address,
480				location_type: geometry.location_type || "PLACES",
481				viewport: viewport,
482				bounds: bounds,
483				location: geometry.location,
484				lat: geometry.location.lat(),
485				lng: geometry.location.lng()
486			});
487
488			// Set the values for all details.
489			$.each(this.details, $.proxy(function(key, $detail){
490				var value = data[key];
491				this.setDetail($detail, value);
492			}, this));
493
494			this.data = data;
495		},
496
497		// Assign a given `value` to a single `$element`.
498		// If the element is an input, the value is set, otherwise it updates
499		// the text content.
500		setDetail: function($element, value){
501
502			if (value === undefined){
503				value = "";
504			} else if (typeof value.toUrlValue == "function"){
505				value = value.toUrlValue();
506			}
507
508			if ($element.is(":input")){
509				$element.val(value);
510			} else {
511				$element.text(value);
512			}
513		},
514
515		// Fire the "geocode:dragged" event and pass the new position.
516		markerDragged: function(event){
517			this.trigger("geocode:dragged", event.latLng);
518		},
519
520		mapClicked: function(event) {
521			this.trigger("geocode:click", event.latLng);
522		},
523
524		// Fire the "geocode:mapdragged" event and pass the current position of the map center.
525		mapDragged: function(event) {
526			this.trigger("geocode:mapdragged", this.map.getCenter());
527		},
528
529		// Fire the "geocode:idle" event and pass the current position of the map center.
530		mapIdle: function(event) {
531			this.trigger("geocode:idle", this.map.getCenter());
532		},
533
534		mapZoomed: function(event) {
535			this.trigger("geocode:zoom", this.map.getZoom());
536		},
537
538		// Restore the old position of the marker to the last knwon location.
539		resetMarker: function(){
540			this.marker.setPosition(this.data.location);
541			this.setDetail(this.details.lat, this.data.location.lat());
542			this.setDetail(this.details.lng, this.data.location.lng());
543		},
544
545		// Update the plugin after the user has selected an autocomplete entry.
546		// If the place has no geometry it passes it to the geocoder.
547		placeChanged: function(){
548			var place = this.autocomplete.getPlace();
549			this.selected = true;
550
551			if (!place.geometry){
552				if (this.options.autoselect) {
553					// Automatically selects the highlighted item or the first item from the
554					// suggestions list.
555					var autoSelection = this.selectFirstResult();
556					this.find(autoSelection);
557				}
558			} else {
559				// Use the input text if it already gives geometry.
560				this.update(place);
561			}
562
563			if (this.options.callback) {
564				this.options.callback();
565			}
566		}
567	});
568
569	// A plugin wrapper around the constructor.
570	// Pass `options` with all settings that are different from the default.
571	// The attribute is used to prevent multiple instantiations of the plugin.
572	$.fn.geocomplete = function(options) {
573
574		var attribute = 'plugin_geocomplete';
575
576		// If you call `.geocomplete()` with a string as the first parameter
577		// it returns the corresponding property or calls the method with the
578		// following arguments.
579		if (typeof options == "string"){
580
581			var instance = $(this).data(attribute) || $(this).geocomplete().data(attribute),
582				prop = instance[options];
583
584			if (typeof prop == "function"){
585				prop.apply(instance, Array.prototype.slice.call(arguments, 1));
586				return $(this);
587			} else {
588				if (arguments.length == 2){
589					prop = arguments[1];
590				}
591				return prop;
592			}
593		} else {
594			return this.each(function() {
595				// Prevent against multiple instantiations.
596				var instance = $.data(this, attribute);
597				if (!instance) {
598					instance = new GeoComplete( this, options );
599					$.data(this, attribute, instance);
600				}
601			});
602		}
603	};
604
605})( jQuery, window, document );

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.