要素ノードにデータを紐付ける関数

HTML5で、紐付けたいデータが文字列なら、data-で始まる属性(custom data attribute)を付けることができる(詳細はコメント欄を参照)。

DOM3のNode#setUserDataNode#getUserDataのように、間接的に要素とデータを紐付けられる関数を作った。jQuery.data()のようなものと言ったほうが分かりやすいかも。ただ、jQuery.data()は要素に独自のプロパティ(「jQuery」に時刻のミリ秒表現を続けた文字列)を生やしてしまう。

特徴

Node#[sg]etUserDataとの違い
  • 要素ノードのみ対応。
  • Node#setUserDataの第三引数のようなハンドラ関数には未対応。
クロスブラウザ:実装によって手段を変える
  1. Node#[sg]etUserDataをサポートしている実装では、単にそれのラッパー(Firefox)。
  2. Element#uniqueIDを持っている実装では、uniqueIDを元にハッシュで管理する(IE)。
  3. その他の実装では、要素ノードと関連付けられたデータを別々の配列にインデックスが一致するように管理する。

IEならハッシュ管理となるし、IE以外はArray#indexOfをネイティブにサポートしているだろうから、速度は十分になると思う。

ソースコード

IEFirefox以外で、値を変更しようとしたときにエラーが発生するバグを直した。
var setElementData;
var getElementData;

(function (init_DOM3, init_JScript, init_array) {
  var elm = document.documentElement || document.createElement('div');

  if ('undefined' !== typeof elm.setUserData &&
      'undefined' !== typeof elm.getUserData)
    init_DOM3();

  else if ('undefined' !== typeof elm.uniqueID)
    init_JScript();

  else
    init_array();
})(
  function /* DOM3 */() {
    setElementData = function (el, key, data) {
      return el.setUserData(key, data, null);
    };
    getElementData = function (el, key) {
      return el.getUserData(key);
    };
  },

  function /* JScript */() {
    var datas = {};

    setElementData = function (el, key, data) {
      var uniqueID = el.uniqueID;
      var _data;
      var oldData;

      _data = datas[uniqueID];
      if (!_data) {
        _data = datas[uniqueID] = {};
        _data[key] = data;
        return null;
      }

      oldData = _data[key];
      _data[key] = data;
      return oldData;
    };

    getElementData = function (el, key) {
      var uniqueID = el.uniqueID;
      var data = datas[uniqueID];

      if (!data || !data.hasOwnProperty(key)) {
        return null;
      }

      return data[key];
    }
  },

  (function (Array_indexOf) {
    return function /* array */() {
      if ('undefined' === typeof Array.prototype.indexOf) {
        Array.prototype.indexOf = Array_indexOf;
      }

      var nodes = [];
      var datas = [];

      setElementData = function (el, key, data) {
        var idx = nodes.indexOf(el);
        var _data;
        var oldData;

        if (idx === -1) {
          idx = nodes.length;
          nodes[idx] = el;
          _data = datas[idx] = {};
          _data[key] = data;
          return null;
        }

        _data = datas[idx];
        oldData = _data[key];
        _data[key] = data;
        return oldData;
      };

      getElementData = function (el, key) {
        var idx = nodes.indexOf(el);
        var data;

        if (idx === -1) {
          return null;
        }

        data = datas[idx];
        if (!data.hasOwnProperty(key)) {
          return null;
        }

        return data[key];
      };
    };
  })(
    function (elt) {
      var len = this.length;
      var from = +arguments[1] || 0;
      from = (from < 0)
           ? Math.ceil(from)
           : Math.floor(from);
      if (from < 0) {
        from += len;
      }
      for (; from < len; ++from) {
        if (from in this && this[from] === elt) {
          return from;
        }
      }
      return -1;
    }
  )
);

使い方

// 変数elは要素ノードを指すものとする

// 要素elに「key」をキーにデータを設定する
setUserData(el, 'key', 'これはデータです');

// 要素elに設定された「key」をキーとするデータを取得する
getUserData(el, 'key');  // => 'これはデータです'
// 存在しないキーを指定した場合、nullが返る。

// 元の要素には影響を与えない
alert(el.outerHTML);