Skip to main content

UI Widgets

UI Widgets are custom Svelte components that can be added to UI Pages. Widgets can display data, provide controls, show charts, integrate external services, or present any custom visualization.

Overview

The UI Widgets page provides a complete development environment for creating custom dashboard widgets using Svelte 5. Widgets are compiled server-side and delivered to clients as standalone components.

Interface Layout

The page is split into sections:

Left Side - Widget List:

  • Dropdown selector of all Svelte widgets
  • Filter/search capabilities
  • New widget button
  • Delete widget button
  • Recompile All button

Right Side - Widget Editor:

  • Widget configuration form
  • Code editor with syntax highlighting
  • Live preview pane
  • Error display
  • Save/test buttons

Creating a Widget

To create a new widget:

  1. Click New Widget button
  2. Enter widget title when prompted (e.g., "Weather Dashboard")
  3. Widget is created with default template
  4. Code editor loads with starter code
  5. Customize the widget (see Coding Widgets section)
  6. Click Save to compile and save

Widget Configuration

Basic Settings

Widget Selector

  • Select existing widget to edit
  • Shows all Svelte widgets
  • Switching widgets preserves unsaved changes

Name

  • Internal identifier (lowercase_with_underscores)
  • Auto-generated from title
  • Cannot contain spaces or special characters

Title

  • Display name shown in widget selector
  • Appears in UI page configuration
  • User-friendly text

Enabled

  • Toggle to enable/disable widget
  • Disabled widgets:
    • Don't compile
    • Cannot be added to pages
    • Configuration preserved

Admin Only

  • When on, the widget is hidden from the end-user UI Pages widget picker
  • Used for admin dashboard tiles (Monitor Tag rollups, Active Zones, Climate Issues, etc.) that don't make sense on regular user-facing UIs
  • Admin-only widgets are still addable to the admin home dashboard via its Customize Dashboard modal
  • An ADMIN badge appears next to the Title field while editing an admin-only widget, and the widget selector dropdown appends • ADMIN after the name so the scope is visible at a glance

Width

  • Percentage of container width (0-100%)
  • Default: 30%
  • Determines widget size on page

Height

  • Percentage of container height (0-100%)
  • Default: 45%
  • Determines widget vertical size

Code Editor

Full-featured Svelte 5 code editor:

Features

  • Syntax Highlighting: JavaScript, HTML, CSS
  • Line Numbers: Easy navigation
  • Auto-Save: Preserves work automatically (draft saved to browser)
  • Tab Support: Proper code indentation
  • Find/Replace: Standard editor features
  • Full Screen: Expand editor for focused coding

Widget Structure

Widgets follow standard Svelte single-file component format:

<script>
import {onMount} from 'svelte';
import GemApp from '../gem/app';

let data = [];

onMount(async function() {
// Initialize widget
loadData();
});

async function loadData() {
// Fetch data for widget
data = await GemApp.getInstance().query('zone', {subsystem_id: 1});
}
</script>

<style>
.widget-container {
padding: 15px;
height: 100%;
}
.widget-title {
font-size: 1.2rem;
font-weight: 600;
margin-bottom: 10px;
}
</style>

