「JavaScript」と一致するもの

AngularJSの流れにちゃんとついていくためng-europeをyoutubeで見ようと思い始めて一ヶ月が過ぎてしまいました。 まだ全部は見ていませんが、今日は一本見ました。興味深かったので記事にします。

https://www.youtube.com/watch?v=tfVA1syv-3o

AngularUIのui-bootstrapのお話です。 単にラップするわけではなくAngular流に作られているのと、カスタマイズ性を重視しているあたりは参考になります。

ui-bootstrapはBootstrap 3のcssのみを利用していてjavascriptの部分は全部書き直しているようです。つまり、jQuery依存もなしです。

ちょっと昔話を。当初、Bootstrap 2を使っていたのですが、クラス名などがどうも好きになれず、その後inkというcssフレームワークを見つけて 、しばらく使っていました。

その時から、inkのcssのみを使ってコーディングしていました。ロジックが必要な場合はAngular側で自前で作ってました。 例えば、カレンダーも当初はAngularUIのui-calendarを使っていましたが、 jQuery依存に抵抗があったため、カレンダーも単純な機能ならinkで自前で実装していました。

その後、Bootstrap 3が登場し、クラス名がinkのようにシンプルになったので、少し気に入ってBootstrap 3を使い始めました。でも、cssだけで、jsは使いません。例えば、alertもロジックはangularで自前で書いていました。と言っても、あまりコーディング量は多くなく、特に困っていません。

さて、話を戻しますと、ui-bootstrapは上記でやっていたようなことをちゃんとやっているような印象です。cssだけを使って、ロジックはangular用にdirectiveを設計してあるようです。 alertを見る限りでは単純なので自前で用意しても変わりませんが、他の複雑な機能はui-bootstrapのdirectiveを使うとだいぶ楽できそうです。

というわけで、jQuery非依存のui-bootstrapはおすすめです。

ProtractorでAngularアプリのE2Eテストやってみた、PhantomJS編に引き続き、 Protractorでwindow.alertをテストする方法を調べたので書いておきます。

E2Eテストを書くときに、alertの文字列を調べたいことがあると思います。 また、confirmでOKしたりCancelしたりする場合の挙動もテストできるとよいでしょう。

ちゃんとできるようです。Protractorというよりその内部のWebDriverがすごいということですね。

http://stackoverflow.com/questions/19879631/protractor-get-text-of-an-alert

に載っていました。

https://github.com/angular/protractor/commit/196c83a9d728e40fecd7a9f206bb985533550fde

が分かりやすいです。ちなみに、現時点でのHEADではnavigation_spec.jsに載っています。これによると、

var alertDialog = browser.switchTo().alert();
expect(alertDialog.getText()).toEqual('Hello');
alertDialog.accept();

と書けばいいとのことです。confirmの場合も同様です。 confirmの場合でcancelするときは、accept()の代わりにdismiss()を使えばよいようです。

ところが、PhantomJSではこれは現時点では使えないことが分かりました。PhantomJSではGhostDriverという別のWebDriverを使っているようです。

https://github.com/detro/ghostdriver/issues/20

これが問題のIssueです。皆さんから切望されていますが、まだ実装されていないようです。しかし、workaroundが記載されていました。埋め込みJavaScriptでなんとかすることができるようです。サンプルを参考に自分でもやってみましたが、うまく行きました。

コードを貼り付けておきます。

describe('alert test spec', function() {
  var ALERT_HANDLER = '(function () { var lastAlert = null; window.alert = function (message) { lastAlert = message; }; window.confirm = function(message) { lastAlert = message; return true; }; window.getLastAlert = function () { var result = lastAlert; lastAlert = null; return result; }; }());';
  var ALERT_GETTER = 'return window.getLastAlert && window.getLastAlert();';

  it('should get register page', function() {
    browser.get('alerttest.html');
    browser.executeScript(ALERT_HANDLER);
    element(by.id('thebutton')).click();
  expect(browser.executeScript(ALERT_GETTER)).toContain('Hello');
  });
});

dismiss()相当を実現するのはちょっと工夫すればできそうです。accept()/dismiss()でタイミング制御をすることは難しそうです。

famo.usは描画エンジンです。 先日紹介したときに言及したデモは下記にありました。

こちら

このfamo.usをAngularと統合した人たちがいます。

http://famo.us/integrations/angular/

これがなかなかいいのです。famo.usはJavaScriptですべて書くのですが、ちょっと手続き的な感じになります。それをAngularのdirectiveに書けるようにするのです。ちゃんと、bindingも動くので、更新系がなんとも簡単なのです。Angularの経験があれば普通なので、特に驚きもないと言えばそれまでなのですが、これに慣れると、生のfamo.usを書くのは面倒くさく感じるようになるかもしれません。

というわけで、サンプルを作ってみました。 1つのHTMLにすべて詰め込みたかったので、single fileのfamous-angularを使いました。通常は、bowerでインストールする方法が紹介されています。

single fileのものはcdnにないので、http://rawgit.com/を使いました。ところで、rawgitはcdn版(beta)もあってすばらしいです。これは続いて欲しいものです。

さて、出来上がったサンプルは

こちら

です。ソースコードはgistにありますが、下記にも貼り付けます。

<html ng-app="MyApp">
<head>
  <title>famo.us angular sample</title>
  <link type="text/css" href="//code.famo.us/famous/0.2/famous.css" rel="stylesheet" />
  <link type="text/css" href="//cdn.rawgit.com/Famous/famous-angular/fee2b717a53ad762c9e3157580ce255901f4ccad/dist/famous-angular.min.css" rel="stylesheet" />
  <script type="text/javascript" src="//code.famo.us/lib/require.js"></script>
  <script type="text/javascript" src="//code.famo.us/famous/0.2/famous.js"></script>
  <script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.18/angular.min.js"></script>
  <script type="text/javascript" src="//cdn.rawgit.com/Famous/famous-angular/fee2b717a53ad762c9e3157580ce255901f4ccad/dist/famous-angular.min.js"></script>
  <script type="text/javascript">
angular.module('MyApp', ['famous.angular'])
.controller('MyController', ['$scope', function($scope) {
  $scope.items = [];
  for (var y = 0; y < 1; y += 0.1) {
    for (var x = 0; x < 1; x += 0.1) {
      $scope.items.push({x: x, y: y, color: 'yellow'});
    }
  }
}]);
  </script>
</head>
<body ng-controller="MyController">
  <fa-app>
    <fa-modifier ng-repeat="item in items" fa-origin="[item.x, item.y]">
      <fa-surface fa-size="[30, 20]" fa-click="item.color = 'red'" fa-background-color="item.color" fa-properties="{textAlign: 'center', lineHeight: '20px'}">{{$index}}</fa-surface>
    </fa-modifier>
  </fa-app>
</body>
</html>

Famo.us/Angularはまだ発展途上ですべての機能がdirective化されていませんが、今後に期待しています。

Famo.us Physicsの簡単なサンプル

  • 投稿日:
  • by

最近注目しているライブラリfamo.us、なにがいいってURLがいいです。

http://famo.us/

今年になってオープンソース化されたのですが、以前プレビュー版のデモを見たときは3次元でぐりぐり動くものだったのですが、正式リリースではモバイルのリッチアプリのデモになっていて、ある意味おとなしくなっていました。実用的ではあります。

famo.usは技術的にもDOMの階層を使わずに自力でレイアウトして、fpsを保証するという面白いものになっています。

もっと感心したのは、Famo.us Universityです。ステップを追ってfamo.usの使い方を学習できます。ACEエディタでその場でいじって動作を確認できるのもすばらしい。

さて、3次元のぐりぐりですが、どうやらfamo.us Physicsというサブライブラリを使うといろいろできるようです。Famo.us Universityのコースではcoming soonとなっています。が、待ちきれずに試してみました。

とにかく簡単に面白いものができないかと思って、作ってみました。 まずはご覧ください。

こちら

どうですか?水色の玉をクリックしてみましたか?まだの人はもう一度。

なんと、これは1つのHTMLファイルに書いています。もちろん中身はほとんどJavaScriptのコードですが。63行なのでブログに貼り付けてもいいのですが、 gistにアップしてあるのでそちらをご参照ください。

ソースはこちら

他にもconstraintのモジュールがあるので、いろいろできそうです。 ただ、ドキュメントはまだ少ないので、現状ではソースを読まざるを得ないことがありました。

しばらく楽しめそうです。

mongodbのaggregateがパワフルで便利

  • 投稿日:
  • by

