goog.ui.ThousandRows - 大量の一覧を軽快に表示するGoogle Closure Library モジュール
Closure Library で、大量に列のあるリストを軽快に表示できるコンポーネントを作りました。
デモ:http://stakam.net/closure/120722/
GitHub:https://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
CentOS6、nginx -> node 最小構成
僕はセキュリティとかに詳しくない人間です。実運用は十分ご注意ください。
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.
http://groups.google.com/group/yesodweb/browse_thread/thread/ffbc9ea24d002c05/7b12f9532ced65d2?show_docid=7b12f9532ced65d2
Actually, I'd recommend waiting until I post the new yesod-platform, ...
expressでOAuth。Twitterに投稿。
なんだかよく分からなかった。やっとできた。基本的なことが分かってないんだね。
追)githubにサンプルを作りました https://github.com/piglovesyou/express-twitter-oauth-sample
やること:
- 下準備。ライブラリの準備。
- app.get('/', routes.index);。ログインしてなかったら、/login にリダイレクト。
- app.get('/login', routes.login); ログイン画面。ユーザーに「Twitterでログイン」をクリックしてもらう。
- app.get('/auth/twitter', routes.auth.twitter); request_token を取得。取得したら、authenticate を叩く。
- app.get('/auth/twitter/callback', routes.auth.twitter.callback); verifierを取得。取得したら、access_tokenを取得。
- 準備完了。ユーザーのscreen_name を出して、ログイン中画面にユーザーをお招き。
- app.post('/post', routes.post); アプリ経由でtweet を投稿してみる。
- 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");
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. ...
https://dev.twitter.com/docs/auth/authorizing-requestPOST /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.comstatus=Hello%20Ladies%20%2b%20Gentlemen%2c%20a%20signed%20OAuth%20request%21
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'); };
ボンッッ。おしまい。
参考: