Safari の Webインスペクタで行番号がずれる問題を解決

注意: この内容は古くなっています。Safari6 に対応した記事を書きました!
Safari6 の Web インスペクタで行番号がずれる問題


Safari の開発メニューに、Webインスペクタというものがあります。
Firefoxでいうところの FireBugChrome の Developer Tools と同等のものです。これなくしては Webアプリの開発はできないくらい超絶便利なものなのですが、一つだけ重要な問題がありました。

それは、日本語が入っているファイルの行番号がずれて表示される、という問題です。リソースタブやスクリプトタブでファイルを開いたときに、横に行番号が表示されます。この行番号が実際のファイルの中身と同期していないのです。


実際に見た方がわかりやすいでしょう。これがその様子です。
スクリプトの方は3行だけ選択しているのですが、対応する行番号がめちゃくちゃになっています。どうも日本語が入ると行が高くなり、その分だけどんどんずれていってしまうようです。ソースが数百、数千行ともなると、このずれも大きくなり、何行目を指しているのかさっぱりわかりません。

特に JavaScriptデバッグ時に大きな問題になります。一般的にブレークポイントを置いてデバッグしますが、ブレークポイントはこの行番号の所に設置するのです。しかし、現在表示しているソースの箇所と行番号が大きく違うため、どこにブレークポイントを置いていいのか非常に難しい。

フォント関係の問題だとは思っていましたが、Safariの設定でも特にそのようなものはないし、めっきりお手上げ状態でした。

それが、遂に、解決する方法を見つけました!

Web インスペクタ自体も JavaScriptで書かれたツールで、以下のディレクトリに入っています。(Macの場合、Windowsはわかりません。。。)

2012/04/24 追記: Safari 5.1 より使用するフレームワークが変更になり、パスが変わりました。指摘頂いたk2oさんありがとうございます!

/System/Library/StagedFrameworks/Safari/WebCore.framework/Versions/Current/Resources/inspector

参考までに それ以前の Safari の場合はこちらになります。

/System/Library/Frameworks/WebKit.framework/Versions/A/Frameworks/WebCore.framework/Versions/A/Resources/inspector

この中の「textViewer.css」が テキストを表示する部分のCSS指定を行っています。このファイルを開いて、先頭の .text-editor 定義にフォント定義を追加します。

.text-editor {
    position: absolute;
    top:0;
    left:0;
    right:0;
    bottom:0;
    white-space: pre;
    overflow: auto;
    /* 以下を追記 */
    font-family: "Mig 1M", monospace;
    font-size: 11px;
    line-height: 1.3;
}

font-family と font-size はお好みで変えてください。重要なのは line-height をあわせること。固定値でも倍数でもどちらでも大丈夫なようです。

これを保存し(編集には管理者権限が必要です)、Safari を再起動すると反映されます。

直った箇所を見てみます。
ちゃんと行番号とソースの選択箇所が一致していますね!

Web インスペクタは HTMLとJSで書かれたツールなので、他にも自分用にカスタマイズすると便利になりそうですね。

これで Safari でもバリバリ開発できるぜー!

Jasmine で 継続的なJavaScriptのテストをする

風邪で3日も寝込んでしまったので、JavaScriptのテストツールについて調べてみた。その中で、特に Jasmine というのが気になった。使ってみるとなかなかよさげ。

Jasmineは、JavaScriptでBDD(behavior-driven development)を行うフレームワークで、RubyRSpecのように振る舞いを定義する。Sencha Touchのテストにも使われているらしい。ちなみに、RSpecについては スはスペックのス が詳しい。

なぜこれが良いかは、公式ドキュメントにずばりある。
Background · jasmine/jasmine Wiki · GitHub

There are some great JavaScript testing frameworks out there already, so why did we write another?

None of the existing frameworks quite worked the way we wanted. Many only work from within a browser. Most don't support testing asynchronous code like event callbacks. Some have syntax that's hard for JS developers or IDEs to understand.

So we decided to start from scratch.

適当意訳

既に数多の優れたJavaScriptテスティングフレームワークがあるのに、なぜ別のを作ったか?

我々の望みを完全に実現するフレームワークが無いからです。ほとんどのものはブラウザ中でしか動かないし、イベントコールバックの様な非同期テストをサポートしていない。またいくつかは難しい文法やIDEを必要とする。