mongodbはドキュメントベースのデータベースですが、APIがシンプルでよいです。これまで、find()とcount()で足りていたのですが、ちょっと複雑なことをやろうとするとクライアント側で処理するはめになってしまいました。

mongodbの集計機能にはいくつかの種類があります。

http://docs.mongodb.org/manual/core/aggregation/

によると、Aggregation Pipeline, Map-Reduce, Single Purpose Aggregation Operationsとあります。 最後のSingle Purpose Aggregation Operationsはまさにcount()のようなものなのですが、他にもdistinct()やgroup()もあります。SQLから類推すれば機能は分かりやすいでしょう。 Map-Reduceは並列処理にはいいでしょう。ただ、JavaScriptの関数を記述することになります。なんでもできるといえばなんでもできるのですが。 残るは、Aggregation Pipelineです。これは処理を逐次的に書くので分かりやすいのが利点ですが、注目したのはJSONで記述できることです。JavaScriptの関数と違ってシリアライズができるのがうれしい。Pipeline Operatorsというのがあって、そこそこ柔軟に書けます。

今回は詳しく述べませんが、参考までにパイプラインのサンプルを載せておきます。

var pipeline = [{
  $project: {
    record: '$records'
  }
}, {
  $unwind: '$record'
}, {
  $match: {
    'record.name': target_name
  }
}, {
  $group: {
    _id: 'all',
    count: {
      $sum: '$record.score'
    }
  }
}];

この$unwindがいいですね。ドキュメントを展開してくれるのです。

欲を言えば、パイプラインではなく、処理を分岐できる有効グラフを記述できたらさらに強力だったろうに。

さて、なぜJSONで記述したかったかというと、social-cms-backendで使いたかったからです。aggregateをサポートしてバージョンアップしました。

https://www.npmjs.org/package/social-cms-backend

iPhoneのWebアプリというのは「ホーム画面に追加する」でインストール、というかブックマーク、するものです。HTML5でもそこそこアプリっぽく作れます。

さて、HTMLのタグで、ズームなどは無効にできます。例えば、次のようにします。

<meta name="viewport" content="width=device-width initial-scale=1, user-scalable=no" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />

ところが、これだけだと、縦にはスクロールできてバウンスしてしまいます。コンテンツの高さがちょうどぴったりだとしてもです。記憶違いでなければ、Androidではそのようなことはなかったような。

検索したところ、stackoverflowがわんさかヒットしました。比較的よくまとまっているかなと思ったのは次の3つ。

http://stackoverflow.com/questions/9651215/how-to-disable-bouncing-in-html5-fullscreen-iphone-app

http://stackoverflow.com/questions/10357844/how-to-disable-rubber-band-in-ios-web-apps

http://stackoverflow.com/questions/7768269/ipad-safari-disable-scrolling-and-bounce-effect

結局、JavaScriptを使わないとできないようですね。fullscreenのサイズで作っておくと、Safariで表示したときは表示エリアが狭くなるのでスクロールが必要です。そこでちょっとだけ工夫をしました。参考までにコードを載せておきます。

document.addEventListener('touchmove', function(e) {
  if (window.innerHeight >= document.body.scrollHeight) {
    e.preventDefault();
  }
}, false);

コンテンツの内部でスクロールやドラッグをしたい場合はさらに工夫が必要かと思いますが、それはまた機会に考えることにします。

Railsより簡単にSNSを作れるようにすることを目指したライブラリ social-cms-backend を改良しました。

これまでは、認証機構はFacebook連携のみが実装されていたのですが、 Facebookに依存したくないアプリを作るケースも考え、 今回HTTP Digest認証も入れました。 Formによるパスワード認証も一応あったのですが、これは動作確認用で、not for production扱いです。 やはり生のパスワードがネット上を流れるのはよくないということで。 (SSLなしの想定)

