Widgets personalizados

Prev Next

Crea tus propios widgets en Tulip utilizando HTML, CSS y JavaScript

Who can use this feature

Users on Professional plans and above.

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

Los elementos que arrastras y sueltas en tu aplicación se denominan «widgets». Pueden ser botones, imágenes, cuadros de entrada y cualquier otro elemento disponible en la barra de herramientas.

Los widgets personalizados te permiten escribir tu propio código y crear tus propios widgets, que luego puedes arrastrar y soltar en tu aplicación e interactuar con ellos. Esta capacidad permite a los usuarios de Tulip ampliar los límites de lo que es posible en Tulip.

Mira el siguiente vídeo para obtener una visión general de esta función:

Consulta los widgets personalizados disponibles en la biblioteca de Tulip.

Conceptos básicos sobre los widgets personalizados

La pantalla de gestión de widgets personalizados se encuentra en la página de configuración de tu cuenta. Aquí es donde puedes crear y eliminar widgets. El editor de widgets personalizados te permite escribir el código de tu widget, crear propiedades (props) y eventos, y obtener una vista previa de tu widget. A continuación se muestra un diagrama que describe qué son las propiedades y los eventos:

Propiedades: Las propiedades son datos que se comparten entre los widgets personalizados y las aplicaciones de Tulip. Las propiedades se mostrarán en el editor de aplicaciones y permitirán a los editores de aplicaciones seleccionar qué variables, registros de tabla u otra información asociar a la propiedad.

Eventos: Los eventos son señales que tu widget puede enviar a tu aplicación. Los eventos permiten que tu aplicación responda con un disparador y pueden transportar información junto con ellos.

Se puede acceder a los widgets personalizados a través del selector desplegable «Personalizado» del editor de aplicaciones.

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

La pantalla del editor de widgets se divide en cuatro secciones. El código que escribas en la sección inferior izquierda aparecerá en la sección de vista previa en cuanto hagas clic en ella. Los valores de las propiedades se pueden modificar directamente en la sección de vista previa con fines de prueba.

Existen funciones especiales para interactuar con las propiedades y los eventos.

Obtener el valor de una propiedad

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.

//Obtener el valor de una propiedad getValue('My Prop');``//Almacenar la propiedad en una variable let myVariable = getValue('My Prop');

Realizar una acción cuando cambia una propiedad

//Realizar una acción cada vez que cambie el valor de un prop getValue('My Prop', (internalVariable) => { doSomething(internalVariable);});

Establecer el valor de un prop


//Establecer el valor de una propiedad de texto en «¡hola!» setValue('My Text Prop', '¡hola!');

//Establecer el valor de un elemento de un objeto setValue('Mi propiedad de objeto', { 'Clave dentro del objeto': 'nuevo valor' });```

### Disparar un evento

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

//Disparar un evento sin carga útil: fireEvent('My Event');

//Disparar un evento con carga útil: fireEvent('My Event', myVariable);```

El siguiente diagrama ilustra el flujo de información a través de un widget personalizado en una aplicación. Los datos de la variable de entrada se almacenan en la propiedad. El cambio de valor activa un evento en el que un disparador almacena los datos en la variable de salida.

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

## Demostración de creación de un widget

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

## Importación/exportación de aplicaciones

Consulta [«Importación y exportación»](/r230/docs/import-export) para obtener más información sobre esta función.

### Exportar

1. En la vista general de widgets personalizados, selecciona el **menú de tres puntos** situado junto al widget que deseas exportar.

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

1. Selecciona **«Exportar**».

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

### Importar

1. En la pantalla de resumen de widgets personalizados, selecciona el **menú de los tres puntos** situado en la parte superior.

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

1. Selecciona **«Importar**».

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

1. Busca el archivo .json del widget personalizado.

## Habilitar bibliotecas externas

Las bibliotecas externas amplían aún más las posibilidades de los widgets personalizados. Las bibliotecas externas se encargan del trabajo manual de escribir código HTML directamente en JavaScript. Las bibliotecas externas deben habilitarse para cada widget en el que quieras utilizarlas.

1. Una vez en el widget, haz clic en el **menú de los tres puntos** situado en la parte superior derecha de la página.

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

1. Selecciona **«Habilitar biblioteca externa**».

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

