pig's diary

何でも忘れるので万年初心者ね

goog.ui.ThousandRows - 大量の一覧を軽快に表示するGoogle Closure Library モジュール

Closure Library で、大量に列のあるリストを軽快に表示できるコンポーネントを作りました。



デモ:http://stakam.net/closure/120722/
GitHubhttps://github.com/piglovesyou/closure-thousandrows

特徴

初期表示、スクロール時の動きが速いです。 

使えるケース

数千件〜の一覧の表示に力を発揮します。過去ログなど大量のデータを表示するときに使えるかも知れません。

使えないケース

  • リストが数十件程度の場合。遅延読み込みの必要がありません。
  • それぞれの列の高さがデザイン上違う場合は使えません。列は全て同じ高さである必要があります。これはコンポーネント側の制約です。

つくった経緯など

このコンポーネントは、既存のWebページに用いられていた「ページング」と「無限スクロール」の問題点を解決したデータの表示ができます。

そもそも、大量のデータを一度にWebページに表示しようとすると、どんな問題があるのでしょうか。サーバの負荷、データ転送量の増大、レンダリング時間の増大が挙げられます。レンダーツリーがあまりに多いと、スクロールなどの動作も重く、ユーザーストレスの原因にもなります。

よくある「ページング」は、一覧の上下に「1 2 3 ... 99」といった数字のリンクがあるデータの表示パターンです。分割して少ない量を読み込むため、サーバもクライアントも安心です。でも、ユーザーとしては毎回ページを読み込みし直すのが億劫です。それがAjaxになっても、「上から下に読んだ一覧を、また上から読み直さないといけない」のがちょっと残念です。

無限スクロールは、ずーっと下にスクロールして情報を見続けることができます。僕もTumblrを見てますが、はまると眺めているのがだんだん辞められなくなります。でも、だんだんページが重くなってくる問題があります。ページの上にDOMが残ったままなので、スクロール時のリフローコストが増大していってしまいます。

それらを解決したのが、ThousandRowsです。

スクロールしていくと通信し、DOMをappendして行くのは、無限スクロールと同じです。でも、不要な列を消していくので、ページが重くなることはありません。さらに、スクロールバーによって、無限スクロールにはない「下へのジャンプ」ができるようになりました。

でもひとつ制約があります。ロードしてないデータの列の高さを予測できないため、全ての列の高さを同じにしなければなりません。そこらへんから、ThousandRowsはiOSのUITableViewに近い動きをすると思っています。

大量の一覧データの表示に困っている方は、ぜひ利用してみてください。

goog.ui.Scroller - Google Closure Library モジュール

Closure Library でうごくスクローラーをつくりました。よく、スクロールバーのデザインをカスタムしたいときとかに使うやつです。
縦、横、両方でスクロールを実装できます。

デモ:http://stakam.net/closure/120618/
GitHub: https://github.com/piglovesyou/closure-scroller

ビットマスクによるフラグ

参考:goog.ui.Control

複数の状態(STATE)を、1つの変数で保持する。例えば、DISABLED状態であり、同時にHOVER状態でもある状態を1つの変数で表現する。以下、そのときのフラグの上げ下げのしかた。

まずビットマスクに使う定数を用意する。1,2,4,8,16,32...とする。

/**
 * @enum {number}
 */
var STATE = {
  DISABLED: 1,
  HOVER: 2,
  ACTIVE: 4,
  FOCUSED: 8
};

/**
 * @type {number}
 */
var state  = 0;

フラグが上がっているかの確認。if文で使うときは、 if (state & STATE.DISABLED) で判定できる。まだ、どのフラグも上がってない。

console.log(!!(state & STATE.DISABLED));  // false
console.log(!!(state & STATE.HOVER));     // false
console.log(!!(state & STATE.ACTIVE));    // false
console.log(!!(state & STATE.FOCUSED));   // false

それでは、2つのフラグを上げてみる。STATE.DISABLEDとSTATE.HOVERだけ。2つのやり方があり、同じ意味。

state |= STATE.DISABLED;
state = state | STATE.HOVER;

console.log(!!(state & STATE.DISABLED));  // true
console.log(!!(state & STATE.HOVER));     // true
console.log(!!(state & STATE.ACTIVE));    // false
console.log(!!(state & STATE.FOCUSED));   // false

他のフラグには影響を与えていないのが分かる。

次に、挙げたフラグを、下げてみる。これにも2つやり方があり、同じ意味。

state &= ~STATE.DISABLED;
state = state & ~STATE.HOVER;

