Como adicionar cartões ao dashboard inicial do aplicativo móvel offline, implementar carregamento sob demanda de alta performance e blindar o projeto contra erros de compilação.

Quando o representante de vendas ou promotor abre o aplicativo do Salesforce Consumer Goods Cloud (CGC) pela manhã, a primeira tela com a qual ele interage é o Cockpit. Seja no User Cockpit (a tela “Your Day”, com o resumo da jornada, metas e sincronização) ou no Store Cockpit (a visão de 360 graus da loja durante uma visita), o dashboard inicial concentra dezenas de métricas essenciais em formato de cartões interativos: visitas pendentes, faturas em aberto, promoções ativas e tarefas de merchandising.

Para o desenvolvedor que precisa estender a aplicação e adicionar um novo cartão de dados customizado a esse painel, o desafio parece simples à primeira vista. No entanto, o Cockpit é o componente mais sensível e acoplado de toda a arquitetura do Modeler.

Se você tentar carregar os dados de todos os cartões de uma vez na inicialização da tela, a renderização inicial travará o aparelho por vários segundos, destruindo a experiência do usuário. Por outro lado, se você esquecer de registrar uma única propriedade em um dos arquivos satélites, o cartão simplesmente desaparecerá sem gerar mensagens de erro ou exibirá o aviso genérico: “We couldn’t render this component”.

Para garantir velocidade instantânea e estabilidade milimétrica, o framework Mv2 estabelece o padrão de Lazy Loading (Carregamento Tardio).

Neste guia técnico, vamos dissecar o ciclo de vida dos Cockpit Cards, desvendar o checklist obrigatório dos 7 locais a tocar, dominar o mecanismo crítico do DisplayedSubcomponentName e solucionar os erros de compilação mais comuns reportados no compilador do Modeler.


1. A Arquitetura do Lazy Loading em Cockpits Móveis

O segredo para que uma tela inicial contendo dez cartões abra em menos de 500 milissegundos em um smartphone modesto reside na separação estrita entre instanciação estrutural e carga real de dados.

O ciclo de vida do cartão opera em quatro fases coordenadas:

  1. Instanciação Vazia em EntryActions (0 ms): Durante a inicialização do processo (.processflow.xml), o sistema não executa nenhuma consulta SQL pesada contra o SQLite local (app.db3). Ele apenas executa ações do tipo CREATE para instanciar coleções (ListObject) completamente vazias na memória do ProcessContext.
  2. First Paint Imediato da Interface: A tela é desenhada instantaneamente para o usuário. Os cartões que estão visíveis no topo da tela iniciam o carregamento, enquanto os cartões inferiores permanecem em estado de espera.
  3. Disparo do Evento de Visibilidade (LoadContainerData): Conforme o usuário rola a tela e o cartão entra no campo de visão, o componente visual dispara automaticamente o evento nativo LoadContainerData.
  4. Cadeia de Carga em Três Ações: O ProcessFlow intercepta o evento, busca os dados daquele cartão específico no SQLite, formata o texto de resumo do cabeçalho (como “3 / 10”) e define se o cartão deve exibir a lista de itens ou a mensagem de lista vazia.
+-----------------------------------------------------------------+
|                       Usuário Abre o Cockpit                    |
+-----------------------------------------------------------------+
                                 |
                                 v [EntryActions: CREATE vazio]
+-----------------------------------------------------------------+
|                Abertura Instantânea da Interface                |
|             (Cards aparecem na tela com listas vazias)          |
+-----------------------------------------------------------------+
                                 |
                                 v [Usuário rola a tela / Visibilidade]
+-----------------------------------------------------------------+
|             Evento de Interface: LoadContainerData              |
|              (Dispara Card{Nome}_loadData no Process)           |
+-----------------------------------------------------------------+
                                 |
                                 v [Cadeia de 3 Ações no ProcessFlow]
+-----------------------------------------------------------------+
| 1. LOGIC: Executa Query e Calcula Métricas (.bl.js)             |
| 2. LOGIC: Gera Texto de Resumo do Cabeçalho ("X / Y")           |
| 3. LOGIC: Avalia DisplayedSubcomponentName (Lista vs Empty)     |
+-----------------------------------------------------------------+
                                 |
                                 v [Renderização Final]
+-----------------------------------------------------------------+
|                  Card Renderizado com Dados Reais               |
+-----------------------------------------------------------------+
Code language: JavaScript (javascript)

2. A Regra dos 7 Locais a Tocar (Checklist Completo)

A implementação de um novo Cockpit Card não reside em um único arquivo. Para que o cartão exista, seja visível, carregue dados e permita navegação, o desenvolvedor deve alterar obrigatoriamente sete locais específicos distribuídos entre o ProcessFlow, a UserInterface e o controlador do Cockpit.

Locais no ProcessFlow (*Cockpit*Process.processflow.xml)

Local 1: <Declarations> (As Variáveis de Memória do Cartão)

Declare as variáveis obrigatórias no ProcessContext. O padrão de nomenclatura canônico exige o prefixo Card{Nome}_{Sufixo}:

<ProcessContext>
  <Declarations>
    <!-- A coleção de dados gerenciada pelo ListObject -->
    <Declaration name="CardOpportunities_List" type="LoOpportunityForStoreCockpit" />
    
    <!-- O texto resumido exibido no cabeçalho do cartão (ex: "3 / 10") -->
    <Declaration name="CardOpportunities_InformationText" type="String" />
    
    <!-- Sinalizador booleano que informa à UI que os dados já foram carregados -->
    <Declaration name="CardOpportunities_DataLoaded" type="DomBool" />
    
    <!-- Variável crítica: dita se renderiza a lista ou o empty-state -->
    <Declaration name="CardOpportunities_DisplayedSubcomponentName" type="String" />
  </Declarations>
</ProcessContext>

Local 2: <EntryActions> (Apenas CREATE, Nunca LOAD!)

Na inicialização do processo, instancie a lista vazia:

<EntryActions>
  <Action name="CardOpportunities_Create" actionType="CREATE" type="LoOpportunityForStoreCockpit">
    <Return name="ProcessContext::CardOpportunities_List" />
  </Action>
</EntryActions>

Local 3: Eventos da Ação VIEW (ShowCockpit)

Registre os ouvintes de eventos disparados pelo cartão visual:

<Action actionType="VIEW" name="ShowCockpit">
  <UIDescription>Call::StoreCockpitUI</UIDescription>
  <Events>
    <!-- Evento de carga sob demanda quando o card fica visível -->
    <Event name="CardOpportunities_loadData" action="CardOpportunities_LoadData" />
    
    <!-- Evento de clique em um item específico da lista do card -->
    <Event name="CardOpportunities_itemSelected" action="CardOpportunities_ShowSelected" />
    
    <!-- Evento de clique no botão de ver tudo no rodapé -->
    <Event name="CardOpportunities_showAll" action="CardOpportunities_ShowAll" />
  </Events>
</Action>

Local 4: Cadeia de Carga no Corpo das Ações

Implemente a sequência encadeada de três ações lógicas que computam os dados do cartão:

<!-- 1. Executa a busca no SQLite e popula o ListObject -->
<Action name="CardOpportunities_LoadData" actionType="LOGIC" call="ProcessContext::CardOpportunities_List.calculateOpportunitiesForCard">
  <Parameters>
    <Input name="customerPKey" value="ProcessContext::customerPKey" />
  </Parameters>
  <TransitionTo action="CardOpportunities_GetCardInformation" />
</Action>

<!-- 2. Formata o texto de resumo do cabeçalho -->
<Action name="CardOpportunities_GetCardInformation" actionType="LOGIC" call="ProcessContext::CardOpportunities_List.getInformationText">
  <Return name="ProcessContext::CardOpportunities_InformationText" />
  <TransitionTo action="CardOpportunities_AssignDisplayedSubcomponentName" />
</Action>

<!-- 3. Invoca o controlador para determinar o subcomponente visual -->
<Action name="CardOpportunities_AssignDisplayedSubcomponentName" actionType="LOGIC" call="ProcessContext::CardController.getDisplayedSubcomponentName">
  <Parameters>
    <Input name="loItems" value="ProcessContext::CardOpportunities_List" />
    <Input name="type" value="Opportunities" type="Literal" />
  </Parameters>
  <Return name="ProcessContext::CardOpportunities_DisplayedSubcomponentName" />
