Examples / Task Manager

Task Manager

A fully interactive CRUD app using every built-in Signet.js directive.

js-for js-if js-show js-model js-bind js-on js-scope js-text js-once computed getters nested scopes

Session started at ยท Signet.js Task Manager

Total
Active
Done
Progress

Add Task

No tasks to show.

Nested Scope Demo

These counters use js-scope โ€” no extra JS. The nested scope inherits step from the parent.

Parent ยท Step: ยท Count:
Nested ยท Uses parent step () ยท Count:

How it works

This app uses createApp() to define state, computed properties, and methods. The HTML template uses js-* directives to bind reactively to that state.

App definition

createApp({
  tasks: [ /* initial tasks */ ],
  newTask: '',
  newPriority: 'medium',
  filter: 'all',

  // Methods auto-unwrap signals; use `this.x` not `this.x.value`
  addTask() {
    const text = this.newTask.trim();
    if (!text) return;
    this.tasks = [...this.tasks, {
      text, done: false, priority: this.newPriority,
    }];
    this.newTask = '';
  },

  // Computed properties โ€” re-evaluate when deps change
  get filteredTasks() {
    if (this.filter === 'active')
      return this.tasks.filter(t => !t.done);
    if (this.filter === 'done')
      return this.tasks.filter(t => t.done);
    return this.tasks;
  },

  get total()       { return this.tasks.length; },
  get doneCount()   { return this.tasks.filter(t => t.done).length; },
  get progress() {
    return this.total === 0
      ? 0
      : Math.round(this.doneCount / this.total * 100);
  },
}).mount('#app');

List rendering

<!-- js-for creates a reactive list.
     $index is the item's position. -->
<div js-for="task in filteredTasks">
  <input
    type="checkbox"
    js-bind:checked="task.done"
    js-on:click="toggleTask($index)"
  />
  <span
    js-text="task.text"
    js-bind:class="task.done ? 'line-through' : ''"
  ></span>
  <button js-on:click.stop="removeTask($index)">
    โœ•
  </button>
</div>

Form binding

<!-- js-on:submit.prevent prevents default,
     js-model provides two-way binding,
     js-bind:disabled evaluates an expression -->
<form js-on:submit.prevent="addTask()">
  <input js-model="newTask" type="text" />
  <select js-model="newPriority">
    <option value="low">Low</option>
    <option value="medium">Medium</option>
    <option value="high">High</option>
  </select>
  <button js-bind:disabled="newTask.length === 0">
    Add
  </button>
</form>

Nested scopes

<!-- Parent scope provides `step` -->
<div js-scope="{ step: 1, parentCount: 0 }">
  <button js-on:click="parentCount = parentCount + step">
    +
  </button>

  <!-- Child inherits `step` from parent scope chain -->
  <div js-scope="{ childCount: 0 }">
    <!-- step is readable from the parent! -->
    <span js-text="step"></span>
    <button js-on:click="childCount = childCount + step">
      +
    </button>
  </div>
</div>
Full source: The complete annotated source code for this example is in examples/index.html in the GitHub repository.