Category Archives: Javascript

JavaScript Advent Calendar 2011 (フレームワークコース) 21日目の記事です。

Sencha Touchを初めて使った時の話で、あまり実用的な内容ではないですが、
これからSencha Touchを使う人に何かしら参考になればと思います。

Sench TouchでiPadモック

Sencha Touchとは…という話は恐らく聞いたことがある人は多いと思うので省きますが、
iOSとAndroid(要はWebkit系)をサポートしたスマートフォン向けのWebアプリケーションフレームワークです。

最近はObjective-CでiPhoneアプリを書いている事が多いのですが、
今回、問診票iPadアプリを作成する事になり、それ向けに動くモックが一週間後ぐらいに必要となりました。
まだiPad向けのアプリは作成した事はなかったので時間的な心配と、 Sencha Touch ちょっと触ってみた所感などを読んでSencha Touchが気になっていたので、
Sencha Touchでモックを作って見ることにしました。

Sencha Touchを初めて3日ほどかかって、かろうじて動く感じのモックができました。

  • http://azu.github.com/SenchTouchiPadMock/www/
    Webkit系のブラウザならiOSじゃなくても見られます
    全然Sencha Touchのルールに則ってない書き方をしてるのでソースは参考にしてはいけない

NewImage

iPadアプリでいうUISplitViewController な感じで、それぞれの項目に入力欄や選択肢が出るような感じのモックです。

NewImage

デザイン的なモックでは無いので、フォントサイズを自由に変更できるスライダーを載せて、サイズをいじりながら見られるようにしていました。

NewImage

このSencha Touchの入力部品(テキストエリアやチェックボックス)は、以下のようなJSONオブジェクト的なものを書くだけで作成できるので、かなり手間を省いてできるのでとりあえず並べるものを作るにはとても便利です。入れ子のような構造もちゃんとUIに反映されるのでかなり便利でした。

