Widgets personalizados

Prev Next

Crie seus próprios widgets no Tulip usando HTML, CSS e JavaScript

Requirement to use Custom Widget

Please allow Outgoing access to Custom Widgets. See Networking requirements for a Tulip Cloud Deployment to learn more.* *.tulip-custom-widgets.com

Os itens que você arrasta e solta em seu aplicativo são chamados de widgets. Podem ser botões, imagens, campos de entrada e qualquer outro elemento disponível na barra de ferramentas.

Os widgets personalizados permitem que você escreva seu próprio código e crie seus próprios widgets, que podem ser arrastados e soltos em seu aplicativo para interação. Esse recurso permite que os usuários do Tulip ampliem os limites do que é possível no Tulip.

Assista ao vídeo a seguir para ter uma visão geral desse recurso:

Confira os widgets personalizados disponíveis na Biblioteca do Tulip.

Noções básicas sobre widgets personalizados

A tela de gerenciamento de widgets personalizados pode ser encontrada na página de configurações da sua conta. É aqui que você pode criar e excluir widgets. O editor de widgets personalizados permite que você escreva o código do seu widget, crie Props e Eventos e visualize seu widget. Abaixo está um diagrama que descreve o que são props e eventos:

Props: Props são dados compartilhados entre widgets personalizados e aplicativos Tulip. Os props serão exibidos no editor de aplicativos e permitirão que os editores de aplicativos selecionem quais variáveis, registros de tabela ou outras informações associar ao prop.

Eventos: Eventos são sinais que seu widget pode enviar para sua aplicação. Os eventos permitem que sua aplicação responda com um gatilho e podem transportar informações junto com eles.

Os widgets personalizados podem ser acessados por meio do seletor suspenso “Personalizado” no Editor de Aplicativos.

Crie um widget

Permission Required

Only users with access to account settings (i.e. Account Owners) or users with specific permission to access the custom widget page can use the custom widget editor.

A tela do Editor de Widgets é dividida em quatro seções. O código que você escrever na seção inferior esquerda aparecerá na seção de visualização assim que você clicar nela. Os valores das propriedades podem ser alterados diretamente na seção de visualização para fins de teste.

Existem funções especiais para interagir com Props e Eventos.

Obter o valor de uma propriedade

Warning

It is not guaranteed that the prop being used by a widget will load before that widget loads, so all props associated with Table record fields, Aggregations, or any other dynamic value should include logic to support cases where they are null at the point the widget is loaded. See this section for details on triggering logic when a prop changes.

//Obtendo o valor de uma propriedade getValue('My Prop');``//Armazenando a propriedade em uma variável let myVariable = getValue('My Prop');

Executar uma ação quando uma propriedade for alterada

//Fazer algo sempre que o valor de um prop mudar getValue('My Prop', (internalVariable) => { doSomething(internalVariable);});

Definir o valor de um Prop


//Definir o valor de uma propriedade de texto como 'hello!' setValue('My Text Prop', 'hello!');

// Defina o valor de um item em um objeto setValue('My Object Prop', { 'Key inside object': 'new value' });```

### Disparar um evento

```**Disparar um evento** fireEvent('event', *payload*);

//Disparar um evento sem carga útil fireEvent('Meu Evento');

//disparar evento com carga de dados fireEvent('Meu Evento', minhaVariável);```

O diagrama a seguir ilustra o fluxo de informações por meio de um widget personalizado em um aplicativo. Os dados da variável de entrada são armazenados na propriedade. A alteração do valor sinaliza um evento, no qual um gatilho armazena os dados na variável de saída.

