-
Notifications
You must be signed in to change notification settings - Fork 296
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Proposal to improve the DOM creation api #150
Comments
There has been discussion here: https://lists.w3.org/Archives/Public/www-dom/2011OctDec/thread.html#msg20. There was also http://www.hixie.ch/specs/e4h/strawman though TC39 didn't really like it. There is https://github.com/domenic/element-constructors by @domenic which should be finished at some point. I don't think libraries have really converged on something here either, other than Also note that the libraries that use object-like notation often confuse content attributes and IDL attributes (aka JavaScript properties), which makes this rather tricky. |
Thanks for those resources, they were very enlightening. From those discussions, it seems that template strings (or quasi-literal templates from the public archives) seem to be where the discussions keep coming back to, and seem to have generated the most consensus. With ES2015 template strings appearing to fulfill the role of the quasi-literal template proposal, I'm guessing the discussion should be focused on how to use the template string on the parent node? Domenic's element constructors sounds a bit like Dart's solution to create a new constructor for elements (Dart went a bit farther and made one for each element). It looks like he uses the object-like notation to define namespaces or attribute declarations, so would it suffer from the same problem of confusing content attributes and IDL attributes? |
Template strings seem like a way forward, though making them work with the HTML parser seems tricky. I think that is why nobody has tried thus far. Element constructors might indeed have that same problem, though they are also solving a more fundamental problem, that these classes don't have constructors. Which I think is why we want them either way, even if they didn't have convenient syntax for attributes and such. |
Ok. I'll go play with how browsers interpret different template strings and the HTML parser and see where it doesn't behave as expected. I'll post my findings here and focus on HTML parsing of template strings. Once a consensus on how to handle HTML parsing of template strings has been reached, the discussion could then turn into how to implement a convenient syntax for using the template string on the parent node. Do you know of any more resources to discussions on template strings and the HTML parser? |
It seems that the discussion around E4H and template strings is more complicated than I would like this proposal to address. In the end, both template strings and E4H rely on the already existent DOM apis of With that in mind, I would like to propose adding new DOM apis that make these use cases much cleaner and easier to carry out:
These apis could then be used by either E4H or template strings, which ever one wins out in the end. If this new api is the (a very simple approach that mimics the behavior would be) function html(str) {
var match = str.match(/<([^>]*)>/);
var rootStr = match[1].trim().split(/\s/);
var tagName = rootStr[0];
var root = document.createElement(tagName);
// attributes
for (var i = 1; i < rootStr.length; i++) {
var attr = rootStr[i];
var name = attr.substring(0, attr.indexOf('='));
var value = attr.match(/=['"]?([^'"]*)/)[1];
root.setAttribute(name, value);
}
// children
root.innerHTML = str.replace(match[0], ''); // kinda hakcy as it will ignore the orphaned closing tag
return root;
}
console.log(html(`<tr class="foo" data-config=bar>
<td class="hello">
<span class="foo">bar</span>
</td>
</tr>`));
console.log(html(`<tr>`)); |
So I stumbled upon tagged template strings, which makes it seem that template strings and E4H can live in harmony. It would seem that you could remove (deprecate, etc.) the This would be the new API to creating DOM that would satisfy my uses cases. var text = "foo";
// `html` is the tagged template function that runs E4H
// create a single node with attributes
document.body.appendChild( html`<div class="bar">${text}</div>` );
// create a series of sibling nodes
var element = html`<div>${text}</div><div>bar</div>`; |
Yeah, I think that kind of API would be ideal. @freddyb, any sanitizing library should look like the above ^^. Making this work with the HTML parser is a lot of work unfortunately. |
I thought one of the benefits of E4H was that you didn't have to go through the HTML parser. Either way, I would be happy to help get something like this working. What can I do to help? |
That is true, E4H had a much simpler grammar. I don't know if folks would find that acceptable though. They probably expect similar parsing rules to I don't know how familiar you are with the HTML parser, but figuring out what adjustments would need to be made to make |
Personally I think the best first step is to produce a library with the desired semantics and have it get reasonable adoption, before we consider standardizing it. It's still early days for template strings and to me it doesn't make sense to talk about standardizing a template string tag yet. |
Yeah, maybe. At some point we need to make sanitizing easier and add it to browsers. That will require a similar API of sorts. And also, this is hugely complicated to get right. @straker buliding a prototype on top of https://github.com/inikulin/parse5 (or similar library) might be a good first step here. |
Sounds good, I'll get to work getting a prototype using a tagged template function built on top of a parsing library. I'll also see if I can incorporate some of the E4H ideals into it just as a proof of concept. |
I'm not sure why you need a parsing library exactly? Can't you just use document.createElement("template");
template.innerHTML = passedInStringAfterSubstitutions;
return template.contents; Maybe the problem is safely generated |
If you didn't care that you always escaped the substituted DOM that would work, but what happens when I trust the substitution (e.g. I created it) and don't want to escape it so it will create DOM instead of string escaped text? I'm guessing that this contributes to what makes this problem hugely complicated to get right. |
I guess that's where contextual auto escaping comes in. |
@domenic you want to be able to set an attribute value without having to account for whether or not the passed in value included |
Alright, here is the initial draft of the I combined the best principles of E4H and contextual auto escaping to prevent XSS attacks, and it turned out pretty well if I do say so myself. What I would love now is help from security experts, like Mike Samuel who wrote about contextual auto escaping, to further the XSS prevention since I don't have a lot of experience in that area. Also, I'm not sure the best way to allow HTML variable substitution to be marked as safe so it isn't escaped when added to the DOM. |
This does look pretty cool. I hope people use it. My one big problem is that I don't like the overloaded return type (sometimes a node, sometimes an array). I think I would probably prefer a DocumentFragment all the time. Or maybe two helpers, one that throws if more than one element is parsed, and one that always gives an array? or always gives a document fragment? |
So I'm not sure how to proceed from here. There's been some nice discussions on the repo, but I feel that unless something changes, it won't go much further than where it currently is. Do you have any suggestions? |
I think the main hindrance is it becoming a popular way to create elements, maybe even the defacto way. And probably if we were to add an API like that I'd like the default to be safer than (Then there's also other activity around the HTML parser that likely takes priority for implementers, such as making custom elements work and providing a streaming API around the HTML parser (once we have sorted out network streaming).) |
I find it pretty bad to make string templates, as soon as it's on client-side, (and you more likely need to keep references, add/remove event listeners, ... I'd really like that document.createElement become like React.createElement and be able to do: (with
|
That seems way worse than h`<ul class="something">${items.map(({text}) => h`
<li onclick=${e=>/*..*/}>
text
<span onclick=${close}>✕</span>
</li>`}
</ul>` |
Ah thanks, indeed, https://github.com/straker/html-tagged-template (is this the right link?) looks interesting, similar to jsx. I'm just sceptic on how events are added After seeing https://github.com/straker/html-tagged-template/blob/master/index.js I don't see anything for event listeners, and all thoses regex feel quite dirty (ofc jsx parser might be similar).. My approach seems unnatural at first-see, but pretty practical, if not more after a bit of training |
The proposal only dealt with the creation of DOM. Adding event listeners can still be done through the normal ways once the DOM is created. var dom = html`<div>
<button>Click me</button>
</div>`
dom.querySelector('button').addEventListener('click', function() { /* ... */ }); |
I saw your implementation, here's a short one that does Domenic's example https://gist.github.com/caub/da489a286b0098d0fcd799b66a252196#file-h-js |
I've been working on a similar library recently and was pointed to this issue as a place that might be interested. The library is called lit-html, and it uses template literals, but not to create Nodes immediately, but to create templates that can be efficiently updated with new data later. https://github.com/PolymerLabs/lit-html The syntax is quite similar, though the templates are usually going to be in a function: const template(data) => html`<ul>${data.map(d=>html`<li>${d}</li>`}</ul>`; The big difference is that the result of the tag is a render(data) {
const result = html`<ul>${data.map(d=>html`<li>${d}</li>`}</ul>`;
result.renderTo(document.body);
} You can call The updating strategy and API is based on discussion on standardizing lit-html is really a layering of two parts which could be looked at for platform support:
I've tried to make the design extensible so that additional opinionated features can be layered on top. There's an included extension that allows templates to set properties on elements by default instead of attributes, and supports declarative event handlers. const button = (data) => html`
<my-button
class$="${data.isPrimary ? 'primary' : 'secondary'}"
on-click=${_=>data.onClick}
someProperty=${data.state}>
${data.label}
</my-button>
`; (you can see that here: https://github.com/PolymerLabs/lit-html/blob/master/src/labs/lit-extended.ts ) I also prototyped stateful template helpers, with an implementation of a const list = (items) => html`
<ul>
${repeat(items, (i) => i.uniqueId, (i, index) => html`
<li>${index}. ${i.title}</li>
`}
</ul>
`; (repeat is here: https://github.com/PolymerLabs/lit-html/blob/master/src/labs/repeat.ts ) Definitely looking for feedback. As far as standardization, I know we'd need to get this to be popular first, which I think is possible... /cc @rniwa |
Template Literals are awesome, but I have a non-template string proposal at #477 which may align more closely with how this discussion started. |
it's this more or less? /*
// html helper, usage
var span = $('span', 'Hello')
var div = h('div', {id:'test', onClick:console.log}, span, 'test2')
*/
const safeAttrs = new Set(['textContent', 'id', 'className', 'htmlFor', 'disabled', 'checked', 'autocomplete', 'crossorigin', 'async', 'innerHTML']); // probably missing some
function $(tag, ...o){
const el = document.createElement(tag);
const childrenIndex = o.findIndex(x => typeof x=='string' || typeof x=='number' || x instanceof Node);
const props = Object.assign({}, ...o.slice(0,childrenIndex);
for (var k in props) {
const value = props[k];
if (typeof value=='function') {
const name = k.slice(2).toLowerCase();
el.addEventListener(name, value);
} else {
if (k=='style') Object.assign(el.style, value);
else if (safeAttrs.has(k)) el[k] = value;
else if (typeof value=='string') el.setAttribute(k, value);
}
}
el.append(...o.slice(childrenIndex+1));
return el;
} |
@caub, in a few ways it’s similar, like the node name as the first argument and recognizing functions as events. The business with safe attributes to cleverly assign CSS seems like the stuff of DOM libraries. I don’t want clever, and I separated my proposal into parts just in case I had anything similarly over-reaching. I just want to easily create elements. Elements typically host attributes, events, and other nodes. That’s it. Doing this natively right now is a behemoth. This proposal doesn’t even solve for namespaced tags or namespaced attributes. And CSS can use selectors.
I think the similarities in libraries does represent a cowpath. I just also think libraries suffer because they are always trying to be so clever. |
I can't imagine how to handle the order of appearance of attributes in a Custom Element. I may assume that the order of appearance could be achieved at class TestHTMLElement extends HTMLElement {
constructor() { super(); }
attributeChangedCallback(name, oldValue, newValue) {
// I want to see here the order or reaction
// as indicated in the observedAttributes
}
static get observedAttributes() {
return ['attr1', 'attr4', 'attr2', 'attr3']
}
} I expose this idea because when you use |
@straker I've done it this way: https://github.com/caub/dom-tagged-template |
I think this issue is getting a bit out of hand and scope. It started off as just wanting to create a simpler interface to DOM creation. We added XSS prevention as a bonus to developers to prevent many serious problems in the web. But now it's been suggested to handle adding event listeners, having binding support and delayed rendering, and keeping attribute order. I feel, as Alex Russell did in his Polymer Summit talk today, that we are trying to solve all problems with a single tool. As such, I wonder if we should be trying to solve each of these problems as their own APIs instead of solving everything with a single API. For example, @justinfagnani suggestion for a binding aware template string could be built on top of a simpler interface to DOM creation, so long as the interface allowed for that to happen. That way we can build up multiple small things that all support one another, just like the Chrome team did to create 20+ different suggestions to different APIs to support Web Components. |
@straker Did you look at my code? yes event listeners are definitely important, I thought your version did it, and XSS is not an issue in my case I could make it work with SVG, shadow DOM too, good idea |
A simple, consistent API would be good. An API compatible with JSX transformed code would be grand:
Live demo on babel repl To be clear I'm asking to make the h (tag, attrs, [text?, Elements?,...]) Its code is quite short, 63 lines: https://github.com/dominictarr/h/blob/master/index.js |
That said, @straker's template literal suggestion is actually quite good-looking, with the exception that it's still string-based and thus slightly error prone. |
I'm sorry if this topic has been discussed already, I tried to do due diligence but couldn't find a similar proposal on the forums or the html or dom github repos.
The DOM creation api is a bit cumbersome to work with. To create a single element with several attributes requires several lines of code that repeat the same thing. The DOM selection api has received needed features that allow developers to do most DOM manipulation without needing a library. However, the DOM creation api still leaves something to be desired which sways developers from using it.
There are several use cases where the api is cumbersome to use. I have compiled a gist of just a few of them. It shows several use cases where the current api requires an awkward solution, and demonstrates a few common hacks of working around the native api to get a more manageable result. It also provides several examples of how popular libraries handle the same use case, usually in a simpler manner.
I would like to propose that the DOM creation api be improved so that developers have a cleaner interface into DOM creation and no longer need libraries to do it.
The text was updated successfully, but these errors were encountered: