Example: Task Tracker

A complete task management app with priorities, due dates, and role-based editing.

Schema

Entity: Tasks — key: tasks

Field Type Notes
title text required
description textarea
status select open / in_progress / done — required
priority select low / medium / high / critical
due_date date
assignee_email email

Source Code

1<!DOCTYPE html>2<html lang="en">3<head>4  <meta charset="UTF-8">5  <meta name="viewport" content="width=device-width, initial-scale=1.0">6  <title>Task Tracker</title>7  <style>8    * { box-sizing: border-box; margin: 0; padding: 0; }9    body { font-family: system-ui, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; color: #1a1a1a; }10    h1 { margin-bottom: 20px; }11    .controls { display: flex; gap: 12px; margin-bottom: 20px; flex-wrap: wrap; }12    .controls select, .controls button { padding: 8px 12px; border: 1px solid #d1d5db; border-radius: 6px; font-size: 14px; }13    .controls button { background: #2563eb; color: white; border: none; cursor: pointer; }14    .controls button:hover { background: #1d4ed8; }15    .task { border: 1px solid #e5e7eb; border-radius: 8px; padding: 16px; margin-bottom: 12px; }16    .task h3 { margin-bottom: 4px; }17    .task .meta { font-size: 13px; color: #6b7280; display: flex; gap: 12px; margin-top: 8px; flex-wrap: wrap; }18    .badge { display: inline-block; padding: 2px 8px; border-radius: 12px; font-size: 12px; font-weight: 500; }19    .badge.open { background: #dbeafe; color: #1e40af; }20    .badge.in_progress { background: #fef3c7; color: #92400e; }21    .badge.done { background: #d1fae5; color: #065f46; }22    .badge.low { background: #f3f4f6; color: #374151; }23    .badge.medium { background: #fef3c7; color: #92400e; }24    .badge.high { background: #fee2e2; color: #991b1b; }25    .badge.critical { background: #991b1b; color: white; }26    .modal-overlay { display: none; position: fixed; inset: 0; background: rgba(0,0,0,0.5); z-index: 100; justify-content: center; align-items: center; }27    .modal-overlay.open { display: flex; }28    .modal { background: white; border-radius: 12px; padding: 24px; width: 100%; max-width: 480px; }29    .modal h2 { margin-bottom: 16px; }30    .field { margin-bottom: 12px; }31    .field label { display: block; font-size: 14px; font-weight: 500; margin-bottom: 4px; }32    .field input, .field select, .field textarea { width: 100%; padding: 8px 12px; border: 1px solid #d1d5db; border-radius: 6px; font-size: 14px; }33    .field textarea { resize: vertical; min-height: 80px; }34    .actions { display: flex; gap: 8px; justify-content: flex-end; margin-top: 16px; }35    .actions button { padding: 8px 16px; border-radius: 6px; font-size: 14px; cursor: pointer; }36    .btn-cancel { background: white; border: 1px solid #d1d5db; }37    .btn-submit { background: #2563eb; color: white; border: none; }38    .error { color: #dc2626; font-size: 13px; margin-top: 4px; }39    .empty { text-align: center; padding: 40px; color: #9ca3af; }40    .loading { text-align: center; padding: 40px; color: #6b7280; }41    .task-actions { float: right; display: flex; gap: 4px; }42    .task-actions button { padding: 4px 8px; font-size: 12px; border: 1px solid #d1d5db; border-radius: 4px; background: white; cursor: pointer; }43    .task-actions button.delete { color: #dc2626; border-color: #fecaca; }44  </style>45</head>46<body>47  <h1>Task Tracker</h1>4849  <div class="controls">50    <select id="filter-status">51      <option value="">All statuses</option>52      <option value="open">Open</option>53      <option value="in_progress">In Progress</option>54      <option value="done">Done</option>55    </select>56    <button id="btn-create" style="display:none;">+ New Task</button>57  </div>5859  <div id="task-list"><div class="loading">Loading tasks...</div></div>6061  <div class="modal-overlay" id="modal">62    <div class="modal">63      <h2 id="modal-title">New Task</h2>64      <form id="task-form">65        <div class="field">66          <label for="title">Title *</label>67          <input type="text" id="title" name="title" required>68          <div class="error" id="error-title"></div>69        </div>70        <div class="field">71          <label for="description">Description</label>72          <textarea id="description" name="description"></textarea>73        </div>74        <div class="field">75          <label for="status">Status *</label>76          <select id="status" name="status" required>77            <option value="open">Open</option>78            <option value="in_progress">In Progress</option>79            <option value="done">Done</option>80          </select>81        </div>82        <div class="field">83          <label for="priority">Priority</label>84          <select id="priority" name="priority">85            <option value="">None</option>86            <option value="low">Low</option>87            <option value="medium">Medium</option>88            <option value="high">High</option>89            <option value="critical">Critical</option>90          </select>91        </div>92        <div class="field">93          <label for="due_date">Due Date</label>94          <input type="date" id="due_date" name="due_date">95        </div>96        <div class="field">97          <label for="assignee_email">Assignee Email</label>98          <input type="email" id="assignee_email" name="assignee_email">99        </div>100        <div class="error" id="error-general"></div>101        <div class="actions">102          <button type="button" class="btn-cancel" id="btn-cancel">Cancel</button>103          <button type="submit" class="btn-submit">Save</button>104        </div>105      </form>106    </div>107  </div>108109  <script src="https://sdk.workapps.tech/v1.js"></script>110  <script>111    const sdk = new WorkAppsSDK();112    let canEdit = false;113    let editingId = null;114115    async function init() {116      const bootstrap = await sdk.getBootstrap();117      canEdit = ['editor', 'admin'].includes(bootstrap.role);118      if (canEdit) {119        document.getElementById('btn-create').style.display = 'block';120      }121      loadTasks();122    }123124    async function loadTasks() {125      const listEl = document.getElementById('task-list');126      const filterStatus = document.getElementById('filter-status').value;127128      try {129        const filter = filterStatus ? { status: { eq: filterStatus } } : undefined;130        const result = await sdk.listRecords('tasks', { filter, sort: 'due_date:asc', pageSize: 50 });131132        if (result.data.length === 0) {133          listEl.innerHTML = '<div class="empty">No tasks found.</div>';134          return;135        }136137        listEl.innerHTML = result.data.map(task => `138          <div class="task">139            ${canEdit ? `140              <div class="task-actions">141                <button onclick="openEdit('${task.id}')">Edit</button>142                <button class="delete" onclick="deleteTask('${task.id}')">Delete</button>143              </div>144            ` : ''}145            <h3>${escapeHtml(task.title)}</h3>146            ${task.description ? `<p style="margin-top:6px;color:#374151;">${escapeHtml(task.description)}</p>` : ''}147            <div class="meta">148              <span class="badge ${task.status}">${task.status.replace('_', ' ')}</span>149              ${task.priority ? `<span class="badge ${task.priority}">${task.priority}</span>` : ''}150              ${task.due_date ? `<span>Due: ${task.due_date}</span>` : ''}151              ${task.assignee_email ? `<span>${escapeHtml(task.assignee_email)}</span>` : ''}152            </div>153          </div>154        `).join('');155      } catch (error) {156        listEl.innerHTML = `<div class="error">Failed to load tasks: ${escapeHtml(error.message)}</div>`;157      }158    }159160    document.getElementById('btn-create').addEventListener('click', () => {161      editingId = null;162      document.getElementById('modal-title').textContent = 'New Task';163      document.getElementById('task-form').reset();164      document.getElementById('modal').classList.add('open');165    });166167    document.getElementById('btn-cancel').addEventListener('click', () => {168      document.getElementById('modal').classList.remove('open');169    });170171    async function openEdit(id) {172      const result = await sdk.getRecord('tasks', id);173      const task = result.data;174      editingId = id;175      document.getElementById('modal-title').textContent = 'Edit Task';176      document.getElementById('title').value = task.title || '';177      document.getElementById('description').value = task.description || '';178      document.getElementById('status').value = task.status || 'open';179      document.getElementById('priority').value = task.priority || '';180      document.getElementById('due_date').value = task.due_date || '';181      document.getElementById('assignee_email').value = task.assignee_email || '';182      document.getElementById('modal').classList.add('open');183    }184185    document.getElementById('task-form').addEventListener('submit', async (e) => {186      e.preventDefault();187      clearErrors();188189      const data = {190        title: document.getElementById('title').value,191        description: document.getElementById('description').value || null,192        status: document.getElementById('status').value,193        priority: document.getElementById('priority').value || null,194        due_date: document.getElementById('due_date').value || null,195        assignee_email: document.getElementById('assignee_email').value || null,196      };197198      try {199        if (editingId) {200          await sdk.updateRecord('tasks', editingId, data);201        } else {202          await sdk.createRecord('tasks', data);203        }204        document.getElementById('modal').classList.remove('open');205        loadTasks();206      } catch (error) {207        const { ErrorCode } = WorkAppsSDK;208        if (error.code === ErrorCode.ValidationFailed) {209          const fieldErrors = error.getFieldErrors();210          Object.entries(fieldErrors).forEach(([field, message]) => {211            const el = document.getElementById('error-' + field);212            if (el) { el.textContent = message; }213          });214        } else {215          document.getElementById('error-general').textContent = error.message;216        }217      }218    });219220    async function deleteTask(id) {221      // In production, use a custom confirmation dialog — never window.confirm222      try {223        await sdk.deleteRecord('tasks', id);224        loadTasks();225      } catch (error) {226        console.error('Delete failed:', error.message);227      }228    }229230    document.getElementById('filter-status').addEventListener('change', loadTasks);231232    function escapeHtml(str) {233      if (str == null) return '';234      const div = document.createElement('div');235      div.textContent = String(str);236      return div.innerHTML;237    }238239    function clearErrors() {240      document.querySelectorAll('.error').forEach(el => el.textContent = '');241    }242243    init();244  </script>245</body>246</html>

Key Patterns

Delete Confirmations

Always use a custom confirmation dialog before deleting — never window.confirm:

1async function deleteTask(id) {2  // Use a custom <dialog> or modal instead of window.confirm3  // window.confirm blocks the JS thread and can't be styled4  if (!await myConfirmDialog('Delete this task?')) return;56  await sdk.deleteRecord('tasks', id);7  loadTasks();8}