Weblocks - widgets - login

一つずつ見ていくとか言いつつ、最初の一個目でガッツリ詰まったのでした。
使った widgetは ログインフォームを作成する "login" widget

基本的な使い方

どの widgetも基本的な使い方は make-instnaceで通常のクラスと同様にインスタンス生成するだけのような気がします。

いくつかのslotがあり、入力項目やSUBMITされた時に呼ばれる関数などが設定できるらしい。
以下、login widgetで定義している slot*1

on-login loginボタン押下時に呼び出される2引数の関数
on-success on-loginが 非nilを返したときに呼ばれる関数
on-cancel キャンセルボタンが押されたときに呼ばれる関数
view 入力項目やボタンの定義
quickform initialize-instance :afterで setfされてるから、自分で設定する必要は無いと思う
data 調べてないです
class-store 調べてないです

これらの slotの内、on-loginだけは必須です。

(make-instance 'login
  :on-login (lambda (login-widget form)
              (declare (ignore login-widget))
              (let* ((email (slot-value form 'email))
                     (password (slot-value form 'password))
                     (user (get-user email password)))
                (if user
                    user
                   (values nil "メールアドレス、又はパスワードが誤っています。")))))

のような感じで私は動かしてみました。get-userはメールアドレスとパスワードでユーザ情報を取得する関数だと思ってもらいたい。
このon-loginに設定した関数が非nilを返した場合、その値がsessionに格納された上でログイン成功として on-successに設定した関数が呼ばれます。逆に nilを返すとログイン失敗になり、同じ画面に戻されてエラーメッセージが表示されます。エラーメッセージは valuesで返した2個目の戻り値、又はデフォルトの "Invalid credentials."。

ログインユーザのログイン情報(on-login-fnで返した非nil値)は (webapp-session-value *authentication-key*)で参照可能。

さっぱり分からないこと

login widgetの動作というより、Weblocksの画面遷移定義の仕方が分かっていない。
ログインフォームでcancelボタンを押したときに同じ画面に戻るようにするにはどうしたら・・・?
あと ログイン出来たときでも、遷移先の画面を状況に応じて変えたいのだけど方法が分からない。もっと中のほうのソース読もう(そして継続周りで思考停止)。

*1:基底クラスの widgetクラスもslot持ってるから、これがlogin widgetが持つ全ての slotと言うわけではありません

Weblocks - widget

Weblocksの画面は、Wedgetを並べて作るらしい。
例えばexamplesに入っている weblocks-demoでは左側にメニュー、右側に一覧が表示されます。weblocks/examples/weblocks-demo/src/layout.lispを見ると、メニュー部分は `navigation'というWidget、右の一覧は `gridedit'というWidgetで作られていることが分かります。Widgetの実体は CLOSのクラスであり defwidgetというマクロを使えば自分で定義することも可能です。よく使う画面部品をWidgetとして作っておけば使いまわしが効くのだと思います。

以下、weblocks/src/widgets/ に入ってた widgetの一覧。まだ全然使っていないので よく分からないモノや、認識が誤っているモノもあると思います。確認後、誤っていれば訂正したいです。

breadcrumbs パン屑リスト -
composite 複数widgetを合成する(使うなってコメントがある) -
data-editor 調べる -
dataform 調べる -
datagrid 編集不可な一覧表 調べた
datalist 編集不可なリスト 調べた
dataseq 調べる -
flash 次のアクションで消えるメッセージ -
gridedit 編集可能なTable 調べた
listedit 編集可能なリスト 調べた
login ログインフォーム 調べた
navigation メニュー部 -
lazy-navigation 名前からすると遅延評価なメニュー? -
teleport 調べる -
on-demand-selector 調べる -
pagination 一覧系をページ付け -
quickform 調べる -
selector 調べる -
static-selector 調べる -
template-block 調べる -
funcall-widget 関数呼べるんじゃね? -
string-widget 単純な文字列を表示 -
widget WIDGETの基底クラス -

名前からの想像で書いたモノも多数!これらWidgetの定義を確認し、使い方を残しておこうと思うのでした。全部見終わるのはいつだろう。

Weblocksの make-appで作った結果

昨日の make-app で作成したソースを見ていたら src/init-session.lisp の内容が以下のようになっていました。

(in-package :hoge)

;; Define callback function to initialize new sessions
(defun init-user-session (comp)
  (setf (composite-widgets comp)
        (list (lambda (&rest args)
                (with-html
                  (:strong "Happy Hacking!"))))))

「composite-widgetsって具体的になりするの?」と思ってweblocksのソース(weblocks/src/widgets/composite.lisp)を見たところ、ファイルの先頭部のコメントに「do not use in new code!」の記述。お前が生成したファイルで使ってるくせに何を仰っているのかしら、と思った。
stableではなくdevの方では widget-children を使うように変わっていたのでした。

dev版バンザイ、というお話でした。

Weblocksの新規アプリケーション作成

久しぶりにWeblocksです。久しぶり過ぎて、以前書いた内容すら覚えていません。自分で読み返します。

Weblocksで新規アプリケーションを作成する際、私はいままで

  1. サンプルをコピー
  2. 適当に削ったり書き換える
  3. わーい自分用のテンプレートだー

を何度も繰り替えしたのです。無意味ですね。残念です。私の脳が。

Weblocksにはアプリケーションの雛形を作成する関数が用意されています。

(wop:make-app 'hoge (merge-pathnames "unspeakable/src/" (user-homedir-pathname)))

第1引数はアプリケーション名。第2引数はオプションで作成ディレクトリ。上記の場合 ~/unspeakable/src/hoge/ 以下に

  • conf/
  • conf/stores.lisp
  • data/
  • pub/
  • pub/images/...
  • pub/scripts/...
  • pub/stylesheets/...
  • src/
  • src/init-session.lisp
  • unspeakable.asd
  • unspeakable.lisp

などのディレクトリとファイルが出来て、とりあえず実行可能な状態で中身が作成されます。

これを知ったときの「今まで無駄な作業してたなー」という絶望感はなかなかのものでした。

JavaScriptをS式で(parenscript)

S式でJavaScriptを書けるライブラリ Parenscript。全部S式じゃないと嫌だっていう欲張りな貴方にピッタリ!ゆるふわ括弧な愛されWEBアプリが作れます!

実際のところ、letter: Parenscript で jQuery を使う場合は chain をで見かけて興味はあったけど今まで使わなかった。ので、Hackathonの時に書いたJavascriptをParenscriptで書き直してみたのでした。

Hackathonで書いていた JavaScriptはこんな有様。

var x = 0;
var y = 0;
var dx = 1;
var dy = 1;
var img = new Image();
img.src = 'http://www.lisperati.com/lisplogo_alien_128.png';
setInterval('draw()', 5);

function draw() {

    var w = 128;
    var h = 75;
    var canvas = document.getElementById('alien-canvas');
    if( !canvas || !canvas.getContext) {
        return false;
    }

    var ctx = canvas.getContext('2d');
    ctx.fillRect(0, 0, 500, 500);
    if(x + dx + w <= 500 && 0 <= x + dx) {
        x += dx;
    }
    else {
        dx *= -1;
    }

    if(y + dy + h <= 500 && 0 <= y + dy) {
        y += dy;
    }
    else {
        dy *= -1;
    }

    ctx.drawImage(img, x, y);
}

酷いですね。
やっていることは座標 x, y、移動量dx, dy、描画する画像を用意して 5ms間隔でdraw関数を呼び出し。draw関数は x座標に dx, y座標に dyを足して動かして描画する。画面から飛び出しそうになったら移動量の符号をを逆転させる。

以下がParenscriptで書いたコード

(ps (defvar x 0)
    (defvar y 0)
    (defvar dx 1)
    (defvar dy 1)
    (defvar img (new -image))
    (setf (@ img src) "http://www.lisperati.com/lisplogo_alien_128.png")

    (set-interval "draw()" 5)

    (defun draw ()
      (let ((w 128)
            (h 75)
            (canvas ((@ document get-element-by-id) "alien-canvas")))
        (if (or (not canvas) (not (@ canvas get-context)))
            (return false))
        (let ((ctx ((@ canvas get-context) "2d")))
          ((@ ctx fill-rect) 0 0 500 500)
          (if (and (<= (+ x dx w) 500) (<= 0 (+ x dx)))
              (setf x (+ x dx))
              (setf dx (* dx -1)))
          (if (and (<= (+ y dy h) 500) (<= 0 (+ y dy)))
              (setf y (+ y dy))
              (setf dy (* dy -1)))
          ((@ ctx draw-image) img x y)))))

そして評価後

var x = 0;
var y = 0;
var dx = 1;
var dy = 1;
var img = new Image;
img.src = 'http://www.lisperati.com/lisplogo_alien_128.png';
setInterval('draw()', 5);
function draw() {
    var w = 128;
    var h = 75;
    var canvas = document.getElementById('alien-canvas');
    if (!canvas || !canvas.getContext) {
        return false;
    };
    var ctx = canvas.getContext('2d');
    ctx.fillRect(0, 0, 500, 500);
    if (x + dx + w <= 500 && 0 <= x + dx) {
        x += dx;
    } else {
        dx *= -1;
    };
    if (y + dy + h <= 500 && 0 <= y + dy) {
        y += dy;
    } else {
        dy *= -1;
    };
    return ctx.drawImage(img, x, y);
};

JavaScriptCommon Lispで書けるなんて嬉しいですね。

  • psの中がJavaScriptになる
  • 大文字で出力すべき文字の前に "-"を置くと出力される識別子では大文字になる 例) get-element-by-id -> getElementById
  • JavaScriptの関数呼び出しは、LISPの関数呼び出しと同じ
  • let, let* などで局所変数を作れる
  • setfで変数に代入
  • @ は 第1引数.第2引数 という識別子として出力。これ単品ならプロパティ参照、もう一つ括弧で囲むとメソッド呼び出しになる

Shibuya.lisp Hackathon #1へ行ってきました

全然準備をせずに行ったため、前半2時間くらいは環境構築で消滅。その後もSBCL上でHunchentootを起動出来なくて全然LISP書けなかった。最終的に書いたのはHTMLとJavaScriptだけになって「HTMLのCanvas上でアレを描画、移動させた」だけになっちゃった。クズい。
HTMLをCL-WHO、JavaScriptをParenScriptに書き換えたら GitHubに置いておこう。

クズいけど、公開する。LISP書いてる人が、渋谷に来られる人だけで60人くらいいるなら、もっと日本語コメントの付いたコードがあっても良くない?JavaとかPHPとかVBとか、すんげー初心者レベルのコードでも公開されてるし。やり方として間違ってたら、たぶん誰か指摘してくれると思うし。

クズい画面がそれなりになったら、クズいLTとかもしてみたい。他の人たちのハードルを下げるだけになるかな。やるならShibuya.lispを貶めるようなことは出来ないから準備するだろう。どうだろう。

Shibuya.lisp Hackathon #1

明後日に迫ってきた Shibuya.lisp Hackathon #1 : ATND
さて、初めてのLISPイベント、初めてのHackathon、何をどうしたら良いか分からないので思いつくままに準備を進めようかと思うのでした。

持っていくもの

  1. ノートPC - Ubuntu 10.10を起動出来るようにしておきたい。Linuxインストール直後の状態から環境構築する手順を、未来の自分のために残そうかな。
  2. マウス - ノートPCのタッチパッドを殺さないとコーディングなんて出来ない。
  3. ガム - 静かに噛む。ガムをクチャクチャ音立てて噛む人とは友達になれない。
  4. 紙のノートとペン - メモしたり、考えを纏めたり。
  5. イヤホン - 音楽聴きながらコードを書くのもいいよね。

こんなもんかな。

何をハックするか

  • Lisp Logo こいつがウロウロするだけのWEBサイトをHunchentoot上で動かす
  • HTMLは CL-WHOで吐く
  • JavaScriptは ParenScriptで吐く
  • CSSは ・・・何を使えばいいのか

誰得感が凄い。