Help us understand the problem. What is going on with this article?

jQueryに学ぶHTML文字列からのDOM要素生成 〜 JSおくのほそ道 #012

More than 5 years have passed since last update.

こんにちは、ほそ道です。

今回は、jQueryの文字列からDOM要素を生成する部分をやっていきます。
ソースコードにはエラーハンドリングや汎化、XHTML対応などの処理分岐が多く見られるので大事な部分だけ抜き出してソリッドなほそ道流の生成処理にして紹介します。

対象バージョン:jQuery-2.1.1
目次はこちら

jQueryのDOM要素生成構文

jQueryを使って文字列からDOM要素を生成する構文は、たとえば下記のような感じです。

//bodyに要素追加
$("<b id='id1'>foo</b><br /><b id='id2'>bar</b><script type='text/javascript'>alert('alert')</script>").appendTo("body")

ではこの構文をブラウザコンソールから実行してみましょう


スクリーンショット 2014-06-15 4.07.42.png

↑のコードをエンターキーで実行します

スクリーンショット 2014-06-15 4.08.47.png

↑ちゃんと各要素が書いた通りにブラウザに反映されます!
id属性やtype属性なども反映されております。


これって何気に凄い事だと思うんですよ。なぜならば、、
文字列からDOM要素に一発で変換出来る組み込み処理が用意されていないから

自作のDOM要素生成処理を実装してみる

それではjQueryソースに習ってDOM要素生成処理を実装します。
簡略化してますがjQueryのソースを参考にしているので、なんとなーく似たような事をやっているんだなと思っていただけると幸いです。

手法は大きく分けて二つの方法があります。

  • 単一DOM要素に属性を追加して生成する
  • 自由なHTML文字列を解釈して生成する

単一DOM要素に属性を追加して生成する

jQueryの実行パターンにも同様のものがあります。
こちらは余りニーズは無いようにも思いますがやってみます。

単一DOM要素生成
function getSingleDom(domStr, attributes) {

  var dom = new Object(),
    rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>|)$/,
    context = document,
    parsed = rsingleTag.exec(domStr),
    dom, prop;

  // パターンマッチングからDOM要素名を取得して要素生成
  if ( parsed ) {
    dom = context.createElement(parsed[1]);
  } else {
    return undefined;
  }

  // 引数2のオブジェクトから属性を追加
  if (attributes) {
    for (prop in attributes) {
      dom[prop] = attributes[prop];
    }
  }

  // HTML要素を返す
  return dom;
}

キモは正規表現マッチングで取り出したタグ名から
createElementで要素を生成しているところですかね。

それではコンソールから実行してみましょう。


スクリーンショット 2014-06-15 5.32.36.png

↑引数一個パターン、要素生成されてます。


スクリーンショット 2014-06-15 5.33.37.png

↑引数二個パターン。第一引数はタグ名で、第二引数はそのタグの属性オブジェクト。
クリックするとイベントが発生してアラートが上がりました。うん、いい感じですね。

自由なHTML文字列を解釈して生成する

今度は利用度が高いと思われる自由な文字列渡しによる要素生成です。
SCRIPT要素の扱いに癖があります。

HTML文字列からのDOM要素生成
function getMultiDom(domStr) {

  var context = document,
    fragment = context.createDocumentFragment(),
    nodes = [],
    scripts = [],
    i = 0, j = 0, elem, tmp;

  // ダミーのDIV要素を作成して中にテキストを挿入
  tmp = fragment.appendChild( context.createElement("div"));
  tmp.innerHTML = domStr;

  for ( ; i < tmp.childNodes.length; i++) {
    // ダミーのDIV要素からHTML要素としてchildNodesで取り出せる
    var node = tmp.childNodes[i];

    // SCRIPT要素は新たに生成し直さなければ実行されない
    if (node.tagName.toLowerCase() === 'script') {
      var script = document.createElement('script');
      if (node.type) {
        script.type = node.type;
      }
      if (node.src) {
        script.src = node.src;
      } else {
        script.text = node.text;
      }
      nodes[i] = script;
    } else {
      // SCRIPT以外の要素
      nodes[i] = node;
    }
  }

  // HTML要素配列を返す
  return nodes;
}

フリーな文字列からのDOM生成の一番の勘所は

ダミーのDIV要素に引数のHTML文字列をinnerHTMLとして差し込んでchildNodesで取り出すこと

これでHTML要素として解釈出来るようです。

しかし、このやり方だとSCRIPT要素だけ対応しきれません。
生成時にページに要素追加してもJSコードは実行されませんでした。
SCRIPT要素を動かす為には

新たにSCRIPT要素を生成して属性を移植してページに挿入します

それではこちらも動かしてみます。


スクリーンショット 2014-06-15 21.55.53.png

↑入れ子構造のHTML要素も処理され、ID検索も可能です。


スクリーンショット 2014-06-15 21.56.48.png

↑インラインコードのJavaScriptも動作します。


スクリーンショット 2014-06-15 21.57.19.png

↑外部ソースのJavaScriptも動作します。


このコードは完璧では無いので改めてご注意!
例えば入れ子の場合に小要素がSCRIPTだと動きません。
親要素だけを抽出してSCRIPT検査を行っている為です。
解決する為には小要素にSCRIPTがあれば内側要素を取り出して検査する、という
メンドーな工程が発生したので端折りました。
その他XHTML対応や細かいブラウザ毎の対応等も必要で、
完全に実装するには結構なコードボリュームと検証を要すると思われます。

まとめ

いかがでしたでしょうか?
ほそ道は日頃、文字列からDOM要素を生成したいとおもうシチュエーションに遭遇する事が結構ありまして、
今後も使えるソリッドなサンプルを残せたかなと思います。
やりたい事に対して「思ったように動かない」ことも多くあるかと思いますが、jQueryソースの中には
先人達の苦労と知恵が詰まっているので何かと参考になるのではないかと思います。

これで「検索」「生成」の大まかな流れは一通り眺めて行きました。
次回はjQuery関数やjQueryインスタンスの拡張について調べます。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away