TransWikia.com

LWC - change background color of selected item in a list

Salesforce Asked on October 4, 2021

I need to show list of cases retreived. Once the list is visible, there are two things i need to do with css that i am struggling with –

  1. When mouse is hovered over a list item, it’s border should become blue
  2. When a list item is selected (clicked), it’s background color should change to dark blue. Which also implies that if a different list item is selected, previously selected item should go back to it’s original background color.

Here is a sample picture that shows the desired result. There is an item that is clicked on, and there is an item that has mouse hove (blue boundary)

desired result

As far as implementation goes, i have a parent component – maMyCasesListView, and a child component maCaseListItem

Parent component’s job is to get list of cases from server. Then iterate over them. In each iteration, it passes the case to child component, which then displays the relevant details.

Parent component code maMyCasesListView
HTML

<template>
    <div class="container slds-p-around_large slds-scrollable_y">
        <div class="slds-text-title_caps slds-text-title_bold slds-p-bottom_medium">My Cases</div>
        <div>
            <template if:true={cases.data}>
                <lightning-layout class="slds-grid slds-grid_vertical" horizontal-align="left">
                    <template for:each={cases.data} for:item="currentcase">
                        <c-ma-case-list-item
                            class="slds-p-top_medium slds-p-bottom_medium"
                            key={currentcase.Id}
                            macase={currentcase}
                            onselect={handleSelect}
                        ></c-ma-case-list-item>
                    </template>
                </lightning-layout>
            </template>
        </div>       
    </div>
</template>

Javascript

import { LightningElement, wire  } from 'lwc';
import getCaseList from '@salesforce/apex/MA_CasesStore.getCaseList';

export default class MaMyCasesListView extends LightningElement {
    selectedcase;
    @wire(getCaseList) cases;

    handleSelect(event) {
        const caseId = event.detail;
        this.selectedcase = this.cases.data.find(
            (macase) => macase.Id === caseId
        );
        const selectEvent = new CustomEvent('caseselect', {
            detail: caseId
        });
        // Fire the custom event
        this.dispatchEvent(selectEvent);
    }

}

Code of child component-
HTML

<template>
    <div onclick={handleClick}>
        <lightning-layout horizontal-align="left" class="slds-grid slds-grid_vertical slds-text-align--left">
            <lightning-layout-item horizontal-align="left" class="slds-text-align--left">
                <b>{macase.Subject}</b>
            </lightning-layout-item>
            <lightning-layout-item class="slds-text-align--left slds-m-top_small">
                <lightning-layout>
                    <lightning-layout-item  horizontal-align="left" size="4">{macase.Status}</lightning-layout-item>
                    <lightning-layout-item horizontal-align="right">{macase.CaseNumber}</lightning-layout-item>
                </lightning-layout>
            </lightning-layout-item>
        </lightning-layout>
    </div>   
</template>

Javascript

import { LightningElement, api } from 'lwc';

export default class MaCaseListItem extends LightningElement {
    @api macase;
    
    

    handleClick(event) {

        const selectEvent = new CustomEvent('select', {
            detail: this.macase.Id
        });
        // Fire the custom event
        this.dispatchEvent(selectEvent);
    }   
}

2 Answers

Since Rahul's answer didn't address the border on mouse hover (and I was working on this anyway), I'm going to give you a second answer.

Key points:

  • Per Rahul, you want to use the custom select event to set a selected property to true on the selected child component
  • Similarly, you can use mouseover and mouseout events to set a public property on your child components that indicates if the mouse is hovering over that component or not.
  • When a component is selected or moused over, you will want to iterate through your child components to make sure that A) the selected / hovered-over component has its relevant property set to true and B) all others have that property set to false.
  • You can use slds-theme_inverse or slds-theme_alt-inverse to easily give your div a dark background and white text (as long as you like the colors Salesforce chose)
  • You will probably want to use custom CSS to set the border. Something like border: 2px solid #1589ee;, probably (#1589ee is defined as Salesforce's brand border color in Lightning Design System)
  • Borders have width to them. To avoid your components growing and shrinking as you add and remove the border, you'll want to make sure that un-moused-over components also have a border -- but a transparent one. Like this: border: 2px solid transparent;

Here's a Playground Link with a working prototype.

And here's another updated version of your code:

Parent HTML

<template>
    <div class="container slds-p-around_large slds-scrollable_y slds-theme_shade">
        <div class="slds-text-title_caps slds-text-title_bold slds-p-bottom_medium">
            My Cases
        </div>
        <div>
            <template if:true={cases.data}>
                <lightning-layout 
                    class="slds-grid slds-grid_vertical" 
                    horizontal-align="left"
                >
                    <template for:each={cases.data} for:item="currentcase">
                        <c-ma-case-list-item
                            data-id={currentcase.Id}
                            key={currentcase.Id}
                            macase={currentcase}
                            onselect={handleSelect}
                            onmouseover={handleMouseover}
                            onmouseout={handleMouseout}
                        ></c-ma-case-list-item>
                    </template>
                </lightning-layout>
            </template>
        </div>       
    </div>
