trying to determine a decent, cross browser method for obtaining attributes with javascript? assume javascript library use (jQuery/Mootools/etc.) is not an option.
I've tried the following, but I frequently get "attributes" is null or not an object error when IE tries to use the "else" method. Can anyone assist?
<script type="text/javascript">
//...
getAttr: function(ele, attr) {
if (typeof ele.attributes[attr] == 'undefined'){
return ele.getAttribute(attr);
} else {
return ele.attributes[attr].nodeValue;
}
},
//...
</script>
<div>
Link
</div>
using the above html, in each browser, how do I getAttr(ele, 'href')? (assume selecting the ele node isn't an issue)
For the vast majority of cases you can simply use the built in getAttribute function.
e.g.
ele.getAttribute(attr)
According to QuirksMode this should work on all major browsers (IE >= 6 included), with a minor exception:
In IE5-7, accessing the style attribute gives an object, and accessing the onclick attribute gives an anonymous function wrapped around the actual content.
With regard to your question's update, you could try this.
It may be overkill, but if getAttribute() and the dot notation don't return a result, it iterates through the attributes object to try to find a match.
Example: http://jsfiddle.net/4ZwNs/
var funcs = {
getAttr: function(ele, attr) {
var result = (ele.getAttribute && ele.getAttribute(attr)) || null;
if( !result ) {
var attrs = ele.attributes;
var length = attrs.length;
for(var i = 0; i < length; i++)
if(attrs[i].nodeName === attr)
result = attrs[i].nodeValue;
}
return result;
}
};
var result = funcs.getAttr(el, 'hash');
It's up to you to do some cross-browser testing, though. :o)
Using ele.attributes, you need to access them by index, as in:
ele.attributes[0].nodeName; // "id" (for example)
ele.attributes[0].nodeValue; // "my_id" (for example)
Trying to pass attributes an attribute name appears to return a value whose typeof is object, so your else code is running even though ele.attributes[attr] doesn't give you the value you want.
You are trying to access properties of ele before you've established if those properties exist. Try this kind of evidence chain:
if (ele.attributes && ele.attributes[attr] && typeof ele.attributes[attr] == 'undefined')
etc.
Related
How can I check if one DOM element is a child of another DOM element? Are there any built in methods for this? For example, something like:
if (element1.hasDescendant(element2))
or
if (element2.hasParent(element1))
If not then any ideas how to do this? It also needs to be cross browser. I should also mention that the child could be nested many levels below the parent.
You should use Node.contains, since it's now standard and available in all browsers.
https://developer.mozilla.org/en-US/docs/Web/API/Node.contains
Update: There's now a native way to achieve this. Node.contains(). Mentioned in comment and below answers as well.
Old answer:
Using the parentNode property should work. It's also pretty safe from a cross-browser standpoint. If the relationship is known to be one level deep, you could check it simply:
if (element2.parentNode == element1) { ... }
If the the child can be nested arbitrarily deep inside the parent, you could use a function similar to the following to test for the relationship:
function isDescendant(parent, child) {
var node = child.parentNode;
while (node != null) {
if (node == parent) {
return true;
}
node = node.parentNode;
}
return false;
}
I just had to share 'mine'.
Although conceptually the same as Asaph's answer (benefiting from the same cross-browser compatibility, even IE6), it is a lot smaller and comes in handy when size is at a premium and/or when it is not needed so often.
function childOf(/*child node*/c, /*parent node*/p){ //returns boolean
while((c=c.parentNode)&&c!==p);
return !!c;
}
..or as one-liner (just 64 chars!):
function childOf(c,p){while((c=c.parentNode)&&c!==p);return !!c}
and jsfiddle here.
Usage:
childOf(child, parent) returns boolean true|false.
Explanation:
while evaluates as long as the while-condition evaluates to true.
The && (AND) operator returns this boolean true/false after evaluating the left-hand side and the right-hand side, but only if the left-hand side was true (left-hand && right-hand).
The left-hand side (of &&) is: (c=c.parentNode).
This will first assign the parentNode of c to c and then the AND operator will evaluate the resulting c as a boolean.
Since parentNode returns null if there is no parent left and null is converted to false, the while-loop will correctly stop when there are no more parents.
The right-hand side (of &&) is: c!==p.
The !== comparison operator is 'not exactly equal to'. So if the child's parent isn't the parent (you specified) it evaluates to true, but if the child's parent is the parent then it evaluates to false.
So if c!==p evaluates to false, then the && operator returns false as the while-condition and the while-loop stops. (Note there is no need for a while-body and the closing ; semicolon is required.)
So when the while-loop ends, c is either a node (not null) when it found a parent OR it is null (when the loop ran through to the end without finding a match).
Thus we simply return that fact (converted as boolean value, instead of the node) with: return !!c;: the ! (NOT operator) inverts a boolean value (true becomes false and vice-versa).
!c converts c (node or null) to a boolean before it can invert that value. So adding a second ! (!!c) converts this false back to true (which is why a double !! is often used to 'convert anything to boolean').
Extra:
The function's body/payload is so small that, depending on case (like when it is not used often and appears just once in the code), one could even omit the function (wrapping) and just use the while-loop:
var a=document.getElementById('child'),
b=document.getElementById('parent'),
c;
c=a; while((c=c.parentNode)&&c!==b); //c=!!c;
if(!!c){ //`if(c)` if `c=!!c;` was used after while-loop above
//do stuff
}
instead of:
var a=document.getElementById('child'),
b=document.getElementById('parent'),
c;
function childOf(c,p){while((c=c.parentNode)&&c!==p);return !!c}
c=childOf(a, b);
if(c){
//do stuff
}
Another solution that wasn't mentioned:
Example Here
var parent = document.querySelector('.parent');
if (parent.querySelector('.child') !== null) {
// .. it's a child
}
It doesn't matter whether the element is a direct child, it will work at any depth.
Alternatively, using the .contains() method:
Example Here
var parent = document.querySelector('.parent'),
child = document.querySelector('.child');
if (parent.contains(child)) {
// .. it's a child
}
You can use the contains method
var result = parent.contains(child);
or you can try to use compareDocumentPosition()
var result = nodeA.compareDocumentPosition(nodeB);
The last one is more powerful: it return a bitmask as result.
Take a look at Node#compareDocumentPosition.
function isDescendant(ancestor,descendant){
return ancestor.compareDocumentPosition(descendant) &
Node.DOCUMENT_POSITION_CONTAINS;
}
function isAncestor(descendant,ancestor){
return descendant.compareDocumentPosition(ancestor) &
Node.DOCUMENT_POSITION_CONTAINED_BY;
}
Other relationships include DOCUMENT_POSITION_DISCONNECTED, DOCUMENT_POSITION_PRECEDING, and DOCUMENT_POSITION_FOLLOWING.
Not supported in IE<=8.
I came across a wonderful piece of code to check whether or not an element is a child of another element. I have to use this because IE doesn't support the .contains element method. Hope this will help others as well.
Below is the function:
function isChildOf(childObject, containerObject) {
var returnValue = false;
var currentObject;
if (typeof containerObject === 'string') {
containerObject = document.getElementById(containerObject);
}
if (typeof childObject === 'string') {
childObject = document.getElementById(childObject);
}
currentObject = childObject.parentNode;
while (currentObject !== undefined) {
if (currentObject === document.body) {
break;
}
if (currentObject.id == containerObject.id) {
returnValue = true;
break;
}
// Move up the hierarchy
currentObject = currentObject.parentNode;
}
return returnValue;
}
Consider using closest('.selector')
It returns null if neither element nor any of its ancestors matches the selector. Alternatively returns the element which was found
try this one:
x = document.getElementById("td35");
if (x.childElementCount > 0) {
x = document.getElementById("LastRow");
x.style.display = "block";
}
else {
x = document.getElementById("LastRow");
x.style.display = "none";
}
TL;DR: a library
I advise using something like dom-helpers, written by the react team as a regular JS lib.
In their contains implementation you will see a Node#contains based implementation with a Node#compareDocumentPosition fallback.
Support for very old browsers e.g. IE <9 would not be given, which I find acceptable.
This answer incorporates the above ones, however I would advise against looping yourself.
i try to detect null this way
if(!$(this))
{
alert('here is null');
}
OR
if($(this)===null)
{
alert('here is null');
}
but still no luck.
here is partial code
$elements.each(function(){
//alert($(this).html());
var $item = $('<li />').append($(this));
if(!$(this))
{
alert('here is null');
}
//alert($item.text());
$list.append($item);
});
anyone can see full code from here https://jsfiddle.net/tridip/41s1pq3a/12/
edit
i was iterate in td's content. td has some link and text. i was trying to wrap each text and link inside li. so iterate this below way. code is working but some time it is also showing null which i need to detect.
i am looking for way not consider any null or empty.
here is the code
var $elements = $('.webgrid-footer td').contents()
.filter(function() {
return this.nodeType === 3 || this.nodeType === 1; // 1 means elements, 3 means text node
});
var $list = $('<ul />');
$elements.each(function(){
//alert($(this).html());
var $item = $('<li />').append($(this));
if(this===null)
{
alert('here is null');
}
//alert($item.text());
$list.append($item);
});
//alert($list.html());
$('#dv').append($list);
see this line var $item = $('<li />').append($(this)); it is getting some time empty or null which i do not want tp consider. if anyone knows it how to handle this situation then share the idea. thanks
$(null) is an empty jQuery object, not null. And all objects are truthy.
If you want to test null, use this === null. You don't need jQuery for this.
However, I don't see why do you expect this to be null sometimes. Instead, it seems you want to ignore whitespace text nodes.
var $elements = $('.webgrid-footer td').contents().filter(function() {
return (this.nodeType === 3 && $.trim(this.nodeValue) !== '')
|| this.nodeType === 1;
});
$(this) will never be either null or falsey, because jQuery always returns an object reference, which is not null or falsey.
In strict mode, it's possible for this (not $(this)) to be null. In loose mode, it isn't; attempts to make this be null will cause this to be a reference to the global object.
So it may be that you want to test this, not $(this). But only in strict mode. In loose mode, bizarrely, you'd want if (this == window) to be your "null" test.
Having said that, $elements is clearly meant to be a jQuery object, and I'm not immediately thinking of a way to to create a jQuery objct with nulls in through the public API. (It's easy if you muck about with the internals...)
I wrote a JavaScript function that will return a "list" of elements that has Id that start with some value:
function getElementsWithIdPrefix(prefix){
if (document.querySelectorAll){
return document.querySelectorAll('*[id^="' + prefix + '"]');
} else {
// none modern browsers support
var elements = document.getElementsByTagName('*');
var relevantElements = [];
for (var i = 0; i < elements.length; i++){
if (element.id && element.id.indexOf(prefix) !== -1){
relevantElements.push(element);
}
}
return relevantElements;
}
}
As you can see my function will return different types depends on browser support for document.querySelectorAll and I have two question regarding this:
Is this bad? I mean - In JavaScript typing system does it consider as a code smell or bad practice?
How can I create Node objects for each Element by my self and construct a new NodeList containing these elements to return?
Is this bad?
Probably, but it's probably a matter of opinion.
How can I create Node objects for each Element by my self and construct a new NodeList containing these elements to return?
You can turn your QSA result into a true array like this:
return Array.prototype.slice.call(document.querySelectorAll('*[id^="' + prefix + '"]'));
That way, you're returning an array in both cases.
Side note: You have to look pretty hard to find a browser that doesn't have QSA. It's on all modern browsers, and also IE8.
Side note 2: This line
if (element.id && element.id.indexOf(prefix) !== -1){
...doesn't look for a prefix. It looks for a substring match anywhere in the ID. For a prefix, use === 0 (or element.id.substring(0, prefix.length) === prefix).
I don't have much experience in JavaScript, so far I have this:
function loop() {
var authorDivs = document.getElementById('ctl00_MainContent_MCPObjectInfo_dvCreatorView').getElementsByTagName("div");
for (var i = 0; i < authorDivs.length; i++) {
var divOfDiv = authorDivs[i].getElementsByTagName("div");
if (typeof divOfDiv.item(i) === 'undefined' || divOfDiv.item(i) === null) {
console.log("This is undefined or null");
}
else {
var realDivs = divOfDiv.item(i);
realDivs.item(i).textContent = "please work plz";
}
}
}
I get the following error from the console in FireFox: TypeError: realDivs is undefined on this line: realDivs.item(i).innerHTML = "please work plz";
Essentially what I have (in my mind) is a loop that goes through authorDivs and gets all of the divs within those divs and saves them in divOfDiv. I then check to see if the divs in divOfDiv are undefined or null, if they are not then those divs get saved in a variable realDivs which I then use to edit the innerHTML. That's what I'd ideally like to see happen, what is causing the error? What am I doing wrong?
Note: I do not have access to jQuery but only JavaScript.
Edit: I've added the changes suggested below and its fixed that -- thanks! But I'm now getting the following error: TypeError: realDivs.item is not a function
What is causing that? And on another note how do I know when I'm dealing with an array and when I'm dealing with an HTMLCollection? Do you just assume? I've never used a loosely typed language before so its new to me.
Well, you'll need to move that code inside the conditional block that is supposed to prevent it! Also, || "null" is not going to work as you expect, you'll need to check for || divOfDiv.item(i) === null explicitly.
So try
for (var i = 0; i < authorDivs.length; i++) {
var divOfDiv = authorDivs[i].getElementsByTagName("div");
if (divOfDiv.item(i) == null) {
console.log("This is undefined or null");
} else {
var realDivs = divOfDiv.item(i)
realDivs.item(i).innerHTML = "please work plz";
console.log(divOfDiv.item(i));
}
}
However, that still doesn't really work for two reasons:
The i index you use to access the i-th divOfDiv comes from the iteration over authorDivs - hardly what you want. Instead, use a second loop over all divOfDivs.
Your realDivs variable does hold a single <div>, which does not have an .item method. You'd just directly access its .innerHTML property.
So you should use
var authorDivs = document.getElementById('authorView').getElementsByTagName("div");
for (var i=0; i<authorDivs.length; i++) {
var divsOfDiv = authorDivs.item(i).getElementsByTagName("div");
for (var j=0; j<divsOfDiv.length; j++) {
var realDiv = divsOfDiv.item(j);
realDiv.innerHTML = "please work plz";
console.log(realDiv);
}
}
it will happen in case when your if (typeof divOfDiv.item(i) === 'undefined' || 'null') returns true. Then you never initialize realDivs (what would happen if condition was falsy). Later you try to call item function on that unitialized object
There are two problems in the code.
comparing DOM object with 'undefined' and null. If div tag is not available in authorDivs[i], it will return empty DOM array. So, comparision of empty DOM array with undefined and null is not good approach. We can use array length property for doing validation.
divOfDiv = authorDivs[i].getElementsByTagName("div");
if(divOfDiv.length > 0) { console statement}
As item(i) is already return single DOM element, item(i) of "realDivs" variable is not proper approach. In addition to this, innerHTML method needs to be used after validating whether realDivs contains DOM element. Please update the code as below.
var realDivs = divOfDiv.item(i);
realDivs ? (realDivs.innerHTML = "please work plz"): null;
Note : item(i) will return null if DOM is not available.
I am trying to set a number as my data type dynamically using .data() method in jQuery, but so far no luck. This works using the .attr() method as I have listed in below. Why does the .data() method not work with numbers?
var container = $(this).find('#container'); // element which should have the data
Attempt 1:
container.data(24, "opacity:0;");
Attempt 2:
container.data("24", "opacity:0;");
The following code works using .attr():
container.attr("data-123", 1223);
My personal code:
function loader($div, $page) {
$div.load(siteURL + $page + '/ #container', function() {
var container = $(this).find('#container');
container.data("24", "opacity:0;");
container.attr("data-24", "opacity:0;"); //this works...
});
}
loader($('section#about'), 'about');
UPDATE: Here is a jsFiddle
Historically jQuery supported the data() method by keeping track of values set using it in a separate data structure. This allowed you do store things like objects using the API.
While retrieving data, the API will check both the data attribute as well as well as its internal store for the value.
Setting, however still goes straight to the internal store.
Since the question has changed significantly:
$.data will fail when sending a number.
Doing this in the console you will see the following (none of these affect the markup of the element itself):
// Error
$('div').data(24, 'foo')
TypeError: Object 24 has no method 'replace'
// Success
$('div').data("24", 'foo')
b.fn.b.init[966]
$('div').data("24")
"foo"
// Success
$('div').data("24", 24)
b.fn.b.init[966]
$('div').data("24")
24
None of these will affect a data attribute on the element itself. The end result of the markup will be:
<div>Hello</div>
If you are looking to set a data-xxx attribute on the element, or any attribute for that matter, an elements attribute must begin with an alpha character:
// Error
$('div').attr("24", "opacity:0")
InvalidCharacterError: An invalid or illegal character was specified, such as in an XML name.
// Success
$('div').attr("data-24", "opacity:0")
b.fn.b.init[966]
The end result of the successful call will be:
<div data-24="opacity:0">Hello</div>
skrollr does not use jQuery's .data function it parses the DOM elements attribute list which is why using .attr("data-24" works as the attribute is added to the DOM attribute list
.data("24","somevalue") does not update the DOM elements attribute list,
.attr("data-24","somevalue") however does update the DOM elemetns attribute list which allows skrollr to parse the new style.
FROM SKROLLR.JS
Starting at Line 343:
//Iterate over all attributes and search for key frame attributes.
var attributeIndex = 0;
var attributesLength = el.attributes.length;
for (; attributeIndex < attributesLength; attributeIndex++) {
var attr = el.attributes[attributeIndex];
if(attr.name === 'data-anchor-target') {
anchorTarget = document.querySelector(attr.value);
if(anchorTarget === null) {
throw 'Unable to find anchor target "' + attr.value + '"';
}
continue;
}
//Global smooth scrolling can be overridden by the element attribute.
if(attr.name === 'data-smooth-scrolling') {
smoothScrollThis = attr.value !== 'off';
continue;
}
//Global edge strategy can be overridden by the element attribute.
if(attr.name === 'data-edge-strategy') {
edgeStrategy = attr.value;
continue;
}
var match = attr.name.match(rxKeyframeAttribute);
if(match === null) {
continue;
}
var constant = match[1];
//If there is a constant, get it's value or fall back to 0.
constant = constant && _constants[constant.substr(1)] || 0;
//Parse key frame offset. If undefined will be casted to 0.
var offset = (match[2] | 0) + constant;
var anchor1 = match[3];
//If second anchor is not set, the first will be taken for both.
var anchor2 = match[4] || anchor1;
var kf = {
offset: offset,
props: attr.value,
//Point back to the element as well.
element: el
};
keyFrames.push(kf);
//"absolute" (or "classic") mode, where numbers mean absolute scroll offset.
if(!anchor1 || anchor1 === ANCHOR_START || anchor1 === ANCHOR_END) {
kf.mode = 'absolute';
//data-end needs to be calculated after all key frames are know.
if(anchor1 === ANCHOR_END) {
kf.isEnd = true;
} else {
//For data-start we can already set the key frame w/o calculations.
//#59: "scale" options should only affect absolute mode.
kf.frame = offset * _scale;
delete kf.offset;
}
}
//"relative" mode, where numbers are relative to anchors.
else {
kf.mode = 'relative';
kf.anchors = [anchor1, anchor2];
}
}
container.data("24", "opacity:0;"); works right? First parameter must be a String (as per jQuery spec).
Because the data() method expects a string.
Hence I guess it'll be breaking if you pass it a number!
Can you try using the jQuery object itself:
var container = $(this).find('#container');
jQuery.data(container, "24", "opacity:0;");
alert("24 is equal to: " + jQuery.data(container, "24") );
You should note this will not affect the DOM as it uses jQuery's local storage.
JSFiddle: http://jsfiddle.net/markwylde/8Q5Yh/1/
container.data("24") actually works for me.
It depends on the version of jQuery too. When you call .data as a setter, all versions will call either .split or .replace which are String methods, not Number methods. Using .data(24) as an accessor seems to work past version 1.8.
This may also be browser dependent as dataset is not available in some browsers.
My advice would be to use a descriptive name for the data rather than just a number (unless you're talking about form 24 or something, but then why not use form24?)
EDIT: Using .data does not alter the HTML if the dataset attribute is available on the element. Using .attr always sets an attribute which will alter the HTML. This has nothing to do with using strings vs. numbers.