Is it possible to control (enable/disable) Google Places Autocomplete SearchBox (google.maps.places.SearchBox) service predictions?
Or in other words: is it possible to temporarily detach HTML input element from Autocomplete SearchBox service and then reattach it?
The thing is that I display service results just bellow HTML input element attached to SearchBox service. Problem is that after results are displayed and user focuses back on the input element, predictions are displayed over results and obscure their view. I would like to disable predictions until text in input element is changed by the user.
EDIT 26/Aug/2016:
Disable predictions is currently not supported by the Javascript API. Therefore I have just opened a feature request on Google. If you are interested in the feature please vote for it: Autocomplete SearchBox - Control (enable/disable) predictions..
EDIT 07/Sep/2016 - bounty award update:
Thanks to all of you who participated in answering and also in promoting the question.
Primary objective of the award was to find solution using currently available means. I am afraid this did not happen so I decided not to award the bounty.
Although none of answers bellow provides a solution, each provides some sort of lead, so thank you! Maybe those leads will point to a solution in future.
Secondary objective of the award (although not communicated directly) was to promote Autocomplete SearchBox - Control (enable/disable) predictions feature request. Its status changed to NeatIdea and has been assigned internal tracking number. It is a good sign.
What you can do is, After the user selects the place, you can add a class disabled to that input field.., This will help you enable/disable predictions based on class name.
And where you have the autocomplete code, you can wrap it within if else statement.
let field = document.getElementById('location');
if ( field.className.indexOf('disabled') > -1 ) {
google.maps.event.clearInstanceListeners(field);
}
else {
let autocomplete = new google.maps.places.Autocomplete( field, {types: ['geocode']} );
autocomplete.addListener('place_changed', () => {
let place = autocomplete.getPlace();
let filed_val = field.value; // incase you need it
field.classList.add('disabled');
});
}
This will remove the autocomplete after user selects a place.. and later if you want, you can remove the disabled class from this field and it will work again.
My solution in AngularJS – it is extract from a directive.
.pac-contained is created after instance of an Autocomplete service is created, e.g.: new google.maps.places.Autocomplete(…) or new google.maps.places.SearchBox(…).
What I do is to find just created .pac-container in the document, store its reference and mark that container as already processes (by adding an arbitrary class .predictions-control on it). "Marking" the container is needed only when more than one .pac-container is expected to be present in application.
Now with the reference I can control visibility (hide or show) of the .pac-contained with predictions.
// Container element with predictions.
var pacContainer = null;
/***
* Find predictions container without predictions-control class set.
* Then set predictions-control class to it and convert it into
* Angular's jqLite object.
* #return {jqLite object} - container or null when not found.
*/
function getPredictionsContainer() {
// Get div.pac-container without predictions-control class.
var e = document.querySelector('div.pac-container:not(.predictions-control)');
if (e){
var container = angular.element(e);
container.addClass('predictions-control');
console.log('predictions-control: Container found.');
return container;
} else {
console.warn('predictions-control: Container not found!');
}
return null;
} // getPredictionsContainer
/***
* Loop in 50ms intervals until container is found.
*/
function untilContainerFound(){
pacContainer = getPredictionsContainer();
if (pacContainer == null){
$timeout(untilContainerFound, 50);
}
} // untilContainerFound
this.init = function() {
untilContainerFound();
}; // this.init
/***
* Prevent predictions to be displayed when user clicks on the
* input element. It is achieved by adding ng-hide CSS class to
* predictions container. Predictions container is identified by
* ".pac-container" CSS class selector.
*/
this.hidePredictions = function() {
// If predictions container was not found at directive
// initialization try to find it now.
if (pacContainer === null){
pacContainer = getPredictionsContainer();
}
if (pacContainer){
console.log('predictions-control: Hiding predictions.');
pacContainer.addClass('ng-hide');
} else {
console.warn('predictions-control: Container not found!');
}
}; // this.hidePredictions
/***
* Show predictions again by removing ng-hide CSS class from
* predictions container.
*/
this.showPredictions = function() {
console.log('predictions-control: Showing predictions.');
if (pacContainer){
pacContainer.removeClass('ng-hide');
}
}; // this.showPredictions
Call init() right after service instance is created:
// Create SearchBox service for auto completing search terms.
autocomplete = new google.maps.places.SearchBox( inputElem[0] );
// OR
// autocomplete = new google.maps.places.Autocomplete( ..... );
autocomplete .addListener('places_changed', callback);
predictionsCtrl.init();
Note:
As long as it is guaranteed that two Autocomplete services are not created at the same time (e.g.: each service is on different tab) or can wait with creation of next service until .pac-container for previous service is found, it reliably works even with multiple instances of Autocomplete service.
There is no way, or much point in having one: predictions are the whole point of SearchBox, its reason to be. If you don't want predictions, you can just use Text Search in the Places library.
If the user clicks/focuses again on the search box, s/he probably didn't care for the results that get obscured by the suggestions. The same behavior is in Google Maps, and it's not an issue, is it?
If you can't put some space between the SearchBox and results (like in this tool), and you absolutely must disable suggestions for a moment, I'd say you can destroy the google.maps.places.SearchBox object and create a new one later, attached to the same HTML input element.
Possibly valuable information.
This is relevant around API V3.29 (not sure if it will always be accurate).
The div element that the API creates for the autocomplete has a class of "pac-container pac-logo".
Utilizing document.querySelector('.pac-container') you may be able to set it's style attribute to display: none on a click event elsewhere.
NOTE: When your users click back in the searchBox google will change the style attribute back to whatever is appropriate, so you only have to set it once, you shouldn't have to set it back again.
(this may be easier and cleaner than getting angular involved).
Hope that helps someone (I had to add a CSS rule to increase the z-index in an application to make the autocomplete show up)
If you add the disabled attributed to the textbox this will disable predictions.
Using readonly attribute does not.
This may help in some circumstances.
Related
I have an Angular 2 app that uses PrimeNG components.
The UI has autocomplete component with multi-select (p-autoComplete) similar to the one from the documentation:
<p-autoComplete [(ngModel)]="countries"
[suggestions]="filteredCountriesMultiple"
(completeMethod)="filterCountryMultiple($event)"
[minLength]="1"
placeholder="Countries"
field="name"
[multiple]="true">
</p-autoComplete>
The only difference is that in my case the input field has fixed dimensions and scroll bar.
Problem:
Every time when I remove an element from the middle of the autocomplete list it moves focus to the bottom of the input field. It looks like this:
It is very annoying for users, especially when there are several fields that should be removed.
Question: How to force scroll to stay on the same position after removing an element?
How to reproduce:
To be more specific, you can reproduce the issue by adding the next css
max-width: 150px;
max-height: 100px;
overflow-y: auto;
directly on the documentation page into ui-autocomplete-multiple-container.ui-inputtext css class using browser console.
UPDATE:
I managed to get the scroller position by setting up onUnselect method in the component using code like this:
onUnselect(event: any) {
document.querySelector("div.my-input-class").scrollTop
}
UPDATE 2: I managed to make it work
The solution is combination of onUnselect and onFocus event handlers.
First. I save the last scroller position into a field in onUnselect call.
Second. I set this value to the specified element during onFocus call.
The corresponding code looks like this:
scrollPosition: number = 0;
onUnselect(event: any) {
let input = document.querySelector("div.my-input-class");
this.scrollPosition = input.scrollTop;
}
onFocus(event: any) {
let input = document.querySelector("div.my-input-class");
input.scrollTop = this.scrollPosition;
}
It works well and probably this will be the final solution.
However I don't like it. It would be much better if PrimeNG guys embed such a useful functionality in their component. For me it is strange why doesn't it come out of the box.
If you find better solution please propose.
You should be using onFocus event to handle the same as below,
<p-autoComplete [(ngModel)]="countries"
[suggestions]="filteredCountriesMultiple"
(completeMethod)="filterCountryMultiple($event)"
styleClass="width12" (onFocus)="onFocus($event)">
....
onFocus(event){
window.scrollTo(0, 0);
}
LIVE DEMO
onUnselect(event: any) {
// get index of the removed element
// get total options count
// get height of the element that contains the options
// divide the height by number of options
// set scroll potion to the result of division multiplied by the index
document.querySelector('.ui-autocomplete-multiple-container.ui-inputtext').scrollTop = calculatedValue;
}
I'm having issues setting the visibility of custom Google Maps API v3 overlays. I have a gmaps API page with various overlay, such as polygons, polylines and symbols. Each has an associated text label, made using a custom overlay I adapted from the answer to [this Stack Overflow post][1]
On page load, the actual overlays (polylines, polygons, markers etc - the built in API objects) work correctly. They are displayed based on the default checkbox states. However, the labels are all displayed, regardless if their check box was set by default. If I cycle the checkboxes, everything works correctly.
The overlays are stored as an object called 'overlays' with layout 'description: [polyline, customoverlaylabel]'
Checkbox example code:
<input type="checkbox" id="sun" onclick="refreshCheck('sun')">Sun</input>
This is how I sync whether a display is hidden or visible to the checkbox:
function refreshCheck(overlay) {
var box = document.getElementById(overlay).checked
var lines = overlays[overlay][0]
var text = overlays[overlay][1]
lines.setVisible(box, overlay)
if (box === true) {
text.show()
}
else {
text.hide()
}
}
This code refreshes all the checkmarks, at the end of the javascript head.
var overlayNames = []
for (var k in overlays) overlayNames.push(k)
for (var o in overlayNames) refreshCheck(overlayNames[o])
Here's the hide method of the custom text overlay:
TxtOverlay.prototype.hide = function(){
if (this.div_) {
this.div_.style.visibility = "hidden";
}
}
It's failing the if (this.div_) check, and not doing anything. If I remove the check, it produces an error since this.div_ doesn't exist.
One way to work around it would be to automatically cycle all checkbox states once the page loads (manually doing to solves it). There might be a more fundamental fix. No matter where I attempt to .hide() or .show() a label in my javascript, it doesn't work - it only works when referenced by the checkbox being clicked.
The issue is the moment when you call refreshChecks() .
You assume that the TxtOverlay's already have been added at this time, but that's not the case(that's why the div_'s are still null).
The TXToverlay's (like any object/shape) on a map will be added when the projection of the map is ready.
A possible approach would be:
Instead of using the visible-property of the shapes/markers/etc. to toggle their visibility use the map-property.
The TXTOverlay's are also MVCObject's, you only have to bind the map-property of the TXTOverlay's to the map-property of the related shape.
I'm working on a map project where I built my own UI. Part of it is a navigation where you can filter the map by clicking a div button in a column floating at the edge of the map. It works perfectly fine, currently, but it is overly complicated and will be a major issue to explain how to add new buttons as the data expands.
The gist is that it first defines a variable as the button id, and on click resets all the classes to a default set, except for the button which is clicked gains the class 'active', and executes a function which sets filtering based on an assigned id in the dataset or return true for All.
The way I have it, which is functional but extremely cluttered
Edit: I've made a few changes that substantially reduce the lines, but I still don't know how to combine multiple similar copies of these into a single function
//define variable to point to a specific button
var navigation = document.getElementById('navigation').children;
var all = document.getElementById('filter-all');
....
//[Currently] nine additional variables of nearly the same thing (now just for selecting the button pressed)
//Show all
all.onclick = function() {
navigation.className = navigation.className.replace(new RegExp('\b' + active + '\b'),'');
this.className += ' active'; //for some reason this doesn't actually add the space
map.featureLayer.setFilter(function(f) {
// Returning true for all markers shows everything.
return true;
});
map.fitBounds(map.featureLayer.getBounds());
return false;
};
....
//Followed by nine nearly identical functions that just reset classes, and sets filtering parameters
I know there is a way to combine the functions to do the same thing without so much bulk, but my search is so far fruitless.
I am very new to building forms with js. I copied and applied a bit of code to get fields to pop up depending on the Radio button I select. This works great for two radio buttons, but my issues is that I want to include several (5+) and would like to reset the fields with every change.
I was thinking I can input additional code to reset all fields "onchange" but I can't get this piece of code to work... here is what I copied and modified for my use:
works great with 2 buttons as designed:
{
toggleSelectionFields: function() {
var isLaptop = ca_fdIsSelectRadio('showhide','group','short');
if (isLaptop) {
ca_fdHideField('showhide','longfields');
ca_fdShowField('showhide','shortfields');
} else {
ca_fdHideField('showhide','shortfields');
ca_fdShowField('showhide','longfields');
}
}
}
Here is what I tried to do:
{
toggleSelectionFields: function() {
Var discovery = ca_fdIsSelectRadio('phone','deskphone4610','selectproblem','SelectIssue','discovery');
Var headset = ca_fdIsSelectRadio('phone','deskphone4610','selectproblem','SelectIssue','headset');
Var fac = ca_fdIsSelectRadio('phone','deskphone4610','selectproblem','SelectIssue','feature');
Var calls = ca_fdIsSelectRadio('phone','deskphone4610','selectproblem','SelectIssue','calls');
if (discovery)
{ca_fdShowField('phone','deskphone4610','selectproblem','discovermode')}
if (headset)
{ca_fdShowField('phone','deskphone4610','selectproblem','headset')}
if (fac)
{ca_fdShowField('phone','deskphone4610','selectproblem','feature')}
if (calls)
{ca_fdShowField('phone','deskphone4610','selectproblem','calls')}
}
}
}
This appears to be a JavaScript question (not Java) and related to a particular framework (CA Service Catalog), so questions about how to do things with particular CA functions would probably be best answered on the CA Service Management Global User Community message boards.
As a general logic/JavaScript question, though, you need to hide the fields you don't want to see in addition to showing the ones you do want to see. Notice that your first example calls ca_fdHideField to hide one set of fields, then ca_fdShowField to show the other. If you don't want to duplicate a lot of code, you could hide them all before the if statements, then just show the one that corresponds with the radio button that was selected:
ca_fdHideField(...)
ca_fdHideField(...)
...
if (discovery) {
...
}
etc.
I have the following jQuery code which works well with getting the city list for the selected country.
var city; var place;
$('#city').on('focus',function(){
input = this, options = {
types: ['(cities)'],
componentRestrictions: {
country: $('#country option:selected').val()
}
};
city = new google.maps.places.Autocomplete(input, options);
google.maps.event.addListener(city, 'place_changed', function() {
place = city.getPlace();
$(input).val(place.address_components[0].long_name);
})
})
Basically, once the person selects the place, it replaces the value in the input box with the "city" value without the country.
It looks a little stupid having City, Country in the dropdown menu when the user has already selected a country, so does anybody know if it is possible to display JUST the city name if you have defined a componentRestrictions value restricting the results to a country?
I find my current method of setting it once the selection has been made to be a bit... rubbish really...
When invoking Google API, specify attribute "region":
<script type="text/javascript" src="http://maps.googleapis.com/maps/api/js?libraries=places&sensor=false&key=your_key&language=fr®ion=FR"></script>
Then, when you create an Autocomplete object, specify the country in the componentRestriction attribute, so that it reflects the region you specified:
new google.maps.places.Autocomplete(
$("#my-input-field").get(0),
{
componentRestrictions: {country: "fr"},
types: ["(regions)"]
}
);
Also you can help yourself by css - hiding last span (=state) in autocomplete item.
.pac-item > span:last-child {
display: none;
}
The short answer is: the response content in the Autocomplete is controlled by Google (you mention this in one of your comments) and the Autocompleteapi-doc doesn't provide a way to customize the response text that is displayed, other than changing the placeholder textdev-guide. The solution you are using is about the best option you have (and is a little reactive, but I don't know if its really "rubbish").
Other than that, if you are less than satisfied with that approach, you could always just create your own input area and take complete control of the request-response back and forth yourself. You can use the Places Librarydev-guide directly and take complete control of everything that is shown.
I wait 100 milleseconds, and then replace the text box with the content I want.
Except in my case I want a blank box, but insert a single space.