Недавно наткнулся на LWC элемент lightning-tree. Когда нахлынувшие воспоминания о С++ разработке немного отступили, решил попробовать использовать его на каком-то простом примере. Поскольку аккаунты с контактами уже успели порядком поднадоесть, посвятим пример чему-либо интересному. Например, футболу.
Во избежание недоразумений уточню, что дерево, о котором идёт речь, не красно-чёрное, не бинарное и даже не хвойное, а выглядит примерно так:
Итак, пусть у нас будет список стран, которые являются ветвями нашего дерева и, раскрываясь по нажатию, показывают список своих топовых футбольных клубов. А чтобы было интереснее, дадим пользователю возможность выбрать свой любимый клуб, и выведем название выбранного клуба на UI где-нибудь неподалёку. А чтобы не хардкодить JSON со структурой дерева, как сделано в примерах из документации, создадим кастомные объекты для стран и клубов, где страна является мастером, а клуб - деталью (как-то топорно звучит на русском, да? ). Для работы с базой данных напишем апекс класс, который вытащит нужные записи из базы и отдаст их нам в нужном виде:
public with sharing class FootballService {
@AuraEnabled(cacheable=true)
public static List<TeamWrapper> getTeamsDataForTree(){
List<Football_country__c> countriesWithTeamsList = [SELECT Name,(SELECT Name FROM Football_teams__r) FROM Football_country__c];
List<TeamWrapper> countryWrappersList = new List<TeamWrapper>();
for(Football_country__c country : countriesWithTeamsList){
TeamWrapper countryWrapper = new TeamWrapper() ;
countryWrapper.name =country.Name ;
countryWrapper.label =country.Name ;
countryWrapper.expanded =false;
countryWrapper.isCountry = true;
List<TeamWrapper> teamsList = new List<TeamWrapper>();
for(Football_team__c team : country.Football_teams__r){
TeamWrapper teamWrapper = new TeamWrapper();
teamWrapper.name =team.Name ;
teamWrapper.label =team.Name ;
teamWrapper.expanded = false ;
teamWrapper.isCountry = false;
teamWrapper.items = new List<TeamWrapper>();
teamsList.add(teamWrapper);
}
countryWrapper.items = teamsList;
countryWrappersList.add(countryWrapper);
}
return countryWrappersList;
}
public Class TeamWrapper{
@AuraEnabled
public String name{get;set;}
@AuraEnabled
public String label{get;set;}
@AuraEnabled
public Boolean expanded{get;set;}
@AuraEnabled
public Boolean isCountry{get;set;}
@AuraEnabled
public List<TeamWrapper> items{get;set;}
}
}
В js коде JSON получится таким:
[
{
"expanded":false,
"isCountry":true,
"items":[
{
"expanded":false,
"isCountry":false,
"items":[
],
"label":"Arsenal",
"name":"Arsenal"
},
{
"expanded":false,
"isCountry":false,
"items":[
],
"label":"Liverpool",
"name":"Liverpool"
},
{
"expanded":false,
"isCountry":false,
"items":[
],
"label":"Manchester United",
"name":"Manchester United"
},
{
"expanded":false,
"isCountry":false,
"items":[
],
"label":"Manchester City",
"name":"Manchester City"
},
{
"expanded":false,
"isCountry":false,
"items":[
],
"label":"Chelsea",
"name":"Chelsea"
}
],
"label":"England",
"name":"England"
},
{
"expanded":false,
"isCountry":true,
"items":[
{
"expanded":false,
"isCountry":false,
"items":[
],
"label":"Real Madrid",
"name":"Real Madrid"
},
{
"expanded":false,
"isCountry":false,
"items":[
],
"label":"Barcelona",
"name":"Barcelona"
},
{
"expanded":false,
"isCountry":false,
"items":[
],
"label":"Sevilla",
"name":"Sevilla"
}
],
"label":"Spain",
"name":"Spain"
},
{
"expanded":false,
"isCountry":true,
"items":[
{
"expanded":false,
"isCountry":false,
"items":[
],
"label":"Inter",
"name":"Inter"
},
{
"expanded":false,
"isCountry":false,
"items":[
],
"label":"Napoli",
"name":"Napoli"
},
{
"expanded":false,
"isCountry":false,
"items":[
],
"label":"Juventus",
"name":"Juventus"
}
],
"label":"Italy",
"name":"Italy"
}
]
Javascript контроллер будет выглядеть как-то так:
import { LightningElement, track, wire } from 'lwc';
import getTeamsDataForTree from '@salesforce/apex/FootballService.getTeamsDataForTree';
export default class FavoriteTeam extends LightningElement {
@track selectedTeamValue;
@track teams;
@wire(getTeamsDataForTree)
wireTeamData({error, data}){
if(data){
this.teams = data;
}
else{
this.error = error;
}
}
handleOnselect(event) {
const team = this.findNested(this.teams, 'name', event.detail.name);
if(team){
this.selectedTeamValue = team.label;
}
}
findNested(obj, key, value) {
if (obj[key] === value && !obj.isCountry) {
return obj;
}
const objKeys = Object.keys(obj);
for (const k of objKeys) {
if (typeof obj[k] === 'object' || Array.isArray(obj[k])) {
const found = this.findNested(obj[k], key, value);
if (found) {
return found;
}
}
}
return null;
}
}
И сама разметка:
<template>
<lightning-card title="Click on your favorite team to choose it.">
<div class="slds-m-top_medium slds-m-bottom_x-large">
<template if:true={teams}>
<div class="slds-p-around_medium lgc-bg">
<lightning-tree items={teams} header="Teams" expanded=true onselect={handleOnselect}></lightning-tree>
</div>
<div class="slds-m-vertical_medium">
<p>Your favorite team is: <span class="slds-text-heading_small">{selectedTeamValue}</span></p>
</div>
</template>
</div>
</lightning-card>
</template>
И в файле .js-meta.xml я указал lightning__HomePage, чтобы вынести наш компонент на домашнюю страницу.
Все страны, и, тем более, все клубы я добавлять само собой не стал, так как это заняло бы неоправданно длительное время. Но в текущей имплементации для добавления элемента достаточно создать запись кастомного объекта, а FootballService подберёт их самостоятельно.
Вот, что получилось в итоге: