Skip to content
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

Add support for template cascading #315

Open
nmetulev opened this issue Mar 6, 2020 · 3 comments
Open

Add support for template cascading #315

nmetulev opened this issue Mar 6, 2020 · 3 comments
Milestone

Comments

@nmetulev
Copy link
Contributor

nmetulev commented Mar 6, 2020

Description

The toolkit components should allow templates to cascade to children components to avoid multiple levels of re-templating.

Rationale

For example, to template parts of the person-card, the current approach forces the developer to define a template in a template. This also makes it difficult to use the templateRendered event as the developer needs to assign the event multiple times at each level.

This proposal simplifies defining templates to children components by allowing the developer to define them higher up.

Here is an example on how to update the additional-details template on a mgt-person-card today:

<mgt-person ...>
    <template data-type="person-card">
        <mgt-person-card inherit-details>
            <template data-type="additional-details">
                <div>My additional details</div>
            </template>
        </mgt-person-card>
    </template>
    <template data-type="loading">
        <div>loading...</div>
    </template>
</mgt-person>
  1. The developer defines a person-card template on mgt-person
  2. Inside the template, they create a new mgt-person-card.
  3. Inside the new mgt-person-card, the developer defines a new additional-details template

Note: I've also included a loading template in this example for completeness which will become relevant in the solutions below.

Preferred Solution

I've been thinking about the solution to this problem for a while now and I've outlined how my proposed solution has evolved here

Approach 1

This approach allows all templates to cascade down to the children of the component. Any template that is defined on the parent component would be passed down as templates to all the children components. :

<mgt-person ...>
    <template data-type="additional-details">
        <div>My additional details</div>
    </template>
    <template data-type="loading">
        <div>Loading</div>
    </template>
</mgt-person>

In this scenario, the mgt-person-card would get all templates defined for the parent mgt-person.

This approach is very simple and straightforward. It also allows templates to be defined at the root element and to cascade to any component in the tree. However, this approach also introduces several issues:

  • It is not clear that additional-details is intended for the mgt-person-card component.
  • This could introduce conflicts and unintended consequences if both the parent and the child have templates with the same name, such as loading

I believe these issues make this approach a non-starter and that is why I continued thinking about the problem.

Approach 2

This approach defines a new attribute on the template element: data-part. The data-part attribute values would be defined by the parent. For example, templating just the additional-details template of the person card in the person component would look like this:

<mgt-person ...>
    <template data-type="additional-details" data-part="person-card">
        <div>My additional details</div>
    </template>
    <template data-type="loading">
        <div>Loading</div>
    </template>
</mgt-person>

Where mgt-person has mapped the value person-card to the mgt-person-card and therefore all templates with data-part="person-card" would cascade down to the mgt-person-card.

This solution is not much more complex than solution 1 but addresses the issues from solution 1 by scoping the template to a specific part. However, templates only cascade a single level.

Approach 2.5

If we want to take this even further and solve the cascading issue of solution 2, the data-part attribute could act as a selector (similar to CSS selectors).

For example, imagine the scenario of templating the person-card inside a person inside the agenda component. Ex:

<mgt-agenda...>
    <template data-type="additional-details" data-part="attendee > person-card">
        <div>My additional details</div>
    </template>
    <template data-type="loading" data-part="attendee">
        <div>Loading</div>
    </template>
</mgt-person>

Here, data-part="attendee > person-card" applies the template to the mgt-person-card inside the mgt-person component used for the attendees in the agenda.

And if we wanted to have the flexibility of Solution 1, data-part="*" could act as a selector for all components.

What about the templateRendered event?

So far, we have not addressed how the templateRendered event would work. Today, the developer needs to register the event multiple times on the component:

mgtPerson.addEventListener('templateRendered', e=> {

  let personCard = e.details.element.querySelector('personCard');
  personCard.addEventListener('templateRendered', e => {

    let templateType = e.detail.templateType; // the data-type of the template
    let dataContext = e.detail.context; // the data context passed to the template
    let element = e.detail.element; // the root element of the rendered template

    let button = element.querySelector('button');
    if (button) {
      button.addEventListener('click', () => {});
    }

  }
});

The solution here is for the event to bubble up through the children components and contain the data-part attribute in its details so the developer can confidently identify which template was rendered, Here is how I'd rewrite the event listener:

mgtPerson.addEventListener('templateRendered', e=> {

    let templateType = e.detail.templateType; // the data-type of the template
    let dataContext = e.detail.context; // the data context passed to the template
    let element = e.detail.element; // the root element of the rendered template
    let part = e.detail.templatePart // the data-part selector for the template
    let target = e.target // the actual component that fired the event

    let button = element.querySelector('button');
    if (button) {
      button.addEventListener('click', () => {});
    }
});
@michael-hawker
Copy link
Contributor

Another approach could be about referencing templates (so they could be re-used across the document), similar to how resources work for XAML templates. 😋

    <template name="person-card-template">
        <mgt-person-card inherit-details>
            <template data-type="additional-details">
                <div>My additional details</div>
            </template>
        </mgt-person-card>
    </template>
    <template name="loading-template">
        <div>loading...</div>
    </template>
...
<mgt-person template-loading="loading-template" template-person-card="person-card-template"...>
</mgt-person>
<mgt-agenda template-loading="loading-template">
</mgt-agenda>

@nmetulev
Copy link
Contributor Author

Yeah, template referencing also makes sense in addition to cascading. I don't think it solves the issue alone as you still have to define template in a template

@shweaver-MSFT
Copy link
Contributor

shweaver-MSFT commented Mar 20, 2020

After a good chat around the various approaches, we have decided to go with approach 2, with data-part being a component tag name (e.g. mgt-person-card). We can then use that to create a concept of parentTemplates which may be referenced from child components during rendering.

@nmetulev nmetulev added the help wanted Extra attention is needed label Oct 12, 2020
@njaci1 njaci1 moved this to Needs Triage 🔍 in Graph Toolkit Sep 15, 2022
@gavinbarron gavinbarron moved this from Needs Triage 🔍 to Proposed 💡 in Graph Toolkit Jul 20, 2023
@sebastienlevert sebastienlevert added this to the Future milestone Jan 30, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: Proposed 💡
Development

No branches or pull requests

4 participants