まめーじぇんと@Tech

技術ネタに関して (Android, GAE, Angular). Twitter: @mame01122

Javascriptでデザインパターン (その1: Singleton)

きっかけ

現状、"prototypeとか即時関数とか、そーいう決まりは分かったけど、じゃあこれって具体的にどんなケースで使うの?どううれしいの?"が分からないので
Javaで言えば、interfaceとabstract classの使い分けタイミングとか、そもそもinterfaceってどんなときに使えばうれしいの?が分からなかった)、
ひとまずJavascriptでのデザインパターンを勉強してみようということになりました。

ということで、まずは一番シンプルでとっつきやすいSingletonパターンから。

そもそもシングルトンパターンって?

”そのクラスのインスタンスが1つしか生成されないことを保証することができる。 ロケールやLook&Feelなど、絶対にアプリケーション全体で統一しなければならない仕組みの実装に使用される。(wikipediaより)”ですが、実際に使うときは、
・そもそもステート(=メンバ変数)を持たないこと(ステートを持ったら、それはただのグローバル変数ですね)
インスタンスを1つしか持たない必然性があること(ウェブサイトによっては、”ポリモーフィズムが絡むこと”、という記述もありましたが、僕としてはもう少し抽象的な”必然性”という言葉の方がしっくりきます。ポリモーフィズムが絡まなくても、HTTPへのアクセスやDBのアクセスなど、ここは必要だよね、という部分はありそうな気がするので)
かと思います。ので、Javascript的に言えば、
インスタンス複数作られないこと
・メンバ変数を外からいじられないこと
の2つが要件になりそうです。

ということで、実際のコード

(お勉強のため、回りくどいことをしています。ご了承を・・・)

まずはダメコードから。オブジェクトリテラルで(それっぽいものを)書いたものです。

//Use Object literal
var singleton_object_literal = {
	id: 1,
	name: "test"
};

ObjectLiteral = function(){
	console.log("ObjectLiteral");
	console.log(singleton_object_literal.id);		// 1
	console.log(singleton_object_literal.name);	// test

	singleton_object_literal.id = 2;

	console.log(singleton_object_literal.id);		// 2
};

ObjectLiteral();

変数がすべてグローバル変数なので、idの値が外から書き換えられてしまいます。
Java的にいえば、このidは"public int id = 1"という感じですかね。

続いて、クロージャを使った(それっぽい)パターン

// Use Closure
var singleton_closure = (function(){
	return {
		id : 1,
		name : "test",

		getId : function(){
			return this.id;
		},

		getName : function(){
			return this.name;
		}
	};
}());

closure = function(){
	console.log("Closure");
	console.log(singleton_closure.getId());		// 1
	console.log(singleton_closure.getName());	// test
	singleton_closure.id = 2;
	console.log(singleton_closure.getId());		// 2
};

closure();

これも、idの値が外から書き換えられてしまうので、NGです。
ということで、クロージャを使いつつ、変数をPrivateで持ったケースです。

// Use Private value
var singleton_private = (function(){
	 var id = 1;
	 var name = "test";
	 return {
	 	getId : function(){
	 		return id;
	 	},

	 	getName : function(){
	 		return name;
	 	}
	 };
})();

privateValue = function(){
	console.log("PrivateValue");
	console.log(singleton_private.getId());	//1
	console.log(singleton_private.getName());	//test

	singleton_private.id = 3;
	console.log(singleton_private.getId());	//1

	// var tmp = new singleton_private();
};

privateValue();

これなら、idの値を外から書き換えられないですね。

そして、(これが一番正当派?)getInstanceでインスタンスを取得するパターンです(だんだんと複雑になってきますが・・・)

// Use constructor
var singleton_constructor = (function(){
	var instance;

	function createInstance(){
		var id = 1;
		var name = "test";

		return {
		 	getId : function(){
		 		return id;
		 	},

		 	getName : function(){
		 		return name;
		 	}
		};

	}

	return {
		getInstance : function(){
			if(!instance){
				instance =  createInstance();
			}
			return instance;
		}
	};

}());

constructor = function(){
	console.log("Constructor");
	console.log(singleton_constructor.getInstance().getId());	//1
	console.log(singleton_constructor.getInstance().getName());	//test

	singleton_constructor.getInstance().id = 2;
	console.log(singleton_constructor.getInstance().getId());	//1

	console.log(singleton_constructor.id);	//undefined

};

constructor();

デザインパターンを勉強すると、言語の仕様とか特性とか、勉強になります。

Javascriptのイコール2つと3つの違い (派生してオブジェクトとプリミティブ型)

そもそも

もろJavascript初心者な感じですが、ソースコードを読んでいて、if文などで比較をするときにイコール2つと3つのケースがあって、何がどう違うんだ?とモヤモヤしたので記載。

Javascriptエキスパートな方は読み飛ばしてくださいませ。

簡単にいうと?

・イコール2つ: 型変換を行った上での比較
・イコール3つ: 型の違いも含めた上での比較
・・・だそうです。

ので、

var x = 1;
var y = "1";

if(x == y) // false、
if(x === y) // true

となりますね。

これがある理由?

・・・という上記の内容が分かったところで、「じゃあなんでこの2つの比較方法があるの?」「実際にどんなケースで使われることを想定しているの?」というところが気になり調べてみました。どうやら、オブジェクトとプリミティブ型があるため、この比較方法があるようです

オブジェクトとプリミティブ型の違い

こちらも簡単に書くと、
・オブジェクト: 値のほかに、さまざまなプロパティやメソッドを持つことができる
・プリミティブ型: 値のみを持ち、プロパティやメソッドを持つことができない
という違いです。
※ただ、プリミティブ型.lengthのような、"実は内部でオブジェクト作ってます"みたいなこともあるようですが・・・。

そして、オブジェクトは参照渡し、プリミティブ型は値渡しとなっています(このあたりはJavaと同じですね)ので、オブジェクト型の変数には値がそのまま入っているのではなく、その値に対してのアドレスが保存されます。

ので、オブジェクト型を別の変数に入れた後にそれを書きかえると、元のオブジェクトの値も変わってしまった・・・というような、”あるある”な不具合も起きそうです (そしてJavascriptで不変オブジェクトを実現することもできるようですが、こちらは何か大変そう(笑)なので次回以降・・・)

じゃあ何故オブジェクトとプリミティブ型がある?

こちらもいろいろと調べてみましたが、まず間違いなさそうなところとしては、
”プリミティブ型はメモリの節約になる”点があるようです(とはいえ、オブジェクト型に余計なメソッドを持たせなければ問題ないのでは?という気もしており、ここは再度調べてみます・・・)

そして、パフォーマンスもプリミティブ型の方が早いようです。そりぁあ、値を直接取得するのと、アドレスから値を取得しに行くのでは、値を直接参照する方が早いのは想像ができます。

ということで、オブジェクト型とプリミティブ型の両方が必要なので・・・

それらを比較するためのイコール2つとイコール3つが存在するようです(半ば強引に結論に持って行きました・・・)

今回の話(特に後半)、書きながらいろいろと「ん?」と思うところもあったので、今後何か分かったら加筆/修正したいと思います。

以上、Javascriptのオブジェクトとプリミティブ型の違いと、そこに紐づくイコール2つ / 3つの話でした!

AngularJSのソースコードを読んでみた(その1: 起動シーケンス編)

そもそも

AngularJSを使いつつ、”これってどんな感じで動いているんだろう・・・”というのが気になったので、AngularJSのソースコードをざっと眺めてみました。が、僕は特にJavascriptが得意なわけではない上にそこまでしっかり時間をかけられていないので、間違いが含まれている可能性があります(あらかじめ言い訳・・・)

今回の目標

まずはライブラリ(angular.js)がざっくり、どんなシーケンスで動き始めるかを調べてみました。対象としたのは、この時点での最新の1.4.2です。

Entry point

まずは、angular.jsの一番下あたり、行数でいえば28356行目のbindJQuery()から始まります。このメソッド内でJQueryを使う設定をしています。その後、jquery準備完了のコールバックが返ってきたら、angularの初期化が始まります。コードはこんな感じ。

  jqLite(document).ready(function() {
    angularInit(document, bootstrap);
  });

このangularInitのメソッドでは、ng-appの設定をしています。どうやら、複数のng-appがある場合は、一番最初に見つかったng-ppをroot elementとするようです。

また、bindJQueryからコールバックが返ってくるまでの間に、publishExternalAPI()メソッドが呼ばれています。このメソッドの中で、AngularJS内で共通で使われるメソッドの定義(isFunctionとかisObjectとか)、各Directiveの定義(ngInitDirectiveなど、ng-xxxをはじめ、HTMLのa(アンカー)やformなども含む)、各Providerの定義($logや$httpなども、実はすべてそれ用のProviderがあって、それぞれ$LogProvider、$HttpProviderと呼ぶようです)などを行っています。行数で言うと、2361行目以降です。
コードを抜粋すると、こんな感じ。

function publishExternalAPI(angular) {
  extend(angular, {
  //メソッドの定義
    'bootstrap': bootstrap,
    'copy': copy,
	.
	.
  });
  //Directiveの定義
  $provide.provider('$compile', $CompileProvider).
    directive({
        a: htmlAnchorDirective,
        input: inputDirective,
        .
        .
    }).
    //Providerの定義
    $provide.provider({
      $anchorScroll: $AnchorScrollProvider,
      $animate: $AnimateProvider,
      .
      .
      });
    }
  ]);
}



それぞれのメソッド、Directive, Providerの定義は、このJavascriptファイルの各地に点在しています。例えば上で出てきたhtmlAnchorDirectiveであれば19390行目あたりにあります。

また、このメソッドからはsetupModuleLoader()というメソッドも呼ばれているのですが、この中でProvider, Factory, Serviceがいつ設定されるか?を決めています。runとconfigのフェーズで設定できる/できないの違いって、ここから生まれているようです。

その他、Angular的なもの

AngularJSって、何か問題が起こったときに結構親切にエラーを吐いてくれると思うのですが、その実際の処理言は、minErr()というメソッドの中でいろいろやっているようです。これはangular.jsの一番上、38行目あたりにあります。
"DirectiveはCamel caseで"という謎の縛り。これはdirectiveNormalize()というメソッドがあり(8769行目あたり)、ここから呼ばれるcamelCaseというメソッド内で行われています。

ということで

もう少し、時間を見つけていろいろと読み進めてみたいと思います。$applyや$digestのあたりも、読んでると(イマイチ理解しきれていないにしても)”なるほどねー。”というところもあり、なかなか面白いものです。

Angularticsでイベントがトラッキングされない

Angularticsについて

AngularJSのライブラリで、Google Analyticsを始めとした各種トラッキングサービスを使うためのAngulatics (
http://luisfarzati.github.io/angulartics/
)というライブラリがあり、非常に便利です。

導入方法

導入も非常に簡単で、ページ遷移なども、下記の本家サイトに従えばサクサク対応できるかと思います。luisfarzati.github.io
※このサイトにもあるように、

ga('send', 'pageview');

を削除するところだけは要・注意でしょうか。

何故かイベントがトラックできない

ただ、ボタンやリンクをクリックしたときのイベントのトラッキングが、本家に載っている下記のコードだと動きません(2015年7月21日時点)

<a href="file.pdf" analytics-on="click" analytics-event="Download">Download</a>

何でかなー、と探しまわることしばし。
どうやら、ライブラリの仕様が変わったようです。。。(本家のドキュメントがそれに追従していないとはどういうことだ。。。)

下記の履歴によれば、0.17.0になったときに、categoryも一緒に送らないといけない仕様になったようです。
https://github.com/luisfarzati/angulartics/commit/4b7c60fc831948a1e64ca7fffbc2b26ef7199941

"Google Analytics - do nothing if there is no event category (required)"って、書いてありますね。(しれっと。。。)AngularJSのライブラリは、日本語の情報が少ないので、こーいうところでハマります。僕が今回気づいたのも、英語版Stack overflowでした。下記リンク。
javascript - Angulartics GA events not getting tracked - Stack Overflow

ということで、こうすれば動く。

こんな感じ。

<a href="file.pdf" analytics-on="click" analytics-category="overview" analytics-event="Download">Download</a>

ちなみにGoogle Analyticsの場合はリアルタイムでは取得できないので(他のサービスは知りませんが・・・)、データの更新は、数時間〜1日ほど待ってからアクセスしてみてください。

以上、Angularticsでイベントをトラッキングする方法でした!

AngularJSでHTML側(特にng-repeat内)からService/Factoryを使う方法

今回は、AngularJSでHTML側(特にng-repeat内)からService/Factoryを使う方法のシェア。

ユースケースは?

Controllerで持っているデータをng-repeatでまわしながらHTMLに表示したい!
 そして表示する文字列の背景の色を、その文字列に応じて動的に変えたい!
 しかもその動的に変更するロジックが1つのControllerに閉じないので、ServiceやFactoryに切り出したい!
  ・・・というケース。

つまり"あ"であれば赤背景、"い"であれば青背景的なのを、複数のControllerから使えるServiceを作ろうというわけです。

もちろんHTML側で頑張ることもできるんですが、これはAngular的にはアンチパターン
これをやろうとすると、HTML側でロジックをもつ必要があるので。

どうする?

Controllerで読み込んだServiceやFactoryをそのまま$scopeに渡してしまいます。

つまり、例えばBackgroundColorGenerateServiceという色を変えるServiceがあるとすると、
Controller側で下記のようにInjectします。

