5
votes

Is there any way to associate the pac-container div for an autocomplete with the input element that it's attached to? Ideally, I'd like to be able to set the ID of each pac-container to something like "pac-", so that I can delete them if the input goes away, and to make it easier to test the autocompletes with Selenium.

This question's answer has one solution, but it's not remotely sustainable, as Google has a tendency to change various minified property names (For example, what was once Mc is now Rc)

I've also tried modifying the last pac-container div on the page whenever a new autocomplete is added, like so:

function attachAutocomplete(id) {
    var input = document.getElementById(id);
    var autocomplete = new google.maps.places.Autocomplete(input);
    $(".pac-container:last").attr("id", "pac-" + id);
}

This works fine with new autocompletes beyond the ones on the page when it's loaded, but for some reason there's a delay between the first couple of autocompletes being assigned and their pac-containers showing up. (Here's a fiddle that should illustrate this approach and how it fails)

Is there some method I'm missing?

2

2 Answers

1
votes

I solved the issue not by associating every .pac-container to its input type form field, but, resetting all .pac-container items, every time an autocomplete input type is focused.

So basically:

1) let's assume we have 3 address input to which google.maps.places.Autocomplete is set up

2) using jquery, let's bind focus event to these 3 inputs

$('#address_1, #address_2, #address_2'.focus(function (e) 
{
    $('.pac-container').each( function() {
        $(this).html( '' );
    });
});

3) bind focusout event to these 3 inputs, and select the corresponding selected address

0
votes

If you are unwilling to make assumptions about property names/structure (totally reasonable), then you are basically left with querying the Autocomplete object itself for the object that has the property className:"pac-container" (or some other identifying feature).

There are libraries that can help with this, such as JSONPath, JSONQuery, and many more that could help you do this. Without adding any additional libraries, though, we can roll our own breadth-first-search through the Autocomplete object's nested hierarchy with the caveat that I am not a Javascript expert, and that while this works right now, I can't promise that I didn't miss an edge case that will break this in the future.

Unfortunately, due to the timing issue you pointed out, you will need to run this at some point after the initial instantiation of the Autocomplete object. The good news is that if you don't immediately need the pac-container object reference, you can defer finding it until you need it, so long as you keep the Autocomplete object reference in scope.

The function ends up looking like this:

var autocompleteDropdown = breadthFirstSearch(autocomplete, function(val) {
    return val.className === "pac-container";
}

Where the breadthFirstSearch function is defined as:

function breadthFirstSearch(object, matchesCriteria) {
    var queue = [];
    var visited = [];
    queue.push(object);
    visited.push(object);
    while (queue.length) {
        var val = queue.shift();
        if (val && typeof val == "object") {
            if (matchesCriteria(val)) {
                return val;
            }
            if (Object.prototype.toString.call(val) == "[object Array]") {
                for (var i=0; i<val.length; i++) {
                    breadthFirstSearchProcessValue(val[i], queue, visited);
                }
            }
            else if (val instanceof Node) {
                if (val.hasChildNodes()) {
                    var children = val.childNodes;
                    for (var i=0; i<children.length; i++) {
                        breadthFirstSearchProcessValue(children[i], queue, visited);
                    }
                }
            }
            else {
                for (var property in val) {
                    breadthFirstSearchProcessValue(val[property], queue, visited);
                }
            }
        }
    }
}
function breadthFirstSearchProcessValue(val, queue, visited) {
    for (var i=0; i<visited.length; i++) {
        if (visited[i] === val) {
            return;
        }
    }
    queue.push(val);
    visited.push(val);
}

In sum, this is obviously a pretty intensive way of just accessing a property (which Google should make visible natively, grumble grumble). I would recommend trying one of the more "naive" but faster methods from the StackOverflow question you linked, and fall back to this when that method returns null/empty.