Setter Toggle page

Toggle all todos complete state (DefineMap setter)

The problem

Make the “toggle all” checkbox work. It should be unchecked if a single todo is unchecked and checked if all todos are checked.

When the “toggle all” checkbox is changed, the application should update every todo to match the status of the “toggle all” checkbox.

The “toggle all” checkbox should be disabled if a single todo is saving.

What you need to know

  • Using setters and getters a virtual property can be simulated like:

    DefineMap.extend({
        first: "string",
        last: "string",
        get fullName() {
            return this.first + " " + this.last;
        },
        set fullName(newValue) {
            const parts = newValue.split(" ");
            this.first = parts[0];
            this.last = parts[1];
        }
    })
    

The solution

Click to see the solution

Update models/todo.js to the following:

// models/todo.js
import {DefineMap, DefineList, realtimeRestModel} from "can";

const Todo = DefineMap.extend("Todo", {
    id: {type: "string", identity: true},
    name: "string",
    complete: {
        type: "boolean",
        default: false
    },
    toggleComplete() {
        this.complete = !this.complete;
    }
});

Todo.List = DefineList.extend("TodoList", {
    "#": Todo,
    get active() {
        return this.filter({
            complete: false
        });
    },
    get complete() {
        return this.filter({
            complete: true
        });
    },
    get allComplete() {
        return this.length === this.complete.length;
    },
    get saving() {
        return this.filter(function(todo) {
            return todo.isSaving();
        });
    },
    updateCompleteTo(value) {
        this.forEach(function(todo) {
            todo.complete = value;
            todo.save();
        });
    }
});

Todo.connection = realtimeRestModel({
    url: "/api/todos/{id}",
    Map: Todo,
    List: Todo.List
});

export default Todo;

Update index.js to the following:

// index.js
import {Component} from "can";
import view from "./index.stache";
import Todo from "~/models/todo";
import "~/models/todos-fixture";
import test from "can-todomvc-test";

Component.extend({
    tag: "todo-mvc",
    view,
    ViewModel: {
        appName: {default: "TodoMVC"},
        todosList: {
            get: function(lastSet, resolve) {
                Todo.getList({}).then(resolve);
            }
        },
        get allChecked() {
            return this.todosList && this.todosList.allComplete;
        },
        set allChecked(newVal) {
            this.todosList && this.todosList.updateCompleteTo(newVal);
        }
    }
});

const appVM = window.appVM = document.querySelector("todo-mvc").viewModel;

test(appVM);

Update index.stache to the following:

<!-- index.stache -->
<can-import from="~/components/todo-create/" />
<can-import from="~/components/todo-list/" />
<section id="todoapp">
    <header id="header">
        <h1>{{ this.appName }}</h1>
        <todo-create/>
    </header>
    <section id="main" class="">
        <input id="toggle-all" type="checkbox"
            checked:bind="this.allChecked"
            disabled:from="this.todosList.saving.length" />
        <label for="toggle-all">Mark all as complete</label>
        <todo-list todos:from="this.todosList" />
    </section>
    <footer id="footer" class="">
        <span id="todo-count">
            <strong>{{ this.todosList.active.length }}</strong> items left
        </span>
        <ul id="filters">
            <li>
                <a class="selected" href="#!">All</a>
            </li>
            <li>
                <a href="#!active">Active</a>
            </li>
            <li>
                <a href="#!completed">Completed</a>
            </li>
        </ul>
        <button id="clear-completed">
            Clear completed ({{ this.todosList.complete.length }})
        </button>
    </footer>
</section>