GAE や Google Closure Library などの話題を扱います。python 初心者です。

2010年3月30日火曜日

Closure Library - TreeControl

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
Closure Library の goog.ui.tree.TreeControl を試してみた。
TreeControl (Closure Library API Documentation - JavaScript)

TreeControler とはエクスプローラなどでおなじみのこんなやつ。


デモコードがあるので、それを参考に簡単なサンプルHTMLを作成してみた。

<!DOCTYPE HTML PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <script src="http://closure-library.googlecode.com/svn/trunk/closure/goog/base.js"></script>
    <script>
      goog.require('goog.dom');
      goog.require('goog.ui.tree.TreeControl');
    </script>

    <link rel="stylesheet" href="/css/demo.css">
    <link rel="stylesheet" href="/css/tree.css">

 <script>
  var testData = ['ROOT',
  [['folderA',
   [['A1', [['fileA1-1'], ['fileA1-2']]],
    ['A2', [['fileA2-1'], ['fileA2-2']]]]],
   ['folderB',
   [['B1', [['fileB1-1'], ['fileB1-2']]],
    ['B2', [['fileB2-1'], ['fileB2-2']]]]]]];

 </script>
  </head>
 <body>
  <h1>Closure Libraray: tree control  sample</h1>
  <hr/>
  <div id="treeContainer" style="width:400px"></div> 
<script>
 function makeTree() {
  var treeConfig = goog.ui.tree.TreeControl.defaultConfig;
  treeConfig['cleardotPath'] = "/images/tree/cleardot.gif";
  var tree = new goog.ui.tree.TreeControl('root', treeConfig);

  createTreeFromTestData(tree, testData);

  tree.render(goog.dom.$('treeContainer'));
 }

 function createTreeFromTestData(node, data) {
  node.setHtml(data[0]);
  if (data.length > 1) {
   var children = data[1];
   var childNotCollapsible = 3; // Hard coded to reduce randomness.

   for (var i = 0; i < children.length; i++) {
    var child = children[i];
    var childNode = node.getTree().createNode('');

    node.add(childNode);
    createTreeFromTestData(childNode, child);

    if (i == childNotCollapsible && child.length > 1) {
     childNode.setIsUserCollapsible(false);
     childNode.setExpanded(true);
     nonCollapseNode = childNode;
    }

   }
  }
 }

 makeTree();
</script>


 </body>
</html>

