WEB開発メモ

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