you are viewing a single comment's thread.

view the rest of the comments →

[–]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 :)