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変態!(言いたいだけ

グローバルナビのカレント処理を静的にexpress + jadeでやってみた

express使ってみよー!という思いつきからやってみた。

導入

まずはデモを作るのにherokuにNode.js乗せましょうということで、 こちらの記事を参考にさせて頂きました。

Node.js + Express を Heroku で動かすまでの手順まとめ

上記を読めばラクラクインストール!なんて上手くいくわけなかった。 色々調べてみるとWin環境だと皆さん同じ所ではまっているみたいで、 何度か心が折れかけましたがエラーメッセージ追っかけていってうまくいった。

ではさっそくexpressをいじり倒す。

ぱぱっとできたのがこちら。ついでにTwitterBootstrapも入れてみた。

ソース

https://github.com/yotanote/express_test

デモ

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

解説

  • routes/index.js
/*
 * GET page.
 */

var TITLE = 'hello world';
var navData = [{
    path : '/',
    str  : 'Entrance',
}, {
    path : '/lounge/',
    str  : 'Lounge',
}, {
    path : '/studio/',
    str  : 'Studio',
}, {
    path : '/editroom/',
    str  : 'Edit Room',
}, {
    path : '/gear/',
    str : 'Gear',
}];

exports.entrance = function(req, res){
    res.render('entrance', {
        title : TITLE,
        currentNav : 0,
        navData : navData
    });
};

exports.lounge = function(req, res){
  res.render('lounge', {
        title : TITLE,
        currentNav : 1,
        navData : navData
    });
};

exports.studio = function(req, res){
  res.render('studio', {
        title : TITLE,
        currentNav : 2,
        navData : navData
    });
};
exports.editroom = function(req, res){
  res.render('editroom', {
        title : TITLE,
        currentNav : 3,
        navData : navData
    });
};
exports.gear = function(req, res){
  res.render('gear', {
        title : TITLE,
        currentNav : 4,
        navData : navData
    });
};


  • views/layout.jade

ページ構造の共通テンプレート

!!! 5
html(lang="ja")
  head
    block head
    meta(charset='UTF-8')
    title= title
    link(rel='stylesheet', href='/css/bootstrap.css')
    link(rel='stylesheet', href='/css/custom.css')
  body(data-spy="scroll", data-target=".bs-docs-sidebar")

block header
  .navbar.navbar-inverse
    .navbar-inner
      .container
        a.brand(href="./index.html")
        .nav-collapse.collapse
          ul.nav
            - navData.forEach(function (o, i) {
              - if (i == currentNav)
                li.active
                  a(href=o.path) #{o.str}
              - else
                li
                  a(href=o.path) #{o.str}
            - })

block main-visual

block content


  • views/entrance.jade

上記のlayout.jadeの継承先

block main-visual
  .jumbotron.masthead
    .container
      h1#logo= title
      p #{title}

block content
  div.location-bar
    p Entrance


馴染みがあるメソッド使える&JSで色々できるからJSerとしてはなかなか便利!

デモを見るとヘッダーのナビが遷移するごとにカレントの表示が変わっていると思います。 この処理は今現在どこにいるのかをroutes/index.js内のcurrentNavプロパティに持たせ、 その値をjade側に渡し、views/layout.jadeのforEachで取得し対象の値に対しカレントを付与しています。

普段クライアントサイド側のJavaScriptを弄ってる自分としてはHTMLパース前にJSが動くこと自体が新鮮!

Railsみたいな感じですが所感としては変数持たせるところはRailsより簡潔な感じがしましたね。 単純にRailsに比べてファイルの数が少ないというのもありますが。。。 ejsからjadeに変わったということもあり、なかなかに変態度(謎)が増しています。

デモのHTMLソース見るとワンライナーになっていて見難いので後々直さなくちゃですね。

まとめ

  • express + jadeで楽にモジュールシステムとか作れちゃう
  • heroku使うときはMac環境じゃないと心が折れる

これを期にexpressをベースにアプリをガツガツ作ろう!

GoogleAnalyticsのイベントトラッキングで盛大にはまった

タイトル通り盛大にはまりました。 まさかまさかこんな事があろうとは、 たかがひとつのイベントトラッキングではまるとは。。。

みなさんご存知、GoogleAnalyticsを使用時でユーザーイベントをトラッキングしたい場合は

<!-- ga.jsを読み込んでる想定 -->

<a id="test" href="http://test.com">外部サイトへ</a>
<script>
(function () {
    var anc = document.getElementById('test');

    anc.addEventListener('click', function () {
        _gaq.push(['_trackEvent', 'category', 'act']);
    }, false);
}());
</script>

大体こんなコードになりますよね。

でもこれ実はブラウザによっては動くかわからない。

え?なんで?と大はまり。 色々調べたのですがどうやらページ遷移の際にunloadが早く走ってしまうからみたいです。

管理画面上に正確なデータを表示するためには、 ビーコンと呼ばれる1px x 1pxのgifを送信して初めてtrackEventを正常に計測されます。 これをビーコンと呼びます。 がしかし、ブラウザによってはこのビーコンが送られる前にunloadが走ってしまい、 計測が正常に行われなケースがあるのです。特に顕著なのはFireFox

Googleではこの問題に対する回答が下記で発表されています。

https://support.google.com/analytics/answer/1136920?hl=en

ほうほう、こうすれば解決できるのね!と早速試してみた。

・・・ 先生、問題回避できません!!!

コードを覗いてみると、どうやら遅延を意図的に発生させて回避させている模様。 これだとIEやChrome等は回避出来てもFireFoxは回避出来なかった。

うーんうーんと四苦八苦したところ、あまいいい方法とは言えませんが、 FireFoxの問題はこうすれば完全に回避できる事が判明。

<script>
(function () {
    var anc = document.getElementById('test');
    var flgEmulate = false;

    anc.addEventListener('click', function (e) {
        var elm;

        if (flgEmulate) {
            flgEmulate = false;
            return;
        }

        elm = e.target;

        while (elm.tagName !== 'A') {
            elm = elm.parentNode;
        }

        e.preventDefault();

        setTimeout(function () {
            var evnt = document.createEvent('MouseEvents'); 
            
            evnt.initEvent('click', false, true);

            flgEmulate = true;

            elm.dispatchEvent(evnt);        
        }, 100);    
    }, false);
}());
</script>

うん、我ながら冗長なコードですね、はい。

内容は、clickを一時的に無効化し、擬似的にエミュレートしたclickイベントを発生させてからunloadを発生させるというもの。

※ただし、clickを擬似的に発生させることにより、ページの既存JavaScriptとの競合なども起こりうるのでご利用は計画的に!

※ほかに何かいい方法がをご存知の方がいらしたら教えていただきたいです。

なにはともあれ動いてすっきり。

追記 7/19 変数名間違っていたのでコード修正しました。

気になるJavaScriptパッケージ&ツール集

また多忙で放置してしまっていましたが、いってみましょう。

  • jsdiff

https://github.com/kpdecker/jsdiff

これは便利!テキストの差分を文字/単語/行ごとに確認できます。
落としてきてローカルで確認すると差分情報やマージまでできてしまう優れもの!
ライセンスもBSDと割と緩いのでカスタマイズの用途があるのでは?
私は仕事で早速使わせてもらっています。


  • wtcss

https://github.com/benfoxall/wtcss

ドキュメントがシンプルすぎてなんのツールだかわからない状態ですが、
これはphantom.jsというブラウザを仮想上で復元してくれるライブラリを更にカスタマイズし、
対象サイトのキャプチャを撮り、そのページのどこにどういうCSSが効いているかを視覚的に表示してくれるというもの。

demo http://css.benjaminbenben.com/

行ったwebページのa要素を判別してクリックしたり、
アクセスするヘッドレスブラウザのウィンドウサイズを指定して、
レスポンシブウェブデザインを採用したサイトにアクセスしてキャプチャ撮ってきたり(casper.js)と使い方は様々。

アイデア次第では発展させたらすごく便利なツールが作れそうですね。