◆ input イベントのたびに内部データの更新は手間
◆ 無駄な処理感がありすぎて気持ち的に抵抗ありすぎ
◆ 保存ボタン押すタイミングで DOM からデータ収集して更新したい
◆ hyperHTML だと更新を減らすために内部でデータを持ってる
  ◆ input イベントの度に更新しないとリセットが画面に反映されないなどの問題が起きる
◆ form 系だけ hyperHTML の管理外にして DOM 操作するのが良いかも

これまでは DOM ありきな デザインがあってそこで使うデータをもたせる感じでしたが 何度もウェブページを作っていると DOM 操作もめんどくさくなってきますし デザインはふんわりとしか決めてなくて そこを確定させないとデータの扱いも決まらない感じでした

どう表示するかがほぼ存在しない CUI やサーバサイドみたいなものがメインの人は データの方を中心に考えるかと思いますが 私はウェブページとか目に見えるものを作るのが好きで そっちが先にあって後からそこで見せるためのデータがあるという考え方なんですよね

ただ フレームワークや hyperHTML などの DOM 描画系のライブラリを使ってると データだけで管理して それを表示するものを作るという流れになることが多いです
どっちが常に優れてるというわけでもなく どっちにもいいところ悪いところがあると思います
単純なボタンを押したらサーバから json を取得して画面を更新するのなら hyperHTML などのツールが楽です


ただ form 系が混ざると hyperHTML などは使いづらくなってきます
form 部分だけこれまでの DOM を直接操作するほうが楽です

単純に input 系の要素の値の設定が少し特殊なのもありますが 一番は入力時の更新です
input イベントごとに常に DOM ではなく裏側に保存されてる値も更新するのがスタンダードな方法かと思います
しかし ユーザが一回キーボードを押すごとにデータを更新って無駄すぎる感じがして気持ち悪いです
文字列がすごく長くなってくると 目に見えて押してから一瞬固まるとかあると思いますし 参照が同じになってないリスナも毎回すべて新しい関数に置き換わります
ユーザが保存ボタンを押すたびくらいな低頻度ならいいですが キーボードの 1 タイプごとに発生する input イベントのたびに全部のリスナの入れ替えはちょっと嫌です
こういう無駄を減らそうとすればできなくもないのですが コード的にやることが増えて逆に複雑になってきます


input イベントごとに更新せず 保存ボタンを押したときに初めて更新するのが理想なのですが 内部で値を保持して変更が必要かをチェックしているので 画面と内部データの同期が取れなくなると おかしな挙動になることがあります
例えば リセットしようとしたときに input イベントで更新していないと内部データは初期状態のままなので上書きしても 変更無しとみなされます
しかし画面上は別の文字列が表示されているのでリセットされてないことになります

また リストがあってその要素をクリックすると編集画面がクリックしたものの情報に置き換わる UI があったときに クリックした要素のあるデータが前にクリックした要素と同じだった場合 別の要素をクリックしたのに画面上は前の要素の編集中文字列が表示されたままになります
input イベントごとに個々のプロパティを更新よりは 保存ボタンの click イベント時にデータをまとめて取得のほうが作るのも楽なんですけどね
内部で保持しているデータのキャッシュをクリアできない以上 毎回更新するしかなくなります


また input イベントごとの更新をするときに キャンセル機能があったら 元のデータをそのまま書き換えられません
編集画面作成のタイミングで そのデータのクローンを作り それを編集用として value などに設定する対象にします
保存のタイミングで 編集用を本来のデータに上書きします
ちょっとしたことですが これも面倒な部分で DOM 操作なら DOM に存在するデータが編集中のものなので こういう手間が発生しません


既存の DOM を直接操作しているのを hyperHTML に置き換えていて このあたりだけが苦痛でした
他にはイベント時の処理を全部見ないとどの状態のときにどういう DOM 構造になるのかわからないなどの問題もありましたが それは実際に動かしてみてブラウザの開発者ツールで DOM 構造を見ればすぐ解決できるものです
入力項目がいっぱいあるツールで全部イベントごとにプロパティ更新や編集用にクローンを作ったりが一番やりたくないなーと思う作業でした

ちなみに 基本的には コードの長さは DOM を直接操作するのが最も短く WebComponents を使ったり hyperHTML を使うほうが長くなりました
hyperHTML などのツールを使うメリットは データを処理する部分と画面表示をわけられるところです
DOM を直接に比べるとかなりスッキリと分かれるので デザイナーの人とプログラマの人が別れて作業する場合には向いてそうです
また コード的にも DOM を操作するのと データを更新するのが混ざっていたのがはっきり別になるので見やすさも上がってます
特に HTML のテンプレートだけを見てどういう状態ではどんな DOM 構造なのかがわかるのは大きなメリットです
なので form 系だけのために 全部 DOM 操作にするのもなぁ と思うところです



とりあえず form 部分だけ hyperHTML 管理とは切り離して DOM 操作できるようにしてみました

import {bind, wire} from "https://cdn.jsdelivr.net/npm/hyperhtml@2.25.5/esm.js"

customElements.define("un-managed", class extends HTMLElement {
constructor(){
super()
this.attachShadow({mode: "open"})
}

get manager(){
return this._manager
}

set manager(value){
this._manager = value
value.init(this.shadowRoot, this.data)
}

get data(){
return this._data
}

set data(value){
if(this.manager){
this.manager.update(value)
}
this._data = value
}
})

const m = {
sroot: null,
init(sroot, data){
this.sroot = sroot
this.render()
if(data) this.update(data)
},
render(){
this.sroot.innerHTML = `
<input id="a">
<input id="b">
<button id="save">Save</button>
`
this.elements = Object.fromEntries(Array.from(this.sroot.querySelectorAll("[id]"), e => [e.id, e]))
this.elements.save.onclick = () => {
const data = {
a: this.elements.a.value,
b: this.elements.b.value,
}
this.sroot.dispatchEvent(new CustomEvent("save", { detail: data, composed: true }))
}
},
update(data){
for(const [k, v] of Object.entries(data)){
this.elements[k].value = v
}
}
}

const gdata = {
view: {
selected: 0,
},
data: {
items: [
{ a: "foo", b: "bar" },
{ a: "123", b: "456" },
],
},
}

const onclickitem = eve => {
if(eve.target.tagName !== "LI") return
gdata.view.selected = +eve.target.dataset.id
render()
}

const onsave = eve => {
gdata.data.items[gdata.view.selected] = eve.detail
render()
}

const render = () => {
bind(document.body)`
<div>...</div>
<ul onclick=${onclickitem}>
${gdata.data.items.map((e, i) => wire(e)`
<li data-id=${i}>
${gdata.view.selected === i ? wire()`<b>${e.a} / ${e.b}</b>` : wire()`${e.a} / ${e.b}`}
</li>
`)}
</ul>
<un-managed manager=${m} data=${gdata.data.items[gdata.view.selected]} onsave=${onsave}></un-managed>
<div>...</div>
`
}

render()

基本部分は hyperHTML を使ってます
ul のところでリストを表示してクリックすると 編集画面が切り替わります
編集画面の部分は un-managed 要素にしていて ここは hyperHTML の管理外です
manager を設定するとそれが un-managed の中身をコントロールします

manager に設定してる m オブジェクトの init メソッドが最初に実行されます
render メソッドで DOM を作ってリスナ設定していて 保存ボタンのクリックイベントがあったら save イベントを発生させます
save イベントで値を更新して再描画します

un-managed 要素はこの用途外にも hyperHTML で管理しない部分を作れるようにしたものなので少し複雑化してますが 実際には xxx-form みたいな専用の要素を作ってその要素のクラスで manager に当たる処理もやってしまえばいいと思います

hyperHTML 的には編集したいデータを un-managed 要素に渡して save イベントが来たら再描画するだけなので 面倒な部分がありません
un-managed 要素の中身も 保存ボタンが押されたらデータを収集して save イベントを起こす比較的単純なものです