adobe / htl-spec

HTML Template Language Specification
Apache License 2.0
280 stars 146 forks source link

Support content projection in data-sly-template #67

Open adam-cyclones opened 6 years ago

adam-cyclones commented 6 years ago

In Angular and a multitude of other templating languages, it is possible to declare slots where templates can hold injected html, this is known as content projection, it is a method in which layout and content of layout are separated. This means that a template becomes extremely flexible and quick to refactor. I have found a very hacky way to do this currently (not practical in prod).

The workaround:

<sly data-sly-template.contentProjectionTest="${ @ FormInputSlot1, FormInputSlot2 }">
    <form>
        <div class='form-control-group-1'>
            ${FormInputSlot1 @context="html"}
        </div>
        <div class='form-control-group-2'>
            ${FormInputSlot2 @context="html"}    
        </div>
    </form>
</sly>

<!--/* Hacky variables for html as a string */-->
<sly data-sly-test.injectedContentToSlot="${'
    <label>creates a slot to inject content</label>
    <input type=\'text\' value=\'Hello world\'>
'}"></sly>

<sly data-sly-test.injectedContentToSlot2="${'
    <label>creates a slot to inject content</label>
    <h1>varient</h1>
'}"></sly>

<!--/* render and inject */-->
<sly data-sly-call="${contentProjectionTest @ 
    FormInputSlot1=injectedContentToSlot, 
    FormInputSlot2=injectedContentToSlot2
}"></sly>

This is less than ideal for several reasons • Escaped quotes • Markup as a string • Involved setup • No ability to have slot contents imported from files

I propose that the functionality could look like this:

<sly data-sly-template.contentProjectionTest>
    <form>
        <!--/*much more flexible template layout*/-->
        <div class='form-control-group-1'>
            <sly data-sly-slot="${contentProjectionTest @ FormInputSlot1}"/>
        </div>
        <div class='form-control-group-2'>
            <sly data-sly-slot="${contentProjectionTest @ FormInputSlot2}"/>
        </div>
    </form>
</sly>

<sly data-sly-call="${contentProjectionTest}">
    <sly data-sly-push="${contentProjectionTest @ FormInputSlot1}">
        <label>creates a slot to inject content</label>
        <input type=\'text\' value=\'Hello world\'>
    </sly>
    <sly data-sly-push"${contentProjectionTest @ FormInputSlot2}">
        <label>creates a slot to inject content</label>
        <h1>varient</h1>
    </sly>
</sly>`

I'd love to hear your thoughts.

raducotescu commented 6 years ago

@acronamy, I have the impression that you can already achieve this, with two or more templates:

HTL script:

<template data-sly-template.inner>
    <span>This is the inner template.</span>
</template>

<template data-sly-template.outer>
    <span class="outer" data-sly-call="${inner}"></span>
</template>

<div class="wrapper" data-sly-call="${outer}"></div>

Output:

<div class="wrapper">
    <span class="outer">
        <span>This is the inner template.</span>
    </span>
</div>

Does this example fit your use-case?

adam-cyclones commented 6 years ago

@raducotescu thanks for your reply, this is a better workaround than my own.

That sounds about right in the simplest use case, In the real world, I wouldn't have 2 templates in the same file as one would be /layouts/column.html and the other /content/select-list.html however so I would data-sly-use the content, so that means the inner template would have to be passed as a parameter. I use parameters in templates for attributes, text content and conditionals. I feel that this inner html insertion via params is a different idea from my current usage or params, which (is imo), not as clear using the method mentioned, a dedicated data-sly method would do more for clarity.

jantimon commented 4 years ago

Some years ago I had the same idea but I have never created a proposal.
Maybe the syntax could be changed but the idea of @adam-cyclones is essential for modern modularisation.

The feature is supported by all modern frontend rendering engines like react, angular or vue.

But also common rendering engines like handlebars support it

Angular:

<dashboard-tile a="10" b="5" c="15">
    Only believe in statistics you've faked yourself.
  </dashboard-tile>

React:

<DashboarTile a="10" b="5" c="15">
    Only believe in statistics you've faked yourself.
</DashboarTile>

Vue:

<dashboard-tile a="10" b="5" c="15">
    Only believe in statistics you've faked yourself.
</dashboard-tile>

Handlebars:

{{#dashboard-tile a="10" b="5" c="15"}}
    Only believe in statistics you've faked yourself.
{{/dashboard-tile}}

Why do I believe that the current htl solution proposed by @raducotescu is not enough?

Lets say you wrote an abstraction for a container, grid row, a grid column, a accordion and a typography:

<Container>
  <Row vertical-alignment="top">
     <Column size="4">
         <Typography size="xl" responsive font="light">Hello World</Typography>
      </Column>
     <Column size="8">
         <Accordion collapsed>
             <Typography size="m" responsive font="medium">Some text about world</Typography>
          </Accordion>
      </Column>
  </Row>
  <Row vertical-alignment="top">
     <Column size="8">
         <Typography size="xl" responsive font="light">Hello Mars</Typography>
      </Column>
     <Column size="4">
         <Accordion collapsed>
             <Typography size="m" responsive font="medium">Some Text about <a href="moon">moon</a></Typography>
          </Accordion>
      </Column>
  </Row>
</Container>

Maybe @raducotescu could correct me but I believe there is no good way to build reusable components similar to the example above in htl.

raducotescu commented 4 years ago

@jantimon, you're right, HTL's templates are not as advanced. I'll take this issue to our team and we'll try to figure out how it would work.

jantimon commented 4 years ago

@raducotescu do you have any update for us?

I guess the most simple way would be the following:

<sly data-sly-template.headline="${ @ children }">
    <h1>${children}</h1>
</sly>

<sly data-sly-call="${headline}">
   Hello World
</sly>

It would be backwards compatible and very similar to react or handlebars

adam-cyclones commented 4 years ago

6 months ago I raised this. It's an important feature and I hope it gets added. I no longer work on AEM for one of your bigger clients so I have no vested interest. But it's interesting to see how it would be solved.