Lightning Components = f(dados);
Lightning Components foi uma aposta muito boa da salesforce ( Minha opinião ), porque podemos montar componentes reutilizáveis, minimizando o famoso “Copia daquela tela e faz igual”, porém é uma forma diferente de pensar ao desenvolver e em algumas situações fica difícil definir como e o que componentizar.
Estarei mostrando como desenvolver Lightning Components baseado em dados, mas antes darei uma breve explicação de um conceito importante que citarei aqui.
Já ouvir falar em programação funcional ou programação declarativa? Se sim, maravilha, você entenderá isso de forma bem simples, mas para quem não conhece vamos la… Para exemplificar usarei Javascript (Que é uma linguagem funcional). Quando falamos de programação funcional estamos querendo dizer isso aqui: y = f(x). Sim, estamos usando o que aprendemos na escola, mas o que isso significa? Significa que não devemos nos preocupar em como as dados são tratados, mas sim com o que queremos que seja retornado. Exemplo:
var array = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20]; var primeNumbers = array.filter(n => n < 4 || (n % 2 !== 0) && (n % 3 !== 0)); console.log(primeNumbers);
Neste exemplo temos um array com números de 1 a 20 e depois iteramos item a item para só pegar os primos. A diferença básica deste exemplo para a programação imperativa é a chamada do método Array.filter, que recebe uma função onde o primeiro parâmetro é o item do array de cada iteração e retorna uma condição para que o elemento esteja no novo array que será retornado pela função Array.filter. Chamando está função, não nos preocupamos como será executado este processo (por mais que saibamos que deva fazer um for ou foreach) só com o que queremos receber de volta. Abaixo está a forma imperativa deste exemplo:
var array = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20]; var primeNumbers = []; for(var i = 0; i < array.length; i++){ var number = array[i]; if(number < 4 || (number % 2 !== 0) && (number % 3 !== 0)) { primeNumbers.push(number); } } console.log(primeNumbers);
Bom acredito que isto seja o mínimo que você precisa saber para entender o que vou mostrar (Considerando que conheça o básico de Lightning Components), mas recomendo fazer um estudo um pouco mais aprofundado neste assunto (Não irei fazer isto aqui porque não é o foco do post).Agora vamos começar o que realmente interessa… Construir Lightning Components baseado em dados, para exemplificar irei montar uma tabela para exibir uma lista de registros de um objeto. Então primeiro vemos como queremos que seja nossa tabela:
Definido como queremos nossa tabela, agora vamos definir quais dados e como temos que passar para essa tabela ser montada:
(Verde) – Cabeçalho: Usaremos um vetor para informar qual será a label de cada coluna
(Vermelho) – Corpo – Usaremos também um vetor com os valores que serão apresentados linha a linha
(Azul) – Tabela – Receberá o objeto contendo o as colunas do cabeçalho e os dados do corpo da tabela.
Próximo passo é definir como será o formato dos dados que terão que ser enviados para o componente.
Cabeçalho: Sabendo que o cabeçalho precisa receber o nome da coluna o campo do objeto referente a ela, podemos montar a seguinte estrutura de dados:
[ "ColumnName1", "ColumnName2", "ColumnName3", "ColumnName4", "ColumnName5" ]
Corpo: Da mesma forma que pensamos para o cabeçalho pensamos para o corpo, onde podemos usar está estrutura:
[ "ValorCampo1", "ValorCampo2", "ValorCampo3", "ValorCampo4", "ValorCampo5" ]
Tabela – Definido as estruturas dos dados do corpo e do cabeçalho, só precisamos juntar as duas estruturas em uma para a tabela receber. Bom podemos definir está estrutura para a tabela:
{ "columns":[ { "label":"ColumnName", "field":"ColumnApiName" } ], "body":[ { "Campo1":"ValorCampo1", "Campo2":"ValorCampo2", "Campo3":"ValorCampo3", "Campo4":"ValorCampo4", "Campo5":"ValorCampo5" } ] }
Aqui tem um ponto interessante para se comentar. Como o componente de tabela recebe os dados e manda para os outros componentes, não necessariamente todas as estruturas precisam ser mandadas da mesma forma, pode ser enviado dados de uma forma e ser tratado para depois enviar para toda cadeia de componentes. Nesta estrutura, temos para cada coluna, a label de cada coluna e seu respectivo campo e no body, temos uma lista de objetos, com seus campos e valores.Próxima etapa é definir o que componentizar. Neste ponto é bom pensarmos conforme os dados que estamos mandamos para os componentes. De acordo com nossos dados teremos um vetor com as colunas da tabela uma lista com os valores de cada linha da tabela. Podemos definir os componentes assim:
Lembrando que isso é uma forma de fazer, pode e deve existir várias formas de fazer está implementação, estou usando está porque fica mais fácil a explicação…. Bom agora vamos para a parte que interessa… Primeiro começaremos pelos componentes mais simples: TableHeader e TableItem:
TableHeader:
<aura:component > <aura:attribute name="columns" type="List" required="true"/> <thead> <tr class="slds-text-title_caps slds-theme--default"> <aura:iteration items="{!v.columns}" var="column"> <th>{!column}</th> </aura:iteration> </tr> </thead> </aura:component>
Este componente só tem como função devolver um cabeçalho de tabela com base da lista de colunas passadas via atributo.
TableItem:
<aura:component > <aura:attribute name="rowData" type="List" default="[]" required="true"/> <tr> <aura:iteration items="{!v.rowData}" var="data"> <td>{!data}</td> </aura:iteration> </tr> </aura:component>
Acredito que o exemplo esteja intuitivo, contudo este componente só monta uma linha da tabela com base nos dados passados via atributo.Agora vamos para o componente Table, este componente além de ter a função de passar os dados para os componentes filhos como também tratar os dados para transforma-los em dados validos para os componentes filhos.
Table:
<aura:component > <aura:attribute name="config" type="Object"/> <aura:attribute access="private" name="dataList" type="Object" /> <aura:attribute access="private" name="columnList" type="List" /> <aura:handler name="init" value="this" action="{!c.doInit}"/> <table class="slds-m-top--medium slds-table slds-table--bordered slds-table__cell-buffer"> <c:tableHeader columns="{!v.columnList}"></c:tableHeader> <tbody> <aura:iteration items="{!v.dataList}" var="row"> <c:tableItem rowData="{!row}"></c:tableItem> </aura:iteration> </tbody> </table> </aura:component>
Sim, essa é a implementação do componente Table, simples não? Mas calma, ainda não acabou. Repare que o componente tem 3 atributos: config, dataList e columnList. O atributo config servirá para receber os dados de configuração da tabela conforme definimos acima, o atributo dataList será uma matriz com os valores de cada linha e o columnList será a lista com os nomes de cada coluna. Criaremos agora uma action que executa na primeira vez que o componente é criado para pegar os dados do atributo config e trata-los para jogar nos atributos dataList e columnList. A controller javascript deste componente ficou assim:
({ doInit : function(cmp, event, helper) { const config = JSON.parse(cmp.get('v.config')); const columnsList = config.columns.map(column => column.label); const columnFields = config.columns.map(column => column.field); const dataList = config.body.map(row =>{ return columnFields.map(field => row[field]); }); cmp.set('v.dataList',dataList); cmp.set('v.columnList',columnsList); } });
Como podem ver, estamos apenas pegando os dados de configuração e transformando-os em dados válidos para os componentes filhos.Agora criando um Lightning app simples para testar nosso componente:
<aura:application extends="force:slds"> <h1 class="slds-text-heading_large"> My Products </h1> <c:table config='{"columns":[{"label":"Name","field":"ProductName"},{"label":"Unity Price","field":"UnityPrice"},{"label":"Cost Price","field":"CostPrice"},{"label":"Type","field":"Type"}],"body":[{"ProductName":"Product 1","UnityPrice":"$ 8.20","CostPrice":"$3.40","Type":"Health and Care"},{"ProductName":"Product 2","UnityPrice":"$ 5.20","CostPrice":"$1.40","Type":"Health and Care"},{"ProductName":"Product 3","UnityPrice":"$ 3.70","CostPrice":"$1.20","Type":"Health and Care"},{"ProductName":"Product 4","UnityPrice":"$ 4.90","CostPrice":"$0.80","Type":"Health and Care"}]}'/> </aura:application>
Coloquei os dados fixos apenas para facilitar a implementação, mas caso queiram, implementem uma Apex controller para este app e retorna dados de algum objeto ou uma classe comum. Os dados colocados nesse exemplo são esses:
{ "columns":[ { "label":"Name", "field":"ProductName" }, { "label":"Unity Price", "field":"UnityPrice" }, { "label":"Cost Price", "field":"CostPrice" }, { "label":"Type", "field":"Type" } ], "body":[ { "ProductName":"Product 1", "UnityPrice": "$ 8.20", "CostPrice": "$3.40", "Type": "Health and Care" }, { "ProductName":"Product 2", "UnityPrice": "$ 5.20", "CostPrice": "$1.40", "Type": "Health and Care" }, { "ProductName":"Product 3", "UnityPrice": "$ 3.70", "CostPrice": "$1.20", "Type": "Health and Care" }, { "ProductName":"Product 4", "UnityPrice": "$ 4.90", "CostPrice": "$0.80", "Type": "Health and Care" } ] }
E ao executar nosso lightninp app temos na tela:
Abraços.