How to change the id of deep nested elements with jQuery - javascript

I have this recursive code that transverses the DOM and adds a prefix to the id for all input tags.
I would like to change this to a more elegant jQuery, but I'm not sure how to structure the selectors or if the selectors need to be recursive..
cheers,
function set_inputs(obj, prefix){
for (var s=0;s< obj.childNodes.length; s++){
var node = obj.childNodes[s];
if(node.tagName == "INPUT"){
node.id= prefix +'_' + node.id;
node.name= prefix +'_' + node.name;
}
else{
set_inputs(node,prefix);
}
}
}

For the entire DOM.
It would be as simple as:
var prefix;
$("input").each(function(i,elem)
{
$(elem).attr("id", prefix + "_" + $(elem).attr("id"));
});
You could change the selector : $("input") - which selects all the doms inputs, to any other selector to target different elements.
If you wanted it separately in a function then:
function() set_inputs(col, prefix) {
col.each(function(i,elem)
{
$(elem).attr("id", prefix + "_" + $(elem).attr("id"));
});
}
You would then use it like this:
set_inputs($("input"), "abc");//prefix ALL the DOM's inputs with abc
set_inputs($("input.btn"), "abc");//prefix inputs with the css-class btn

No particular need to use jQuery for this either. It could be done in plain javascript without recursion using getElementsByTagName() like this:
function set_inputs(obj, prefix) {
var nodes = obj.getElementsByTagName("input");
for (var i = 0, len = nodes.length; i < len; i++) {
if (nodes[i].id) {
nodes[i].id = prefix + '_' + nodes[i].id;
}
if (nodes[i].name) {
nodes[i].name = prefix + '_' + nodes[i].name;
}
}
}
P.S. I added protection in the code that your code did not have in case input tags exist without an id or a name attribute so the code won't error out if it encounters that. If you didn't want that protection, the code would be shorter like this:
function set_inputs(obj, prefix) {
var nodes = obj.getElementsByTagName("input");
for (var i = 0, len = nodes.length; i < len; i++) {
nodes[i].id = prefix + '_' + nodes[i].id;
nodes[i].name = prefix + '_' + nodes[i].name;
}
}
You call this function by passing it two arguments, the DOM object that represents the top of the part of the DOM tree you want to search for input tags in and the prefix you want to add to the IDs. If you do something like this: set_inputs(document.body, "test") it will search the entire document. If you do something like this: set_inputs(document.getElementById("top"), "test"), it will only search a portion of the DOM tree (the part under the id=top element). You can pass it any arbitrary DOM object and it will only search the nodes in that hierarchy.

Just want to suggest a small change
$('input').each(function(){
this.id = prefix + this.id;
});

To pull the deep nested inputs, use jquery find(). This solution is much simpler code than recursive javascript. I did leave out the steps verifying the existence of id and name attributes which should be done for production code.
$(obj).find("input").each(function(){
$(this).attr('id',prefix + "_" + $(this).attr('id'));
$(this).attr('name',prefix + "_" + $(this).attr('name'));
});

Related

Selector syntax for not inside a container