var data = [{
        title:"年齢",
        instructions:"あなたの年齢を入力してください",
        xtype:'fieldset',
        id:'yearsold',
        items:[
            {
                xtype:"textfield",
                inputType:"number",
                name:'yearsold',
                label:'年齢',
                placeholder:"年齢を入力"
            }
        ]
    },
    {
        title:"一日に吸うタバコの本数",
        instructions:"一日にタバコを吸う本数(禁煙中ならば、以前は何本くらいすっていましたか?)
" + "今まで、合計で何年間くらいタバコを吸っていましたか?", xtype:'fieldset', id:'cigarettes', items:[ { xtype:"textfield", inputType:"number", label:"一日の喫煙本数", name:"cigarettesPerDay" }, { xtype:"textfield", inputType:"number", label:"喫煙年数", name:"yearsOfSmoking" } ] }, { title:"体重・身長", instructions:"あなたの体重と身長を入力してください", xtype:'fieldset', items:[ { xtype:"textfield", inputType:"number", label:"体重", name:"bodyWeight", placeHolder:"体重を入力" }, { xtype:"textfield", inputType:"number", label:"身長", name:"bodyHeight", placeHolder:"身長を入力" } ] }, { title:"チェックシート", instructions:"それぞれの項目に対して該当するものをチェックしてください", xtype:'container', cls:'checksheet', // heightがたりないので無理やり100%に id:'checksheet', scroll:'vertical', items:[ // それぞれの問題 { title:"天候により、せきがひどくなることがありますか?", xtype:'fieldset', defaultType:"radiofield", items:[ { name:'tmp_seki', label:'はい(天候によりひどくなる)', value:'3' }, { name:'tmp_seki', label:'いいえ(天候は関係ない)', value:'0' }, { name:'tmp_seki', label:"せきは出ない", value:'0' } ] }, { title:"風邪をひいていないのにたんが絡むことがありますか?", xtype:'fieldset', defaultType:"radiofield", items:[ { name:'kaze_tan', label:'はい', value:'3' }, { name:'kaze_tan', label:'いいえ', value:'0' } ] }, { title:"朝起きてすぐにたんがカラムことがよくありますか?", xtype:'fieldset', defaultType:"radiofield", items:[ { name:'tan_karamu', label:'はい', value:'0' }, { name:'tan_karamu', label:'いいえ', value:'3' } ] }, { title:"喘鳴(ゼイゼイ、ヒューヒュー)がよくありますか?", xtype:'fieldset', defaultType:"radiofield", items:[ { name:'zenmei', label:'いいえ、ありません', value:'0' }, { name:'zenmei', label:'時々、もしくはよくある', value:'4' } ] }, { title:"今現在(もしくは今まで)アレルギーの症状はありますか?", xtype:'fieldset', defaultType:"radiofield", items:[ { name:'allergy', label:'はい', value:'0' }, { name:'allergy', label:'いいえ', value:'3' } ] }, ] } ];

モックからネイティブアプリに

上記のSencha Touchで作成したモックも参考にしつつ、Objective-CでネイティブのiPadアプリとして作りなおしています。
まだ作成中な感じですが、なんとなく雰囲気だけ残っています…

ネイティブの場合も同じように、JSONで質問項目やメタデータの一覧を書いて、そのJSONを元にUIを作るコード書いて作成しました。
Sencha Touchの場合だと既にJSONからUI(フォーム系)を作るものが用意されているので、この辺がかなり簡略化できていたんだなとObjective-Cで書いてて思いました。

NewImage

NewImage

 

Sencha Touchの感想

Sencha TouchはMVCなど書き方に一見つかみにくい要素があるので少し学習する時間がかかると思いますが、
その辺が上手くつかめるようになれば、jQuery Mobileなどよりもいろいろな事がやりやすかったり、ガッチリしたものも作成できると思います。

Sencha Touch2ではもう少しコードがすっきりしたり、PhoneGapのようなネイティブアプリへのパッケージング機能を持ったり、
よりスマートフォン向けアプリが書きやすくなると思うので、触る機会が増えるかもしれません。

今回のモックを作るにおいて参考したサイトなど

Sencha Touchのインターフェースのレイアウトについてのスクリーンキャスト。
layoutの値による違いを視覚的に把握できるので見ておくべき。
SenchはLearn Sencha Touch | Learn | Senchaなどにスクリーンキャスト等がいろいろあるが、
この中でも特にDrew Neilによるスクリーンキャストはとても解りやすくできているため見ておいて損はない。
スクリーンキャストとしての出来がとても良い感じ Learn Sencha Touch w/ Drew Neil on Vimeo

まだ発売前の書籍ですが、サンプルコードも置かれているのでコンポーネントの表示を確認するのに便利。
ただ、実際のコードはExt.setupなど最低限な構成なため実際に使うときはコピペなどをしないほうがいいと思います。
(後、ライブラリのパスとかがまちまちなので自分で調整しないとまともに見えないサンプルも多い) 
サンプルで公開されているIntroducing Sencha Touch (Green Paper – PDF)には、Sencha Touchにおける構成部品の名前などの図が乗っているので、結構参考になる。

Sencha Touchを使ってノートアプリの作成チュートリアル
モックデザインなど図も多くあり、かなり細かく解説している。(書籍も出してるみたい)

その他の参考になるもの

JavaScript Advent Calendar 2011 (オレ標準コース) : ATND の 5日目の記事です。

最近書いた、スマートフォン向けのJavaScriptライブラリ?(紙芝居のようなものを描画するライブラリ)を書いている時に、テストをどうしようかといろいろ考えていましたが、RequireJS + QUnit + QUnit-TAP + PhantomJS という構成で書くことにしました。
(Jasmineが好きだったのでpavlovMochaも気になっていましたが、上記の構成が安定しそうと思った)
RequireJSが入っているのは、そのライブラリはRequireJSでモジュールを書いていて、r.jsでビルドするような感じにしている為です。

この構成を説明するにあたって、AMDTestingというサンプルプロジェクトを作成したので、それを見ながら書いていきます。

DomModule js   ~ Dropbox workspace toybox AMDTesting  JetBrains WebStorm  WebStorm WS 110 518

シンプルなプロジェクト(AMDTesting)の構成

用意するライブラリとしては以下の2つです。

PhantomJSDownloadするなり$ brew install phantomjsするなりしてインストールしておきます。

QUnit-TAPは以下のようにsubmoduleとしてプロジェクトに追加しています。(アップデート対応とか考えるとコチラのほうがよさそうなので)

➜  AMDTesting ✗ git submodule add git://github.com/twada/qunit-tap.git test/lib/qunit-tap
➜  AMDTesting ✗ cd test/lib/qunit-tap
➜  qunit-tap ✗ git submodule update --init
# QUnit-TapのsubmoduleにQUnitなどが含まれている

qunit-tapのexampleからrunnerとなるphantomjs_test.shとrun_qunit.jsを取り出してtest直下に置いています。
同様にtest直下にQUnitを実際に走らせるページとして以下のような内容のindex.htmlを置きます(ファイル名自体は phantomjs_test.sh に合わせる)

index.htmlのbody以下はQUnitのテンプレート、”requireJSを読み込む”部分より上はQUnit-TAPのテンプレートになっているのであんまり気にしなくても大丈夫です。requireJSで書いたモジュールをテストするのに必要な構成を書いている部分はその間の部分で、
モジュールのパスを楽に書けるように、baseUrl:’spec’でベースとなるパスをspecディレクトリにしています。
また、’src’:’../../src’とすることで、モジュールパスにsrc/hogeと書くと../../src/hogeとなるようにしています。

そして、それぞれのモジュールのテストをモジュールとしてdefineし、index.htmlでまとめて読み込んでいます。
テストする量が多くなったらグループ分けするようなモジュールを作るとイイきがします

AMD形式のモジュールのテストするモジュールについて

例えば、以下のようなモジュールを定義して

// sampleモジュール
define(function () {
    var module = {};
    module.getCharacterAt = function (str, pos) {
        return str.substr(pos - 1, 1);
    }
    return module;
});

次のようにテストするモジュールを書いて、テストコードを追加していきます。

// テスト対象をsrcから読み込んでテストコードを書く
define(["src/sample"], function (module) {
    test("getCharacterAt", function () {
        var str = "STRING";
        equal(module.getCharacterAt(str, 1), "S");
        equal(module.getCharacterAt("STRING", 3), "R");
        equal(module.getCharacterAt(str, str.length), "G");
    });
});

そして、テストを走らせる時はテストモジュールの方を読みこめば、requireJSが依存関係を解決(src/sampleの方をテストモジュール内に読み込んで)してテストを実行できます。

DOMを扱うモジュールのテスト

ここまで触れていませんでしたが、QUnit-TAPのsampleにはPhantomJSに対応したサンプルが含まれいます。
sampleからコピーしてきたphantomjs_test.shというのがまさにそれで、index.htmlを PhantomJS を使って実行します。
PhantomJSはGUIがないWebkitベースのブラウザをコマンドライン動かすようなツールで、DOMを扱ったヘッドレステストも行う事ができます。

今までCUIで完結するテストはEnvjsのようにDOMをエミュレートするか、本物のブラウザを立ち上げて裏で実行させるかのような方法をとっていましたが、PhantomJSはそれがもう少し手軽にできるようになった感じです。(Webkit系の動作だけしかテストできないという欠点もありますが)

次のようなDOMに依存したモジュールのテストも行う事ができます。
まず以下のようなDOMに依存するモジュールがあるとします。


// domModule
define(function () {
    var module = {};
    module.append = function (parentNode, elem) {
        if (elem.nodeType === 1) {
            parentNode.appendChild(elem);
        }
    }
    return module;
});

そして、これをテストするモジュールを作成します。

define(["src/domModule"], function (module) {
    // #qunit-fixture" はreset時に初期化される
    test("append", function () {
        var fixture = document.getElementById("qunit-fixture");
        var addElem = document.createElement("div");
        module.append(fixture, addElem);
        equal(fixture.firstElementChild.nodeName, "DIV");
    });
    test("append-2", function () {
        var fixture = document.getElementById("qunit-fixture");
        var addElem = document.createElement("a");
        module.append(fixture, addElem);
        equal(fixture.firstElementChild.nodeName, "A");
    });
});

QUnitのテンプレートにはid=”qunit-fixture”の空divがありますが、この領域はtest()を実行する度に初期値の内容に書き換えられるようになっているため、htmlの断片などのfixtureを入れる場所として利用できます。
ブラウザでアクセスすれば、上記のテストが実行できるのが通るのがわかると思います。
file:///だと実行できない場合もあるので、

➜  AMDTesting git:(master) ✗ python -m SimpleHTTPServer 8000

という感じで、localhostを立ててブラウザで http://localhost:8000/test/ にアクセスという形をよくとっています。
話を戻して、 PhantomJSを使った場合は、phantomjs_test.shを実行するとブラウザでindex.htmlにアクセスしたのと同じように、コマンドラインからテストが実行できます。

QUnit-TAP

QUnit-TAP : JavaScript のテスティングフレームワークQUnitからTAP出力する – t-wadaの日記

QUnit-TAPはTAP形式のテスト結果を出してくれるため、proveコマンドでテストを実行できます。

➜  test git:(master) ✗ prove -o phantomjs_test.sh

proveでテストを実行できるとCIで回すのが簡単に行えたり、Perlのテスト資産が利用できたり、失敗したテストだけを実行するなどといったことができるようになるので、TAP形式の出力はいろいろと発展性があると思います。

あまり小難しいこと考えなくても、テスト結果がシンプルにわかるから使うとかでもいい気がします。

参考

最初からRequireJSを使うことは決めていて、後からAMDモジュールのテストはどう書くのがいいのかを模索し始めた時に、
以下はAMDモジュールのテスト構成を考える上でとても参考になりました。

こんにちは。先日のブログで以下のような発言をしたイッシーです。(●´ω`●)

僕は生産設備を持っていませんので食料をはじめとした物流における支援は難しいのかもしれません。しかしながらシステムエンジニアとして間接的な支援や情報における支援はできます。僕は、天災における「破壊」を修復するのは人々の「生産」と考えるとともに、その「生産」の一部分を担う者として頑張っていきたいと思います。

有言実行ということで緊急地震速報をお知らせするChrome用の拡張機能を作ってみました。そこで今回は技術的な部分をまとめておきます。

■仕様

クライアント側

Web Notifications

お知らせを表示する小窓です。一番はじめのブログの題材にしました。

WebSocket

双方向通信の規格。これを使うとサーバ側から情報をプッシュできます。

Audio API

お知らせ音を鳴らす際に使用しました。

サーバ側

node.js

サーバサイドJavaScript。一言では表せない存在。同時接続ユーザ数が増えた場合のパフォーマンスがapacheと比べて非常に良いと思います。

■困ったこと1

node-websocket-serverの接続数が1024以上にならない。

解決策

node serverを立ち上げる前に以下のコマンドを実行することで解決しました。

[cc lang="bash"]
ulimit -n 2048
[/cc]

デフォルトでファイルディスクリプタの数が1024になっているので、1プロセスあたり1024個の接続がリミットになっていたわけです。twitterで教えてくださった方に感謝です!

■困ったこと2

30分以上のスリープ状態から復帰すると接続が切れているが、イベントが発火せずプロパティにも特に変化がない。

解決策

以下のようなコードを使用することで回避しました。

[cc lang="javascript"]
var reload = 600000;
var before = new Date();
setInterval(function(){
var current = new Date();
if((current – before) > (reload + 15000)){
connect();// 再接続
}
before = current;
},reload);
[/cc]

タイマーを利用してスリープを検知しています。通常はcurrentとbeforeの差は殆どreloadと等しくなりますが、スリープした場合に差が大きくなります。それを利用して再接続をしているわけです。ヒラメキに感謝!

コードが美しくないのでhtml5-developers-jpに質問してみたところ、「NATやFireWallがあり通信が一定期間無い場合、リソース節約のためにそのセッションテーブルを切ってしまうため、readyStateなどは変化しない」とアドバイスを頂きました。教えてくださった方ありがとうございます!

■困ったこと3

千人くらい使ってくれるかなと考えていましたので、一万人を超えた時は正直焦りました。急激にトラフィックが制限に近づいてます。ソーシャルゲームとは違いユーザ間でデータの共有はしてませんので、サーバさえあれば分散は容易になってます。また、協力してくださる方もいますので協力してくださる方に感謝です!

■まとめ

当初、不具合がありまして多くの皆様に迷惑をかけたにもかかわらず、温かい言葉をかけてくださいまして大変励みになりました。また、個人で制作したものではありますが、サーバーなど会社のバックアップがあってこそ運営できているものです。改めて周囲の皆様に感謝し今後も頑張っていきたいと思うとともに、震災にあわれた地域のいち早い復興を願っております。

みなさんこんにちは。2月に入社した石本(通称:イッシー)です。会社ではスマートフォンのネイティブアプリを作ってます。\(^o^)/

さて、ご存知の方も多いと思いますが、Gmailに新しいオプションが付きました。「設定 > 全般」の項目の「デスクトップ通知」という項目です。この機能を”オン”にした場合、Google Chromeではデスクトップ右下(Macは右上)に、メール受信のお知らせが表示されるようになります。この通知機能によって、メールの受信に気づけるようになるわけです。

そこで本日は、スマートフォンのネイティブアプリには直接関係ないですが、Web Notifications(通知機能)について解説したいと思います。

サンプル

Google Chromeで以下のボタンを押すと、右下に小窓が表示されます。

※初回は「許可」を選択し、再度ボタンをクリックしてお試しください。

サンプルコード

以下のようにするとWeb Notificationsが使用できます。細かい解説はコードの中にコメントとして記述しました。

[cc lang="javascript"]
document.getElementById(‘button’).addEventListener(
‘click’,
function(e){
// 通知機能を実装していないブラウザを除外します
if(typeof window.webkitNotifications == ‘undefined’){return;}

// 通知機能はユーザが許可したサイトでしか使うことができません。
// そこでユーザのブラウザから許可を得ているかを取得します。
var permission = webkitNotifications.checkPermission();
switch(permission){
case 0:// 許可されているとき
var notification = webkitNotifications.createNotification(
”, // アイコンの画像ファイル
‘通知ですー’, // 通知のタイトル
‘(サンプル)お知らせが届きました!\(^o^)/’// 通知の内容
);
notification.show();// 通知の表示
break;
case 1:// 許可されていないとき
webkitNotifications.requestPermission(function(){
// 「許可」もしくは「拒否」が押された後に呼ばれるコールバック関数。
// 但し、どちらが押されたかを取得することはできない。
});
break;
case 2:// 拒否されているとき
break;
}
},
false
);
[/cc]

いかがでしょうか?意外と少ないコード量で実現することができました。上述の場合、通知機能は表示されたままですが、一定時間の表示後に消すこともできます。

[cc lang="javascript"]
switch(permission){
case 0:// 許可されているとき
var notification = webkitNotifications.createNotification(
”,// アイコンの画像ファイル
‘通知ですー’,// 通知のタイトル
‘(サンプル)お知らせが届きました!\(^o^)/’// 通知の内容
);
notification.ondisplay = function(){// 通知が表示されたときのイベント
setTimeout(
function(){
notification.cancel();// 通知を消す処理
},
2000// 2秒後
);
};
notification.show();
break;
case 1:// 許可されていないとき
webkitNotifications.requestPermission(function(){});
break;
case 2:// 拒否されているとき
break;
}
[/cc]

その他のイベントハンドラやメソッドについては以下のようになってます。(参考:chromium Design Document

[cc lang="c"]
interface Notification : EventTarget {
// メソッド
void show();// 表示する
void cancel();// 閉じる

// イベントハンドラ
attribute Function ondisplay;// 表示されたとき
attribute Function onerror;
attribute Function onclose;// 閉じたとき
}
[/cc]

以下のように、W3Cのthe Notification interfaceとは若干実装されているものが異なってるようですね。(参考:the Notification interface

[cc lang="c"]
interface Notification : EventTarget {
void show();
void cancel();
attribute Function onclick;
attribute Function onshow;
attribute Function onerror;
attribute Function onclose;
attribute DOMString replaceId;
attribute DOMString dir;
};
[/cc]

2011年2月現在では、Chromeと同じWebkitを採用しているSafariで、この機能を使用できませんでした。しかし、今後はSafariだけでなく、より多くのブラウザで今回解説した通知機能が使用できるようになるはずです。

参考

ちなみに去年のGoogle Developer Day 2010 Japanでも紹介されていました。