2018-03-21 20:11:08 -04:00
|
|
|
/**
|
|
|
|
* @license
|
|
|
|
* Copyright Google Inc. All Rights Reserved.
|
|
|
|
*
|
|
|
|
* Use of this source code is governed by an MIT-style license that can be
|
|
|
|
* found in the LICENSE file at https://angular.io/license
|
|
|
|
*/
|
|
|
|
|
2018-04-06 13:29:22 -04:00
|
|
|
import {CommonModule, NgForOf, NgIf} from '@angular/common';
|
2018-04-04 16:12:27 -04:00
|
|
|
import {ChangeDetectionStrategy, Component, EventEmitter, InjectFlags, Injectable, Input, IterableDiffers, NgModule, Output, createInjector, defineInjector, inject, ɵComponentDef as ComponentDef, ɵComponentType as ComponentType, ɵDirectiveDef as DirectiveDef, ɵDirectiveType as DirectiveType, ɵNgOnChangesFeature as NgOnChangesFeature, ɵdefaultIterableDiffers as defaultIterableDiffers, ɵdefineDirective as defineDirective, ɵinjectTemplateRef as injectTemplateRef, ɵinjectViewContainerRef as injectViewContainerRef, ɵmarkDirty as markDirty, ɵrenderComponent as renderComponent} from '@angular/core';
|
2018-03-21 20:11:08 -04:00
|
|
|
|
2018-04-06 13:29:22 -04:00
|
|
|
|
|
|
|
export class Todo {
|
|
|
|
editing: boolean;
|
|
|
|
|
|
|
|
private _title: string;
|
|
|
|
get title() { return this._title; }
|
|
|
|
set title(value: string) { this._title = value.trim(); }
|
|
|
|
|
|
|
|
constructor(title: string, public completed: boolean = false) {
|
|
|
|
this.editing = false;
|
|
|
|
this.title = title;
|
|
|
|
}
|
2018-03-21 20:11:08 -04:00
|
|
|
}
|
2018-04-06 13:29:22 -04:00
|
|
|
|
|
|
|
@Injectable()
|
|
|
|
export class TodoStore {
|
|
|
|
todos: Array<Todo> = [
|
|
|
|
new Todo('Demonstrate Components'),
|
|
|
|
new Todo('Demonstrate Structural Directives', true),
|
|
|
|
new Todo('Demonstrate NgModules'),
|
|
|
|
new Todo('Demonstrate zoneless change detection'),
|
|
|
|
new Todo('Demonstrate internationalization'),
|
2018-03-21 20:11:08 -04:00
|
|
|
];
|
|
|
|
|
2018-04-06 13:29:22 -04:00
|
|
|
private getWithCompleted(completed: boolean) {
|
|
|
|
return this.todos.filter((todo: Todo) => todo.completed === completed);
|
|
|
|
}
|
2018-03-21 20:11:08 -04:00
|
|
|
|
2018-04-06 13:29:22 -04:00
|
|
|
allCompleted() { return this.todos.length === this.getCompleted().length; }
|
|
|
|
|
|
|
|
setAllTo(completed: boolean) { this.todos.forEach((t: Todo) => t.completed = completed); }
|
|
|
|
|
|
|
|
removeCompleted() { this.todos = this.getWithCompleted(false); }
|
|
|
|
|
|
|
|
getRemaining() { return this.getWithCompleted(false); }
|
2018-03-21 20:11:08 -04:00
|
|
|
|
2018-04-06 13:29:22 -04:00
|
|
|
getCompleted() { return this.getWithCompleted(true); }
|
2018-03-21 20:11:08 -04:00
|
|
|
|
2018-04-06 13:29:22 -04:00
|
|
|
toggleCompletion(todo: Todo) { todo.completed = !todo.completed; }
|
2018-03-21 20:11:08 -04:00
|
|
|
|
2018-04-06 13:29:22 -04:00
|
|
|
remove(todo: Todo) { this.todos.splice(this.todos.indexOf(todo), 1); }
|
|
|
|
|
|
|
|
add(title: string) { this.todos.push(new Todo(title)); }
|
2018-03-21 20:11:08 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
@Component({
|
|
|
|
selector: 'todo-app',
|
2018-04-06 13:29:22 -04:00
|
|
|
// TODO(misko) remove all `foo && foo.something` once `ViewContainerRef` can separate creation and
|
|
|
|
// update block.
|
|
|
|
// TODO(misko): make this work with `[(ngModel)]`
|
2018-03-21 20:11:08 -04:00
|
|
|
template: `
|
2018-04-06 13:29:22 -04:00
|
|
|
<section class="todoapp">
|
|
|
|
<header class="header">
|
|
|
|
<h1>todos</h1>
|
|
|
|
<input class="new-todo" placeholder="What needs to be done?" autofocus=""
|
|
|
|
[value]="newTodoText"
|
|
|
|
(keyup)="$event.code == 'Enter' ? addTodo() : newTodoText = $event.target.value">
|
|
|
|
</header>
|
|
|
|
<section *ngIf="todoStore.todos.length > 0" class="main">
|
|
|
|
<input *ngIf="todoStore.todos.length"
|
|
|
|
#toggleall class="toggle-all" type="checkbox"
|
|
|
|
[checked]="todoStore.allCompleted()"
|
|
|
|
(click)="todoStore.setAllTo(toggleall.checked)">
|
|
|
|
<ul class="todo-list">
|
|
|
|
<li *ngFor="let todo of todoStore.todos"
|
|
|
|
[class.completed]="todo && todo.completed"
|
|
|
|
[class.editing]="todo && todo.editing">
|
|
|
|
<div class="view">
|
|
|
|
<input class="toggle" type="checkbox"
|
|
|
|
(click)="toggleCompletion(todo)"
|
|
|
|
[checked]="todo && todo.completed">
|
|
|
|
<label (dblclick)="editTodo(todo)">{{todo && todo.title}}</label>
|
|
|
|
<button class="destroy" (click)="remove(todo)"></button>
|
|
|
|
</div>
|
|
|
|
<input *ngIf="todo && todo.editing"
|
|
|
|
class="edit" #editedtodo
|
|
|
|
[value]="todo && todo.title"
|
|
|
|
(blur)="stopEditing(todo, editedtodo.value)"
|
|
|
|
(keyup)="todo.title = $event.target.value"
|
|
|
|
(keyup)="$event.code == 'Enter' && updateEditingTodo(todo, editedtodo.value)"
|
|
|
|
(keyup)="$event.code == 'Escape' && cancelEditingTodo(todo)">
|
|
|
|
</li>
|
|
|
|
</ul>
|
|
|
|
</section>
|
|
|
|
<footer *ngIf="todoStore.todos.length > 0" class="footer">
|
|
|
|
<span class="todo-count">
|
|
|
|
<strong>{{todoStore.getRemaining().length}}</strong>
|
|
|
|
{{todoStore.getRemaining().length == 1 ? 'item' : 'items'}} left
|
|
|
|
</span>
|
|
|
|
<button *ngIf="todoStore.getCompleted().length > 0"
|
|
|
|
class="clear-completed"
|
|
|
|
(click)="removeCompleted()">
|
|
|
|
Clear completed
|
|
|
|
</button>
|
|
|
|
</footer>
|
|
|
|
</section>
|
2018-04-04 16:12:27 -04:00
|
|
|
`,
|
2018-04-06 13:29:22 -04:00
|
|
|
// TODO(misko): switch oven to OnPush
|
|
|
|
// changeDetection: ChangeDetectionStrategy.OnPush
|
2018-03-21 20:11:08 -04:00
|
|
|
})
|
|
|
|
export class ToDoAppComponent {
|
2018-04-06 13:29:22 -04:00
|
|
|
todoStore: TodoStore;
|
|
|
|
newTodoText = '';
|
|
|
|
|
|
|
|
// TODO(misko) Fix injection
|
|
|
|
// constructor(todoStore: TodoStore) { this.todoStore = todoStore; }
|
|
|
|
constructor() { this.todoStore = new TodoStore(); }
|
|
|
|
|
|
|
|
stopEditing(todo: Todo, editedTitle: string) {
|
|
|
|
todo.title = editedTitle;
|
|
|
|
todo.editing = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
cancelEditingTodo(todo: Todo) { todo.editing = false; }
|
|
|
|
|
|
|
|
updateEditingTodo(todo: Todo, editedTitle: string) {
|
|
|
|
editedTitle = editedTitle.trim();
|
|
|
|
todo.editing = false;
|
|
|
|
|
|
|
|
if (editedTitle.length === 0) {
|
|
|
|
return this.todoStore.remove(todo);
|
|
|
|
}
|
2018-03-21 20:11:08 -04:00
|
|
|
|
2018-04-06 13:29:22 -04:00
|
|
|
todo.title = editedTitle;
|
2018-03-21 20:11:08 -04:00
|
|
|
}
|
|
|
|
|
2018-04-06 13:29:22 -04:00
|
|
|
editTodo(todo: Todo) { todo.editing = true; }
|
|
|
|
|
|
|
|
removeCompleted() { this.todoStore.removeCompleted(); }
|
|
|
|
|
|
|
|
toggleCompletion(todo: Todo) { this.todoStore.toggleCompletion(todo); }
|
|
|
|
|
|
|
|
remove(todo: Todo) { this.todoStore.remove(todo); }
|
|
|
|
|
|
|
|
addTodo() {
|
|
|
|
if (this.newTodoText.trim().length) {
|
|
|
|
this.todoStore.add(this.newTodoText);
|
|
|
|
this.newTodoText = '';
|
|
|
|
}
|
2018-03-21 20:11:08 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO(misko): This hack is here because common is not compiled with Ivy flag turned on.
|
|
|
|
(CommonModule as any).ngInjectorDef = defineInjector({factory: () => new CommonModule});
|
|
|
|
|
|
|
|
// TODO(misko): This hack is here because common is not compiled with Ivy flag turned on.
|
|
|
|
(NgForOf as any).ngDirectiveDef = defineDirective({
|
|
|
|
type: NgForOf,
|
|
|
|
selectors: [['', 'ngFor', '', 'ngForOf', '']],
|
|
|
|
factory: () => new NgForOf(
|
|
|
|
injectViewContainerRef(), injectTemplateRef(),
|
|
|
|
// TODO(misko): inject does not work since it needs to be directiveInject
|
|
|
|
// inject(IterableDiffers, defaultIterableDiffers)
|
|
|
|
defaultIterableDiffers),
|
|
|
|
features: [NgOnChangesFeature({
|
|
|
|
ngForOf: 'ngForOf',
|
|
|
|
ngForTrackBy: 'ngForTrackBy',
|
|
|
|
ngForTemplate: 'ngForTemplate',
|
|
|
|
})],
|
|
|
|
inputs: {
|
|
|
|
ngForOf: 'ngForOf',
|
|
|
|
ngForTrackBy: 'ngForTrackBy',
|
|
|
|
ngForTemplate: 'ngForTemplate',
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2018-04-06 13:29:22 -04:00
|
|
|
// TODO(misko): This hack is here because common is not compiled with Ivy flag turned on.
|
|
|
|
(NgIf as any).ngDirectiveDef = defineDirective({
|
|
|
|
type: NgIf,
|
|
|
|
selectors: [['', 'ngIf', '']],
|
|
|
|
factory: () => new NgIf(injectViewContainerRef(), injectTemplateRef()),
|
|
|
|
inputs: {ngIf: 'ngIf', ngIfThen: 'ngIfThen', ngIfElse: 'ngIfElse'}
|
|
|
|
});
|
|
|
|
|
2018-03-21 20:11:08 -04:00
|
|
|
|
2018-04-06 13:29:22 -04:00
|
|
|
@NgModule({declarations: [ToDoAppComponent, ToDoAppComponent], imports: [CommonModule]})
|
2018-03-21 20:11:08 -04:00
|
|
|
export class ToDoAppModule {
|
|
|
|
}
|
|
|
|
|
2018-04-06 13:29:22 -04:00
|
|
|
// TODO(misko): create cleaner way to publish component into global location for tests.
|
|
|
|
(window as any).toDoAppComponent = renderComponent(ToDoAppComponent);
|