myApp.controller('MainController',
 ['$scope', 'BackgroundColorGenerateService',
 function($scope, BackgroundColorGenerateService){
 	$scope.colorGenerator = BackgroundColorGenerateService;
}]);

的な。

あとはこれをHTML側で、ng-styleと一緒に利用します。

<ul ng-repeat="str in strings">
    <li>
    <div ng-style="{'background': colorGenerator.generateColor(str)}">str</div>
    </li>
</ul>

※generateColorはBackgroundColorGenerateServiceに定義されたメソッドだと思ってください。

これで、HTML側でロジックを持つことなく、HTML側でServiceを使うことができます。

以上、AngularJSでHTML側(特にng-repeat内から)Serviceを使う方法でした!

How to make Google Crawler crawl/index your Ajax (AngularJS) based web application

Let Google Crawler index your Ajax-base web application

Recently, I have tried to find out a solution to make the Google crawler to crawl and index my SPA (Single Page Application) based on Angular JS. And finally it has indexed my web application today!. Then, I'd like to share how I let it index my web application.

I known there are bunch of web sites for SEO (Search Engine Optiomization), but for Ajax, there are quite few. Then, I hope this article would help your person who has trouble for crawler / index for Ajax-based application.

What is minimum way to let the Google crawler index your application?

As you might know, there are a lot of tasks and options for SEO. Such as:

robots.txt
Site map
URL parameter
Structure data
Data highligher
HTML5Mode vs Hashbang

And so on..

And I had struggled to get to know what is mandatory and what is optional. And I came to my goal now. It seems that minimum work to index your application is:

Site map.

That's it. For others, they are optional items. Not needed.

And you can find description sitemap is mandatory for index. But I think it is difficult to recognize...
https://developers.google.com/webmasters/ajax-crawling/docs/specification

In order to crawl your site's URLs, a crawler must be able to find them"

(Neither bold font nor red font highlight.)

And some web page that describe SEO say that robots.txt is mandatory. But as you can see below, robotx.txt is not a mandatory item.
https://support.google.com/webmasters/answer/6062608?hl=ja

You only need a robots.txt file if your site includes content that you don't want Google or other search engines to index.

How to prepare sitemap.xml

And then, here is how to prepare sitemap.xml. Format is like this:

<url>
	<loc>https://xxx.yyy.com/#!/</loc>
	<lastmod>2015-06-18</lastmod>
	<priority>1.0</priority>
	<changefreq>daily</changefreq>
</url>

You have to write for each page you have.
And you can refer to other web sites if you want to get to know more details. And this can be generate many free services (If your application is NOT Ajax-based application)

But...

