Associate Google Places Autocomplete pac-container with input field - javascript

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?

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

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.

Related

JQuery onChange uses values before change (values after change needed)

I am working on a module, which should select the only possible value of a Multi- or Single selection field, if there is only one valid value and a empty one available.
So far its working fine, until I use ACLs to disable selection values.
For example, I got a single selection field with 3 possible values. Then I disable 2 of them (ACL - if there is a special Queue selected) so theres only one value (+ an empty one) left.
My module wont pick the last value at first, but when I change anything else on the same page (second onchange call) it will pick the last possible value.
The first if condition checks if the Field has only one possible value in it. When I log the 'Change' array it always got the disbaled values still in there even when the 'change' that called the whole function was the ACL disabling those values.
Im still kinda new to javascript and havent found a solution yet.
I would realy appreciate any help.
$('.TableLike').on("change", function () {
var Change = [];
$('.Row').each( function() {
$(this).children().next().children().next().children().each( function(index) {
Change[index] = $(this).text()
} )
if ( (!Change[2] || /.*Field needed.*/i.test(Change[2])) && Change[0] === "-") {
SoleOption = Change[1];
$(this).children().next().children().next().children().each(function() {
if ($(this).text() === "-") {
$(this).removeAttr("selected");
}
if ($(this).text() === SoleOption) {
$(this).attr("selected", "selected");
}
} )
$(this).children().next().children().children().children().val(SoleOption)
}
SoleOption = "";
Change = [];
} )
} )
I managed to fix the issue with the setTimeout() Method.
So the DOM updated before the ACL did the changes.
I opened up the setTimeout Method after the onChange method and inserted all the code thats supposed to run after the change into the setTimeout method.
I hope this will be helpfull for others in the future.

array of objects for an html5 canvas nav bar