</Action>

Local 5: Ações de Navegação (PROCESS)

Define para onde o usuário é direcionado ao tocar em um item ou no botão de ver todos:

<Action name="CardOpportunities_ShowSelected" actionType="PROCESS" process="Opportunity::OpportunityDetailProcess">
  <Parameters>
    <Input name="opportunityPKey" value="ProcessContext::selectedItemId" />
  </Parameters>
</Action>

<Action name="CardOpportunities_ShowAll" actionType="PROCESS" process="Opportunity::OpportunityOverviewProcess">
  <Parameters>
    <Input name="customerPKey" value="ProcessContext::customerPKey" />
  </Parameters>
</Action>

Local na UserInterface (*Cockpit*UI.userinterface.xml)

Local 6: Inserção do <CardContainer> no Grid Visual

No arquivo de interface, o cartão deve conter os bindings de controle, o evento de visibilidade, a lista interna e a mensagem de dados ausentes:

<CardContainer name="CardOpportunities">
  <Bindings>
    <!-- Binding obrigatório que controla a alternância entre lista e empty-state -->
    <Binding target="DisplayedSubcomponentName" type="Text" binding="ProcessContext::CardOpportunities_DisplayedSubcomponentName" bindingMode="ONE_WAY" />
    
    <!-- Binding que informa à engine se o card já concluiu o carregamento -->
    <Binding target="IsReadyToLoad" type="DomBool" binding="ProcessContext::CardOpportunities_DataLoaded" bindingMode="ONE_WAY" />
    
    <!-- Título internacionalizado do cartão -->
    <Resource target="title" type="Label" id="CardOpportunitiesTitleId" defaultLabel="Oportunidades" />
  </Bindings>

  <Events>
    <!-- Evento nativo disparado quando o card entra na viewport do app -->
    <LoadContainerData event="CardOpportunities_loadData" />
  </Events>

  <!-- Subcomponente 1: A lista de registros (nome DEVE bater com o literal do ProcessFlow) -->
  <CockpitList name="Opportunities" dataSource="ProcessContext::CardOpportunities_List.Items[]">
    <Items itemPattern="DefaultItems">
      <ItemListLayout>
        <Default>
          <Col width="100%">
            <Row layoutType="itemIdentifierCockpit" bindingId="oppName">
              <Bindings>
                <Binding target="Text" type="Text" binding=".name" />
              </Bindings>
            </Row>
          </Col>
        </Default>
        <Tablet>
          <!-- Layout adaptado para Tablet -->
        </Tablet>
        <Phone>
          <!-- Layout compacto para Smartphone -->
        </Phone>
      </ItemListLayout>
    </Items>
  </CockpitList>

  <!-- Subcomponente 2: A mensagem de lista vazia (nome SEMPRE fixo como CardNoDataMessageUiPlugin) -->
  <NoDataMessage name="CardNoDataMessageUiPlugin">
    <Bindings>
      <Resource target="Text" type="Label" id="NoOpportunitiesFoundId" defaultLabel="Nenhuma oportunidade identificada para esta loja." />
    </Bindings>
  </NoDataMessage>
</CardContainer>

Locais no Controlador Central (BoSalesCockpitHelper / BoStoreCockpitHelper)

Local 7: Regras de Visibilidade e Colapso

Todo cockpit possui um Business Object auxiliar que atua como controlador central da tela (geralmente BoSalesCockpitHelper ou BoStoreCockpitHelper). Se o cartão não for registrado no controlador, ele não será renderizado.

No contrato XML do controlador (BoStoreCockpitHelper.businessobject.xml): Adicione a propriedade que armazena o estado de colapso:

<SimpleProperty name="collapseState_CardOpportunities" type="DomBool" />

No método JavaScript de visibilidade (BoStoreCockpitHelper.IsCardVisible.bl.js):

