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:
- 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 tipoCREATEpara instanciar coleções (ListObject) completamente vazias na memória doProcessContext. - 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.
- 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. - 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 DisplayedSubcomponentName | Componente 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). |
null, undefined ou string divergente | Falha 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:
- O atributo
nameem<CockpitList name="Opportunities">na UI. - O valor literal passado na action do ProcessFlow:
<Input name="type" value="Opportunities" type="Literal" />. - 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
CardContainerexige a presença simultânea do bindingtarget="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.