console.log(!!(state & STATE.DISABLED));  // false
console.log(!!(state & STATE.HOVER));     // false
console.log(!!(state & STATE.ACTIVE));    // false
console.log(!!(state & STATE.FOCUSED));   // false

続いて、いっぺんに複数のフラグを上げてみる。

state = STATE.DISABLED | STATE.HOVER | STATE.ACTIVE;

console.log(!!(state & STATE.DISABLED));  // true
console.log(!!(state & STATE.HOVER));     // true
console.log(!!(state & STATE.ACTIVE));    // true
console.log(!!(state & STATE.FOCUSED));   // false

上記は、初期値の時点でいくつかのフラグを上げておきたいときなどに使える。代入文であることからも分かるように、元のフラグを無視して上書きする。

全てのフラグを下げるには、0を代入する。

state = 0;

console.log(!!(state & STATE.DISABLED));  // false
console.log(!!(state & STATE.HOVER));     // false
console.log(!!(state & STATE.ACTIVE));    // false
console.log(!!(state & STATE.FOCUSED));   // false

gentoo + kde + ibus で、ブラウザに日本語入力できない

gentoo + kde + ibus + ibus-mozc で日本語が使えるようになった!
でも、ブラウザにだけ日本語入力できませんでした。

http://code.google.com/p/ibus/issues/detail?id=1349

gtk USEフラグを上げ、emerge --update --newuse ibus したら、日本語が使えるようになりました。

CentOS6、nginx -> node 最小構成

僕はセキュリティとかに詳しくない人間です。実運用は十分ご注意ください。

環境

さくらVPS 1G

$ cat /etc/redhat-release 
CentOS release 6.2 (Final)

nginx インストール

$ sudo vim /etc/yum.repos.d/nginx.repo
# /etc/yum.repos.d/nginx.repo
[nginx]
name=nginx repo
baseurl=http://nginx.org/packages/centos/$releasever/$basearch/
gpgcheck=0
enabled=1
$ sudo yum install nginx
$ sudo nginx # :80 でlistenできてるか、ブラウザで確認する。「Welcome to nginx!」ならOK
$ sudo nginx -s quit

nodeでサーバを起動

$ cd
$ vim ./test_server.js
// ./test_server.js
var http = require('http');
http.createServer(function (req, res) {
    res.writeHead(200, {'Content-Type': 'text/plain'});
    res.end('YEAH!!!!!!\n');
}).listen(3000, '127.0.0.1');
console.log('Server running at http://127.0.0.1:1337/');
$ npm install forever -g # foreverお気に入りです 何度も生き返してくれました 
$ forever start test_server.js # nodeサーバが127.0.0.1:3000で起動

nginx 設定ファイル編集

$ sudo vim /etc/nginx/conf.d/mynginx.conf # 新規ファイル
# /etc/nginx/conf.d/mynginx.conf 
server {
    listen        80;
    server_name   localhost;

    location / {
        proxy_pass    http://localhost:3000; # :3000 に投げ渡す
        break;
    }
}

nginxサーバ起動

$ sudo service nginx start # nginx サーバ起動

ブラウザで、:80 にアクセス。nodeサーバの返す「YEAH!!!!!」が表示されました。

設定、もっと

  • nginx の設定回り。もっとかっこいい設定にして、ちゃんとしたリバースプロキシサーバにする。

yesod-test-0.2.0.2 でビルドエラー

今直し中だそうです(僕はHaskell1ミリも読めない)

Mac OS 10.7.3
yesod-core version:1.0.0.2

$ cabal-dev install && yesod --dev devel

localhost:3000 が立ち上がるはずが、だめ。
最後のほうのログ:

cabal: Error: some packages failed to install:
yeah-0.0.0 depends on yesod-test-0.2.0.2 which failed to install.
yesod-test-0.2.0.2 failed during the building phase. The exception was:
ExitFailure 1

そしたら

.. Thanks for the heads up, I'm in the process of fixing it right now.
Actually, I'd recommend waiting until I post the new yesod-platform, ...

http://groups.google.com/group/yesodweb/browse_thread/thread/ffbc9ea24d002c05/7b12f9532ced65d2?show_docid=7b12f9532ced65d2

expressでOAuth。Twitterに投稿。

なんだかよく分からなかった。やっとできた。基本的なことが分かってないんだね。

追)githubにサンプルを作りました https://github.com/piglovesyou/express-twitter-oauth-sample

やること:

  1. 下準備。ライブラリの準備。
  2. app.get('/', routes.index);。ログインしてなかったら、/login にリダイレクト。
  3. app.get('/login', routes.login); ログイン画面。ユーザーに「Twitterでログイン」をクリックしてもらう。
  4. app.get('/auth/twitter', routes.auth.twitter); request_token を取得。取得したら、authenticate を叩く。
  5. app.get('/auth/twitter/callback', routes.auth.twitter.callback); verifierを取得。取得したら、access_tokenを取得。
  6. 準備完了。ユーザーのscreen_name を出して、ログイン中画面にユーザーをお招き。
  7. app.post('/post', routes.post); アプリ経由でtweet を投稿してみる。
  8. app.get('/logout', routes.logout); ログアウト。セッションを破壊。大爆発。

1. 下準備。ライブラリの準備。

https://dev.twitter.com/appsで、アプリを登録。今回はtweetしたいので、read and write に設定しておく。
expressは、sessionを使うオプションでプロジェクトを作成。
npm install oauth しておいて、./routes/index.js で読みこむ。 ./routes/index.js の先頭で、oauth の設定をする(インスタンス作り)。

// ./routes/index.js
var OAuth       = require('oauth').OAuth;
var oa = new OAuth(
  "https://twitter.com/oauth/request_token",
  "https://twitter.com/oauth/access_token", 
  "Your consumer key is here.",
  "Your consumer secret is here.",
  "1.0",
  "http://localhost.com:3000/auth/twitter/callback",
  "HMAC-SHA1");

これでTwitterアプリ用のoauthインスタンスができた。

2. app.get('/', routes.index);

このページは、ログインユーザーしか見れなくする。
ログインしてる人のために、テンプレート「index」が必要。
ログインしてない人は、ただちに /login に飛んでもらう。

exports.index = function (req, res) {
  if(req.session.oauth && req.session.oauth.access_token) {
    res.render('index', {
      screen_name: req.session.twitter.screen_name
    });
  } else {
    res.redirect("/login");
  }
};

3. app.get('/login', routes.login);

ログインしてない人が、「ログイン」リンクをクリックしてもらうためのページ。無くてもいい。ここに「Twitterでログイン」「Facebookでログイン」の2つをあとで置こうかなと思ってます。今は、Twitterだけ。
テンプレート「login」が要ります。

exports.login = function (req, res) {
  if(req.session.oauth && req.session.oauth.access_token) {
  } else {
    res.render('login');
  }
};

4. app.get('/auth/twitter', routes.auth.twitter);

認証のためのURL。テンプレートはなし。アクセスと同時に、oauthインスタンスにrequest_tokenを取りに行かせる。
request_tokenをtwitterにもらったら、ユーザーにそれを握らせて、アプリの認証画面に行ってもらう。あと、貰ったほかのものはsessionにしまっておく。ユーザーが「許可」してくれることを祈って待つ。

exports.auth = {};
exports.auth.twitter = function(req, res){
  oa.getOAuthRequestToken(function(error, oauth_token, oauth_token_secret, results){
    if (error) {
      res.send("yeah no. didn't work.")
    } else {
      req.session.oauth = {};
      req.session.oauth.token = oauth_token;
      req.session.oauth.token_secret = oauth_token_secret;
      res.redirect('https://twitter.com/oauth/authenticate?oauth_token='+oauth_token)
    }
  });
};

5. app.get('/auth/twitter/callback', routes.auth.twitter.callback);

OAuthをnew したところと同じcallback用のURL。これはTwitterのアプリ設定画面でも同じものを登録しとく必要がある。
ユーザーが「許可します」ボタンを押してくれたら、Twitterはユーザーをここに連れてくる。パラメータには、ユーザーからのOKサインである「verifier」が入っているので、ありがたく貰っておく。
verifierと、さっきsessionにしまったものをきっちり揃えて、Twitterにお願いしに行く。書類をそろえて、「このユーザーと懇意にさせてください」とお願いする。書類に不備がなければ、OKのハンコがもらえる。OKハンコは「access_token」と「access_token_secret」の2つ。これがずっと欲しかったもの。目的を達成したから、それをsessionに大事にしまって、ユーザーをログイン中画面に連れていく。

exports.auth.twitter.callback = function(req, res, next){
  if (req.session.oauth) {
    req.session.oauth.verifier = req.query.oauth_verifier;
    var oauth = req.session.oauth;
    oa.getOAuthAccessToken(oauth.token, oauth.token_secret, oauth.verifier, 
        function(error, oauth_access_token, oauth_access_token_secret, results){
          if (error){
            res.send("yeah something broke.");
          } else {
            req.session.oauth.access_token = oauth_access_token;
            req.session.oauth.access_token_secret = oauth_access_token_secret;
            req.session.twitter = results;
            res.redirect("/");
          }
        }
    );
  } else
    next(new Error("you're not supposed to be here."));
};

