Construir IU para listas de opções dependentes

Objetivos de aprendizagem

Após concluir esta unidade, você estará apto a:
  • 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. Editor de listas de opções dependentes mostrando EUA, Califórnia, Los Angeles.

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.

A API de interface de usuário existe para resolver problemas para desenvolvedores de IU, então mostramos propriedades que descrevem esses relacionamentos entre campos.
  • 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.
Também mostramos um recurso que obtém todos os valores da lista de opções para um determinado tipo de registro.
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.

  1. Em Configuração, insira objeto e selecione Gerenciador de objetos.
  2. Selecione Conta | Campos e relacionamentos | Dependências de campo.
  3. Clique em Novo.
  4. 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.
  5. 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.Edite a matriz de dependência de campo no Salesforce.
  6. Abra o aplicativo Visualizador de registros e selecione uma conta da lista Itens recentes.
  7. Clique em Editar.
  8. Ao lado dos campos Classificação da conta ou Prioridade do cliente, clique no ícone de lápis. Ícone de lápis ao lado do campo Classificação da conta.

    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.Editor de listas de opções dependentes com os campos Classificação da conta e Prioridade do cliente.

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. Classificação controla Prioridade do cliente.

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. JSON no aplicativo Visualizador de registros.

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"],
            ...
Além de sabermos o relacionamento entre os campos dependentes, também precisamos saber os valores de cada campo de lista de opções em relação ao seu campo de controle imediato, porque é esse o campo que filtra os valores. A API de interface de usuário oferece um ponto de extremidade útil para obter todos esses valores para um tipo de registro em uma única chamada.
GET /ui-api/object-info/{objectApiName}/picklist-values/{recordTypeId}
Esse trecho da resposta JSON mostra os valores do campo CustomerPriority__c.
"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.

Para ver o código que constrói a URL da API de IU que faz a pré-busca dos valores da lista de opções, confira a saga picklistFetcher.js.
/* /sagas/picklistFetcher.js */

let url = action.creds.instanceUrl + '/services/data/v48.0/ui-api/object-info/' 
          + action.apiName + '/picklist-values/' + action.recordType + '/';
O redutor picklists.js atualiza o repositório Redux com os valores da lista de opções.
/* /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. Ícone de lápis ao lado de campos de lista 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.Editor de listas de opções dependentes

O código que faz esse trabalho é o componente RecordRow.js. Ele verifica se o campo existe em uma árvore de dependência ou não, e identifica o campo raiz da árvore de dependência. Ele renderiza o ícone de lápis ao lado dos campos e abre a caixa de diálogo modal quando o usuário clica no ícone de lápis.
/* /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.

Para ver a alteração do modo, veja o código do redutor record.js.
/* /reducers/record.js */

case 'EDIT_DEP_GRAPH':
  return {
    ...state,
    prevMode: action.prevMode, // save previous mode to return to on close
    mode: 'EditDepGraph'
  }
Para ver como a caixa de diálogo é aberta na alteração do modo, veja o código do componente Registro, que retorna o componente DepGraphEditor para o modo 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.

Para ver o código, confira a saga depGraphValueUpdater.js. O código obtém a estrutura de árvore de dependência abaixo do campo com um novo valor usando as propriedades controllingFields e fieldTree que extraímos no componente RecordRow.
/* /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!

Recursos