Passing an object of attributes to document.createElement() as the second argument


#1

Change the current document.createElement() method to do this:

var div = document.createElement("div", {
 "id": "id",
 "class": "this that and so on",
 "data-whatever": "whatever data",
});

As of right now document.createElement second paremeter takes a string for the “is” attribute.

The above eliminates the reuse of the div variable like in the following:

var div = document.createElement('div');
div.id = "id";
div.className = "this that and so on"; //Also see how I have to use className instead of what I use in markup. Which obviously is fine we have had className for a while that's not going to change. (Not the point)
div.dataset.whatever = "whatever data"; //Having to access dataset

Also I’m not sure if there’s a reason why HTMLDivElement and all of the rest of HTML"Whatever"Element are not constructors. If there’s a good reason for that disregard the following:

If HTML"Whatever"Element can be constructors allow the following:

var div = new HTMLDivElement({
 "id": "id",
  ...//you get the point
});

This will also get rid of having a random window.Option constructor. Which to me is really weird.


A Defense of Declarative Content
#2

This has been discussed several times before; it’s mostly failed due to relative lack of interest in someone driving it forward. That said, it’s definitely a good idea!

Also I’m not sure if there’s a reason why HTMLDivElement and all of the rest of HTML"Whatever"Element are not constructors.

It’s not a particularly good reason, but the reason is that several of the classes are shared by multiple elements. For example, there’s a single HTMLHeadingElement which <h1>-<h6> all use. Several elements that don’t need anything special on their interface just use the generic HTMLElement class, which is a superclass of HTMLDivElement and the like. Making the interfaces into constructors would thus be pretty clumsy.


#3

I like the idea, too. It would make code much more readable.


#4

Also maybe the following:

Let’s get rid of String.prototype.HTML methods by implementing a wrapHTML or HTMLWrap or something:

"This is a link".HTMLWrap("a", {
     id: "id",
     class: "whatever class",
     href: "https://www.google.com/",
     ...//You get the point.
 });

My apologies if this would be another topic, but because the second argument matches this topic I posted it here.


#5

I don’t know about extending String, but I’d certainly like a way to create an element with its child nodes pre-loaded, rather than having to set textContent, or make a bunch of appendChild calls, after creating it.


#6

The only thing I can think of for what you want is using client side templating with the template element. Extending the String.prototype is useful to eliminate the other String.prototype.HTML methods, and avoiding adding new ones. HTMLWrap will handle any future element.


#7

@Edwin_Reynoso I would be surprised if TC-39 (JavaScript’s standardization body) accepted HTML-related String extensions like .HTMLWrap(). Also, such an approach isn’t really needed, I think, as you could further extend .createElement(), e.g.:

document.createElement("a", {
     id: "id",
     class: "whatever class",
     href: "https://www.google.com/",
     innerHTML: "This is a link"
 });

Now, this blurs the line between HTML attributes and DOM element properties, so it might not be a good approach, but in any way, there should be room for setting the element’s content via an argument like this.

P.S. Regarding class in the object literal, although it’s a reserved word, I think it’s valid to have it (unquoted) like this. (Somebody correct me if I’m wrong.)


#8

Yes I thought about that. It’ll be pretty confusing between HTML attributes and DOM element properties. Unless prioritized somehow by first looking at HTML attributes if not found look for DOM element property, but IMO that would be absurd. The one that confused me right away was class and className. Also above I used data-whatever using DOM element properties would be dataset.whatever. So idk, but like I mentioned, the HTMLWrap() method is only to remove the String.prototype.HTML methods, and avoid future ones (which I wouldn’t even mind not having, but I believe something has to be done about the String.prototype.HTML methods). But I agree with you on:

there should be room for setting the element’s content via an argument like this.


#9

Yeah, I like the idea of the second attribute taking an object of properties to read, named the same as the corresponding DOM element’s properties (so no setting of non-DOM attributes, but allowing the setting of innerHTML / textContent / children / className / dataset etc).

The trick there would be establishing a hierarchy of which conflicting properties have priority (so if someone passes innerHTML, children, childNodes, and textContent, it’s clear which one gets used).

Maybe it would also be possible to override the definitions of some existing element constructors to take objects with named properties like this?


#10

The Custom Elements spec already uses a second parameter on .createElement() for type extensions.

Source: http://w3c.github.io/webcomponents/spec/custom/#extensions-to-document-interface-to-instantiate


#11

I believe I mentioned that already:

As of right now document.createElement second paremeter takes a string for the “is” attribute.

If I’m not mistaking is the same thing, so yea that may be a problem since developers are already using Custom Elements so perhaps 3rd argument or if second argument is an object then set those as the attributes. Also allow the is property to be a part of the attributes object.


#12

Ah, missed that.

Yes, since is is an attribute, it would be defined as a property of the attributes object.

I’m not confident that the relevant W3C working groups would allow different types on the same parameter like that. Is there an instance of such a pattern in any of the existing DOM API?

Note that Google’s Polymer project already uses the API extension defined in Custom Elements (see here). Looks like the second parameter as string is set in stone.


#13

The only one I can think of right away is the new CSSOM smooth scrolling.

window.scrollBy(0, 0);
window.scrollBy({top: 0, behavior: 'smooth'});

I’m sure there are more.

UPDATE:

document.addEventListener('click', function() {
    console.log('Hi');
});

document.addEventListener('click', {
   handleEvent() {
     console.log('Hi');
   }
});

#14

It might be worth mentioning here that @annevk is discussing the removal of is="" so I assume the API would follow suit too.

https://lists.w3.org/Archives/Public/public-webapps/2015AprJun/0515.html

There are other examples to APIs that use different data types like parseInt which varies parsing. setTimeOut is another.


#15

Yeah is="" is not set in stone at all. Its API even less.


#16

Possible polyfill I guess I would call, obviously this is not the way it would be done because we wouldn’t actually do Document.prototype.createElement.call(document, elementName).

But I need help improving if anyone cares to:

document.createElement = function(elementName, attributes) {
var element = Document.prototype.createElement.call(document, elementName);

if(attributes && !(attributes instanceof Object)) {
	throw Error('Idk what to put, give feedback');	
} else if(attributes) {
	for(var attr of Object.keys(attributes)) {
		if(element[attr] !== undefined) {
			element[attr] = attributes[attr];
		} else if (!element.hasAttribute(attr)) {
			element.setAttribute(attr, attributes[attr]);
		}
	}
}

return element;
}
  • I don’t know how to check if attr is a proper attribute so for now I just put if not recognized as property then call setAttribute.

  • What would be a proper Error?

  • And more feedback please

Example uses:

var div = document.createElement('div'); // Still returns normal div without anything
var div = document.createElement('div', {
  "id": 'uniqueDiv',
  "className": 'one two three',
  "class": 'this will not be applied because it checks if attribute is already set which it is because of className',
"data-num": 5,
"is": "button", // Taking care of is attribute but again is, is not set and stone.
"innerHTML": '<div class="child">'
}); // <div id="uniqueDiv" class="one two three" data-num="5">

What’s missing?


#17

A few questions or suggestions:

If the second parameter is an object of attributes, might it accept camelCase attributes, similarly to dataset?

document.createElement('nav', {
	id: 'navigation',
	class: 'site-nav is-dropdown',
	ariaExpanded: true,
	ariaHaspopup: true
});

Or, might the first argument support CSS selectors, similarly to jQuery?

document.createElement('nav#navigation.site-nav.is-dropdown[aria-expanded="true"][aria-haspopup="true"]');

And, if the first argument supports CSS selector, might this allow for creation of child elements?

document.createElement('section h1'); // returns <section> containing a child <h1>

I scripted this functionality on document.createDocumentFragment, for demonstration purposes.


#18

Wow I really like that idea. It would eliminate some things for simple elements with a set of children.

Another neat idea would be supporting emmet as in:

document.createElement('div#container > div{Div number $}*10');

Yet I don’t think this part would be natively implmeneted, seems like a library would do that.

But anyways this would be really neat, eliminating some function calls like appendChild for elements with one child element.

Now the camelCased properties? I’m pretty confused. Take innerHTML which is technically camelCased it’s a property of the element therefore it’d be the same as:

var nav = document.createElement('nav');
nav.ariaExpanded = true;

If I’m not mistaking attributes are not case sensitive meaning:

document.createElement('nav', {
    CLASS: "site-nav is-dropdown"
}); // returns <nav class="site-nav is-dropdown"></nav>

At least that’s how it’s working with my prollyfill I provided above.


#19

For an explanation of the camelCase conversion, see https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/dataset.

It goes something like this: The dataset object represents custom data attributes on an element. To become a property on the dataset object, each custom data attribute’s data- prefix is removed (including the dash), and then any dash followed by a lowercase letter is replaced with the letter transformed into its uppercase counterpart. For example:

<div data-foo-bar="baz"></div>

Becomes:

return div.dataset.fooBar // returns "baz"

So, in the case of document.createElement(name, attributes), I was suggesting that the attributes object might do the reverse, replacing any uppercase letters with a dash followed by the letter. For example:

document.createElement('div', {
	dataFooBar: 'baz'
});

Becomes:

<div data-foo-bar="baz"></div>

#20

Well using my prollyfill, I couldn’t figure out how to check whether an attribute is an actual attribute in the DOM. Therefore It results to setting anything to an attribute:

document.createElement('div', {
  fooBar: true,
  "data-fooBar": true
});

It returns:

<div foobar="true" data-foobar="true"></div>

Do you know how to prollyfill what you expect? I’m not sure how

Thanks.