PluginProbe ʕ •ᴥ•ʔ
Secure Custom Fields / 6.9.1
Secure Custom Fields v6.9.1
6.9.1 6.9.0 6.8.9 6.8.7 6.8.8 6.8.6 6.8.4 6.8.5 trunk 6.4.0-beta1 6.4.0-beta2 6.4.1 6.4.1-beta3 6.4.1-beta4 6.4.1-beta5 6.4.1-beta6 6.4.1-beta7 6.4.2 6.5.0 6.5.1 6.5.2 6.5.3 6.5.4 6.5.5 6.5.6 6.5.7 6.6.0 6.7.0 6.7.1 6.8.0 6.8.1 6.8.2 6.8.3
secure-custom-fields / assets / src / js / _acf-field-google-map.js
secure-custom-fields / assets / src / js Last commit date
bindings 6 months ago commands 1 week ago pro 1 week ago _acf-compatibility.js 1 year ago _acf-condition-types.js 7 months ago _acf-condition.js 1 year ago _acf-conditions.js 1 year ago _acf-field-accordion.js 6 months ago _acf-field-button-group.js 7 months ago _acf-field-checkbox.js 1 month ago _acf-field-color-picker.js 7 months ago _acf-field-date-picker.js 1 month ago _acf-field-date-time-picker.js 1 month ago _acf-field-file.js 1 month ago _acf-field-google-map.js 6 months ago _acf-field-icon-picker.js 1 month ago _acf-field-image.js 1 month ago _acf-field-link.js 1 year ago _acf-field-oembed.js 1 month ago _acf-field-page-link.js 1 year ago _acf-field-post-object.js 1 year ago _acf-field-radio.js 1 month ago _acf-field-range.js 1 year ago _acf-field-relationship.js 1 month ago _acf-field-select.js 1 month ago _acf-field-tab.js 3 weeks ago _acf-field-taxonomy.js 7 months ago _acf-field-time-picker.js 1 month ago _acf-field-true-false.js 1 month ago _acf-field-url.js 1 year ago _acf-field-user.js 1 year ago _acf-field-wysiwyg.js 1 month ago _acf-field.js 1 year ago _acf-fields.js 1 year ago _acf-helpers.js 1 year ago _acf-hooks.js 1 year ago _acf-internal-post-type.js 7 months ago _acf-media.js 1 week ago _acf-modal.js 1 year ago _acf-model.js 1 year ago _acf-notice.js 7 months ago _acf-panel.js 1 year ago _acf-popup.js 7 months ago _acf-postbox.js 1 year ago _acf-screen.js 10 months ago _acf-select2.js 1 year ago _acf-tinymce.js 6 months ago _acf-tooltip.js 10 months ago _acf-unload.js 1 year ago _acf-validation.js 1 month ago _acf.js 7 months ago _browse-fields-modal.js 7 months ago _field-group-compatibility.js 1 year ago _field-group-conditions.js 1 year ago _field-group-field.js 3 months ago _field-group-fields.js 10 months ago _field-group-locations.js 1 year ago _field-group-settings.js 1 year ago _field-group.js 10 months ago acf-escaped-html-notice.js 1 year ago acf-field-group.js 1 year ago acf-input.js 1 year ago acf-internal-post-type.js 1 year ago acf.js 1 year ago
_acf-field-google-map.js
627 lines
1 ( function ( $, undefined ) {
2 var Field = acf.Field.extend( {
3 type: 'google_map',
4
5 map: false,
6
7 wait: 'load',
8
9 events: {
10 'click button[data-name="clear"]': 'onClickClear',
11 'click button[data-name="locate"]': 'onClickLocate',
12 'click button[data-name="search"]': 'onClickSearch',
13 'keydown .search': 'onKeydownSearch',
14 'keyup .search': 'onKeyupSearch',
15 'focus .search': 'onFocusSearch',
16 'blur .search': 'onBlurSearch',
17 showField: 'onShow',
18 },
19
20 $control: function () {
21 return this.$( '.acf-google-map' );
22 },
23
24 $search: function () {
25 return this.$( '.search' );
26 },
27
28 $canvas: function () {
29 return this.$( '.canvas' );
30 },
31
32 setState: function ( state ) {
33 // Remove previous state classes.
34 this.$control().removeClass( '-value -loading -searching' );
35
36 // Determine auto state based of current value.
37 if ( state === 'default' ) {
38 state = this.val() ? 'value' : '';
39 }
40
41 // Update state class.
42 if ( state ) {
43 this.$control().addClass( '-' + state );
44 }
45 },
46
47 getValue: function () {
48 var val = this.$input().val();
49 if ( val ) {
50 return JSON.parse( val );
51 } else {
52 return false;
53 }
54 },
55
56 setValue: function ( val, silent ) {
57 // Convert input value.
58 var valAttr = '';
59 if ( val ) {
60 valAttr = JSON.stringify( val );
61 }
62
63 // Update input (with change).
64 acf.val( this.$input(), valAttr );
65
66 // Bail early if silent update.
67 if ( silent ) {
68 return;
69 }
70
71 // Render.
72 this.renderVal( val );
73
74 /**
75 * Fires immediately after the value has changed.
76 *
77 * @date 12/02/2014
78 * @since ACF 5.0.0
79 *
80 * @param object|string val The new value.
81 * @param object map The Google Map instance.
82 * @param object field The field instance.
83 */
84 acf.doAction( 'google_map_change', val, this.map, this );
85 },
86
87 renderVal: function ( val ) {
88 // Value.
89 if ( val ) {
90 this.setState( 'value' );
91 this.$search().val( val.address );
92 this.setPosition( val.lat, val.lng );
93
94 // No value.
95 } else {
96 this.setState( '' );
97 this.$search().val( '' );
98 this.map.marker.setVisible( false );
99 }
100 },
101
102 newLatLng: function ( lat, lng ) {
103 return new google.maps.LatLng(
104 parseFloat( lat ),
105 parseFloat( lng )
106 );
107 },
108
109 setPosition: function ( lat, lng ) {
110 // Update marker position.
111 this.map.marker.setPosition( {
112 lat: parseFloat( lat ),
113 lng: parseFloat( lng ),
114 } );
115
116 // Show marker.
117 this.map.marker.setVisible( true );
118
119 // Center map.
120 this.center();
121 },
122
123 center: function () {
124 // Find marker position.
125 var position = this.map.marker.getPosition();
126 if ( position ) {
127 var lat = position.lat();
128 var lng = position.lng();
129
130 // Or find default settings.
131 } else {
132 var lat = this.get( 'lat' );
133 var lng = this.get( 'lng' );
134 }
135
136 // Center map.
137 this.map.setCenter( {
138 lat: parseFloat( lat ),
139 lng: parseFloat( lng ),
140 } );
141 },
142
143 initialize: function () {
144 // Ensure Google API is loaded and then initialize map.
145 withAPI( this.initializeMap.bind( this ) );
146 },
147
148 initializeMap: function () {
149 // Get value ignoring conditional logic status.
150 var val = this.getValue();
151
152 // Construct default args.
153 var args = acf.parseArgs( val, {
154 zoom: this.get( 'zoom' ),
155 lat: this.get( 'lat' ),
156 lng: this.get( 'lng' ),
157 } );
158
159 // Create Map.
160 var mapArgs = {
161 scrollwheel: false,
162 zoom: parseInt( args.zoom ),
163 center: {
164 lat: parseFloat( args.lat ),
165 lng: parseFloat( args.lng ),
166 },
167 mapTypeId: google.maps.MapTypeId.ROADMAP,
168 marker: {
169 draggable: true,
170 raiseOnDrag: true,
171 },
172 autocomplete: {},
173 };
174 mapArgs = acf.applyFilters( 'google_map_args', mapArgs, this );
175 var map = new google.maps.Map( this.$canvas()[ 0 ], mapArgs );
176
177 // Create Marker.
178 var markerArgs = acf.parseArgs( mapArgs.marker, {
179 draggable: true,
180 raiseOnDrag: true,
181 map: map,
182 } );
183 markerArgs = acf.applyFilters(
184 'google_map_marker_args',
185 markerArgs,
186 this
187 );
188 var marker = new google.maps.Marker( markerArgs );
189
190 // Maybe Create Autocomplete.
191 var autocomplete = false;
192 if ( acf.isset( google, 'maps', 'places', 'Autocomplete' ) ) {
193 var autocompleteArgs = mapArgs.autocomplete || {};
194 autocompleteArgs = acf.applyFilters(
195 'google_map_autocomplete_args',
196 autocompleteArgs,
197 this
198 );
199 autocomplete = new google.maps.places.Autocomplete(
200 this.$search()[ 0 ],
201 autocompleteArgs
202 );
203 autocomplete.bindTo( 'bounds', map );
204 }
205
206 // Add map events.
207 this.addMapEvents( this, map, marker, autocomplete );
208
209 // Append references.
210 map.acf = this;
211 map.marker = marker;
212 map.autocomplete = autocomplete;
213 this.map = map;
214
215 // Set position.
216 if ( val ) {
217 this.setPosition( val.lat, val.lng );
218 }
219
220 /**
221 * Fires immediately after the Google Map has been initialized.
222 *
223 * @date 12/02/2014
224 * @since ACF 5.0.0
225 *
226 * @param object map The Google Map instance.
227 * @param object marker The Google Map marker isntance.
228 * @param object field The field instance.
229 */
230 acf.doAction( 'google_map_init', map, marker, this );
231 },
232
233 addMapEvents: function ( field, map, marker, autocomplete ) {
234 // Click map.
235 google.maps.event.addListener( map, 'click', function ( e ) {
236 var lat = e.latLng.lat();
237 var lng = e.latLng.lng();
238 field.searchPosition( lat, lng );
239 } );
240
241 // Drag marker.
242 google.maps.event.addListener( marker, 'dragend', function () {
243 var lat = this.getPosition().lat();
244 var lng = this.getPosition().lng();
245 field.searchPosition( lat, lng );
246 } );
247
248 // Autocomplete search.
249 if ( autocomplete ) {
250 google.maps.event.addListener(
251 autocomplete,
252 'place_changed',
253 function () {
254 var place = this.getPlace();
255 field.searchPlace( place );
256 }
257 );
258 }
259
260 // Detect zoom change.
261 google.maps.event.addListener( map, 'zoom_changed', function () {
262 var val = field.val();
263 if ( val ) {
264 val.zoom = map.getZoom();
265 field.setValue( val, true );
266 }
267 } );
268 },
269
270 searchPosition: function ( lat, lng ) {
271 //console.log('searchPosition', lat, lng );
272
273 // Start Loading.
274 this.setState( 'loading' );
275
276 // Query Geocoder.
277 var latLng = { lat: lat, lng: lng };
278 geocoder.geocode(
279 { location: latLng },
280 function ( results, status ) {
281 //console.log('searchPosition', arguments );
282
283 // End Loading.
284 this.setState( '' );
285
286 // Status failure.
287 if ( status !== 'OK' ) {
288 this.showNotice( {
289 text: acf
290 .__( 'Location not found: %s' )
291 .replace( '%s', status ),
292 type: 'warning',
293 } );
294
295 // Success.
296 } else {
297 var val = this.parseResult( results[ 0 ] );
298
299 // Override lat/lng to match user defined marker location.
300 // Avoids issue where marker "snaps" to nearest result.
301 val.lat = lat;
302 val.lng = lng;
303 this.val( val );
304 }
305 }.bind( this )
306 );
307 },
308
309 searchPlace: function ( place ) {
310 //console.log('searchPlace', place );
311
312 // Bail early if no place.
313 if ( ! place ) {
314 return;
315 }
316
317 // Selecting from the autocomplete dropdown will return a rich PlaceResult object.
318 // Be sure to over-write the "formatted_address" value with the one displayed to the user for best UX.
319 if ( place.geometry ) {
320 place.formatted_address = this.$search().val();
321 var val = this.parseResult( place );
322 this.val( val );
323
324 // Searching a custom address will return an empty PlaceResult object.
325 } else if ( place.name ) {
326 this.searchAddress( place.name );
327 }
328 },
329
330 searchAddress: function ( address ) {
331 //console.log('searchAddress', address );
332
333 // Bail early if no address.
334 if ( ! address ) {
335 return;
336 }
337
338 // Allow "lat,lng" search.
339 var latLng = address.split( ',' );
340 if ( latLng.length == 2 ) {
341 var lat = parseFloat( latLng[ 0 ] );
342 var lng = parseFloat( latLng[ 1 ] );
343 if ( lat && lng ) {
344 return this.searchPosition( lat, lng );
345 }
346 }
347
348 // Start Loading.
349 this.setState( 'loading' );
350
351 // Query Geocoder.
352 geocoder.geocode(
353 { address: address },
354 function ( results, status ) {
355 //console.log('searchPosition', arguments );
356
357 // End Loading.
358 this.setState( '' );
359
360 // Status failure.
361 if ( status !== 'OK' ) {
362 this.showNotice( {
363 text: acf
364 .__( 'Location not found: %s' )
365 .replace( '%s', status ),
366 type: 'warning',
367 } );
368
369 // Success.
370 } else {
371 var val = this.parseResult( results[ 0 ] );
372
373 // Override address data with parameter allowing custom address to be defined in search.
374 val.address = address;
375
376 // Update value.
377 this.val( val );
378 }
379 }.bind( this )
380 );
381 },
382
383 searchLocation: function () {
384 //console.log('searchLocation' );
385
386 // Check HTML5 geolocation.
387 if ( ! navigator.geolocation ) {
388 return alert(
389 acf.__( 'Sorry, this browser does not support geolocation' )
390 );
391 }
392
393 // Start Loading.
394 this.setState( 'loading' );
395
396 // Query Geolocation.
397 navigator.geolocation.getCurrentPosition(
398 // Success.
399 function ( results ) {
400 // End Loading.
401 this.setState( '' );
402
403 // Search position.
404 var lat = results.coords.latitude;
405 var lng = results.coords.longitude;
406 this.searchPosition( lat, lng );
407 }.bind( this ),
408
409 // Failure.
410 function ( error ) {
411 this.setState( '' );
412 }.bind( this )
413 );
414 },
415
416 /**
417 * parseResult
418 *
419 * Returns location data for the given GeocoderResult object.
420 *
421 * @date 15/10/19
422 * @since ACF 5.8.6
423 *
424 * @param object obj A GeocoderResult object.
425 * @return object
426 */
427 parseResult: function ( obj ) {
428 // Construct basic data.
429 var result = {
430 address: obj.formatted_address,
431 lat: obj.geometry.location.lat(),
432 lng: obj.geometry.location.lng(),
433 };
434
435 // Add zoom level.
436 result.zoom = this.map.getZoom();
437
438 // Add place ID.
439 if ( obj.place_id ) {
440 result.place_id = obj.place_id;
441 }
442
443 // Add place name.
444 if ( obj.name ) {
445 result.name = obj.name;
446 }
447
448 // Create search map for address component data.
449 var map = {
450 street_number: [ 'street_number' ],
451 street_name: [ 'street_address', 'route' ],
452 city: [ 'locality', 'postal_town' ],
453 state: [
454 'administrative_area_level_1',
455 'administrative_area_level_2',
456 'administrative_area_level_3',
457 'administrative_area_level_4',
458 'administrative_area_level_5',
459 ],
460 post_code: [ 'postal_code' ],
461 country: [ 'country' ],
462 };
463
464 // Loop over map.
465 for ( var k in map ) {
466 var keywords = map[ k ];
467
468 // Loop over address components.
469 for ( var i = 0; i < obj.address_components.length; i++ ) {
470 var component = obj.address_components[ i ];
471 var component_type = component.types[ 0 ];
472
473 // Look for matching component type.
474 if ( keywords.indexOf( component_type ) !== -1 ) {
475 // Append to result.
476 result[ k ] = component.long_name;
477
478 // Append short version.
479 if ( component.long_name !== component.short_name ) {
480 result[ k + '_short' ] = component.short_name;
481 }
482 }
483 }
484 }
485
486 /**
487 * Filters the parsed result.
488 *
489 * @date 18/10/19
490 * @since ACF 5.8.6
491 *
492 * @param object result The parsed result value.
493 * @param object obj The GeocoderResult object.
494 */
495 return acf.applyFilters(
496 'google_map_result',
497 result,
498 obj,
499 this.map,
500 this
501 );
502 },
503
504 onClickClear: function () {
505 this.val( false );
506 },
507
508 onClickLocate: function () {
509 this.searchLocation();
510 },
511
512 onClickSearch: function () {
513 this.searchAddress( this.$search().val() );
514 },
515
516 onFocusSearch: function ( event, searchInput ) {
517 const currentValue = this.val();
518 const currentAddress = currentValue ? currentValue.address : '';
519
520 if ( searchInput.val() !== currentAddress ) {
521 this.setState( 'searching' );
522 }
523 },
524
525 onBlurSearch: function ( e, $el ) {
526 // Get saved address value.
527 var val = this.val();
528 var address = val ? val.address : '';
529
530 // Remove 'is-searching' if value has not changed.
531 if ( $el.val() === address ) {
532 this.setState( 'default' );
533 }
534 },
535
536 onKeyupSearch: function ( e, $el ) {
537 const val = this.val();
538 const address = val ? val.address : '';
539
540 if ( $el.val() ) {
541 // If search input has value
542 if ( $el.val() !== address ) {
543 this.setState( 'searching' );
544 } else {
545 this.setState( 'default' );
546 }
547 } else {
548 // If search input is empty
549 this.val( false );
550 this.setState( 'default' );
551 }
552 },
553
554 // Prevent form from submitting.
555 onKeydownSearch: function ( e, $el ) {
556 if ( e.which == 13 ) {
557 e.preventDefault();
558 $el.blur();
559 }
560 },
561
562 // Center map once made visible.
563 onShow: function () {
564 if ( this.map ) {
565 this.setTimeout( this.center );
566 }
567 },
568 } );
569
570 acf.registerFieldType( Field );
571
572 // Vars.
573 var loading = false;
574 var geocoder = false;
575
576 /**
577 * withAPI
578 *
579 * Loads the Google Maps API library and triggers callback.
580 *
581 * @date 28/3/19
582 * @since ACF 5.7.14
583 *
584 * @param function callback The callback to execute.
585 * @return void
586 */
587
588 function withAPI( callback ) {
589 // Check if geocoder exists.
590 if ( geocoder ) {
591 return callback();
592 }
593
594 // Check if geocoder API exists.
595 if ( acf.isset( window, 'google', 'maps', 'Geocoder' ) ) {
596 geocoder = new google.maps.Geocoder();
597 return callback();
598 }
599
600 // Geocoder will need to be loaded. Hook callback to action.
601 acf.addAction( 'google_map_api_loaded', callback );
602
603 // Bail early if already loading API.
604 if ( loading ) {
605 return;
606 }
607
608 // load api
609 var url = acf.get( 'google_map_api' );
610 if ( url ) {
611 // Set loading status.
612 loading = true;
613
614 // Load API
615 $.ajax( {
616 url: url,
617 dataType: 'script',
618 cache: true,
619 success: function () {
620 geocoder = new google.maps.Geocoder();
621 acf.doAction( 'google_map_api_loaded' );
622 },
623 } );
624 }
625 }
626 } )( jQuery );
627