だから、我々は一から作ることを決めたのです。

JsUnitやQUnitSeleniumのことを言ってるなぁという感じですが、たしかに後発なゆえ、なかなか良くできているかと思いました。BDDというところもなかなか良い。

Jasmine の使い方は他に色々あるので、以下のサイトなどを参考にしてください。

Jasmine には 静的なHTMLファイルを用いたスタンドアローンと、rubyでサーバを立ててテストする rubygems 版があります。rubygems 版のメリットは、コマンドラインから実行可能という点。継続的テストにはこれが必要不可欠。

Jasmine では、 Selenium-rc を利用して自動的にブラウザを起動してテストする仕組みが有ります。

rake jasmine:ci

で実行できる。
デフォルトでは FireFox でのテストになる。実施するブラウザを変更したい場合は、環境変数 JASMINE_BROWSER に実施したいブラウザを指定すれば良い。Jasmineの場合、jasmine_config.rb で設定することが可能だ。
jasmine_runner.rb と同じ場所に jasmine_config.rb という名前のファイルを作成し、以下のように記述すれば safari でのテストが可能になる。

ENV["JASMINE_BROWSER"] = 'safari'

safariでの実行で注意することは、ポップアップブロックが有効になっているとテストが途中で止まってしまうため、これを無効にする必要がある。safariのメニュー「sfari」から「ポップアップウインドウを開かない」のチェックを外す。

なお、chromeは指定してもなぜかfirefoxになってしまう。。。
また今度調べてみよう。

JavaScript の getter/setter について

JavaScript で getter/setter が使えるということを今更ながら知った。同じ ECMAScript ベースの ActionScript は使えるのになー、と思ってたら案の定使えた。はい。私の不勉強でした。。。

参考:

しかし、ほとんど使ってるのを見たことがない。いろんなライブラリでは速度が重視されるからか?とも思ったが、ライブラリだからこそ、こういうアクセッサメソッドは重要なんじゃないだろうか。
IEでは非サポートなので意味ないということかもしれないが。。(が、一応対応策はある)

とりあえず、どれくらい遅いのか測ってみることにした。

こんなオブジェクトを作って、直接アクセス、getter/setter アクセスの実行時間を比べてみた。

var Test = {
	a: 10,
	_num: 1,
	_str: 'hoge',
	_obj: {i: 1},
	get num() { return this._num; },
	set num(n) { this._num = n; },
	get str() { return this._str; },
	set str(s) { this._str = s; },
	get obj() {return this._obj; },
	set obj(o) { this._obj = o; },
};

なお、計測はこんな感じ。

function calcTime(count, fn){
	var d = new Date().getTime();
	for(var i=0; i<count; i++){
		fn();
	}
	var pd  = new Date().getTime();
	return (pd - d);
}

