3

I want to build a custom configurable interface to pass two buttons to header and footer of a standard card element.

So, I have a component card with card template

<template>
    <lightning-card  title={title}>
        <slot name="cancel" slot="actions"><lightning-button label="Cancel"></lightning-button></slot>
        <slot name="save" slot="actions"><lightning-button label="Save"></lightning-button></slot>

        Content

        <slot name="cancel" slot="footer"><lightning-button label="Cancel"></lightning-button></slot>
        <slot name="save" slot="footer"><lightning-button label="Save"></lightning-button></slot>

    </lightning-card>    
</template>

and another custom component customCard which uses this template

<template>
    <c-card>
        <lightning-button label="Cancel" title="Cancel" onclick={handleCancel} class="slds-m-left_x-small" 
            slot="cancel"></lightning-button>
        <lightning-button variant="success" label="Save" title="Save" onclick={handleSave} class="slds-m-left_x-small" 
            slot="save"></lightning-button>
    </c-card>
</template>

When I open card component, I see both buttons in the header and the footer enter image description here

When I open customCard component, I see only buttons in the footer but not in the header enter image description here

I have found that for example for another frameworks like vue.js it is possible to insert the same content into multiple slots using v-for

I tried to adopt this approach and define a property with slot list since I cannot set it directly

import { LightningElement } from 'lwc';

export default class Card extends LightningElement {
    slots = ['actions','footer'];
}

and then use template:forEach for LWC

    <template for:each={slots} for:item='slot'>
        <slot name="cancel" key={slot} slot={slot}><lightning-button label="Cancel"></lightning-button></slot>
    </template>
    <template for:each={slots} for:item='slot'>
        <slot name="save" key={slot} slot={slot}><lightning-button label="Save"></lightning-button></slot>
    </template>

however, when I try to deploy this code, it raises an exception

LWC1081: Slot attribute value can't be an expression.

Is there any way to work around this problem?


Update: I didn't mention that I would like to process the data from properties stored in customCard on save click

So actually I have something like following

<template>
    <lightning-card  title={title}>
        <slot name="cancel" slot="actions"><lightning-button label="Cancel"></lightning-button></slot>
        <slot name="save" slot="actions"><lightning-button label="Save"></lightning-button></slot>

        <slot>Content</slot>

        <slot name="cancel" slot="footer"><lightning-button label="Cancel"></lightning-button></slot>
        <slot name="save" slot="footer"><lightning-button label="Save"></lightning-button></slot>

    </lightning-card>    
</template>

and custom component

<template>
    <c-card>
        <lightning-button label="Cancel" title="Cancel" onclick={handleCancel} class="slds-m-left_x-small" 
            slot="cancel"></lightning-button>
        <lightning-button variant="success" label="Save" title="Save" onclick={handleSave} class="slds-m-left_x-small" 
            slot="save"></lightning-button>
        <lightning:input value={data1}></lightning:input>
        <lightning:input value={data2}></lightning:input>
         ....
        <lightning:input value={data100}></lightning:input> 
    </c-card>
</template>

and in handleSave

handleSave() {
   process(data1);
   process(data2);
   ...
   process(data100);
}

2 Answers 2

2

So far I ended up with the following code in template component

<template>
    <lightning-card  title={title}>
        <!-- <template for:each={slots} for:item='slot'>
            <slot name="cancel" key={slot} slot={slot}><lightning-button label="Cancel"></lightning-button></slot>
        </template>
        <template for:each={slots} for:item='slot'>
            <slot name="save" key={slot} slot={slot}><lightning-button label="Save"></lightning-button></slot>
        </template>-->


        <slot name="cancel1" slot="actions"><lightning-button label="Cancel"></lightning-button></slot>
        <slot name="save1" slot="actions"><lightning-button label="Save"></lightning-button></slot>

        Content

        <slot name="cancel2" slot="footer"><lightning-button label="Cancel"></lightning-button></slot>
        <slot name="save2" slot="footer"><lightning-button label="Save"></lightning-button></slot>

    </lightning-card>    
</template>

and the following code in the custom component

<template>
    <c-card>
        <lightning-button label="Cancel" title="Cancel" onclick={handleCancel} class="slds-m-left_x-small" 
            slot="cancel1"></lightning-button>
        <lightning-button variant="success" label="Save" title="Save" onclick={handleSave} class="slds-m-left_x-small" 
            slot="save1"></lightning-button>

        <lightning-button label="Cancel" title="Cancel" onclick={handleCancel} class="slds-m-left_x-small" 
            slot="cancel2"></lightning-button>
        <lightning-button variant="success" label="Save" title="Save" onclick={handleSave} class="slds-m-left_x-small" 
            slot="save2"></lightning-button>
    </c-card>
</template>

however, I personally consider this code ugly and repetitive and I would opt for a better and cleaner solution if such solution exists.

enter image description here

2

It's impossible to have the same DOM node in multiple places of the DOM tree. A DOM node can only have one parent, the same restriction applies to slotted content. (We should add a warning to the LWC compiler when a component has 2 slots with the same name)

I would recommend you to create 2 slots in your c-card component (header and footer), and to create another component c-actions containing your 2 buttons that will be passed into those slots. This way you don't have to have 4 components in the c-custom-card component.

<!-- c-card -->
<template>
    <lightning-card  title={title}>
        <slot name="header" slot="header">
           <lightning-button label="Cancel"></lightning-button>
           <lightning-button label="Save"></lightning-button>
        </slot>

        Content

        <slot name="footer" slot="footer">
           <lightning-button label="Cancel"></lightning-button>
           <lightning-button label="Save"></lightning-button>
        </slot>
    </lightning-card>    
</template>

<!-- c-custom-card -->
<template>
    <c-card>
        <c-actions slot="header"></c-actions>
        <c-actions slot="footer"></c-actions>
    </c-card>
</template>

<!-- c-actions -->
<template>
  <lightning-button label="Custom cancel"></lightning-button>
  <lightning-button label="Custom save"></lightning-button>
</template>
5
  • This seems interesting, but one of my goals here which I didn't mention was to have button on click handlers in custom card component so that I could process all on click handlers in one place. On save button click I would like to refer to properties inside custom card component so that I could process and save related changes from UI in custom card.
    – Patlatus
    Commented Mar 12, 2019 at 8:16
  • Nothing prevents you to handle the save click in the c-custom-card component even if the button is part of the c-actions component. You will need to catch the click event and re-dispatch a custom save or cancel event in the c-actions that will then be handled in the c-custom-card. The same approach has to also be applied to the slot fallback buttons in the c-card component.
    – pmdartus
    Commented Mar 12, 2019 at 10:56
  • I should try that, haven't yet experience with redispatching events from children to parents in LWC
    – Patlatus
    Commented Mar 13, 2019 at 9:57
  • @pmdartus is there an lwc pattern that would allow us to use slots inside a loop when iterating over a list? (e.g. <template for:each={}><slot></slot></template>) Assuming the error that I'm getting is related to your comment about having multiple DOM nodes in multiple places in the DOM tree. Commented May 2, 2019 at 22:20
  • 1
    @ScottMcClung you should not add <slot> in iterations. I don't know what is the current behavior in LWC, but it will not work as you expect with the standard shadow DOM. Elements will only be assigned to a single slot.
    – pmdartus
    Commented May 3, 2019 at 6:24

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .