Files
2026-05-06 13:35:47 -03:00

371 lines
18 KiB
Markdown

---
title: 5 Erros comuns em widgets no TOTVS Fluig
source: https://tdn.totvs.com/display/fluig/5+Erros+comuns+em+widgets+no+TOTVS+Fluig
path: \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](#id-5ErroscomunsemwidgetsnoTOTVSFluig-Erroscomunsnodesenvolvimentodewidgets)
- [Erro 1 - Escopo da super widget e o perigo das múltiplas instâncias](#id-5ErroscomunsemwidgetsnoTOTVSFluig-Erro1-Escopodasuperwidgeteoperigodasmúltiplasinstâncias)
- [Erro 2 - Deixar dados fixos (Hardcoded) e ignorar o Style Guide](#id-5ErroscomunsemwidgetsnoTOTVSFluig-Erro2-Deixardadosfixos(Hardcoded)eignoraroStyleGuide)
- [Erro 3 - Confundir bindings locais e globais](#id-5ErroscomunsemwidgetsnoTOTVSFluig-Erro3-Confundirbindingslocaiseglobais)
- [Erro 4 - Não planejar o identificador (código) da widget](#id-5ErroscomunsemwidgetsnoTOTVSFluig-Erro4-Nãoplanejaroidentificador(código)dawidget)
- [Erro 5 - Lentidão de requisições e conflito com "Template Literals"](#id-5ErroscomunsemwidgetsnoTOTVSFluig-Erro5-Lentidãoderequisiçõeseconflitocom"TemplateLiterals")
# 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**!
![](..\..\images\5erroswidget_1920px_8fps_128c_none_bicubic_rectangle.gif)
Download do exemplo:
Caso você tenha interesse em fazer o *download* do projeto que criamos para este exemplo, [clique aqui](https://tdn.totvs.com/download/attachments/1040986447/5erroscomunswidgets.zip?version=1&modificationDate=1777061311597&api=v2).
## 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 `var` provê na raiz do arquivo. Utilizar `const` ou `let` para declarar a variável da SuperWidget impede de instanciar o objeto no `window`.
- **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 `id` da `div` encapsuladora nos arquivos `view.ftl` e `edit.ftl` possui a mesma nomenclatura base da variável JavaScript. Exemplo: se a div é `<div id="wExemplosPraticos_${instanceId}">`, a variável deve ser `var 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.
1. 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();
}
});
```
2. 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-heading` e `.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) e `fs-font-16` (tamanho de fonte 16px).
- **Iconografia:** elementos de interface usam as fontes oficiais do produto, como `flaticon-aim` e `flaticon-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.modal` ou 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/Await` no 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](https://tdn.totvs.com/display/fluig/Layouts#Layouts-Vari%C3%A1veisDispon%C3%ADveisnoFTL) 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.