function start(){
	var aGetTime = calcTime(count, function(){
		var a = Test.a;
	});
	var aSetTime = calcTime(count, function(){
		Test.a = 100;
	});
	・・・

なぜか FireFox(3.6.13)では動かず。。。
Safari 5.0.3 と Chrome 9.0 で試しました。ループ回数は10000回で、何回か回した平均的な時間(ms)です。

項目 Safari Chrome
Test.a get 38 71
Test.a set 35 70
Test.num get 47 145
Test.num set 53 149
Test.str get 47 145
Test.str set 54 150
Test.obj.i get 47 147
Test.obj.i set 64 158

Safari はやっ!!プロパティアクセスとメソッド呼び出しはほとんど違いありません。Chromeのほうは、大体倍くらいになってますね。しかしまあ、10万回アクセスでこんなものなので通常はほとんど誤差ですね。
というわけで、積極的に使っていこうと思います。

なお、当方の環境は MacBook Air11"(OS X 10.6.6) です。

まあ、しかし毎回打つのはめんどくさいので、Objective-C の @synthesize とかみたいにできないかな、と思って作ってみた。ruby のあれですね。

function attr_accessor(obj, name, initialValue){
	var pname = "_" + name;
	obj.prototype[pname] = typeof initialValue === 'undefined' ? null : initialValue;
	obj.prototype.__defineGetter__(name, function() { return this[pname]; } );
	obj.prototype.__defineSetter__(name, function(val) { this[pname] = val; } );
};

// 使い方
TestObj = function(){
};
// オブジェクトにプロパティとGetter/Setter、初期値を付ける。
attr_accessor(TestObj, 'foo', 'Hello Accessor!');

var o = new TestObj();
alert('o.foo: ' + o.foo);

これでプロパティもばっちり隠蔽! o._foo とかでアクセスできちゃうけど、まあないよりいいだろ。
しかし、作って思ったが、あんまり意味ないな。微妙。。。

Step 5: クライアント機能のコーディング

目次:Google Web Toolkit チュートリアル(目次) - webとかmacとかやってみようか
原文:http://code.google.com/intl/ja/webtoolkit/doc/1.6/tutorial/codeclient.html

ここまでやってきたが、今更になって素晴らしい訳を作って下さっていることを知った。
Google Web Toolkit(GWT) 2.0入門チュートリアル日本語訳:スタートガイド | 自由で気ままな日々シーズン2

というわけで、私が訳すまでもない。。。

最近は GWTCanvasを使ったり、iphone向けにしたりしてるので、今後はそのあたりをネタにしようかと思う。

Step 3: ユーザインターフェースの構築

目次:Google Web Toolkit チュートリアル(目次) - webとかmacとかやってみようか
原文:http://code.google.com/intl/ja/webtoolkit/doc/1.6/tutorial/buildui.html

ここまでで、StockWatcherプロジェクトの作成と、仕様の確認をしてきたよね。このセクションでは、GWTウィジェットとパネルを使ったユーザインターフェースの構築をするよ。

  1. UI要素を実装するのに必要なGWTウィジェットの選択
  2. UI要素を配置するのに必要なGWTパネルの選択
  3. ホストページ StockWatcher.html に アプリケーションの埋め込み
  4. StockWatcher.javaウィジェットとパネルの実装
  5. ホストモードでレイアウトのテスト

GWTクロスブラウザ互換の心配からあなたを保護するよ。GWTウィジェットでインターフェースを構築すると、最新の FireFoxIEOperaSafari でアプリケーションは同じように動くでしょう。しかし、DHTMLは非常に癖があるので、全てのブラウザでテストしないといけないよ。

1. UI要素を実装するのに必要なGWTウィジェットの選択

最初に、 Widget Gallery を見て、それぞれのUI要素のGWTウィジェットを選択するよ。

ウィジェットギャラリーのウィジェットはデフォルトのスタイルなので、最終的な StockWatcherのように見えるわけではないよ。今は心配はいらないよ。最初にウィジェットの動きを見て、それからCSSを使って見た目を変えるよ。

株データテーブル

GWT は FlexTable と呼ばれる特別なテーブルを提供しているよ。FlexTableウィジェットは要求毎にセルを作成する。ユーザが株データをいくつ追加しているか分からないので、これはまさに株データを含むテーブルに必要としているよ。FlexTableウィジェットで実装されたテーブルは、ユーザが株を追加、削除すると伸び縮みするよ。

ボタン

可能であればいつでも、GWTはブラウザ固有のユーザインターフェース要素を尊重するよ。例えば、Buttonウィジェットは、<div>要素などで作られたボタンの様なウィジェットではなく、まさに HTML の <button&gh; になるよ。これは、GWTのボタンは別のブラウザ、別のOSでちょうどよく描画されることを意味しているよ。ブラウザネイティブの制御を使うメリットは、高速で、利用しやすく、ユーザにとって最も馴染みがあることだよ。また、CSSでスタイルもできるよ。

インプットボックス

GWTは様々な入力フィールドを提供するよ。

  • TextBox ウィジェット: 一行のテキストボックス
  • PassWordTextBox ウィジェット: 入力をマスクするテキストボックス
  • TextArea ウィジェット: 複数行のテキストボックス
  • SuggestBox: 予め設定されたアイテムを表示

StockWatcher では、ユーザは 株コードを一行で入力するので、TextBox ウィジェットを実装するよ。

ラベル

Buttonウィジェットと比較すると、Label ウィジェットは HTMLフォームで使われる<label>要素に結びつけられていない。代わりに、HTMLで解釈されない、任意のテキストを含む<div>要素に結びつけられるよ。これはインライン要素ではなくブロック要素になるよ。

<div class="gwt-Label">Last update : Oct 1, 2008 1:31:48 PM</div>

もし、StockWatcherインターフェースの構築に使うGWTウィジェットAPIリファレンスを覗いてみたければ、下のテーブルのリンクをクリックしてね。

UI要素 GWT実装
テーブル:株データを保持 FlexTable ウィジェット
二つのボタン:追加/削除ボタン Button ウィジェット
入力ボックス:証券コードを入力 TextBox ウィジェット
タイムスタンプ: 最終更新日時を表示 Label ウィジェット
ロゴ HTMLホストページで参照される画像ファイル
ヘッダー HTMLホストページの静的HTML
価格差のプラスマイナスの色表示 ダイナミックCSS

詳細: もし求めるウィジェットが無ければ自分で作ることもできるよ。複合ウィジェットや、JavajavaScriptでスクラッチウィジェットを作る詳細については、ディベロッパーガイド Creating Custom Widgets を見てね。

2. UI要素を配置するのに必要なGWTパネルの選択

どのウィジェットを使うかは分かったね。次はGWTパネルを使ってそれらを配置するよ。GWTはレイアウトを管理する様々なパネルがあるよ。パネルは入れ子できるよ。これは HTML で、div 要素や table を使って配置するのに似ているよ。StockWatcher では垂直パネルにネストした水平パネルを使うよ。
http://code.google.com/intl/ja/webtoolkit/doc/1.6/tutorial/images/StockWatcherUIpanel4.jpg

水平パネル

株を追加するの二つの要素(新しい株の記号を入力するボックスと追加ボタン)は機能的に密接に関連していて、一緒に表示したい。それらを横に並べて置くために、TextBoxウィジェットとButtonウィジェットを水平パネルにいれる。Javaのコードでは、HorizontalPanelインスタンスを作成して、addPanel と名前を付けるよ。

垂直パネル

残りの要素を垂直に配置するよ。

これを垂直パネルに入れるよ。Javaコードでは、VerticalPanelインスタンスを作成して、mainPanel と名前をつけるよ。

ルートパネル

ユーザインターフェースでは見えない、もう一つ必要なパネル、ルートパネルがあるよ。ルートパネルは動的要素のための入れ物だよ。これは GWTユーザインターフェース階層の一番上にあるよ。ルートパネルを使うには二通りある。ページのbodyを生成するか、bodyに埋め込まれた特別な要素を生成するかだよ。

ルートパネルは HTMLホストページを包み込んで働くよ。デフォルトでは(ホストページにプレイスホルダーを追加しない場合)、ルートパネルは body 要素を包み込むよ。しかし、ルートパネルを呼ぶときに名前をパラメータで渡すと、任意の要素で包めるよ。次の2行を見ると、どのように動くかをわかるでしょ。

RootPanel.get()             // Default. Wraps the HTML body element.
RootPanel.get("stockList")  // Wraps any HTML element with an id of "stockList"

ホストページは複数のルートパネルを含むことができるよ。例えば、ホストページに複数のGWTウィジェットやパネルを埋め込んでいるとき、それぞれ自分のルートパネルでラップされ独立して実装されるよ。

3. ホストページ StockWatcher.html に アプリケーションの埋め込み

StockWathcerアプリケーションをブラウザで動かすには、HTMLファイルに埋め込む必要があるよ。StockWathcerプロジェクトのホストページ StockWathcer.html は webAppCreator で生成されているよ。スターターアプリケーションのための StockWathcer.html は body 要素が空だよ。その結果、ルートパネルはbody要素を包んでいるよ。ブラウザに表示されているものは全部GWTで構築されているよ。もしアプリケーションが静的要素を持たないなら、HTMLホストページを編集する必要はだろうね。

しかし、StockWathcerは動的要素に加えて、いくつかの静的なHTMLテキスト、画像を使うよ。stockList という名前の <div> 要素のプレイスホルダーを使って、ブラウザにGWTアプリケーションを埋め込むよ。この実装方法は、既存のアプリケーションにGWTを埋め込むのに役に立つよ。

  1. StockWatcher/war/StockWatcher.html ホストページを開く
  2. head要素で、タイトルテキストを StockWatcherに変更する
  3. body要素で、<h1> 見出し StockWathcer を追加
  4. body要素で、<div> 要素を追加して id に stockList を付ける
  5. スターターアプリケーションから、必要ない要素を削除
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">

<html>
  <head>
    <meta http-equiv="content-type" content="text/html; charset=UTF-8">

    <link type="text/css" rel="stylesheet" href="StockWatcher.css">

    <title>StockWatcher</title>

    <script type="text/javascript" language="javascript" src="stockwatcher/stockwatcher.nocache.js"></script>
  </head>

  <body>

    <h1>StockWatcher</h1>

    <div id="stockList"></div>

    <iframe src="javascript:''" id="__gwt_historyFrame" tabIndex='-1' style="position:absolute;width:0;height:0;border:0"></iframe>

    <!-- 以下削除する
    <h1>Web Application Starter Project</h1>
    <table align="center">
      <tr>
        <td colspan="2" style="font-weight:bold;">Please enter your name:</td>

      </tr>
      <tr>
        <td id="nameFieldContainer"></td>
        <td id="sendButtonContainer"></td>
      </tr>
    </table>
    ここまで -->
  </body>
</html>

注: 簡潔にするため、HTMLコメントは省略している。

4. StockWatcher.javaウィジェットとパネルの実装

次に GWTウィジェットとパネルからユーザインターフェースを構築していくよ。

UI のほとんどは StockWathcer が起動するとすぐに表示されるよ。そのため onMouseLoad メソッドでそれを実装するよ。このセクションでは次のことをするよ。

  1. ウィジェットとパネルのインスタンス
  2. 株データを保持するテーブルの作成
  3. 株追加パネルとメインパネルの配置
  4. メインパネルとルートパネルの関連づけ
  5. 入力ボックスにカーソルフォーカスの移動

このセクションを順に続けていくか、最後の Summary からコピペすることもできるよ。

1. 各ウィジェットとパネルのインスタンス

1. クラスフィールドの初期化子を使って、各ウィジェットとパネルをインスタンス
StockWatcher/src/com/google/gwt/sample/stockwatcher/client/StockWatcher.java を開く。
StockWatcher.java の中で、既にあるスターターアプリケーションのコードを全部、次のコードに置き換える。

package com.google.gwt.sample.stockwatcher.client;

public class StockWatcher implements EntryPoint {

  private VerticalPanel mainPanel = new VerticalPanel();
  private FlexTable stocksFlexTable = new FlexTable();
  private HorizontalPanel addPanel = new HorizontalPanel();
  private TextBox newSymbolTextBox = new TextBox();
  private Button addStockButton = new Button("Add");
  private Label lastUpdatedLabel = new Label();

  /**
   * Entry point method.
   */
  public void onModuleLoad() {
    // TODO Create table for stock data.
    // TODO Assemble Add Stock panel.
    // TODO Assemble Main panel.
    // TODO Associate the Main panel with the HTML host page.
    // TODO Move cursor focus to the input box.

  }

}

2. 型を解決できないため、Eclipse が変数定義の警告をする。
Tip: Eclipseの効力を使うには、必要な import宣言を追加するのに suggest機能を使うことだよ。

3. 対応する import宣言の追加
X の忠告が付いているものをクリックする。
エンターを押して、"import EntryPoint (com.google.gwt.core.client.EntryPoint)" を選択する。

4. 同じように他のimport宣言のエラーも解決する。

package com.google.gwt.sample.stockwatcher.client;

import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.FlexTable;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.VerticalPanel;

public class StockWatcher implements EntryPoint {

  private VerticalPanel mainPanel = new VerticalPanel();
  private FlexTable stocksFlexTable = new FlexTable();
  private HorizontalPanel addPanel = new HorizontalPanel();
  private TextBox newSymbolTextBox = new TextBox();
  private Button addStockButton = new Button("Add");
  private Label lastUpdatedLabel = new Label();

  /**
   * Entry point method.
   */
  public void onModuleLoad() {
    // TODO Create table for stock data.
    // TODO Assemble Add Stock panel.
    // TODO Assemble Main panel.
    // TODO Associate the Main panel with the HTML host page.
    // TODO Move cursor focus to the input box.

  }

}
2. 株データを保持するテーブルの作成

株データを保持するテーブルを実装する。StockWathcerを起動したときに表示するヘッダー行を準備する。記号、価格、差額、削除のそれぞれのカラムのヘッダーのラベルを削除するために setText メソッドを使う。

1. 株データテーブルの作成
onModuleLoad メソッドのなかで、TODOコメントを 以下のコードに置き換える。

package com.google.gwt.sample.stockwatcher.client;

import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.FlexTable;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.VerticalPanel;

public class StockWatcher implements EntryPoint {

  private VerticalPanel mainPanel = new VerticalPanel();
  private FlexTable stocksFlexTable = new FlexTable();
  private HorizontalPanel addPanel = new HorizontalPanel();
  private TextBox newSymbolTextBox = new TextBox();
  private Button addStockButton = new Button("Add");
  private Label lastUpdatedLabel = new Label();

  /**
   * Entry point method.
   */
  public void onModuleLoad() {
    // 株データテーブルの作成
    stocksFlexTable.setText(0, 0, "Symbol");
    stocksFlexTable.setText(0, 1, "Price");
    stocksFlexTable.setText(0, 2, "Change");
    stocksFlexTable.setText(0, 3, "Remove");

    // TODO Assemble Add Stock panel.
    // TODO Assemble Main panel.
    // TODO Associate the Main panel with the HTML host page.
    // TODO Move cursor focus to the input box.

  }

}
3. 株追加パネルとメインパネルの配置

ウィジェットをレイアウトするために、株追加パネルとメインパネルの二つを組み立てるよ。最初に入力ボックスと追加ボタンをラップした水平パネル、株追加パネルを組み立てるよ。それから、株リストテーブル、株追加パネル、タイムスタンプをレイアウトした垂直パネル、メインパネルを組み立てるよ。

1. 株追加パネルとメインパネルの中のウィジェットをレイアウトするよ。
onModuleLoadメソッドの中で、TODOコメントを以下のものに置換するよ。

package com.google.gwt.sample.stockwatcher.client;

import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.FlexTable;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.VerticalPanel;

public class StockWatcher implements EntryPoint {

  private VerticalPanel mainPanel = new VerticalPanel();
  private FlexTable stocksFlexTable = new FlexTable();
  private HorizontalPanel addPanel = new HorizontalPanel();
  private TextBox newSymbolTextBox = new TextBox();
  private Button addStockButton = new Button("Add");
  private Label lastUpdatedLabel = new Label();

  /**
   * Entry point method.
   */
  public void onModuleLoad() {
    // 株データテーブルの作成
    stocksFlexTable.setText(0, 0, "Symbol");
    stocksFlexTable.setText(0, 1, "Price");
    stocksFlexTable.setText(0, 2, "Change");
    stocksFlexTable.setText(0, 3, "Remove");

    // 株追加パネルの組み立て.
    addPanel.add(newSymbolTextBox);
    addPanel.add(addStockButton);

    // メインパネルの組み立て
    mainPanel.add(stocksFlexTable);
    mainPanel.add(addPanel);
    mainPanel.add(lastUpdatedLabel);

    // TODO Associate the Main panel with the HTML host page.
    // TODO Move cursor focus to the input box.

  }

}
4. メインパネルとルートパネルの関連づけ

HTMLホストページに埋め込まれるGWTウィジェットやパネルは、ルートパネルに含まれていなければいけないよ。ルートパネルと垂直パネル mainPanel を関連づけるよ。ルートパネルは、id が "stocklist" の StockWathcer のホストページのHTML要素を包むよ。ここでは それは <div>要素だよ。

1. メインパネルをルートパネルを通したホストページと関連づけるよ
onModuleLoad メソッドの中で、TODOコメントを次のコードと置換するよ。

package com.google.gwt.sample.stockwatcher.client;

import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.FlexTable;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.VerticalPanel;

public class StockWatcher implements EntryPoint {

  private VerticalPanel mainPanel = new VerticalPanel();
  private FlexTable stocksFlexTable = new FlexTable();
  private HorizontalPanel addPanel = new HorizontalPanel();
  private TextBox newSymbolTextBox = new TextBox();
  private Button addStockButton = new Button("Add");
  private Label lastUpdatedLabel = new Label();

  /**
   * Entry point method.
   */
  public void onModuleLoad() {
    // 株データテーブルの作成
    stocksFlexTable.setText(0, 0, "Symbol");
    stocksFlexTable.setText(0, 1, "Price");
    stocksFlexTable.setText(0, 2, "Change");
    stocksFlexTable.setText(0, 3, "Remove");

    // 株追加パネルの組み立て.
    addPanel.add(newSymbolTextBox);
    addPanel.add(addStockButton);

    // メインパネルの組み立て
    mainPanel.add(stocksFlexTable);
    mainPanel.add(addPanel);
    mainPanel.add(lastUpdatedLabel);

    // メインパネルをHTMLホストページと関連づける
    RootPanel.get("stockList").add(mainPanel);

    // TODO Move cursor focus to the input box.

  }

}

Eclipse はRootPanel に警告して、import宣言を含むように提案するよ。

2. import宣言を含める

import com.google.gwt.user.client.ui.RootPanel;
5. 入力ボックスにカーソルフォーカスの移動

最後に、StockWathcerがロードされたときに、カーソルフォーカスを入力ボックスに移動するよ。

1. onModuleLoad メソッドで、TODOコメントを以下のコードに置き換えるよ。

package com.google.gwt.sample.stockwatcher.client;

import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.FlexTable;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.VerticalPanel;

public class StockWatcher implements EntryPoint {

  private VerticalPanel mainPanel = new VerticalPanel();
  private FlexTable stocksFlexTable = new FlexTable();
  private HorizontalPanel addPanel = new HorizontalPanel();
  private TextBox newSymbolTextBox = new TextBox();
  private Button addStockButton = new Button("Add");
  private Label lastUpdatedLabel = new Label();

  /**
   * Entry point method.
   */
  public void onModuleLoad() {
    // 株データテーブルの作成
    stocksFlexTable.setText(0, 0, "Symbol");
    stocksFlexTable.setText(0, 1, "Price");
    stocksFlexTable.setText(0, 2, "Change");
    stocksFlexTable.setText(0, 3, "Remove");

    // 株追加パネルの組み立て.
    addPanel.add(newSymbolTextBox);
    addPanel.add(addStockButton);

    // メインパネルの組み立て
    mainPanel.add(stocksFlexTable);
    mainPanel.add(addPanel);
    mainPanel.add(lastUpdatedLabel);

    // メインパネルをHTMLホストページと関連づける
    RootPanel.get("stockList").add(mainPanel);

    // カーソルフォーカスを入力ボックスに移動する
    newSymbolTextBox.setFocus(true);

  }

}
要約

ここまでで出来たことをここで示すよ。

package com.google.gwt.sample.stockwatcher.client;

import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.FlexTable;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.VerticalPanel;

public class StockWatcher implements EntryPoint {

  private VerticalPanel mainPanel = new VerticalPanel();
  private FlexTable stocksFlexTable = new FlexTable();
  private HorizontalPanel addPanel = new HorizontalPanel();
  private TextBox newSymbolTextBox = new TextBox();
  private Button addStockButton = new Button("Add");
  private Label lastUpdatedLabel = new Label();

  /**
   * Entry point method.
   */
  public void onModuleLoad() {
    // Create table for stock data.
    stocksFlexTable.setText(0, 0, "Symbol");
    stocksFlexTable.setText(0, 1, "Price");
    stocksFlexTable.setText(0, 2, "Change");
    stocksFlexTable.setText(0, 3, "Remove");

    // Assemble Add Stock panel.
    addPanel.add(newSymbolTextBox);
    addPanel.add(addStockButton);

    // Assemble Main panel.
    mainPanel.add(stocksFlexTable);
    mainPanel.add(addPanel);
    mainPanel.add(lastUpdatedLabel);

    // Associate the Main panel with the HTML host page.
    RootPanel.get("stockList").add(mainPanel);

    // Move cursor focus to the input box.
    newSymbolTextBox.setFocus(true);

  }

}

5. ホストモードでレイアウトのテスト

AJAXアプリケーション開発で GWT を使う一つの利点は、ホストモードブラウザを更新してすぐにコードの変更の効果を見ることが出来ることだよ。Eclipse で StockWathcer をデバッグモードで実行して、変更を見ることが出来るよ。StockWathcerを再起動せずに Java パースペクティブ と Debugパースペクティブを切り替えることが出来るよ。

1. 編集したファイルを保存
StockWatcher.java を保存

2. StockWathcerをホストモードで起動
Eclipse のメニューバーから、Run > Debug を選択
もし、Eclipse を使ってないなら、コマンドラインから "ant hosted" を入力

3. ホストモードブラウザは StockWathcerアプリケーションの最初の繰り返しを表示するよ。
http://code.google.com/intl/ja/webtoolkit/doc/1.6/tutorial/images/BuildUI.png

StockWatcher は flexテーブルのヘッダ、入力ボックス、追加ボタンを表示するよ。まだLabelのテキストをセットしてないので何も表示されないよ。株の更新メカニズムを実装すると表示されるでしょう。

4. ホストモードでの実行を離れる
このチュートリアルの残りで、頻繁にホストモードでのテストを行うよ。

ホストモードの更新

ソースコードの変更の後にホストモードでアプリケーションをいつも再起動する必要はないよ。代わりに変更を保存した後にブラウザの再読込ボタンをクリックすれば、自動的に再コンパイルされて新しいバージョンが開くよ。

ベストプラクティス: 時々、再読込していないのに変更されていることに気がつくかもしれないね。これはホストモードがコンパイルされたコードと相互作用している結果だけど、いつもこれが当てになるわけではないよ。これは既存機能の少しの修正の場合だけ起こるよ。変更がしっかり取り込まれるために、いつも再読込する癖を付けた方がいいよ。

つぎは

ここまでで、GWTを使った基本的なUIコンポーネントを構築したよ。まだウィジェットは何も反応しないよ。

これでクライアントのイベントハンドリングの準備が出来たよ。ウィジェットにイベントを聞くように結びつけて、そのイベントに反応するようコードを書くよ。

Step 4: クライアントのイベント管理 - webとかmacとかやってみようか

Step 2: アプリケーションの設計

目次:Google Web Toolkit チュートリアル(目次) - webとかmacとかやってみようか
原文:http://code.google.com/intl/ja/webtoolkit/doc/1.6/tutorial/design.html

ここまでで、StockWatcherをコーディングするのに必要なスタブファイルを作成できてるよね。

このセクションでは、機能的要求の確認とユーザインターフェースの設計をするよ。

機能的要求の確認

最初に、StockWatcher アプリ は 6つのことができるように望んでいるね。

  • 株式を追加する機能を提供する。(不正な文字や存在していることの検証を与える)
  • それぞれの株式に次の情報を表示する。シンボル、価格、最終更新からの変化。
  • リストから株式を削除する機能
  • 株式価格の更新
  • 最終更新からの差分を 数値とパーセントで計算する
  • 最終更新のタイムスタンプの表示

UIデザイン要素の確定

http://code.google.com/intl/ja/webtoolkit/doc/1.6/tutorial/images/DesignStockWatcherFinal.png

StockWatcher の機能要求を検討してから、これらの UI要素が必要だと決めた。

  • テーブル: 株データを保持
  • 二つのボタン: 株式の追加と削除ボタン
  • 入力ボックス: 証券コードを入力
  • タイムスタンプ: 最終更新日時

デザインチームは次の追加項目を提案してくる。

  • ロゴ
  • ヘッダー
  • 差額がプラスかマイナスの表示する色
静的要素の取り込み

GWT は HTMLページのレイアウトに左右されない。GWT アプリケーションは、スタータアプリのようにブラウザウィンドウ全体にとることができるよ。または、このチュートリアルの Getting Start ページのように既存のページに埋め込むこともできるよ。

StockWatcherアプリは静的/動的要素の両方を含むよ。Google CodeロゴとヘッダーはHTML干すとページの静的要素だよ。他の全ての要素は GWTウィジェットとパネルを使って動的に作られているよ。

次は

ここまでで、StockWatcherアプリの仕様を検討したよ。StockerWatcherが何をするかはっきりしたね。実装に必要なUI要素と、それらをどう置くかわかったね。

これで GWTウィジェットとパネルを使ったユーザインターフェースを構築する準備が整ったよ。

Step 3: ユーザインターフェースの構築 - webとかmacとかやってみようか