Component Edit page

Edit todo names (can-stache-bindings)

The problem

Make it possible to edit a todos name by double-clicking its label which should reveal a focused input element. If the user hits the enter key, the todo should be updated on the server. If the input loses focus, it should go back to the default list view.

This functionality should be encapsulated by a <todo-list {todos} /> custom element. It should accept a todos property that is the list of todos that will be managed by the custom element.

What you need to know

  • The can-stache-bindings presentation on data bindings.

  • The focused custom attribute can be used to specify when an element should be focused:

    focused:from="shouldBeFocused()"
    
  • Use key:from to pass a value from the scope to a component:

    <some-component nameInComponent:from="nameInScope" />
    
  • this can be used to get the current context in stache:

    <div on:click="doSomethingWith(this)" />
    

The solution

Click to see the solution

Create components/todo-list/todo-list.stache as follows:

<!-- components/todo-list/todo-list.stache -->
<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()"
                    disabled:from="todo.isSaving()" />
                <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>

Create components/todo-list/todo-list.js as follows:

// components/todo-list/todo-list.js
import {Component} from "can";
import view from "./todo-list.stache";
import Todo from "~/models/todo";

export default Component.extend({
    tag: "todo-list",
    view,
    ViewModel: {
        todos: Todo.List,
        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;
        }
    }
});

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">
        <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>