Digest認証のサーバ側は passport-http を使いました。 Digest認証をそのまま使うのは不便なので、2つ工夫をしました。 一つは、RememberMeクッキーを併用することで、 もう一つは、JavaScriptでDigestを生成して認証することです。 Digest認証の最大の問題はUIがブラウザ依存だということです。 realmで指定する文字列しか指定できません。 また、ブラウザがパスワードを覚えてしまうので、アプリ側からログアウトできません。 そこで、DigestのヘッダーをJavaScriptで生成しました。 これは、完全な仕様通りではありません。細かい話は省きますが、 今回のサーバ実装ではたまたまnonceのサーバ側のチェックを省略していたのでできてしまったまでです。 ただ、もしチェックではじかれた場合でも通常のブラウザのDigest認証に fallbackするだけなので、リスクが増えるわけではないはずです。 逆に、パスワードを一回目で間違えた場合もfallbackしてしまいます。 このあたり、いい解決法がないようです。 (そもそもDigest認証の仕様に従わず、なんちゃってDigestにしてしまえばいいのかも)

Digest認証のクライアント側として request をunit testで使いました。使おうとしてから気づいたのですが、 Digest認証の実装が不十分だったので、直してPull Requestしておきました。 無事マージされました。

social-cms-backendはangular.jsでTwitterっぽいアプリを作るのに 便利なのですが、ちょっと使い方にくせがあります。 もっとチュートリアルを増やさないと使えるレベルにならないかも。

JavaScriptの正規表現のパフォーマンスに興味があったので調べてみました。 現時点での考察であり、JavaScriptの処理系による違いもありますし、将来的に最適化が進んだ場合は結果が変わることがあると思いますので、ご注意を。

まず、初めに見つけた記事が、これ。

http://stackoverflow.com/questions/9750338/dynamic-vs-inline-regexp-performance-in-javascript

簡単に言うと、

/[a-z]/.exec(str);

と

new RegExp('[a-z]').exec(str);

で、パフォーマンスが違うけど何で?という質問でした。 結論から言うと、これは等価ではありません。 前者は正規表現を一度しか評価しないのに対し、後者は毎回RegExpオブジェクトを作ります。

どうやら、次の3つ関数はほぼ等価のようです。

function func1(str) {
  return /[a-z]/.exec(str);
}

var re2 = /[a-z]/;
function func2(str) {
  return re2.exec(str);
}

var re3 = new RegExp('[a-z]');
function func3(str) {
  return re3.exec(str);
}

「ほぼ」というのは、実際に中身を見たわけではないからです。ただ、処理時間から推測しただけです。

さて、ここまでは理解できていたのですが、実はこの状態だと正規表現のコンパイルは行われていないようなのです。

どういうことかと言うと、

var re4 = new RegExp('[a-z]');
re4.compile();
function func4(str) {
  return re4.exec(str);
}

とすると、処理時間が短くなるケースがあるのです。 これはちょっと意外でした。正規表現のコンパイルというのは最適化の過程で勝手に行われるのかと思っていました。もしかしたら、最適化が行われるのはもっと長い時間処理が回っていないからなのかもしれませんが。

まずは、nodeでテストをしてみました。

https://gist.github.com/dai-shi/7394062

このベンチマークを走らせたら、一つの書き方だけ明らかに速くなりました。それが上記の書き方です。nodeのバージョンをv0.8, v0.10, v0.11と試しましたが、傾向はどれも同じです。

次にjsperfでも試しました。

http://jsperf.com/classname-check-regex-vs-indexof

ちょっと目的が違うのでindexOfとの比較が入っていますが、RegExpの方は大体同じです。 Chromeでの実行結果は、compile()を実行したケースがわずかに速くなりました。試している正規表現が違うので、コンパイルが効きにくいのかもしれません。 Firefoxでの実行結果も、compile()のケースが速くなりました。こちらは10%以上。しかも、全体的にChromeの実行結果より速い。ちょっと意外です。

さて、別のベンチマークも見てみましょう。

http://jsperf.com/javascript-compiled-regex/15

これは、globalマッチのテストケースも入っているのでちょっと分かりにくいですが、それを除けばやっぱりcompile()しているケースが速いです。Chromeで20%ほどアップ。Firefoxで30%ほどアップ。

ちなみに、globalマッチはChromeの場合は最速で、Firefoxの場合はもっとも遅いようです。これは使い方を悩みますね。

これらのベンチマーク結果を踏まえてどういう方針にするかですが、 まず、クライアントサイドのコードの場合は、10%~30%程度速くなることにどれくらい意味があるかですが、ばらつきもあるので、よほどパフォーマンスにシビアでなければ、inline型の正規表現でよいように思います。読みやすいといのが最大のメリットです。つまり、

/[a-z]/.exec(str);

こういう感じです。