![Custom Widget App Setup Diagram](https://cdn.document360.io/7c6ff534-cad3-4fc8-9583-912c4016362f/Images/Documentation/Custom%20Widget%20App%20Setup%20Diagram.jpg){height="" width=""}

## Demonstração de criação de widget

![Video](https://www.youtube.com/embed/H08-W2M2Ypo?rel=0){height="576" width="1024"}

## Importação/exportação de aplicativos

Consulte [Importação e exportação](/r230/docs/import-export) para saber mais sobre esse recurso.

### Exportar

1. Na visão geral do widget personalizado, selecione o **menu de três pontos** ao lado do widget que você deseja exportar.

![](https://cdn.document360.io/7c6ff534-cad3-4fc8-9583-912c4016362f/Images/Documentation/Custom%20Widgets%20Overview_477703991.png){height="" width=""}

1. Selecione **“Exportar**”.

![](https://cdn.document360.io/7c6ff534-cad3-4fc8-9583-912c4016362f/Images/Documentation/Custom%20Widgets%20Overview_477704053.png){height="" width="800"}

### Importar

1. Na tela de visão geral dos widgets personalizados, selecione o **menu de três pontos** na parte superior.

![](https://cdn.document360.io/7c6ff534-cad3-4fc8-9583-912c4016362f/Images/Documentation/Custom%20Widgets%20Overview_477704175.png){height="" width="800"}

1. Selecione **“Importar**”.

![](https://cdn.document360.io/7c6ff534-cad3-4fc8-9583-912c4016362f/Images/Documentation/Custom%20Widgets%20Overview_477704228.png){height="" width="350"}

1. Localize o arquivo .json do widget personalizado.

## Habilite bibliotecas externas

As bibliotecas externas ampliam ainda mais as funcionalidades dos widgets personalizados. Elas cuidam do trabalho manual de escrever HTML diretamente em JavaScript. As bibliotecas externas devem ser ativadas para cada widget em que você deseja utilizá-las.

1. No seu widget, clique no **menu de três pontos** no canto superior direito da página.

![](https://cdn.document360.io/7c6ff534-cad3-4fc8-9583-912c4016362f/Images/Documentation/Custom%20Widgets%20Overview_477684182.png){height="" width=""}

1. Selecione **“Ativar biblioteca externa**”.

![](https://cdn.document360.io/7c6ff534-cad3-4fc8-9583-912c4016362f/Images/Documentation/Custom%20Widgets%20Overview_477684247.png){height="" width="250"}

1. Selecione a **extensão** que deseja habilitar.

![Screenshot 2025-02-06 at 9.13.14 AM](https://cdn.document360.io/7c6ff534-cad3-4fc8-9583-912c4016362f/Images/Documentation/Screenshot%202025-02-06%20at%209.13.14%20AM.png){height="" width=""}

Aqui está uma descrição básica do que cada extensão faz:

* **jQuery** — permite uma seleção mais simplificada de elementos HTML, além da manipulação desses elementos.
* **D3 —** o padrão de excelência para visualização de dados; tem uma curva de aprendizado íngreme, mas oferece muita flexibilidade.
* **Google Visualization —** uma ótima ferramenta para fazer exatamente o que o nome indica: visualização.
* **Lodash —** O Lodash oferece uma infinidade de ferramentas que facilitam o trabalho com tipos de dados em JavaScript.
* **ChartJS –** É provavelmente a biblioteca de gráficos mais simples que existe, com muitos recursos online.
* **Moment -** O Moment oferece inúmeras ferramentas para trabalhar com datas e horários.

## Versão do runtime

A forma como os widgets personalizados são carregados no novo runtime foi atualizada (consulte [Melhorias nos widgets personalizados](/r230/docs/custom-widget-improvements)). Essa mudança afeta o momento em que você pode acessar com segurança as propriedades do seu widget, como aquelas recuperadas com `getValue()`.

Para verificar qual versão do runtime seu widget utiliza:

* Na página **da lista de Widgets Personalizados** ou no editor, se o widget tiver um ícone **“Legacy”** ao lado do nome, ele usa o runtime antigo. Se não houver ícone, o widget usa o novo runtime.
* No painel lateral de informações do Widget Personalizado, consulte a seção **“Runtime** ”.
* Nas **configurações de permissão** de um widget personalizado, se a opção **“Runtime do widget personalizado** ” estiver ativada, o widget está usando o novo runtime.

:::(Info) (NOTE)
By default, newly created Custom Widgets use the old runtime.
:::

Para alterar a versão do runtime de um widget personalizado, use o botão de alternância **“Runtime do widget personalizado”** nas **configurações de permissão**.![Custom widget runtime toggle](https://cdn.document360.io/7c6ff534-cad3-4fc8-9583-912c4016362f/Images/Documentation/Custom%20widget%20runtime%20toggle.png){height="" width=""}

## Exemplos

Abaixo estão vários widgets personalizados da [Biblioteca Tulip](https://tulip.co/library/) que ilustram os recursos descritos acima:

### Widget de temporizador simples

[Widget de temporizador simples](https://library.tulip.co/apps/simple-timer-widget)

Um widget de temporizador fácil de usar para monitorar o tempo decorrido durante os fluxos de trabalho do aplicativo. Os operadores podem iniciar, parar e reiniciar o temporizador diretamente da interface do aplicativo, o que o torna útil para cronometrar tarefas ou processos.

![Screenshot 2025-07-02 at 15.33.20.png](https://cdn.document360.io/7c6ff534-cad3-4fc8-9583-912c4016362f/Images/Documentation/Screenshot%202025-07-02%20at%2015.33.20.png){height="" width=""}

### Widget de medidor radial

[Widget de medidor radial](https://library.tulip.co/apps/radial-gauge-widget)

Este widget representa visualmente valores numéricos por meio de um medidor radial personalizável. É ideal para exibir métricas como velocidade da máquina, temperatura ou progresso em um formato rápido e de fácil compreensão.

![Screenshot 2025-07-02 at 15.34.22.png](https://cdn.document360.io/7c6ff534-cad3-4fc8-9583-912c4016362f/Images/Documentation/Screenshot%202025-07-02%20at%2015.34.22.png){height="" width=""}

### Widget de linha do tempo

[Widget de linha do tempo](https://library.tulip.co/apps/timeline-widget)

Mostra eventos ou etapas ao longo de uma linha do tempo horizontal, ajudando os usuários a visualizar etapas de processos, tarefas concluídas ou dados temporais de forma rápida e intuitiva.

![Screenshot 2025-07-02 at 15.34.59.png](https://cdn.document360.io/7c6ff534-cad3-4fc8-9583-912c4016362f/Images/Documentation/Screenshot%202025-07-02%20at%2015.34.59.png){height="" width=""}

## Melhores práticas

### 1. Evite efeitos colaterais indesejados causados pelo código do widget que é executado ao carregar

O Tulip executa o código real do seu widget sempre que ele é renderizado — não apenas no Player, mas também no editor de Widgets Personalizados, no Editor de Aplicativos e na Visualização ao Vivo de uma Estação. Isso oferece uma visualização ao vivo precisa do seu widget. Qualquer código que cause efeitos colaterais (por exemplo, chamadas de API, gravações em tabelas, notificações) no nível superior do seu script é executado toda vez que o widget é renderizado, inclusive cada vez que o widget é reaberto no editor ou a pré-visualização é recarregada.

Condicione qualquer ação com efeito no mundo real a um gatilho explícito (por exemplo, o pressionamento de um botão, um `` `fireEvent` `` em resposta a uma ação do usuário ou uma alteração de `Prop`), em vez de acioná-la automaticamente ao carregar a página.

```// CORRETO: só é acionado quando o aplicativo solicita explicitamente getValue('TriggerIncrement', (shouldIncrement) => { if (shouldIncrement) { fetch("https://api.example.com/increment-counter", { method: "POST" }); }});

// RUIM: é acionado toda vez que o widget é carregado, inclusive no editor e na visualizaçãofetch("https://api.example.com/increment-counter", { method: "POST" });```Se um widget precisar carregar dados automaticamente, use um endpoint somente leitura ou idempotente para que carregamentos repetidos não causem efeitos indesejados.

### 2. Teste de forma iterativa sem acionar efeitos colaterais reais

Ao desenvolver um widget que chama uma API de produção com efeitos colaterais reais, aponte para um endpoint de teste ou de staging até concluir a iteração; em seguida, mude para um endpoint de produção antes de publicar. Isso evita registros duplicados, notificações ou outros efeitos colaterais decorrentes da edição e visualização do seu widget.

Para ações que devem ocorrer na produção mesmo após a publicação, incorpore a confirmação na própria interface do usuário do widget, em vez de depender do tempo de carregamento. Ao exigir uma ação visível (por exemplo, um clique em um botão), a pessoa que visualiza o widget tem controle sobre quando o efeito ocorre.

```// Exige um clique explícito antes que a ação real seja acionada,// independentemente de quantas vezes o próprio widget tenha sido renderizado document.getElementById('submitBtn')?.addEventListener('click', () => { fetch("https://api.example.com/submit-order", { method: "POST" });});

3. Gerenciamento de props — Valide o valor da prop

Certifique-se de que seu widget personalizado lide com os dados de maneira confiável. As props são uma forma de passar informações para o seu widget, mas seus valores podem mudar ou podem estar inicialmente indefinidos. Mesmo que as props tenham um tipo definido, elas podem estar inicialmente indefinidas, o que pode causar erros no código do seu widget personalizado. Seguir um bom padrão evita erros e torna seu widget mais robusto.

O exemplo abaixo usa uma função de retorno de chamada como segundo parâmetro para o getValue. Essa função de retorno de chamada será executada sempre que o widget personalizado receber o valor atualizado da propriedade.

Como o valor inicial da prop pode estar indefinido e, posteriormente, receber o valor atualizado, é importante garantir que a configuração seja aplicada assim que o valor atualizado for recebido. Usar getValue sem a função de callback retorna o valor da prop naquele momento da execução do código.

getValue('config', function(config) { if (config) { applyConfiguration(config); }});

O exemplo a seguir não é recomendado, pois o valor inicial da prop pode estar indefinido:const config = getValue('config');applyConfiguration(config);

4. Tratamento de eventos — Validação de dados

Os dados de eventos têm um tipo. Embora disparar um evento com um tipo de dados incorreto não cause falha nem interrompa a execução do código do seu widget personalizado, é uma boa prática ter suas próprias verificações para garantir que os dados enviados no fireEvent sejam validados e tenham o tipo correto.

Essa prática evita comportamentos inesperados e torna seu código mais previsível.

if (data != null && typeof data === 'string') { fireEvent(eventName, data);} else { // Opcionalmente, trate o caso de dados inválidos.}

5. Use uma função de retorno de chamada para acesso assíncrono a props

Quando você chama setValue(), a atualização da prop é assíncrona. Isso significa que ela não ocorre imediatamente, pois seu widget personalizado está se comunicando com o aplicativo principal a partir de um iframe. A solicitação setValue() é enviada, mas seu código continua sendo executado sem esperar que a alteração entre em vigor. Se você tentar usar getValue() logo em seguida, provavelmente obterá o valor antigo.

Para obter os dados mais atualizados, sempre use getValue() com uma função de retorno de chamada. A função de retorno de chamada só é executada quando a propriedade for atualizada com sucesso, o que garante que você esteja trabalhando com as informações mais recentes.

O exemplo a seguir ilustra essa prática recomendada:```// CORRETO: Acessar o valor de uma propriedade de forma assíncronaconst newConfig = { setting: 'value' };setValue('config', newConfig);

// A função de retorno de chamada será executada quando a atualização da propriedade estiver concluída.getValue('config', function(updatedConfig) { console.log('A propriedade foi atualizada. A nova configuração é:', updatedConfig); // Faça algo com o novo valor garantido applyConfiguration(updatedConfig);});```

O exemplo a seguir mostra o que você deve evitar:```// RUIM: Isso pode não funcionar como esperadoconst newConfig = { setting: 'value' };setValue('config', newConfig);

// Provavelmente, isso registrará o valor antigo, e não o novo.const currentConfig = getValue('config'); ```

6. Use cabeçalhos compatíveis para chamadas de API

Ao fazer chamadas de API externas a partir de widgets personalizados, tenha cuidado com os cabeçalhos HTTP para evitar problemas de CORS (Cross-Origin Resource Sharing). Os widgets personalizados são executados em um ambiente iframe com restrições de segurança específicas.

Use apenas cabeçalhos explicitamente suportados. Definir cabeçalhos não suportados, como Content-Type ou outros cabeçalhos personalizados, pode acionar erros de CORS e impedir que suas chamadas de API funcionem.

O exemplo a seguir ilustra essa prática recomendada:// CORRETO: Chamada de API simples sem cabeçalhos personalizadosfetch("https://akhatri.bulb.cloud/api/v3/w/1/tables", { headers: { Authorization: "<chave da API>", },}) .then((response) => response.json()) .then((data) => console.log(data)) .catch((error) => console.error("Erro:", error));

O exemplo a seguir mostra o que você deve evitar:// INCORRETO: Definir o cabeçalho Content-Type causará problemas de CORSfetch("https://api.example.com/data", { headers: { Authorization: "<chave da API>", "Content-Type": "application/json", // Isso causará erros de CORS },})

Troubleshoot CORS error

If you encounter CORS errors, first remove any custom headers you've set (especially Content-Type) and test with minimal headers. The iframe security model restricts which headers can be set from custom widgets.

7. Inicialize widgets com base no seu ambiente de execução

Consulte “Versão do ambiente de execução ” para obter mais informações.

Novo ambiente de execução

No novo ambiente de execução, o atributo src retorna um arquivo HTML vazio. Depois de carregado, esse arquivo envia uma mensagem para o aplicativo pai. O aplicativo pai envia o código do widget personalizado, as funções utilitárias (como getValue()) e os valores iniciais das propriedades para serem injetados no iframe.

Por causa disso, DOMContentLoaded e window.onload são disparados quando o shell vazio é carregado, antes que o código do seu widget seja injetado. Qualquer manipulador vinculado a esses eventos é registrado tarde demais: o evento já foi disparado, então a chamada de retorno nunca é executada. O código dentro desses manipuladores é silenciosamente ignorado, conforme mostrado no exemplo abaixo:

document.addEventListener('DOMContentLoaded', function() { // É disparado quando o SHELL é carregado, antes da injeção do código do usuário // O código do usuário ainda nem foi injetado! const myProp = getValue('myProp'); // ERRO: getValue não está definido});

Você não precisa usar DOMContentLoaded ou window.onload no custom widgets. O Tulip carrega recursos na seguinte ordem: bibliotecas de terceiros, HTML do usuário e, em seguida, JavaScript. Quando o seu JavaScript for executado, o DOM já estará disponível. Você pode acessar elementos do DOM diretamente sem esperar por um evento de carregamento, conforme mostrado no exemplo abaixo:

// É seguro chamar diretamente — o DOM já está carregado quando isso é executado.const btn = document.getElementById('my-btn');

Runtime antigo

No ambiente de execução antigo, o atributo srcDoc injeta o código do widget personalizado no iframe, juntamente com funções utilitárias como getValue() e os valores iniciais das propriedades. Como o código do widget faz parte do documento desde o início, getValue() e suas propriedades ficam disponíveis assim que o conteúdo do iframe é carregado.Como resultado, DOMContentLoaded e window.onload funcionam conforme o esperado no ambiente de execução antigo, conforme mostrado no exemplo a seguir:

document.addEventListener('DOMContentLoaded', function() { // É disparado quando o HTML do usuário está pronto // As propriedades já estão disponíveis const myProp = getValue('myProp'); // Funciona imediatamente});

{{snippet.Você encontrou o que estava procurando?}}