closure-library付属の tree関係の画像(images/tree/*)と CSS(css/tree.css) を適切な場所に置いておくのがポイント。

実行するとこんな感じ。

2010年3月1日月曜日

Closure Library - XhrManager (3) 全部終わったかどうかを知る

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
XhrManager検証続き。

今回はXhrManagerを使い複数のリクエストを送った場合、それらすべてが終了したことを知る方法を調べる。

XhrManager 自体にはそういった機能は無いので基本的に自前で実装する必要がありそうだ。考えられる方法としては、リクエストの状態を管理しておき(未送信 or 送信済)、すべての状態が”送信済”になるかを判断する、というのがある。

具体的な方法は2つ考えられて、一つは XhrIo の処理終了毎に条件をチェックして全部終わっていたら所定の関数を呼ぶ方法。通常はこれで十分。

もう一つは一定間隔で状態をチェックし、全部終了していれば所定の関数を呼ぶ方法。一つ目に比べると複雑になるが、処理全体のタイムアウト処理を入れることができるのがメリットとしてある。


この2番目の処理にうってつけなのが以前紹介した goog.async.ConditionalDelay。

これを使うと一定間隔[ms]で関数が呼ばれ、この関数が true を返すまでは繰り返すという動作が作れる。

今回の場合、
条件:すべてのリクエストが送信済
間隔:ポーリング間隔(500ms〜1000ms ぐらいで十分?)


前回までのサンプルに手を入れて ConditionalDelay を使った終了処理を実装してみよう。
xhrio3.html
<script>
  goog.require('goog.async.ConditionalDelay');
  goog.require('goog.net.XhrManager');
  goog.require('goog.ui.CustomButton');
  goog.require('goog.ui.decorate');
</script>

<script>
    var xhrm_ = new goog.net.XhrManager(1, {}, 1, 10, 3000);

  function output(msg) {
    var console = goog.dom.$('console');
    console.innerHTML = console.innerHTML + msg + "<br/>";
  }

  function handleResponse(e) {
    var xhr = e.target;
    output(xhr.getStatus() + " : " + xhr.getResponseText());
  }

  function start(e) {
      delay.start(500, -1);
      btn_start.setEnabled(false);
      btn_cancel.setEnabled(true);
      for (i=0; i < 10; i++) {
          xhrm_.send(
              i, '/echo?name=' + i, 'GET', null, {}, 1, handleResponse);
        output('send: ' + i);
      }
  }
  function cancel(e) {
      btn_start.setEnabled(true);
      btn_cancel.setEnabled(false);
      for (i=0; i < 20; i++) {
          xhrm_.abort(i, true);
      }
      alert('cancel');
  }

  var delay = new goog.async.ConditionalDelay(
    function() {
        var count = xhrm_.getOutstandingCount();
        goog.dom.$('info').innerHTML = 'getOutstandingCount: ' + count;
        return (count == 0);
    });

delay.onSuccess = function() {
      btn_start.setEnabled(true);
      btn_cancel.setEnabled(false);
      alert('success!');
}

サンプルでは状態管理は行わず安易に XhrManagerの処理中件数(getOutstandingCount)だけで判断している。

実行してみよう。
処理中は getOutstandingCount の値が増減する。やがて終わると..
出た。

goog.async.ConditionalDelay はタイムアウト指定もできるので、複数リクエスト全体の処理時間に対してのタイムアウト処理も行うことができる。この組み合わせは、なかなか便利だ。

2010年2月28日日曜日

Closure Library - XhrManager (2) キャンセル処理

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
(前回)WEB開発メモ: Closure Library - XhrManager

XhrManager 検証続き。

処理をキャンセル(abort)してみる。前回のコードにキャンセルボタンを追加し、これを押した時に XhrIo通信をキャンセルさせる。

function cancel(e) {
  btn_start.setEnabled(true);
  btn_cancel.setEnabled(false);
  for (i=0; i < 20; i++) {
     xhrm_.abort(i);
  }
  alert('cancel');
}

abort() には IDを指定する必要がある。サンプルでは 0〜19 の値を割り当てていたので、ここでは安易に全部の IDで abortをかけてみた。実際には呼び出し側で処理中の ID を覚えておいて必要なものだけ abortするのがいいだろう。

サンプル実行
無事?キャンセルできた。キャンセル時もコールバック関数は呼び出されていることがわかる。またabortをかけられた XhrIo は  getStatus() を呼び出すと -1 を返しているのがわかる。

abortの第2引数は opt_force となっていて、これを true にするとコールバック関数は呼び出されなくなる。



abort(idopt_force)
Aborts the request associated with id.
Arguments:
id :
The id of the request to abort.
opt_force :
boolean=
If true, remove the id now so it can be reused. No events are fired and the callback is not called when forced.
やってみる。
function cancel(e) {
  btn_start.setEnabled(true);
  btn_cancel.setEnabled(false);
  for (i=0; i < 20; i++) {
     xhrm_.abort(i, ture);
  }
  alert('cancel');
}

確かに呼ばれなくなった(-1表示が無い)。

2010年2月27日土曜日

Closure Library - Delay と ConditionalDelay

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
goog.async.Delay

goog.async.Dealy を使うと一定時間後にある関数を呼び出すことができる。
Delay (Closure Library API Documentation - JavaScript)

<script>
goog.require('goog.dom');
goog.require('goog.async.Delay');
</script>

<script>
function output(msg) {
var console = goog.dom.$('console');
console.innerHTML = console.innerHTML + msg + "
";
}

var counter_ = 1;
var delay = new goog.async.Delay(
function() {
output("count: " + counter_++);
delay.start();
}, 1000);
delay.start();
</script>

呼び出された関数内でさらに start() を呼び出すと、一定間隔で繰り返し関数を呼ぶことができる。


goog.async.ConditionalDelay

こちらは条件付きの Delay が作れる。コールバック関数の戻り値が true になるまでは指定した時間間隔で関数が呼び出され続ける。
ConditionalDelay (Closure Library API Documentation - JavaScript)

戻り値に false を固定で返せば延々と一定間隔で呼び出され続けるが、タイムアウトを設定することもできる。一定間隔でリトライをかけるような通信処理などに使えそうだ。

onSuccess, onFailure に成功時、失敗時(タイムアウト)の処理を書くこともできる。

サンプル:
<script>
      goog.require('goog.dom');
      goog.require('goog.async.ConditionalDelay');
    </script>

    <script>
      function output(msg) {
        var console = goog.dom.$('console');
        console.innerHTML = console.innerHTML + msg + "<br/>";
      }

      var counter_ = 1;
      var delay = new goog.async.ConditionalDelay(
          function() {
              output("count: " + counter_++);
              return counter_ >= 10;;
          });
      delay.onSuccess = function() { alert('good job!');};
      delay.onFailure = function() { alert('timeout');};
      delay.start(1000, 5000);
    </script>

start( ) の第2引数はデフォルトで0。これは0秒を表していて1回実行後は必ずタイムアウトになる(1回だけ実行したい場合に使える)。タイムアウト無しにする場合は負の値を指定する。


以下はソースコードのコメントから引用。

* Example:
 *
 *  function deferred() {
 *     var succeeded = false;
 *     // ... custom code
 *     return succeeded;
 *  }
 *
 *  var deferredCall = new goog.async.ConditionalDelay(deferred);
 *  deferredCall.onSuccess = function() {
 *    alert('Success: The deferred function has been successfully executed.');
 *  }
 *  deferredCall.onFailure = function() {
 *    alert('Failure: Time limit exceeded.');
 *  }
 *
 *  // Call the deferred() every 100 msec until it returns true,
 *  // or 5 seconds pass.
 *  deferredCall.start(100, 5000);
 *
 *  // Stop the deferred function call (does nothing if it's not active).
 *  deferredCall.stop();

2010年2月26日金曜日

Closure Library - XhrManager

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
Closure Library には XMLHttpRequest のラッパーである XhrIo が用意されている。

Asynchronous XMLHttpRequests with XhrIo - Closure Library - Google Code

これを使うと簡単にサーバとの通信が行える。こんな感じ(Googleのページより引用)。

  goog.net.XhrIo.send(dataUrl, function(e) {
      var xhr = e.target;
      var obj = xhr.getResponseJson();
      log('Received Json data object with title property of "' +
          obj['title'] + '"');
      alert(obj['content']);
  });


この XhrIo を使って複数のリクエストを扱う用途で XhrIoPool と XhrManager が用意されている。
XhrIoPool (Closure Library API Documentation - JavaScript)
XhrManager (Closure Library API Documentation - JavaScript)

XhrIoPool はその名の通りで、複数の XhrIo のインスタンスをプールして使いまわす用途で使う。XhrManager はその XhrIoPool を内部に持っていて複数リクエストを簡単に扱えるようにしたもの。XhrIoPool を使って自前で実装する方法もあるが、特別問題が無い限りは XhrManager を使った方が楽(※XhrIoPool を使った場合、インスタンスの開放やイベントの管理などを自前でやらなければならない)。


今回はこの XhrManager を試してみた。コードはこんな感じ。

xhrio1.html
<script>goog.require('goog.net.XhrManager');
</script>
<script>
var xhrm_ = new goog.net.XhrManager();

function output(msg) {
var console = goog.dom.$('console');
console.innerHTML = console.innerHTML + msg + "<br/>";
}

function handleResponse(e) {
var xhr = e.target;
output(xhr.getStatus() + " : " + xhr.getResponseText());
}

function start(e) {
  for (i=0; i < 10; i++) {
      xhrm_.send(
          i,
          '/echo?name=' + i,
          'GET',
          null,
          {},
          1,
          handleResponse);
    output('send: ' + i);
  }
}

</script>

基本的には (1)XhrManagerのインスタンスを作り、(2) send()で送信するだけ。上のサンプルでは 10回 send を実行し、結果を画面に書き出している。

呼び出している /echo はこんな感じ ※GAEで動作。
import os, time
from google.appengine.ext import webapp
from google.appengine.ext.webapp import util
from google.appengine.ext.webapp import template

class MainHandler(webapp.RequestHandler):
  def get(self):
    self.response.out.write(self.request.get('name'))


def main():
  application = webapp.WSGIApplication(
    [('/echo', MainHandler),
                  ], debug=True)
  util.run_wsgi_app(application)


if __name__ == '__main__':
  main()

単純に name パラメータの内容を書き出すだけ。これを表示することで、何番目のリクエストが処理されているかがわかる。

実行するとこんな感じ。
XhrManager.send は通信結果を待たずに 10本連続して実行される(非同期)。画面上 send: 0, 1,.. と表示されているのがそう。その後、処理の終わったものから 200: 0 (ステータスコード:id)と表示されていく。処理は並行実行されて、送信順とは違った順番となっているのがわかる。

2010年2月1日月曜日

Closure Library - トグルボタン

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
トグルボタンとはボタンを1回押すと押したままの状態を保持し、もう一度押すともとに戻るボタンのこと。Closure Library では ui.ToggleButton が用意されている。サンプルを作ってみた。

サンプルでは2つのボタンを用意し、交互に押し状態が変わるようにしてみた。

toggle.html
goog.require('goog.dom');
  goog.require('goog.events');
  goog.require('goog.ui.ToggleButton');

goog.ui.ToggleButton を require しておく。


メインはこんな感じ。
<div id="toggle1" class="goog-toggle-button">Toggle1</div>
    <div id="toggle2" class="goog-toggle-button">Toggle2</div>

    <script>
      var btn1 = goog.ui.decorate(goog.dom.$('toggle1'));
      var btn2 = goog.ui.decorate(goog.dom.$('toggle2'));
      btn1.setDispatchTransitionEvents(goog.ui.Component.State.ALL, true);
      btn2.setDispatchTransitionEvents(goog.ui.Component.State.ALL, true);

      goog.events.listen(btn1, goog.ui.Component.EventType.ACTION,
        function(e) {
          btn2.setChecked(!e.target.isChecked());
        });
      goog.events.listen(btn2, goog.ui.Component.EventType.ACTION,
        function(e) {
          btn1.setChecked(!e.target.isChecked());
        });
      btn1.setChecked(true);
      btn2.setChecked(false);
    </script>

書き方は通常のボタンと同じだが setDispatchTransitionEvents( ) がミソ(のようだ)。それぞれのボタンのイベントに相互の状態(setChecked)を変えるコードを書いて見た目を切り替えている。



下記リンクからサンプルが見られる。
http://closuresample.appspot.com/html/toggle.html


2010年1月29日金曜日

Closure Library - ポップアップメニュー

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
goog.ui.PopupMenu と goo.ui.MenuButton を試した。

PopupMenu は名前のまんまのメニュー。ボタンや特定のエリアにくっつけて使う。
上記はボタンにくっつけた例。
作成方法は先日紹介したボタンと同様に2通りある。
(1) Popup インスタンスを作成する方法
(2) 特定の DIV を装飾(decorate)する方法

(2)の方法を使った場合はこんな感じ。
<h2>(1) Decorated Popup</h2>
    <button id="button1">Popup</button>

    <div id="menu1" for="button1" class="goog-menu" style="display:none">
      <div class="goog-menuitem">monkey</div>
      <div class="goog-menuitem">dog</div>
        <div class="goog-menuseparator"></div>
      <div class="goog-menuitem">cat</div>
        <div class="goog-menuseparator"></div>
      <div class="goog-menuitem">dolphin</div>
    </div>

    <script>
      var pm1 = new goog.ui.PopupMenu();
      pm1.setToggleMode(true);
      pm1.decorate(goog.dom.$('menu1'));

      goog.events.listen(pm1, goog.ui.Component.EventType.ACTION,
        function(e) {
          alert(e.target.getCaption());
        });
    </script>

ボタンと PopupMenuを作り紐づける。ボタン以外の何でも紐付けすることができる。



次に MenuButton。こちらはボタンと PopupMenu を組み合わせたもの。GMailで使われているあのスタイル。

コードはこんな感じ。
<h2>(2) Decorated MenuButton</h2>
    <div id="button2" class="goog-menu-button" title="MenuButton">
      <span style="vertical-align:middle">Format</span>
      <div id="menu2" class="goog-menu">
        <div class="goog-menuitem">monkey</div>
        <div class="goog-menuitem">dog</div>
        <div class="goog-menuseparator"></div>
        <div class="goog-menuitem">cat</div>
        <div class="goog-menuseparator"></div>
        <div class="goog-menuitem">dolphin</div>
      </div>
    </div>

    <script>
      var btn2 = goog.ui.decorate(goog.dom.$('button2'));
      goog.events.listen(btn2, goog.ui.Component.EventType.ACTION,
        function(e) {
          alert(e.target.getCaption());
        });
    </script>

こちらはボタンの中に PopupMenuが入った感じだ。ボタンとPopupMenuを組み合わせる場合はこちらの方が簡単に書ける。

デモはこちら:
http://closuresample.appspot.com/html/popup.html

2010年1月23日土曜日

Closure Library を Closure Compiler でコンパイル

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
JavaScriptファイルの圧縮
下記が非常に参考になった。
Closure Library で作る簡易ドローツール(Python Hack-a-thon #3 資料) - WebOS Goodies

まだまだ日本語情報が少ない中、Closure Tools に関する貴重な情報がたくさん載っている。

このページの中の「JavaScriptファイルの圧縮」を参考にして、Closure Library を使った簡単な JavaScriptをコンパイルしてみる。

レシピ
まず HTMLファイル:html/button-min.html
<!DOCTYPE HTML PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">

    <script src="http://closure-library.googlecode.com/svn/trunk/closure/goog/base.js"></script>
    <script src="/js/button.js"></script>
    <link rel="stylesheet" href="/css/dialog.css">
    <link rel="stylesheet" href="/css/button.css">
  </head>
  <body>
    <h1>Closure Libraray: button sample</h1>
    <hr/>

    <h2>(1) goog.ui.Button</h2>
    <div id="button1"></div>

    <h2>(2) goog.ui.decorate</h2>
    <div id="button2" class="goog-custom-button">
      <span style="color: blue">BUTTON-<b>2</b></span>
    </div>

    <div id="button3" class="goog-custom-button">
      <span style="font-size: 7pt">BUTTON-<b>3</b></span>

    </div>

  <script>
    setupButtons();
  </script>

  </body>
</html>

goog.ui.CustomButton や decorate を使っている。Closure Library はとりあえず googleのサイトを参照している(コンパイル後に取り除く予定)。

JavaScriptファイル:js/button.js
goog.require('goog.ui.CustomButton');
goog.require('goog.ui.decorate');

function setupButtons() {
        var btn1 = new goog.ui.CustomButton('BUTTON-1');
        btn1.render(goog.dom.$('button1'));
        goog.events.listen(btn1, goog.ui.Component.EventType.ACTION,
            function(e) {
                var target = e.target;
                alert('button1: ' + target);
            }); 

        var btn2 = goog.ui.decorate(goog.dom.$('button2'));
        goog.events.listen(btn2, goog.ui.Component.EventType.ACTION,
            function(e) {
                var target = e.target;
                alert('button2: ' + target);
            }); 
        var btn3 = goog.ui.decorate(goog.dom.$('button3'));
}

ボタンの定義を行っている。button-min.html の最後で setupButtons()が呼び出される。

実行するとこんな画面が出る。

コンパイル
やってみよう。

closure ライブラリは ~/Development/closure-tools/closure に、
closuer compiler は ~/Development/closure-tools/compiler にあるとする。

python ~/Development/closure-tools/closure/bin/calcdeps.py \
            -i button.js \
            -p ~/Development/closure-tools/closure \
            -o compiled \
            -c ~/Development/lib/closure-tools/compiler/compiler.jar \
            -f "--comilation_level=ADVANCED_OPTIMIZATIONS" > button-min.js
calcdeps.py: Scanning files...
calcdeps.py: Finding Closure dependencies...
calcdeps.py: Compiling with the following command: java -jar /Users/hashi/Development/lib/closure-tools/compiler/compiler.jar --js /Users/hashi/Development/closure-tools/closure/go
   :
   :
.js --js /Users/hashi/Development/closure-tools/closure/goog/ui/custombutton.js --js button.js --compilation_level=ADVANCED_OPTIMIZATIONS
$

数分後に button-min.js が生成された(PowerPCは今となっては非力でとても重い..)。
$ ls -l 
total 48
-rw-r--r--  1 hashi  hashi  19163  1 22 21:35 button-min.js
-rw-r--r--  1 hashi  hashi    593  1 22 21:18 button.js

サイズが大きくなっているのは Closure Library を取り込んでいるため。

中身はこんな感じ。


これで元の JavaScriptを差し替えてみよう。

新バージョン
button-min.html を書き換える。
書き換え前:
  6     <script src="http://closure-library.googlecode.com/svn/trunk/closure/goo    g/base.js"></script>
  7     <script src="/js/button.js"></script>
これを下のようにする。
書き換え後:
  6     <script src="/js/button-min.js"></script>

動かしてみよう。

ボタンがうまく表示されていない。問題があるようだ。
Safariのエラーログを見ると setupButtons()が見つけられないとのこと。



ドキュメントを見ると ADVANCED_OPTIMIZATIONS を付けた場合、呼び出しの無い関数は削除されてしまうとのこと。
Advanced Compilation and Externs - Closure Compiler - Google Code

setupButtons()の呼び出しは HTML内だけに記述してあって button.js には定義だけしか書いていない。コンパイラは button.js だけしか見ていないので使われていない関数と判断したようだ。

オプションから ADVANCED_OPTIMIZATIONS を外して再度コンパイルしてみる。
python ~/Development/closure-tools/closure/bin/calcdeps.py \
            -i button.js \
            -p ~/Development/closure-tools/closure \
            -o compiled \
            -c ~/Development/closure-tools/compiler/compiler.jar \
            > button-min.js

$ ls -l
-rw-r--r--  1 hashi  hashi  133677  1 22 22:12 button-min.js
-rw-r--r--  1 hashi  hashi     593  1 22 21:18 button.js
サイズはずいぶん大きくなった。さっきのが 19,163バイトだったので約7倍。

さて実行しなおしてみよう。

出た。

比較
コンパイル前とコンパイル後でどれだけ違うか比べてみた。サンプルは GAEにデプロイしてある。

コンパイル前
http://closuresample.appspot.com/html/button.html

コンパイル後
http://closuresample.appspot.com/html/button-min.html


結果はこの通り。※Safariの Webインスペクタで計測

コンパイル前

コンパイル後


JavaScriptの読み込み時間が8割近く短縮された。全体でも約1/3に短縮。体感速度も全然速い。

- - - -
Closure Compiler は使えそうだ。ただし開発中は未コンパイル状態なのでやっぱり遅いまま。開発効率にかかわるのでどうにか改善したいところだが。

2010年1月22日金曜日

Mac OS X v10.5 (PowerPC) で Closure Compiler を使う(までの長い道のり)

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
例外発生
Closure Compiler を試すべく compiler.jar をダウンロードして実行するも例外発生。
$ java -jar compiler.jar --help
Exception in thread "main" java.lang.UnsupportedClassVersionError: Bad version number in .class file
     at java.lang.ClassLoader.defineClass1(Native Method)
     at java.lang.ClassLoader.defineClass(ClassLoader.java:676)
     at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:124)
     at java.net.URLClassLoader.defineClass(URLClassLoader.java:260)
     at java.net.URLClassLoader.access$100(URLClassLoader.java:56)
     at java.net.URLClassLoader$1.run(URLClassLoader.java:195)
     at java.security.AccessController.doPrivileged(Native Method)
     at java.net.URLClassLoader.findClass(URLClassLoader.java:188)
     at java.lang.ClassLoader.loadClass(ClassLoader.java:317)
     at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:280)
     at java.lang.ClassLoader.loadClass(ClassLoader.java:252)
     at java.lang.ClassLoader.loadClassInternal(ClassLoader.java:375)
調べると、どうも Closure Compiler は Java1.6以上で動作するらしい。自分のノート (Mac OS X v10.5 / PowerPC)のバージョンを調べると Java1.5だった。
$ java -version
java version "1.5.0_22"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.5.0_22-b03-333-9M3125)
Java HotSpot(TM) Client VM (build 1.5.0_22-147, mixed mode, sharing)

Java1.6
Mac OS X 10.5 でも Java1.6が提供されているっぽい。
About the security content of Java for Mac OS X 10.5 Update 6

実際 Library内を見ると 1.6 というのが見つかった。
$ ls -l /System/Library/Frameworks/JavaVM.framework/Versions/
total 56
lrwxr-xr-x  1 root  wheel    5 12 18 09:10 1.3 -> 1.3.1
drwxr-xr-x  3 root  wheel  102  9 29  2007 1.3.1
lrwxr-xr-x  1 root  wheel    5  9 17 17:25 1.4 -> 1.4.2
lrwxr-xr-x  1 root  wheel    3 10 27  2007 1.4.1 -> 1.4
drwxr-xr-x  8 root  wheel  272 11 19  2006 1.4.2
lrwxr-xr-x  1 root  wheel    5 12 18 09:10 1.5 -> 1.5.0
drwxr-xr-x  8 root  wheel  272 11 19  2006 1.5.0
lrwxr-xr-x  1 root  wheel    5 12 18 09:10 1.6 -> 1.6.0
drwxr-xr-x  8 root  wheel  272 10  2  2008 1.6.0
drwxr-xr-x  9 root  wheel  306 12 18 09:11 A
lrwxr-xr-x  1 root  wheel    1 12 18 09:11 Current -> A
lrwxr-xr-x  1 root  wheel    3 12 18 09:10 CurrentJDK -> 1.5
JDKの差し替え方法の情報も見つかった。
Gridshore » Java 6 for Mac OSX leopard

少し調べてみる。

まずJAVA_HOME を見てみる。
echo $JAVA_HOME
/System/Library/Frameworks/JavaVM.framework/Home
この Homeはどこにリンクしているのか。
ls -l $JAVA_HOME
lrwxr-xr-x  1 root  wheel  24 12 18 09:10 /System/Library/Frameworks/JavaVM.framework/Home -> Versions/CurrentJDK/Home
CurrentJDK はどこにリンクしているのか。
$ ls -l /System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK
lrwxr-xr-x  1 root  wheel  3 12 18 09:10 /System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK -> 1.5
つまり $JAVA_HOME は
/System/Library/Frameworks/JavaVM.framework/Versions/1.5 にリンクしている。

一方 javaコマンドはどうか。
$ which java
/usr/bin/java
$ ls -l /usr/bin/java
lrwxr-xr-x  1 root  wheel  74 12 18 09:10 /usr/bin/java -> /System/Library/Frameworks/JavaVM.framework/Versions/Current/Commands/java
Current はどこへリンクしているのか。
$ ls -l  /System/Library/Frameworks/JavaVM.framework/Versions/Current
lrwxr-xr-x  1 root  wheel  1 12 18 09:11 /System/Library/Frameworks/JavaVM.framework/Versions/Current -> A
なるほど。Current と CurrentJDK を差し替えれば 1.6 になりそうだ。
やってみよう。
$ cd /System/Library/Frameworks/JavaVM.framework/Versions/
$ sudo rm CurrentJDK
$ sudo ln -s 1.6 CurrentJDK
java コマンドの A は何に差し替えればいいのだろうか。わからずとりあえず java実行。
$ java
Unable to launch "/System/Library/Frameworks/JavaVM.framework/Home/bin/java" (86)
だめだな。

Java Preferences.app
いろいろ探していると
アプリケーション > ユーティリティ> Java Preferences.app
なるものを見つけた。ここで Javaのバージョンが切り替えられるらしい。

が、ここに出てくるのは 1.5 と 1.4.2 だけ。

うーむ。どういうことだ。

Apple Supports..
さらに調べると PowerPCでは Java 1.6が使えないことが判明。
OS XでJava 6が利用可能に:PPCと32-bit Intelは切り捨て - builder by ZDNet Japan

Appleによる公式サポート対象は Intel64bitのみ。
おー。なんてこった。

上記ページでは SoyLatte という FreeBSDから移植された Java6実装が紹介されていた。こっちを見てみよう。

SoyLatte
SoyLatte - Port of BSD Java

このページの PowerPC 向け(Java7)ダウンロードする。
32-bit OpenJDK 7 Beta 1 for Mac OS X 10.5 PowerPC (Beta Release): openjdk7-macppc-2009-12-16-b4.tar.bz2 (sig)
一気にバージョンが 7 になっているがまあ大丈夫だろう。

tar コマンドで解凍する。
$ tar xvfj openjdk7-macppc-2009-12-16-b4.tar.bz2
  :
  :
できたフォルダを適当なところへ移動しておく。ここでは /opt/local/openjdk を作成してその中へ入れておいた(先に移動してから解凍すれば良かった)。
$ sudo mkdir /opt/local/openjdk
$ sudo mv openjdk7-macppc-2009-12-16-b4 /opt/local/openjdk/
てっとりばやく javaコマンドを試してみる。
$ cd /opt/local/openjdk/openjdk7-macppc-2009-12-16-b4/jre/bin
$ ./java -version
openjdk version "1.7.0-internal"
OpenJDK Runtime Environment (build 1.7.0-internal-landonf_2009_12_16_12_54-b00)
OpenJDK Zero VM (build 17.0-b05, interpreted mode)
$
おー、動いた?

とりあえず javaコマンドが使えればいいので bashのエイリアスを切っておこう。
$ alias java="/opt/local/openjdk/openjdk7-macppc-2009-12-16-b4/jre/bin/java"
※ ~/.bash_profile などに書いておくと良い。

いよいよ Closure Compiler を動かしてみよう。

Compile JavaScript
$ java -jar compiler.jar --help
Usage: java [jvm-flags...] com.google.javascript.jscomp.CompilerRunner [flags...] [args...]
where flags are
 Standard flags:
   --help describes all flags
   --helpshort describes the main class' flags
   --helpxml emits XML description of all flags
  :
  :
出た。やった。

試しに hello.js をコンパイルしてみる。
$ cat hello.js
// A simple function.
function hello(longName) {
 alert('Hello, ' + longName);
}
hello('New User');
$ java -jar compiler.jar --js hello.js --js_output_file hello-compiled.js
  :
(待つ事 1分弱)
$
$ ls *.js
hello-compiled.js hello.js
中身は?
$ cat hello-compiled.js 
function hello(a){alert("Hello, "+a)}hello("New User");
出来た。ちょっとうれしい。
(ただ PowerPC で Javaを動かすのはチト重い)

2010年1月20日水曜日

GAE で closure library を使う

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
当初ローカルにあった closureフォルダ一式を GAEへ上げていたが、デプロイで時間がかかるし無駄が多い。

そこで(勝手に)googleに置いてあるライブラリを参照させてもらった。

<script src="http://closure-
 library.googlecode.com/svn/trunk/closure/goog/base.js"></script>

css はカスタマイズするので最低限のみ自前でホストする。
goog/demos/css/ 内で使うものだけ持ってきてある。

[closuresample]$ ls css
button.css common.css dialog.css
[closuresample]$ 

Closure Library - ボタン

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
ボタンを試してみた。

Button (Closure Library API Documentation - JavaScript)




デモ:
http://closuresample.appspot.com/html/button.html

ボタンの作り方には2種類ある。

一つは goog.ui.Button のオブジェクトを生成する方法。(1)がこれにあたる。
<h2>(1) goog.ui.Button</h2>
    <div id="button1"></div>
    <script>
      var btn1 = new goog.ui.CustomButton('BUTTON-1');
      btn1.render(goog.dom.$('button1'));
      goog.events.listen(btn1, goog.ui.Component.EventType.ACTION,
        function(e) {
          var target = e.target;
          alert('button1: ' + target);
        });
    </script>

もう一つはあらかじめ用意した <div> をボタン化する方法。(2)がこれにあたる。面白い。
<h2>(2) goog.ui.decorate</h2>
<div id="button2" class="goog-custom-button">
      <span style="color: blue">BUTTON-<b>2</b></span>
    </div>

    <div id="button3" class="goog-custom-button">
      <span style="font-size: 7pt">BUTTON-<b>3</b></span>

    </div>
    <script>
      var btn2 = goog.ui.decorate(goog.dom.$('button2'));
      goog.events.listen(btn2, goog.ui.Component.EventType.ACTION,
        function(e) {
          var target = e.target;
          alert('button2: ' + target);
        });
      var btn3 = goog.ui.decorate(goog.dom.$('button3'));
    </script>

こっちだと装飾がしやすい。どちらも生成したオブジェクトはイベントハンドリングを追加したりして好きにできる。

2010年1月16日土曜日

サーバ(GAE)へデプロイする

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
デプロイするためには先に GAE(Google app engine)へログインしてアプリケーションを作成しておく。




この時 Application Identifier をローカルで作ったアプリ名と合わせておく。

GAE上で作成ができたら GoogleAppEngineLauncher を使ってデプロイする。アプリを選んで "Deploy" ボタンを押すだけ。たったこれだけでデプロイができる。


なお前回シンボリックリンクで作成した Closure Library のディレクトリは今回実体としてアプリディレクトリ内に配置しておいた。

ログ出力:
*** Running appfg.py with the following flags:
    --no_cookies --email=xcatsan@mac.com --passin update
Application: closuresample; version: 1.
Server: appengine.google.com.
Scanning files on local disk.
Scanned 500 files.
Scanned 1000 files.
Scanned 1500 files.
Scanned 2000 files.
Scanned 2500 files.
Scanned 3000 files.
Scanned 3500 files.
Scanned 4000 files.
Scanned 4500 files.
Scanned 5000 files.
Scanned 5500 files.
Scanned 6000 files.
Scanned 6500 files.
Scanned 7000 files.
Scanned 7500 files.
Scanned 8000 files.
Scanned 8500 files.
Initiating update.
Password for xcatsan@mac.com: Cloning 2855 static files.
Cloned 100 files.
Cloned 200 files.
Cloned 300 files.
Cloned 400 files.
Cloned 500 files.
Cloned 600 files.
Cloned 700 files.
Cloned 800 files.
Cloned 900 files.
Cloned 1000 files.
Cloned 1100 files.
Cloned 1200 files.
Cloned 1300 files.
Cloned 1400 files.
Cloned 1500 files.
Cloned 1600 files.
Cloned 1700 files.
Cloned 1800 files.
Cloned 1900 files.
Cloned 2000 files.
Cloned 2100 files.
Cloned 2200 files.
Cloned 2300 files.
Cloned 2400 files.
Cloned 2500 files.
Cloned 2600 files.
Cloned 2700 files.
Cloned 2800 files.
Cloning 2 application files.
Uploading 1082 files and blobs.
Processed 500 out of 1082.
Processed 1000 out of 1082.
Uploaded 1082 files and blobs
Deploying new version.
Checking if new version is ready to serve.
Will check again in 1 seconds.
Checking if new version is ready to serve.
Will check again in 2 seconds.
Checking if new version is ready to serve.
Closing update: new version is ready to start serving.
Uploading index definitions.
If deploy fails you might need to 'rollback' manually.
The "Make Symlinks..." menu option can help with command-line work.
*** appcfg.py has finished with exit code 0 ***

デプロイ直後からアプリへアクセスできる。
実際に稼働中のURL↓
http://closuresample.appspot.com/



GAEでは Webベースの管理画面が用意されていて、アクセス統計やバッチ(Cron Jobs)管理、キュー管理、データ(DB)管理、などなどさまざまなことが行えるようになっている。



なおGAEでアプリ作成せずにローカルのアプリをデプロイするとエラーとなる。
:
  :
Scanned 8500 files.
Initiating update.
Password for xcatsan@mac.com: Error 403: --- begin server output ---
You do not have permission to modify this app (app_id=u'closuresample').
--- end server output ---
If deploy fails you might need to 'rollback' manually.
The "Make Symlinks..." menu option can help with command-line work.
*** appcfg.py has finished with exit code 1 ***

2010年1月15日金曜日

Google Closure Library を GAEで使う

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
Google Closure Library は Google製のJavaScriptライブラリ(フレームワーク)。

Closure Library - Google Code


Googleのアプリケーションでも使われているライブラリらしい。

UIコンポーネントも用意されている。


ダイアログとかは確かにどこかでみたデザイン。



特徴
  • Google謹製
  • よくテストされたコード(自称)
  • UI コンポーネント
  • DOM操作
  • サーバとの通信
  • アニメーション
  • データ構造
  • ユニットテスト
  • リッチテキストエディタ
  • 複数のブラウザ互換
  • などなど

ライセンスはソースコードを見ると Apache License, Version 2.0 となっていた。


APIドキュメント(英語)
Closure Library API Documentation (Closure Library API Documentation - JavaScript)

APIドキュメントは非常によく整備されている。ドキュメントが整備されているかどうかはライブラリの品質の一部であって、この点は仕事で使う場合とても重要。日本で使う場合の難点は英語のドキュメントしかないというところか。実際に動作するサンプルコード、ライブラリのソースコードなどにアクセスできる。


GAEで使う

これを GAEで使う。まずは subversionでソースをゲット。手順は下記を参考。
Getting Started with the Closure Library - Closure Library - Google Code

チェックアウトすると closure-library-read-only が作成される。
[closure-library-read-only]$ ls
LICENSE  all_tests.html closure
README  alltests.js third_party

GAEのアプリフォルダ内にここへのシンボリックリンクを作る(※とりあえず動かす。後日デプロイする時に正式な配置を考える。)

$ ln -s /Users/hashi/development/closure-library-read-only/closure \
 /Users/hashi/development/GAE/closuresample/closure


index.html を編集して Google Closure Library 用のコードを書く。今回はダイアログを表示させてみる。

index.html
1 <html>
  2   <head> 
  3     <link rel="stylesheet" href="closure/goog/demos/css/dialog.css">
  4     <script src="/closure/goog/base.js"></script>
  5     <script>
  6       goog.require('goog.ui.Dialog');
  7     </script>
  8     <script>
  9       var fu_dialog = new goog.ui.Dialog();
 10       fu_dialog.setContent('content');
 11       fu_dialog.setTitle('File upload window');
 12       fu_dialog.setButtonSet(goog.ui.Dialog.ButtonSet.CONTINUE_SAVE_CANCEL);      
 13     </script>
 14     
 15   </head>
 16   <body>
 17     <h1>name={{ name }}</h1>
 18     <ul>
 19     {% for animal in animals %}
 20       <li>{{ animal }}</li>   
 21     {% endfor %}
 22     </ul>
 23     <button onclick="fu_dialog.setVisible(true)">open dialog</button>
 24   </body>
 25 </html>

CSS は demos 配下のものを使う(CSSを指定しないとダイアログが体をなさない)。


静的ファイル

GAE でHTMLやJavaScript, CSSなどの静的ファイルを扱うにはパスを登録する必要がある。これは app.yaml に記述する。
(参考)Using Static Files - Google App Engine - Google Code

こんな感じ。
app.yaml
1 application: closuresample
  2 version: 1
  3 runtime: python
  4 api_version: 1
  5 
  6 handlers:
  7 - url: /closure
  8   static_dir: closure
  9  
 10 - url: .*
 11   script: main.py
 12 
なお handlers: は上から評価されていくので順番を間違えると後ろの方の設定が評価されない。上の例の場合、/closure を下にすると、先に .* が評価されるので、無視されてしまう。


実行

さて実行してみよう。

ボタンを押すと

出た。

- - - -
Python も初めてだし、JavaScriptもモダンなフレームワークを使うのは初めて。もちろんGAEも初めて。初めてづくしでやることだらけ。。

2010年1月14日木曜日

GAE - Django はじめの一歩

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
GAE では Django というテンプレートエンジンが使えるらしい。試してみる。

参考:
Using Templates - Google App Engine - Google Code

ここのサンプルコードをコピペして単純なコードを書く。

main.py
19 import os
 20 from google.appengine.ext import webapp
 21 from google.appengine.ext.webapp import util
 22 from google.appengine.ext.webapp import template
 23
 24 class MainHandler(webapp.RequestHandler):
 25
 26   def get(self):
 27         animals = ['monkey', 'cat', 'dog', 'dolphin']
 28         name = 'happy catsan'
 29         
 30         template_values = {
 31           'animals': animals,
 32           'name': name,
 33         }
 34         
 35         path = os.path.join(os.path.dirname(__file__), 'index.html')
 36         self.response.out.write(template.render(path, template_values))
 37 
 38
 39 def main():
 40   application = webapp.WSGIApplication([('/', MainHandler)],
 41                                        debug=True)
 42   util.run_wsgi_app(application)
 43 
 44 
 45 if __name__ == '__main__':
 46   main()

index.html
1 <html>
  2   <body>
  3     <h1>name={{ name }}</h1>
  4     <ul>
  5     {% for animal in animals %}
  6       <li>{{ animal }}</li>
  7     {% endfor %}
  8     </ul>
  9   </body>
 10 </html>

app.yaml
1 application: closuresample
  2 version: 1
  3 runtime: python
  4 api_version: 1
  5 
  6 handlers:
  7 - url: .*
  8   script: main.py


実行するとこんな感じ。



雰囲気がわかってきた。
#インデントで何度もお叱りを受けてしまったが。。

2010年1月13日水曜日

GAE - 雛形

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
GoogleAppEngineLauncher はアプリケーション新規作成時にアプリ名とフォルダを指定すると自動的に雛形を作ってくれる。例えば closuresample というアプリを /Users/hashi/development/GAE に作ると closuresample というフォルダが生成され、app.yampl, index.yaml, main.py が自動生成される。



実行すると Hello World ! が表示される。



なおアプリケーション名に使える文字種には制限がある。エラーから判断すると文字種は次の定義のようだ(正規表現)。

'^(?!-)[a-z\d\-]{1,100}$'

アンダーバーや大文字は使えない。

困ったことに新規作成時には文字チェックが入らず実行時にエラーが出て判明する。

エラーログ内容:

ERROR    2010-01-12 11:24:14,631 dev_appserver_main.py:363] Fatal error when loading application configuration:
Unable to assign value 'closure_sample' to attribute 'application':
Value 'closure_sample' does not match expression '^(?!-)[a-z\d\-]{1,100}$'
  in "/Users/hashi/development/GAE/closure_sample/app.yaml", line 1, column 14


エラーログはランチャの "Logs" から開くことができる。



2010年1月12日火曜日

GAE 開発開始 - HelloWorld

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
GAE開発開始。以下 Mac OS X v10.6でのメモ。

アカウントは以前申し込んでおいたものを使用。

Google App Engine - Google Code


ログイン後に SDKをダウンロード
Downloads - Google App Engine - Google Code


適当なところへコピー。


実行。

これを使って簡易サーバを立ち上げ、ローカルでの開発、デプロイなどが行える。

ドキュメント Hello, World! を元にまずは HelloWorldを表示させる。
Hello, World! - Google App Engine - Google Code

/Users/hashi/development/GAE/helloworld を作成し、そこへドキュメントで紹介されていた2つのファイルを設置する(全部コピぺ)。
helloworld.py

print 'Content-Type: text/plain'
print ''
print 'Hello, world!'


app.yaml

application: helloworld
version: 1
runtime: python
api_version: 1

handlers:
- url: /.*
  script: helloworld.py


GAEのランチャを起動し、メニューから New Application... を選択。

先程作ったフォルダを指定。

登録されたら "Run" で起動させる。

"Browse"ボタンを押すと Safari が立ち上がり、ターゲットのページを開いてくれる。

出た。最初の一歩終わり。