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

Standardize <template> variables and event handlers #2254

Open
Mevrael opened this Issue Jan 10, 2017 · 76 comments

Comments

@Mevrael

Mevrael commented Jan 10, 2017

Proposal by Apple - https://github.com/w3c/webcomponents/blob/gh-pages/proposals/Template-Instantiation.md
Proposal by Google - https://github.com/domenic/template-parts


This is a proposal on standardizing <template> parser which would allow developers:

  1. put variables,
  2. use simple statements
  3. and attach event handlers when template Node generated/inserted into DOM

Current stage: Open Discussion about the HTML syntax and JS API

Why?

Templates finally standardized the way of making re-usable parts of HTML, templates for widgets/components, however, they are still weak and one of the most general use cases in templates is to put data into them which, currently, is not possible without using a 3rd party library (template engine) or a custom implementation.

This brings:

  1. too much diversity across the Web (everyone is doing the same but in a different way)
  2. code works slower because it is not a native code which is implemented by a browser.
  3. bundle size increases and in case of huge production apps with many 3rd party components it is often required to import also different template engines or frameworks.

What is needed to be discussed and standardized?

  1. How variables should be included inside a <template>.
  2. How event handlers should be attached to elements inside a <template>.
  3. Which statements (for example "if") are allowed and how they should be used inside a <template>

1. Variables inside a <template>

I would +1 the most popular solution used across many template engines in many languages - variable name should be put inside a {{ }}. To make life even easier and faster it could be implemented as a simple string.replace. We may force developers to write a variable name without a spaces, i.e. {{ var }} will be invalid and {{var}} will work.

2. Attaching event handlers to a <template> elements

I would propose here new handler="method" attribute which would receive an Node when called.

Example HTML

<template id="card">
  <card>
    <h2>{{title}}</h2>
    <div>{{description}}</div>
    <a href="/card/{{id}}">Read more</a>
    <button handler="onEdit">Edit</button>
  </card>
</temlate>

Example JavaScript

In JS I would suggest just adding new function

Node parseTemplate(String id, Object data = {}, Object handlers = {})

because current syntax and manual clone/importNode is ridiculous. We already have functions parseInt, parseFloat, etc.