I don't know how I write Ajax-based (AngularJS) sitemap.xml...
If I use auto generate service for my Ajax-base application (which contains # and !), it shows an error...

I had to manage these problems.

Then, I'd like show you my sitemap.xmlformat. It's like this:
sitemap_mobile.xml

<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
	xmlns:mobile="http://www.google.com/schemas/sitemap-mobile/1.0">
	<url>
		<loc>https://xxx.yyy.com/#!/</loc>
		<lastmod>2015-06-18</lastmod>
		<priority>1.0</priority>
		<changefreq>daily</changefreq>
	</url>
	<url>
		<loc>https://xxx.yyy.com/#!/signin</loc>
		<lastmod>2015-06-18</lastmod>
		<priority>1.0</priority>
		<changefreq>daily</changefreq>
	</url>
</urlset>

sitemap_pc.xml

<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9
            http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd">
	<url>
		<loc>https://xxx.yyy.com/#!/</loc>
		<lastmod>2015-06-18</lastmod>
		<priority>1.0</priority>
		<changefreq>daily</changefreq>
	</url>
	<url>
		<loc>https://xxx.yyy.com/#!/signin</loc>
		<lastmod>2015-06-18</lastmod>
		<priority>1.0</priority>
		<changefreq>daily</changefreq>
	</url>
</urlset>

As you already might know, Google announced that they prioritize mobile web site and I'd recommend to create sitemap.xml for mobile.

Once you create this xml file, you deploy it to your service and teach its URL to Google Web master tool (Of course. you can teach it from Web master tool)

In my case, my web application was indexed about one day. You can check if it is indexed by changing "http" or "https" part to "site". Like this: "site://xxx.yyy.com/#!/" If your application is indexed. indexed page list shall be shown up.

And "#" and "!" part depends on your settings. It could vary.

Now, you have finished all minimum preparation to let Crawler index your web application!

Ajax(AngularJS)ベースのウェブサービスをクロール/インデックスしてもらう方法

Ajaxベースのサイトをインデックスしてもらおう!

ここ最近、GoogleのSpiderにAngularJSで作ったSPA (Single Page Application)をクロール、インデックスに登録(=Google検索にひっかかるようになる)してもら方法を模索しており、ようやく今日、Google検索でひっかかるようになったのでそのやり方を記載します。

SEO対策のページはたくさんあるんですが、それがAjax用となると途端に少なくなるのは(特に日本語は見つからない・・・)僕だけでしょうか。ということで、少しでもAjaxのサービスを作っていてクロールに困っている人の助けになれば。

Google検索でひっかかるようにするために必要な、最低限のモノって何?

SEOクローラー対策で、いろいろなオプションややるべきことがあることはわかるんです。
ざっと調べただけでも、

robots.txt
サイトマップ
URLパラメータ
構造化データ
データハイライター
HTML5Mode vs Hashbang

などなど。。。

もーーーーどれ必須でどれが任意項目や!!と散々探し回って悩みまくった挙句、必要なのは、
サイトマップ
のみのようです。

他はなくても何とかなる、ようです(違ったらゴメンなさい。)

下記のFull Specificationにしっかり書いてありました(が、なかなか気づかないですよね、コレ。。)
https://developers.google.com/webmasters/ajax-crawling/docs/specification

In order to crawl your site's URLs, a crawler must be able to find them"

(特にbold体や赤文字でハイライトされることもなく、さらっと。。。)

ちなみにウェブページによってはrobots.txtが必須だ!的なことを書いているものもありましたが、
下記にある通り、robotx.txtはなくてもよいようです。

https://support.google.com/webmasters/answer/6062608?hl=ja

robots.txt ファイルが必要になるのは、Google などの検索エンジンのインデックスに登録したくないコンテンツがある場合のみです。

sitemap.xmlの書き方

・ということでsitemap.xmlの書き方ですが、フォーマットはこんな感じ。

<url>
	<loc>https://xxx.yyy.com/#!/</loc>
	<lastmod>2015-06-18</lastmod>
	<priority>1.0</priority>
	<changefreq>daily</changefreq>
</url>

これを、各ページ毎に記載します。
ただ、このあたりの情報は他のサイトを見ればわかるんです。そして、このサイトマップは、(Ajaxベースでないサイトであれば)いろいろな自動作成サイトもあるので、そこで作ることもできます。

が。

・Ajax (AngularJS)ベースのサイトマップの書き方がよーわからん。。
・自動作成サイトにAjaxベースの (#やら!を含んだ)URLを入れるとエラー吐く。。

と、ここでも落とし穴が。。

ということで、僕が使っているフォーマットを共有します。こんな感じ。
sitemap_mobile.xml

<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
	xmlns:mobile="http://www.google.com/schemas/sitemap-mobile/1.0">
	<url>
		<loc>https://xxx.yyy.com/#!/</loc>
		<lastmod>2015-06-18</lastmod>
		<priority>1.0</priority>
		<changefreq>daily</changefreq>
	</url>
	<url>
		<loc>https://xxx.yyy.com/#!/signin</loc>
		<lastmod>2015-06-18</lastmod>
		<priority>1.0</priority>
		<changefreq>daily</changefreq>
	</url>
</urlset>

sitemap_pc.xml

<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9
            http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd">
	<url>
		<loc>https://xxx.yyy.com/#!/</loc>
		<lastmod>2015-06-18</lastmod>
		<priority>1.0</priority>
		<changefreq>daily</changefreq>
	</url>
	<url>
		<loc>https://xxx.yyy.com/#!/signin</loc>
		<lastmod>2015-06-18</lastmod>
		<priority>1.0</priority>
		<changefreq>daily</changefreq>
	</url>
</urlset>

Googleがモバイルファーストの方向を打ち出しているので、モバイル用のサイトマップも作っておくことをオススメします。

後はこれをお持ちのサービスにデプロイし、このxmlのURLをGoogleウェブマスターツールに教えてあげればOK。(もちろんウェブマスターツールから可能です)

僕の場合、上記の作業から1日ほどでインデックスされました。インデックスされたかどうかは、ブラウザのアドレスバー上でhttps://xxx.yyy.com/#!/httpsの部分をsiteに変えれば(つまりsite://xxx.yyy.com/#!/にすれば)確認可能です。インデックスされていれば、インデックスされたページ一覧が表示されます。

ちなみに僕の場合hashbangモードなのでURLに#!が入っていますが、そこは設定次第です。

これで最低限の対応は完了です!

以上、Ajax(AngularJS)ベースのウェブサービスをクロール/インデックスしてもらう方法でした!