I have a html5 Canvas animation that I am doing on Adobe Animate and tweaking with some code.
I have a portion on the animation that will be like a combobox with all the links to navigate through the different frames. The thing is, I don't want to be creating a bunch of EventListener to many buttons because from experience I know that doesn't work so well. So I am trying to think of a more creative solution. This is my idea.
Create an array that will contain all the buttons.
Assing a variable for each target frame.
Create a for loop with a function inside that assigns the listener to the selected button and then points it to the desired frame (variable)
This is what I have got so far, (not much)
var combobox = [this.btncasco , this.btnbanyera , this.btnLumbrera , this.btnproapopa, this.btnestriborbabor ];
for (var i=0; i<combobox.length; i++) {
var clipcasco = gotoAndStop(0);
var clipbanyera = gotoAndStop(2);
var cliplumbera = gotoAndStop(4);
var clipproapoa = gotoAndStop(6);
var clipestriborbabor = gotoAndStop(8);
}
Would that be feasible ?
In your example, you are just assigning the result of gotoAndStop (with no scope, so likely you're getting an error in the console)
I think you are looking for something like this:
for (var i=0; i<combobox.length; i++) {
// This is kind of complex, but if you just reference "i" in your callback
// It will always be combobox.length, since it references the value of i
// after the for loop completes variable.
// So just store a new reference on your button to make it easy
combobox[i].index = i*2; // x2 lines up with your code 0,2,4,etc.
// Add a listener to the button
combobox[i].on("click", function(event) {
// Use event.target instead of combobox[i] for the same reason as above.
event.target.gotoAndStop(event.target.index);
}
}
You might have the same problem as your other StackOverflow post where the button is undefined (check the console). There is actually a bug in Animate export right now where children of a clip are not immediately available. To get around this, you can call this.gotoAndStop(0); at the start to force it to update the children.

keeping localStorage objects that iterate whilst using clear() within the same function

I'm trying to clear all local storage when the user either completes the game loop or starts a new game, but also keep some values.
I can do this already with my sound values for volume:
// inside a conditional statement that fires when the user chooses to start a new game.
if (newGameBool === '1') {
var tst = myAu;
//myAu is the stored value that the user sets as sound using a range type input
localStorage.clear();
localStorage.setItem("Au", tst);//A newly cleared localStorage just got a new value, and it's the same as it was before.
UI.myLoad();//reload the function that uses LS to do things.
}
How do I do this for key's that have an iterating number attached to them?
Here is how I save them:
var i = +v + +1;
localStorage.setItem("v", i);
var vv = localStorage.getItem("v");
localStorage.setItem("LdrBrd_" + vv, JSON.stringify(LdrBrd));//saves all data with the iterating key name.
Calling them the way i did the sound function:
var gv = v + 1;//v calls the value from LS and adjusted for off-by-one error. gv is a local variable.
if (newGameBool === '1') {
var ldd, vg;
for (var ii = 0; ii < gv; ii++) {
var ld = localStorage.getItem("LdrBrd_" + ii);
if (ld != null) {
//these are the values that i want to pass beyond the clear point
ldd = JSON.parse(ld);//JSON string of data saved
vg = ii;//how many of them.
}
}
localStorage.clear();
for (var xx = 0; xx < vg; xx++) {
var nld = localStorage.getItem("LdrBrd_" + xx);
if (nld != null) {
localStorage.setItem("LdrBrd_" + ii, JSON.stringify(ldd));
}
}
localStorage.setItem("v", vg);
UI.myLoad();
}
I have been using console.log() in various spots to see what is going on. I comment-out the clear function just to see if the values were wrong and they don't save all all. I tried to make a fiddle, but the local storage wasn't working at all there. In visual studio, it works fine but the script to this file is almost 2000 lines long, so i tried to dress it up the best i knew how.
Thanks in advance for any help or guidance.
I was stuck on this for a few days, but i think i found something that will work, so i'll answer my own question in case there is value in posterity.
locatStorage.clear();
/* ^LS clear() function is above all new setItem codes, some variables are declared globally and some are declared at the top of the functional scope or as param^ */
var itemClass = document.querySelectorAll(".itemClass");//the strings are here
if (itemClass) {//make sure some exist
for (var p = 0; p < itemClass.length; p++) {//count them
mdd = JSON.parse(itemClass[p].innerText);//parse the data for saving
localStorage.setItem("v", v);//this is the LS item that saves the amount of items i have, it's declared at the top of the functions timeline.
localStorage.setItem("LdrBrd_" + p, JSON.stringify(mdd));//this setItem function will repeat and increment with 'p' and assign the right string back to the key name it had before.
}
}
The key is to keep the strings physically attached to an element, then call the class name. The i ran a loop counting them. 'mdd' will spit back each item i want. So then all that is left to do is re-set the item back to it's original status.
This has allowed me to create a way for my users to collect trophies and keep them even after clearing the localStorage when the he/she decides to start a new game.
I use CSS to hide the text from the string.
color:transparent;
In my gameLoop, i have a function that will read the saved strings and show them as cards just below the hidden strings.
Since you want to keep some values I recommend one of two things:
Don't call localStorage.clear() and instead only wipe out the values that you want using localStorage.removeItem('itemName'). Since you said the item names have a numeric component, maybe you can do this in a loop to reduce code.
Pull item(s) that you want saved first and restore them after calling clear(). This option is best if there are way more items that you want removed rather than saved (see below)
function mostlyClear() {
var saveMe = {};
saveMe['value1'] = localStorage.getItem('value1');
saveMe['anotherValue'] = localStorage.getItem('anotherValue');
localStorage.clear();
for(var prop in saveMe) {
if(!saveMe.hasOwnProperty(prop)) continue;
localStorage.setItem(prop, saveMe[prop]);
}
}

Run 4 individual form submit functions from a single button click using Javascript

I am currently attempting to make a page that would allow for users to search multiple knowledge bases from a single field.
Currently, I have been able to build this tool out so that clicking the corresponding button will search the designated tool, but I am trying to get a single button to search all 4.
Where I am stuck is the function tied to the All button. When I click it, it only appears to be running the last function in the group rather than opening 4 browser tabs with all 4 results.
I have attached a JSFiddle, in case my explanation is poor.
Note: The page is not pretty as I am trying to get it working before I add any CSS. I really just need JS advice. I am still somewhat of a novice with JS, so if anyone can provide a fairly simple solution, that would be most ideal.
Super Search Fiddle:
This is just to give an idea on how it might work depending on you needs. Ill assume that all the searches return a boolean value. So the code would go something like this:
function doAll() {
var msg = ["google","payroll","inquira","sdfc"]
var retvalue = [googleSearch(),payrollSearch(),inquiraSearch(),sfdcSearch()];
for (var i = 0; i < retvalue.length; i++){
if(retvalue[i] == false){
console.log(msg[i]+" search returned false");
}
}
}
It will do all the searches first and after it finishes, it will give out which searches failed, but you can change that functionality according to your needs.
Hope it helps
Update/Alternative(Almost same code):
function doAll() {
var msg = ["google","payroll","inquira","sdfc"]
var retvalue1 = googleSearch();
var retvalue2 = payrollSearch();
var retvalue3 = inquiraSearch();
var retvalue4 = sfdcSearch();
var retvalue = [retvalue1,retvalue2,retvalue3,retvalue4];
//var retvalue = [googleSearch(),payrollSearch(),inquiraSearch(),sfdcSearch()];
for (var i = 0; i < retvalue.length; i++){
if(retvalue[i] == false){
console.log(msg[i]+" search returned false");
}
}
}

How do I get element's className inside loop of elements?

I am trying to create a function that given a divid, and a list of classes, will then do some text replacing inside them.
Having learned of how Firefox Dom is handling text nodes differently, I read that I had to use javascript to loop through the elements, sibling to nextSibling.
The last obstacle I had in my script, of which you see a small portion of, is getting the classname. I need the class name so that I can filter down what content get's text replaced.
Having looked all the answers, and with the help of a co-worker named Ryan at work, we have redone this in jquery.
$(divid).find(".status_bar").each( function() {
var value = $.trim($(this).text());
// if value is not defined thru browser bugs do not replace
if (typeof(value) != 'undefined') {
// it is a text node. do magic.
for (var x = en_count; x > 0; x--) {
// get current english phrase
var from = en_lang[x];
// get current other language phrase
var to = other_lang[x];
if (value == from) {
console.log('Current Value ['+value+'] English ['+from+'] Translation ['+to+']');
value = to;
$(this).attr('value', to);
}
}
}
});
This currently works in all areas, except in the replacing of text.
The reason I had originally with doing this in jQuery, had to be not sure I could loop thru elements, and avoid the problem with firefox and text nodes.
I am doing a loop of all elements inside a div, and I now need to get the classname of the element that I am looping by.
Then i can check if the current element's class is one, I need to do something with...
// var children = parent.childNodes, child;
var parentNode = divid;
// start loop thru child nodes
for(var node=parentNode.firstChild;node!=null;node=node.nextSibling){
var myclass = (node.className ? node.className.baseVal : node.getAttribute('class'));
}
But this code for getting the classname only get's null values.
Any suggestions?
For those of you who are trying to figure out what the whole point is, read this JavaScript NextSibling Firefox Bug Fix I have code that does my language translation that works in Google Chrome and IE. But when I use it in Firefox, and try to translate div content after ajax has loaded it, it fails because of the whitespace issue.
I really don't have a preference of jQuery or Pure Javascript, I just want a working solution.
Thank you all for being patient. I personally thought I was extremely clear in my description, I apologize if it wasn't. I wasn't trying to be obscure or make it difficult to get help. But please don't insult me, by implying I am trying to make it unclear.
Thanks.
Hm... You have jQuery but don't use it?
$(divid).children(".yourSpecialClassName").each( function() {
doSomethingWith(this);
});
To get the CSS class attribute value, this will do:
$(divid).children().each( function() {
alert(this.className);
});
Based on the function you posted now, you want this:
$(divid).find(".status_bar").each( function() {
$(this).text( function(i, text) {
var x = $.inArray(en_lang, $.trim(text));
if (x > -1) {
console.log('Current Value ['+text+'] English ['+en_lang[x]+'] Translation ['+other_lang[x]+']');
return other_lang[x];
}
return text;
});
});
And please, don't ever use "do magic" as a comment again. This is incredibly lame.
EDIT. This can be made much more efficient (superfluous console.log() removed):
$(divid).find(".status_bar").each( function() {
// prepare dictionary en_lang => other_lang
var dict = {};
$.each(en_lang, function(x, word) { dict[word] = other_lang[x]; });
$(this).text( function(i, text) {
var t = $.trim(text);
return (t in dict) ? dict[t] : text;
});
});
if you are using jquery you can do this:
$("#myDiv").find("*").each(
function(){
var myclass = $(this).attr("class");
}
);
Your sample code doesn't make sense.
$(this).attr('value', to);
'value' is an attribute of the tag, not the text content.
Did you really mean to do this instead?
$(this).text(to);
Also, you've re-edited your question, but you're still trying to loop through the child nodes using non-jQuery methods. You said "The last obstacle I had in my script, of which you see a small portion of, is getting the classname. I need the class name so that I can filter down what content get's text replaced."
If you are using jQuery it is completely unnecessary to loop through anything to get a class name. You simply have to use a proper selector in the first place.
$(divid).find(".status_bar.replaceme").each( function() {
// .replaceme is whatever class you're using for the stuff you want to change
// .status_bar.replaceme matches all elements with BOTH status_bar and replaceme classes
var value = $.trim($(this).text());
// if value is not defined thru browser bugs do not replace
if (typeof(value) != 'undefined') {
// it is a text node. do magic.
// NOTE: The following is inefficient but I won't fix it.
// You're better off using an associative array
for (var x = en_count; x > 0; x--) {
// get current english phrase
var from = en_lang[x];
// get current other language phrase
var to = other_lang[x];
if (value == from) {
console.log('Current Value ['+value+'] English ['+from+'] Translation ['+to+']');
// value = to; <-- useless, get rid of it.
$(this).text(to);
// or $(this).html(to);
}
}
}
});

Categories