Code

2014年11月9日日曜日

ローカル Web App : AngularJS + ExpressJS + Node.js

 先週、検証チームから依頼があって、protractor を UI で動くようにしたいと。確かに、コマンドラインはちょっと使いづらいので。慣れてる人は、それでいいかもしれないけど、テスターはやはり、もうちょっとビジュアル的な UI や、テストケースレポートが欲しがるようです。
 いろいろ考えてみると、protractor も Node.js で動かしているので、そのまま Node を使えるなら、HTML 5 の Web App を作れば、簡単に UI ができるし、NodeJS の力を使って、コマンドラインを実行すれば、早くできると思われました。
 実際調べて見たら、Express.js と Hapi.js みたいなライブラリーがあって、web server もすぐ立てられそうです。また将来、コードをそのまま使って、どこかのサーバに置いたら、同じ UI で、すぐ Online できるので、メリットが大きいです。
 それで、AngularJS を Front End で、Express.js を Back End にして、ツールを作ることになりました。インストールは npm で済ませて、Yeoman, bower で AngularJS のアップを作って、grunt でビルドなども立てて、すぐ開発に入りました。
 Express.js に generator がついてるので、それを使って、Back End のアプリも簡単に立ち上がりました。
    $ npm install express-generator -g
  $ express myapp
  下記のフォルダストラクチャーができました。app.js に path に基づいて、get, post などを書けば簡単には response 作れるようになります。static ファイルを front end に送るには、 sendfi le 関数を使えば、protractor のテスト結果レポートも簡単に Front End で表示できる。
.
├── app.js
├── bin
│   └── www
├── package.json
├── public
│   ├── images
│   ├── javascripts
│   └── stylesheets
│       └── style.css
├── routes
│   ├── index.js
│   └── users.js
└── views
    ├── error.jade
    ├── index.jade
    └── layout.jade

7 directories, 9 files

さらに、もし興味があれば、 MEAN.js というライブラリがあるので
mean.js
MangoDBをデータベースとして、フル JavaScript のアプリも簡単につくれます。
Meteor も有名です。そのまま iPhone, Android アプリも作れるので、どんどん良くなっています。

それでは。

2014年10月31日金曜日

IE8 AngularJS & jQuery ui - problem -> ng-non-bindable の使い方

 昨日、隣のチームから変なバグが聞かれました。具体的に、jQuery ui の datepicker が IE8 では動かなくなった。まぁ、最新版の AngularJS は IE8 サポートしてないから、問題ないけど、今のプロジェクトでは 1.0.8 を使ってるし、IE8 もサポート対象ブラウザーなので。。。あ〜頭が痛くなる。。。
 jQuery の datepicker のサープルを IE8 で開いて、動きは正しいようです。多分、AngularJS と jQuery ui の間に何か干渉が起きたと思われました。HTML を見てみたら、各日付の <td> tag 中の <a> に <!-- IE fix --> コメントが追加されたことが気づきました。AngularJS には <a> tag を IE8 での特別処理が入ってるようです。
       // add a comment node to anchors to workaround IE bug that causes element content to be reset
      // to new attribute content if attribute is updated with value containing @ and element also
      // contains value with @
      // see issue #1949

つまり、AngularJS は上記の IE fix コメントを追加したのです。一般的なライブラリは innerHTML を使って、タグの内容を取得すると、その IE fix も一緒に取得されるのです。parseInt などを使うと、エクセプションが throw されます。本来数字のみ入っている場所に他の文字列が入ったので、何か起きると、不思議ではない。
 
 もう少し調べてみたら、Angular は ng-non-bindable という directive を提供しています。これを使うと、 Angular は HTML DOM をスキャンする時、そのブロックをスキップさせます。つまりその中にある要素は Angular の $digest サイクルに入らない。
 これで、Angular は勝手に <a> tag 何かを入れることがなくなります。問題解決。

 今後 IE 8 をサポートしている間、気をつけないと。
 それでは。

2014年10月18日土曜日

Protractor での自動テスト

 先週から、検証チームを助けて、Protractor での自動テストが始めました。先日ダウンロードして、インストールしようと思ったんですけど、npm がなぜかshanum エラーが出ました。調べたら、ネットワークのせいかもしれないと、もう一回インストールして、無事成功しました。
 Protractor を動かすには、まず Selenium サーバーを起動して、config ファイルにそのアドレスと、spec の場所を入れて、後、Jasmine sytax での spec が必要です。
 Protractor とともに、webdriver-manager と言う Node のコマンドがインストールされます。webdriver-manager update を実行すると、google から chromedriver.exe, IEserverdriver.exeと、Selenium jar ファイルがダウンロードされます。その後、webdriver-manager start で selenium サーバーが起動されます。Jetty なんちゃら 4444 がコンソールに表示されたら、起動成功。
 その後、config に下記の JSON を記入して、spec を書いて、すぐ実行できるようになります。