<div class="widget-container">
<div class="widget-title">My Widget</div>
{#each data as item}
<div>{item.label}: {item.power}</div>
{/each}
</div>

Available APIs in Widgets

Widgets have access to:

GemApp Instance:

const gem = GemApp.getInstance();

Common Methods:

// Query data
let zones = await gem.query('zone', {subsystem_id: 1});
let device = await gem.queryOne('device', {id: 5});

// Send commands
await gem.command({zone_id: 10, command: 'on'});

// Execute macros
await gem.macro(macroId);

// Get attributes
let attrs = await gem.getAttributes('zone', zoneId);
let value = await gem.getAttribute('device', deviceId, 'temperature');

// Set attributes
await gem.setAttribute('zone', zoneId, 'brightness', 75, 'int');

// Subscribe to updates
let token = await gem.subscribe('zone', zoneId, (data) => {
console.log('Zone updated:', data);
});

// Unsubscribe
await gem.unsubscribe('zone', zoneId, token);

// Notifications
gem.showMessage({message: 'Action complete', severity: 'success'});

Svelte 5 Features:

<script>
import {onMount} from 'svelte';

let count = $state(0); // Reactive state
let doubled = $derived(count * 2); // Derived value

function increment() {
count++;
}
</script>

<button onclick={increment}>
Count: {count} (Doubled: {doubled})
</button>

Live Preview

The preview pane shows the widget as it will appear:

Preview Features

  • Live Rendering: Widget renders in real-time
  • Theme Applied: Uses current system theme
  • Actual Size: Shows configured width/height percentages
  • Interactive: Widget functionality works in preview
  • Error Display: Compilation/runtime errors shown below

Preview Controls

Collapse/Expand

  • Toggle preview visibility
  • Collapse for more editor space
  • Expand to see changes

Size Preview

  • Preview respects width/height settings
  • Change width/height to see size impact

Compiling Widgets

Automatic Compilation

When you click Save:

  1. Widget configuration saved to database
  2. Widget code compiled to JavaScript
  3. Compilation output cached
  4. Preview updated automatically
  5. Success or error message shown

Compilation Process

Steps:

  1. Svelte compiler processes the code
  2. JavaScript generated
  3. CSS extracted and scoped
  4. Component registered with GEM
  5. Cache updated
  6. UIs notified of new widget version

Compilation Errors

If compilation fails:

Error Display:

  • Red error box appears below preview
  • Shows error message
  • Shows line number and column
  • Indicates problem area

Common Errors:

  • Syntax errors (missing brackets, quotes)
  • Invalid Svelte syntax
  • Import errors
  • Type errors

Fix Process:

  1. Review error message
  2. Locate problem line
  3. Fix the issue
  4. Click Save to recompile
  5. Error clears when successful

Runtime Errors

Errors that occur when widget runs:

Error Display:

  • Shown in preview below widget
  • Logged to browser console
  • May include stack trace

Common Causes:

  • API call failures
  • Null reference errors
  • Invalid data format
  • Subscription errors

Debugging:

  • Use console.log() extensively
  • Check browser developer console
  • Verify API calls return expected data
  • Test with sample data first

Recompile All Widgets

The Recompile All button recompiles all enabled widgets:

When to Use:

  1. After Svelte Upgrade: Ensure compatibility with new Svelte version
  2. After System Update: Recompile for new GEM APIs
  3. Bulk Fix: Apply compilation improvements to all widgets
  4. Cache Clear: Force regeneration of all widget code

Process:

  1. Click Recompile All
  2. Confirm action
  3. System compiles all enabled widgets
  4. Success/failure message shows results
  5. Individual errors logged for failed widgets

Common Widget Types

Status Widgets

Display system or device status:

<script>
import {onMount} from 'svelte';
let devices = $state([]);

onMount(async () => {
devices = await GemApp.getInstance().query('device', {enabled: true});
});
</script>

<div class="status-widget">
<h3>Device Status</h3>
{#each devices as device}
<div class:online={device.connected} class:offline={!device.connected}>
{device.label}: {device.connected ? 'Online' : 'Offline'}
</div>
{/each}
</div>

Chart Widgets

Display data visualizations:

<script>
import {onMount} from 'svelte';
import Chart from 'chart.js/auto';

let canvas;
let history = $state([]);

onMount(async () => {
history = await GemApp.getInstance().query('attribute_history', {
name: 'temperature',
_limit: 100
});

new Chart(canvas, {
type: 'line',
data: {
labels: history.map(h => h.timestamp),
datasets: [{
label: 'Temperature',
data: history.map(h => h.value)
}]
}
});
});
</script>

<canvas bind:this={canvas}></canvas>

Control Widgets

Interactive controls:

<script>
let zones = $state([]);

async function toggleZone(zoneId) {
await GemApp.getInstance().command({
zone_id: zoneId,
command: 'toggle'
});
await loadZones(); // Refresh
}
</script>

<div class="controls">
{#each zones as zone}
<button onclick={() => toggleZone(zone.id)}>
{zone.label}: {zone.power}
</button>
{/each}
</div>

Data Widgets

Display API or sensor data:

<script>
import {onMount} from 'svelte';
let weather = $state(null);

onMount(async () => {
// Get weather from attribute or API
let w = await GemApp.getInstance().getAttribute('system', 0, 'weather_data');
weather = JSON.parse(w.weather_data || '{}');
});
</script>

{#if weather}
<div class="weather-widget">
<div class="temp">{weather.temp}°F</div>
<div class="conditions">{weather.conditions}</div>
</div>
{/if}

Camera Widgets

Display camera feeds:

<script>
export let cameraUrl = 'rtsp://camera.local/stream';

// Proxy camera stream through GEM
let streamUrl = `/api/camera/stream?url=${encodeURIComponent(cameraUrl)}`;
</script>

<div class="camera-widget">
<img src={streamUrl} alt="Camera Feed" />
</div>

Advanced Topics

Widget Configuration Schema

Widgets can accept configuration:

// In widget code
export let config = {};

// Access config values
let zoneId = config.zone_id;
let refreshRate = config.refresh_rate || 5000;

Configure when adding widget to page:

{
"zone_id": 10,
"refresh_rate": 3000,
"show_graph": true
}

Real-Time Updates

Subscribe to real-time zone/device updates:

<script>
import {onMount, onDestroy} from 'svelte';

let zone = $state(null);
let token;

onMount(async () => {
const zoneId = 10;

// Initial load
zone = await GemApp.getInstance().queryOne('zone', {id: zoneId});

// Subscribe to updates
token = await GemApp.getInstance().subscribe('zone', zoneId, (updatedZone) => {
zone = updatedZone; // Auto-updates UI
});
});

onDestroy(async () => {
if (token) {
await GemApp.getInstance().unsubscribe('zone', 10, token);
}
});
</script>

<div>
Zone: {zone?.label}
Power: {zone?.power}
</div>

Third-Party Libraries

Import and use external libraries:

<script>
import Chart from 'chart.js/auto';
import dayjs from 'dayjs';
// Other npm packages available in GEM
</script>

Available Libraries (already installed in GEM):

  • chart.js
  • dayjs
  • lodash
  • And many more (check package.json)

State Management

Complex widgets can use Svelte stores:

<script>
import {writable} from 'svelte/store';

const zones = writable([]);

async function loadZones() {
let data = await GemApp.getInstance().query('zone', {subsystem_id: 1});
zones.set(data);
}

onMount(loadZones);
</script>

{#each $zones as zone}
<div>{zone.label}</div>
{/each}

Widget Best Practices

  1. Error Handling: Always handle API failures gracefully

  2. Loading States: Show loading indicators

  3. Null Checks: Verify data exists before accessing

  4. Cleanup: Unsubscribe in onDestroy

  5. Performance: Avoid excessive polling or subscriptions

  6. Responsive: Design for multiple screen sizes

  7. Theme Compatibility: Use theme CSS variables

  8. Documentation: Comment complex logic

  9. Testing: Test in preview before deploying

  10. Version Control: Export widget code to external files

Troubleshooting

Widget Won't Compile

Check:

  1. Syntax Errors: Review error message for line number
  2. Missing Imports: Verify all imports are available
  3. Svelte Version: Ensure Svelte 5 syntax
  4. Quotes: Use consistent quote style
  5. Brackets: Ensure all brackets are closed

Widget Crashes on Load

Check:

  1. API Calls: Wrap in try/catch
  2. Null Values: Check for null before accessing properties
  3. Subscriptions: Ensure valid IDs
  4. Browser Console: Check for detailed error messages

Widget Shows Blank

Check:

  1. Data Loading: Verify API calls return data
  2. Conditional Rendering: Check {#if} conditions
  3. CSS: Verify styles don't hide content
  4. Size: Widget has reasonable width/height
  5. Console: Check for errors

Preview Not Updating

Try:

  1. Click Save to recompile
  2. Toggle preview collapse/expand
  3. Clear browser cache
  4. Check for compilation errors

Cannot Save Widget

Check:

  1. Name: Must be valid (lowercase_with_underscores)
  2. Compilation: Widget must compile successfully
  3. Permissions: User has permission to update widgets
  4. Network: Connection to GEM server
  • UI Pages - Adding widgets to pages
  • UI Themes - Styling widgets
  • UIs - Deploying widgets in UIs
  • Technical Reference - Full widget API documentation