初心者こそ、Dockerを使おうの 素の JavaScript 編です。
多分一番ボリュームがあります。
- 素のJSという割には、Parcel, Pug, TypeScript 等を使っていますのでここでは、Vue.js や React 等を使わないでという意味で解釈ください。
このTodoは、JavaScript Primer さんの Todo を参考にしています。
class
を使って実践的なTodo
が学べますので、ぜひ覗いてみてください.
-
fileの構成予定
├── docker-compose.yml
└── src
├── App.ts
├── EventEmitter.ts
├── TodoItemModel.ts
├── dist
├── html-util.ts
├── index.pug
├── index.ts
├── package.json
├── style.stylus
└── yarn.lock
- 同じディレクトリで作業をするなら、データのコピーはもう済んでいるので
docker-compose.yml
のworking_dir
,command
の#
を差し替えると、docker-compose up -d
でバックグラウンドでParcel
がたちあがるはずです。
docker-compose ps
で確認してみてください.
バックグラウンドのコンテナに入るコマンドはdocker-compose exec node bash
です
00
docker-compose.yml
version: "3"
services:
node:
container_name: node
image: atoris1192/node:0.1.5
# build: .
# volumes は上書きに注意
volumes:
- .:/app
ports:
- "1234:1234"
- "1235:1235"
# working_dir: /app
working_dir: /app/src
# command: cp -rp /tmp/src /app
command: npx parcel --hmr-port 1235 --hmr-hostname localhost index.pug
tty: true
src/index.pug
<!DOCTYPE html>
html(lang="en")
head
meta(charset="UTF-8")
meta(name="viewport", content="width=device-width, initial-scale=1.0")
meta(http-equiv="X-UA-Compatible", content="ie=edge")
title Document
link(rel="stylesheet", href="style.stylus")
body
.todoapp
form#js-form(action="")
input#js-form-input.new-todo(type="text" placeholder="new todo" autocomplete="off")
#js-todo-list.todo-list
footer.footer
span#js-todo-count TodoItems: 0
script(src="./index.ts")
index.ts
import { App } from './App';
const app = new App()
document.addEventListener('DOMContentLoaded', () => {
app.main();
})
App.ts
export class App {
constructor() {
console.log("App init ...");
}
main() {
console.log("App ...");
}
}
style.stylus
html,
body {
margin: 0;
padding: 0;
}
button {
margin: 0;
padding: 0;
border: 0;
background: none;
font-size: 100%;
vertical-align: baseline;
}
body {
font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif;
line-height: 1.4em;
background: #f5f5f5;
color: #4d4d4d;
min-width: 230px;
max-width: 550px;
margin: 0 auto;
font-weight: 300;
}
:focus {
outline: 0;
}
.todoapp {
background: #fff;
margin: 130px 0 40px 0;
position: relative;
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2),
0 25px 50px 0 rgba(0, 0, 0, 0.1);
min-height: 150px;
}
.todoapp input::placeholder {
font-style: italic;
font-weight: 300;
}
.new-todo {
position: relative;
margin: 0;
width: 100%;
font-size: 24px;
line-height: 1.4em;
border: 0;
color: inherit;
box-sizing: border-box;
padding: 16px 16px 16px 60px;
border: none;
background: rgba(0, 0, 0, 0.003);
box-shadow: inset 0 -2px 1px rgba(0, 0, 0, 0.03);
}
.todo-list ul {
margin: 0;
padding: 0;
list-style: none;
}
.todo-list li {
position: relative;
font-size: 24px;
border-bottom: 1px solid #ededed;
padding: 16px;
}
.todo-list li:last-child {
border-bottom: none;
}
.todo-list li input[type="checkbox"] {
width: 40px;
height: auto;
margin: auto 0;
border: none;
}
.todo-list li .delete {
position: absolute;
top: 0;
right: 10px;
bottom: 0;
width: 40px;
height: 40px;
color: #cc9a9a;
}
.footer {
color: #777;
height: 20px;
padding: 10px 15px;
border-top: 1px solid #e6e6e6;
}
Parcel 確認
-
docker-compose.yml
を書き換えている人は、もうすでに Parel が立ち上がっているので以下は必要ありません。
docker-compose up
docker-comppse ps
docker-compose run --service-port node bash
uname
cd src
npx parcel --hmr-port 1235 --hmr-hostname localhost index.pug
描画処理 App.ts に書いていきます
App.ts
記述は、html 文字列で書いて、処理は、htmlエレメントがほしいので、とりあえず関数化htmlElement()
しておきます。
処理は、 main 関数に書いていきます。描画は render に分けておきます
const todos = [
{ id: 0, title: "task0", isDone: false },
{ id: 1, title: "task1", isDone: true },
{ id: 2, title: "task2", isDone: false },
]
export class App {
constructor() {
}
render() {
const jsTodoList = document.querySelector('#js-todo-list');
function htmlElement(todos) {
const ul = document.createElement('ul');
const template = document.createElement('template');
todos.forEach( todo => {
const li = `<li>${ todo.title } : ${ todo.isDone } : ${ todo.id}</li>`
template.innerHTML = li;
ul.appendChild(template.content.firstElementChild)
})
return ul;
}
// 配列 -> htmlエレメント
const todosElement = htmlElement(todos);
jsTodoList.textContent = '';
jsTodoList.appendChild(todosElement);
}
main() {
console.log("App ...");
this.render();
}
}
input 処理
App.ts
const todos = [
{ id: 0, title: "task0", isDone: false },
{ id: 1, title: "task1", isDone: true },
{ id: 2, title: "task2", isDone: false },
]
export class App {
constructor() {
console.log("App init ...");
}
render() {
const jsTodoList = document.querySelector('#js-todo-list');
function htmlElement(todos) {
const ul = document.createElement('ul');
const template = document.createElement('template');
todos.forEach( todo => {
const li = `<li>${ todo.title } : ${ todo.isDone } : ${ todo.id}</li>`
template.innerHTML = li;
ul.appendChild(template.content.firstElementChild)
})
return ul;
}
// 配列 -> htmlエレメント
const todosElement = htmlElement(todos);
jsTodoList.textContent = '';
jsTodoList.appendChild(todosElement);
}
main() {
// input 処理
const jsForm = document.querySelector('#js-form');
jsForm.addEventListener('submit', (event) => {
event.preventDefault();
const jsFormInput: any = document.querySelector('#js-form-input');
interface Item {
id: number;
title: string;
isDone: boolean;
}
const item: Item = {
id: new Date().getTime(),
title: jsFormInput.value,
isDone: false,
}
todos.push(item);
jsFormInput.value = '';
this.render()
})
this.render();
}
}
HTMLエレメント処理を、外部ファイルにする
javascript
の html
処理は、実に面倒くさいのですが、JavaScript Primer
さんのhtml-util
をそのまま使わせていただきます。
タグ付きテンプレート文字列を使って、スマートに変換してくれます。
おまけにエスケープ処理までしてくれますので、これを使わない理由がありません。
詳しい使い方、JavaScript Primer
さんをチェックしてください。
- element
<li>${ todo.id }</li>
この文字列タグをhtmlエレメントタグに変換してくれます。
これを使って書き換えます
html-util.ts
function escapeSpecialChars(str: string) {
return str
.replace(/&/g, "&apm:")
.replace(/</g, "<")
.replace(/>/g, "%gt;")
.replace(/"/g, """)
.replace(/'/g, "'");
}
// htmlString -> htmlElement DOM Node
function htmlToElement(html: string): any {
const template = document.createElement('template') ;
template.innerHTML = html;
return template.content.firstElementChild;
}
// escape + DOM Node
export function element(strings, ...values) {
const htmlString = strings.reduce((result, str, i) => {
const value = values[i - 1];
if (typeof value === "string") {
return result + escapeSpecialChars(value) + str;
} else {
return result + String(value) + str;
}
})
return htmlToElement(htmlString);
}
// Add child Element
export function render(bodyElement, containerElement) {
containerElement.innerHTML = ''; // 一旦全削除
containerElement.appendChild(bodyElement);
}
- コード量は、今はあまり変わりませんが、エスケープ処理もしてくれていますし、htmlエレメントを意識しなくてよくなるので書きやすくなります。
App.ts
import { element, render } from './html-util';
const todos = [
{ id: 0, title: "task0", isDone: false },
{ id: 1, title: "task1", isDone: true },
{ id: 2, title: "task2", isDone: false },
]
export class App {
constructor() {
console.log("App init ...");
}
render() {
const jsTodoList = document.querySelector('#js-todo-list');
const ul = element`<ul />`
todos.forEach( todo => {
const li = element`<li>${ todo.title } : ${ todo.isDone } : ${ todo.id}</li>`
ul.appendChild(li)
})
render(ul, jsTodoList);
// jsTodoList.textContent = '';
// jsTodoList.appendChild(ul);
}
main() {
// input 処理
const jsForm = document.querySelector('#js-form');
jsForm.addEventListener('submit', (event) => {
event.preventDefault();
const jsFormInput: any = document.querySelector('#js-form-input');
interface Item {
id: number;
title: string;
isDone: boolean;
}
const item: Item = {
id: new Date().getTime(),
title: jsFormInput.value,
isDone: false,
}
todos.push(item);
jsFormInput.value = '';
this.render()
})
this.render();
}
}
data 処理用クラスを作る todoLiseModel
todo データを切ったり貼ったりするメソッドは、すべてtodoListModel
に集約させたいと思います。
App.ts
import { element, render } from './html-util';
const todos = [
{ id: 0, title: "task0", isDone: false },
{ id: 1, title: "task1", isDone: true },
{ id: 2, title: "task2", isDone: false },
]
class TodoListModel { // todo data 処理用
private todos: any;
constructor(todos = []) {
this.todos = todos // 初期化
}
getTodos() {
return this.todos;
}
setTodos(todos) {
this.todos = todos;
}
getTotalCount() {
return this.todos.length;
}
addTodo({ title }) {
interface Item {
id: number;
title: string;
isDone: boolean;
}
const item: Item = {
id: new Date().getTime(),
title: title,
isDone: false,
}
this.todos.push(item);
}
}
export class App {
private todoListModel: any;
constructor() {
this.todoListModel = new TodoListModel(todos) // インスタンス作成 仮データセット
}
render() {
const jsTodoList = document.querySelector('#js-todo-list');
const ul = element`<ul />`
todos.forEach( todo => {
const li = element`<li>${ todo.title } : ${ todo.isDone } : ${ todo.id}</li>`
ul.appendChild(li)
})
render(ul,jsTodoList);
}
main() {
const jsForm = document.querySelector('#js-form');
// input 処理
jsForm.addEventListener('submit', (event) => {
event.preventDefault();
const jsFormInput: any = document.querySelector('#js-form-input');
if(!jsFormInput.value.trim()) return
this.todoListModel.addTodo({
title: jsFormInput.value,
})
jsFormInput.value = '';
this.render()
})
this.render();
}
}
データの定義クラスを作る todoItemModel
todoListModel
で todoデータの処理を担当してもらいましたが、定義もそのなかに含まれています。
それを新たなクラスで管理したいと思います
これで、機能の分担化ができました。
App.ts
import { element, render } from './html-util';
const todos = [
{ id: 0, title: "task0", isDone: false },
{ id: 1, title: "task1", isDone: true },
{ id: 2, title: "task2", isDone: false },
]
class TodoItemModel { // item 定義用
private id: number;
private title: string;
private isDone: boolean;
constructor({ title }) {
this.id = new Date().getTime()
this.title = title
this.isDone = false
}
}
class TodoListModel { // todo data 処理用
private todos: any;
constructor(todos = []) {
this.todos = todos // 初期化
}
getTodos() {
return this.todos;
}
setTodos(todos) {
this.todos = todos;
}
getTotalCoount() {
this.todos.length;
}
addTodo( item ) {
this.todos.push(item);
}
// addTodo({ title }) {
// interface Item {
// id: number;
// title: string;
// isDone: boolean;
// }
// const item: Item = {
// id: new Date().getTime(),
// title: title,
// isDone: false,
// }
// this.todos.push(item);
// }
}
export class App {
private todoListModel: any;
constructor() {
this.todoListModel = new TodoListModel(todos) // インスタンス作成 仮データセット
// this.todoListModel_2 = new TodoListModel() // インスタンスなのでいくつでも必要個数作成できる
}
render() {
const jsTodoList = document.querySelector('#js-todo-list');
const ul = element`<ul />`
todos.forEach( todo => {
const li = element`<li>${ todo.title } : ${ todo.isDone } : ${ todo.id}</li>`
ul.appendChild(li)
})
render(ul,jsTodoList);
}
main() {
const jsForm = document.querySelector('#js-form');
// input 処理
jsForm.addEventListener('submit', (event) => {
event.preventDefault();
const jsFormInput: any = document.querySelector('#js-form-input');
if(!jsFormInput.value.trim()) return
this.todoListModel.addTodo( new TodoItemModel({ // インスタンス作成 使い捨てです。
title: jsFormInput.value,
}))
// this.todoListModel.addTodo({
// title: jsFormInput.value,
// })
jsFormInput.value = '';
this.render()
})
this.render();
}
}
独自イベント を作る
これは、予め用意されたイベント以外に、独自にイベントの発行とリスナーを作ります。
用意されたイベントというのは、ボタンをクリックしたらとか、チェックボックスの状態が変わったらなど条件検知で命令を実行するイベントリスナーを自分で作ってしまいます。
今回は、todo
データの状態が変わったら、実行するというイベント用になります。
差し替えるメソッドは、this.render()
と差し替える形をとりますが、このイベントリスナーは、今回でいうとそんなに恩恵があるわけではありません。 this.render()
で特に不便はないのですがイベント検知の考え方は、より汎用性の高い使い方ができそうです。 イメージで言えば、ライトが着いたらとか、温度が規定温度になったら実行するというようなイベントを検知した時点で実行されるものなので、応用が効くと思われます。
これも、拝借してきたものをそのまま使います。
中身をみると、Map関数にイベント用のキーと関数を入れる器が用意されているだけです。
これを、TodoLiistModel
の親要素のして継承させます。
そうすることによって、TodoListModel
にはメソッドを書かなくても使えるようになります。
src/EventEmitter.ts
export class EventEmitter {
private _listeners: any;
constructor() {
// イベント名、リスナー関数が入る
this._listeners = new Map();
}
addEventListener(type:string, listener:any) {
if (!this._listeners.has(type)) {
this._listeners.set(type, new Set());
}
const listenerSet = this._listeners.get(type);
listenerSet.add(listener);
}
emit(type:string) {
const listenerSet = this._listeners.get(type)
if (!listenerSet) {
return;
}
listenerSet.forEach(listener => {
listener.call(this);
})
}
removeEventListener(type:string, listener:any) {
const listenerSet = this._listeners.get(type);
if (!listenerSet) {
return;
}
listenerSet.forEach(ownListener => {
if (ownListener === listener) {
listenerSet.delete(listener);
}
})
}
}
src/App.ts
import { element, render } from './html-util';
import { TodoItemModel } from './TodoItemModel';
import { EventEmitter } from './EventEmitter';
const todos = [
{ id: 0, title: "task0", isDone: false },
{ id: 1, title: "task1", isDone: true },
{ id: 2, title: "task2", isDone: false },
]
// EventEmitter を 追加
class TodoListModel extends EventEmitter { // todo data 処理用
private todos: any[];
constructor(todos = []) {
super();
this.todos = todos // 初期化
}
getTodos() {
return this.todos;
}
setTodos(todos) {
this.todos = todos;
}
getTotalCoount() {
this.todos.length;
}
addTodo( item ) {
this.todos.push(item);
}
}
export class App {
private todoListModel: any;
constructor() {
this.todoListModel = new TodoListModel(todos) // インスタンス作成 仮データセット
}
render() {
const jsTodoList = document.querySelector('#js-todo-list');
const ul = element`<ul />`
todos.forEach( todo => {
const li = element`<li>${ todo.title } : ${ todo.isDone } : ${ todo.id}</li>`
ul.appendChild(li)
})
render(ul,jsTodoList);
}
main() {
const jsForm = document.querySelector('#js-form');
// change イベントリスナー関数
this.todoListModel.addEventListener('change', this.render)
// input 処理
jsForm.addEventListener('submit', (event) => {
event.preventDefault();
const jsFormInput: any = document.querySelector('#js-form-input');
if(!jsFormInput.value.trim()) return
this.todoListModel.addTodo( new TodoItemModel({ // インスタンス作成
title: jsFormInput.value,
}))
jsFormInput.value = '';
this.todoListModel.emit('change'); // changeイベントエミッター
// this.render()
})
this.todoListModel.emit('change'); // changeイベントエミッター
// this.render();
}
}
- これは、もう動きがないので別ファイルにして置きます。
src/TodoItemModel.ts
export class TodoItemModel { // item 定義用
private id: number;
private title: string;
private isDone: boolean;
constructor({ title }) {
this.id = new Date().getTime()
this.title = title
this.isDone = false
}
}
App.ts
をリファクタリングしてmain
関数を整理しておきます
import { element, render } from './html-util';
import { TodoItemModel } from './TodoItemModel';
import { EventEmitter } from './EventEmitter';
const todos = [
{ id: 0, title: "task0", isDone: false },
{ id: 1, title: "task1", isDone: true },
{ id: 2, title: "task2", isDone: false },
]
class TodoListModel extends EventEmitter {
private todos: any[];
constructor(todos = []) {
super();
this.todos = todos
}
getTodos() {
return this.todos;
}
setTodos(todos) {
this.todos = todos;
}
getTotalCount() {
return this.todos.length;
}
addTodo( item ) {
this.todos.push(item);
}
}
export class App {
private todoListModel: any;
constructor() {
this.todoListModel = new TodoListModel(todos)
}
totalCount(totalCount) {
const jsTodoCount = document.querySelector('#js-todo-count');
jsTodoCount.textContent = `TodoItems: ${ totalCount }`
}
inputTodo() {
const jsFormInput: any = document.querySelector('#js-form-input');
if(!jsFormInput.value.trim()) return
this.todoListModel.addTodo( new TodoItemModel({
title: jsFormInput.value,
}))
jsFormInput.value = '';
this.todoListModel.emit('change');
}
render() {
const jsTodoList = document.querySelector('#js-todo-list');
const ul = element`<ul />`
todos.forEach( todo => {
const li = element`<li>${ todo.title } : ${ todo.isDone } : ${ todo.id}</li>`
ul.appendChild(li)
})
render(ul,jsTodoList);
}
main() {
const jsForm = document.querySelector('#js-form');
this.todoListModel.addEventListener('change', this.render) // リスナーは複数立てれる
this.todoListModel.addEventListener('change', () => {
const totalCount = this.todoListModel.getTotalCount();
this.totalCount(totalCount)
})
jsForm.addEventListener('submit', (event) => {
event.preventDefault();
this.inputTodo();
});
this.todoListModel.emit('change');
}
}
後は、削除や、チェックボックス, パージ処理を書いていきます
App.ts
import { element, render } from './html-util';
import { TodoItemModel } from './TodoItemModel';
import { EventEmitter } from './EventEmitter';
const todos = [
{ id: 0, title: "task0", isDone: false },
{ id: 1, title: "task1", isDone: true },
{ id: 2, title: "task2", isDone: false },
]
class TodoListModel extends EventEmitter {
private todos: any[];
constructor(todos = []) {
super();
this.todos = todos
}
getTodos() {
return this.todos;
}
setTodos(todos) {
this.todos = todos;
}
getTotalCount() {
return this.todos.length;
}
addTodo( item ) {
this.todos.push(item);
}
}
export class App {
private todoListModel: any;
constructor() {
this.todoListModel = new TodoListModel(todos)
}
purge() { // 完了分全削除
const todos = this.todoListModel.getTodos();
const newTodos = todos.filter( todo => {
return !todo.isDone
});
this.todoListModel.setTodos(newTodos);
this.todoListModel.emit('change');
}
totalCount() {
const jsTodoCount = document.querySelector('#js-todo-count');
const totalCount = this.todoListModel.getTotalCount()
jsTodoCount.textContent = `TodoItems: ${ totalCount }`
}
inputTodo() {
const jsFormInput: any = document.querySelector('#js-form-input');
if(!jsFormInput.value.trim()) return
this.todoListModel.addTodo( new TodoItemModel({
title: jsFormInput.value,
}))
jsFormInput.value = '';
this.todoListModel.emit('change');
}
render() {
const jsTodoList = document.querySelector('#js-todo-list');
const todos = this.todoListModel.getTodos()
const ul = element`<ul />`
todos.forEach( todo => {
const li = todo.isDone
? element`<li><input type="checkbox" class="checkbox" checked/><del>${ todo.title } : ${ todo.isDone } : ${ todo.id}</del><button class="delete">[x]</button></li>`
: element`<li><input type="checkbox" class="checkbox" />${ todo.title } : ${ todo.isDone } : ${ todo.id}<button class="delete">[x]</button></li>`
// チェックボックス状態
const checkboxState = li.querySelector('.checkbox');
checkboxState.addEventListener('change', () => {
const todos = this.todoListModel.getTodos();
const item = todos.find( item => {
return item.id === todo.id;
})
item.isDone = !item.isDone
this.todoListModel.emit('change');
})
// 削除処理
const deleteBtn = li.querySelector('.delete');
deleteBtn.addEventListener('click', () => {
const todos = this.todoListModel.getTodos();
const pos = todos.map( todo => {
return todo.id
}).indexOf(todo.id);
todos.splice(pos, 1)
this.todoListModel.emit('change');
})
ul.appendChild(li)
})
render(ul,jsTodoList);
}
main() {
const jsForm = document.querySelector('#js-form');
const purge = document.querySelector('#purge');
purge.addEventListener('click', () => {
this.purge();
})
// changeリスナー関数
this.todoListModel.addEventListener('change', () => {
this.render();
})
this.todoListModel.addEventListener('change', () => { // 分ける必要は無いが、説明の為分けている
this.totalCount()
})
jsForm.addEventListener('submit', (event) => {
event.preventDefault();
this.inputTodo();
});
this.todoListModel.emit('change');
}
}
FireStore
このままでは、リロードでデータが消えてしまうので、FireStoreに接続させます
詳しくは、vue.js
編のデータの永続化の投稿を参照ください。
firebase関係のエラーがでていたら、コンテナに入って
uname
cd src
yarn add Firebase
npx parcel --hmr-port 1235 --hmr-hostname localhost index.pug
ポートマッピングで二重起動していると失敗しますので、注意してください。
App.ts
import { element, render } from './html-util';
import { TodoItemModel } from './TodoItemModel';
import { EventEmitter } from './EventEmitter';
import * as firebase from 'firebase/app';
import 'firebase/firestore';
import { config } from '../firebase';
firebase.initializeApp(config);
const db = firebase.firestore()
const collection = db.collection('todos')
class TodoListModel extends EventEmitter {
private todos: any[];
constructor(todos = []) {
super();
this.todos = todos
}
getTodos() {
return this.todos;
}
setTodos(todos) {
this.todos = todos;
}
getTotalCount() {
return this.todos.length;
}
async addTodo({ title }) {
interface Item {
id: number;
titel: string;
isDone: boolean;
created_at: any;
}
const item = {
id: new Date().getTime(),
title: title,
isDone: false,
created_at: firebase.firestore.FieldValue.serverTimestamp(),
}
await collection.add(item)
.then(result => {
console.log(result);
})
.catch(err => console.log(err)
)
}
// addTodo( item ) {
// this.todos.push(item);
// }
}
export class App {
private todoListModel: any;
constructor() {
this.todoListModel = new TodoListModel()
}
async purge() { // 完了分全削除
// const todos = this.todoListModel.getTodos();
// const newTodos = todos.filter( todo => {
// return !todo.isDone
// });
// this.todoListModel.setTodos(newTodos);
await collection.where('isDone', '==', true)
.get()
.then(snapshot => {
snapshot.forEach( doc => {
collection.doc(doc.id).delete();
})
})
.catch(err => console.log(err))
this.todoListModel.emit('change');
}
totalCount() {
const jsTodoCount = document.querySelector('#js-todo-count');
const totalCount = this.todoListModel.getTotalCount()
jsTodoCount.textContent = `TodoItems: ${ totalCount }`
}
inputTodo() {
const jsFormInput: any = document.querySelector('#js-form-input');
if(!jsFormInput.value.trim()) return
this.todoListModel.addTodo({
title: jsFormInput.value,
});
// this.todoListModel.addTodo( new TodoItemModel({
// title: jsFormInput.value,
// }))
jsFormInput.value = '';
this.todoListModel.emit('change');
}
async render() {
const fs_data = await collection.orderBy("created_at", "desc").get();
const fs_dataItems = fs_data.docs.map( items => {
return({
dbId: items.id,
id: items.data().id,
title: items.data().title,
isDone: items.data().isDone,
created_at: items.data().created_at,
})
})
this.todoListModel.setTodos(fs_dataItems);
const jsTodoList = document.querySelector('#js-todo-list');
const jsTodoCount = document.querySelector('#js-todo-count');
const todos = this.todoListModel.getTodos()
const totalCount = this.todoListModel.getTotalCount()
jsTodoCount.textContent = `TodoItems: ${ totalCount }`
const ul = element`<ul />`
todos.forEach( todo => {
const li = todo.isDone
? element`<li><input type="checkbox" class="checkbox" checked/><del>${ todo.title } : ${ todo.isDone } : ${ todo.id}</del><button class="delete">[x]</button></li>`
: element`<li><input type="checkbox" class="checkbox" />${ todo.title } : ${ todo.isDone } : ${ todo.id}<button class="delete">[x]</button></li>`
// チェックボックス状態
const checkboxState = li.querySelector('.checkbox');
checkboxState.addEventListener('change', async() => {
// const todos = this.todoListModel.getTodos();
// const item = todos.find( item => {
// return item.id === todo.id;
// })
// item.isDone = !item.isDone
await collection.doc(todo.dbId).update({
isDone: !todo.isDone
})
this.todoListModel.emit('change');
})
// 削除処理
const deleteBtn = li.querySelector('.delete');
deleteBtn.addEventListener('click', async() => {
// const todos = this.todoListModel.getTodos();
// const pos = todos.map( todo => {
// return todo.id
// }).indexOf(todo.id);
// todos.splice(pos, 1)
await collection.doc(todo.dbId).delete();
this.todoListModel.emit('change');
})
ul.appendChild(li)
})
render(ul,jsTodoList);
}
main() {
const jsForm = document.querySelector('#js-form');
const purge = document.querySelector('#purge');
purge.addEventListener('click', () => {
this.purge();
})
// changeリスナー関数
this.todoListModel.addEventListener('change', () => {
this.render();
})
jsForm.addEventListener('submit', (event) => {
event.preventDefault();
this.inputTodo();
});
this.todoListModel.emit('change');
}
}
Vue.js や React.js を使わないとどれだけ苦労するのかを、試してみましたが確かに記述は増えてhtmlの処理は面倒くさくなるものの。
書いているのは、一番楽しいという結果になりました。(笑)
生JS が一番練習になると思いますので、ツールを使わないとどうなるかの検証もいいかなと思います。
見てくれた方ありがとうございました。m(_ _)m
以上、Docker +
終了です。
一応、git hub
に UP しておきます。
https://github.com/atoris1192/docker-todos-vanilla