18 KiB
title, source, path
| title | source | path |
|---|---|---|
| 5 Erros comuns em widgets no TOTVS Fluig | https://tdn.totvs.com/display/fluig/5+Erros+comuns+em+widgets+no+TOTVS+Fluig | \Plataforma Documentação técnica\Recurso de Páginas e Widgets (WCM)\5 Erros comuns em widgets no TOTVS Fluig.md |
- Erros comuns no desenvolvimento de widgets
- Erro 1 - Escopo da super widget e o perigo das múltiplas instâncias
- Erro 2 - Deixar dados fixos (Hardcoded) e ignorar o Style Guide
- Erro 3 - Confundir bindings locais e globais
- Erro 4 - Não planejar o identificador (código) da widget
- Erro 5 - Lentidão de requisições e conflito com "Template Literals"
Erros comuns no desenvolvimento de widgets
Desenvolver widgets no TOTVS Fluig exige o cumprimento de padrões arquiteturais específicos da plataforma (FreeMarker e Fluig Style Guide). Ignorar essas regras frequentemente causa falhas silenciosas de renderização, lentidão na interface, quebra de código ao migrar entre ambientes (Homologação para Produção) e conflitos visuais.
Este guia documenta os 5 erros mais recorrentes entre desenvolvedores e apresenta as melhores práticas estruturais para evitá-los, para isso vamos ter como base um exemplo prático!
Download do exemplo:
Caso você tenha interesse em fazer o download do projeto que criamos para este exemplo, clique aqui.
Erro 1 - Escopo da super widget e o perigo das múltiplas instâncias
A arquitetura do TOTVS Fluig é rigorosa no carregamento e instanciamento de uma widget na página. Se a estrutura global não for respeitada, o componente falhará ao renderizar. Os principais equívocos incluem:
-
Usar ES6 na declaração principal: a arquitetura do Fluig requer um escopo global que apenas a declaração
varprovê na raiz do arquivo. Utilizarconstouletpara declarar a variável da SuperWidget impede de instanciar o objeto nowindow. -
Funções soltas: é ideal manter toda a implementação dentro da SuperWidget e evitar criar funções fora dela, caso contrário, você não terá acesso aos atributos fornecidos por ela (como o
this.instanceId). -
Divergência entre o ID da DIV principal e a variável JS: o
iddadivencapsuladora nos arquivosview.ftleedit.ftlpossui a mesma nomenclatura base da variável JavaScript. Exemplo: se a div é<div id="wExemplosPraticos_${instanceId}">, a variável deve servar wExemplosPraticos = SuperWidget.extend({...});. -
Ignorar o suporte a múltiplas instâncias na mesma página: um desenvolvedor deve assumir que sua widget pode ser inserida duas ou mais vezes na mesma página. Para que ações de clique e manipulações de DOM não afetem a widget errada, sempre concatene o
${instanceId}nos seletores de ID e utilize as funções de binding nativas.
- Estrutura correta de instanciação:
Sempre inicie sua widget com var e o nome idêntico ao código do projeto:
No javascript
// ❌ INCORRETO (O Fluig não conseguirá colocar a widget na página)
const wExemplosPraticos = SuperWidget.extend({ ... });
// ✅ CORRETO (Escopo Global garantido)
var wExemplosPraticos = SuperWidget.extend({
// Nos métodos internos, o uso de let/const é liberado e encorajado!
realizarBusca: function() {
const termo = $(`#input-search_${this.instanceId}`).val();
}
});
- Cuidado com callbacks assíncronos (a perda do this):
Em requisições AJAX, o contexto do this muda, fazendo você perder o acesso ao this.instanceId.
No javascript
// ✅ CORRETO: Preservando o escopo do Fluig
carregarDados: function() {
var that = this; // Guarda a referência da Widget
$.ajax({
url: '/api/...',
success: function(response) {
// Usa 'that' para manipular apenas a div da instância atual
$('#resultado_' + that.instanceId).text(response.data);
}
});
}
O exemplo abaixo mostra o uso de Arrow Functions (success: (data) => { ... }), que é a abordagem moderna e limpa. Ela herda o this automaticamente da widget, eliminando a necessidade de criar variáveis de escape (como o antigo var that = this).
No javascript
// ✅ CORRETO: Preservando o escopo do Fluig
carregarDados: function() {
$.ajax({
url: '/api/public/search/advanced',
// ✅ CORRETO: Arrow function não cria novo escopo 'this'
success: (data) => {
console.log(this.instanceId); // Acessível com sucesso!
}
});
}
Erro 2 - Deixar dados fixos (Hardcoded) e ignorar o Style Guide
1. Hardcode de dados e ambientes
Anotar IDs fixos (pastas, formulários, logins) no código Javascript fará com que a widget quebre imediatamente ao ser exportada para outro ambiente. Ex.: de Homologação para Produção, onde os IDs são diferentes.
A solução correta é criar uma tela de configuração (edit.ftl) para o administrador salvar esses dados usando a API UPDATEPREFERENCES nativa do Fluig e depois resgatá-los no view.ftl via FreeMarker.
No arquivo Javascript (.js), capture e salve os dados:
Javascript
savePreferences: function() {
// Montamos um objeto com os valores preenchidos na tela de edição
const preferences = {
folderIdPref: $(`#folderId_${this.instanceId}`).val() || "0",
limitPref: $(`#limit_${this.instanceId}`).val() || "15",
orderingPref: $(`#ordering_${this.instanceId}`).val()
};
// A API nativa grava isso com segurança no Fluig
WCMSpaceAPI.PageService.UPDATEPREFERENCES({
async: true,
success: () => {
FLUIGC.toast({ title: 'Sucesso', message: 'Preferências salvas!', type: 'success' });
}
}, this.instanceId, preferences);
}
2. A importância da identidade visual e uso do Style Guide
Um erro de UI/UX é criar telas com CSS customizado (<style>) ou ignorar os componentes oficiais. O usuário precisa sentir que sua widget é uma funcionalidade que faz parte do Fluig e não um sistema terceiro colado na tela.
Para isso, você deve usar as classes do Fluig Style Guide. Veja como nosso código funcional aplica isso na prática:
-
Estrutura de painéis: envelopamos a widget com as classes
.panel.panel-primary,.panel-headinge.panel-body. Isso cria a borda, o cabeçalho e a sombra idênticos aos portais nativos. -
Grid System responsivo: o layout não flutua de forma aleatória. Ele respeita a grade do Style Guide(
.row>.col-md-12,.col-md-8), garantindo que a barra de busca se adapte perfeitamente, tanto no celular quanto em monitores grandes. -
Classes utilitárias (Helpers CSS): evite criar CSS duplicado para margens ou fontes. Utilizamos os helpers oficiais do Fluig, como
fs-mb-32(Margin-Bottom de 32px),fs-p-16(Padding de 16px) efs-font-16(tamanho de fonte 16px). -
Iconografia: elementos de interface usam as fontes oficiais do produto, como
flaticon-aimeflaticon-open-package, garantindo padronização.
Exemplo do edit.ftl usando o Style Guide e capturando as preferências:
<div id="wExemplosPraticos_${instanceId}"
class="super-widget wcm-widget-class fluig-style-guide"
data-params="wExemplosPraticos.instance({'folderIdPref': '${folderIdPref!0}', 'limitPref': '${limitPref!15}', 'orderingPref': '${orderingPref!'RELEVANT'}'})">
<div class="panel panel-warning fs-mb-32">
<div class="panel-heading">
<h3 class="panel-title">
<i class="flaticon flaticon-settings icon-sm fs-mr-8"></i> Configurações da Busca
</h3>
</div>
<div class="panel-body">
<div class="row">
<div class="col-md-4 col-sm-12 form-group">
<label for="folderId_${instanceId}" class="fs-color-neutral-09">ID da Pasta Raiz</label>
<input type="number" class="form-control" id="folderId_${instanceId}" placeholder="Ex: 0">
</div>
</div>
<div class="text-right">
<button type="button" class="btn btn-success" data-save-preferences>
<i class="flaticon flaticon-save icon-sm fs-mr-4"></i> Salvar Configurações
</button>
</div>
</div>
</div>
</div>
Erro 3 - Confundir bindings locais e globais
Criar eventos de clique que não funcionam em modais ou elementos dinâmicos porque foram declarados de forma errada. A distinção é feita pela origem do elemento HTML:
-
Local: exclusivo para elementos DOM que já existem estaticamente na renderização inicial do seu arquivo
.ftl; -
Global: obrigatório para elementos injetados dinamicamente no DOM após o carregamento. Ex.: botões dentro de janelas
FLUIGC.modalou linhas de tabela inseridas por requisições AJAX.
JavaScript
var wExemplosPraticos = SuperWidget.extend({
// ✅ CORRETO: Variável para armazenar a instância da modal.
modalTesteInstance: null,
bindings: {
// ✅ LOCAL: Botão estático que veio no view.ftl
local: {
'abrir-modal': ['click_abrirModalTest']
},
// ✅ GLOBAL: Botão dinâmico que só existirá quando a Modal for aberta
global: {
'salvar-modal': ['click_salvarDadosModal']
}
},
abrirModalTest: function() {
// ✅ CORRETO: Armazenamos a instância no contexto da Widget (this)
this.modalTesteInstance = FLUIGC.modal({
title: 'Interação Dinâmica',
content: '<button class="btn btn-success" data-salvar-modal>Confirmar</button>',
id: `modalTeste_${this.instanceId}`
});
},
salvarDadosModal: function(el, ev) {
// 'el' é o elemento clicado, 'ev' é o evento do DOM
FLUIGC.toast({ title: 'Sucesso', message: 'Botão dinâmico acionado!', type: 'success' });
// ✅ CORRETO: Destruímos a modal acessando a instância salva
if (this.modalTesteInstance) {
this.modalTesteInstance.remove();
}
}
});
Erro 4 - Não planejar o identificador (código) da widget
O código da sua widget (application.code) não pode ser igual ao código de nenhuma outra widget, de nenhum layout e de nenhuma URL de página pública do portal. Caso isso ocorra, o deploy falhará por duplicidade.
JavaScript
# ❌ INCORRETO (Nome genérico)
application.code=teste_widget
application.plugin=teste_widget
# ✅ CORRETO (Nome planejado e único)
application.code=wExemplosPraticos
application.plugin=wExemplosPraticos
Atenção ao renomear arquivos!
Se você decidir alterar o código identificador de uma widget já iniciada, não basta alterar o arquivo application.info. Para não quebrar, você é obrigado a renomear proporcionalmente o arquivo Javascript base (seu_codigo.js) e o arquivo de propriedades de idiomas (seu_codigo_pt_BR.properties).
Erro 5 - Lentidão de requisições e conflito com "Template Literals"
Dois problemas que afetam muito a performance e a estabilidade da página:
-
Requisições síncronas (bloqueio de tela):
Um dos erros mais graves de performance no Fluig é realizar requisições travando a interface. Primeiramente, precisamos compreender a diferença:
- Síncrona: o código é executado de maneira sequencial. O JavaScript bloqueia a execução da thread principal até que a resposta da API seja recebida. Isso na prática congela a aplicação, impedindo o usuário de clicar, rolar a página ou digitar.
- Assíncrona: o código não espera pela conclusão da chamada para continuar. O controle retorna imediatamente à thread principal, permitindo que a interface do usuário continue fluída e responsiva enquanto a resposta não chega.
Vantagens da abordagem assíncrona:
- Evita bloqueio da UI: a experiência do usuário permanece limpa e sem travamentos.
- Melhor manipulação de erros: permite um trabalho mais efetivo com falhas de rede usando blocos
try/catch. - Código escalável e limpo: com a introdução do padrão
Async/Awaitno ES6, o código assíncrono tornou-se altamente legível, parecendo um código sequencial comum.
-
Conflito de variáveis do FreeMarker com o ES6:
O recurso nativo do JavaScript ES6 para interpolação de strings utiliza crases e a notação ${variavel} (ex: `Meu nome é ${nome}`). Porém, o processador backend do Fluig (FreeMarker) utiliza exatamente a mesma sintaxe (${...}) para injetar dados do servidor. Se você utilizar um Template Literal do ES6 diretamente dentro de uma tag <script> no arquivo .ftl, o servidor do Fluig tentará processá-lo antes da tela carregar, resultando em um erro 500 ou Internal Server Error de sintaxe.
Boa prática integrada (corrigindo ambos os problemas)
No arquivo HTML (view.ftl), monte as variáveis do servidor escapando via data-params para o JS consumir. (O data-params deve obrigatoriamente ser escrito em uma única linha contínua, sem formatar com "Enters")
HTML (view.ftl):
<div id="wExemplosPraticos_${instanceId}"
class="super-widget wcm-widget-class fluig-style-guide"
data-params="wExemplosPraticos.instance({'usuarioLogado': '${user.login!}', 'nomeCompleto': '${pageRender.getUser().getFullName()!}', 'folderIdPref': '${folderIdPref!0}', 'limitPref': '${limitPref!15}', 'orderingPref': '${orderingPref!'RELEVANT'}'})">
<div class="panel panel-info">
<div class="panel-body">
<p class="fs-font-16 fs-mb-16">
<strong class="fs-color-brand-01-dark">Usuário Autenticado:</strong>
<span id="user-info_${instanceId}" class="label label-info fs-p-8"></span>
</p>
<div class="input-group fs-mb-16">
<input type="text" class="form-control" id="input-search_${instanceId}" placeholder="Pesquise no GED...">
<span class="input-group-btn">
<button class="btn btn-success" type="button" data-btn-buscar>
<i class="flaticon flaticon-search icon-sm"></i> Buscar
</button>
</span>
</div>
<div id="loading-search_${instanceId}" class="fs-display-none fs-p-16 fs-text-center">
<i class="flaticon flaticon-refresh icon-md flaticon-spin fs-color-success"></i> Carregando...
</div>
<ul id="list-results_${instanceId}" class="list-group fs-no-margin-bottom"></ul>
</div>
</div>
</div>
Javascript:
Em vez de utilizar $.ajax, você pode modernizar sua widget utilizando a API nativa fetch combinada com funções async. Veja um exemplo:
// ✅ Função declarada como 'async' no objeto da SuperWidget
realizarBusca: async function() {
// Captura o valor do input contido no view.ftl
const termo = $(`#input-search_${this.instanceId}`).val();
if (!termo) {
FLUIGC.toast({ message: 'Digite um termo.', type: 'warning' });
return;
}
// Exibe a div de carregamento removendo a classe do Style Guide
const $loading = $(`#loading-search_${this.instanceId}`).removeClass('fs-display-none');
// Payload configurado via preferências do data-params (Sem Hardcode)
const apiPayload = {
"searchType": "GLOBAL",
"pattern": termo,
"ordering": this.orderingPref || "RELEVANT",
"limit": this.limitPref ? this.limitPref.toString() : "15",
"offset": "0",
"contentSearch": "false",
"documentTypes": ["FILEDOCUMENT"],
"folderToSearch": this.folderIdPref ? this.folderIdPref.toString() : "0"
};
try {
// ✅ 'await' pausa a execução DESTA função, mas a interface do usuário continua livre
const response = await fetch('/api/public/search/advanced', {
method: 'POST',
headers: { 'Content-Type': 'application/json; charset=utf-8' },
body: JSON.stringify(apiPayload)
});
if (!response.ok) {
throw new Error(`Erro na requisição: ${response.status}`);
}
// Aguarda a conversão para JSON
const data = await response.json();
// Esconde o loading após o retorno
$loading.addClass('fs-display-none');
// Navegação defensiva no objeto antes de renderizar na tag <ul>
const docs = (data && data.content && data.content.items) ? data.content.items : [];
this.renderizarResultados(docs); // Função auxiliar que alimenta o #list-results
} catch (error) {
// Tratamento de erros limpo e estruturado
$loading.addClass('fs-display-none');
console.error("Erro API Search:", error);
FLUIGC.toast({ message: 'Erro na API.', type: 'danger' });
}
}
Consulte a documentação oficial do TDN sobre Variáveis e objetos disponíveis no WCM para ver a lista completa.
Dica!
Para dominar o TOTVS Fluig, é essencial compreender e seguir seus padrões. Ao evitar estes erros comuns, você poupará tempo de debug e entregará soluções com muito mais qualidade e eficiência.