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


#23

My solution deals with “passing an object of attributes to document.createElement() as the second argument”, as that was the original subject of your post

If you wish to assign properties to the object, then I suggest you use Object.assign.

Example:

element = Object.assign(document.createElement('div'), {
	className: 'foo bar',
	id: 'qux',
	innerHTML: 'Hello there'
});

#24

Yes but the discussion lead to also assigning properties to the element that way (using the attributes object/second argument object) therefore if all of it is possible why not.


#25

I quite like the original idea (assigning attributes in the document.createElement() call), but mixing attributes with properties is, I believe, a bad idea. The semantics are different. Attributes behave in a quite simple, predictable way; properties can have all sorts of behind-the-scenes behaviour attached. For instance, this is perfectly fine:

var span = document.createElement('span');  // -->  <span></span>
span.setAttribute('tabindex', 'banana');  //  -->  <span tabindex="banana"></span>

All we did was create an element with an attribute with a value. No behaviour implied. This is “valid” DOM (valid in the sense that the elements and attributes are actually created and inserted in the DOM). But this…

span.tabIndex = 'banana';  // -->  span.tabIndex is now 0

Not at all equivalent. document.createElement is concerned (and should continue to be, IMO, for clarity) with just the elements/attributes side of the DOM. Properties are, if appropriate, derived from the attributes, just like they are in normal HTML (so <span tabindex="2"> translates to span.tabIndex == 2).

@jonathantneal’s sample code is close to how I’d see this working… I would just remove the “prefix capitals with letters” behaviour — what’s the point of that?


#26

I don’t think this idea needs to be specced. It does not enable anything that is not already possible. It is straightforward for a library to implement this. Then there does not need to be any DOM API change or spec work, and you still get a convenient API.


#27

Yes if that library adds more functionality than just that, so IMO I rather have it Natively done. Where now everyone who uses document.createElement() can easily shorten their code a bit.


#28

Hmm true, it’d be really easy to implement for just attributes. So perhaps not to confuse and complicate that object, a third parameter could be done for it’s properties. Which will still be complicated.

On the part where you talk about:

span.tabIndex = 'banana';

I don’t fully agree, because you should be aware of how those properties work in general.

But yes becasue it’s probably less work to deal with, and there’s too many properties compare to attributes I’m fine with just setting attributes.

But still would want the string to be able to set IDs and classes:

document.createElement('div#id.class1.class2');

#29

I’d rather not have this built-in, because if it is ever released, browsers will be obliged to support it indefinitely to avoid breaking websites. It could cause difficult compatibility issues for any future extensions to createElement that provide genuine new utility which cannot already be done. For example a new feature some years down the line may require an options object as a parameter, e.g. document.createElement("div", { enableNewFeature: true }). To the browser, your “initial attributes” parameter is indistinguishable from this, so to preserve backwards compatibility you’d now have to have document.createElement("div", null, { enableNewFeature: true }). Now there is API cruft, and everyone’s code is a bit more ugly, on account of a feature that was supposed to just be “nice to have”.

For this reason I am against implementing any “nice to have” features at the spec level. Using a library completely circumvents this problem, while providing exactly the developer-friendly features you want. IMO spec extensions should be reserved for things which are not currently achievable on the web platform, even with a library.


#30

That’s an interesting thought I agree, but not with:

even with a library

There are millions of libraries out there, I’m sure some functionality came from library implementations to browsers.

But looking at:

document.createElement("div", { enableNewFeature: true });

Wondering what Web APIs that exist before were updated with a functionality that a library couldn’t have done?? Just by passing another object, it must be doing something internal that’s not possible, but I can’t think of one?


#31

Features only make it from libraries to browsers if the browser can offer something the library cannot, such as improved performance.

I don’t think it’s unrealistic to think another parameter could be added to createElement some time in the future, so I don’t see why it matters if that’s ever happened to a different web API. I know that canvas.getContext(“2d”) has now been extended to support canvas.getContext(“2d”, {alpha: false}). It would be more forwards-thinking to propose the second parameter as an options argument anyway, so it is easier to extend it again in future. For example: document.createElement("div", { initialAttributes: { ... } }). Then the options object could have all manner of features, such as initialAttributes, initialClasses, attachToParent, innerHTML, customElementType or any other features anyone else ever thinks of. But I still can’t think of any reason the browser can do any of these things better than a library function, which is the main reason any library features would move to being built-in to the browser.


#32

I’ve implemented a function that does work this way as https://github.com/stuartpb/cre. It uses the DOM attributes rather than the HTML ones, so you can provide more complex values (for properties like style and classList).

Other enhancements in cre:

  • Lets you provide an element (ie. a template) to use with cloneNode, since cloneNode is faster than createElement
  • Lets you specify class or ID as part of the tag name, using selector syntax like Jade/Haml
    • I might strip this out if there’s a significant perf hit - too bad jsperf.com is down
  • Lets you provide child elements (or text) as an array, which will then be appended to the new element (using an intermediary DocumentFragment if there are more than one)
  • Also works as a shorthand for creating document fragments and text nodes when given an array as the first argument

DocumentFragment#innerHTML
#33

This would go a long way towards compensating for recent onerous requirements by the Big 4 in their add-on/app guidelines (such as, insistence on the use of setAttribute, eventListener, and in place of straight HTML/innerHTML writes, on basis that HTML is “less readable”). I’m in favor.


#35

We could have the following:

document.createElement('div#id.class1.class2', {
  "attributes": {
    "data-index": 1,
    "is": "button"
  },
  "properties": {
    "textContent": "Hello World"
  }
});

Update:

Or perhaps not have properties because every part is a property:

document.createElement('div#id.class1.class2', {
  "attributes": {
    "data-index": 1,
    "is": "button"
  },
  "dataset": {
    "index": 1
  },
  "textContent": "Hello World"
});

Now its really like how browsers create the Element under the hood (sort of)

Because Element#attributes is an object with properties as well as Element#dataset

That way like @AshleyScirra says we can add features that may be needed in the future and separate the attributes and properties


#36

Yes, this looks like a much better idea to me, but I’d also move the ID and classes to the options object as well, and I don’t see why “properties” needs to be its own key:

document.createElement("div", {
  id: "id",
  classes: ["class1", "class2"],
  attributes: {
    "data-index": 1,
    "is": "button"
  },
  "textContent": "Hello World"
});

Edit: Discourse seems to have messed up the formatting on that, it looks OK in preview mode…

Perhaps even “div” could be specified as a tagName property in the options object, and then document.createElement only takes one parameter - the options object.

I guess it would be possible to polyfill this if someone wants to come up with a demo?


#37

I was updating my last post before you posted, take a look, and to fix the formatting you probably need one more empty line before the code snippet

And yes and no to the tagName property which is an actual property of Element#tagName:

No beacuase of backwards compatibility and easily create an Element without any class or ID:

// This
document.createElement('div'); // <div></div>

// Over this:
document.createElement({
 "tagName": 'div'
});

// This:
document.createElement('div#id.class1.class2');

// Over this:
document.createElement('div', {
 id: 'id',
 className: 'class1 class2'
});

However both should be possible, and if both are used div#id.class1.class2 and specifying it in the second argument, will override it.


#38

As I mentioned a few weeks ago, this is how cre works (except, since property inputs match the structure of the created element, it’s classList instead of classes, and attributes must be specified as a NamedNodeMap-like array of objects with name and value properties).


#39

Just finish this: https://github.com/eorroe/document.createElement

Also @stuartpb thanks for the Regex in your cre code.

This isn’t for use like @stuartpb 's cre, its only to show how it would work.


#40

FWIW, here is a related proposal and discussion in the www-dom@w3.org mailing list:

Convenient way to create element and set its attributes at once (2013-11-22)


#41

This reminds me of Reactjs and JSX: https://facebook.github.io/react/docs/jsx-in-depth.html

React.createElement('div', {className: 'myDiv'}, 'text or more nodes')
<div className="myDiv">text or more nodes</div>

They have a bunch of hacks like not being able to use class in ie8 (so they use className), certain attributes are whitelisted as dom, data-* is passed through, the rest are for what could basically be considered custom elements.

It translates pretty well to how the dom is actually structured, you see this data structure in many vdom systems.

Looking at the JSX spec might be worthwhile for people interested in this, some other frameworks are already picking it up. I think this would be really nice (although way out of scope, pie int he sky etc :P):

document.renderElement(document.createElement('button', {onClick:()=>{alert('hello world')}}, 'hello world'), document.body);

#42

Internet Explorer supports:

document.createElement("

")

#43

I think extending setAttribute is the best solution to the original stated problem when weighing the various points made above. It removes the concerns about forward/backward compatibility of createElement; it avoids the discussion of properties vs. attributes; it accomplishes the goal of reducing the number of repetitions of el.setAttribute following the el = document.createElement() line from N to 1; and it should be very intuitive to anyone who is already familiar with the two argument version of setAttribute('key', 'value'). I’d also feel comfortable speculating that it would be easier for vendors to implement. Win, win, win?