I've got an image gallery where each item in the gallery has a unique ID I'm using to tie it to my backend so that I can run DB actions etc. I'm using the data attribute to hold the 'imageid' on the parent div of each image for example: data-imageid="168"
The image gallery items have alot of nested divs so on rollover/click I find myself doing alot of the below in order to run actions:
$(this).parent().parent().attr('data-imageid');
It doesn't feel like a clean solution or the best performance wise. How are other people handling this? What's the best solution? Whatever the solution, this must be a problem solved everyday.
Edit: I realize my subject line is vague but it further explains where my heads at with this problem.
You can specify a criterion to go "up to", so if you want to go up to the nearest div or li or whatever, either use jQuery's .closest() or a simple upTo() function:
function upTo(el, tagName) {
tagName = tagName.toLowerCase();
var el;
do {
el = el.parentNode;
if (el && el.tagName && el.tagName.toLowerCase() == tagName) {
return el;
}
} while (el)
}
This sounds suspiciously like you have a case of divitis. In any case you can use .parents([selector]) with a filter to make your code a bit less verbose. e.g.
$(this).parents(".classOfDivContainingData").attr('data-imageid');
or you could loop through the parent divs on initial load, and copy the data attribute to the "this" in your example. Is there a reason why you can't set the attribute here in the first place?
You can try this
$(this).parents("[data-imageid]:first");
I would recommend you to use additional attributes to the elements:
<div data-imageid="367">
<div>
<div data-parent-imageid="367">
</div>
</div>
</div>
So you can make action knowing only the element which fired the event. Using this solution you don't care about structure of the elements, move them in the DOM tree freely and performance will be improved too.
Related
On an HTML page which repeats a nested structure like
<div>
<div class="ugga">
<button class="theButton">
</div>
</div>
several times, with one ".theButton" also having class "active", I would like to use jquery to find the button after the active button.
$(".theButton .active").parents(".ugga").parent().next().find(".theButton")
would roughly do the trick. However, this is still under development, so that I am not sure that the nesting level div/div/button as well as the parent element with ".ugga" will be stable. So whenever there is a structure change on the HTML side, I would have to change the above jquery-magic accordingly.
What is stable is that there will be a list of ".theButton" elements at some nesting level and all on the same nesting level.
Is there a simple way in Jquery to find the next button after the active one even if the structure is changed to just div/button or to form/div/div/button and the ".ugga" I rely on currently disappears? Something like
$(".theButton active").nextOnSameLevel(".theButton")
There's no short and simple solution I know of, which would let you to do that.
The most convenient way would be to have the HTML ready before setting up javascript for DOM manipulation. Even thinking of project updates I would personally spent that little while to change a small part of js.
However, if that's needed for some reason, then I would probably loop through the elements, to find the one I need, eg.:
var found = false;
$(".theButton").each(function(){
if(found){
// do something with $(this) ...
return false;
}
if($(this).hasClass('active')){
found = true;
}
});
JSFiddle
And yet another, oneliner solution:
$(".theButton").eq(($.inArray($(".theButton.active")[0], $(".theButton").toArray()))+1);
JSFiddle
This is for looking at all the other elements having the same parent as your element of interest.
$(".theButton active").siblings(".theButton");
This will return all the elements having theButton before and after your active button elements but if you're looking specifically for the element after active, use next() with a selector like this
$(".theButton active").next(".theButton");
Given the following HTML:
<fieldset>
<legend>
<input type="checkbox" name="amazon_activated" />
</legend>
<table>
<tr>
<td>
Amazon Data
</td>
</tr>
</table>
</fieldset>
next code allows hide/show the data container table related to the checkbox:
$("input[name='amazon_activated']").click(function(){
$(this).parent("legend").next("table").toggle( $(this).is(":checked") );
});
and next code it should init the hide/show state after page's load:
if ( ! $("input[name='amazon_activated']").is(":checked")){
$(this).parent("legend").next("table").hide();
}
Well, it is failing.
I already know why: this refers to the page element, not the checkbox element.
So I wonder:
Is it the best policy immediately choose an id/class:
$("#id")
for the important elements in order to facilitate their control through jquery, over form-based selectors?:
$("input[name='amazon_activated']")
Cache the element.
var element = this;
When this is in the right context.
You usually code Javascript for a given markup, not the other way around. ID's and classes have meaning other than being used as selectors.
I think you can cache your elements at the top of the outer function. It will be convenient to use them in other places. Also, caching elements will also enhance the performance.
You can do something like this:
var input = $("input[type='checkbox']"),
table = $("fieldset table");
if ( ! $("input[name='amazon_activated']").is(":checked")){
$(this).parent("legend").next("table").hide();
}
within above code this belong to page element scope, where
$("input[name='amazon_activated']").click(function(){
$(this).parent("legend").next("table").toggle( $(this).is(":checked") );
});
in above code this belongs to input[name='amazon_activated']'s callback scope which point to input[name='amazon_activated'].
So to make active first code you should try
if ( ! $("input[name='amazon_activated']").is(":checked")){
$("input[name='amazon_activated']").parent("legend").next("table").hide();
}
Its better to keep reference of input in a variable and use that
var checkboxElement = $("input[name='amazon_activated']"); // you can also use id
then you this like
if ( ! checkboxElement.is(":checked")){
checkboxElement.parent("legend").next("table").hide();
}
checkboxElement.click(function(){
$(this).parent("legend").next("table").toggle( $(this).is(":checked") );
});
I think the reason every answer is telling you to cache your objects is that it will be tremendously faster. To answer the specific question, disregarding the rest, i.e.:
Is it the best policy immediately choose an id/class for the important
elements in order to facilitate their control through jquery, over
form-based selectors?
First, I would say "attribute selectors" over "form-based selectors" as I don't believe jQuery distinguishes between, say $('input[name="amazon_activated"]') and $('a[href="#"]') as far as how it searches.
That said, the general rule of thumb is:
id selectors are faster than class selectors are faster than attribute
selectors.
So, if all you care about is jQuery speed, that's key. However, adding ids and classes, only for the sake of targeting via jQuery could slow down your page load times more than the corresponding speed-up in selector performance.
In summary to this overly-long answer:
Cache the result of a jQuery selector when possible
Use ids and classes when possible, but
Don't add unnecessary ids and classes unless testing proves them necessary
Thanks to all for your answers. All have been very helpful (I give +1 to all).
I want to add my final solutions that takes in account your tips.
Indeed there are several checkbox elements involved, so I have cached them and use an associative array to iterate over it (avoiding add more id/class).
var checkboxElements = {
"amazon_activated" : $("input[name='amazon_activated']"),
"clickbank_activated" : $("input[name='clickbank_activated']"),
"ebay_activated" : $("input[name='ebay_activated']")
}
$.each(checkboxElements, function(i, el){
$(el).parent("legend").next("table").toggle( $(el).is(":checked") );
$(el).click(function(){
$(el).parent("legend").next("table").toggle( $(el).is(":checked") );
});
});
This is the same question as this:
Referring to a div inside a div with the same ID as another inside another
except for one thing.
The reason there are two elements with the same ID is because I'm adding rows to a table, and I'm doing that by making a hidden div with the contents of the row as a template. I make a new div, copy the innerhtml of the template to my new div, and then I just want to edit bits of it, but all the bits have the same ID as the template.
I could dynamically create the row element by element but it's a VERY complex row, and there's only a few things that need to be changed, so it's a lot easier to just copy from a template and change the few things I need to.
So how do I refer to the elements in my copy, rather than the template?
I don't want to mess up the template itself, or I'll never be able to get at the bits for a second use.
Or is there another simpler way to solve the problem?
It will probably just be easiest when manipulating the innerHtml to do a replace on the IDs for that row. Maybe something like...
var copiedRow = templateRow.innerHTML.replace(/id=/g,"$1copy")
This will make the copied divs be prefixed with "copy". You can develop this further for the case that you have multiple copies by keeping a counter and adding that count variable to the replace() call.
When you want to make a template and use it multiple times its best to make it of DOM, in a documentFragment for example.
That way it doesn't respond to document.getElementById() calls in the "live" DOM.
I made an example here: http://jsfiddle.net/PM5544/MXHRr/
id's should be unique on the page.
PM5544...
In reality, there's no use to change the ID to something unique, even though your document may not be valid.
Browsers' selector engines treat IDs pretty much the same as class names. Thus, you may use
document.querySelector('#myCopy #idToLookFor');
to get the copy.
IDs on a page are supposed to be unique, even when you clone them from a template.
If you dynamically create content on your page, then you must change the id of your newly cloned elements to something else. If you want to access all cloned elements, but not the template, you can add a class to them, so you can refer to all elements with that class:
var clonedElement = template.cloneNode(yes); // make a deep copy
clonedElement.setAttribute("id", "somethingElse"); // change the id
clonedElement.setAttribute("class",
clonedElement.getAttribute("class") + " cloned"
);
To access all cloned elements by classname, you can use the getElementsByClassName method (available in newer browsers) or look at this answer for a more in-depth solution: How to getElementByClass instead of GetElementById with Javascript?
Alternatively, if you have jQuery available, you can do this is far less lines of code:
$("#template").clone().attr("id","somethingElse")
.addClass("cloned").appendTo("#someDiv");
The class lookup is even simpler:
$(".cloned").doSomethingWithTheseElements();
Try to avoid using IDs in the child elements of the cloned structure, as all ids of the cloned element should be changed before adding the clone to the page. Instead, you can refer to the parent element using the new id and traverse the rest of the structure using classnames. Class names do not need to be unique, so you can just leave them as they are.
If you really must use ID's (or unique "name" attributes in form fields), I can strongly suggest using a framework like jQuery or Prototype to handle the DOM traversal; otherwise, it is quite a burden to resolve all the cross-browser issues. Here is an example of some changes deeper in the structure, using jQuery:
$("#template").clone().attr("id","somethingElse")
.addClass("cloned") // add a cloned class to the top element
.find("#foo").attr("id","bar").end() // find and modify a child element
.appendTo("#someDiv"); // finally, add the node to the page
Check out my ugly but functional cheese. I wrote a function that works like getelementbyid, but you give it a start node instead of the document. Works like a charm. It may be inefficient but I have great faith in the microprocessors running today's browsers' javascript engines.
function getelement(node, findid)
{
if (node)
if (node.id)
if (node.id == findid)
return node;
node = node.firstChild;
while(node)
{
var r = getelement(node, findid);
if (r != null)
return r;
node = node.nextSibling;
}
return null;
}
When you copy the row, don't you end up having a reference to it? At that point can't you change the ID?
I wonder how I in the best way can see if a container div contains a child element. I have a click event that fires on a child of either div id=unread or div id=read. I want to check where this child is.
Something like this is what I'm thinking:
if ($("#unread").find($(this)))
alert("unread");
else
alert("read");
Edit:
$(this) is a descendants two levels from #unread or #read.
Regards
Make use of : .children()
if( $("#unread").children().length > 0)
alert("unread");
else
alert("read");
EDIT
if($(event.target).closest('#unread').length > 0)
alert('unread');
else
alert('read');
I think just adding .length should work:
if ($("#unread").find($(this)).length > 0)
alert("unread");
else
alert("read");
Use .closest() or .parents() to search up the tree from the clicked element:
if ($(this).closest("#unread").length == 1)
// etc
Otherwise, are you interested in a non-jQuery answer? Given you've said the div with the click event is exactly two levels below the "read" or "unread" divs you could just do this:
if (this.parentNode.parentNode.id === "unread")
alert("unread");
else
alert("read");
// or just alert(this.parentNode.parentNode.id);
Go backwards from $(this) to look for #unread as an ancestor using closest:
if($(this).closest('#unread').length > 0)
alert('unread');
else
alert('read');
Depending on your HTML structure, this will be faster than searching all the children of #unread to find this. The speed difference probably won't matter that much though but you should be aware of the option of going backwards and the possible benefits of doing so.
Checking for the ancestor might be a better match for your intent as well: you have this in hand and what you really want to know is "is it inside #unread?". Using closest to go back up the DOM tree exactly matches the question you're asking.
If for some reason you're dead set on starting at #unread and looking at its descendants, then you can use find:
if($('#unread').find(this))
alert('unread');
else
alert('read');
But this approach will only work if you're using at least jQuery 1.6.
I wonder how I in the best way can see if a container div contains a child element.
Use $.contains(), especially if performance is a concern. From the docs:
jQuery.contains( container, contained )
Returns: Boolean
Description: Check to see if a DOM element is a descendant of another DOM element.
It's more efficient than .find(), .children(), or .closest(), as others have recommended. Be aware, however, that it only accepts DOM elements as parameters, not jQuery objects (which is part of why its performance is better).
In your case, you could do this in your click event handler:
if ($.contains($("#unread")[0], this)) {
alert("unread");
} else {
alert("read");
}
EDIT: Upon thinking about this again, it's probably more intuitive to search up from the clicked element, rather than down from the parent element, and performance wouldn't normally be a concern in a click event handler. That is, I would opt to use the .closest() method (as suggested by #nnnnnn and #mu is too short), so my click event handler would contain:
if ($(this).closest("#unread").length > 0) {
alert("unread");
} else {
alert("read");
}
What could be best way to add some content into ParentNode, if it has several child nodes without id.
the condition are :
<div class="parent_div">
<div class="child_div_1" > I need to add contents inside this div </div>
<div class ="child_div_2" onmouseover="addClass(this)"> </div>
</div>
My possible solution : function addClass(obj) {
obj.parentNode.firstChild.appendChild(...);
}
But i have doubt if someone changes the position of first child then what? according to you what could be best way to sort out this problem.
Use JQuery :
function addClass(obj) {
$(obj.parentNode)
.find("ClassName_in_which_you_want_to_append")
.append("your_derived_contents");
}
Note : find() it just look up the class_name whatever you want. Position doesn't matter. It just look up inside the parent node.
append() : just add the contents as a child
I hope this helps you a lot.
I'm not sure I understand. There must be something determining which element you want to put the content in and, if that's the case, it should have an ID (a unique element deserves a unique field, right?)
If those class names that you've defined (child_div_1) are meaningful to the element in which you need to insert content, you can use the method getElementsByClassName to select the correct element.
function addClass(obj) {
obj.parentNode.getElementsByClassName('child_div_1')[0].appendChild(/* ... */);
}
If the class names you've given in the example are just arbitrary, you're going to have to add some additional information to your block elements.
please add the ids for those tags that you need to refer to, it's much easier and more accurate for your javascript code to run.