bitovi/canjs

View on GitHub
docs/can-guides/experiment/todomvc/8-routing/js.js

Summary

Maintainability
D
2 days
Test Coverage
import {
  StacheElement,
  ObservableArray,
  ObservableObject,
  fixture,
  realtimeRestModel,
  type,
  domEvents,
  enterEvent,
  route
} from "//unpkg.com/can@6/everything.mjs";

domEvents.addEvent(enterEvent);

class Todo extends ObservableObject {
  static props = {
    id: { type: type.convert(Number), identity: true },
    name: String,
    complete: { type: type.convert(Boolean), default: false }
  };
}

class TodoList extends ObservableArray {
  static items = Todo;

  static props = {
    get active() {
      return this.filter({ complete: false });
    },

    get complete() {
      return this.filter({ complete: true });
    }
  };
}

const todoStore = fixture.store(
  [
    { name: "mow lawn", complete: false, id: 5 },
    { name: "dishes", complete: true, id: 6 },
    { name: "learn canjs", complete: false, id: 7 }
  ],
  Todo
);

fixture("/api/todos", todoStore);
fixture.delay = 200;

realtimeRestModel({
  url: "/api/todos",
  ObjectType: Todo,
  ArrayType: TodoList
});

class TodoCreate extends StacheElement {
  static view = `
    <input id="new-todo"
      placeholder="What needs to be done?"
      value:bind="this.todo.name"
      on:enter="this.createTodo()"
    >
  `;

  static props = {
    todo: {
      get default() {
        return new Todo();
      }
    }
  };

  createTodo() {
    this.todo.save().then(() => {
      this.todo = new Todo();
    });
  }
}
customElements.define("todo-create", TodoCreate);

class TodoListElement extends StacheElement {
  static view = `
    <ul id="todo-list">
      {{# for(todo of this.todos) }}
        <li class="todo {{# if(todo.complete) }}completed{{/ if }}
          {{# if(todo.isDestroying()) }}destroying{{/ if }}
          {{# if(this.isEditing(todo)) }}editing{{/ if }}">
          <div class="view">
            <input 
              class="toggle"
              type="checkbox"
              checked:bind="todo.complete"
              on:change="todo.save()"
            >
            <label on:dblclick="this.edit(todo)">{{ todo.name }}</label>
            <button class="destroy" on:click="todo.destroy()"></button>
          </div>
          <input 
            class="edit"
            type="text"
            value:bind="todo.name"
            on:enter="this.updateName()"
            focused:from="this.isEditing(todo)"
            on:blur="this.cancelEdit()"
          >
        </li>
      {{/ for }}
    </ul>
  `;

  static props = {
    todos: type.maybeConvert(TodoListModel),
    editing: Todo,
    backupName: String
  };

  isEditing(todo) {
    return todo === this.editing;
  }

  edit(todo) {
    this.backupName = todo.name;
    this.editing = todo;
  }

  cancelEdit() {
    if (this.editing) {
      this.editing.name = this.backupName;
    }
    this.editing = null;
  }

  updateName() {
    this.editing.save();
    this.editing = null;
  }
}
customElements.define("todo-list", TodoListElement);

class TodoMVC extends StacheElement {
  static view = `
    <section id="todoapp">
      <header id="header">
        <h1>{{ this.appName }}</h1>
        <todo-create />
      </header>
      <section id="main" class="">
        <input id="toggle-all" type="checkbox">
        <label for="toggle-all">Mark all as complete</label>
        <todo-list todos:from="this.todosPromise.value" />
      </section>
      <footer id="footer" class="">
        <span id="todo-count">
          <strong>{{ this.todosPromise.value.active.length }}</strong> items left
        </span>
        <ul id="filters">
          <li>
            <a href="{{ routeUrl(filter=undefined) }}"
              {{# routeCurrent(filter=undefined) }}class="selected"{{/ routeCurrent }}>All</a>
          </li>
          <li>
            <a href="{{ routeUrl(filter='active') }}"
              {{# routeCurrent(filter='active') }}class="selected"{{/ routeCurrent }}>Active</a>
          </li>
          <li>
            <a href="{{ routeUrl(filter='complete') }}"
              {{# routeCurrent(filter='complete') }}class="selected"{{/ routeCurrent }}>Completed</a>
          </li>
        </ul>
        <button id="clear-completed">
          Clear completed ({{ this.todosPromise.value.complete.length }})
        </button>
      </footer>
    </section>
  `;

  static props = {
    appName: { default: "TodoMVC" },
    routeData: {
      get default() {
        route.register("{filter}");
        route.start();
        return route.data;
      }
    },
    get todosPromise() {
      if (!this.routeData.filter) {
        return Todo.getList({});
      } else {
        return Todo.getList({
          filter: { complete: this.routeData.filter === "complete" }
        });
      }
    }
  };
}
customElements.define("todo-mvc", TodoMVC);