exports.config = {
  seleniumAddress: 'http://localhost:4444/wd/hub',
  specs: ['todo-spec.js']
};
spec のサンプル:
describe('angularjs homepage todo list', function() {
  it('should add a todo', function() {
    browser.get('http://www.angularjs.org');

    element(by.model('todoText')).sendKeys('write a protractor test');
    element(by.css('[value="add"]')).click();

    var todoList = element.all(by.repeater('todo in todos'));
    expect(todoList.count()).toEqual(3); browser.sleep(3000);
    expect(todoList.get(2).getText()).toEqual('write a protractor test');
  });
});
protractor conf.js を実行すれば、angular のウェブページが表示されて、TODO に自動的に一つアイテムを追加されて、その後、 TODO の数をチェックして、終了。

 注意すべきところは、protractor は selenium のラッバーみたいなもので、AngularJS 終了を待ってから、コマンドを実行してます。(おそらく、Angular の phase をチェックしてるかもしれない)、もし、ウェブページに Angular がなければ、変なエラーが出るのです。原因は protractor ずっと待ってて、待ち続けて、タイムアウトになります。
 このとき、browser.get や element などが使えなくなります。browser.driver を使うべきです。browser.driver は webdriver js で、findElement などが使えます。この時、適当に sleep を入れて、JS の実行完了や、ajax の実行などを自分でまつ必要になります。。。
 やはり、protractor が使いやすいですね。
 
 後日、また続きを。それでは。

Android WebView の 動画が遅い件

 先日、新しい Ads のパフォーマンスが良くないなと思っただけで、今日は他のチームも発見して、相談されました。具体的には WebView を使って、HTML ファイルを表示してますが、画像では iOS, Android 両方問題なく、良く表示されてますが、動画の場合、ある Android 機器が超遅くて、遅くて、使い物になれないほどです。
 実際、Web Inspect で見てみると、JS の実行が遅いです。じゃ、 CSS ではどうでしょうと思って、ほぼすべてのコードを書き換えて、CSS でアニメーションを実現して、試しました。。。でも、遅いです。。。CSSの場合、ハードウェアの加速があるので、速いと言われていますが、Android ではそうではないそうです。まぁ、機器のスペクもよるので、Nexus 5 で試しましたが、iOS ほどスムーズではないですが、そこそこ動いています。
 こう見ると、多分古い Android 機器はだめみたいです。。。何でだろうと思って、やはり WebView はブラウザーより遅くて、開発時は注意しないと。
 一応メモして、将来何か参考になるかもしれないです。
 それでは。

2014年9月27日土曜日

モバイルでは viewport 設定

 先週、作ってるサイトがバグ見つかった。問題は non-responsive のページは一画面に全部表示されなかったことです。期待としては responsive ページはそのまま表示、non-responsive ページは縮小して、一画面に収まれるようになることです。
 調べてみたら、viewport の設定に問題があった。今のプロジェクトでは、responsive ページとnon-reesponsive ページは C# の属性を標識して、render engine で違う viewport を設定します。
 viewport と言えば、携帯のピクセルは実際ページのどのぐらい当てるかの設定です。例えば、viewport の width を 800 に設定したら、携帯のスクリーンは 800 pixel となります。もしサイトの横サイズは 800 だったら、ちょうど一画面に収まれます。1024 に設定したら、800 pixel サイズのサイトはちょっと小さく表示されます。
 一般的には responsive サイト、またはそのサイズのままで表示したい場合、 width を device-width を設定すれば良いです。これで、ディバイスの横幅をそのまま使われます。また、サイトを小さくしたい場合、init-scale を小さくすれば良いです。それに、minmum-scale, maximum-scale でサイトの拡大と縮小倍率はコントロールできます。user-scalable でユーザがサイトピンチで拡大できるかどうか設定できます(yes or no).

 今回の問題は width を 800 に設定したら、initial-scale も 1.0 に設定したからです。これで、width は正しく 800 になっても、サイトのイニシャルスケールは 1.0 つまり、サイトはどのぐらい大きいか、もうそのまま表示されます。つまり、width の設定が上書きされました。今後注意しましょう。

 ちなみに、古いバージョンの iPhone または Android のデフォルトブラウザーでは、width を指定した後、scale を指定すると、viewport が効かないようになりますので、ご注意ください。つまり、content="width=1024, user-scalable=no" は動きますが、content="width=1024, maximum-scalable=2" は動かないです。

 後、Android と iOS の古いバージョンでは、viewport を正しく解釈できないことが発見しました。多分 CSS の影響だと思いますが。。。
 最新の Chrome (Android) をダウンロードして、チェックしてよいです。iOS なら、できるだけ、iOS 7を使いましょう。iOS 6 の Safari はまだまだバグがあります。特に、transform に関して、正しく表示できない時があります。

 それでは。