</template>

Parent JS:

import { LightningElement, track, wire } from 'lwc';
import getCaseList from '@salesforce/apex/MA_CasesStore.getCaseList';

export default class MaMyCasesListView extends LightningElement {
    selectedCase;
    @wire(getCaseList) cases;

    handleSelect(event) {
        let caseId = event.detail;
        this.selectedCase = this.cases.data.find(c => c.Id === caseId);
        this.toggleListItems('selected', caseId);
    }

    handleMouseover(event) {
        this.toggleListItems('mouseIsOver', event.target.dataset.id);
    }

    handleMouseout(event) {
        event.target.mouseIsOver = false;
    }

    toggleListItems(property, caseId) {
        this.template.querySelectorAll('c-ma-case-list-item').forEach(item => {
            if (item.macase.Id === caseId) {
                item[property] = true;
            } else {
                item[property] = false;
            }
        });
    }
}

Child HTML

<template>
    <div 
        class={divClass}
        onclick={handleClick}
    >
        <lightning-layout 
            horizontal-align="left" 
            class="slds-grid slds-grid_vertical slds-text-align--left"
        >
            <lightning-layout-item class="slds-text-align--left">
                <b>{macase.Subject}</b>
            </lightning-layout-item>
            <lightning-layout-item class="slds-text-align--left slds-m-top_x-small">
                <lightning-layout>
                    <lightning-layout-item  size="4">
                        {macase.Status}
                    </lightning-layout-item>
                    <lightning-layout-item>
                        {macase.CaseNumber}
                    </lightning-layout-item>
                </lightning-layout>
            </lightning-layout-item>
        </lightning-layout>
    </div>
</template>

Child JS

import { LightningElement, api } from 'lwc';

export default class MaCaseListItem extends LightningElement {
    @api macase;
    @api selected;
    @api mouseIsOver;
    
    handleClick(event) {
        this.dispatchEvent(new CustomEvent('select', {
            detail: this.macase.Id
        }));
    }

    get divClass() {
        let cls = 'slds-p-around_small'
        if (this.selected) {
            cls += ' slds-theme_inverse';
        } 
        if (this.mouseIsOver) {
            cls += ' c-mouseover-border'
        }
        return cls;
    }
}

Child CSS

div {
    border: 2px solid transparent;
}

.c-mouseover-border {
    border: 2px solid #1589ee;
}

Correct answer by Matthew Souther on October 4, 2021

We use the event to send data from child to parent. Public attribute to send data from parent to child. Looks like you are trying to fire an event from parent to child which does not work.

Instead,

  1. You need to create a property on the case record to hold its selected state.
  2. Then you need a public property on the child component which will be set from the parent component.
  3. Now, when the div is clicked you need to fire an event from the child component to the parent. Which is you were doing right in your original code.
  4. In the select event handler, you need to iterate all cases and set the selected attribute of the particular selected case. And that's it.

Here is the code.

Child JS

Define a public property to hold the selected attribute.

@api selected = false;

handleClick(event) {
    const selectEvent = new CustomEvent('select', {
        detail: this.macase.Id
    });
    // Fire the custom event
    this.dispatchEvent(selectEvent);
}

// Set the background color class if this attribute is true.
get selectedClass(){
    return this.selected ? 'slds-theme_alt-inverse' : ''; // you can use your custom class here.
}

Child HTML

Note how I have set the dynamic class to the div on line to.

<template>
    <div onclick={handleClick} class={selectedClass}>
        <lightning-layout class="slds-grid slds-grid_vertical slds-text-align--left">
            <lightning-layout-item class="slds-text-align--left">
                <b>{macase.Subject}</b>
            </lightning-layout-item>
            <lightning-layout-item class="slds-text-align--left slds-m-top_small">
                <lightning-layout>
                    <lightning-layout-item  size="4">{macase.Status}</lightning-layout-item>
                    <lightning-layout-item>{macase.CaseNumber}</lightning-layout-item>
                </lightning-layout>
            </lightning-layout-item>
        </lightning-layout>
    </div>   
</template>

Parent JS

Now mark the selected cases when event is fired from the child.

handleSelect(event) {
    const caseId = event.detail;
    this.cases.forEach(mycase => {
        if (mycase.Id === caseId) {
            mycase.selected = true;
        } else {
            mycase.selected = false;
        }
    });
}

Live Playground

Note: As we are directly making changes to the wired result, you will need to deep clone it using this.cases = JSON.parse(JSON.stringify(data));. Also, you will need to use wired function instead of property.

Answered by Rahul Gawale on October 4, 2021

Add your own answers!

Ask a Question

Get help from others!

© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP