DOMの取得と更新
二件の作業一覧を用意し、選択結果の保持、文字列とHTML、属性と入力値、class、要素の追加・移動・削除を順に試します。最後に、同じHTMLへ置き換えても元の要素と同一にはならないことを確認します。
説明
HTMLを読み込んだブラウザは、要素や文字をノードの木として保持します。このDOMをJavaScriptで変更すると、表示も更新されます。文字列を書き換える操作、要素を移動する操作、別の要素に作り替える操作では、残る参照やイベント処理が異なります。
二件の作業一覧を用意し、選択結果の保持、文字列とHTML、属性と入力値、class、要素の追加・移動・削除を順に試します。最後に、同じHTMLへ置き換えても元の要素と同一にはならないことを確認します。
1要素と文字ノードの並び
作業一覧と入力欄を持つ文書を作り、一覧の子ノードを表示します。
(globalThis.doc=createFormDocument作業一覧の文書を作成('<!doctype html><html lang="ja"><head><style>body{background:#111;color:#eee;font:16px/1.6 system-ui;margin:16px}.done{text-decoration:line-through;color:#aaa}</style></head><body><h1>作業一覧</h1><ul id="tasks">\n<li id="one" class="task">下書き</li>\n<li id="two" class="task">確認</li>\n</ul><label>担当 <input id="owner" value="あおい"></label><p id="message"></p><div id="archive"></div></body></html>'),globalThis.list=doc.getElementById("tasks"),{children:[...list.children要素だけの子一覧].map(e=>e.id),nodes:[...list.childNodes文字も含む子ノード一覧].map(n=>({type:n.nodeType,name:n.nodeName,text:n.textContent}))})childrenは要素二つだけを返します。childNodesには改行を含む文字ノードも入り、要素はnodeType=1、文字は3です。HTMLの見た目の行分けもDOMの一部になります。
2選択結果を保存する
同じ二件をquerySelectorAllとgetElementsByClassNameで取得しておきます。
(globalThis.fixed=list.querySelectorAllその時点の一致一覧(".task"),globalThis.live=list.getElementsByClassName変更に追随する一覧("task"),globalThis.first=doc.querySelector("#one"),{fixed:fixed.length,live:live.length,same:first===同じ要素かを比較doc.getElementById("one")})二つの検索はいま二件を返します。querySelectorとgetElementByIdは、この例では同じ要素オブジェクトを返すためsameはtrueです。fixedとliveを保持し、追加後に件数がどうなるか確かめます。
3要素を追加した後の一覧
三件目を作り、末尾へ追加します。
(globalThis.third=doc.createElement("li")新しい要素,third.id="three",third.className="task",third.textContent="公開",list.append(third)末尾へ追加,{fixed:fixed.length以前に取得した一覧の件数,live:live.length,fresh:list.querySelectorAll(".task").length})fixedは2のまま、liveと新しく検索した一覧は3になります。querySelectorAllのNodeListは取得時点の構成を保持します。保存した件数を最新状態だと思い込むと、追加要素の処理漏れが起こります。
4タグを文字として表示する
messageへ山括弧を含む文字列を設定します。
(globalThis.message=doc.getElementById("message"),message.textContent=文字列を設定"<strong>完了</strong> & 確認",{text:message.textContent,html:message.innerHTMLHTMLにした表現,children:message.children.length作られた要素数})表示されるのはタグを含む文字列です。要素は増えず、HTMLへ直列化したinnerHTMLでは山括弧と&がエスケープされます。textContentは渡された値をHTMLとして解析しません。
5同じ場所へHTMLを設定する
固定のstrong要素をinnerHTMLで設定します。
(message.innerHTML=HTMLとして解析して置換"<strong>完了</strong> & 確認",{text:message.textContent要素内の文字を取得,html:message.innerHTML,children:[...message.children].map(e=>e.localName)})今度はstrong要素が作られ、&は文字の&になります。HTMLを挿入するAPIはマークアップを解釈するため、外部の文字列を表示するだけならtextContentを選びます。固定の構造を組み立てる場合もcreateElementとtextContentを組み合わせられます。
6属性と現在の入力値
入力欄のvalueプロパティだけを変えます。
(globalThis.owner=doc.getElementById("owner"),owner.value="はる"現在の入力値を変更,{attribute:owner.getAttribute("value")HTML属性の値,default:owner.defaultValue初期値,current:owner.value現在値})現在値は「はる」ですが、HTML属性とdefaultValueは「あおい」のままです。入力欄の現在値と初期値は別に保持されます。DOMをHTMLへ変換した文字列だけで、利用者が入力した最新値を取得できるとは限りません。
7classとdata属性を更新する
一件目を完了にし、作業番号をdata属性へ保存します。
(first.classList.add("done")既存classを残して追加,first.dataset.taskId="7"data-task-idを設定,{class:first.className,taskId:first.dataset.taskId,type:typeof first.dataset.taskId保存値の型,attribute:first.getAttribute("data-task-id")})doneを加えても元のtaskは残り、CSSで取り消し線が付きます。dataset.taskIdはdata-task-idに対応し、取得値は文字列です。数値計算に使うなら数値への変換が必要になります。
8classの有無を明示する
完了classを外し、作業番号を数値に変換します。
(first.classList.toggle("done",false)doneを外した状態にする,{class:first.className,id:Number(first.dataset.taskId)文字列から数値へ,same:first===fixed[0]要素の同一性})toggleの第二引数falseは「必ず外す」という指定です。単純なtoggleと違い、繰り返しても同じ状態になります。classが変わっても要素そのものは同じなので、保存したfirstとfixed[0]は一致します。
9既存要素を先頭へ移動する
三件目をprependで先頭へ移します。
(list.prepend(third)既存要素を先頭へ移動,{order:[...list.children].map(e=>e.id),count:list.children.length,same:list.firstElementChild===third移動前と同じ要素})prependへ既存の要素を渡すと移動になります。複製は増えず、件数は3のままです。参照しているthirdも同じ要素を指し続けます。
10取り外した要素の参照
一件目を文書から外し、保持した参照を調べます。
(first.remove()親から取り外す,{connected:first.isConnected文書につながっているか,parent:first.parentElement,fixed:fixed.length,live:live.length,text:fixed[0].textContent保存済み参照から文字を取得})firstは文書から外れ、親はnullです。それでも変数やfixedが参照しているため、要素の文字は取得できます。削除は参照先を消去する操作ではなく、親子関係を切り離す操作です。
11別の場所へ同じ要素を戻す
保管領域へfirstを追加します。
(doc.getElementById("archive").append(first)保持した要素を再接続,{connected:first.isConnected,parent:first.parentElement.id新しい親,list:live.length,all:doc.querySelectorAll文書全体を検索(".task").length})同じ要素がarchive配下へ接続されます。listを起点とするliveは2件、文書全体の検索は3件です。検索の起点によって対象範囲が変わります。
12同じHTMLでも別の要素になる
二件目に処理を付け、一覧を同じHTML文字列で置き換えます。
(globalThis.old=doc.getElementById("two"),globalThis.hits=0,old.addEventListener元の要素へ処理を登録("click",()=>hits++),list.innerHTML=list.innerHTML同じHTMLから作り直す,doc.getElementById("two").click()新しい要素を起動,{same:old===doc.getElementById("two"),oldConnected:old.isConnected,hits})画面の文字が同じでも要素は新しく作られます。古い要素へ登録したlistenerは新要素へ引き継がれず、hitsは0です。HTMLの文字列一致と要素オブジェクトの同一性は別の性質です。
13古い要素の処理は残る
取り外されたoldに対してclickを呼びます。
(old.click()元の要素を起動,{hits,connected:old.isConnected文書から外れたまま,newElement:doc.getElementById("two").textContent})hitsは1になります。古い要素は文書から外れていても、参照と登録済みlistenerを保持しています。画面の更新方法によって、既存の要素を残すか作り替えるかが処理の継続にも影響します。
まとめ
DOMには要素だけでなく文字ノードもあり、検索結果には固定一覧と更新に追随する一覧があります。
文字列・HTML・入力値・属性は、それぞれのAPIが何を変更するか確認して扱います。
要素の移動・取外しと、HTMLからの作り直しでは、保持した参照とlistenerの行先が異なります。
操作履歴と解説を振り返れます。
ブラウザ / DOM・イベント
(globalThis.doc=createFormDocument('<!doctype html><html lang="ja"><head><style>body{background:#111;color:#eee;font:16px/1.6 system-ui;margin:16px}.done{text-decoration:line-through;color:#aaa}</style></head><body><h1>作業一覧</h1><ul id="tasks">\n<li id="one" class="task">下書き</li>\n<li id="two" class="task">確認</li>\n</ul><label>担当 <input id="owner" value="あおい"></label><p id="message"></p><div id="archive"></div></body></html>'),globalThis.list=doc.getElementById("tasks"),{children:[...list.children].map(e=>e.id),nodes:[...list.childNodes].map(n=>({type:n.nodeType,name:n.nodeName,text:n.textContent}))}){
"children": [
"one",
"two"
],
"nodes": [
{
"type": 3,
"name": "#text",
"text": "\n"
},
{
"type": 1,
"name": "LI",
"text": "下書き"
},
{
"type": 3,
"name": "#text",
"text": "\n"
},
{
"type": 1,
"name": "LI",
"text": "確認"
},
{
"type": 3,
"name": "#text",
"text": "\n"
}
]
}<!doctype html><html lang="ja"><head><meta charset="utf-8"><meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src 'unsafe-inline'; img-src data:; base-uri 'none'; form-action 'none'"><style>:root{color-scheme:dark}body{margin:16px;background:#111;color:#eee;font:14px/1.6 system-ui}body::after{content:"";display:block;height:calc(100vh - 32px)}a{color:#80c4d6}table{border-collapse:collapse}th,td{border:1px solid #555;padding:4px 8px}img{max-width:100%}body{background:#111;color:#eee;font:16px/1.6 system-ui;margin:16px}.done{text-decoration:line-through;color:#aaa}
:root{}</style></head><body inert><h1>作業一覧</h1><ul id="tasks">
<li id="one" class="task">下書き</li>
<li id="two" class="task">確認</li>
</ul><label>担当 <input id="owner" value="あおい"></label><p id="message"></p><div id="archive"></div></body></html>(globalThis.fixed=list.querySelectorAll(".task"),globalThis.live=list.getElementsByClassName("task"),globalThis.first=doc.querySelector("#one"),{fixed:fixed.length,live:live.length,same:first===doc.getElementById("one")}){
"fixed": 2,
"live": 2,
"same": true
}<!doctype html><html lang="ja"><head><meta charset="utf-8"><meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src 'unsafe-inline'; img-src data:; base-uri 'none'; form-action 'none'"><style>:root{color-scheme:dark}body{margin:16px;background:#111;color:#eee;font:14px/1.6 system-ui}body::after{content:"";display:block;height:calc(100vh - 32px)}a{color:#80c4d6}table{border-collapse:collapse}th,td{border:1px solid #555;padding:4px 8px}img{max-width:100%}body{background:#111;color:#eee;font:16px/1.6 system-ui;margin:16px}.done{text-decoration:line-through;color:#aaa}
:root{}</style></head><body inert><h1>作業一覧</h1><ul id="tasks">
<li id="one" class="task">下書き</li>
<li id="two" class="task">確認</li>
</ul><label>担当 <input id="owner" value="あおい"></label><p id="message"></p><div id="archive"></div></body></html>(globalThis.third=doc.createElement("li"),third.id="three",third.className="task",third.textContent="公開",list.append(third),{fixed:fixed.length,live:live.length,fresh:list.querySelectorAll(".task").length}){
"fixed": 2,
"live": 3,
"fresh": 3
}<!doctype html><html lang="ja"><head><meta charset="utf-8"><meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src 'unsafe-inline'; img-src data:; base-uri 'none'; form-action 'none'"><style>:root{color-scheme:dark}body{margin:16px;background:#111;color:#eee;font:14px/1.6 system-ui}body::after{content:"";display:block;height:calc(100vh - 32px)}a{color:#80c4d6}table{border-collapse:collapse}th,td{border:1px solid #555;padding:4px 8px}img{max-width:100%}body{background:#111;color:#eee;font:16px/1.6 system-ui;margin:16px}.done{text-decoration:line-through;color:#aaa}
:root{}</style></head><body inert><h1>作業一覧</h1><ul id="tasks">
<li id="one" class="task">下書き</li>
<li id="two" class="task">確認</li>
<li id="three" class="task">公開</li></ul><label>担当 <input id="owner" value="あおい"></label><p id="message"></p><div id="archive"></div></body></html>(globalThis.message=doc.getElementById("message"),message.textContent="<strong>完了</strong> & 確認",{text:message.textContent,html:message.innerHTML,children:message.children.length}){
"text": "<strong>完了</strong> & 確認",
"html": "<strong>完了</strong> & 確認",
"children": 0
}<!doctype html><html lang="ja"><head><meta charset="utf-8"><meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src 'unsafe-inline'; img-src data:; base-uri 'none'; form-action 'none'"><style>:root{color-scheme:dark}body{margin:16px;background:#111;color:#eee;font:14px/1.6 system-ui}body::after{content:"";display:block;height:calc(100vh - 32px)}a{color:#80c4d6}table{border-collapse:collapse}th,td{border:1px solid #555;padding:4px 8px}img{max-width:100%}body{background:#111;color:#eee;font:16px/1.6 system-ui;margin:16px}.done{text-decoration:line-through;color:#aaa}
:root{}</style></head><body inert><h1>作業一覧</h1><ul id="tasks">
<li id="one" class="task">下書き</li>
<li id="two" class="task">確認</li>
<li id="three" class="task">公開</li></ul><label>担当 <input id="owner" value="あおい"></label><p id="message"><strong>完了</strong> & 確認</p><div id="archive"></div></body></html>(message.innerHTML="<strong>完了</strong> & 確認",{text:message.textContent,html:message.innerHTML,children:[...message.children].map(e=>e.localName)}){
"text": "完了 & 確認",
"html": "<strong>完了</strong> & 確認",
"children": [
"strong"
]
}<!doctype html><html lang="ja"><head><meta charset="utf-8"><meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src 'unsafe-inline'; img-src data:; base-uri 'none'; form-action 'none'"><style>:root{color-scheme:dark}body{margin:16px;background:#111;color:#eee;font:14px/1.6 system-ui}body::after{content:"";display:block;height:calc(100vh - 32px)}a{color:#80c4d6}table{border-collapse:collapse}th,td{border:1px solid #555;padding:4px 8px}img{max-width:100%}body{background:#111;color:#eee;font:16px/1.6 system-ui;margin:16px}.done{text-decoration:line-through;color:#aaa}
:root{}</style></head><body inert><h1>作業一覧</h1><ul id="tasks">
<li id="one" class="task">下書き</li>
<li id="two" class="task">確認</li>
<li id="three" class="task">公開</li></ul><label>担当 <input id="owner" value="あおい"></label><p id="message"><strong>完了</strong> & 確認</p><div id="archive"></div></body></html>(globalThis.owner=doc.getElementById("owner"),owner.value="はる",{attribute:owner.getAttribute("value"),default:owner.defaultValue,current:owner.value}){
"attribute": "あおい",
"default": "あおい",
"current": "はる"
}<!doctype html><html lang="ja"><head><meta charset="utf-8"><meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src 'unsafe-inline'; img-src data:; base-uri 'none'; form-action 'none'"><style>:root{color-scheme:dark}body{margin:16px;background:#111;color:#eee;font:14px/1.6 system-ui}body::after{content:"";display:block;height:calc(100vh - 32px)}a{color:#80c4d6}table{border-collapse:collapse}th,td{border:1px solid #555;padding:4px 8px}img{max-width:100%}body{background:#111;color:#eee;font:16px/1.6 system-ui;margin:16px}.done{text-decoration:line-through;color:#aaa}
:root{}</style></head><body inert><h1>作業一覧</h1><ul id="tasks">
<li id="one" class="task">下書き</li>
<li id="two" class="task">確認</li>
<li id="three" class="task">公開</li></ul><label>担当 <input id="owner" value="はる"></label><p id="message"><strong>完了</strong> & 確認</p><div id="archive"></div></body></html>(first.classList.add("done"),first.dataset.taskId="7",{class:first.className,taskId:first.dataset.taskId,type:typeof first.dataset.taskId,attribute:first.getAttribute("data-task-id")}){
"class": "task done",
"taskId": "7",
"type": "string",
"attribute": "7"
}<!doctype html><html lang="ja"><head><meta charset="utf-8"><meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src 'unsafe-inline'; img-src data:; base-uri 'none'; form-action 'none'"><style>:root{color-scheme:dark}body{margin:16px;background:#111;color:#eee;font:14px/1.6 system-ui}body::after{content:"";display:block;height:calc(100vh - 32px)}a{color:#80c4d6}table{border-collapse:collapse}th,td{border:1px solid #555;padding:4px 8px}img{max-width:100%}body{background:#111;color:#eee;font:16px/1.6 system-ui;margin:16px}.done{text-decoration:line-through;color:#aaa}
:root{}</style></head><body inert><h1>作業一覧</h1><ul id="tasks">
<li id="one" class="task done" data-task-id="7">下書き</li>
<li id="two" class="task">確認</li>
<li id="three" class="task">公開</li></ul><label>担当 <input id="owner" value="はる"></label><p id="message"><strong>完了</strong> & 確認</p><div id="archive"></div></body></html>(first.classList.toggle("done",false),{class:first.className,id:Number(first.dataset.taskId),same:first===fixed[0]}){
"class": "task",
"id": 7,
"same": true
}<!doctype html><html lang="ja"><head><meta charset="utf-8"><meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src 'unsafe-inline'; img-src data:; base-uri 'none'; form-action 'none'"><style>:root{color-scheme:dark}body{margin:16px;background:#111;color:#eee;font:14px/1.6 system-ui}body::after{content:"";display:block;height:calc(100vh - 32px)}a{color:#80c4d6}table{border-collapse:collapse}th,td{border:1px solid #555;padding:4px 8px}img{max-width:100%}body{background:#111;color:#eee;font:16px/1.6 system-ui;margin:16px}.done{text-decoration:line-through;color:#aaa}
:root{}</style></head><body inert><h1>作業一覧</h1><ul id="tasks">
<li id="one" class="task" data-task-id="7">下書き</li>
<li id="two" class="task">確認</li>
<li id="three" class="task">公開</li></ul><label>担当 <input id="owner" value="はる"></label><p id="message"><strong>完了</strong> & 確認</p><div id="archive"></div></body></html>(list.prepend(third),{order:[...list.children].map(e=>e.id),count:list.children.length,same:list.firstElementChild===third}){
"order": [
"three",
"one",
"two"
],
"count": 3,
"same": true
}<!doctype html><html lang="ja"><head><meta charset="utf-8"><meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src 'unsafe-inline'; img-src data:; base-uri 'none'; form-action 'none'"><style>:root{color-scheme:dark}body{margin:16px;background:#111;color:#eee;font:14px/1.6 system-ui}body::after{content:"";display:block;height:calc(100vh - 32px)}a{color:#80c4d6}table{border-collapse:collapse}th,td{border:1px solid #555;padding:4px 8px}img{max-width:100%}body{background:#111;color:#eee;font:16px/1.6 system-ui;margin:16px}.done{text-decoration:line-through;color:#aaa}
:root{}</style></head><body inert><h1>作業一覧</h1><ul id="tasks"><li id="three" class="task">公開</li>
<li id="one" class="task" data-task-id="7">下書き</li>
<li id="two" class="task">確認</li>
</ul><label>担当 <input id="owner" value="はる"></label><p id="message"><strong>完了</strong> & 確認</p><div id="archive"></div></body></html>(first.remove(),{connected:first.isConnected,parent:first.parentElement,fixed:fixed.length,live:live.length,text:fixed[0].textContent}){
"connected": false,
"parent": null,
"fixed": 2,
"live": 2,
"text": "下書き"
}<!doctype html><html lang="ja"><head><meta charset="utf-8"><meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src 'unsafe-inline'; img-src data:; base-uri 'none'; form-action 'none'"><style>:root{color-scheme:dark}body{margin:16px;background:#111;color:#eee;font:14px/1.6 system-ui}body::after{content:"";display:block;height:calc(100vh - 32px)}a{color:#80c4d6}table{border-collapse:collapse}th,td{border:1px solid #555;padding:4px 8px}img{max-width:100%}body{background:#111;color:#eee;font:16px/1.6 system-ui;margin:16px}.done{text-decoration:line-through;color:#aaa}
:root{}</style></head><body inert><h1>作業一覧</h1><ul id="tasks"><li id="three" class="task">公開</li>
<li id="two" class="task">確認</li>
</ul><label>担当 <input id="owner" value="はる"></label><p id="message"><strong>完了</strong> & 確認</p><div id="archive"></div></body></html>(doc.getElementById("archive").append(first),{connected:first.isConnected,parent:first.parentElement.id,list:live.length,all:doc.querySelectorAll(".task").length}){
"connected": true,
"parent": "archive",
"list": 2,
"all": 3
}<!doctype html><html lang="ja"><head><meta charset="utf-8"><meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src 'unsafe-inline'; img-src data:; base-uri 'none'; form-action 'none'"><style>:root{color-scheme:dark}body{margin:16px;background:#111;color:#eee;font:14px/1.6 system-ui}body::after{content:"";display:block;height:calc(100vh - 32px)}a{color:#80c4d6}table{border-collapse:collapse}th,td{border:1px solid #555;padding:4px 8px}img{max-width:100%}body{background:#111;color:#eee;font:16px/1.6 system-ui;margin:16px}.done{text-decoration:line-through;color:#aaa}
:root{}</style></head><body inert><h1>作業一覧</h1><ul id="tasks"><li id="three" class="task">公開</li>
<li id="two" class="task">確認</li>
</ul><label>担当 <input id="owner" value="はる"></label><p id="message"><strong>完了</strong> & 確認</p><div id="archive"><li id="one" class="task" data-task-id="7">下書き</li></div></body></html>(globalThis.old=doc.getElementById("two"),globalThis.hits=0,old.addEventListener("click",()=>hits++),list.innerHTML=list.innerHTML,doc.getElementById("two").click(),{same:old===doc.getElementById("two"),oldConnected:old.isConnected,hits}){
"same": false,
"oldConnected": false,
"hits": 0
}<!doctype html><html lang="ja"><head><meta charset="utf-8"><meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src 'unsafe-inline'; img-src data:; base-uri 'none'; form-action 'none'"><style>:root{color-scheme:dark}body{margin:16px;background:#111;color:#eee;font:14px/1.6 system-ui}body::after{content:"";display:block;height:calc(100vh - 32px)}a{color:#80c4d6}table{border-collapse:collapse}th,td{border:1px solid #555;padding:4px 8px}img{max-width:100%}body{background:#111;color:#eee;font:16px/1.6 system-ui;margin:16px}.done{text-decoration:line-through;color:#aaa}
:root{}</style></head><body inert><h1>作業一覧</h1><ul id="tasks"><li id="three" class="task">公開</li>
<li id="two" class="task">確認</li>
</ul><label>担当 <input id="owner" value="はる"></label><p id="message"><strong>完了</strong> & 確認</p><div id="archive"><li id="one" class="task" data-task-id="7">下書き</li></div></body></html>(old.click(),{hits,connected:old.isConnected,newElement:doc.getElementById("two").textContent}){
"hits": 1,
"connected": false,
"newElement": "確認"
}<!doctype html><html lang="ja"><head><meta charset="utf-8"><meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src 'unsafe-inline'; img-src data:; base-uri 'none'; form-action 'none'"><style>:root{color-scheme:dark}body{margin:16px;background:#111;color:#eee;font:14px/1.6 system-ui}body::after{content:"";display:block;height:calc(100vh - 32px)}a{color:#80c4d6}table{border-collapse:collapse}th,td{border:1px solid #555;padding:4px 8px}img{max-width:100%}body{background:#111;color:#eee;font:16px/1.6 system-ui;margin:16px}.done{text-decoration:line-through;color:#aaa}
:root{}</style></head><body inert><h1>作業一覧</h1><ul id="tasks"><li id="three" class="task">公開</li>
<li id="two" class="task">確認</li>
</ul><label>担当 <input id="owner" value="はる"></label><p id="message"><strong>完了</strong> & 確認</p><div id="archive"><li id="one" class="task" data-task-id="7">下書き</li></div></body></html>演習完了