function isCardVisible(cardName) {
    var me = this;

    switch (cardName) {
        case "CardOpportunities":
            // Retorne true ou avalie permissões de perfil do usuário
            return true;
        // ... outros cartões existentes ...
        default:
            return false;
    }
}

No método JavaScript de colapso (BoStoreCockpitHelper.IsCardCollapsible.bl.js):

function isCardCollapsible(cardName) {
    var me = this;

    switch (cardName) {
        case "CardOpportunities":
            return true;
        // ...
    }
}

3. O Mecanismo do DisplayedSubcomponentName: Evitando o Erro “We couldn’t render this component”

Um dos comportamentos mais peculiares do CardContainer no Modeler é que ele opera como um container multi-subcomponente seletivo. Ele só consegue desenhar exatamente um elemento filho por vez, e a escolha desse elemento é feita por comparação estrita de strings (string match) com o valor presente na variável DisplayedSubcomponentName.

A Tabela de Decisão do Runtime

Valor da String em DisplayedSubcomponentNameComponente Filho Renderizado na Tela
Exatamente igual a <CockpitList name="Opportunities">Renderiza a grade de itens com dados.
"CardNoDataMessageUiPlugin"Renderiza o bloco de mensagem de dados ausentes (empty-state).
nullundefined ou string divergenteFalha crítica: O aplicativo exibe a caixa cinza com o aviso: “We couldn’t render this component”.

A Tripla Amarração Mandatória

Para que a alternância entre dados reais e empty-state funcione perfeitamente, você deve assegurar que três locais no seu código compartilhem exatamente o mesmo identificador sem prefixos extras:

  1. O atributo name em <CockpitList name="Opportunities"> na UI.
  2. O valor literal passado na action do ProcessFlow: <Input name="type" value="Opportunities" type="Literal" />.
  3. O nome exato da tag de mensagem vazia: <NoDataMessage name="CardNoDataMessageUiPlugin"> (essa string é fixa do framework e nunca deve ser alterada).

4. Prevenção dos Erros de Compilação Críticos do Modeler

Ao rodar o comando de compilação sf mdl build, pequenas inconsistências sintáticas em cartões de cockpit disparam erros numéricos imediatos. Veja como solucioná-los:

Erro 00000001: Element ‘isCollapsible’: This element is not expected

  • Causa: Tentar declarar isCollapsible="true" como atributo direto na tag <CardContainer>.
  • Solução: O estado de colapso não é um atributo XML nativo; ele deve ser configurado como um Binding dentro da tag <Bindings>:<Binding target="IsCollapsible" type="DomBool" binding="ProcessContext::CardController.collapseState_CardOpportunities" />

Erro 00000016 / 00000018: Missing required event or binding

  • Causa: Todo CardContainer exige a presença simultânea do binding target="IsReadyToLoad" e da tag de evento <LoadContainerData event="..." />.
  • Solução: Verifique se ambos os blocos estão presentes dentro do container visual.

Erro 00000029: AutoReload limit exceeded

  • Causa: A arquitetura do Modeler limita a no máximo três arquivos no workspace inteiro que podem registrar a assinatura de atualização automática via <CardEvent name="onAutoReload">.
  • Solução: Se você clonou um cockpit existente para criar um novo painel, abra o arquivo XML clonado e remova a tag <CardEventSubcription> do cartão de sincronização duplicado.

Conclusão

Os Cockpit Cards representam a vitrine de qualquer solução desenvolvida no Salesforce Consumer Goods Cloud Modeler. Ao implementar com rigor o padrão de Lazy Loading com instanciação vazia nas EntryActions, aplicar o checklist disciplinado dos 7 locais a tocar e respeitar as regras de ouro do DisplayedSubcomponentName, arquitetos e desenvolvedores garantem painéis iniciais extremamente velozes, visualmente elegantes e perfeitamente estáveis para a rotina diária no ponto de venda.

Sua equipe já teve problemas com cartões que não renderizavam ou lentidão na abertura do Cockpit no Consumer Goods Cloud? Compartilhe suas experiências e dúvidas nos comentários abaixo ou participe do debate técnico com nossa comunidade no LinkedIn.

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *