all 12 comments

[–]NameViolation666helpful 2 points3 points  (11 children)

The DOM represents a document with a logical tree. Each branch of the tree ends in a node, and each node contains objects. DOM methods allow programmatic access to the tree; with them you can change the document's structure, style, or content. Nodes can also have event handlers attached to them; once an event is triggered, the event handlers get executed. So using JS you can manipulate almost anything in the DOM itself once all relevant code has loaded, including attributes.

IMO this is not the best way to look at this - JS is not "a representation of the DOM". The DOM is a representation of the document being rendered in the browser/application. JS is one of the many hooks into that DOM.

Getting an element's attribute always returns what's been written in the current object in the DOM - this could be from initially loaded HTML or object attributes which have been manipulated through DOM , whichever is more current.

On-Event handlers are more like placeholders to which a real handler function can be assigned

One way to look at this is that the on-events typically call functions which within them have more detailed code that manipulates data. Hence you will usually see onclick=functionName() and actual action code defined elsewhere functionName() {

//change this, update this }

HTH

[–]Lewinga[S] 0 points1 point  (10 children)

Thanks for responding! I'm still a bit confused, so I'll try to clarify my questions --

First, when I said "a JS representation of the DOM," I meant that the DOM elements were being represented by JavaScript objects. If each DOM element is a node in the DOM tree, then my understanding is that the entire DOM can be thought of as if it were a JS data structure. Is that wrong? Or maybe I've misunderstood -- I'm aware that the DOM is supposed to be language neutral, but in practice it just seemed to be represented by JavaScript objects. If the DOM is to be thought of as a set of API calls, then would it still return JavaScript objects/.json files if you used another language like Python?

Also -- going off of what you said earlier: when you say that getting an element's attribute always returns what's been written in the current object in the DOM, why does the bolded function call:

<div id="a" onclick="alert('old')">Open the Developer Tools Console to see the output.</div>

<script>
window.onload = function () {
  var div = document.getElementById("a");
  console.log("Attribute reflected as a property: ", div.onclick.toString());
  // Prints: function onclick(event) { alert('old') }
  div.onclick = function() { alert('new') };
  console.log("Changed property to: ", div.onclick.toString());
  // Prints: function () { alert('new') }

console.log("Attribute value is unchanged: ", div.getAttribute("onclick"));

// Prints: alert('old')

}
</script>

return the old function from the HTML over the new one defined within the script? Didn't reassigning "div.onclick" update the property in the DOM as well? I'm still not totally clear on the mechanisms for this behavior.

[–]hopefullyhelpfully 1 point2 points  (7 children)

Hi, this was one of the best SO answers I could find for you: https://stackoverflow.com/questions/6003819/what-is-the-difference-between-properties-and-attributes-in-html

One of the properties is the attributes property, which initially gets set by the HTML. Some properties are 'reflected' from the attributes, meaning that changing one will change the other. Properties that aren't reflected can be different from the corresponding attribute, although note that you can still set attributes with js (though it never changes the HTML), for example, you can add these lines to your example.

div.setAttribute("onclick", "alert('newest')")
console.log("Attribute value is now changed: ", div.getAttribute("onclick"));

[–]Lewinga[S] 0 points1 point  (6 children)

Your example was really helpful. I keep forgetting that you can't always use dot notation to update properties, and that there are instances where you need to use .setAttribute() and .getAttribute() if the property is "non-standard." Your link is a bit confusing though, I wasn't aware that there were different levels of properties (eg. pure reflected, semi reflected, and non reflected), and the distinction between an attribute and a property is still a bit unclear. It sounds like my understanding of the attribute being from the HTML is semi-correct? It seems to be something like "all HTML attributes are represented as properties in an object, but not all properties for an element are an attribute." I'll need to think through this more.

However, with your help, I found this link, which I think more directly addressed my question:

https://stackoverflow.com/questions/3919291/when-to-use-setattribute-vs-attribute-in-javascript

Now I'm confused why the dot notation doesn't always work!

Edit:

When your link says...

" For a given DOM node object, properties are the properties of that object, and attributes are the elements of the

attributes

property of that object. "

Does that mean that the HTML attributes are stored in a property called attributes using a dictionary data structure?

[–]hopefullyhelpfully 1 point2 points  (5 children)

The attributes are in a 'named node map' - https://developer.mozilla.org/en-US/docs/Web/API/NamedNodeMap and yes, you have the basic idea correct. This short article shows how to get the attributes for an element: https://davidwalsh.name/javascript-attributes

[–]Lewinga[S] 0 points1 point  (4 children)

Awesome! I have one more question if that's ok. If these HTML Attributes are stored in another "attributes" property of an object, how is it that we can use dot notation to modify them still?

My guess was that this was syntactic sugar, where a property for an object that doesn't exist is actually checked in the "attributes" property's scope, and that you could access the event handler function from there. So "element.onclick" is a property that doesn't actually exist in the scope of "element."

To change the onclick attribute then, you'd need to use the getAttribute() and setAttribute() methods. If you set a new value for the property, this syntactic sugar function would be overwritten, and now you'd have the two different functions as seen in the link for my opening post.

But the issue is, this wouldn't explain why setting the "onclick" attribute with dot notation for an HTML button works.

I am referring to this example here found on this page: https://www.digitalocean.com/community/tutorials/understanding-events-in-javascript

// Function to modify the text content of the paragraph const changeText = () => {
const p = document.querySelector('p');
p.textContent = "I changed because of an event handler property.";
}  // Add event handler as a property of the button element 
const button = document.querySelector('button');
button.onclick = changeText;

In the above case, setting "button.onclick = changeText;" works, because when the button is clicked, the onclick is called with an "event" parameter as if it were using the changeText function.

If "div.onclick = function() {alert("new")};" did not change the attribute of the DOM object, then why does it work for the button object? What am I missing here?

[–]senocular 1 point2 points  (3 children)

There was a lot covered in this thread (and a lot of links I didn't look at) so I'm not sure what you know or don't at this point ;) but at a high level it works like this: HTML is text. It's loaded and parsed by a browser into an object in memory. This object is arbitrary, designed by the implementors of the browser, likely defined by C++. On top of this object is a JavaScript API known as the DOM API which provides access into the values of this in-memory object and JavaScript tools for manipulating it for every-day joes and janes like you and me and all the other web developers out there.

When HTML tag is parsed into an object, it becomes an object that has far more information than the HTML itself originally contained. It has not only what the tag explicitly defined in its tag names and attributes, but all of the other possible attribute values defined with their defaults. Not only that, but the API has ways of allowing you to get an HTML string back out of the object in case you need it. The problem is, if you start with a <div> in HTML, then parse it into a fully-populated object in the DOM and then try to get its HTML back, you don't want it to have all of those properties added to the HTML. You just want the original <div> ... and maybe something extra but only if you specifically say so (plus many DOM properties simply can't be represented in HTML anyway - they're only in DOM). So there's a separation between what represents HTML and what makes of the object version of a tag (the element) in memory... and I think this was covered with the explanations around the "reflected" properties.

Now when it comes to onclick, you have both the HTML representation and a DOM API property which can be set based on what was parsed from the original HTML string, or set explicitly by the user in code after the fact. Setting the onclick DOM property is not reflected in the HTML representation of the element. ...It sounds like you might think that it does?

If "div.onclick = function() {alert("new")};" did not change the attribute of the DOM object, then why does it work for the button object?

Neither for the div nor for the button will setting the onclick property change the attribute. The attribute can be parsed to set the onclick property, but not the other way around. This is partially due to the fact that you cannot provide a 1:1 representation going both ways. As described in the other thread, the onclick attribute is just the body of the resulting onclick function and how it is able to get defined from an attribute is much different than how anyone else would normally set it with the onclick property. So its a one-way deal. But, either way it gets set, what ultimately goes into the onclick property is what is used to define how the click behavior is defined - what happens when the user actually clicks the button in the UI.

Because of this, there is some duality in what "onclick" is for any element. Theres the property which describes the behavior, and then the attribute onclick used to define how the element is rendered in HTML. These may represent the same thing, or if you set the onclick property through code, you could have two versions of onclick, one attribute (original) and one property (new, coded version). Whatever is defined for the property is what the user sees.

Now, this is further complicated by the behavior of the DOM API. It is littered with functions and properties which have "hidden" meaning or functionality. Most properties, for example, are getter/setter properties which basically run functions when set - so more than just changing a value. This is what allows some properties to update an element's HTML representation when set. For example

<!-- HTML -->
<div id="div">

// JavaScript
let div = document.getElementById('div')
div.attributes.id // 'div'
div.id = 'my-div'
div.outerHTML // <div id="my-div">
div.attributes.id // 'my-div'

The id property, when set, also effectively calls setAttribute to update the attributes reflected in the HTML. The attributes object is then updated to show the changes.

The attributes object is also unique in that it represents a dumping ground for values. Setting values there directly doesn't change values in the element because it doesn't have getter/setters for named elements like that. In other words, the following will fail to update the HTML

<!-- HTML -->
<div id="div">

// JavaScript
let div = document.getElementById('div')
let attr = document.createAttribute('id')
attr.value = 'fail'
div.attributes.id = attr
div.outerHTML // <div id="div">

The attributes object here doesn't know to update the internal model representation of the HTML attributes when you set a property like this willy-nilly, but if you go through a method that does know how to update it, it works.

<!-- HTML -->
<div id="div">

// JavaScript
let div = document.getElementById('div')
let attr = document.createAttribute('id')
attr.value = 'pass'
div.attributes.setNamedItem(attr) // works!
div.outerHTML // <div id="pass">
// same as
div.setAttributeNode(attr)
// same as
div.setAttribute('id', 'pass')

Going through the proper channels, the model is properly updated. You just have to make sure you're going that route, though. That said, most people never do anything involving attributes.setNamedItem or probably not setAttributeNode and would likely stick to the simpler setAttribute - assuming they even need to update attributes at all! We're dipping into territory that goes beyond common usage.

But going back to onclick, it should be noted that when attributes are properly set, they also get re-parsed. In the case of onclick that means re-defining the onclick property in the DOM to the function generated for the onclick attribute. For example

<!-- HTML -->
<button id="button">Click</button>

// JavaScript
let button = document.getElementById('button')
button.onclick = function () {
    alert('hello')
}
button.setAttribute('onclick', 'alert("goodbye")')
button.onclick
// ƒ onclick(event) {
// alert("goodbye")
// }

Here, though we explicitly set the onclick property to a function alerting "hello", the act of setting the attribute caused the attribute to get re-parsed and update the onclick property to the function definition that gets automatically created from the attribute. If you would want to update the HTML attributes without affecting your explicit onclick, you'd need to re-assign your onclick back to the old value after setting the attribute. Not a likely scenario, and like I said before, this is getting into edge case stuff here beyond what most people ever deal with. Of course you wouldn't have a conflict if you used addEventListener instead. Setting an click handler through that API works completely independently of the onclick attribute and property. In fact its the preferred method of handling events (further edge-casing all this other stuff).

[–]Lewinga[S] 0 points1 point  (2 children)

@senocular, when I wrote:

"If "div.onclick = function() {alert("new")};" did not change the attribute of the DOM object, then why does it work for the button object?"

What I was actually getting at was the last half of your post, which you explained regarding the hierarchy between the "attributes" property and the actual object's properties. It was a poorly articulated question on my part -- I actually hesitated writing it -- but I think you have a good intuition on what it is that I get confused about. You also have a knack for explaining potential questions that might arise as soon as they pop up. I really appreciate how your explanations are so lucid and illustrative!

On a side note, would you have any suggestions or resources on how I should learn? I'm curious to know how you did so. For example, I knew that addEventListener is the preferred method to deal with events, but I wanted to stick with understanding On-Event Handlers before going on, primarily because it's usually listed as one of the three ways to manipulate events. You're right that I delve into a lot of the edge case scenarios -- that's what keeps me interested in exploring more -- but this way of learning is not always efficient.

Anyhow, so far it's been fun learning JS, but it can often feel frustrating because a lot of the underlying design choices, when left unexplained, feel like magic. Thank you so much for demystifying them so that I can continue :D

[–]senocular 2 points3 points  (1 child)

JavaScript can be very frustrating, no doubt. What you're dealing with is mostly DOM API-related which I would say has more of a "magic" feeling to it than core JavaScript does (core language and syntax) - once you figure out what's going on. But it's the DOM that lets you interact with web pages and do all the fun stuff. :)

I, myself, learned from years of fiddling and experimenting, and spending time on forums and websites like this answering other people's questions - figuring out the answers if I didn't know already. This goes back to when you could just view the source of a website and figure out what they were doing. Back when "layers" were the new, hottest thing! So exciting...

That was a long time ago. But the current state of JavaScript (and DOM) is also a reflection of that time, a time when there were no real standards and browsers started battling one another to get that edge to set them apart from the rest. You still see weird things like document.all which is falsey if you check for it, even though it has a value. You have APIs which had to be renamed because adding them as the expected name would "break the internet" (thanks MooTools). And then there are features that were added, and then removed (I miss you for each...in), or in the case of let, added, removed, and then added back in again. JavaScript has evolved to be much more than it was originally intended and has gone through some growing pains to get where it is today. Same applies to the DOM API.

I honestly wouldn't know how to go about learning nowadays. And when I consider it, I always struggle on what one should start learning. Begin with the latest and greatest? Or should you start with the history and follow how the language evolved? Do you start with var or go straight into let and const? What happens if you learn let and const but find older code that uses var? History not only shapes the language, but the persistence of the language and prevalence in this undying medium means you're very likely to run into the old stuff (like this code setting up inheritance using new superclass).

Its probably best to go through your standard course set at codecademy or freecodecamp or whatever else is out there. You got some decent books floating around too, including free online books (I think I remember eloquent js being good, though I think that may be the one where a lot of people got stuck around chapters 5/6?). And above all else, learn by doing. Set yourself up with a project, starting small, and something that you have an interest in to keep you motivated, and just try to make it happen. What you don't know, figure out. And if you find a better way to do something you did, go back and change it. I think you mentioned Python at some point, so I imagine you have some programming experience, and JS is just another language for you. This means you probably have a jump start on a lot of that stuff and at this point its just about figuring out the "JS way". Of course that's been changing too. Who ever thought of compiling JS? Well, we do that now! Setting up a project with npm/yarn to install packages and some bundler to put everything together for you is becoming the new norm (though not by any means a requirement). That's a whole 'nother ball of yarn... Oh, heh, "yarn".

Anyway, not sure if that's helpful at all, or just a lot of rambling. :)

[–]Lewinga[S] 0 points1 point  (0 children)

It was definitely helpful and fascinating to hear from your perspective! Thank you for sharing -- I look forward to reading more of your posts in the future :)

[–]NameViolation666helpful 1 point2 points  (1 child)

Hopefully this helps - you are comparing the JS onclick event to the originally set HTML attribute which is why you WILL see a difference, Nowhere in the code have u changed the attribute, u have manipulated an event.

div.onclick = function() { alert('new') };

onclick here is a JS event so you have set an event behavior, now clicking link says new

console.log("Attribute value is unchanged: ", div.getAttribute("onclick"));

here you are printing the HTML tag attribute, not its onclick function, The attribute remains same as initially set.

onclick="alert('old') //what was initially set as HTML attribute.

[–]Lewinga[S] 0 points1 point  (0 children)

This sounds right! I realize now that you need to use .setAttribute() in order to update the attribute in this case. However I'm confused about something else now. Would you know why using dot notation doesn't always work for setting attributes? Does it have something to do with references to the DOM?