6. 準備完了。

ユーザーのscreen_name を出して、「ログイン中画面」にユーザーをお招きする。

exports.index = function (req, res) {
  if(req.session.oauth && req.session.oauth.access_token) {
    // コッチ。
    res.render('index', {
      screen_name: req.session.twitter.screen_name
    , title: 'Express'
    });
  } else {
    res.redirect("/login");
  }
};

これで「Twitterでログイン」ができました。

7. app.post('/post', routes.post);

このアプリ経由で、Tweetしてもらう。
ここは、xhrで叩く想定。ユーザーがここにPOSTすると、textパラメータの値をTwitterに投稿するしくみにする。

クライアントサイドのHTMLとJSはこんな。さきにjQueryを読んでおく。

// index.jade
form#tweet
  input(type="text", name="text")
  input(type="submit", value="ツイート。")
// client side script
$('#tweet').submit(function (e) {
  var text = e.target.text.value;
  $.ajax({
    url: '/post'
  , type: 'POST'
  , data: {text: text}
  });
  return false;
});

うん。
nodeは、リクエストを受け取ると、こんどはoauthインスタンスにリクエストを投げさせる。引数に、TwitterのOKハンコであるaccess_tokenとaccess_token_secretを渡す(ユーザ特定のヒントとユーザ許可済みのしるし)。oauthインスタインスは、Twitterに特別なリクエストを投げてくれる。
特別っていうか、セッションに保存しているtokenやらいろいろを使って、特別なヘッダを作って、リクエストを飛ばす。
このときどんなリクエストを投げて欲しいかを、Twitterは丁寧に解説している。

... At a very simplified level, Twitter's implementation requires that requests needing authorization contain an additional HTTP Authorization header with enough information to answer the questions listed above. ...

POST /1/statuses/update.json?include_entities=true HTTP/1.1
Accept: */*
Connection: close
User-Agent: OAuth gem v0.4.4
Content-Type: application/x-www-form-urlencoded
Authorization:
OAuth oauth_consumer_key="xvz1evFS4wEEPTGEFPHBog",
oauth_nonce="kYjzVBB8Y0ZFabxSWbWovY3uYSQ2pTgmZeNu2VS4cg",
oauth_signature="tnnArxj06cWHq44gCs1OSKk%2FjLY%3D",
oauth_signature_method="HMAC-SHA1",
oauth_timestamp="1318622958",
oauth_token="370773112-GmHxMAgYyLbNEtIKZeRNFsMKPR9EyMZeS9weJAEb",
oauth_version="1.0"
Content-Length: 76
Host: api.twitter.com

status=Hello%20Ladies%20%2b%20Gentlemen%2c%20a%20signed%20OAuth%20request%21

https://dev.twitter.com/docs/auth/authorizing-request

consumer_keyやら、access_tokenやら。もう必要なものは全部持っているんだけど、この特別なヘッダ(Authorization header)を作るのが大変。いろいろ混ぜたりこねたりしなくちゃいけない。
これをやってくれるのが、node-oauthモジュール。node-oauthの中身をのぞくと、たとえばaccess_token_secretを使って、まぜたりこねたりして、シグネチャーを作っているのが分かる。絶対に作りたくないね。それをやってくれる。ありがたいね、本当の話。

exports.post = function (req, res) {
  if(req.session.oauth && req.session.oauth.access_token) {
    var text = req.body.text;
    oa.post(
      'https://api.twitter.com/1/statuses/update.json',
      req.session.oauth.access_token, 
      req.session.oauth.access_token_secret,
      {"status": text},
      function (err, data, response) {
        if (err) {
          res.send('too bad.' + JSON.stringify(err));
        } else {
          res.send('posted successfully...!');
        }
      });
  } else {
    res.send('fail.');
  }
};

登録しているアプリ経由で、投稿できたはず。

8. app.get('/logout', routes.logout);

ログアウトする。
ログアウトのリンクをどっかに置いておいて、テンプレート「logout」も作っておく。テンプレートには「完全にログアウトしたから安心するように」と書いておく。

exports.logout = function (req, res) {
  req.session.destroy();
  res.render('logout');
};

ボンッッ。おしまい。


参考: