console.trace();

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

新 GoogleTagManagerでクロスドメイントラッキング

巷で盛り上がってる某カレンダーblog祭りに参加し損ねたyotanoteです。 最近のお気に入りはこれ 闇 Advent Calendar 2013

これからはタメ口御免のスタイルでいこうと思う。

今回の話題は当blogで一番注目のあるワードだと思われるクロスドメインについて。

以前こんな記事を書いた。

記事の内容はかなり古い状態だし yotanote式GoogleTagManager + クロスドメイントラッキングをやってみることにする。

10月頃カスタムJavaScriptのマクロが使えるようになり、 アユダンテさんでも取り上げられていた。

今回の肝はこのマクロでリリースされたカスタムJavaScriptとクリックリスナーだ

出来るだけマクロを少なくしてみるという試みも同時にしつつ、 早速いつものように準備していく

cross_link - Googleアナリティクスタグ

f:id:yotanote123:20131214214233j:plain

ターゲットURLはdataLayer内のtargetURLプロパティの値を参照している。 ルールは後のカスタムJavaScriptからdataLayer.pushされるcross_linkになる。 等しいを選択しているのできっと内部的にはJavaScriptでは同値(または等値)演算が行われているのかな

カスタムHTML - タグ

f:id:yotanote123:20131214214709j:plain

カスタムHTMLは{{matchLink}}呼び出しのみ。 ルールはクリックリスナーから提供されるイベントであるgtm.clickに引っ掛ける

クリックリスナー

f:id:yotanote123:20131214214244j:plain

全てのページで配信。楽に設定できる。

{{targetURL}}マクロ - データレイヤー変数

f:id:yotanote123:20131214215737j:plain

ここでdataLayerの変数設定をしておかないと、先ほどのcross_linkのタグから{{targetURL}}を呼び出せなくなるので注意

{{getURL}}マクロ - 自動イベント変数

f:id:yotanote123:20131214214403j:plain

自動イベント変数の要素タイプをURLと設定することでクリックリスナーのイベントが発火した際に同時にpushされるgtm.elementUrlを参照することが可能になる。 他のタグやマクロ内から{{getURL}}とすることで呼び出せる。

{{matchLink}}マクロ - カスタムJavaScript
function () {
    var reg = /google\.co\.jp/i;
    var url = {{getURL}};
    var res = url.match(reg);

    if (!res) {
        return;
    }

    window.dataLayer.push({
        'event' : 'cross_link',
        'targetURL' : typeof res !== 'string' ? res.toString() : res
    });

    return res;
}

変数var reg = /google.co.jp/i;の部分に計測したいドメインを正規表現で記述すれば簡単に計測できる。 本当は定数文字列のマクロとして扱いたかったんだけど、文字制限が255文字までと意外に短いので断念した。

眺めてみると昔に比べてだいぶ簡単に実装できるようになったのがわかる。

GoogleTagManager API Expertになりたいこのごろ(ないよね

快適!! 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のサポートしてる範囲ではほぼほぼ問題ありません

GoogleTagManagerのデータレイヤー変数を使ってイベントトラッキングをしてみた

先日のあのページを更にトラッキングのデモにしてしまおうという計画が進行中です。

前回の記事でも触れたとおりクロスドメイントラッキングはGoogleからサンプルコードが発表されているので、 今回はそのコードを元に色々といじっていみました。

一概にイベントトラッキングといっても様々な要件があると思います。 例えば、対象のリンクはa要素なのか、ドメインを判別するのか、ユーザーイベントは?などなど、 スコープを絞らないと際限なく作れてしまうので、今回は下記の要件を考えます。

ソース要件

  • 対象: 特定class属性値を持った要素配下のa要素
  • イベント: clickのみ
  • valueやlabelについて: a要素のテキストをJSで取得

デモでは.navbarの配下であるa要素を対象にしています。

ソース

https://github.com/yotanote/gtm-event/blob/master/source.js

GTM上のタグの設定

f:id:yotanote123:20130728202956j:plain

デモ

http://gtm-test.herokuapp.com/

結果

f:id:yotanote123:20130728212620j:plain

どうやらデータレイヤー変数は、データレイヤー内のオブジェクトのプロパティを指すみたいです。 なので、JavaScript側でトラッキングしたい正確なタイミングでdataLayerにpushするコードを開発すれば、 後は管理画面側のマクロでデータレイヤーのプロパティの指定をしてあげて、簡単に_gaqにpushするあたりは実装出来そう。 解析士と開発者の分業も夢じゃないですね!

ほかのマクロについてはちょこちょこいじってはいますがイマイチ有効な使い方がピンとこないですね。。。

自分メモ

  • ga.jsはソースコード上に複数現れる問題ですが、どうやらこれはGTM側の仕様みたい。
  • gadebuggerのconsoleに関係ないものがたくさん表示されてしまうので注意
  • 併用すればDOMのタイミングも怖くない!!

いじるの面白くなってきた!!

GoogleTagManager + GoogleAnalytics でクロスドメイン トラッキングを試してみた

さっそく昨日のテンプレートを使いフロント周りの技術をいじり倒してやる!という訳で試してみた。

概要

GTM(=GoogleTagManager)の公式アナウンスでクロスドメイントラッキングが簡単にできるよ!ということなのでさくっと入れてみた。

ソース

https://github.com/yotanote/gtm-test

デモ

http://gtm-test.herokuapp.com/

コンテナ

画像ですいません。。。出来上がると大体こんな感じになります。

f:id:yotanote123:20130722231452j:plain

  • cross

f:id:yotanote123:20130722232116j:plain

  • custom: 上記のリンク先内のソースコードべたっと貼ったもの

  • pv: 通常のpv設定

注意したいのが上記のリンクに書かれている内容の5番目

引用

5, イベントに一致する cross_link が発生したときにクロスドメイン リンク タグを配信する新しいルールを作成します。

つまり、cross_linkを設定しなさいって事みたいですね。 日本語読解力が残念なのでソース読んで納得&解決しました。。。

デモを見ての通りgoogleってリンク触るとちゃんと_getLinkerUrl()が動いていることが確認できると思います。

計測したいドメインをwhitelistの配列に追加するだけでページ内の対象ドメインを含むa要素のリンクをトラッキング出来るので、多数のリンクを管理する場合にはもってこいですね。

ただ、劇的な開発効率だとは思いますが、やはりビジネスシーンで使うには、 GTMとJavaScriptに精通したエンジニアがいないと運用は難しそうですねー。

後はこれを応用して外部ドメインへの遷移なんかも実装できそうですね。今度やってみます。 これだと何故かga.jsが2つ読み込まれているのでそこも調査&改善しなくては。。。

触っていた所感として、 GTMの方向性としてはJavaScriptを独自開発する場合はDOMやイベントうんぬんは全部マクロに委ねてね!って感じなんでしょうかね。それはそれですごく有難い!

まとめ

  • マクロを使いこなせればかなりの開発効率が見込める
  • 慣れですね、慣れ



Google変態!(言いたいだけ

Google Tag ManagerとGoogle Analytics  たぶんその1

つい先日出ましたね。GoogleTagManager(=以下GTM)

最近仕事て使う機会が増えてきたので自分用メモがてらちょこちょことまとめて行こうと思いますよっと。

概要

ざっくりどんなものかを説明すると、

管理画面にアクセス出来る人がタグマネージャを使用したいドメインを設定し、コンテナと呼ばれるタグの集合体を管理する箱を作成。 発行されるタグを対象ドメインに貼り付け、独自のタグや独自のスクリプトを記述したタグを設定し、 公開ボタンをぽちっとするだけで3で作成したコンテナがサイトに埋め込まれる。

これだけでサイトがいじれてしまうという優れもの。 メリットデメリットは他の人も触れているみたいなので、 今回フォーカスしたいのはイベントについて。

タグ読み込みとイベントのタイミング

ご存知の通り、GTMはタグの読み込まれる順番を指定できない。これが非常に厄介。 けども、任意のタイミングで何かを行うscriptを記述したい場合、適切なタイミングで実行する方法が用意されている。

未知との遭遇にハマったのでリファレンス見たり、内部読んでみて自分なりに調べてみた。(間違っていたら指摘いただけると幸いです)

  • gtm.js

    • gtm.jsがscriptタグでdocumentappendされたタイミングでのタグの実行

    • DOMの状態はページや環境によるのでここでDOMを使っての操作は行わないほうが無難

  • gtm.dom

    • DOMContentLoadedのタイミングでのタグの実行

    • DOMが準備されているので要素を操作する際に最適

  • gtm.load

    • window.onloadのタイミングでのタグの実行

    • ブラウザのレンダリングが完了している段階なので操作は出来るだけ少なめにしたいところ

と、3つのイベントのタイミングを用意しているみたい。 ただ、何も指定しなかった場合は具体的にどのタイミングで読み込まれるのだろう?という疑問点もあるので後々調べて追記予定。

4/16追記

どうやら上で書いたイベントのタイミングは大嘘だったようだ。。。 上で書いたのは今自分が触っているページにのみ起こる事象で、ページによってだいぶかわってくるみたい。

で、さっそくテストケースを用意して試してみた。


<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>gtm</title>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
</head>

<body>
<noscript><iframe src="//www.googletagmanager.com/ns.html?id=GTM-XXXX"
height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
<script>
window.addEventListener('DOMContentLoaded', function () {
    console.log('window.DOMContentLoaded', new Date().getTime(), document.getElementById('elem'));
});
document.addEventListener('DOMContentLoaded', function () {
    console.log('document.DOMContentLoaded', new Date().getTime(), document.getElementById('elem'));
});

$(function () {
    console.log('$(document).ready()', new Date().getTime(), document.getElementById('elem'));
});


window.addEventListener('load', function () {
    console.log('onload', new Date().getTime(), document.getElementById('elem'));
});

(function(w,d,s,l,i){

    w[l]= w[l] || [];
    w[l].push({
        'gtm.start': new Date().getTime(),
        event:'gtm.js'
    });

    var f=d.getElementsByTagName(s)[0],
        j=d.createElement(s),
        dl= l!='dataLayer' ? '&l=' + l : '' ;
        j.async=true;
        j.src = '//www.googletagmanager.com/gtm.js?id=' + i + dl;

    console.log(w[l], new Date().getTime(), document.getElementById('elem'));

    f.parentNode.insertBefore(j,f);

})(window, document, 'script', 'dataLayer', 'GTM-XXXX');

</script>

<a id="elem" href="#">test</a>
<div></div>
<div></div>
.
.
.
<!-- divの繰り返し -->
.
.
<div></div>
<div></div>
</body>
</html>

GTM上で前述の3つの各タイミング + 無指定(=ここではunknownと表示)でnew Date().getTime()でミリ秒を取得し、 console.logを実行してどういったタイミングでGTMのイベントが行われているのかを厳密に調べてみた。

Chrome

f:id:yotanote123:20130416212709j:plain

IE9

f:id:yotanote123:20130416213147j:plain

環境によってかなりの差があることがわかる。 このあたりはブラウザのAsynchronous Loadingが密接に関係しているのだろう。 でも、結果を見て思うのはgtm.domでDOMは確実に用意されているので、DOMが必要な処理の際はgtm.domで実行するのがいいのかも。

参考 CSS/JavaScriptのAsynchronous Loadingをめぐる熱い論議

上記ソースコード<!-- divの繰り返し -->の部分にリソースのロードを挟んでいたらまた挙動が変わってくるのだろう。 またそっちも後日テスト予定。

GoogleAnalyticsとの併用について

やっと本題です。

GTM使うとなると更に組み合わせたくなるのがGoogleAnalytics(=以下GA)。 これで現在推奨されている第三世代のコードを掛け合わせると、またまた厄介なことになる。

コードを見てみよう。

  var _gaq = _gaq || [];
  _gaq.push(['_setAccount', 'UA-XXXXX-X']);
  _gaq.push(['_trackPageview']);

  (function() {
    var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
    ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
    var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
  })();

そう、async属性を指定している

さっき紹介したGTMの3つのタイミング + ga.jsのロード時間も考慮しなくちゃならない。 何が怖いって、さっきの3つのタイミングでga.jsが確実にロードしている保証なんてどこにもないのである

カスタム変数の値を見て、その値を元にページ内のとあるa要素を取得してそこにトラックイベントを飛ばすイベントハンドラをセットしたいなんて場合、 DOMContentLoadedのタイミングとga.jsのロードのタイミングが等価である保証は絶対にない。

まとめ

  • gtm.jsとga.jsを併用した場合、 windowまたはdocumentのロード状態とga.jsのロードのタイミングが等価である保証は絶対にないので、 GTMの設定は適切なタイミングを管理画面から設定して、ga.jsの読み込みを確認しながらscript書こう

  • ページのnodeの数や閲覧ブラウザや通信状況などによってga.jsのロードタイミングもかわってくるよ

  • 解析士が開発しないなんて言う時代は終わったと声を大にして言いたい(自分は解析士じゃないので戯言)

おわり