一方、サーバサイドのコード、つまり、nodeの場合は、2倍近く速くなるケースもあり、また通常サーバサイドのコードはパフォーマンスを重視することから、現時点ではcompile型の正規表現を使うことに意味があるかもしれません。つまり、

var re = new RegExp('[a-z]');
re.compile();
function func() {
  return re.exec(str);
}

こういう感じです。関数の外で定義しないといけないので、場合によっては気をつける必要があります。変数名の衝突や、意図しない書き換えなど。

var func = (function() {
  var re = new RegExp('[a-z]');
  re.compile();
  return function() {
    return re.exec(str);
  };
})();

のようにする手もありますが、ますます読みにくくなっていきますね。

本来なら、re.compile()がなくてもコンパイルしてくれたらいいように思うのですが、コンパイルそのものにコストがかかるケースもあるので一回しか使わないような場合はコンパイルすべきじゃないということですね。JITなどの最適化はあまり詳しくないのですが、もしかしたら、次第にコンパイルされていくのかもしれません。

と、ここまで書いたところで見つけたのですが、

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Deprecated_and_obsolete_features

あれ、compile()は非推奨なのですね。ということは、やっぱり処理系が最適化の過程でコンパイルしていくのが筋ということですね。 非推奨のcompile()を使うとそれを強制的に動かせるのでベンチマークコードの場合は有利といったところでしょうか。

というわけで結論は、よほどの理由がなければ、inline型を使っておけばよいでしょう。new RegExp()を使ってもパフォーマンスが悪くなるわけではないので、場合によっては使ってもよいでしょう。

ぐるっと回って振り出しに戻った感じですが、個人的にはすっきりしました。

JSONの仕様では、Date型は定義されていません。しかし、今回どうしてもsocial-cms-backendでJSONにDate型を埋め込みたくなりました。

stackoverflowで面白い投稿を見つけました。

http://stackoverflow.com/questions/4511705/how-to-parse-json-to-receive-a-date-object-in-javascript

どうやらMicrosoftが.NET Frameworkで独自に拡張しているようです。

http://msdn.microsoft.com/en-us/library/bb412170.aspx

これは、"\/Date(1234567)\/という文字列をDate型とみなすというものです。正確にはタイムゾーンも指定できるようですが、使わないので省略します。

さて、JSON.parseには第二引数にreceiverを指定して独自のdeserializeができるようです。

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse

stackoverflowの投稿はこれを使ったサンプルです。ただし、receiverに届くときにはバックスラッシュのエスケープは消えているので、"/Date(1234567)/"もDate型に変換されます。分かって使っている分にはよいですが、JSONの仕様からは外れ(いや、Microsoftのやり方もハックですが)、独自のコンベンションという感じですね。

参考までに、コードを載せておきます。

function parseJSON(str) {
  function receiver(key, value) {
    if (typeof value === 'string') {
      var match = /\/Date\((\d+)\)\//.exec(value);
      if (match) {
        return new Date(parseInt(match[1], 10));
      }
    }
    return value;
  }

  return JSON.parse(str, receiver);
}

そうそう、例で示した1234567の数値のところは、Date.now()と同じようにミリ秒です。

RSS Pipesは、しばらく前に作ったRSSアグリゲーターで、WikiっぽくJavaScriptでフィルターアルゴリズムを書くものです。

ちょっと思い立って英語化しました。といってもコード上のJadeはすでに英語化されていて、今回は説明ページを英語にしただけです。

困ったこと。GitHub PagesはAccept-Languageヘッダーに対応していないようです。仕方ないので、index.htmlとindex-ja.htmlの二つのファイルを、herokuのアプリからリダイレクトするときに振り分けるようにしました。

これだけではネタ不足なので、Jadeで英語と日本語の両方を埋め込む方法を紹介します。

簡単にいうと、

p ようこそ

とするところを、

p= (lang === 'ja' ? 'ようこそ' : 'Welcome')

と書くようにしました。2ヶ国語だからできる技かもしれませんね。 Jadeのテンプレートを呼び出す方は、

var lang = '';
if (req.headers['accept-language']) {
  lang = req.headers['accept-language'].substring(0, 2);
}
res.render(view_name, {
  lang: lang
});

のようにします。 もうちょっとうまいやり方がないだろうか考えていますが、今のところ思いついていません。

ご参考になれば。