Should I use setAttribute to add properties to an element? - javascript

I'm reading through elements on the page (of all kinds) with JavaScript and modifying them if needed. When I do modify one of the elements, I want to leave a marker behind to say that I modified it. Then later, I can read through the elements on the page and if I find that marker, I know it's one of the elements I modified and can restore it. Here's code that's working for me, but it was suggested I should be using setAttribute and getAttribute rather than what I'm doing:
//hide some elements, after first leaving a marker and saving orig. val
for(var i=0; i<elements.length; i++) {
if(should_i_hide_this_element(elements[i])) { //external logic unimportant
elements[i].wasModifiedByMe = true; //mark element as modified
elements[i].origViz = elements[i].style.visibility; //save visibility val
elements[i].style.visibility = "hidden"; //and give it a new val
}
}
The corresponding code to restore the element values is like this:
for(var i=0; i<elements.length; i++) {
if(elements[i].wasModifiedByMe) { //This is one I modified
elements[i].style.visibility = elements[i].origViz; //restore original val
elements[i].wasModifiedByMe = false; //mark as not modified now
}
}
The question is, should I be using setAttribute and getAttribute for my wasModifiedByMe boolean and my origViz properties? I don't believe currently that I need to use the attribute functions for my own added properties.
Following thread discussions below, I tried this test:
<!doctype html>
<html>
<body>
<div id="mydiv">DIV</div>
<script>
var elem = document.getElementById("mydiv");
elem.secretproperty = "not_seen_in_elements_tab_in_chrome_dev_tools";
elem.setAttribute("publicproperty","is_visible_in_elements_tab");
</script>
</body>
</html>
and in the elements tab in the chrome dev tools, I saw that mydiv was showing that publicproperty attribute as part of the div. It was NOT showing the secretproperty though.
It's as I thought. Using setAttribute is setting a HTML Attribute that is also reflected in the javascript object, but when NOT using setAttribute and adding a property to the javascript host object, the reflection does not go the other way (TO the HTML attributes). This is what I want. I don't want that every element I've hidden suddenly displays a wasHiddenByMe="true" attribute (although there is merit in that, I see that).

There are some issues with setting your own properties of DOM elements, covered by this article on perfectionkills.com. It talks about extending DOM element prototypes, but the sections
Host objects have no rules,
Chance of collisions (see also Don’t modify objects you don’t own) and
IE DOM is a mess
are relevant to you. Yet, if your are aware of the issues I think it is OK - it is the only way to accociate custom objects to DOM elements.
If you can use HTML5 techniques, you also may have a look at data attributes. For simple boolean markers they may be the superior approach.

For adding attributes to HTML Elements, the W3C standard is setAttribute. You can't use it for element.style.property.
However, setting attributes by simply doing element.*attribute* still works. It's just not a real standard, so it may be better to use setAttribute, but this is up to your preference.

Related

Why should HTML DOM properties be reflected into HTML DOM attributes as well?

It's said by this article that one of the important reasons for HTML properties to be reflected back to the DOM is because CSS selectors rely on attributes, but why's that? This could be done without the reflection based on the spec.
For people who don't know what I'm talking about, read below:
In browsers, CSS selectors rely on attributes to work.
#myButton[someAttribute] {
opacity: 0.5;
font-weight: bold
}
So in our JavaScript if we change the property of an element, eventually we have to reflect it to the HTML DOM as well like this:
// we have changed some property
myButton.someAttribute= true;
// but this is not adequate, we need to reflect as well
myButton.setAttribute('someAttribute', '');
so we get this:
<button id="myButton" someAttribute></button>
not this non-reflected button:
<button id="myButton"></button>
Not all DOM properties map to attributes. The ones that do reflect to and from attributes, do so to maintain parity with the document language — in this case, HTML, which only has a concept of attributes, which as you've correctly pointed out is relied on by Selectors.
If attribute selectors mapped directly to DOM properties without the DOM discriminating between attribute properties and other kinds of properties, then attribute selectors such as the following would match, even though none of these exist as attributes in HTML:
[classList]
[className]
[dataset]
[offsetLeft]
[offsetTop]
[offsetWidth]
[offsetHeight]
... as well as [someAttribute] matching elements with your non-existent someAttribute as a property even when you don't reflect it with setAttribute().
In fact, this is exactly why label[htmlFor] incorrectly matches label[for] elements in Internet Explorer 7, even though the for attribute in HTML is simply called for, not htmlFor — the DOM uses htmlFor to make up for the fact that for is a reserved word in many languages including JavaScript, the main DOM scripting language, preventing it from being used as a property ident.
DOM attributes and properties are not equivalent, but they're related.
Attributes are intended to be used to initialize DOM properties. When the HTML is parsed, all the attributes are used to initialize the corresponding DOM properties. If you later modify an attribute with setAttribute or removeAttribute, the corresponding property is also updated (similar to reloading the HTML with the new attribute).
But it doesn't go the other way. Updating a property doesn't change the corresponding attribute. This is why you can assign to the .value of an input, and see this reflected in the browser display, but when you look at the element in Developer Tools you still see the original value="whatever" attribute. In some cases this has special benefits -- when you click on the Reset button of a form, it resets all the value properties from their value attributes.
IMHO; Some attributes have a 1:1 mapping with their respective properties in the DOM. The reflection is automatically made for common attributes like id. You can also define your own attributes (your HTML will be considered invalid, but you can use the doctype to validate them). My guess is that due to this uncertainty created by rogue attributes. They preferred to map only the attribute:property which has predictable behaviour and usage. You can still use your custom attributes in your css but you're in manual mode. You got to keep your s**t together and reflect them yourself. This far west(freedom) mentality is one the things that made web tech so popular and easy to use. You can do things as you see fit. I do not recommend it for maintainability reasons but you could.
Your example uses a button, but the article is using the disabled property but with something other than a button. On a button, the browser will automatically reflect changes to the disabled property onto the attribute, and vice versa, but this doesn't happen with all elements. Change your example to use a div and you'll see that you'd need to manually coordinate the two if desired.
Or for custom attributes, use data- attributes instead. If you delete the property from my_element.dataset, I'm pretty sure the attribute will be deleted too.
This is to keep the HTML and DOM synchronized, because at some point CSS selectors will be checking the DOM element and relying on the attributes to be accurate.
If the DOM isn't accurate, then the CSS won't be accurate either. What if HTML didn't bother to reflect attributes back to the DOM?
Let's say the text of an input field is initially black, and you want the text to be red when it is disabled. Now let's say the user did something and a function you wrote disabled the input field through javascript.
If HTML didn't reflect that 'disabled' attribute back to the DOM, CSS would NEVER KNOW that the element was disabled.
So the text color would never be changed to red. Remember, CSS checks and relies on DOM attributes. If HTML doesn't change the DOM attributes, for all CSS cares about, nothing has changed so everything will remain the same.
For a less technical analogy, let's say CSS is Batman, HTML is Gotham Police Department, an Attribute is the bat-signal, and the DOM is the sky.
Batman(css) constantly checks the sky(dom) to see if his bat-signal light(attribute) is being shown by the Gotham Police Department(html). If there was some event(an attribute changed) which happened in Gotham where the Gotham Police Department(html) needed Batman(css) to help, but they just didn't bother to send him an update through the sky(dom) with the bat-signal(attribute update), Batman would never know there was a job that needs to be done.
I mean he's an awesome super hero so he would eventually find out but sadly, CSS is no Batman.
The article speaks about custom elements, and takes the example of a <div> element with it's natural behaviour for some properties like hidden or disabled.
So, first of all, don't take the sentence you mention as a directive from your god, because it's not.
Simply, if you have an application with some css using the disasbled property for specific styling, be aware that, if you want to :
create some custom elements
manipulate their attributes through Javascript, including disasbled
see the css applied for disasbled property of custom elements you are manipulating
Then, yes, you'll need to reflect back to DOM
Well, this is the first question I'm answering but I'll try either way.
To be honest, it's kinda hard to tell what you're asking but if you're looking to reflect HTMLElement property changes back on the DOM (via attributes). Then here's the code (using HTMLElement's):
// Defines a new property on an Object.
Object.defineProperty(HTMLElement.prototype, "someAttribute", {
// Configurable
configurable: true,
// Enumerable
enumerable: true,
/* Getter
(Allows you get the value like this =>
element.someAttribute // returns the value of "someAttribute"
)
*/
get: function() {
return this.getAttribute("someAttribute")
},
/* Setter
(Allows you to modify/ update the value like this =>
element.someAttribute = "lorem ipsum"
)
*/
set: function(data) {
this.setAttribute("someAttribute", data)
}
})
Hope this answered your question.

What is the difference between querySelectorAll and getElementsByTagName?

I was wondering about two different syntax of selecting element in JavaScript.
suppose if I want to select all divs from current document then:
var divs = document.getElementsByTagName("div");
console.log("There are "+divs.length+" Divs in Document !");
Will work fine. But there is also another way of doing so, like:
var divs = document.querySelectorAll("div");
console.log("There are "+divs.length+" Divs in Document !");
When both of them works in the same way. What's the difference between them ?
Which one is faster?
Why?
How both works?
Thanks in advance. I've seen the questions like this but they didn't satisfied the need.
Most answeres are wrong. Nicolae Olariu is the only, who answered correcly
Which one is faster? Why?
are not the questions. The real question "How it works?"
The main difference is in this example:
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Yandex</title>
</head>
<body>
Яндекс,
Yandex
</body>
<script>
var elems1 = document.getElementsByTagName('a'), // return 2 lements, elems1.length = 2
elems2 = document.querySelectorAll("a"); // return 2 elements, elems2.length = 2
document.body.appendChild(document.createElement("a"));
console.log(elems1.length, elems2.length); // now elems1.length = 3!
// while elems2.length = 2
</script>
</html>
Because querySelectorAll returns a static (not live) list of elements.
Selections
getElementsByTagName only selects elements based on their tag name. querySelectorAll can use any selector which gives it much more flexibility and power.
Return value
gEBTN returns a live node list.
qSA returns a static node list.
Live node lists can be useful (you can query once, store the value, and have it update as the DOM changes) but are responsible for a lot of confusion such as the example in this question.
Usually a static list is easier to deal with.
Support
See caniuse for gEBTN and qSA.
gEBTN has more support, but qSA has support in all browsers that are relevant for most use cases today.
Performance
You probably shouldn't care. These functions are unlikely to be a bottleneck in your code.
I've seen conflicting reports about which is faster. It likely varies between browsers anyway.
From MDN:
element = document.querySelector(selectors);
Returns the first element within the document (using depth-first pre-order traversal of the document's nodes) that matches the specified group of selectors.
elements = element.getElementsByTagName(tagName)
Returns a list of elements with the given tag name. The subtree underneath the specified element is searched, excluding the element itself. The returned list is live, meaning that it updates itself with the DOM tree automatically. Consequently, there is no need to call several times element.getElementsByTagName with the same element and arguments.
querySelector also supports other CSS selectors such as "#id" to get an element by id, and "input[type=text]" to get all input elements with a type=text attribute. See here for more details.
They would probably be about equally fast for simple queries like the one you asked about, but for advanced CSS selectors it is likely much faster (not to mention much less code to write) to use querySelectorAll than applying some manual filtering yourself, which is why libraries like jQuery use querySelectorAll when the browser supports it.
Here is an example about the difference between querySelector and getElementsByTagName.
In this example,the writer choose the querySelector to solve the problem.
The getElementsByTagName also returns a live nodeList, and when we append the links to the in-memory unordered list, the link is removed from the document, and the length of the collection is affected.
So
if(you don't want to change the NodeList during the follow-up script work){
"use querySelectorAll"}
else if(you want to change the NodeList during the follow-up script work) {
"use getElementsByTagName"
}
And you can have a try to use getElementsByTagName in this example,you will see it can't work.
Selectors
Apart from the return values already covered in another answers one other key difference is that getElementBy*TagName ( or -Id -Class ) and querySelector/querySelectorAll is that the latter accepts a selector where the others a tag, id or class. With querySelector() you can do things like:
document.querySelectorAll('p.my16')
which is not possible with getElementByTagName
ForEach
If one needs a foreach on the result of a getElements* function a simple trick (spread) will allow that.
[...document.getElementsByClassName('my16')].forEach(e=>console.log(`hallo ${e}`))
Btw. I agree with the poster that performance is highly irrelevant. It makes sense to focus on the use case at hand.
Both the selectors give different outputs. Check the image.
In this example:
<html>
<head>
<meta charset="utf-8">
<title>Yandex</title>
</head>
<body>
Яндекс,
Yandex
</body>
<script>
var elems1 = document.getElementsByTagName('a'), // return 2 lements, elems1.length = 2
elems2 = document.querySelectorAll("a"); // return 2 elements, elems2.length = 2
document.body.appendChild(document.createElement("a"));
console.log(elems1.length, elems2.length); // now elems1.length = 3!
// while elems2.length = 2
</script>
</html>
the element that is created is placed after the script tag, and cannot be read by querySelector. Only the getElementsByTagName can find the new element.

javascript find node without id

Due to a limitation of the Javascript library I'm using, I can't assign an id to a <div>. Unfortunately, I don't know how to attach a Tooltip object from Tipped, a Javascript tooltip library, to the element without an id. I don't know if this is possible, but I'm trying to find the object via other means which will hopefully allow me to modify the id.
The element I'm looking for is a button on a toolbar, but it's not an HTML button. It's a <div> that has CSS styles and Javascript events assigned to make it look and feel like a button. I know the class name of the parent and I know the id of the grandparent <div>, but that's as much as I know. Part of the issue is that there doesn't seem to be a good reference for how to iteratively operate on HTML objects once you get a reference to them. I've seen plenty of examples like this:
var x = document.getElementsById("asdf")
but no follow-up code showing how to actually do anything. What's in x? What methods does it have? I know of innerHTML and innerTEXT, but I can't figure out if they apply to x, a child of x, or ???. The Chrome console has helped a little bit, but I'm basically lost.
This is the relevant code for my button:
As you can see, there is no id on the Export button, but the parent has a class name and the grandparent has an id. The other snag is that the grandparent's id isn't static. It always starts with "dhxtoolbar" and there is only one toolbar on the page, but I haven't been able to make a regex search find the toolbar.
Ultimately, I'd like to be able to attach a Tipped tooltip to the Export button. I think Tipped requires an id, but maybe it doesn't. Regardless, I'd like to understand more about how to iterate through the DOM and, as a bonus, figure out how or if I can change the id of an element on a live page. Thanks.
Tipped actually accepts any CSS selector as an argument. If the class is unique, you could target it that way:
Tipped.create('.dhx_toolbar_btn', 'some tooltip text');
Or if the class isn't unique, you could try target it via the tree structure. Made up example:
Tipped.create('.header .sidebar .dhx_toolbar_btn', 'some tooltip text');
I noticed in your html that the buttons have empty title attributes (or maybe your inspector just added them). If you can set the title attribute for the buttons Tipped will pick it up automatically. Example:
<div class="dhx_toolbar_btn" title="test title">
You would then only have to use:
Tipped.create('.dhx_toolbar_btn');
And Tipped will automatically pick up the title and use it.
This is what I was trying to have explained:
var Obj = document.getElementsByClassName("classname");
var ObjChildren = Obj[0].getElementsByTagName("tag")
var searchText = "string";
for (var i = 0; i < ObjChildren.length; i < i++) {
if (ObjChildren[i].innerHTML == searchText) {
console.log(ObjChildren[i].innerHTML);
}
}

How can I access a particular div on a page which has the same id in two places?

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?

JavaScript & copy style

I am copying a table cell with javascript.
It works fine, just that it doesn't copy the style.
I wanted to copy like below, but that didn't work.
newCell.style=oldCell.style;
So I figured that for my text-align, I have to copy it like this:
newCell.style.textAlign=oldCell.style.textAlign;
That worked, but whenever I add a new style item, I have to remember to register it here.
So, my problem now is how can I loop over the style and copy every item in there?
With chrome, I managed to do it like this:
var strAttribute = GetDomNameFromAttributeName(oRow.cells[1].style[0]);
var styletocopy = eval('oRow.cells[1].style.'+strAttribute);
eval("newCell.style."+strAttribute+"='"+styletocopy+"'"); // //newCell.style.textAlign='center';
But that doesn't work with IE. Haven't tested it with FF, but assume chrome compatibiity.
Is there any way to loop over the style elements in IE?
Or is there any better way to copy all style elements?
eval('oRow.cells[1].style.'+strAttribute)
Never use eval like this(*). In JavaScript you can access a property whose name is stored in a string using square brackets. object.plop is the same as object['plop']:
to.style[name]= from.style[name];
(*: never use eval at all if you can help it. There are only a few very specific and rare occasions you need it.)
Is there any way to loop over the style elements
The style object is supposed to support the DOM Level 2 CSS CSSStyleDeclaration interface. You could loop over the rules and apply them to another element like this:
for (var i= from.style.length; i-->0;) {
var name= from.style[i];
to.style.setProperty(name,
from.style.getPropertyValue(name),
priority= from.style.getPropertyPriority(name)
);
}
in IE?
No, IE does not support the whole CSSStyleDeclaration interface and the above won't work. However there is a simpler way not involving looping that will work on IE and the other browsers too:
to.style.cssText= from.style.cssText;
As simple as that! IE doesn't quite preserve the CSS text the way it should, but the difference doesn't matter for simple inline style copying.
However, as Pikrass said (+1), if you are copying a whole element and not just the styles, cloneNode is by far the most elegant way to do that.
You can copy a DOM Element with all its content (including attributes) with .cloneNode(true) :
var clonedTr = document.getElementById('id').cloneNode(true);
Then clonedTr is an exact copy of the tr #id.
The "true" means you want to copy the content of the element.
To copy all style elements from one node to another you can use
newCell.setAttribute('style', oRow.cells[1].getAttribute('style'))

Categories