Construir IU para listas de opções dependentes
Objetivos de aprendizagem
- Fazer uma solicitação à API de interface de usuário para obter todos os valores da lista de opções para um tipo de registro.
- Nomear a propriedade que contém um mapa dos campos dependentes de um objeto.
- Nomear a propriedade que contém uma lista de campos de controle de um campo.
O que as listas de opções dependentes têm de tão especial?
Criar uma interface de usuário para campos de lista de opções dependentes é complicado, mas a API de interface de usuário torna sua vida mais fácil. Os valores de um campo de lista de opções dependente são filtrados com base em uma seleção feita em outra lista de opções ou caixa de seleção (chamada de campo de controle). Veja esta captura de tela do editor de listas de opções dependentes no aplicativo Visualizador de registros. Uma lista de opções País controla uma lista de opções Estado/Província (que depende da lista de opções País). Quando um usuário seleciona um país, os valores Estado/Província são filtrados para corresponderem. Quando um usuário seleciona um estado ou uma província, essa seleção filtra os valores de Cidade.
Então é isto: listas de opções dependentes facilitam o preenchimento de formulários porque os valores são filtrados, então você gasta menos tempo rolando página. E um editor de listas de opções dependentes facilita a edição de campos relacionados, especialmente em um dispositivo móvel. Imagine como seria difícil rolar de campo para campo enquanto os valores dependentes mudam. É muito mais fácil alterar todos os valores da lista de opções relacionados em uma caixa de diálogo.
Mas são esses relacionamentos entre campos de controle e campos dependentes que dificultam a criação de uma interface de usuário. Uma lista de opções (ou caixa de seleção) pode controlar várias listas de opções dependentes. E uma lista de opções dependentes é controlada, mas também pode controlar. Na realidade, uma lista de opções dependentes pode controlar várias outras listas de opções dependentes. A hierarquia dos campos da lista de opções dependentes é uma árvore. E um objeto pode ter uma floresta de árvores.
- objectInfo.dependentFields contém um mapa dos campos dependentes de um objeto.
- objectInfo.fields[fieldName].controllingFields contém uma lista de campos de controle de um campo.
GET /ui-api/object-info/{objectApiName}/picklist-values/{recordTypeId}
O aplicativo Visualizador de registros usa essas propriedades e recursos para criar seu editor de listas de opções dependentes.
Usar o editor de listas de opções dependentes
Para ver o editor de listas de opções dependentes no aplicativo Visualizador de registros, crie um relacionamento dependente entre pelo menos dois campos em um objeto.
Em seu Trailhead Playground, crie uma dependência de campo para o objeto Conta. Vamos então editar uma conta no aplicativo Visualizador de registros para ver o editor de listas de opções dependentes em ação.
- Em Configuração, insira objeto e selecione Gerenciador de objetos.
- Selecione .
- Clique em Novo.
- Vamos criar um relacionamento entre dois campos existentes. Para o campo de controle, selecione Classificação. Para o campo dependente, selecione Prioridade do cliente. Clique em Continuar.
- Edite as dependências de campo de modo que Em alta inclua Alta, Na média inclua Baixa e Média, e Em baixa inclua Baixa. Clique em Salvar.
- Abra o aplicativo Visualizador de registros e selecione uma conta da lista Itens recentes.
- Clique em Editar.
- Ao lado dos campos Classificação da conta ou Prioridade do cliente, clique no ícone de lápis.
A caixa de diálogo do editor de listas de opções dependentes abre. Use-a para selecionar um valor para os dois campos. Quando você seleciona um valor para um campo, os valores para o outro campo são filtrados com base nas suas seleções em Configuração.
Como as listas de opções dependentes são exibidas na API de interface de usuário
Os campos de lista de opções dependentes fazem parte de uma árvore de dependência, que é uma hierarquia de campos de controle e dependentes. Cada nó em uma árvore pode ter qualquer quantidade de nós filho. Um nó sem filhos é chamado de folha.
Nossa árvore de dependência de exemplo é simples porque tem um campo raiz, Classificação, que controla um campo CustomerPriority__c. Se Classificação controlasse outro campo, nossa hierarquia de dependência começaria a parecer uma árvore. Mas com apenas dois campos, nossa árvore de dependência não tem ramificações, é apenas um tronco.
Um objeto pode ter qualquer quantidade dessas árvores de dependência. Para construir uma IU que permite aos usuários editar listas de opções dependentes, você precisa saber a hierarquia completa de cada árvore.
Por exemplo, o objeto Conta poderia ter duas árvores de dependência, uma cuja raiz é País e outra cuja raiz é Classificação. As árvores de dependência indicadas em nossos exemplos são simples, pois cada campo de controle controla apenas um campo dependente. Imagine só que o campo País, além de controlar o campo Estado/Província, poderia também controlar o campo Idioma e o campo Fuso horário.
Como você já sabe, a API de interface de usuário tem duas propriedades de resposta que fornecem a estrutura de dependência de campo. As propriedade são incluídas na carga objectInfo, que é parte da resposta de /ui-api/record-ui/{recordIds}. Para ver a resposta JSON, abra um objeto Conta no aplicativo Visualizador de registros e clique em Mostrar JSON.
- objectInfo.dependentFields
- Essa propriedade fornece árvores de campo dependente para todos os campos em um objeto. Este exemplo JSON tem um campo raiz, Classificação, mas um objeto pode ter vários campos raiz.
"objectInfos" : { "Account" : { ... "dependentFields" : { "Rating" : { "CustomerPriority__c" : { } } }, ...
- Quando um objeto tem uma hierarquia estendida de campos dependentes, como nossos exemplos País, Estado/Província e Cidade, a propriedade dependentFields os aninha até que eles alcancem um nó de folha, como Cidade.
"objectInfos" : { "Account" : { ... "dependentFields": { "Country__c": { "StateProvince__c": { "City__c": {} } } }, ...
- objectInfo.fields[fieldName].controllingFields
- Esta propriedade fornece a lista de campos que controlam este campo específico, começando pelo campo que controla imediatamente este campo. Em nosso exemplo simples, o campo CustomerPriority__c tem um campo de controle, Classificação.
"objectInfos" : { "Account" : { ... "fields" : { ... "CustomerPriority__c" : { "apiName" : "CustomerPriority__c", ... "controllingFields" : [ "Rating" ], ...
- Quando um objeto tem uma hierarquia estendida de campos de controle, como nossos exemplos País, Estado/Província e Cidade, a propriedade controllingFields os lista por ordem, a começar pelo campo de controle imediato.
"objectInfos" : { "Account" : { ... "fields" : { ... "City__c" : { "apiName" : "City__c", ... "controllingFields" : [ "StateProvince__c", "Country__c"], ...
GET /ui-api/object-info/{objectApiName}/picklist-values/{recordTypeId}
"CustomerPriority__c" : { "controllerValues" : { "Hot" : 0, "Warm" : 1, "Cold" : 2 }, "defaultValue" : null, "url" : "/services/data/v48.0/ui-api/object-info/Account/picklist-values/012000000000000AAA/CustomerPriority__c", "values" : [ { "attributes" : null, "label" : "High", "validFor" : [ 0 ], "value" : "High" }, { "attributes" : null, "label" : "Low", "validFor" : [ 1, 2 ], "value" : "Low" }, { "attributes" : null, "label" : "Medium", "validFor" : [ 1 ], "value" : "Medium" } ] },
A propriedade controllerValues fornece os valores da lista de opções e os índices do campo de controle imediato. Então, em nosso exemplo, o campo é Prioridade do cliente, e o campo de controle imediato é Classificação. Os valores de Classificação são Em alta, Na média e Em baixa, e os índices são 0, 1 e 2.
Para cada valor de Prioridade do cliente (Alta, Baixa e Média), a propriedade validFor contém os valores de Classificação que, quando selecionados em uma lista de opções, incluem o valor Prioridade do cliente, em vez de excluí-lo.
Por exemplo, quando Classificação é Em alta (0), a lista de opções de Prioridade do cliente é filtrada para incluir somente Alta. Quando Classificação é Na média (1), a lista de opções de Prioridade do cliente é filtrada para incluir Baixa e Média. Quando Classificação é Em baixa (2), a lista de opções de Prioridade do cliente é filtrada para incluir somente Baixa.
Agora que entendemos quais informações precisamos e como obtê-las por meio da API de interface de usuário, vamos ver como o aplicativo Visualizador de registros cria o editor de listas de opções dependentes.
Obter valores da lista de opções
Quando o usuário clica em Criar, Clonar ou Editar para criar, clonar ou editar um registro, o aplicativo Visualizador de registros faz uma pré-busca dos valores da lista de opções para todos os campos no objeto para o tipo de registro especificado.
Veja o código em RecordViewerWrapper.js. Procure uma chamada para dispatch(actions.fetchPicklists(creds, apiName, recordType)) para cada cenário: onNewRecordClick, onCloneClick e onEditClick.
/* /sagas/picklistFetcher.js */ let url = action.creds.instanceUrl + '/services/data/v48.0/ui-api/object-info/' + action.apiName + '/picklist-values/' + action.recordType + '/';
/* /reducers/picklists.js */ const picklists = (state = {values: undefined}, action) => { switch (action.type) { case 'RECEIVE_PICKLISTS': return { fieldValues : action.result.picklistFieldValues } case 'FETCH_PICKLISTS': // Clear values when new collection is requested. return { fieldValues: undefined } default: return state } } export default picklists
Exibir o editor de listas de opções dependentes
Quando um registro está no modo de edição, o aplicativo exibe um ícone de lápis ao lado de campos dependentes para indicar que o usuário pode abrir o editor de listas de opções dependentes.
Quando o usuário clica no ícone de lápis, o aplicativo abre o editor de listas de opções dependentes como uma caixa de diálogo modal. No contexto de código, o editor de listas de opções dependentes é chamado de componente DepGraphEditor.
/* /components/RecordRow.js */ if (objectInfo && ((component.field && component.field in objectInfo.dependentFields) || (component.fieldInfo && component.fieldInfo.controllingFields.length > 0))) { // Last field in controlling fields is the root field let lastControllingIndex = component.fieldInfo.controllingFields.length - 1 let rootField = null if (component.field in objectInfo.dependentFields){ rootField = component.field } else { rootField = component.fieldInfo.controllingFields[lastControllingIndex] } // Retrieve the picklist fields that need to show up. const subfieldTree = objectInfo.dependentFields[rootField] const modalFields = getFlattenedTree(objectInfo, subfieldTree, rootField) // Open a modal on click of the input field. let fieldTree = {} fieldTree[rootField] = subfieldTree return ( <div> <label key={'componentInput' + itemLabel + ',' + i} onClick={(event) => onEditDepGraph(picklists, modalFields, editValues, fieldTree, uiMode.toString())} value={currPicklistValue}> {editValues[component.field].current} </label> <button className="fa fa-pencil" key={'componentInput' + itemLabel + 'button,' + i} onClick={(event) => onEditDepGraph(picklists, modalFields, editValues, fieldTree, uiMode.toString())}> </button> </div> ); }
Para abrir a caixa de diálogo modal, o método onClick() invoca o método onEditDepGraph(), que envia a ação EDIT_DEP_GRAPH, que altera o modo para EditDepGraph. A caixa de diálogo modal abre quando o modo altera.
/* /reducers/record.js */ case 'EDIT_DEP_GRAPH': return { ...state, prevMode: action.prevMode, // save previous mode to return to on close mode: 'EditDepGraph' }
/* /components/Record.js */ <div> {uiMode === 'EditDepGraph' && <DepGraphEditor depGraph={depGraph} picklists={picklists} editValues={recordView.editValues} onFieldValueUpdate={onDepGraphFieldValueUpdate} onClose={() => onDepGraphClose(prevMode)}/> }
Atualizar valores da lista de opções
Quando um usuário seleciona um novo valor para um campo de lista de opções, o aplicativo tem de atualizar os valores de seus campos de lista de opções filho. E se esses filhos tiverem filhos, o aplicativo os atualiza também, até chegar aos campos de lista de opções folha que não têm filhos. Para fazer essas atualizações, é importante saber a subárvore de dependência abaixo do campo cujo valor foi alterado.
Do mesmo modo, se o valor pré-selecionado para um campo permanecer válido, o aplicativo tem de preservar esse valor.
/* /sagas/depGraphValueUpdater.js */ // Traverse down to the target field in the tree. const field = modalFieldsMap[action.field]; var treeNode = action.fieldTree; for (var i = field.controllingFields.length -1; i>=0; i--) { var controllingField = field.controllingFields[i] treeNode = treeNode[controllingField]; } treeNode = treeNode[action.field]; // Now treeNode is the subtree rooted at the target field.
Ao usar essa estrutura de subárvore extraída e os valores da lista de opções obtidos quando o usuário clicou em Criar, Clonar ou Editar para criar, clonar ou editar um registro, construímos os valores da lista de opções legais para cada campo de lista de opções na subárvore.
Para ver como o aplicativo obtém os valores da lista de opções legais para um campo específico, veja getLegalValues() em /helpers/depGraphHelper.js. Para saber como o código percorre a subárvore e popula recursivamente os valores legais para cada campo de lista de opções filho usando a primeira pesquisa de amplitude, confira a saga depGraphValueUpdater.js.
Parabéns! É isto que temos para hoje! É verdade que não podemos fazer com que a criação de IU para listas de opções dependentes seja mais fácil do que cortar pão, mas certamente é muito mais fácil do que fazer um suflê de chocolate e igualmente prazeroso.
Recapitulando: para construir uma IU para um editor de listas de opções dependentes, a API de interface de usuário fornece todas as informações que você precisa em três locais, as propriedades objectInfo.dependentFields e objectInfo.fields[fieldName].controllingFields, e a resposta do ponto de extremidade /ui-api/object-info/{objectApiName}/picklist-values/{recordTypeId}.
A API de interface de usuário fornece a capacidade e a flexibilidade de construir interfaces de usuários do Salesforce personalizadas que deixam seus clientes contentes. Ainda bem que o aplicativo Visualizador de registros encaminha você para um bom começo. Sinta-se à vontade para usar o repositório GitHub e enviar solicitações pull. Aguardamos ansiosamente por notícias sobre seus aplicativos!