Jquery appending issues - javascript

I have a JSON object which I loop through and works correct
iGenerateChilds = function (obj, div, $new) {
var $new = $();
$.each(obj.objects, function (p, par) {
$(div).append(iGenerateObject(par, ""));
alert(par.objects.length);
if (par.objects.length != 0) {
iGenerateChilds(par, div, $new);
}
});
return div;
};
With this I want to approach that the objects are getting attached to each other, which it now does, however wrong and can't manage to figure out how to do this correct.
Currently it generates
<table></table><tr></tr><td></td><td></td>
and I want
<table><tr><td></td><td></td></tr></table>
Somebody has the solution on this?

Try this:
iGenerateChilds = function (obj, div) {
$.each(obj.objects, function (p, par) {
var genObj = iGenerateObject(par, "");
$(div).append(genObj);
alert(par.objects.length);
if (par.objects.length != 0) {
// add the children to the newly added genObj-element
iGenerateChilds(par, genObj);
}
});
return div;
};

If you could post more information, like your HTML structure and your JSON object, that'd be helpful.
But at first glance I'd say that you're appending the elements at too high of a level. With this line $(div).append(iGenerateObject(par, "")); It looks like you're appending it to the element that's wraps your table, correct? It needs to be appended to the <table> within that passed element.

Related

DOM Traversa l Question - Using mutationObserver to insertAdjacentHTML after each node, and to addEventListener to each button

So, I'm using setMutationObserver to insertAdjacentHTML input buttons in to a div class named '._5pcq'. after the end of that class. That class is located inside of a container named '._3576'.
https://greasyfork.org/scripts/12228/code/setMutationHandler.js
Once that's done, I am using the setMutationObserver to addEventListener('click', function(event) {} to each button that has been inserted in to the '._5pcq' class. Which means it's looking inside of each '._3576' class for the buttons after they've been inserted, which means that I'm pointing the second MutationObserver to look for '.TextCopyButton' inside _3576, and for each node(.TextCopyButton) that it finds, it's adding the listener.
Now, in order to copy the text in the '._3576' class. I need to traverse from the event.target which is (.TextCopyButton) up to where the text to be copied is located at. Which is 2 parentNodes up, nextSibling, nextSibling, and then up 9 parentNodes. Without losing track of the current node.
event.target.parentNode.parentNode.nextSibling.nextSibling.parentNode.parentNode.parentNode.parentNode.parentNode.parentNode.parentNode.parentNode.nextSibling;
I can not do getElementsByClassName(_3576), because that will require a node index number. Which means I need to use event.target to reference the current node that the button is clicked from. My question is how can I reference the current node, and get to the location I need to without using so many parentNode chains.
I need something along the lines of event.target.parentNode[2].nextSibling[2].parentNode[9]
OR
event.target.traverseToNode('.3576');
I have no idea what else to try. I have tried getElementsByClassName('_3576')[event.target]
I have tried using the THIS keyword.
My code is located here:
setMutationHandler({
target: document.querySelector('._3576'),
selector: '.copybtnP',
handler: nodes => nodes.forEach(node => {
node.setAttribute("style", "color:blue; border: 1px solid blue;");
node.addEventListener('click', function(event) {
var classCheck = document.getElementsByClassName('canvasArea');
var status_content = event.target.parentNode.parentNode.nextSibling.nextSibling.parentNode.parentNode.parentNode.parentNode.parentNode.parentNode.parentNode.parentNode.nextSibling;
if (classCheck.length > 0) {
status_content = event.target.previousSibling.previousSibling.previousSibling;
}
//alert(status_content + ' (Working)');
//alert(event.target.previousSibling.innerText);
getSelection().removeAllRanges();
var range = document.createRange();
range.selectNode(status_content);
window.getSelection().addRange(range);
try { var successful = document.execCommand('copy');
var msg = successful ? 'successful' : 'unsuccessful';
//console.log('Copying text command was ' + msg);
} catch (err) {console.log('Oops, unable to copy');}
return false;
})
})
});
https://pastebin.com/Et66j0uc
I think you buried your question inside a bunch of unnecessary description. From what I understand, you want to simply reduce the number of .parentNode calls when traversing up the DOM.
The most obvious answer is to create a function that takes the starting point and the number of traversals, and does them in a loop.
function up(start, num) {
while (--num >= 0 && (start = start.parentNode)) {
// nothing to do in the loop body
}
return start
}
const res = up(event.target, 2)
Then you can do the same for sibling traversal.
However, you can also use the .closest() method for going up through the ancestors, assuming you can describe the element targeted with a selector.
const res = event.target.closest(".someParentClass")
This should prove to be more resilient against changes you make to the DOM, assuming your selector is well defined.

How to check if html if any html element contains text from some Object

I have some object with label and value. Those elements holds some data.
For now i want to loop on html elements and check, what element match this elements. Object is called ui.
Right now i want to select all elements that contains text from any of object elements.
Those elements have class .img.
Below simple javascript:
$("#project").autocomplete({
source: Items,
appendTo: ".autocomplete",
minLength: 2,
response: function (event, ui) {
if (ui.content.length == 0) {
return false;
}
var content = ui.content[0].label;
$.each($('.img'), function () {
if ($(this).is(':contains(' + content + ')')) {
return;
} else {
$(this).fadeOut(100);
}
});
}
});
This code acts as autocomplete, and i want to hide all emelents that don't match data from ui object.
This code works for me almost fine, but there is one thing that break things:
var content = ui.content[0].label;
This selects only first item from object, and i'm looking for a way, how to check all items from obejct to check if text contains data, not only from first obejct element [0].
Thanks for advice.
I create a jsfiddle for you, If I understand the problem
var matchLabelString=_.reduce(ui,function(accumulateString,current){
if(accumulateString == ""){
return current.label;
}
return accumulateString + "|"+current.label;
},"")
var regex = new RegExp("^"+matchLabelString);
$.each($('.img'), function () {
if (regex.test($(this).text())) {
return;
} else {
$(this).fadeOut(100);
}
});
https://jsfiddle.net/3o1oy5y2/
EDIT: I see your code, and I think that my code work anyway. Try it, as you can see I have used underscore library to create a string seperatad by | from array
You can add a loop in your $each callback :
$.each($('.img'), function() {
for (var i = 0; i < ui.content.length; i++) {
var content = ui.content[i].label;
...
}
});
EDIT: Change let to var

Google places autocomplete, how to clean up pac-container?

I'm using the google places autocomplete control, and it creates an element for the drop down with a class pac-container.
I'm using the autocomplete in an ember app, and when I'm done with it the DOM element the autocomplete is bound to gets removed, but the pac-container element remains, even thought its hidden. Next time I instantiate a new autocomplete, a new pac-container is created and the old one remains. I can't seem to find anything like a dispose method on the API, so is there a way of doing this correctly? If not I guess I should just use jquery to clear up the elements.
I was having the same problem, and hopefully Google eventually provides an official means of cleanup, but for now I was able to solve the problem by manually removing the pac-container object, a reference to which can be found in the Autocomplete class returned from:
var autocomplete = new google.maps.places.Autocomplete(element, options);
The reference to the pac-container element can be found at:
autocomplete.gm_accessors_.place.Mc.gm_accessors_.input.Mc.L
Which I simply removed from the DOM in my widget destructor:
$(autocomplete.gm_accessors_.place.Mc.gm_accessors_.input.Mc.L).remove();
Hope this helps.
Update
I'm not sure how Google's obfuscation works, but parts of the above seem obfuscated, and obviously will fail if the obfuscation or internal structures of the API change. Can't do much about the latter, but for the former you could at least search the object properties by expected criteria. As we can see, some of the property names are not obfuscated, while some appear to be, such as "Mc" and "L". To make this a little more robust, I wrote the following code:
var obj = autocomplete.gm_accessors_.place;
$.each(Object.keys(obj), function(i, key) {
if(typeof(obj[key]) == "object" && obj[key].hasOwnProperty("gm_accessors_")) {
obj = obj[key].gm_accessors_.input[key];
return false;
}
});
$.each(Object.keys(obj), function(i, key) {
if($(obj[key]).hasClass("pac-container")) {
obj = obj[key];
return false;
}
});
$(obj).remove();
The code expects the general structure to remain the same, while not relying on the (possibly) obfuscated names "Mc" and "L". Ugly I know, but hopefully Google fixes this issue soon.
My implementation of code from above without jquery.
var autocomplete = new google.maps.places.Autocomplete(element, options);
export function getAutocompletePacContainer(autocomplete) {
const place: Object = autocomplete.gm_accessors_.place;
const placeKey = Object.keys(place).find((value) => (
(typeof(place[value]) === 'object') && (place[value].hasOwnProperty('gm_accessors_'))
));
const input = place[placeKey].gm_accessors_.input[placeKey];
const inputKey = Object.keys(input).find((value) => (
(input[value].classList && input[value].classList.contains('pac-container'))
));
return input[inputKey];
}
getAutocompletePacContainer(autocomplete).remove()
This works for now until Google changes the class name.
autocomplete.addListener('place_changed', function() {
$('.pac-container').remove();
});
Built this recursive function to locate element position inside autocomplete object.
Get first matching object
var elementLocator = function(prop, className, maxSearchLevel, level) {
level++;
if (level === (maxSearchLevel + 1) || !prop || !(Array.isArray(prop) || prop === Object(prop))) {
return;
}
if (prop === Object(prop) && prop.classList && prop.classList.contains && typeof prop.classList.contains === 'function' && prop.classList.contains(className)) {
return prop;
}
for (const key in prop) {
if (prop.hasOwnProperty(key)) {
var element = elementLocator(prop[key], className, maxSearchLevel, level);
if (element) {
return element;
}
}
}
};
Usage:
var elm = null;
try {
//set to search first 12 levels, pass -1 to search all levels
elm = elementLocator(this.autocomplete, 'pac-container', 12, null);
} catch(e) {
console.log(e);
}
I just encountered this issue as well. It may have something to do with my input field being inside of a flexbox but I haven't tried restructuring my page yet. Instead I added an onfocus listener to my input field as well as an onscroll listener to it's container. Inside I get the input field's position with getBoundingClientRect and then update my stylesheet with the values. I tried directly selecting and updating the .pac-container via document.querySelctor but that didn't seem to work. You may need a setTimeout to allow it to be added to the DOM first.
Here is my code:
let ruleIndex = null;
const form = document.body.querySelector('.form');
const input = document.body.querySelector('.form-input');
const positionAutoComplete = () => {
const { top, left, height } = inputField.getBoundingClientRect();
if(ruleIndex) document.styleSheets[0].deleteRule(ruleIndex);
ruleIndex = document.styleSheets[0].insertRule(`.pac-container { top: ${top + height}px !important; left: ${left}px !important; }`);
}
form.addEventListener('scroll', positionAutoComplete);
input.addEventListener('focus', positionAutoComplete);
As mentioned in an earlier answer, this breaks the minute google decides to rename .pac-container so not a perfect fix but works in the meantime.

How to make my css selector engine more flexible?

I created a custom css selector engine function for my custom javascript library like so,
var catchEl = function(el) { // Catching elements by identifying the first character of a string
var firstChar = el[0],
actualNode = el.substring(1, el.length),
elements,
tempElems = [];
if (!document.querySelectorAll) {
try{
if(firstChar === "#") {//So, we can look for ids
tempElems.push(document.getElementById(actualNode));
} else if(firstChar === ".") {//or classes
elements = document.getElementsByClassName(actualNode);
for(i=0;i<elements.length;i++) tempElems.push(elements[i]);
} else {//or tags
elements = document.getElementsByTagName(el);
for(i=0;i<elements.length;i++) tempElems.push(elements[i]);
}
} catch(e) {};
} else {//but before everything we must check if the best function is available
try{
elements = document.querySelectorAll(el);
for(i=0;i<elements.length;i++) tempElems.push(elements[i]);
} catch(e) {};
}
return tempElems;
}
This function returns an array of elements. However, I turned my head around and tried to make it more flexible so that it can also return the window, document or this object, but was unsuccessful. Whenever I try to push the window object into the tempElems array, the array is still empty.
So, I want to know how to make this function return an array of elements when a string is passed through it or return the respective objects(window, document or this) as desired.
Note: I don't want to work with jQuery. So, please don't post any answers regarding jQuery.

My function returns empty. why?

I know that this is a basic question but I am stuck with it somewhere in my code. I got that code from somewhere but now I am modifying it according to my need.
What does jQuery('#selector') do? In my code it always return empty.
Here is my code
query: function (selector, context) {
var ret = {}, that = this, jqEls = "", i = 0;
if(context && context.find) {
jqEls = context.find(selector);
} else {
jqEls = jQuery(selector);
}
ret = jqEls.get();
ret.length = jqEls.length;
ret.query = function (sel) {
return that.query(sel, jqEls);
}
return ret;
}
when I call this query function then I pass selector as parameter. When I do console.log(selector) it does have all the selectors which I need in this function. But the problem is on this line jqEls = jQuery(selector);. when I do console.log(jqEls) after this it returns empty thus the whole function returns empty.
Can I use something different then this to make it work?
jquery('#selector') is the equivalent of document.getElementById('selector'). If there is no DOM node with an id of selector, you get an empty result.
e.g.
<div id="selector">...</div>
would return the dom node corresponding to this div. Do you have jquery loaded?
jQuery(selector) is looking for a DOM element that meets the selector criteria.
$('#example') == jQuery('#example')
Both will look for something with id "example"
$(selector).get() will return undefined if no element is found. This is why your function returns undefined. To fix this, you could use a default value if there is no element found:
ret = jqEls.length ? jqEls.get() : {};
This way your function will always return an object that has your length and query properties, but it will not have an element if jQuery did not find one.
After reading your code I have a question : do you put the # in your variable selector ?
A solution to solve this by replacing the bad line by jqEls = jQuery("#" + selector);
If the problem isn't due to that can you say the type of selector ? string ? object ? jQueryObject ?

Categories