document.body.appendChild(parseTemplate('card', {
  title: 'Card title',
  description: 'Hello, World',
  id: 42
}, {
 onEdit(btn) {
   btn.addEventListener('click', () => {
      // ...
   });
  }
});

Currently I have no opinion on statements, may be, we could allow only simple inline statements like in ES6 template strings.

@Yay295

This comment has been minimized.

Show comment
Hide comment
@Yay295

Yay295 Jan 10, 2017

Contributor
  1. Too much diversity across the Web (everyone is doing the same but in a different way).
    Is this a problem? It's not like they have to interact with each other, so I don't see why this matters.
  2. Code works slower because it is not a native code which is implemented by a browser.
    Again, is this actually a problem? Are you generating so much HTML that it has a noticeable negative impact on the user experience?
  3. Bundle size increases, and in case of huge production apps with many 3rd party components it is often required to import many different template engines and frameworks.
    Surely each component isn't doing the same thing, so they would still need their own individual code, would they not (not to mention needing to stay backwards compatible with older browsers)?

As it is, I can easily create 'Card's with a simple factory function in JavaScript:

function makeCard(title, description, id, method) {
    var card = document.createElement('div');
    card.classList.add('card');

    card.innerHTML = '<h2>' + title + '</h2>' +
                     '<div>' + description + '</div>' + 
                     '<a href="/card/' + id + '">Read more</a>' + 
                     '<button>Edit</button>';

    card.querySelector('button').addEventListener('click', method);

    return card;
}
Contributor

Yay295 commented Jan 10, 2017

  1. Too much diversity across the Web (everyone is doing the same but in a different way).
    Is this a problem? It's not like they have to interact with each other, so I don't see why this matters.
  2. Code works slower because it is not a native code which is implemented by a browser.
    Again, is this actually a problem? Are you generating so much HTML that it has a noticeable negative impact on the user experience?
  3. Bundle size increases, and in case of huge production apps with many 3rd party components it is often required to import many different template engines and frameworks.
    Surely each component isn't doing the same thing, so they would still need their own individual code, would they not (not to mention needing to stay backwards compatible with older browsers)?

As it is, I can easily create 'Card's with a simple factory function in JavaScript:

function makeCard(title, description, id, method) {
    var card = document.createElement('div');
    card.classList.add('card');

    card.innerHTML = '<h2>' + title + '</h2>' +
                     '<div>' + description + '</div>' + 
                     '<a href="/card/' + id + '">Read more</a>' + 
                     '<button>Edit</button>';

    card.querySelector('button').addEventListener('click', method);

    return card;
}
@rianby64

This comment has been minimized.

Show comment
Hide comment
@rianby64

rianby64 Jan 11, 2017

This proposal breaks all sites that use angular default template. They will be forced to change {{ }} brackets to something else, I think.

rianby64 commented Jan 11, 2017

This proposal breaks all sites that use angular default template. They will be forced to change {{ }} brackets to something else, I think.

@Mevrael

This comment has been minimized.

Show comment
Hide comment
@Mevrael

Mevrael Jan 11, 2017

@Yay295

  1. How your example is re-usable and scalable?
  2. How I can change the CSS of this card, add classes at least? (without rebuilding your JS of course)
  3. What is your example on more complicated components, like the row inside a datatable and you from JS can't know what columns, data UI engineer will need?
  4. How you can edit a template/layout/HTML from the back-end?

@rianby64

It doesn't breaks anything. Angular is not forced to use parseTemplate(), moreover, Angular in the future also will be able to benefit from the specification by keeping almost the same syntax.

Mevrael commented Jan 11, 2017

@Yay295

  1. How your example is re-usable and scalable?
  2. How I can change the CSS of this card, add classes at least? (without rebuilding your JS of course)
  3. What is your example on more complicated components, like the row inside a datatable and you from JS can't know what columns, data UI engineer will need?
  4. How you can edit a template/layout/HTML from the back-end?

@rianby64

It doesn't breaks anything. Angular is not forced to use parseTemplate(), moreover, Angular in the future also will be able to benefit from the specification by keeping almost the same syntax.

@rianby64

This comment has been minimized.

Show comment
Hide comment
@rianby64

rianby64 Jan 11, 2017

@Mevrael Sounds good to add a new special case for element.textContent in order to process combinations like {{myVariable}} and compile it as Mustache, Angular and others do. But, what about if try something like this:

window.parseTemplate = (template, data) => {
  var clone = document.createElement('template');
  clone.innerHTML = Object.keys(data)
    .reduce((acc, variable) => 
      acc.replace('{{' + variable + '}}', data[variable]), template.innerHTML);
  return clone.content;
};

And instead of passing an id you could pass a template. The handlers param looks like should be in other place. parseTemplate shouldn't take a lot of responsibilities.

In this way, there were no need to add an special case for {{variable}}... I think.

rianby64 commented Jan 11, 2017

@Mevrael Sounds good to add a new special case for element.textContent in order to process combinations like {{myVariable}} and compile it as Mustache, Angular and others do. But, what about if try something like this:

window.parseTemplate = (template, data) => {
  var clone = document.createElement('template');
  clone.innerHTML = Object.keys(data)
    .reduce((acc, variable) => 
      acc.replace('{{' + variable + '}}', data[variable]), template.innerHTML);
  return clone.content;
};

And instead of passing an id you could pass a template. The handlers param looks like should be in other place. parseTemplate shouldn't take a lot of responsibilities.

In this way, there were no need to add an special case for {{variable}}... I think.

@Mevrael

This comment has been minimized.

Show comment
Hide comment
@Mevrael

Mevrael Jan 11, 2017

@rianby64

This is how I and many other engineers do now with a custom function. The problem and fact is still with a "custom" implementation. Just having such simple parseTemplate() in a Web Standards would solve many small problems and people would know what to use in that case by default and stop writing another framework.

However, I may agree on that 1st argument should be a Node and not a String as all current native API works.

Don't have strong opinion on handlers. Yes, it could be done later.

There is still a question about how to handle at least simple if statements inside a template or what else we might need there by default. Since it is part of JS and we already have ES6 template strings implemented I believe it won't be too hard to implement this, something like:

<template>
  <alert class="alert {{ className ? className : 'alert-info' }}">
     ...
  </alert>
</template>

However I prefer real if constructions with may be custom syntax:

<alert class="alert 
@if(className) 
  {{className}} 
@else
  alert-info
@endif
">

Mevrael commented Jan 11, 2017

@rianby64

This is how I and many other engineers do now with a custom function. The problem and fact is still with a "custom" implementation. Just having such simple parseTemplate() in a Web Standards would solve many small problems and people would know what to use in that case by default and stop writing another framework.

However, I may agree on that 1st argument should be a Node and not a String as all current native API works.

Don't have strong opinion on handlers. Yes, it could be done later.

There is still a question about how to handle at least simple if statements inside a template or what else we might need there by default. Since it is part of JS and we already have ES6 template strings implemented I believe it won't be too hard to implement this, something like:

<template>
  <alert class="alert {{ className ? className : 'alert-info' }}">
     ...
  </alert>
</template>

However I prefer real if constructions with may be custom syntax:

<alert class="alert 
@if(className) 
  {{className}} 
@else
  alert-info
@endif
">
@EisenbergEffect

This comment has been minimized.

Show comment
Hide comment
@EisenbergEffect

EisenbergEffect Jan 11, 2017

I'm not going to respond at all to the templating language proposed. There are problems there, but they are secondary to the bigger issues.

Here are a few quick, before breakfast, thoughts:

  • One of the things that tends to differentiate frameworks is their templating language. So, none of the big players are going to agree with something like this. This isn't a technical critique but it's a matter of politics, which play a bigger part in web standards than anyone admits.
  • While this seems like a simple idea, this is actually a huge proposal. So, you aren't just trying to get consensus on a small contentious item. You are trying to get consensus on a bunch of large, complicated, contentious items.
  • Are you going to be the person to drive this? The above issues aside, something like this is going to take a lot of time and work. It wasn't too long ago that Object.observe (something that would be part of a proposal like this) was dropped because the person working on it just lost interest.
  • Templating languages/engines are still a pretty big area of research, experimentation and rapid change. It seems premature to tie the web to something like this now. Whoever did drive this is probably going to favor their own favorite framework, which is going to make everyone else mad...there's just a ton of baggage to deal with.
  • I don't think this is in line with the spirit of web "bedrock". There are things that could be done to improve the lower level capabilities of the template element which would be fundamental enablers to today's frameworks. These items should be done first and then, once those have trickled out, we can think of higher level abstractions.

Here are a few lower-level improvements that could be made to the template element:

  • Make sure that the template's content (DocumentFragment) has the same set of query selector methods as the Document. Right now, it's missing a bunch of methods and so less-performant querying is required by templating engines that rely on the template element. We should fix this.
  • Improve template cloning by enabling the consumer to pass a "reviver" function. Similar to how the reviver works when parsing JSON, the reviver used when cloning a template would pass each cloned node to the reviver function, enabling it to process the node. This would enable templating engines built on the template element to work more efficiently. As it stands today, they have to first clone and then query...and per the issue above, they have to use less performant query operations as well.

I'm sure there are others who have additional thoughts on improving template. I would prefer to stick to lower-level improvements at this point in time.

EisenbergEffect commented Jan 11, 2017

I'm not going to respond at all to the templating language proposed. There are problems there, but they are secondary to the bigger issues.

Here are a few quick, before breakfast, thoughts:

  • One of the things that tends to differentiate frameworks is their templating language. So, none of the big players are going to agree with something like this. This isn't a technical critique but it's a matter of politics, which play a bigger part in web standards than anyone admits.
  • While this seems like a simple idea, this is actually a huge proposal. So, you aren't just trying to get consensus on a small contentious item. You are trying to get consensus on a bunch of large, complicated, contentious items.
  • Are you going to be the person to drive this? The above issues aside, something like this is going to take a lot of time and work. It wasn't too long ago that Object.observe (something that would be part of a proposal like this) was dropped because the person working on it just lost interest.
  • Templating languages/engines are still a pretty big area of research, experimentation and rapid change. It seems premature to tie the web to something like this now. Whoever did drive this is probably going to favor their own favorite framework, which is going to make everyone else mad...there's just a ton of baggage to deal with.
  • I don't think this is in line with the spirit of web "bedrock". There are things that could be done to improve the lower level capabilities of the template element which would be fundamental enablers to today's frameworks. These items should be done first and then, once those have trickled out, we can think of higher level abstractions.

Here are a few lower-level improvements that could be made to the template element:

  • Make sure that the template's content (DocumentFragment) has the same set of query selector methods as the Document. Right now, it's missing a bunch of methods and so less-performant querying is required by templating engines that rely on the template element. We should fix this.
  • Improve template cloning by enabling the consumer to pass a "reviver" function. Similar to how the reviver works when parsing JSON, the reviver used when cloning a template would pass each cloned node to the reviver function, enabling it to process the node. This would enable templating engines built on the template element to work more efficiently. As it stands today, they have to first clone and then query...and per the issue above, they have to use less performant query operations as well.

I'm sure there are others who have additional thoughts on improving template. I would prefer to stick to lower-level improvements at this point in time.

@Mevrael

This comment has been minimized.

Show comment
Hide comment
@Mevrael

Mevrael Jan 11, 2017

@EisenbergEffect

Make sure that the template's content (DocumentFragment) has the same set of query selector methods as the Document. Right now, it's missing a bunch of methods and so less-performant querying is required by templating engines that rely on the template element. We should fix this.

Yes, DocumentFragment has only getElementById(), querySelector() and querySelectorAll().

However, why do you even need to search the template.content (DocumentFragment itself)? You should NOT change it because by that you will also modify the template itself while it should be static. Template should be cloned first and then you will get a fresh instance of Node which you will be able to iterate/search/do whatever you want.

function cloneTemplate(id) {
	return document.getElementById(id).content.firstElementChild.cloneNode(true);
}

About your second point (reviver function). I agree that parseTemplate() at the end may receive some kind of callback (but definitely with a different name and not a "reviver"). Could you elaborate more, what do you mean by each node? Should it be each node in DocumentFragment only which usually has only one or should it be each Node with all children recursively, should it contain only element nodes and ignore text nodes?

In example above I used firstElementChild and in all templates I've seen they all had only one root node - component itself, row of the table, etc. Do we even need to return all the nodes or just first element child?

At the and if we will have an official template parser, wouldn't be it parsed simply as an template.innerHTML string without any nodes or you suggest to parse each node's textContent, arguments and children? There will be nothing to pass to a callback/reviver, well the parsed string could be and a custom template engine on the top of that could use it.

And about the

was dropped because the person working on it just lost interest

It is not up to a one person to change the world, it is the responsibility of all of us.

Mevrael commented Jan 11, 2017

@EisenbergEffect

Make sure that the template's content (DocumentFragment) has the same set of query selector methods as the Document. Right now, it's missing a bunch of methods and so less-performant querying is required by templating engines that rely on the template element. We should fix this.

Yes, DocumentFragment has only getElementById(), querySelector() and querySelectorAll().

However, why do you even need to search the template.content (DocumentFragment itself)? You should NOT change it because by that you will also modify the template itself while it should be static. Template should be cloned first and then you will get a fresh instance of Node which you will be able to iterate/search/do whatever you want.

function cloneTemplate(id) {
	return document.getElementById(id).content.firstElementChild.cloneNode(true);
}

About your second point (reviver function). I agree that parseTemplate() at the end may receive some kind of callback (but definitely with a different name and not a "reviver"). Could you elaborate more, what do you mean by each node? Should it be each node in DocumentFragment only which usually has only one or should it be each Node with all children recursively, should it contain only element nodes and ignore text nodes?

In example above I used firstElementChild and in all templates I've seen they all had only one root node - component itself, row of the table, etc. Do we even need to return all the nodes or just first element child?

At the and if we will have an official template parser, wouldn't be it parsed simply as an template.innerHTML string without any nodes or you suggest to parse each node's textContent, arguments and children? There will be nothing to pass to a callback/reviver, well the parsed string could be and a custom template engine on the top of that could use it.

And about the

was dropped because the person working on it just lost interest

It is not up to a one person to change the world, it is the responsibility of all of us.

@rianby64

This comment has been minimized.

Show comment
Hide comment
@rianby64

rianby64 Jan 11, 2017

As far as I understand, this proposal has an small meaning because you can template/compile by using the current set of JavaScript and HTML features. The proof of this - there are already different template solutions in the market with different flavors. And this means, there's no need to bring template/compile/interpolate process to be part of the standard.

rianby64 commented Jan 11, 2017

As far as I understand, this proposal has an small meaning because you can template/compile by using the current set of JavaScript and HTML features. The proof of this - there are already different template solutions in the market with different flavors. And this means, there's no need to bring template/compile/interpolate process to be part of the standard.

@rniwa

This comment has been minimized.

Show comment
Hide comment
@rniwa

rniwa Jan 12, 2017

Collaborator

I think there is a value in coming up with a standardized syntax for HTML templates just like template literals in ES6.

We can let each framework & library define how each string inside {{ and }} are interpreted but having this capability in the browser would avoid having to implement (often incompletely) a HTML parser in JS frameworks & libraries.

I do like tagged template string model. They’re simple enough for anyone to understand but can be powerful depending on what the tagged function does.

Collaborator

rniwa commented Jan 12, 2017

I think there is a value in coming up with a standardized syntax for HTML templates just like template literals in ES6.

We can let each framework & library define how each string inside {{ and }} are interpreted but having this capability in the browser would avoid having to implement (often incompletely) a HTML parser in JS frameworks & libraries.

I do like tagged template string model. They’re simple enough for anyone to understand but can be powerful depending on what the tagged function does.

@justinfagnani

This comment has been minimized.

Show comment
Hide comment
@justinfagnani

justinfagnani Jan 12, 2017

@rianby64 I don't think this would necessarily break Angular. This would only apply within <template>, and presumably be a new API. template.content should return the same nodes as it does now, but something like template.likeTaggedTemplateLiteral(callback) could provide new functionality.

justinfagnani commented Jan 12, 2017

@rianby64 I don't think this would necessarily break Angular. This would only apply within <template>, and presumably be a new API. template.content should return the same nodes as it does now, but something like template.likeTaggedTemplateLiteral(callback) could provide new functionality.

@rniwa

This comment has been minimized.

Show comment
Hide comment
@rniwa

rniwa Jan 12, 2017

Collaborator

Yeah, this will most likely be an opt-in feature. We can add a new attribute to enable this. e.g.

<template processor=“myLibrary.templateFunction”>
   ~
</template>

Then we may have, in scripts:

myLibrary = {~};
myLibrary.templateFunction = (template, parts) => {
    return parts.forEach((part) => { eval(part); })
}

where parts is an array of strings wrapped in {{ and }}. I think this model is a bit problematic in that most of template library would like to know the context in which these strings appear so more realistic model introduces a new interface that wraps each part so that you can query things like: part.element, part.attribute, etc...

Collaborator

rniwa commented Jan 12, 2017

Yeah, this will most likely be an opt-in feature. We can add a new attribute to enable this. e.g.

<template processor=“myLibrary.templateFunction”>
   ~
</template>

Then we may have, in scripts:

myLibrary = {~};
myLibrary.templateFunction = (template, parts) => {
    return parts.forEach((part) => { eval(part); })
}

where parts is an array of strings wrapped in {{ and }}. I think this model is a bit problematic in that most of template library would like to know the context in which these strings appear so more realistic model introduces a new interface that wraps each part so that you can query things like: part.element, part.attribute, etc...

@justinfagnani

This comment has been minimized.

Show comment
Hide comment
@justinfagnani

justinfagnani Jan 12, 2017

There are a range of needs arounds templates, and many different ways they might be used. It'd be useful to list the needs to see how well a proposal might address them.

Some of the problems I'm aware of that that frameworks and template libraries encounter:

  1. Loading, finding and/or associating templates with components
  2. Finding expressions within attribute and text nodes
  3. Parsing expressions
  4. Evaluating expressions
  5. Stamping templates into nodes
  6. Re-evaluating templates to incrementally update previously stamped nodes.
  7. Implementing control flow constructs like if and repeat
  8. Implementing template composition or inheritance

In my experience, finding expressions within a template is the easiest problem of the above to tackle. Actual expression handling and incremental updates are the hard parts.

It would be great if platform supported template syntax could scale from a case where I just want to provide some data and get back nodes with expressions evaluated (all with workable defaults) to a case where I want to provide my own expression parser/evaluator, and want to get back a data structure I can use to bridge to a vdom or incremental-dom library.

The first simple use case would require some kind of "safe" or "controlled" eval that didn't have access to surrounding scopes. Something like evalScope(expression, scope). Then you could do:

<template id="foo"><div>{{ x }}</div></template>

Simple template stamping:

fooTemplate.eval({x: 42}); // <div>42</div>

Template interpreting with incremental updates:

const container = document.createElement('div');
container.appendChild(fooTemplate.eval(data));
const parsedTemplate = fooTemplate.parse(); // like template.content but expressions get their own nodes?

// later on data change, some made up APIs in here...
const walker = document.createTreeWalker(parsedTemplate, ...);
idom.patch(container, () => {
  while (walker.nextNode()) {
    const node = walker.currentNode;
    if (node.nodeType = NodeType.ELEMENT_NODE) {
      for (const attr of getAttributes(node)) {
        const attrs = new Map();
        attrs.set(attr.name, attr.hasExpression() ? evalScope(attr.expression, data) : attr.value;
      }
      idom.elementOpen(node.nodeName, attrs);
    } else if (node.nodeType = NodeType.TEXT_NODE) {
      if (node.hasExpression()) {
        idom.text(evalScope(node.expression, data));
      } else {
        idom.text(node.textContent);
      }
    } else { ... }
  }
});

I'm trying to show there that adding expression finding, parsing and safe eval could enable more advanced template rendering, maybe compatible with existing template systems, with much less code than now.

justinfagnani commented Jan 12, 2017

There are a range of needs arounds templates, and many different ways they might be used. It'd be useful to list the needs to see how well a proposal might address them.

Some of the problems I'm aware of that that frameworks and template libraries encounter:

  1. Loading, finding and/or associating templates with components
  2. Finding expressions within attribute and text nodes
  3. Parsing expressions
  4. Evaluating expressions
  5. Stamping templates into nodes
  6. Re-evaluating templates to incrementally update previously stamped nodes.
  7. Implementing control flow constructs like if and repeat
  8. Implementing template composition or inheritance

In my experience, finding expressions within a template is the easiest problem of the above to tackle. Actual expression handling and incremental updates are the hard parts.

It would be great if platform supported template syntax could scale from a case where I just want to provide some data and get back nodes with expressions evaluated (all with workable defaults) to a case where I want to provide my own expression parser/evaluator, and want to get back a data structure I can use to bridge to a vdom or incremental-dom library.

The first simple use case would require some kind of "safe" or "controlled" eval that didn't have access to surrounding scopes. Something like evalScope(expression, scope). Then you could do:

<template id="foo"><div>{{ x }}</div></template>

Simple template stamping:

fooTemplate.eval({x: 42}); // <div>42</div>

Template interpreting with incremental updates:

const container = document.createElement('div');
container.appendChild(fooTemplate.eval(data));
const parsedTemplate = fooTemplate.parse(); // like template.content but expressions get their own nodes?

// later on data change, some made up APIs in here...
const walker = document.createTreeWalker(parsedTemplate, ...);
idom.patch(container, () => {
  while (walker.nextNode()) {
    const node = walker.currentNode;
    if (node.nodeType = NodeType.ELEMENT_NODE) {
      for (const attr of getAttributes(node)) {
        const attrs = new Map();
        attrs.set(attr.name, attr.hasExpression() ? evalScope(attr.expression, data) : attr.value;
      }
      idom.elementOpen(node.nodeName, attrs);
    } else if (node.nodeType = NodeType.TEXT_NODE) {
      if (node.hasExpression()) {
        idom.text(evalScope(node.expression, data));
      } else {
        idom.text(node.textContent);
      }
    } else { ... }
  }
});

I'm trying to show there that adding expression finding, parsing and safe eval could enable more advanced template rendering, maybe compatible with existing template systems, with much less code than now.

@rniwa

This comment has been minimized.

Show comment
Hide comment
@rniwa

rniwa Jan 12, 2017

Collaborator

I agree that adding parsing & custom processing/eval function is the key to make it work for many frameworks & libraries.

I think dynamic updating is nice-to-have but not must-have for v1 of this API. For example, if that custom processing function could return an arbitrary list of node, or could get hung of an node/attribute getting instantiated, then such a mechanism could be implemented in JS.

Collaborator

rniwa commented Jan 12, 2017

I agree that adding parsing & custom processing/eval function is the key to make it work for many frameworks & libraries.

I think dynamic updating is nice-to-have but not must-have for v1 of this API. For example, if that custom processing function could return an arbitrary list of node, or could get hung of an node/attribute getting instantiated, then such a mechanism could be implemented in JS.

@Mevrael

This comment has been minimized.

Show comment
Hide comment
@Mevrael

Mevrael Jan 13, 2017

@justinfagnani

It would be great if platform supported template syntax could scale from a case where I just want to provide some data and get back nodes with expressions evaluated (all with workable defaults) to a case where I want to provide my own expression parser/evaluator, and want to get back a data structure I can use to bridge to a vdom or incremental-dom library.

This is exactly the final goal of this proposal.

However, implementing complicated parser at the beginning might be too much work to start from and, probably, for now we should just focus on simpler stuff which would allow just to put variables (including objects like author.name) into a parseTemplate() or similar function which would return a Node or a DocumentFragment.

So I would suggest for now discussing only those topics:

1. Should there be a global function parseTemlate() (or other name ideas?) If no what Interface/prototype/whatever that function/method should be part of?

2. Should the function above return a
a: Node always (only first template.content.childNode[0])
b: DocumentFragment always;
c: Node if template has only 1 node and fragment if there are > 1
d: other options?

3. Repeating single template many times if data is an Array of Objects and not an Object. Function should return in that case a DocumentFragment of nodes where each node is a same template but with a different data - for example to generate all table rows at once.

Mevrael commented Jan 13, 2017

@justinfagnani

It would be great if platform supported template syntax could scale from a case where I just want to provide some data and get back nodes with expressions evaluated (all with workable defaults) to a case where I want to provide my own expression parser/evaluator, and want to get back a data structure I can use to bridge to a vdom or incremental-dom library.

This is exactly the final goal of this proposal.

However, implementing complicated parser at the beginning might be too much work to start from and, probably, for now we should just focus on simpler stuff which would allow just to put variables (including objects like author.name) into a parseTemplate() or similar function which would return a Node or a DocumentFragment.

So I would suggest for now discussing only those topics:

1. Should there be a global function parseTemlate() (or other name ideas?) If no what Interface/prototype/whatever that function/method should be part of?

2. Should the function above return a
a: Node always (only first template.content.childNode[0])
b: DocumentFragment always;
c: Node if template has only 1 node and fragment if there are > 1
d: other options?

3. Repeating single template many times if data is an Array of Objects and not an Object. Function should return in that case a DocumentFragment of nodes where each node is a same template but with a different data - for example to generate all table rows at once.

@justinfagnani

This comment has been minimized.

Show comment
Hide comment
@justinfagnani

justinfagnani Jan 13, 2017

@rniwa for the incremental update case I'm talking about exposing the nodes so that it can be implemented in JS - not trying to bake in any incremental updating functionality into the platform (yet). In my snippet above I'm just iterating over the template nodes with expressions parsed out and calling into a library like incremental-dom to handle the updates.

To me parsing and evaluating expressions is key to this area because simply finding double-brace delimited expressions is trivial - it's by far the easiest task among everything that a template system has to take care of.

I implemented a template system on top <template> with a simplifying restriction that expressions have to occupy an entire text node or attributes. Checking for an expression is as easy as text.startsWith('{{') && text.endsWith('}}'): https://github.com/justinfagnani/stampino/blob/master/src/stampino.ts#L20

Even allowing arbitrary positions for expressions requires only a tiny more logic, and this work is often done once - making the performance benefits less than something that speeds up every render.

justinfagnani commented Jan 13, 2017

@rniwa for the incremental update case I'm talking about exposing the nodes so that it can be implemented in JS - not trying to bake in any incremental updating functionality into the platform (yet). In my snippet above I'm just iterating over the template nodes with expressions parsed out and calling into a library like incremental-dom to handle the updates.

To me parsing and evaluating expressions is key to this area because simply finding double-brace delimited expressions is trivial - it's by far the easiest task among everything that a template system has to take care of.

I implemented a template system on top <template> with a simplifying restriction that expressions have to occupy an entire text node or attributes. Checking for an expression is as easy as text.startsWith('{{') && text.endsWith('}}'): https://github.com/justinfagnani/stampino/blob/master/src/stampino.ts#L20

Even allowing arbitrary positions for expressions requires only a tiny more logic, and this work is often done once - making the performance benefits less than something that speeds up every render.

@rniwa

This comment has been minimized.

Show comment
Hide comment
@rniwa

rniwa Jan 14, 2017

Collaborator

@justinfagnani : Sure, by “parsing”, I mean that it needs to provide some kind of context to interact with the interleaved parts.

Okay, let's consider a concrete example.

<template id="foo"><div class="foo {{ y }}">{{ x }} world</div></template>

Here, x and y are variables we want to expose.

I think we want some interface that can represent a position in DOM like Range's boundary point and let the script set/get the text out of it. Let us call this object part for now. Then we want to be able to set string values:

y.value = 'bar'; // Equivalent to div.setAttribute('foo bar').
x.value = 'hello'; // Equivalent to div.textContent = 'hello world';  We might want to keep 'hello' as a separate text node for simplicity.

We might want to replace the content with a list of elements:

y.replace([document.createElement('span'), 'hello']); // Throws.
x.replace([document.createElement('span'), 'hello']); // Inserts span and a text node whose value is "hello" as children of div before ' world'.
x.replaceHTML('<b>hello</b>'); Inserts the result of parsing the HTML before ' world'.

We also want to figure out where these things exist in the template:

y.expression; // Returns y, or whatever expression that appeared with in {{ and }}.
y.attributeOwner; // Returns div.
y.attributeName; // Returns "class".
y.attributeParts; // Returns ['foo ', y].  If class="foo {{y}} {{z}}" then we'd have ['foo', y, ' ', z] instead.

x.expression; // Returns x.
x.parentNode; // Returns div.
x.nextSibling; // Returns the text node of ' world'.
x.previousSibling // Returns null.

As for how to get these parts objects, we can either have a callback for each part as the engine parses it or can have a single callback for the entire thing. I'd imagine having a single callback is better for performance because going back & forth between the engine code & JS is slow. So how about something like this:

foo.processor = (clonedContent, parts, params) => {
  [y, x] = parts;
  y.value = params.y;
  x.value = params.x;
  return clonedContent;
}
foo.createInstance({y: 'foo', x: 'hello'});

Supporting looping or conditional constructs that appear in many templating languages with this model requires a bit of thinking, however. Since the engine doesn't know that these constructs exist, it would probably create a single part object for each. Then how does library & framework clone these part objects so that it'll continue to function after such logical statements are evaluated?

To see why. Let's say we have:

<template id="list">
  <ul>
    {{foreach items}}
        <li class={{class}} data-value={{value}}>{{label}}</li>
    {{/foreach}}
  </ul>
</template>
list.processor = (clonedContent, parts, params) => {
  for (let part of parts) {
    ...
    if (command == 'foreach') {
      for (let item of params.items) {
          // BUT there are only one part for each: class, value, and label. 
      }
    }
    ...
  }
  return clonedContent;
}
list.createInstance({items: [{class: 'baz', value: 'baz', label: 'hello world'}]});

When this came up last time (in 2011?), we thought that these logical constructions need to have nested templates as in:

<template id="list">
  <ul>
    <template directive='foreach items'>
        <li class={{class}} data-value={{value}}>{{label}}</li>
    </template>
  </ul>
</template>

Then we can recurse whenever these constructs appear. To avoid having to traverse the cloned DOM, we can include each nested template instance in our part array so that we can do something like:

list.processor = function myProcessor(clonedContent, parts, params) {
  for (let part of parts) {
    ...
    if (part instance of HTMLTemplate) {
        [directive, directiveParam] = part.getAttribute('directive').split(' ');
        ...
        if (directive == 'foreach') {
          part.processor = myProcessor;
          part.replace(params[directiveParam].map((item) => { return part.createInstance(item); }));
        }
    }
    ...
  }
  return clonedContent;
}
list.createInstance({items: [{class: 'baz', value: 'baz', label: 'hello world'}]});

If people are fined with these nested template elements, I think this is a pretty clean approach.

Collaborator

rniwa commented Jan 14, 2017

@justinfagnani : Sure, by “parsing”, I mean that it needs to provide some kind of context to interact with the interleaved parts.

Okay, let's consider a concrete example.

<template id="foo"><div class="foo {{ y }}">{{ x }} world</div></template>

Here, x and y are variables we want to expose.

I think we want some interface that can represent a position in DOM like Range's boundary point and let the script set/get the text out of it. Let us call this object part for now. Then we want to be able to set string values:

y.value = 'bar'; // Equivalent to div.setAttribute('foo bar').
x.value = 'hello'; // Equivalent to div.textContent = 'hello world';  We might want to keep 'hello' as a separate text node for simplicity.

We might want to replace the content with a list of elements:

y.replace([document.createElement('span'), 'hello']); // Throws.
x.replace([document.createElement('span'), 'hello']); // Inserts span and a text node whose value is "hello" as children of div before ' world'.
x.replaceHTML('<b>hello</b>'); Inserts the result of parsing the HTML before ' world'.

We also want to figure out where these things exist in the template:

y.expression; // Returns y, or whatever expression that appeared with in {{ and }}.
y.attributeOwner; // Returns div.
y.attributeName; // Returns "class".
y.attributeParts; // Returns ['foo ', y].  If class="foo {{y}} {{z}}" then we'd have ['foo', y, ' ', z] instead.

x.expression; // Returns x.
x.parentNode; // Returns div.
x.nextSibling; // Returns the text node of ' world'.
x.previousSibling // Returns null.

As for how to get these parts objects, we can either have a callback for each part as the engine parses it or can have a single callback for the entire thing. I'd imagine having a single callback is better for performance because going back & forth between the engine code & JS is slow. So how about something like this:

foo.processor = (clonedContent, parts, params) => {
  [y, x] = parts;
  y.value = params.y;
  x.value = params.x;
  return clonedContent;
}
foo.createInstance({y: 'foo', x: 'hello'});

Supporting looping or conditional constructs that appear in many templating languages with this model requires a bit of thinking, however. Since the engine doesn't know that these constructs exist, it would probably create a single part object for each. Then how does library & framework clone these part objects so that it'll continue to function after such logical statements are evaluated?

To see why. Let's say we have:

<template id="list">
  <ul>
    {{foreach items}}
        <li class={{class}} data-value={{value}}>{{label}}</li>
    {{/foreach}}
  </ul>
</template>
list.processor = (clonedContent, parts, params) => {
  for (let part of parts) {
    ...
    if (command == 'foreach') {
      for (let item of params.items) {
          // BUT there are only one part for each: class, value, and label. 
      }
    }
    ...
  }
  return clonedContent;
}
list.createInstance({items: [{class: 'baz', value: 'baz', label: 'hello world'}]});

When this came up last time (in 2011?), we thought that these logical constructions need to have nested templates as in:

<template id="list">
  <ul>
    <template directive='foreach items'>
        <li class={{class}} data-value={{value}}>{{label}}</li>
    </template>
  </ul>
</template>

Then we can recurse whenever these constructs appear. To avoid having to traverse the cloned DOM, we can include each nested template instance in our part array so that we can do something like:

list.processor = function myProcessor(clonedContent, parts, params) {
  for (let part of parts) {
    ...
    if (part instance of HTMLTemplate) {
        [directive, directiveParam] = part.getAttribute('directive').split(' ');
        ...
        if (directive == 'foreach') {
          part.processor = myProcessor;
          part.replace(params[directiveParam].map((item) => { return part.createInstance(item); }));
        }
    }
    ...
  }
  return clonedContent;
}
list.createInstance({items: [{class: 'baz', value: 'baz', label: 'hello world'}]});

If people are fined with these nested template elements, I think this is a pretty clean approach.

@rniwa

This comment has been minimized.

Show comment
Hide comment
@rniwa

rniwa Jan 14, 2017

Collaborator

I guess an alternative approach to the conditionals & loops would be letting developers create part objects manually scripts to a specific place in DOM. That would allow scripts to create these part objects as they parse objects in addition to ones we automatically create initially.

Collaborator

rniwa commented Jan 14, 2017

I guess an alternative approach to the conditionals & loops would be letting developers create part objects manually scripts to a specific place in DOM. That would allow scripts to create these part objects as they parse objects in addition to ones we automatically create initially.

@justinfagnani

This comment has been minimized.

Show comment
Hide comment
@justinfagnani

justinfagnani Jan 14, 2017

I'm partial to nested templates, since it's been working very well so far in Polymer and my Stampino library. It also looks like it meshes very well with parts.

I really like the Part objects and being able to directly manipulate them, after creating a template instance from a new method.

This addresses one problem I kind of skip over as part 5) of my list above, and that's keeping track of where to insert content. vdom and incremental-dom approaches, as they have re-render-the-world semantics, can do extra work when conditionals and loops are involved - if they don't know that new nodes are being inserted to a location, they may change nodes that are after the insertion point which should instead be preserved. This is usually solved by assigning unique keys to nodes. Currently with <template> directives you can solve this problem by cloning the directives into the stamped output as placeholders, but this increases the overall node count, and has trouble in SVG.

Even though this doesn't address expression evaluation, it increases the ergonomics enough to be very useful, IMO, and I think it can be easily polyfilled :)

I'm almost afraid to bring up my next thought, but bear with me here... :)

I'm unsure about the stateful templateEl.processor setter, and how it relates to nested templates. It seems like the top-level template processor would need to understand the attributes/signals used on nested templates, or have some abstraction for that, when there's possibly an opportunity here to decouple nested templates from their parents a bit, and to rationalize the template processor function via custom elements.

That is, these could simply be customized templates with a special callback:

class FancyTemplate extends HTMLTemplateElement {
  cloneTemplateCallback(clonedContent, parts, params) {
    // ...
  }
}
customElements.register('fancy-template', FancyTemplate);
<template is="fancy-template"><div class="foo {{ y }}">{{ x }} world</div></template>

This allows for a few niceties like lazy-loading template processors and attaching them to all templates of a type with the same machinery we have for custom elements, creating auto-stamping templates that clone themselves on attach, etc. Of course this all can be done with wrapping to.

I think where it might really click with the rest of your API is with nested templates. If they are also just custom elements, they can interact with the parent template via an instance API to receive data:

<template is="fancy-template">
  <ul>
    <template is="for-each" items="{{items}}">
      <li class={{class}} data-value={{value}}>{{label}}</li>
    </template>
  </ul>
</template>
class FancyTemplate extends HTMLTemplateElement {
  cloneTemplateCallback(clonedContent, parts, params) {
    for (let part of parts) {
      if (part instance of HTMLTemplate) {
        // attributes parts, like items="{{items}}" must have been set first for this to work
        // passing params lets the directive access the outer scope
        part.replace(part.createInstance(params)); 
      } else if (part.isAttribute) {
        // in this hypothetical template system we set properties by default
        part.attributeOwner[part.attributeName] = params[part.expression];
      } else if (part.isText) {
        part.replace(params[part.expression]);
      }
    }
    return clonedContent;
  }
}

const list = new FancyTemplate();
list.createInstance({items: [{class: 'baz', value: 'baz', label: 'hello world'}]});

This example doesn't support incremental updates, but FancyTemplate.cloneTemplateCallback could stash parts and offer an update() that took the clonedContent and new data.

I do think overall this is a pretty clean approach.

justinfagnani commented Jan 14, 2017

I'm partial to nested templates, since it's been working very well so far in Polymer and my Stampino library. It also looks like it meshes very well with parts.

I really like the Part objects and being able to directly manipulate them, after creating a template instance from a new method.

This addresses one problem I kind of skip over as part 5) of my list above, and that's keeping track of where to insert content. vdom and incremental-dom approaches, as they have re-render-the-world semantics, can do extra work when conditionals and loops are involved - if they don't know that new nodes are being inserted to a location, they may change nodes that are after the insertion point which should instead be preserved. This is usually solved by assigning unique keys to nodes. Currently with <template> directives you can solve this problem by cloning the directives into the stamped output as placeholders, but this increases the overall node count, and has trouble in SVG.

Even though this doesn't address expression evaluation, it increases the ergonomics enough to be very useful, IMO, and I think it can be easily polyfilled :)

I'm almost afraid to bring up my next thought, but bear with me here... :)

I'm unsure about the stateful templateEl.processor setter, and how it relates to nested templates. It seems like the top-level template processor would need to understand the attributes/signals used on nested templates, or have some abstraction for that, when there's possibly an opportunity here to decouple nested templates from their parents a bit, and to rationalize the template processor function via custom elements.

That is, these could simply be customized templates with a special callback:

class FancyTemplate extends HTMLTemplateElement {
  cloneTemplateCallback(clonedContent, parts, params) {
    // ...
  }
}
customElements.register('fancy-template', FancyTemplate);
<template is="fancy-template"><div class="foo {{ y }}">{{ x }} world</div></template>

This allows for a few niceties like lazy-loading template processors and attaching them to all templates of a type with the same machinery we have for custom elements, creating auto-stamping templates that clone themselves on attach, etc. Of course this all can be done with wrapping to.

I think where it might really click with the rest of your API is with nested templates. If they are also just custom elements, they can interact with the parent template via an instance API to receive data:

<template is="fancy-template">
  <ul>
    <template is="for-each" items="{{items}}">
      <li class={{class}} data-value={{value}}>{{label}}</li>
    </template>
  </ul>
</template>
class FancyTemplate extends HTMLTemplateElement {
  cloneTemplateCallback(clonedContent, parts, params) {
    for (let part of parts) {
      if (part instance of HTMLTemplate) {
        // attributes parts, like items="{{items}}" must have been set first for this to work
        // passing params lets the directive access the outer scope
        part.replace(part.createInstance(params)); 
      } else if (part.isAttribute) {
        // in this hypothetical template system we set properties by default
        part.attributeOwner[part.attributeName] = params[part.expression];
      } else if (part.isText) {
        part.replace(params[part.expression]);
      }
    }
    return clonedContent;
  }
}

const list = new FancyTemplate();
list.createInstance({items: [{class: 'baz', value: 'baz', label: 'hello world'}]});

This example doesn't support incremental updates, but FancyTemplate.cloneTemplateCallback could stash parts and offer an update() that took the clonedContent and new data.

I do think overall this is a pretty clean approach.

@rniwa

This comment has been minimized.

Show comment
Hide comment
@rniwa

rniwa Jan 14, 2017

Collaborator

That's an interesting approach but if a template element is specialized for a specific purpose at the time of creation, it doesn't address your concern about processor being stateful.

Perhaps it's better for templete.createInstance to take a second argument which is a function that processes it.

Alternatively, template.createInstance could just be a function that returns a cloned tree with a new node type which has parts objects as it's property such that library can manipulate them.

e.g.

function instantiate(template, params) {
  let instance = template.createInstance();
  instance.parts.map((part) => {
    part.value = params[part.expression];
  });
  return instance;

One drawback with this approach is that we almost always need some framework / library code to supplement this feature. But this may not be the end of the world.

Collaborator

rniwa commented Jan 14, 2017

That's an interesting approach but if a template element is specialized for a specific purpose at the time of creation, it doesn't address your concern about processor being stateful.

Perhaps it's better for templete.createInstance to take a second argument which is a function that processes it.

Alternatively, template.createInstance could just be a function that returns a cloned tree with a new node type which has parts objects as it's property such that library can manipulate them.

e.g.

function instantiate(template, params) {
  let instance = template.createInstance();
  instance.parts.map((part) => {
    part.value = params[part.expression];
  });
  return instance;

One drawback with this approach is that we almost always need some framework / library code to supplement this feature. But this may not be the end of the world.

@rniwa

This comment has been minimized.

Show comment
Hide comment
@rniwa

rniwa Jan 14, 2017

Collaborator

Note that I can see some library may want to dynamically change the "processor" function at runtime based on the semantics or even "directive" specified on a template if it appears inside some other library-specific constructs. It may even need to combine multiple functions to create a processor at runtime (e.g. Pipelining filters).

From that perspective, passing a processor function as an argument to createInstance seems pretty clean to me because then template element doesn't hold onto any state.

We could even make this argument optional and make it possible to specify the default one via attribute so that an user of a library that utiliziss this feature doesn't have to specify it when calling createInstance.

Collaborator

rniwa commented Jan 14, 2017

Note that I can see some library may want to dynamically change the "processor" function at runtime based on the semantics or even "directive" specified on a template if it appears inside some other library-specific constructs. It may even need to combine multiple functions to create a processor at runtime (e.g. Pipelining filters).

From that perspective, passing a processor function as an argument to createInstance seems pretty clean to me because then template element doesn't hold onto any state.

We could even make this argument optional and make it possible to specify the default one via attribute so that an user of a library that utiliziss this feature doesn't have to specify it when calling createInstance.

@Mevrael

This comment has been minimized.

Show comment
Hide comment
@Mevrael

Mevrael Jan 14, 2017

Thanks everyone for interest and replies

To make this discussion organized and moving forward step-by-step please share your opinion by answering those questions:

For now let presume that there is something called a "Template Function (TF)" which needs a use-cases 1st, API 2nd, and an implementation third.

1. What TF should exactly do?

1.1. Only parse template.innerHTML/contents, everyone is ok with {{ }} syntax, what about spaces?

I would stay with {{ }}, spaces doesn't matter, they could be trimmed first.

1.2. Attach event handlers (how handlers should be defined - in the same function or there should be another one, should handlers be attached during TF call or they should be attached manually later?)

There are 2 options.

  1. Both parsing and attaching event handlers happens in the same TF, i.e. parseTemplate(tpl, data, handlers), however that approach won't allow to register a new event handlers outside of the TF.
  2. Have a "Template Events Function (TEF)", for example a new global function registerTemplateEvents(tpl, handlers) or a template.registerHandlers(handlers). This approach is more flexible and there could be a template.handlers property. When a TF is called and a Node/DocumentFragment returned - all the event handlers are already attached

Example syntax with a new handler attribute: <template id="card">...<button handler="delete">...</template>. and TEF(tpl, {delete(btn) { ... }}).

Totally against attaching event handlers manually later. They can be defined later (Option 2) but are still attached when a TF is called.

1.3. Have a built-in lexer and expose all the statements/variables to something like template.expressions?

Not a big fan of that, however, the only use-case I see is allowing developers adding a custom control structures/syntax to a template statements, for example to register {{ doit(user.name) }} and to do so there could be a a new function/method template.registerLexer()

1.4. What TF should parse itself (recognize) by default?

  • variables, including object properties
  • if, endif, inside only variables and ==, ===, !=, !==, >, >=, <, <= allowed
  • since JS don't have foreach - for (indexOrPropertyName in variable) and endfor

2. Should TF be a new global function, or a new method (where?), or a new instance (of what?). TF example names?

There is no need in complicating things and I am totally against any "instances" and class syntactic sugar. A new global function parseTemplate(template, data, ...?) would be enough. We don't have any global Template like JSON anyway. For now we have only a DocuemntFragment available in the template.elements, however, there also could be a template.parse() method. Since templates are always referenced by IDs I still like a short syntax with a parseTemplate(templateId, ...) more.

3. What TF should return? Always a Node and ignore rest child elements, or always a DocumentFragment, or it depends (on what, elaborate)?

First of all in my vision if there are multiple child nodes in DocumentFragment, ignore them, template usually have only one main element child

And about the return type, it depends:

  1. If params is an object, return a Node
  2. If params is an Array of Objects - return DocumentFragment, just parse same template but with different data sets, useful for table rows, list items, etc.

4. What about a nested templates?

May be leave it for now and discuss it later.

Mevrael commented Jan 14, 2017

Thanks everyone for interest and replies

To make this discussion organized and moving forward step-by-step please share your opinion by answering those questions:

For now let presume that there is something called a "Template Function (TF)" which needs a use-cases 1st, API 2nd, and an implementation third.

1. What TF should exactly do?

1.1. Only parse template.innerHTML/contents, everyone is ok with {{ }} syntax, what about spaces?

I would stay with {{ }}, spaces doesn't matter, they could be trimmed first.

1.2. Attach event handlers (how handlers should be defined - in the same function or there should be another one, should handlers be attached during TF call or they should be attached manually later?)

There are 2 options.

  1. Both parsing and attaching event handlers happens in the same TF, i.e. parseTemplate(tpl, data, handlers), however that approach won't allow to register a new event handlers outside of the TF.
  2. Have a "Template Events Function (TEF)", for example a new global function registerTemplateEvents(tpl, handlers) or a template.registerHandlers(handlers). This approach is more flexible and there could be a template.handlers property. When a TF is called and a Node/DocumentFragment returned - all the event handlers are already attached

Example syntax with a new handler attribute: <template id="card">...<button handler="delete">...</template>. and TEF(tpl, {delete(btn) { ... }}).

Totally against attaching event handlers manually later. They can be defined later (Option 2) but are still attached when a TF is called.

1.3. Have a built-in lexer and expose all the statements/variables to something like template.expressions?

Not a big fan of that, however, the only use-case I see is allowing developers adding a custom control structures/syntax to a template statements, for example to register {{ doit(user.name) }} and to do so there could be a a new function/method template.registerLexer()

1.4. What TF should parse itself (recognize) by default?

  • variables, including object properties
  • if, endif, inside only variables and ==, ===, !=, !==, >, >=, <, <= allowed
  • since JS don't have foreach - for (indexOrPropertyName in variable) and endfor

2. Should TF be a new global function, or a new method (where?), or a new instance (of what?). TF example names?

There is no need in complicating things and I am totally against any "instances" and class syntactic sugar. A new global function parseTemplate(template, data, ...?) would be enough. We don't have any global Template like JSON anyway. For now we have only a DocuemntFragment available in the template.elements, however, there also could be a template.parse() method. Since templates are always referenced by IDs I still like a short syntax with a parseTemplate(templateId, ...) more.

3. What TF should return? Always a Node and ignore rest child elements, or always a DocumentFragment, or it depends (on what, elaborate)?

First of all in my vision if there are multiple child nodes in DocumentFragment, ignore them, template usually have only one main element child

And about the return type, it depends:

  1. If params is an object, return a Node
  2. If params is an Array of Objects - return DocumentFragment, just parse same template but with different data sets, useful for table rows, list items, etc.

4. What about a nested templates?

May be leave it for now and discuss it later.

@domenic

This comment has been minimized.

Show comment
Hide comment
@domenic

domenic Jan 14, 2017

Member

@Mevrael hey, just wanted to poke my head in here and lend some hopefully-wisdom to the great thread you started. Which is: don't try to keep too tight a reign on the discussion here :). You've gotten that rarest of things---implementer and framework interest---in a remarkably short time. I'd encourage you to let @rniwa and @justinfagnani continue their free-form exploration instead of trying to redirect their energies with large posts full of headings and to-do lists for them to address.

It's certainly up to you; just some friendly advice.

Member

domenic commented Jan 14, 2017

@Mevrael hey, just wanted to poke my head in here and lend some hopefully-wisdom to the great thread you started. Which is: don't try to keep too tight a reign on the discussion here :). You've gotten that rarest of things---implementer and framework interest---in a remarkably short time. I'd encourage you to let @rniwa and @justinfagnani continue their free-form exploration instead of trying to redirect their energies with large posts full of headings and to-do lists for them to address.

It's certainly up to you; just some friendly advice.

@Yay295

This comment has been minimized.

Show comment
Hide comment
@Yay295

Yay295 Jan 14, 2017

Contributor

@Mevrael

since JS don't have foreach

JS does have a foreach for arrays.

Contributor

Yay295 commented Jan 14, 2017

@Mevrael

since JS don't have foreach

JS does have a foreach for arrays.

@rniwa

This comment has been minimized.

Show comment
Hide comment
@rniwa

rniwa Jan 16, 2017

Collaborator
  1. I think we should agree on a single tokenizer / lexer. There is very little value in allowing different syntax. e.g. SGML allowed <, >, etc... to be replaced by other symbols but in practice, nobody did. Also one of the biggest benefit of standardizing templates is to have a common syntax at least for tokenizing things.

    However, I am of the opinion that we shouldn't natively support conditionals and logical statements like for, if, etc... at least in v1. Libraries and frameworks are quite opinionated about how to approach them, and I'd rather have an API which allows framework authors to easily implement those semantics than baking them into the platform at least for now.

    I don't understand what you're referring to by "event handler". If you're talking about regular event handlers like onclick, etc... then they should already work.

  2. It should probably be a method on HTMLTemplateElement's prototype. Adding new things in the global name scope for every new feature is not a scalable approach, and this doesn't need to be in the global scope at all since it's specific to templating.

  3. I don't see why we want to make that change. What are use cases which require this behavior? People have different opinions and preferences for these things, and it's not useful to discuss based on personal preferences and what you typically do. Instead, we need to evaluate each behavior & proposal using concrete use cases and evaluate against the cost of introducing such a feature / behavior.

  4. Nested templates is probably the best way of defining logical and looping so we absolutely have to consider it as a critical part of this feature.

Collaborator

rniwa commented Jan 16, 2017

  1. I think we should agree on a single tokenizer / lexer. There is very little value in allowing different syntax. e.g. SGML allowed <, >, etc... to be replaced by other symbols but in practice, nobody did. Also one of the biggest benefit of standardizing templates is to have a common syntax at least for tokenizing things.

    However, I am of the opinion that we shouldn't natively support conditionals and logical statements like for, if, etc... at least in v1. Libraries and frameworks are quite opinionated about how to approach them, and I'd rather have an API which allows framework authors to easily implement those semantics than baking them into the platform at least for now.

    I don't understand what you're referring to by "event handler". If you're talking about regular event handlers like onclick, etc... then they should already work.

  2. It should probably be a method on HTMLTemplateElement's prototype. Adding new things in the global name scope for every new feature is not a scalable approach, and this doesn't need to be in the global scope at all since it's specific to templating.

  3. I don't see why we want to make that change. What are use cases which require this behavior? People have different opinions and preferences for these things, and it's not useful to discuss based on personal preferences and what you typically do. Instead, we need to evaluate each behavior & proposal using concrete use cases and evaluate against the cost of introducing such a feature / behavior.

  4. Nested templates is probably the best way of defining logical and looping so we absolutely have to consider it as a critical part of this feature.

@rniwa

This comment has been minimized.

Show comment
Hide comment
@rniwa

rniwa Jan 16, 2017

Collaborator

But most importantly, we need a list of concrete use cases to evaluate each proposal.

Collaborator

rniwa commented Jan 16, 2017

But most importantly, we need a list of concrete use cases to evaluate each proposal.

@rniwa

This comment has been minimized.

Show comment
Hide comment
@rniwa

rniwa Jan 17, 2017

Collaborator

Since I had some time over the MLK weekend, I wrote a polyfill in ES2016 for TemplatePart:
https://bugs.webkit.org/show_bug.cgi?id=167135 (BSD licensed with Apple copyright).

Collaborator

rniwa commented Jan 17, 2017

Since I had some time over the MLK weekend, I wrote a polyfill in ES2016 for TemplatePart:
https://bugs.webkit.org/show_bug.cgi?id=167135 (BSD licensed with Apple copyright).

@Mevrael

This comment has been minimized.

Show comment
Hide comment
@Mevrael

Mevrael Jan 22, 2017

Why so complicated and why Apple copyright.

I've created a repository for making PRs on Use-cases and Implementation

https://github.com/Mevrael/html-template

And here is a first implementation which works currently only with vars and nested vars, i.e. author.name:
https://github.com/Mevrael/html-template/blob/master/template.js

It adds a parse(data) method to a template prototype.

Talking about the custom logic, I suppose many other methods I defined there could be also available in JS and authors, for custom logic could be able to replace any method on template prototype.

Talking about the nested templates, it is NOT part of this proposal, moreover, currently <template> inside a <template> is also not supported. I don't see any use-cases there.

Mevrael commented Jan 22, 2017

Why so complicated and why Apple copyright.

I've created a repository for making PRs on Use-cases and Implementation

https://github.com/Mevrael/html-template

And here is a first implementation which works currently only with vars and nested vars, i.e. author.name:
https://github.com/Mevrael/html-template/blob/master/template.js

It adds a parse(data) method to a template prototype.

Talking about the custom logic, I suppose many other methods I defined there could be also available in JS and authors, for custom logic could be able to replace any method on template prototype.

Talking about the nested templates, it is NOT part of this proposal, moreover, currently <template> inside a <template> is also not supported. I don't see any use-cases there.

@rniwa

This comment has been minimized.

Show comment
Hide comment
@rniwa

rniwa Jan 23, 2017

Collaborator

The most of complexity comes from having to monitor DOM mutations made by other scripts. Apple copyright comes from a simple fact that it is Apple's work (since I'm an Apple employee). Also, please don't move the discussion to another Github repo. For various reasons, many of us won't be able to look at your repository or participate in discussions that happen outside WHATWG/W3C issue trackers and repositories.

A template element inside a template element definitely works. In fact, this was one of the design constraints we had when we were spec'ing template elements. If anything with a template inside another template doesn't work that's a bug (either of a spec or of a browser).

Collaborator

rniwa commented Jan 23, 2017

The most of complexity comes from having to monitor DOM mutations made by other scripts. Apple copyright comes from a simple fact that it is Apple's work (since I'm an Apple employee). Also, please don't move the discussion to another Github repo. For various reasons, many of us won't be able to look at your repository or participate in discussions that happen outside WHATWG/W3C issue trackers and repositories.

A template element inside a template element definitely works. In fact, this was one of the design constraints we had when we were spec'ing template elements. If anything with a template inside another template doesn't work that's a bug (either of a spec or of a browser).

@baocang

This comment has been minimized.

Show comment
Hide comment
@baocang

baocang Sep 19, 2017

I'm very very expect of this feature, which will make HTML5's template easily to use likes other more javascript template engine.

baocang commented Sep 19, 2017

I'm very very expect of this feature, which will make HTML5's template easily to use likes other more javascript template engine.

@caub

This comment has been minimized.

Show comment
Hide comment
@caub

caub Oct 12, 2017

I think this template standard is interesting, with the variables addition, but what's most interesting are event handlers, and I think it's best solved with a template tagged function (https://github.com/caub/dom-tagged-template an attempt), but the validation is not as strong as <template> or jsx, since it's dynamic

caub commented Oct 12, 2017

I think this template standard is interesting, with the variables addition, but what's most interesting are event handlers, and I think it's best solved with a template tagged function (https://github.com/caub/dom-tagged-template an attempt), but the validation is not as strong as <template> or jsx, since it's dynamic

@rniwa

This comment has been minimized.

Show comment
Hide comment
@rniwa
Collaborator

rniwa commented Nov 2, 2017

@caub

This comment has been minimized.

Show comment
Hide comment
@caub

caub Nov 2, 2017

@rniwa I see, but sorry, it looks not practical to me, just like Angular, .. it tries to copy. A more React-like approach would be awesome, I'll try to make a proposal if I can

caub commented Nov 2, 2017

@rniwa I see, but sorry, it looks not practical to me, just like Angular, .. it tries to copy. A more React-like approach would be awesome, I'll try to make a proposal if I can

@snuggs

This comment has been minimized.

Show comment
Hide comment
@snuggs

snuggs Nov 2, 2017

Member

@rniwa FWIW createInstance => create.
Also, why not just evolve from Mustache? IMHO <template>{foo}</template>. Perhaps reserving {{escaped}} for brace escaping. A superior dev ergonomics experience than <template>{{foo}}</template>. I do realize old habits die hard but may be the perfect timing to make conventions more succinct if we have the opportunity. /cc @brandondees

Member

snuggs commented Nov 2, 2017

@rniwa FWIW createInstance => create.
Also, why not just evolve from Mustache? IMHO <template>{foo}</template>. Perhaps reserving {{escaped}} for brace escaping. A superior dev ergonomics experience than <template>{{foo}}</template>. I do realize old habits die hard but may be the perfect timing to make conventions more succinct if we have the opportunity. /cc @brandondees

@justinfagnani

This comment has been minimized.

Show comment
Hide comment
@justinfagnani

justinfagnani Nov 2, 2017

@snuggs the expression delimiter can be changed to anything reasonable - the proposal doe not hang on it being {{}}. I would strongly argue against {} though because it interferes with CSS and JavaScript. Look at how ugly it is to embed CSS in JSX.

justinfagnani commented Nov 2, 2017

@snuggs the expression delimiter can be changed to anything reasonable - the proposal doe not hang on it being {{}}. I would strongly argue against {} though because it interferes with CSS and JavaScript. Look at how ugly it is to embed CSS in JSX.

@justinfagnani

This comment has been minimized.

Show comment
Hide comment
@justinfagnani

justinfagnani Nov 2, 2017

@caub it's important to keep in mind the new capability this proposal includes: the ability to create and update large trees of DOM without having to do any tree walks or create wrapper objects for static nodes. That's the really important thing here for the platform and all kinds of frameworks and template libraries.

It's easy to look at the specific syntax and think that it looks like Angular or Handlebars, but in many cases users might not even be writing these template by hand - they'll be generated at compile time or runtime from some other template representation, even JSX.

lit-html, a HTML-template-in-JS library (that works and looks similar JSX) I've been working to on generates HTML templates from JS template literals. It would easily be able to take advantage of this proposal instead of it's own template part management. Someone even wrote a JSX-to-lit-html compiler, which could probably be changed to target this proposed spec directly, giving us a way to speed up JSX directly in the platform.

justinfagnani commented Nov 2, 2017

@caub it's important to keep in mind the new capability this proposal includes: the ability to create and update large trees of DOM without having to do any tree walks or create wrapper objects for static nodes. That's the really important thing here for the platform and all kinds of frameworks and template libraries.

It's easy to look at the specific syntax and think that it looks like Angular or Handlebars, but in many cases users might not even be writing these template by hand - they'll be generated at compile time or runtime from some other template representation, even JSX.

lit-html, a HTML-template-in-JS library (that works and looks similar JSX) I've been working to on generates HTML templates from JS template literals. It would easily be able to take advantage of this proposal instead of it's own template part management. Someone even wrote a JSX-to-lit-html compiler, which could probably be changed to target this proposed spec directly, giving us a way to speed up JSX directly in the platform.

@TejasQ

This comment has been minimized.

Show comment
Hide comment
@TejasQ

TejasQ Nov 2, 2017

1. How variables should be included inside a <template>, and 
3. Which statements (for example "if") are allowed and how they should be used inside a <template>

Similar to ${expression} in JS, it may be nice to not just substitute variables, but entire JS expressions in between delimiters: functions, conditionals, iterations. I believe this would answer both questions.

Additionally, I think the { } React-style delimiters are the most familiar for most developers (it is a fact that React is the most popular declarative UI framework, ergo most familiar). To make a stronger case for these delimiters, JS itself uses them, however prepended with the $ symbol in template literals.

How event handlers should be attached to elements inside a <template>.

My suggestion:

<template>

    <!-- Where `const doThing = () => something` is declared somewhere else -->
    <button onClick={() => doThing()}>Do it!</button>

    <!-- Alternatively, -->
    <button onClick={() => { let thing = 1; return thing + window.otherThing }>Add?</button>

</template>

It would be nice to pass the event to the event listener, which of course, contains the Node as well.

I feel most developers would have an easier time adopting this.

TejasQ commented Nov 2, 2017

1. How variables should be included inside a <template>, and 
3. Which statements (for example "if") are allowed and how they should be used inside a <template>

Similar to ${expression} in JS, it may be nice to not just substitute variables, but entire JS expressions in between delimiters: functions, conditionals, iterations. I believe this would answer both questions.

Additionally, I think the { } React-style delimiters are the most familiar for most developers (it is a fact that React is the most popular declarative UI framework, ergo most familiar). To make a stronger case for these delimiters, JS itself uses them, however prepended with the $ symbol in template literals.

How event handlers should be attached to elements inside a <template>.

My suggestion:

<template>

    <!-- Where `const doThing = () => something` is declared somewhere else -->
    <button onClick={() => doThing()}>Do it!</button>

    <!-- Alternatively, -->
    <button onClick={() => { let thing = 1; return thing + window.otherThing }>Add?</button>

</template>

It would be nice to pass the event to the event listener, which of course, contains the Node as well.

I feel most developers would have an easier time adopting this.

@justinfagnani

This comment has been minimized.

Show comment
Hide comment
@justinfagnani

justinfagnani Nov 2, 2017

@TejasQ the delimiters are really the most changeable aspect of this proposal, and it's too easy to get bogged down in syntax discussions when the features are what matters here. That said :) ...

{} is a poor choice, IMO, because it breaks: <template><style>x-foo {color: blue;}</style></template>. While <style> tags are not so popular in JSX because most VDOM frameworks lack style scoping, they're extremely common in templates meant to stamp to Shadow DOM.

${} is enticing for matching JS, but it makes writing templates w/ interpolation more cumbersome in JS template literals: template.innerHTML = `<div>${foo}</div>`; <- here the ${} is interpreted as a template literal expression. To write this as an HTML expression, you'd have to escape: template.innerHTML = `<div>\${foo}</div>`;. A small annoyance, but I'd rather avoid it.

justinfagnani commented Nov 2, 2017

@TejasQ the delimiters are really the most changeable aspect of this proposal, and it's too easy to get bogged down in syntax discussions when the features are what matters here. That said :) ...

{} is a poor choice, IMO, because it breaks: <template><style>x-foo {color: blue;}</style></template>. While <style> tags are not so popular in JSX because most VDOM frameworks lack style scoping, they're extremely common in templates meant to stamp to Shadow DOM.

${} is enticing for matching JS, but it makes writing templates w/ interpolation more cumbersome in JS template literals: template.innerHTML = `<div>${foo}</div>`; <- here the ${} is interpreted as a template literal expression. To write this as an HTML expression, you'd have to escape: template.innerHTML = `<div>\${foo}</div>`;. A small annoyance, but I'd rather avoid it.

@justinfagnani

This comment has been minimized.

Show comment
Hide comment
@justinfagnani

justinfagnani Nov 2, 2017

Over in Chrome land we've been working on very similar things, and @domenic wrote up our ideas here: https://github.com/domenic/template-parts

It's a little smaller in scope, and the document less detailed, but it's mostly the same ideas.

justinfagnani commented Nov 2, 2017

Over in Chrome land we've been working on very similar things, and @domenic wrote up our ideas here: https://github.com/domenic/template-parts

It's a little smaller in scope, and the document less detailed, but it's mostly the same ideas.

@EisenbergEffect

This comment has been minimized.

Show comment
Hide comment
@EisenbergEffect

EisenbergEffect Nov 2, 2017

I've read over both specs. I have a lot of issues with them. I'm not feeling well right now, so I'm not going to be exhaustive or dump everything out here. I'd like to start with one basic request though. Please separate out the phases of:

  • template compilation
  • template instantiation
  • data binding

These seem to be all mixed together under the instantiate action. In practice, to make templating engines fast and to enable real-world usage patterns, these steps need to be separated.

EisenbergEffect commented Nov 2, 2017

I've read over both specs. I have a lot of issues with them. I'm not feeling well right now, so I'm not going to be exhaustive or dump everything out here. I'd like to start with one basic request though. Please separate out the phases of:

  • template compilation
  • template instantiation
  • data binding

These seem to be all mixed together under the instantiate action. In practice, to make templating engines fast and to enable real-world usage patterns, these steps need to be separated.

@Mevrael

This comment has been minimized.

Show comment
Hide comment
@Mevrael

Mevrael Nov 2, 2017

I've added Apple and Google proposal links to the 1st post.

I am not happy with them both either. I would say people are trying to add too much complexity at this stage.

We will move faster if we will first prepare a short list of features and will vote on them after that.

  1. Basic var replace {{ }}
  2. ifs
  3. loops
  4. update() feature, i.e. ability to track and update variables in templates after they were compiled/inserted into DOM aka one-way data binding.
  5. Nested <template>'s, i.e. template inside another template (I am not talking about foreach or similar syntax)
  6. event handling
  7. something else?

Mevrael commented Nov 2, 2017

I've added Apple and Google proposal links to the 1st post.

I am not happy with them both either. I would say people are trying to add too much complexity at this stage.

We will move faster if we will first prepare a short list of features and will vote on them after that.

  1. Basic var replace {{ }}
  2. ifs
  3. loops
  4. update() feature, i.e. ability to track and update variables in templates after they were compiled/inserted into DOM aka one-way data binding.
  5. Nested <template>'s, i.e. template inside another template (I am not talking about foreach or similar syntax)
  6. event handling
  7. something else?
@rniwa

This comment has been minimized.

Show comment
Hide comment
@rniwa

rniwa Nov 2, 2017

Collaborator

@justinfagnani, Thanks for pretty summarizing my thinking process on {{ vs ${ vs {. Those are exactly the reasons I went with {{. It's pretty important that this feature works well with CSS & JS template literals when we're considering to change the delimiters.

@EisenbergEffect our proposal doesn't have any native data binding beyond what the default template processor provides by default. The separation of template compilation & instantiation is an interesting idea. What kind of operations do you want to do during the compilation phase? FWIW, parsing/processing of templates won't be an issue because browsers can optimize it.

Collaborator

rniwa commented Nov 2, 2017

@justinfagnani, Thanks for pretty summarizing my thinking process on {{ vs ${ vs {. Those are exactly the reasons I went with {{. It's pretty important that this feature works well with CSS & JS template literals when we're considering to change the delimiters.

@EisenbergEffect our proposal doesn't have any native data binding beyond what the default template processor provides by default. The separation of template compilation & instantiation is an interesting idea. What kind of operations do you want to do during the compilation phase? FWIW, parsing/processing of templates won't be an issue because browsers can optimize it.

@EisenbergEffect

This comment has been minimized.

Show comment
Hide comment
@EisenbergEffect

EisenbergEffect Nov 2, 2017

The fact that the create API allows passing in a data object for replacement, implies that the binding phase is part of the creation phase, which it should not be.

EisenbergEffect commented Nov 2, 2017

The fact that the create API allows passing in a data object for replacement, implies that the binding phase is part of the creation phase, which it should not be.

@rniwa

This comment has been minimized.

Show comment
Hide comment
@rniwa

rniwa Nov 2, 2017

Collaborator

@TejasQ : Being able to use any JS expressions is totally possible with a custom template processor (just call eval). I think the only concern we had was that we wanted to prevent XSS as much as possible by design since innerHTML and friends historically had a bunch of bad XSS attack surfaces. How do you feel about making this an optional or a separate syntax. e.g. ${~} for any JS expression to match template literal but {{~}} for the template processor to do data binding.

Collaborator

rniwa commented Nov 2, 2017

@TejasQ : Being able to use any JS expressions is totally possible with a custom template processor (just call eval). I think the only concern we had was that we wanted to prevent XSS as much as possible by design since innerHTML and friends historically had a bunch of bad XSS attack surfaces. How do you feel about making this an optional or a separate syntax. e.g. ${~} for any JS expression to match template literal but {{~}} for the template processor to do data binding.

@rniwa

This comment has been minimized.

Show comment
Hide comment
@rniwa

rniwa Nov 2, 2017

Collaborator

@EisenbergEffect : Why? Telling something shouldn't be is very unproductive in standardization processes. We need objective empirically observable use cases or some other evidence as to what is a problem.

Note that createInstance certainly takes a JS object but it's really up to the template processor to handle it. There is no automatic storing of the JS object to a template instance. The default template processor, for example, completely forgets about the JS object passed to createInstance, and you'd have to call update with the same object next time you update it.

If you wanted to create just a template instance and not do any data bindings, you simply omit the JS object to createInstance. The fact it takes a JS object is sort of a syntax sugar over template.createInstance().update(state) to improve the developer ergonomics.

Collaborator

rniwa commented Nov 2, 2017

@EisenbergEffect : Why? Telling something shouldn't be is very unproductive in standardization processes. We need objective empirically observable use cases or some other evidence as to what is a problem.

Note that createInstance certainly takes a JS object but it's really up to the template processor to handle it. There is no automatic storing of the JS object to a template instance. The default template processor, for example, completely forgets about the JS object passed to createInstance, and you'd have to call update with the same object next time you update it.

If you wanted to create just a template instance and not do any data bindings, you simply omit the JS object to createInstance. The fact it takes a JS object is sort of a syntax sugar over template.createInstance().update(state) to improve the developer ergonomics.

@Yay295

This comment has been minimized.

Show comment
Hide comment
@Yay295

Yay295 Nov 2, 2017

Contributor

There seems to be a lot of JavaScript in this HTML. If someone disables JavaScript, are templates still supposed to work?

Contributor

Yay295 commented Nov 2, 2017

There seems to be a lot of JavaScript in this HTML. If someone disables JavaScript, are templates still supposed to work?

@matthewp

This comment has been minimized.

Show comment
Hide comment
@matthewp

matthewp Nov 2, 2017

Templates already don't work without JavaScript.

matthewp commented Nov 2, 2017

Templates already don't work without JavaScript.

@justinfagnani

This comment has been minimized.

Show comment
Hide comment
@justinfagnani

justinfagnani Nov 2, 2017

@EisenbergEffect

The fact that the create API allows passing in a data object for replacement, implies that the binding phase is part of the creation phase, which it should not be.

Interpolating the data during clone is actually a very important part of this proposal.

lit-html, which is based on a lot of the ideas here, currently has two-phase template instantiation:

  1. Clone the template with no values
  2. Call TemplateInstance#update() to interpolate the values.

This is done only because there's currently no way to customize cloning in Document#importNode(). The problem is that between the clone and the update() call, Custom Element reactions like construction, connectedCallback and attributeChangedCallback run, while the DOM is in an incomplete, intermediate state. Then the values are populated, causing possibly another round of attributeChangedCallback, but also more construction and connectedCallbacks if some of the values are themselves TemplateInstances.

It'll be both faster and more correct to create the initial DOM with values filled in and run all the reactions at the end of createInstance() before returning to user script.

justinfagnani commented Nov 2, 2017

@EisenbergEffect

The fact that the create API allows passing in a data object for replacement, implies that the binding phase is part of the creation phase, which it should not be.

Interpolating the data during clone is actually a very important part of this proposal.

lit-html, which is based on a lot of the ideas here, currently has two-phase template instantiation:

  1. Clone the template with no values
  2. Call TemplateInstance#update() to interpolate the values.

This is done only because there's currently no way to customize cloning in Document#importNode(). The problem is that between the clone and the update() call, Custom Element reactions like construction, connectedCallback and attributeChangedCallback run, while the DOM is in an incomplete, intermediate state. Then the values are populated, causing possibly another round of attributeChangedCallback, but also more construction and connectedCallbacks if some of the values are themselves TemplateInstances.

It'll be both faster and more correct to create the initial DOM with values filled in and run all the reactions at the end of createInstance() before returning to user script.

@justinfagnani

This comment has been minimized.

Show comment
Hide comment
@justinfagnani

justinfagnani Nov 4, 2017

@snuggs I was referring to JSX's choice of {} making it difficult to write style tags, not whether templates are in JavaScript or HTML. If we used {} in <template>the same problem would apply.

Anyway, this really is the least interesting part of this discussion. The delimiter can be changed without effecting the rest of the proposal at all.

justinfagnani commented Nov 4, 2017

@snuggs I was referring to JSX's choice of {} making it difficult to write style tags, not whether templates are in JavaScript or HTML. If we used {} in <template>the same problem would apply.

Anyway, this really is the least interesting part of this discussion. The delimiter can be changed without effecting the rest of the proposal at all.

@snuggs

This comment has been minimized.

Show comment
Hide comment
@snuggs

snuggs Nov 4, 2017

Member

@justinfagnani Hence why i deleted my comment in retrospect as I see your point for sure. But must have empathy for what may be the least interesting to you doesn't speak for rest of community. As long as it's been noted (multiple times) it's something people care about there is interest. 🙏

Member

snuggs commented Nov 4, 2017

@justinfagnani Hence why i deleted my comment in retrospect as I see your point for sure. But must have empathy for what may be the least interesting to you doesn't speak for rest of community. As long as it's been noted (multiple times) it's something people care about there is interest. 🙏

@EisenbergEffect

This comment has been minimized.

Show comment
Hide comment
@EisenbergEffect

EisenbergEffect Mar 14, 2018

@rniwa If it's just syntax over create/update, then that's fine, assuming there's no negative effect to not passing a data object. It's important that a program be able to instantiate a template in one place, and then pass the instance to some other part of the program to actually supply the data. We made the mistake of mixing the create and bind responsibilities early on during the alpha phase of Aurelia and it caused us a lot of problems in the context of the larger component model. So, we had to make a breaking change to split them apart.

EisenbergEffect commented Mar 14, 2018

@rniwa If it's just syntax over create/update, then that's fine, assuming there's no negative effect to not passing a data object. It's important that a program be able to instantiate a template in one place, and then pass the instance to some other part of the program to actually supply the data. We made the mistake of mixing the create and bind responsibilities early on during the alpha phase of Aurelia and it caused us a lot of problems in the context of the larger component model. So, we had to make a breaking change to split them apart.

@snuggs

This comment has been minimized.

Show comment
Hide comment
@snuggs

snuggs Mar 15, 2018

Member

@EisenbergEffect I remember that... 💜

Member

snuggs commented Mar 15, 2018

@EisenbergEffect I remember that... 💜

@rniwa

This comment has been minimized.

Show comment
Hide comment
@rniwa

rniwa Mar 18, 2018

Collaborator

@EisenbergEffect yeah, it seems like this is an important feature for libraries & frameworks. The polymer team had a proposal which fixes this problem. My only concern is that the API exposed to the end user should be simple but I'm sure we can come up with an API that satisfies both concerns.

Collaborator

rniwa commented Mar 18, 2018

@EisenbergEffect yeah, it seems like this is an important feature for libraries & frameworks. The polymer team had a proposal which fixes this problem. My only concern is that the API exposed to the end user should be simple but I'm sure we can come up with an API that satisfies both concerns.

@snuggs

This comment has been minimized.

Show comment
Hide comment
@snuggs

snuggs Mar 28, 2018

Member

...I'm sure we can come up with an API that satisfies both concerns. @rniwa

Apologize in advance for losing track of context. However this conversation has gotten quite detailed. Can you please refresh me with a comment link perhaps to the two "concerns"?

Thanks in advance 🙏

Member

snuggs commented Mar 28, 2018

...I'm sure we can come up with an API that satisfies both concerns. @rniwa

Apologize in advance for losing track of context. However this conversation has gotten quite detailed. Can you please refresh me with a comment link perhaps to the two "concerns"?

Thanks in advance 🙏

@rniwa

This comment has been minimized.

Show comment
Hide comment
@rniwa

rniwa Apr 2, 2018

Collaborator

Two concerns are:

  1. Framework & library authors need a way to "compile" a template before being instantiated.
  2. Users of a template shouldn't have to first compile then instantiate a template. (i.e. there should be a single step API for end-users of a template).
Collaborator

rniwa commented Apr 2, 2018

Two concerns are:

  1. Framework & library authors need a way to "compile" a template before being instantiated.
  2. Users of a template shouldn't have to first compile then instantiate a template. (i.e. there should be a single step API for end-users of a template).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment