javascript check if DOM element exists best practice - javascript

What is the best practice to check if a DOM element exists in javascript?
Should one check if an item exists prior to using it, like so?
if ($("#" + machineId + packageId.removeSpecialChars().toUpperCase() + "").size() != 0) {
var row = $("#" + machineId + packageId.removeSpecialChars().toUpperCase() + "");
}
wouldn't this execute packageId.removeSpecialChars().toUpperCase() twice?
OR would this be better option?
var row = $("#" + machineId + packageId.removeSpecialChars().toUpperCase() + "");
if (row)
{
// do something
}
However, wouldn't it throw an exception when not found?

When you're actually working with DOM elements, then yes, you should check that it exists before you attempt to work with it to avoid JavaScript errors. However, you're not working with DOM elements, you're working with a jQuery object that (potentially) contains DOM elements.
jQuery functions already handle cases where there are no matches in its set of elements, so you don't need to explicitly check for yourself that there are elements before attempting to work with them. You'd only need to do so if you're trying to directly reference DOM elements from inside that set, using the .get() function or the [index] square bracket notation.
As an aside, the .size() jQuery function was deprecated in version 1.8, you should use the jQuery object's length property directly to check if there are elements, so:
var $object = $('a-selector');
if($object.length) {
// there's at least one matching element
}

Better to cache it:
var machId = $("#" + machineId + packageId.removeSpecialChars().toUpperCase());
if (machId.size() != 0) {
var row = machId;
}

General programming conventions say don't repeat yourself. So, in this case, you could at least do the finding of the thing only once and keep a variable reference:
var thing = $("#" + machineId + packageId.removeSpecialChars().toUpperCase() + "");
Then the selection lookup doesn't happen twice, redundant method calls removed etc. This also has the benefit of allowing you to write more self-explanatory code, when you can name a variable to something meaningful, other than thing, or, eeek!, a (though it isn't necessarily so that code must be more meaningful, people still use names like a!)
if (thing != null) { }
if (thing.size() != 0) {
}
etc.
As for calling methods multiple times, that's often unavoidable.

It does, what you need is:
var a = $("#" + machineId + packageId.removeSpecialChars().toUpperCase() + "");
if (a.size()) {
var row = a;
}

You basically need to see if the DOM element is exist in your HTML, but beer in mind that jQuery doesn't throw a fatal error if the element not exist in your DOM, but would a good practice to check, it adds one more secure layer on your code, there was something called .size() that deprecated from version 1.8, so not recommended to use even you use old version of jQuery, so the best solution at the moment would be something like below code:
if($('.class').length) { // check if any element with this class exist, if not exist, it return 0 and can not pass the if estatement
// do something
}

Related

0.id is null or not an object, parentElement of the parentElement and IE 8

I am using the following 3 lines of code in my web app.
selected_build = event.currentTarget.parentElement.parentElement.id;
var jselect = $( "#" + selected_build + " .roomc" ).children(".room");
selected_room = jselect[0].id;
This works in most browsers but in IE 8 I get the error
'0.id' is null or not an object
which points to the third line of code. I assume this has something to do with parentElement only working on Elements, but I can't seem to figure it out.
I assume this has something to do with parentElement only working on Elements...
If IE8 doesn't have parentElement, since by definition you're dealing with an element as of event.currentTarget, you can safely use parentNode instead.
But as you're using jQuery anyway, why not...use jQuery?
selected_build = $(event.currentTarget).parent().parent().attr("id");
if (selected_build) {
var jselect = $("#" + selected_build + " .roomc").children(".room");
selected_room = jselect.attr("id");
if (selected_room) {
// ... use it
}
}
Note that I'm using .attr("id") to get the id. That's because if the jQuery set is empty, .attr("id") gives you undefined, whereas [0].id gives you an error.
And actually — you don't need to use ids at all here other than at the end for selected_room:
selected_room =
$(event.currentTarget)
.parent().parent()
.find(".roomc > .room")
.attr("id");
if (selected_room) {
// ... use it
}
...and I bet there's a way to hook up the event so we can avoid that .parent().parent(), too, which is fragile. But without seeing the structure, it's impossible to suggest something.

What is the best way to access an element through a data-attribute whose value is an object (JSON)?

Say I have the following element:
<div class='selector' data-object='{"primary_key":123, "foreign_key":456}'></div>
If I run the following, I can see the object in the console.
console.log($('.selector').data('object'));
I can even access data like any other object.
console.log($('selector').data('object').primary_key); //returns 123
Is there a way to select this element based on data in this attribute? The following does not work.
$('.selector[data-object.foreign_key=456]');
I can loop over all instances of the selector
var foreign_key = 456;
$('.selector').each(function () {
if ($(this).data('object').foreign_key == foreign_key) {
// do something
}
});
but this seems inefficient. Is there a better way to do this? Is this loop actually slower than using a selector?
You can try the contains selector:
var key_var = 456;
$(".selector[data-object*='foreign_key:" + key_var + "']");
I think that you may gain a little speed here over the loop in your example because in your example jQuery is JSON parsing the value of the attribute. In this case it's most likely using the JS native string.indexOf(). The potential downside here would be that formatting will be very important. If you end up with an extra space character between the colon and the key value, the *= will break.
Another potential downside is that the above will also match the following:
<div class='selector' data-object="{primary_key:123, foreign_key:4562}"></div>
The key is clearly different but will still match the pattern. You can include the closing bracket } in the matching string:
$(".selector[data-object*='foreign_key:" + key_var + "}']");
But then again, formatting becomes a key issue. A hybrid approach could be taken:
var results = $(".selector[data-object*='" + foreign_key + "']").filter(function () {
return ($(this).data('object').foreign_key == foreign_key)
});
This will narrow the result to only elements that have the number sequence then make sure it is the exact value with the filter.
With a "contains" attribute selector.
$('selector[data-object*="foreign_key:456"]')

Can I make a compound selector including this in jquery?

I have a function that uses each to go over each element in a set and renumber them after one is removed from the DOM.
Right now that function looks like this:
renumber_items = function(){
$(".item_set").each(function(index){
$(this).find('legend').html("Item " + (index+1));
});
};
I remember reading somewhere that find is a really inefficient operation, so I was wondering if there's a way to combine the 'legend' selector into a compound selector with this.
If there is only one legend per .item_set this will abbreviate things a bit:
renumber_items = function(){
$(".item_set legend").html(function(index){
return "Item " + (index+1);
});
};
.html can take a function and the result is stored.
If there is more than one legend per .item_set you will need to retain an outer each to keep the numbers sequential for each set.
Generally if you have speed issues, on a function called many times, and the jQuery selector result is on a fixed set of elements, you just archive the search to a variable once at page load and reuse that:
var $legends = $(".item_set legend");
renumber_items = function(){
$legends.html(function(index){
return "Item " + (index+1);
});
};
Maybe you can try with .filter(). As others say, it shouldn't be such a performance issue as long as you're not using it all the time. Consider labeling all the items you want to find/filter, so that you can get them all in one JQuery selector at once, and you don't have to go filtering everything after. Else you can use (as commented out by #Regent):
renumber_items = function(){
$(".item_set legend").each(function(index){
$(this).html("Item " + (index+1));
});
};
You can replace:
$(this).find('legend')
with:
$('legend', this)
The second argument sets the context in which jQuery searches for 'legend'.
If it is omitted, the context defaults to be document.

How can I get back to the original DOM after being affected by javascript

Imagine I have a loaded HTML page which has been already affected by javascript adding/deleting dynamic elements or new classes/attributes/id to elements while initializing(e.g: original source code [html] tag has no classes, after javascript loads [html] tag has class="no-responsive full-with"). Imagine after that I add/amend some id values manually (through my app). And imagine I need to be able to save in database the original source code (without any amends) but with the id attributes I added manually.
Basically I need to add a given id attribute to an element within the source code of an HTML, loaded through PHP.
Do you guys have any idea of how to do such a thing?
There's no simple solution here. The exact nature of the complex solution will be determined by your full set of requirements.
Updated Concept
You've said that in addition to changing things, you'll also be adding elements and removing them. So you can't relate the changed elements to the originals purely structurally (e.g., by child index), since those may change.
So here's how I'd probably approach it:
Immediately after the page is loaded, before any modifications are made, give every element in the a unique identifier. This is really easy with jQuery (and not particularly hard without it):
var uniqueId = 0;
$("*").attr("data-uid", function() {
return ++uniqueId;
});
Now every element on the page has a unique identifier. Next, copy the DOM and get a jQuery wrapper for it:
var clone = $("html").clone();
Now you have a reliable way to relate elements in the DOM with their original versions (our clones), via the unique IDs. Allow the user to make changes.
When you're ready to find out what changes were made, you do this:
// Look for changes
clone.find("*").addBack().each(function() {
// Get this clone's unique identifier
var uid = $(this).attr("data-uid");
// Get the real element corresponding to it, if it's
// still there
var elm = $("[data-uid=" + uid + "]")[0];
// Look for changes
if (!elm) {
// This element was removed
}
else {
if (elm.id !== this.id) {
// This element's id changed
}
if (elm.className !== this.className) {
// This element's className changed
}
// ...and so on...
}
});
That will tell you about removed and changed elements. If you also want to find added elements, just do this:
var added = $(":not([data-uid])");
...since they won't have the attribute.
You can use the information in clone to reconstruct the original DOM's string:
clone.find("[data-uid]").addBack().removeAttr("data-uid");
var stringToSend = clone[0].outerHTML;
(outerHTML is supported by any vaguely modern browser, the latest to add it was Firefox in v11.)
...and of course the information above to record changes.
Live proof of concept
HTML:
<p class="content">Some content</p>
<p class="content">Some further content</p>
<p>Final content</p>
<input type="button" id="makeChange" value="Make Change">
<input type="button" id="seeResults" value="See Results">
JavaScript:
// Probably unnecessary, but I wanted a scoping
// function anyway, so we'll give the parser time
// to completely finish up.
setTimeout(function() {
// Assign unique identifer to every element
var uniqueId = 0;
$("*").attr("data-uid", function() {
return ++uniqueId;
});
// Clone the whole thing, get a jQuery object for it
var clone = $("html").clone();
// Allow changes
$("#makeChange").click(function() {
this.disabled = true;
$("p:eq(1)").attr("id", "p1");
$("p:eq(2)").addClass("foo");
alert("Change made, set an id on one element and added a class to another");
});
// See results
$("#seeResults").click(function() {
this.disabled = true;
// Look for changes
clone.find("*").addBack().each(function() {
// Get this clone's unique identifier
var uid = $(this).attr("data-uid");
// Get the real element corresponding to it, if it's
// still there
var elm = $("[data-uid=" + uid + "]")[0];
// Look for changes
if (!elm) {
display("Element with uid " + uid + ": Was removed");
}
else {
if (elm.id !== this.id) {
display("Element with uid " + uid + ": <code>id</code> changed, now '" + elm.id + "', was '" + this.id + "'");
}
if (elm.className !== this.className) {
display("Element with uid " + uid + ": <code>className</code> changed, now '" + elm.className + "', was '" + this.className + "'");
}
}
});
});
function display(msg) {
$("<p>").html(String(msg)).appendTo(document.body);
}
}, 0);
Earlier Answer
Assuming the server gives you the same text for the page every time it's asked, you can get the unaltered text client-side via ajax. That leaves us with the question of how to apply the id attributes to it.
If you need the original contents but not necessarily identical source (e.g., it's okay if tag names change case [div might become DIV], or attributes gain/lose quotes around them), you could use the source from the server (retrieved via ajax) to populate a document fragment, and apply the id values to the fragment at the same time you apply them to the main document. Then send the source of the fragment to the server.
Populating a fragment with the full HTML from your server is not quite as easy as it should be. Assuming html doesn't have any classes or anything on it, then:
var frag, html, prefix, suffix;
frag = document.createDocumentFragment();
html = document.createElement("html");
frag.appendChild(html);
prefix = stringFromServer..match(/(^.*<html[^>]*>)/);
prefix = prefix ? prefix[1] : "<!doctype html><html>";
suffix = stringFromServer.match(/(<\/html>\s*$)/);
suffix = suffix ? suffix[1] : "</html>";
html.innerHTML = stringFromServer.replace(/^.*<html[^>]*>/, '').replace(/<\/html>\s*$/, '');
There, we take the server's string, grab the outermost HTML parts (or use defaults), and then assign the inner HTML to an html element inside a fragment (although the more I think about it, the less I see the need for a fragment at all — you can probably just drop the fragment part). (Side Note: The part of the regular expressions above that identifies the start tag for the html element, <html[^>]*>, is one of those "good enough" things. It isn't perfect, and in particular will fail if you have a > inside a quoted attribute value, like this: <html data-foo="I have a > in me">, which is perfectly valid. Working around that requires much harder parsing, so I've assumed above that you don't do it, as it's fairly unusual.)
Then you can find elements within it via html.querySelector and html.querySelectorAll in order to apply your id attributes to them. Forming the relevant selectors will be great fun, probably a lot of positional stuff.
When you're done, getting back the HTML to send to the server looks like this:
var stringToSend = prefix + html.innerHTML + suffix;

Unique element ID, even if element doesn't have one

I'm writing a GreaseMonkey script where I'm iterating through a bunch of elements. For each element, I need a string ID that I can use to reference that element later. The element itself doesn't have an id attribute, and I can't modify the original document to give it one (although I can make DOM changes in my script). I can't store the references in my script because when I need them, the GreaseMonkey script itself will have gone out of scope. Is there some way to get at an "internal" ID that the browser uses, for example? A Firefox-only solution is fine; a cross-browser solution that could be applied in other scenarios would be awesome.
Edit:
If the GreaseMonkey script is out of scope, how are you referencing the elements later? They GreaseMonkey script is adding events to DOM objects. I can't store the references in an array or some other similar mechanism because when the event fires, the array will be gone because the GreaseMonkey script will have gone out of scope. So the event needs some way to know about the element reference that the script had when the event was attached. And the element in question is not the one to which it is attached.
Can't you just use a custom property on the element? Yes, but the problem is on the lookup. I'd have to resort to iterating through all the elements looking for the one that has that custom property set to the desired id. That would work, sure, but in large documents it could be very time consuming. I'm looking for something where the browser can do the lookup grunt work.
Wait, can you or can you not modify the document? I can't modify the source document, but I can make DOM changes in the script. I'll clarify in the question.
Can you not use closures? Closuses did turn out to work, although I initially thought they wouldn't. See my later post.
It sounds like the answer to the question: "Is there some internal browser ID I could use?" is "No."
The answer is no, there isn't an internal id you can access. Opera and IE (maybe Safari?) support .sourceIndex (which changes if DOM does) but Firefox has nothing of this sort.
You can simulate source-index by generating Xpath to a given node or finding the index of the node from document.getElementsByTagName('*') which will always return elements in source order.
All of this requires a completely static file of course. Changes to DOM will break the lookup.
What I don't understand is how you can loose references to nodes but not to (theoretical) internal id's? Either closures and assignments work or they don't. Or am I missing something?
Closure is the way to go. This way you'll have exact reference to the element that even will survive some shuffling of DOM.
Example for those who don't know closures:
var saved_element = findThatDOMNode();
document.body.onclick = function()
{
alert(saved_element); // it's still there!
}
If you had to store it in a cookie, then I recommend computing XPath for it (e.g. walk up the DOM counting previous siblings until you find element with an ID and you'll end up with something like [#id=foo]/div[4]/p[2]/a).
XPointer is W3C's solution to that problem.
A bit confused by the wording of your question - you say that you "need a string ID that [you] can use to reference that element later, " but that you "can't store the references in [your] script because when [you] need them, the GreaseMonkey script itself will have gone out of scope."
If the script will have gone out of scope, then how are you referencing them later?!
I am going to ignore the fact that I am confused by what you are getting at and tell you that I write Greasemonkey scripts quite often and can modify the DOM elements I access to give them an ID property. This is code you can use to get a pseudo-unique value for temporary use:
var PseudoGuid = new (function() {
this.empty = "00000000-0000-0000-0000-000000000000";
this.GetNew = function() {
var fourChars = function() {
return (((1 + Math.random()) * 0x10000)|0).toString(16).substring(1).toUpperCase();
}
return (fourChars() + fourChars() + "-" + fourChars() + "-" + fourChars() + "-" + fourChars() + "-" + fourChars() + fourChars() + fourChars());
};
})();
// usage example:
var tempId = PseudoGuid.GetNew();
someDomElement.id = tempId;
That works for me, I just tested it in a Greasemonkey script myself.
UPDATE: Closures are the way to go - personally, as a hard-core JavaScript developer, I don't know how you didn't think of those immediately. :)
myDomElement; // some DOM element we want later reference to
someOtherDomElement.addEventListener("click", function(e) {
// because of the closure, here we have a reference to myDomElement
doSomething(myDomElement);
}, false);
Now, myDomElement is one of the elements you apparently, from your description, already have around (since you were thinking of adding an ID to it, or whatever).
Maybe if you post an example of what you are trying to do, it would be easier to help you, assuming this doesn't.
UPDATE: Closures are indeed the answer. So after fiddling with it some more, I figured out why closures were initially problematic and how to fix it. The tricky thing with a closure is you have to be careful when iterating through the elements not to end up with all of your closures referencing the same element. For example, this doesn't work:
for (var i = 0; i < elements.length; i++) {
var element = elements[i];
var button = document.createElement("button");
button.addEventListener("click", function(ev) {
// do something with element here
}, false)
}
But this does:
var buildListener = function(element) {
return function(ev) {
// do something with event here
};
};
for (var i = 0; i < elements.length; i++) {
var element = elements[i];
var button = document.createElement("button");
button.addEventListener("click", buildListener(element), false)
}
Anyway, I decided not to select one answer because the question had two answers: 1) No, there are no internal IDs you can use; 2) you should use closures for this. So I simply upvoted the first people to say whether there were internal IDs or who recommended generating IDs, plus anyone who mentioned closures. Thanks for the help!
If you can write to the DOM (I'm sure you can). I would solve this like this:
Have a function return or generate an ID:
//(function () {
var idCounter = new Date().getTime();
function getId( node ) {
return (node.id) ? node.id : (node.id = 'tempIdPrefix_' + idCounter++ );
}
//})();
Use this to get ID's as needed:
var n = document.getElementById('someid');
getId(n); // returns "someid"
var n = document.getElementsByTagName('div')[1];
getId(n); // returns "tempIdPrefix_1224697942198"
This way you don't need to worry about what the HTML looks like when the server hands it to you.
If you're not modifying the DOM you can get them all by indexed order:
(Prototype example)
myNodes = document.body.descendants()
alert(document.body.descendants()[1].innerHTML)
You could loop through all of the nodes and give them a unique className that you could later select easily.
You can set the id attribute to a computed value. There is a function in the prototype library that can do this for you.
http://www.prototypejs.org/api/element/identify
My favorite javascript library is jQuery. Unfortunately jQuery does not have a function like identify. However, you can still set the id attribute to a value that you generate on your own.
http://docs.jquery.com/Attributes/attr#keyfn
Here is a partial snippet from jQuery docs that sets id for divs based on the position in the page:
$(document).ready(function(){
$("div").attr("id", function (arr) {
return "div-id" + arr;
});
});
You can generate a stable, unique identifier for any given node in a DOM with the following function:
function getUniqueKeyForNode (targetNode) {
const pieces = ['doc'];
let node = targetNode;
while (node && node.parentNode) {
pieces.push(Array.prototype.indexOf.call(node.parentNode.childNodes, node));
node = node.parentNode
}
return pieces.reverse().join('/');
}
This will create identifiers such as doc/0, doc/0/0, doc/0/1, doc/0/1/0, doc/0/1/1 for a structure like this one:
<div>
<div />
<div>
<div />
<div />
</div>
</div>
There are also a few optimisations and changes you can make, for example:
In the while loop, break when that node has an attribute you know to be unique, for example #id
Not reverse() the pieces, currently it is just there to look more like the DOM structure the ID's are generated from
Not include the first piece doc if you don't need an identifier for the document node
Save the identifier on the node in some way, and reuse that value for child nodes to avoid having to traverse all the way up the tree again.
If you're writing these identifiers back to XML, use another concatenation character if the attribute you're writing is restricted.
Use mouse and/or positional properties of the element to generate a unique ID.
In javascript, you could attach a custom ID field to the node
if(node.id) {
node.myId = node.id;
} else {
node.myId = createId();
}
// store myId
It's a bit of hack, but it'll give each and every node an id you can use. Of course, document.getElementById() won't pay attention to it.
You can also use pguid (page-unique identifier) for unique identifier generation:
pguid = b9j.pguid.next() // A unique id (suitable for a DOM element)
// is generated
// Something like "b9j-pguid-20a9ff-0"
...
pguid = b9j.pguid.next() // Another unique one... "b9j-pguid-20a9ff-1"
// Build a custom generator
var sequence = new b9j.pguid.Sequence({ namespace: "frobozz" })
pguid = sequence.next() "frobozz-c861e1-0"
http://appengine.bravo9.com/b9j/documentation/pguid.html
I 'think' I've just solved a problem similar to this. However, I'm using jQuery in a browser DOM environment.
var objA = $("selector to some dom element");
var objB = $("selector to some other dom element");
if( objA[0] === objB[0]) {
//GREAT! the two objects point to exactly the same dom node
}
OK, there is no ID associated to DOM element automatically.
DOM has a hierarchycal structure of elements which is the main information.
From this perspective, you can associate data to DOM elements with jQuery or jQLite. It can solve some issues when you have to bind custom data to elements.

Categories

Resources