console.trace();

旬なものから枯れたテクノロジーまで

快適!! GoogleTagManager + JSONで複数ページの要素の管理

こんばんは。早いものでもう年の瀬ですね。 またまたblog放置してしまいましたが、 やっとひと段落&ネタが溜まってきたのでぼちぼち書こうと思います。

早速本題に入りましょう。

GoogleTagManager(=以下GTM)を使って、 複数のページでたくさんのDOM要素を必要とするときあなたならどうしますか? いろんな方法があると思います。

  • 特定のイベントでルールで引っ掛けるもよし
  • マクロを使って一からJavaScript開発するもよし

様々な方法があると思います。 そこで最近閃いたのですが、JSONを使えば上記の方法よりもっともっと効率よく、 JavaScript開発者と運用者の分離が出来るのではないかと。

JSONとは

JSON(ジェイソン、JavaScript Object Notation)は軽量なデータ記述言語の1つである。構文はJavaScriptにおけるオブジェクトの表記法をベースとしているが、JSONはJavaScript専用のデータ形式では決してなく、様々なソフトウェアやプログラミング言語間におけるデータの受け渡しに使えるよう設計されている。 - wikipediaより

そうです、JavaScriptのオブジェクトベースなので親和性が非常に高いのです。

で、一つここで問題が。

JSONファイルをGTMからどうやって読み込めばいいのか

ではここから実際にどうやってJSONをGTMに落とし込んでいくのか画像を交えて見ていきます。

タグの準備

まず下記のような画像のタグを用意します。

f:id:yotanote123:20131212224729p:plain

HTML

<script>
{{run}}
</script>

scriptタグ内からrunというマクロを呼び出す記述だけでOK。 そしてルールはgtm.js*1とgtm.dom*2に等しいとします。

マクロの準備

タグの準備が出来たら次はマクロの準備です。

run - カスタムJavaScript
function () {
    var ev = {{event}};
    var pt, json;

    // IE8未満除外
    if (
        !JSON
        || !JSON.parse
        || !window.addEventListener
    ) {
        return;
    }

    switch (ev) {
    case 'gtm.js':
        // ページの初期設定
        pt = {{url path}}.replace('index.html', '');
        json = JSON.parse({{LIST}});

        if (!json[pt]) {
            return;
        }

        // gtm.jsから提供されるオブジェクトの拡張
        window.dataLayer[0].pageData = json[pt];
        break;
    case 'gtm.dom':
        if (!window.dataLayer[0].pageData) {
            return;
        }

        {{cacheCollection}}
        break;
    default:
        break;
    }

    return;
}

{{run}}マクロ内で呼ばれているマクロの解説

  • {{event}}は今何のイベントでこのscriptが呼ばれているかを返してくれるマクロ
  • {{url path}}はページのパスを文字列で返してくれるマクロ
  • {{LIST}}マクロはこれから用意するのですが、こちらの行が非常に大切な部分になるので解説は後ほど

次に、肝となる{{LIST}}マクロを準備します。

LIST - カスタムJavaScript
function () {
    return '{"/":{"event":"custom1","category":"custom1","selector":[".navbar",".jumbotron",".content-body"],"action":["HEADER","MAINVISUAL","CONTENT"]},"/lounge/":{"event":"custom2","category":"custom2","selector":[".navbar","#under-content","#footer"],"action":["HEADER","CONTENT","FOOTER"]}}';
}

こちらのマクロは何をしているかというと、JSONの文法で記述された文字列を単純にリターンしているだけです。

改行とスペースをトリムする前はこちら。

{
    "/" : {
        "event" : "custom1",
        "category" : "custom1",
        "selector" : [".navbar", ".jumbotron", ".content-body"],
        "action" : ["HEADER", "MAINVISUAL", "CONTENT"]
    },
    "/lounge/" : {
        "event" : "custom2",
        "category" : "custom2",
        "selector" : [".navbar", "#under-content", "#footer"],
        "action" : ["HEADER", "CONTENT", "FOOTER"]
    }
}

特定のバージョン以降のブラウザにはwindow.JSON*3というオブジェクトがあるので、 これを用いて{{LIST}}の文字列をオブジェクトとして取り扱います。

そして最後にDOM要素を取得する{{cacheCollection}}マクロを準備します。

cacheCollection - カスタムJavaScript
function () {
    var win = window;
    var doc = document;
    var selectors = win.dataLayer[0].pageData.selector;
    var len = selectors.length;
    var res = [];
    var el;


    for (var i = 0; i < len; i++) {
        el = doc.querySelector(selectors[i]);

        if (!el) {
            continue;
        }

        res.push(el);
    }

    // data形成
    win.dataLayer[0].pageData.nodeListArr = res;

    return res;
}

これですべての準備が終わりました。 どうなるのか実際の実行結果を見てみましょう。

トップページ - /

f:id:yotanote123:20131212233211j:plain

一連の流れの解説
  1. gtm.jsのタイミングでdataLayer内のgtm.jsと同じオブジェクト内にpageDataというプロパティを作成し
  2. {{LIST}}内のJSON形式の文字列をオブジェクトに変換して保存
  3. gtm.domのタイミングで{{cacheElement}}を実行
  4. 先ほどのpageData内のselectorを参照し、HTML内の対象を取得しnodeListArr配列として保存しています。

で先ほどのJSONの設計

{
    "ページのパス" : {
        "event" : "好きなイベント名",
        "category" : "好きなカテゴリー名",
        "selector" : ["要素のclass名またはid名 - 1", "素のclass名またはid名 - 2"],
        "action" : ["要素のclass名またはid名 - 1のアクション値", "要素のclass名またはid名 - 2のアクション値"]
    }
}

上記のような形になります。

でも、別のページではHTMLの構造が違うんです。という要件に対しても、 もう一つ別の"ページパス"をJSONに記述して別の要素のclassまたはidを用意すれば対応できます。

下層 - /lounge/

f:id:yotanote123:20131212233214j:plain

ざっとこんな感じですね。

別のページでも動いてしまうのでは?

という懸念もありますが、実際に見てみましょう。

下層 - /studio/

f:id:yotanote123:20131212235128j:plain

pageDataオブジェクトが取得できない場合は何もしません! という処理を行っているので別のページに何か影響が出るということはありません。

あとはこのnodeListArrに入っている要素を取得して対となるaction値を取得して_gaq.pushしたりと様々な用途に使えると思います。 また、サイトがリニューアルしてしまった場合でもJavaScript側の処理は変えずにJSONだけ変更をすればOKですね。

これで運用も安心ですね!

*1:gtm.jsがロードした際のイベント

*2:DOMContentLoadedに相当するイベント

*3:サポートはInternet Explorer8以上、FireFox3.5からなのでGTMのサポートしてる範囲ではほぼほぼ問題ありません