371 lines
18 KiB
Markdown
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**!
|
|
|
|

|
|
|
|
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. |