1. Selecciona la **extensión** que quieras 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=""}

A continuación se ofrece una descripción básica de lo que hace cada extensión:

* **jQuery**: permite una selección más ágil de elementos HTML, además de la manipulación de los mismos.
* **D3:** el estándar de referencia para la visualización de datos; tiene una curva de aprendizaje pronunciada, pero ofrece una gran flexibilidad.
* **Google Visualization:** una herramienta estupenda para, como su nombre indica, la visualización.
* **Lodash:** ofrece un sinfín de herramientas que facilitan el trabajo con los tipos de datos de JavaScript.
* **ChartJS:** es probablemente la biblioteca de gráficos más sencilla que existe, con muchísimos recursos en línea.
* **Moment:** Moment ofrece un sinfín de herramientas para trabajar con fechas y horas.

## Versión del entorno de ejecución

Se ha actualizado la forma en que se cargan los widgets personalizados en el nuevo entorno de ejecución (consulta [«Mejoras en los widgets personalizados»](/r230/docs/custom-widget-improvements)). Este cambio afecta al momento en el que puedes acceder de forma fiable a las propiedades de tu widget, como las que se recuperan con `getValue()`.

Para saber qué versión del entorno de ejecución utiliza tu widget:

* En la página **de la lista de widgets personalizados** o en el editor, si el widget tiene una insignia **«Legacy»** junto a su nombre, significa que utiliza el entorno de ejecución antiguo. Si no hay ninguna insignia, el widget utiliza el nuevo entorno de ejecución.
* En el panel lateral de información del widget personalizado, consulta la sección **«Entorno de ejecución** ».
* En la **configuración de permisos** de un widget personalizado, si el conmutador **«Entorno de ejecución del widget personalizado»** está activado, el widget utiliza el nuevo entorno de ejecución.

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

Para cambiar la versión del entorno de ejecución de un widget personalizado, utiliza el interruptor **«Entorno de ejecución del widget personalizado»** en **la configuración de permisos**.![Custom widget runtime toggle](https://cdn.document360.io/7c6ff534-cad3-4fc8-9583-912c4016362f/Images/Documentation/Custom%20widget%20runtime%20toggle.png){height="" width=""}

## Ejemplos

A continuación se muestran varios widgets personalizados de la [biblioteca Tulip](https://tulip.co/library/) que ilustran las capacidades de la función descritas anteriormente:

### Widget de temporizador sencillo

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

Un widget de temporizador fácil de usar para realizar un seguimiento del tiempo transcurrido durante los flujos de trabajo de la aplicación. Los operadores pueden iniciar, detener y reiniciar el temporizador directamente desde la interfaz de la aplicación, lo que resulta útil para cronometrar tareas o procesos.

![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 indicador radial

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

Este widget representa visualmente valores numéricos mediante un indicador radial personalizable. Es ideal para mostrar métricas como la velocidad de una máquina, la temperatura o el progreso en un formato rápido y fácil de entender.

![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 línea de tiempo

[Widget de línea de tiempo](https://library.tulip.co/apps/timeline-widget)

Muestra eventos o pasos a lo largo de una línea de tiempo horizontal, lo que ayuda a los usuarios a visualizar de un vistazo las etapas de un proceso, las tareas completadas o los datos basados en el tiempo.

![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=""}

## Prácticas recomendadas

### 1. Evita efectos secundarios no deseados del código del widget que se ejecuta al cargarse

Tulip ejecuta el código real de tu widget cada vez que se renderiza, no solo en el reproductor, sino también en el editor de widgets personalizados, el editor de aplicaciones y la vista previa en directo de una emisora. Esto te ofrece una vista previa precisa y en tiempo real de tu widget. Cualquier código que provoque efectos secundarios (por ejemplo, llamadas a la API, escrituras en tablas o notificaciones) en el nivel superior de tu script se ejecuta cada vez que se renderiza el widget, incluyendo cada vez que el widget se vuelve a abrir en el editor o se recarga la vista previa.

Condiciona cualquier acción con un efecto en el mundo real mediante un disparador explícito (por ejemplo, pulsar un botón, un `` `fireEvent` `` en respuesta a una acción del usuario o un cambio de `Prop`), en lugar de activarla automáticamente al cargarse.

```// CORRECTO: solo se ejecuta cuando la aplicación lo solicita explícitamente getValue('TriggerIncrement', (shouldIncrement) => { if (shouldIncrement) { fetch("https://api.example.com/increment-counter", { method: "POST" }); }});

// MAL: se activa cada vez que se carga el widget, incluso en el editor y en la vista previafetch("https://api.example.com/increment-counter", { method: "POST" });```Si un widget necesita cargar datos automáticamente, utiliza un punto final de solo lectura o idempotente para que las cargas repetidas no provoquen efectos no deseados.

### 2. Realiza pruebas de forma iterativa sin provocar efectos secundarios reales

Mientras desarrollas un widget que llama a una API de producción con efectos secundarios reales, apunta a un punto final de prueba o de entorno de staging hasta que hayas terminado de iterar; a continuación, cambia a un punto final de producción antes de publicar. Esto evita registros duplicados, notificaciones u otros efectos secundarios derivados de la edición y la vista previa de tu widget.

Para las acciones que deban realizarse en el entorno de producción incluso después de la publicación, incorpora una confirmación en la propia interfaz de usuario del widget en lugar de depender del tiempo de carga. Al requerir una acción visible (por ejemplo, hacer clic en un botón), la persona que ve el widget tiene control sobre cuándo se produce el efecto.

```// Requiere un clic explícito antes de que se active la acción real,// independientemente de cuántas veces se haya renderizado el propio widgetdocument.getElementById('submitBtn')?.addEventListener('click', () => { fetch("https://api.example.com/submit-order", { method: "POST" });});

3. Gestión de props: valida el valor de los props

Asegúrate de que tu widget personalizado gestione los datos de forma fiable. Las propiedades son una forma de pasar información a tu widget, pero sus valores pueden cambiar o estar inicialmente sin definir. Aunque las propiedades tengan un tipo establecido, podrían estar inicialmente sin definir, lo que podría provocar errores en el código de tu widget personalizado. Seguir un buen patrón evita errores y hace que tu widget sea más robusto.

El ejemplo siguiente utiliza una función de devolución de llamada como segundo parámetro para getValue. Esta función de devolución de llamada se ejecutará cada vez que el widget personalizado reciba el valor actualizado de la propiedad.

Dado que el valor inicial de la prop puede estar indefinido y posteriormente podría recibir el valor actualizado, es importante asegurarse de aplicar la configuración una vez recibido el valor actualizado. El uso de getValue sin la función de devolución de llamada devuelve el valor de la prop en ese momento de la ejecución del código.

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

No se recomienda el siguiente ejemplo, ya que el valor inicial de la propiedad puede estar indefinido:const config = getValue('config');applyConfiguration(config);

4. Gestión de eventos: validar datos

Los datos de los eventos tienen un tipo. Aunque disparar un evento con un tipo de datos incorrecto no provocará un fallo ni detendrá la ejecución del código de tu widget personalizado, es una buena práctica disponer de tus propios controles para garantizar que los datos enviados en fireEvent se validen y sean del tipo correcto.

Esta práctica evita comportamientos inesperados y hace que tu código sea más predecible.

if (data != null && typeof data === 'string') { fireEvent(eventName, data);} else { // Opcionalmente, gestiona el caso de datos no válidos.}

5. Utiliza una función de devolución de llamada para el acceso asíncrono a las propiedades

Cuando llamas a `setValue()`, la actualización de la propiedad es asíncrona. Esto significa que no se produce de forma inmediata, ya que tu widget personalizado se comunica con la aplicación principal desde un iframe. La solicitud de `setValue()` se envía, pero tu código sigue ejecutándose sin esperar a que el cambio surta efecto. Si intentas utilizar getValue() justo después, es probable que obtengas el valor antiguo.

Para obtener los datos más actualizados, utiliza siempre getValue() con una función de devolución de llamada. La función de devolución de llamada solo se ejecuta cuando la propiedad se ha actualizado correctamente, lo que garantiza que estés trabajando con la información más reciente.

El siguiente ejemplo muestra esta buena práctica:```// CORRECTO: Acceder al valor de una propiedad de forma asíncronaconst newConfig = { setting: 'value' };setValue('config', newConfig);

// La función de devolución de llamada se ejecutará cuando se haya completado la actualización de la propiedad.getValue('config', function(updatedConfig) { console.log('La propiedad se ha actualizado. La nueva configuración es:', updatedConfig); // Haz algo con el nuevo valor garantizado applyConfiguration(updatedConfig);});```

El siguiente ejemplo muestra lo que debes evitar:```// MAL: Es posible que esto no funcione como se espera const newConfig = { setting: 'value' }; setValue('config', newConfig);

// Es probable que esto registre el valor antiguo, no el nuevo.const currentConfig = getValue('config'); ```

6. Utiliza encabezados compatibles para las llamadas a la API

Al realizar llamadas a API externas desde widgets personalizados, ten cuidado con los encabezados HTTP para evitar problemas de CORS (Cross-Origin Resource Sharing). Los widgets personalizados se ejecutan en un entorno iframe con restricciones de seguridad específicas.

Utiliza únicamente encabezados que sean explícitamente compatibles. Establecer encabezados no compatibles, como Content-Type u otros encabezados personalizados, puede provocar errores de CORS e impedir que tus llamadas a la API funcionen.

El siguiente ejemplo muestra esta buena práctica:// CORRECTO: Llamada sencilla a la API sin encabezados personalizadosfetch("https://akhatri.bulb.cloud/api/v3/w/1/tables", { headers: { Authorization: "<clave de API>", },}) .then((response) => response.json()) .then((data) => console.log(data)) .catch((error) => console.error("Error:", error));

El siguiente ejemplo muestra lo que debes evitar:// MAL: Establecer el encabezado Content-Type provocará problemas de CORSfetch("https://api.example.com/data", { headers: { Authorization: "<clave API>", "Content-Type": "application/json", // Esto provocará errores 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. Inicializa los widgets según tu entorno de ejecución

Consulta «Versión del entorno de ejecución » para obtener más información.

Nuevo entorno de ejecución

En el nuevo entorno de ejecución, el atributo `src` devuelve un archivo HTML vacío (shell). Una vez cargado, este archivo envía un mensaje a la aplicación principal. La aplicación principal envía el código del widget personalizado, las funciones de utilidad (como `` `getValue() ```) y los valores iniciales de las propiedades que se deben inyectar en el iframe.

Debido a esto, DOMContentLoaded y window.onload se activan cuando se carga el archivo HTML vacío, antes de que se inyecte el código de tu widget. Cualquier controlador asociado a estos eventos se registra demasiado tarde: el evento ya se ha activado, por lo que la llamada de retorno nunca se ejecuta. El código dentro de esos controladores se omite silenciosamente, tal y como se muestra en el ejemplo siguiente:

document.addEventListener('DOMContentLoaded', function() { // Se activa cuando se carga SHELL, antes de la inyección del código del usuario // ¡El código del usuario ni siquiera se ha inyectado todavía! const myProp = getValue('myProp'); // ERROR: getValue no está definido});

No es necesario utilizar DOMContentLoaded ni window.onload en custom widgets. Tulip carga los recursos en el siguiente orden: bibliotecas de terceros, HTML del usuario y, a continuación, JavaScript. Cuando se ejecuta tu JavaScript, el DOM ya está disponible. Puedes acceder a los elementos del DOM directamente sin esperar a un evento de carga, tal y como se muestra en el ejemplo siguiente:

// Se puede llamar directamente sin problema: el DOM ya está cargado cuando se ejecuta esto.const btn = document.getElementById('my-btn');

Antiguo entorno de ejecución

En el entorno de ejecución antiguo, el atributo srcDoc inyecta el código del widget personalizado en el iframe, junto con funciones de utilidad como getValue() y los valores iniciales de las propiedades. Dado que el código del widget forma parte del documento desde el principio, getValue() y tus propiedades están disponibles tan pronto como se carga el contenido del iframe.Como resultado, DOMContentLoaded y window.onload funcionan como se espera en el entorno de ejecución antiguo, tal y como se muestra en el siguiente ejemplo:

document.addEventListener('DOMContentLoaded', function() { // Se activa cuando el HTML del usuario está listo // Las propiedades ya están disponibles const myProp = getValue('myProp'); // Funciona inmediatamente});

{{snippet.¿Has encontrado lo que buscabas?}}