2014年9月19日金曜日

リターンキーでフォーム Submit

 昨日、変なバグが見つかった:ある input text にフォーカスを押して、リターンキーをクリックしたら、画面が更新されました。。。何でだろうと思った。。。
 実際見てみると、input は form タグの中にあるので、フォームの action は "#" と書かれています。# だと、自分のアドレスに submit されますので。。。でも input は text type だし、submit タイプではないから、何で submit がトリッガーしただろう??
 調べてみたら、もし Form の中に input text が一つだけある場合、リターンキーを押すだけで、 form が submit されます。これはブラウザーの固有動作です。しかも IE 5 からあります。
 また、IE から変な動作を継承したな〜と思いました。
 直す方法としては form の action を削除するか、display : none の input text をもう一個追加するです。
 将来のブラウザーはこれを直さないだろう。メモしておこう。

 それでは。

2014年9月8日月曜日

Angularjs $timeout 調べ

 DI (Dependency Injection) は Javaや、ASP.NET などバックエンドには人気ですけど、AngularJS はそれを JavaScript に応用できるようにしました。具体的に言うと、モジュールをロードするときや、関数を呼び出す場合、$injector の invoke 関数で、呼び出す対象の引数を全部パースして、providerと instance キャッシュの中に探して行く、もし同じ名前の対象があったら、それを使います。
 さらに、Ajax call を実行するとき、$http と $httpBackend を利用することで、$browser も内部で持つことで、すべての操作をライブラリ内部で intercept できるようになります。
 この中に、面白いのは $timeout というものです。実際の timeout 関数は既にあるのに、何で $timeout を実装するのか?
 理由としては、二つあります。まずは Unit test を書くとき、自分で timeout をトリッガーできるようになります。実際のコードを見ると、promise を使っていますが、promise の中には、window.timeout 関数が使われています。これで timeout を同じ機能が実行できるようになります。さらに、もし外部で同じ promise を resolve したら、同じ関数が呼ばれますので、timeout 発生時と同じ動作になります。これはかなりいいことです。もう timeout を待つ必要がなくなります。
 もう一つは Angular Scope の $digest に関係あります。$timeout の中に $rootScope.apply() が呼ばれています。これを理解するには JavaScript 実行のサイクルを理解する必要があります。以前では setTimeout(function() {}, 0) を呼び出すことで、今までの JavaScript コールスタックをすべてクリアした後に、コードを実行する方法があります。$timeout も同じです。AngularJS に自分のサイクル、いわゆる $digest がありますの、この関数ですべての watcher を oldValue, newValue を計算して、listener をその後呼び出します。一つのサイクルを終わらないと、Angular は対象の変更が察知できないことがあります。例えば、ng-include でテンプレートの path を変更したい場合、関数の中に
    /* templateUrl の値は abc とします。*/
    templateUrl = "";
    /* その他の操作*/
    templateUrl = "abc"; // テンプレートをリロードしたい
をやると、$digest はこの関数終わった後に実行されますので、templateUrl は変更してないように見えます。つまり、変数の変更をAngularJS に察知させる為に、一つの $digest サイクルの終わりを待つ必要があります。このとき、$timeout は利用すれば、ちゃんと意図通りできます。
     templateUrl = "";
     /* 他の操作 */
     $timeout(function() {templateUrl = "abc"})
これで、最初の templateUrl がクリアされて、$digest の実行で、ng-include directive がクリアされます。その後、次の $digest サイクルで、templateUrl がまた元に戻したので、すべての expression がもう一度更新されます。
 もちろん、自分で setTimeout を実行して、scope.apply() を呼び出しても、同じ機能ができますの、そのショートカットとして、$timeout を使えば、簡単になります。
 
 まぁ、dirty check にはいろいろ不利なところもありますが、Knockout.js の set 関数を比べると、自分は Angular のほう好きです。
 最近、このライブラリも人気だそうです:
    durandal
   このライブラリは require.js を使っていますので、Angular でできない遅延ロードもできます。聞いたことですが、テンプレートのほうも Angular よりちょっとだけ優れています。
 まぁ、これらは Angular 2.0 で改善される予定なので、少し待ってもいいような気がしますけど。
 
 それでは。