input要素のvalue属性の値を監視したい

例えばinput要素のtype属性がhidden値のHTMLElementのvalue属性の値をフォームの送信時にセットするのではなく、何かしらのアクションがあったタイミングでセットしておきたい場合にどうしようかなと思って調べていたら行きついたMutationObserverについての解説。

まず以下のようなHTMLを準備します。

<section>
  <h1>年月を監視して値が変わったら「値」を自動更新する。</h1>
  <p>値:<input type="text" id="box"></p>
  <div>
    <input type="month" id="month">
    <a href="" id="now">今月</a>
    <a href="" id="prev">前月</a>
    <a href="" id="next">次月</a>
    <a href="" id="observe">監視をやめる</a>
  </div>
</section>

画面表示をするとこんな感じ。

MutationObserverのHTML準備

今月・前月・次月のclickイベント発生時、<input type="month">要素のchangeイベント発生時にid="box"の値を代入したい。それぞれのイベントに代入処理を記述すればいいんだけど、同じ処理をするのでイベントが増える都度記述するのもなぁと感じ調べてみました。

値の変化を監視するMutationObserver

そこで登場するのがMutationObserver。ひとまずソースコードscriptの全文は以下の通り。

const
  elmBox = document.getElementById('box'),
  elmMonth = document.getElementById('month'),
  elmNow = document.getElementById('now'),
  elmPrev = document.getElementById('prev'),
  elmNext = document.getElementById('next'),
  elmObserve = document.getElementById('observe');

let today = new Date();

const observer = new MutationObserver((mutations) => {
  mutations.forEach((mutation) => {
    if (mutation.target === elmMonth) {
      elmBox.setAttribute('value', mutation.target.value);
    }
  });
});
const config = {
  attributes: true,
  attributeFilter: ['value']
};
observer.observe(elmMonth, config);

const formatMonth = () => {
  let strDate = '';
  strDate = strDate.concat(today.getFullYear(), '-');
  strDate = strDate.concat((today.getMonth() + 1).toString().padStart(2, '0'));
  return strDate;
};

elmNow.addEventListener('click', (e) => {
  e.preventDefault();
  today = new Date();
  elmMonth.setAttribute('value', formatMonth());
});

elmPrev.addEventListener('click', (e) => {
  e.preventDefault();
  today.setMonth(today.getMonth() - 1);
  elmMonth.setAttribute('value', formatMonth());
});

elmNext.addEventListener('click', (e) => {
  e.preventDefault();
  today.setMonth(today.getMonth() + 1);
  elmMonth.setAttribute('value', formatMonth());
  // 動作しないパターン
  // elmMonth.value = formatMonth();
});

elmObserve.addEventListener('click', (e) => {
  e.preventDefault();
  observer.disconnect();
});

elmMonth.addEventListener('change', (e) => {
  e.preventDefault();
  e.target.setAttribute('value', e.target.value);
})

elmMonth.setAttribute('value', formatMonth());

ポイントをちょっと解説。

MutationObserverのインスタンス生成とコールバック関数の定義

全文の11行目から17行目の記述でMutationObserverのインスタンス生成とコールバック関数の定義を行っています。observerインスタンスで監視する対象(エレメント)を複数設定できるので、コールバック関数では監視対象を判定して値をセットするように記述しました。

const observer = new MutationObserver((mutations) => {
  mutations.forEach((mutation) => {
    if (mutation.target === elmMonth) {
      elmBox.setAttribute('value', mutation.target.value);
    }
  });
});

MutationObserverのオプション設定

全文の18行目から21行目の記述で、observerインスタンスにオプションを設定するための定数を定義しています。

const config = {
  attributes: true,
  attributeFilter: ['value']
};

attributesをtrueにすることで属性値が変更された場合に反応してくれます。attributeFilterは、どの属性値が変更されたら反応させるかをフィルタリングします。今回の場合は、input要素のvalue値を監視したいので、上記のように記述しました。

監視対象要素の値を変更する

あとは監視対象要素の変更をどのタイミングで行うかだけです。各要素のclickイベントやchangeイベントで監視対象要素の属性値(value値)をsetAttributeメソッドでセットします。

elmMonth.setAttribute('value', formatMonth());

できた!\(^o^)/

値の変更の定義が値をセットするということで、メソッドでセットしないといけない模様。最初、以下のようにvalueプロパティに代入するパターンでは動作しなかったので注意が必要。

// OKパターン
elmMonth.setAttribute('value', formatMonth());
// 動作しないパターン
// elmMonth.value = formatMonth();

参考サイト

MutationObserver
https://developer.mozilla.org/ja/docs/Web/API/MutationObserver
MutationObserver: observe() method
https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver/observe
setAttribute
https://developer.mozilla.org/ja/docs/Web/API/Element/setAttribute
value
https://developer.mozilla.org/ja/docs/Web/API/Attr/value