Google Places autocomplete not working (in Bootstrap modal) - google-maps

I'm trying to include a google places autocomplete input box in my React app.
I've followed the guide here to place an <input> text field, and initializing the search box like so:
export default class MySearch extends class Component {
...
componentDidMount() {
var defaultBounds = new google.maps.LatLngBounds(
new google.maps.LatLng(-33.8902, 151.1759),
new google.maps.LatLng(-33.8474, 151.2631));
var input = document.getElementById('searchTextField');
var searchBox = new google.maps.places.SearchBox(input, {
bounds: defaultBounds
});
}
render() {
return (
...
<input id="searchTextField"
type="text"
className="form-control"
placeholder="Search for a location"
/>
);
}
}
But I don't see any suggestions dropping down from the text field.
I inspected the networks tab, to see whether API requests are being hit as I type, and I, not only see requests, but responses from the API, with matching locations, based on my search term, as I type through.
I have no idea why the received suggestions are not being displayed in a dropdown suggestions list below my input box.
Thanks in advance :)
Update
PS: I've placed the text box inside a bootstrap modal. When I place exactly the same text box, outside the bootstrap modal, it works like a breeze.
Any idea why the text box isn't showing suggestions while inside the modal?

It is a styling issue, as the modal's z-index > dropdown's (.pac-container's) z-index. Fixed it with the following CSS snippet:
.pac-container {
background-color: #FFF;
z-index: 2001;
position: fixed;
display: inline-block;
float: left;
}
.modal{
z-index: 2000;
}
.modal-backdrop{
z-index: 1000;
}​

DOM reference (findDOMNode)
You should not select a dom element with id in react component. Use ref (reference) instead. Learn more about findDOMNode here https://facebook.github.io/react/docs/react-dom.html#finddomnode
import { findDOMNode } from 'react-dom';
export default class MySearch extends class Component {
componentDidMount() {
var defaultBounds = new google.maps.LatLngBounds(
new google.maps.LatLng(-33.8902, 151.1759),
new google.maps.LatLng(-33.8474, 151.2631));
var input = findDOMNode(this.refs['searchTextField']);
var searchBox = new google.maps.places.SearchBox(input, {
bounds: defaultBounds
});
}
render() {
return (
<input ref="searchTextField"
type="text"
className="form-control"
placeholder="Search for a location"
/>
);
}
}

Related

Extending native HTML element in Angular 6

I have recently created a native web component which is working well in all browsers. I moved this web component into an Angular 6 application and all works as expected. I then tried to extend a native HTML element which again worked perfectly except when I brought it into my Angular 6 application.
Using the examples from Mozilla I will try and illustrate my issue. Using the following trying to extend a native 'p' element:
// Create a class for the element
class WordCount extends HTMLParagraphElement {
constructor() {
// Always call super first in constructor
super();
// count words in element's parent element
var wcParent = this.parentNode;
function countWords(node){
var text = node.innerText || node.textContent
return text.split(/\s+/g).length;
}
var count = 'Words: ' + countWords(wcParent);
// Create a shadow root
var shadow = this.attachShadow({mode: 'open'});
// Create text node and add word count to it
var text = document.createElement('span');
text.textContent = count;
// Append it to the shadow root
shadow.appendChild(text);
// Update count when element content changes
setInterval(function() {
var count = 'Words: ' + countWords(wcParent);
text.textContent = count;
}, 200)
}
}
// Define the new element
customElements.define('word-count', WordCount, { extends: 'p' });
<p is="word-count">This is some text</p>
By taking that same code and putting it into an Angular 6 application, the component never runs. I put console log statements in the constructor and connectedCallback methods and they never trigger. If I remove the {extends: 'p'} object and change the extends HTMLParagraphElement and make it an extend HTMLElement to be an autonomous custom element everything works beautifully. Am I doing something wrong or does Angular 6 not support the customized built-in element extension?
I assume the reason is the way that Angular creates those customized built-in elements when parsing component templates - it probably does not know how to properly do that. Odds are it considers is a regular attribute which is fine to add after creation of the element (which it isn't).
First creating the element and then adding the is-attribute will unfortunately not upgrade the element.
See below example: div#d has a non-working example of that customized input.
customElements.define('my-input', class extends HTMLInputElement {
connectedCallback() {
this.value = this.parentNode.id
this.parentNode.classList.add('connected')
}
}, {
extends: 'input'
})
document.addEventListener('DOMContentLoaded', () => {
b.innerHTML = `<input type="text" is="my-input">`
let el = document.createElement('input', {
is: 'my-input'
})
el.type = 'text'
c.appendChild(el)
// will not work:
let el2 = document.createElement('input')
el2.setAttribute('is', 'my-input')
el2.type = 'text'
d.appendChild(el2)
})
div {
border: 3px dotted #999;
padding: 10px;
}
div::before {
content: "#"attr(id)" ";
}
.connected {
background-color: lime;
}
<div id="a"><input type="text" is="my-input"></div>
<div id="b"></div>
<div id="c"></div>
<div id="d"></div>
So to get it to work with Angular, hook into the lifecycle of your Angular component (e.g. onInit() callback) and pick a working way to create your element there.

How to process image input to Angular app in an <img> element?

I am trying to create an Angular PWA that is able to take a picture, and after taking it to display it in an img element.
The first step I succeeded in: I now have an FAB button that opens the camera viewfinder (using the html input tag) with this small piece of code:
const element: HTMLElement = document.getElementById('imageCapturer') as HTMLElement;
element.click();
This simulates a click on the following HTML element:
<input type="file" accept="image/*" capture="environment" id="imageCapturer"">
However, after taking a picture I then want to use the image to render it in an img element (and also sending the image to a database with an API call)
I have tried multiple solutions including trying to add the file.name as src to the img element, and trying to create a new img HTML element with an attribute that uses the file. But I am still unable to process the image to be used in the img tag. Can somebody explain to me what the proper way to process the file is to be able to do this?
Cheers!
I solved this problem in the following way:
First, I changed the HTML to this:
<input type="file" accept="image/*" id="imageCapturer" style="display: none;" (change)="useImage($event)">
<img *ngIf="URL" [src]="URL" id="imageViewer">
Then the function useImage looks like this:
useImage(event) {
if (event.target.files && event.target.files[0]) {
const reader = new FileReader();
reader.readAsDataURL(event.target.files[0]); // Read file as data url
reader.onloadend = (e) => { // function call once readAsDataUrl is completed
this.URL = e.target['result']; // Set image in element
this._changeDetection.markForCheck(); // Is called because ChangeDetection is set to onPush
};
}
}
Now whenever a new image is captured or selected it is updated in the view
You can use event change of input file.
This example:
$('#imageCapturer').on('change', function () {
var input = $(this)[0];
if (input.files && input.files[0]) {
var fileName= input.files[0].name;
var reader = new FileReader();
reader.onload = function (e) {
var buffer = e.target.result;
var fileSize = input.files[0].size / 1024 / 1024; // in MB
// SET IMG DISPLAY
$('#image-display').attr('src', buffer).css({
'max-height': '170px',
'max-width': '170px',
};
reader.readAsDataURL(input.files[0]);
}
});
Note: You add more attribute [enctype="multipart/form-data"] in tag form when sumbit form it will send file byte[].
<form id="process_form" method="POST" enctype="multipart/form-data" autocomplete="off" role="form" data-parsley-focus="none">

PrimeFaces GMap Inside div Element Does Not Show Up

I have a GMap inside a div with display:none;.
Inside the div is a PrimeFaces map component.
After clicking on a button, the content of the div element should appear, but only a blank page is showing.
<div class="form-group" id="mapContainer" style="display:none;">
<p:gmap id="gmap" center="51.30993291552862,9.448113441467285" zoom="15" type="terrain" style="width:100%;height:700px;" widgetVar="gmap" navigationControl="false" />
</div>
But outside the div element, the map is built and showing correctly.
How can I solve this problem?
As mentioned in one of comments, google map object is not initialized if mapContainer div is hidden (display: none) during page load...
so you will need to "manually" initialize google map object after you make mapContainer div visible.
Here is fully working code (based on your posted code) that will do what you need:
Add this JavaScript to your page
<script>
function resizeElement(elementId,width,height){
console.log("Resizing element " + elementId + " W/H="+ width + "/" + height);
var element = document.getElementById(elementId);
element.style.width=width+"px";
element.style.height=height+"px"
}
function resizePfGmapInsideWrapperElement(wrapperElementId){
var wrapperElement=document.getElementById(wrapperElementId);
var width=wrapperElement.clientWidth-40;
var height=wrapperElement.clientHeight-60;
resizeElement("gmap",width,height);
}
function resizePfGmapInsideDiv(){
var gmap = PF('gmap').getMap();
console.log(gmap);
resizePfGmapInsideWrapperElement("mapContainer");
}
function toggleDivVisibility() {
var div = document.getElementById("mapContainer");
if(div.style.display === "block"){
div.style.display = "none";
}else{
div.style.display = "block";
div.style.width="600px";
div.style.height="400px";
initializeGmap();
resizePfGmapInsideDiv();
}
}
function initializeGmap() {
var myOptions = {
zoom: 15,
center: new google.maps.LatLng(51.30993291552862, 9.448113441467285),
mapTypeId: google.maps.MapTypeId.TERRAIN
}
new google.maps.Map(document.getElementById("gmap"),myOptions);
}
</script>
and, just for testing purposes, add a button that will toggle mapContainer div visibility
<p:commandButton value="Show/hide map" onclick="toggleDivVisibility();"/>
The crucial JS method is self-explanatory initializeGmap() executed in the moment when you make div visible: it will create "a new map inside of the given HTML container, which is typically a DIV element." as stated in documentation referenced above.

Google map gray box in a hidden div in react js

I tried the solution here Google maps in hidden div but it didn't work. Think it might be an issue with react. The map loads fine when not placed in the hidden div.
When state.hideScore turns false in the parent container, the map shows up but as a gray box. any help?
Parent container
<div hidden={this.state.hideScore}>
<ScoreExplanation score={this.state.score} />
<br />
<ResultList data={this.state.scoreArray} />
<ResultMap />
</div>
Component
import React, { Component, PropTypes } from 'react';
var $ = require ('jquery');
var map;
var ResultMap = React.createClass({
componentDidMount: function() {
// ** Instantiating the Map ** //
map = new google.maps.Map(document.getElementById('map'), {
center: new google.maps.LatLng(-34.397, 150.644),
zoom: 14
});
google.maps.event.trigger(map, 'resize');
map.setZoom( map.getZoom() );
},
render: function() {
return (
<div style={{overflow:'visible',height:'300px',width:'300px'}} id="map"></div>
);
}
});
export default ResultMap;
Instead of initializing the map in componentDidMount, you should instead initialize it when the parent re-renders after calling setState to change this.state.hideScore. What's happening right now is, your map is getting loaded into the ResultMap component before its parent is visible. You should instead wait until the parent component is visible, then instantiate the ResultMap.
Example:
Parent component render method
// I prefer using CSS classes to hide/display components.
// Initialize hideScore to 'hidden' and see CSS.
render () {
return (
<div className={this.state.hideScore}>
<ScoreExplanation score={this.state.score} />
<br />
<ResultList data={this.state.scoreArray} />
<div id='result-container'></div>
</div>
)
}
Parent component click handler method (Can be whatever method).
handleClick = () => {
this.setState({
hideScore: 'shown' // See CSS.
});
ReactDOM.render(
<ResultMap />,
document.getElementById('result-container')
);
}
CSS
.shown {
display: block; // Or whatever is your preference.
}
.hidden {
display: none;
}

Text input is using original typed text instead of selected google place autocomplete text

I'm using the Google Maps JavaScript API v3 to autocomplete a text input in my angular/node app.
The problem I'm running into is that for some reason, my angular function is using the original typed text instead of the selected google place autocomplete text that gets filled into the text input.
Here is what happens (in pictures!):
1) Type in the beginning of the place I'm looking for
2) click on the autocomplete place, which fills the text input
3) The string used in my get calls to Google Places API is the original "siz" from step one instead of the place info from autocomplete, which returns the wrong place
Here is the code that I think is relevant to this problem:
From main.html:
<form class="form-horizontal" role="form">
<div class="input-group">
<input ng-model="businessName" googleplace class="form-control input-lg">
<span class="input-group-btn">
<button ng-click="findGPlace2()" class="btn btn-default" type="button">Find!</button>
</span>
</div><!-- /input-group -->
From Google.js controller (the console.log($scope.businessName); is printing out siz in this example):
$scope.findGPlace2 = function() {
console.log($scope.businessName);
GoogleService.findPlace2($scope.businessName).then(function(data) {
$scope.gPlace2 = data;
// for(var i = 0; i < data.results.length; i++) {
// console.log(data.results[i]);
// }
showGData(data.results[0].place_id);
});
};
From App.js (custom autocomplete directive):
analyticsApp.directive('googleplace', function() {
return {
require: 'ngModel',
link: function(scope, element, attrs, model) {
var options = {
types: [],
componentRestrictions: {}
};
scope.gAuto = new google.maps.places.Autocomplete(element[0], options);
google.maps.event.addListener(scope.gPlace, 'place_changed', function() {
scope.$apply(function() {
model.$setViewValue(element.val());
});
});
}
};
});
There must be something wrong with your googleplace directive.
Check this fiddile. It might help you
scope.gAuto = new google.maps.places.Autocomplete(element[0], options);
This should be
scope.gPlace = new google.maps.places.Autocomplete(element[0], options);
I think that should do it.