I made this helper function to assist me with stuff
function formToObject(form) {
var items = form.querySelectorAll("[name]");
var json = "{";
for (var i = 0; i < items.length; i++) {
json += '"' + items[i].name + '":"' + items[i].value + '",';
}
json = json.substring(0, json.length-1) + "}";
return JSON.parse(json);
}
There is a problem with it though, it doesn't exclude forms that are inside the form element thats passed to it. Didn't come up before but now it seems to be necessary. Is there a way to write the selector string in a way that it would exclude any children of other forms inside it?
Like "[name]:not(form *)" (which obviously doesn't work)
EDIT: got a little closer with "[name] :not(fieldset) *" but that also ignores the immediate fieldset children

Element undefined in jQuery each iteration

I'm trying to iterate over a collection of jQuery elements as follows:
var l = $(".ace_line").length;
$(".ace_line").each($(function(index,element) {
console.log("Element = " + element);
console.log(index + ": " + element.text());
}));
When I examine l its value is 39 so I know the collection is not null.
However element is undefined when I loop through the collection.
What am I doing wrong?
Any help would be appreciated!
A couple of problems there:
You're wrapping your callback in $(), which makes jQuery think you're using the shorthand version of $(document).ready(function...). Since the DOM is ready, it calls that function (once) passing it the jQuery instance as the first argument and no second argument at all.
You're not using $() around element. element will just be a DOM element, not a jQuery instance, so to call text on it, you need to wrap it first.
So:
var l = $(".ace_line").length;
$(".ace_line").each(function(index,element) {
// No $( here ------^
var $el = $(element); // <=== Do wrap `element`
console.log("Element = " + $el);
console.log(index + ": " + $el.text()); // <=== Use $el
}); // <== Removed a ) here
Note that the more normal thing to do would be to use this:
var l = $(".ace_line").length;
$(".ace_line").each(function(index) {
var $el = $(this); // <===
console.log("Element = " + $el);
console.log(index + ": " + $el.text()); // <===
});
Remove the $( from within the each function, like so:
var l = $(".ace_line").length;
$(".ace_line").each(function(index,element) {
console.log("Element = " + element);
console.log(index + ": " + $(element).text());
});
Additionally, your element will be a HTML DOM element, not a jQuery item, so to get .text() you would need $(element).text()
you are using wrong syntax $(function(){})
caching variables makes your script is faster
for is faster than each()
by using for you do not have to wrap your element with $() again
jQuery
var lines = $(".ace_line"); //caching the element
var l = lines.length; //getting length for loop without selecting element again
for (var j = 0; j < l; j++){ //for loop where j is your index
console.log(lines.eq(j)); //getting the element by using jQuery's eq()
console.log(lines.eq(j).text()); //use any jQuery function on the element
}

Trying to remove jQuery [duplicate]

This question already has answers here:
Using querySelectorAll to retrieve direct children
(14 answers)
Closed 8 years ago.
There is the site : http://youmightnotneedjquery.com/ : and with my AngularJS application, I would really like to remove jQuery. The reason I still need jQuery and I can't just use jQLite that AngularJS comes with is because jQLite does not support selectors based on classes.
The issue with querySelectorAll() is that when I try to run this:
el.querySelectorAll('> .content')
I get this:
SyntaxError: Failed to execute query: '> span' is not a valid selector.
Is there a way to write this selector with just native DOM methods?
If you are really forced to use jQuery to find the .content first level childs, use XPath instead, it will do the exact same thing:
var xpathQuery = "./*[#class='content'";
xpathQuery += " or starts-with(#class,'content ')";
xpathQuery += " or contains(#class,' content ')";
xpathQuery += " or substring(#class, string-length(#class)-6)=' content']";
var iterator=document.evaluate(xpathQuery , el, null, XPathResult.ANY_TYPE, null );
var contentNode = iterator.iterateNext();
just be careful using ./ instead of .// is something like using '>' instead of space ' ' in jQuery selectors.
I managed to create a general function for your use:
function findByClassName(el, className, firstLevel) {
var xpathQuery = firstLevel ? "./" : ".//";
xpathQuery += "*[#class='" + className + "'";
xpathQuery += " or starts-with(#class,'" + className + " ')";
xpathQuery += " or contains(#class,' " + className + " ')";
xpathQuery += " or substring(#class, string-length(#class)-" + (className.length) + ")=' " + className + "']";
var iterator = document.evaluate(xpathQuery, el, null, XPathResult.ANY_TYPE, null);
var nodes = [];
var node;
while (node = iterator.iterateNext()) {
nodes.push(node);
}
return nodes;
}
for your use case you should use it like:
var contentNodes = findByClassName(el, 'content', true);
and this is the working jsfiddle DEMO.
If you want only direct children of el that have the class .content, then you can use this:
function queryChildren(el, sel) {
var all = el.querySelectorAll(sel);
var filtered = [];
for (var i = 0; i < all.length; i++) {
if (all[i].parentNode == el) {
filtered.push(all[i]);
}
}
return filtered;
}
queryChildren(el, ".content");
This runs the query that finds all descendants that match the class name and then filters that list to only the ones who are direct children of the passed in element.
You need a parent in order to have a child. Maybe something like this ?
el.querySelectorAll('body > .content')

PrototypeJS loop won't run

I have a loop which won't run using Prototype + Scriptaculous. It runs once for the first object in my array then stops.
var myMessages = new Object();
myMessages = ['success','info','warning','error']; // define the messages types
function hideAllMessages()
{
var messagesHeights = new Array(); // this array will store height for each
enter code here
// This one runs just once for the first item in the array
var i = 0;
myMessages.each(function(element) {
alert(element);
messagesHeights[i] = $('.' + element).getHeight();
i++;
$$('.' + element + ' message').invoke('hide');
});
//This won't run at all===============================
for (var index = 0; index < myMessages.length; index++)
{
messagesHeights[index] = $('.' + myMessages[index]).getHeight();
$('x').hide();
//$('.' + myMessages[i]).css('top', -messagesHeights[i]); //move element outside viewport
}
}
I'm not a prototype user, but here's what I see so far:
$ is for IDs. I believe you need $$ here:
$$('.' + element)
This returns an Array, so I think you need invoke() like this:
$$('.' + element).invoke('getHeight');
Also, .each() passes the index as the second argument to the callback, so you don't need to maintain your own i.
myMessages.each(function(element, i) {
Also, this:
$$('.' + element + ' message')
...would seem to be looking for elements with the tag named message. I assume you want a class instead.
$$('.' + element + ' .message').invoke('hide');

How to identify the chosen selector in jQuery?

$('#select_id1, #select_id2, #select_id3').change(function() {
// If '#select_id1' has changed, 'str' should be equal to 'select_id1'.
// If '#select_id2' has changed, 'str' should be equal to 'select_id2'.
// If '#select_id3' has changed, 'str' should be equal to 'select_id3'.
str = <what should be here ?>
});
You can get the id of the element that invoked the change by this.id.
$('#select_id1, #select_id2, #select_id3').change(function() {
str = this.id;
});
Or (less efficiently):
$('#select_id1, #select_id2, #select_id3').change(function() {
str = $(this).attr("id");
});
But basically this is set to the element on which the event took place.
For the more general case, where not only IDs are used, as suggested by #Anurag, you can do the following:
// Save the selector
var selector = ".someClass, #someId, tr.someTrClass";
$(selector).change(function () {
var selectors = selector.split(","),
matching = []; // Remember that each element can
// match more than one selector
for (var i = 0, s; s = selectors[i]; i++) {
if ($(this).is(s)) matching.push(s);
}
str = matching.join(","); // Your list of all matching selectors
});
You can either look at this.id directly or indirectly via a passed in event object:
$('#select_id1, #select_id2, #select_id3').change(function (e) {
alert(e.target.id + ' == ' + this.id + ' ... ' + (e.target.id == this.id));
});
Most people just look at this but there are times when you might be interested in more than just the target of your event.

Categories