Using if.bind in a template that is replaceable

I have this component

<template class="card">
    <require from="./card.styl"></require>
    <div class="card-header">
        <span class="card-header-title">
            ${title}
        </span>
        <template replaceable part="side-action">
            <a href="#" if.bind="link && link.length > 0" class="card-header-link" click.delegate="doAction()">
                ${link}
            </a>
        </template>
    </div>
    <div class="card-body">
        <slot></slot>
    </div>
</template>

I want to use this component but I want to add the replaceable template conditionally like this

<card title="${title}" link="Ver todos" link-action.call="doAlert()">
    <template if.bind="layoutOverriden" replace-part="side-action">
        <div>
            <em click.delegate="gotoPrevious()" class="dm dm-left-open"></em>
            <em click.delegate="gotoNext()" class="dm dm-right-open"></em>
        </div>
    </template>
</card>

This does not work, it always replace the card template with this one even with the if.bind, is there any way that I can achieve this behavior ?

1 Like

Have you tried wrapping it inside another template?

<template if.bind="layoutOverriden">
<template replace-part="side-action">
        <div>
            <em click.delegate="gotoPrevious()" class="dm dm-left-open"></em>
            <em click.delegate="gotoNext()" class="dm dm-right-open"></em>
        </div>
    </template></template>

Another, imho cleaner, option would be to conditionally render 2 cards

<template if.bind="layoutOverriden">
<card>with custom content</card>
</template>
<template else>
<card></card>
</template
2 Likes

I am using the second option, but I had to create some external html files to avoid code duplication, with compose.
The first option i’ve tried and it does not work =(
I’ve also tried to use processContent, but I could manage this to work

1 Like

Have you tried putting the if.bind inside the component? That is something like this?

<template replaceable part="side-action" if.bind="someCondition">
  <a href="#" if.bind="link && link.length > 0" class="card-header-link" click.delegate="doAction()">
    ${link}
  </a>
</template>

And then use it like.

<card some-condition.bind="layoutOverriden">
  <template replace-part="side-action">
        ...
  </template>
</card>
2 Likes

I have a working version using an similar approach, but I really would like to use the if.bind in template, and not in other places too, because its is very weak, if developer forget to send the bind in card component I will not render like it should, so thats why I want to have this replacement based on the existence of the template with replace-part.

Thats my current version of something like you proposed

card.html

<template class="card">
    <require from="./card.styl"></require>
    <div class="card-header">
        <span class="card-header-title">
            ${title}
        </span>
        <template if.bind="!sideActionReplaced" containerless>
            <a href="#" if.bind="link && link.length > 0" class="card-header-link" click.delegate="doAction()">
                ${link}
            </a>
        </template>
        <template else replaceable part="side-action" containerless>
        </template>
    </div>
    <div class="card-body">
        <slot></slot>
    </div>
</template>

card.ts

import { oneTime } from 'utils';
import { autoinject, bindable } from 'aurelia-framework';

@autoinject()
    export class Card {
        @bindable(oneTime) title: string;
        @bindable(oneTime) link: string;
        @bindable(oneTime) linkAction: Function;
        @bindable(oneTime) sideActionReplaced = false;

        constructor(private readonly element: Element) { }

        doAction() {
            this.linkAction();
        }
    }

usage

<card title="${title}" link="Ver todos" side-action-replaced.bind="layoutOverriden" link-action.call="doAlert()">
    <template replace-part="side-action">
        <div>
            <em click.delegate="gotoPrevious()" class="dm dm-left-open"></em>
            <em click.delegate="gotoNext()" class="dm dm-right-open"></em>
        </div>
    </template>
    <!-- more code here -->
</card>
1 Like

Nice to hear that you have a working version. Cheers :tada:

I would like to point out couple of things. First, when you say

…if developer forget to send the bind in card component I will not render like it should…

I see that as a documentation issue. Because the same argument applies to every custom elements with @bindables. I think this problem should be tackled with proper documentation.

Second, from your example it seems that the following is the fallback content of the side-action slot/part.

<a href="#" if.bind="link && link.length > 0" class="card-header-link" click.delegate="doAction()">
  ${link}
</a>

If that’s really the case, then we don’t even need the gymnastic with the whole if.bind just use it as the fallback content in the side-action slot/part.

Finally, as far as I remember, <template> are by default containerless. As long as you have a template controller like if, else, repeat.for etc. on it, the template content will be displayed.

Have fun :slight_smile:

1 Like

If that’s really the case, then we don’t even need the gymnastic with the whole if.bind just use it as the fallback content in the side-action slot/part.

Thats the problem, it does not work.
If I add the <a> as a fallback value inside the empty template, it will always be overridden, no matter what if the “replace-part” is with an if.bind="false"

I see that as a documentation issue. Because the same argument applies to every custom elements with @bindable s. I think this problem should be tackled with proper documentation.

I dont agree, because I am working with the framework features, when some feature does not work, I have to create and workaround, and that is the problem.

1 Like

With fallback I meant something like this: Dumber Gist. This example uses the <a> purely as a fallback. If you have no usage of the bindable condition in your card CE, other than controlling the fallback, then you can get rid of that like it is done in the example, and let the the replaceable fallback work as it should.

1 Like

Take a look on this

1 Like

I understand. I think that’s not supported, and it is also unlikely that will be supported in Au2. In that case, the if-else in the CE itself is the best option IMO.

The reason it is unlikely to be supported in Au2 is that templating related to content projection is inherently a “compile” (this is the jit compilation of template, and not to be confused with the code comilation/bundler) time thing, as opposed to a “runtime” thing as usually expected with if-like template controller. @bigopon I believe that it is not much different for Au1 as well, right?

1 Like

Thanks for the help!

For

    ...
        <template else replaceable part="side-action" containerless>
        </template>

you don’t need containerless, since else/replaceable alone is already enough.

For the example

<my-element title="with replacement" link="foo-bar">
  <template if.bind="false" replace-part="side-action">
    <i>foo bar this should not have been shown</i>
  </template>
</my-element>

It will work if you change it to:

<my-element title="With replacement" link="foo-bar">
  <template replace-part="side-action">
    <template if.bind="false">
      <i>foo bar</i>
    </template>
  </template>
</my-element>

Running gist here Dumber Gist

The idea is replace-part template should be wrapping if:

  • alignment with compiler: the view compiler sees replace-part in a special way, so putting it a level above if makes you aware of that
  • good habit: placing multiple template controllers (if/else/repeat/containeless) on a single element works, but be careful with legacy Edge browser and IE, since they shuffle the attributes. You may not be able to predict the end result order of the attributes. If it feels good placing multiple template controllers on one element, then at least let replace-part wrap everything else.

For what @Sayan751 said with:

The reason it is unlikely to be supported in Au2 is that templating related to content projection is inherently a “compile” (this is the jit compilation of template, and not to be confused with the code comilation/bundler) time thing, as opposed to a “runtime” thing as usually expected with if -like template controller. @bigopon I believe that it is not much different for Au1 as well, right?

Yes. Custom element content extraction & projection are done similarly.

P/S: great answers @Sayan751

1 Like