[IM]ドキュメントの方針

INTER-Mediatorについて、ドキュメントをもっと充実させるべきという声は常に聞かれますし、その通りだと思います。とは言え、いかに効率的に作るかを考えないと、本体以上に破綻するかもしれません。ドキュメントはテストドリブンでの制作はできないのですから。そういうわけで、当面の方針をきちんと作り、そして書いて、実行することにします。

以前は、最終的には「パターン」への落とし込みが重要ではないかと考えていましたが、パターンとして落とし込むには、知の集約が必要であり、一定期間に少人数でできることではありません。既存のパターンを使うとしても同様です。

現在考えているのは、プラクティスの充実です。たぶん、サンプルや例題というのもプラクティスの一つなのでしょうけど、サンプルは説明がしづらいとか、デバッグ用に無意味な組み合わせの機能があるとか、プラットフォームを初めて見る人には辛いものです。その意味で「例題」なのですが、fluent 2014に言ったときにEmber.jsのセッションで言われていたのは「フレームワークはコミュニティのプラクティスを取り込まないといけない」というところで、ピンと来ました。例題の題材は、コミュニティにあるものを採用しないといけないということです。機能説明になっては本末転倒になるというところでしょうか。そういうわけで、プラクティスという言葉がきっかけで、やるべきことが整理された気がします。

ソフトウエアプラットフォームの開設のための文書にどのようなタイプのものがあるかを考えてみました。マーケティング的なことを抜きにして、利用者(通常は開発者)に対するコミュニケーションのためには次の4つがあるのではないかということです。

  • 仕様の記述:当然必要、網羅性が求められる
  • チュートリアル:手を動かして学習、INTER-Mediatorでは有償コンテンツとして用意
  • プラクティス:これから充実させたい
  • パターン:抽象度の高いノウハウ

今は、プラクティスを充実させる段階であり、それが一段落してからパターンに進むのが手法としてはステップを踏む感じがするのです。

プラクティスとして作成する予定のテーマは次の通りです。もちろん、みなさんからの要望やアドバイスを期待します。とは言え、あまり重いものはプラクティスではなく、実システムになってしまい、微妙なところです。以下の5、6はえらく長くなりそうですし、むしろ上級向けのチュートリアルのテーマではないかとも思います。

  1. 検索をしてその結果を一覧表示する
  2. 一覧表示と詳細表示を行き来する
  3. アンケートなどの入力のみのページ
  4. 伝票形式のページを作成する
  5. 会員制サイト(ヘビーか?)
  6. カート(不要か?)

一方、「こういう作りのページを簡単に作れることは意図していない」という、ある意味のアンチプラクティスも記述が必要と考えています。たとえば、こんなテーマです。とりあえず、1つだけです。

  1. 入力フォームの後の確認ページが不要な理由

ということで、プランを立てないと前に進めない気がするので、とりあえず、書いておこうと思います。

[IM]ローカルコンテキストの利用方法

INTER-Mediatorでは、タグのdata-im属性に、「テーブル名@フィールド名」の形式で記述するターゲット指定により、そのタグ要素がデータベースのフィールドにバインドされて、データベースから読み込んだデータを表示し、フォーム用のコンポーネントであればユーザによる変更結果をデータベースに書き戻す事が自動的に設定されます。しかしながら、データベースとは関係ない記憶領域があると何かと便利です。言い換えれば、データベースにしか保持できないとなると設計の順内製がそがれます。そこで、クライアントサイドの計算フィールドを実装し、さらにローカルコンテキストという機能を実装しました。

ローカルコンテキストの利用方法

ローカルコンテキストとは、データベースと連動していない記憶領域で、テーブル名は一律に「_」を使います。フィールド名は任意の変数名でかまいません。Sample_searchでは、type属性がtextのINPUTタグに対して、<input type=”text” data-im=”_@placeCondition”/> という設定を行いました。placeConditionがローカルコンテキスト内の名前です。この記述をするだけで、テキストフィールドに一度入力したデータは、ページを更新しても必ず再現されます。イメージとしてはどこかに記録されて、それが必要に応じて自動的に復活すると考えてください。保存場所はクッキーの中です。

通常のコンテキストは、1つのコンテキストに複数のレコードがあり、そのレコード内にフィールドがあるという構造です。ローカルコンテキストは「レコード」という構造がありません。コンテキスト名も常に決まっているので、実質的にはシンプルなキーバリューストアと同一です。一般のコンテキストは、エンクロージャーとリピーターという繰り返しを誘発する階層構造をベースにしますが、ローカルコンテキストは1ページごとに1つずつ持っており、どこにどう書いてもかまいません。同一のフィールド名のタグ要素が複数あれば、それらでの編集結果も連動します。

ローカルコンテキストへの直接アクセス

テキストフィールドがある場合、そこに入力した値を取り出すのは、通常はgetElementById等を使ってノードを参照します。一方、ローカルコンテキストを利用したテキストフィールドなら、ローカルコンテキストから値を取り出すほうが確実です。たとえば、前述のinputタグ要素の場合、

var c1 = IMLibLocalContext.getValue("placeCondition");

とすれば、テキストフィールドに入れた値を取得できます。なお、テキストフィールドをローカルコンテキストに反映するタイミングは、フォーカスがはずれたとき、つまりonchangeイベントで行っています。もし意図的に値をローカルコンテキストに記録するには、

IMLibLocalContext.setValue("placeCondition", value);

と記述します。

計算式の中に、ローカルコンテキストのリンクノードを参照する記述「_@field」などがあった場合、他のコンテキストにあるフィールドと同様なルートで検索します。つまり、同一のエンクロージャーを探し、なければ上位のエンクロージャを探してその子孫のノードを検索します。

ローカルコンテキストは、URLにひもづくクッキーに記録し、とりあえず、期限は1年にしてみました。いろいろなタイミングで自動的にクッキーに記録し、一方でクッキーから取り出すので、ほぼ、そういうことは意識しなくてもいいのですが、この後に説明する件で、一部のブラウザで書き込みを明示的に記述しないといけない場合があります。

連動するプロパティ

こうして、ローカルコンテキストが稼働しているので、一部のプロパティについては、ローカルコンテキストで保持することにしました。

INTERMediator.startFrom = 0;
INTERMediator.additionalCondition = {};
INTERMediator.additionalSortKey = {};

これらは、前から順番に、検索結果の何レコード目から表示するのか、追加の検索条件、追加のソート条件となります。たとえば、ユーザインタフェースを使って、追加の検索条件を与えると、それがローカルコンテキストに記録されて、事実上永続化されます。検索条件は、随時適用されるので、検索条件を与えるといつまでもその条件が適用されるようになります。

これらの動作に関連する情報は、以下のキー名を使ってローカルコンテキストに保存しています。なお、_im_で始まるキーは、システム予約としておきます。今後、こうした記録が増える可能性があります。

_im_startFrom
_im_additionalCondition
_im_additionalSortKey

たとえば、検索条件を特定のコンテキスト「context」に対して設定する場合、例えば、次のように記述するのは、従来と変わりありません。

INTERMediator.additionalCondition['context'] = {
    field: 'zipcode',
    operator: 'LIKE',
    value: IMLibLocalContext.getValue("criteria") + '%'
};

このとき、背後では、ローカルコンテキストに右辺のオブジェクトを記録し、さらにクッキーへの記録まで同時に行います。

Internet Explorer 8での対処

Ineternet Explorer Ver.8のみ、プロパティに記録したデータを自動的に永続化する事ができません。IE8の場合のみ、INTERMediatorオブジェクトのstartFrom、additionalCondition、additionalSortKeyプロパティに設定した直後に、

IMLibLocalContext.archive();

という呼び出しを入れて、ローカルコンテキストに反映しつつ、クッキーへの保存を行うようにします。IE9以降や、その他のブラウザではこの呼び出しはなくてもかまいませんが、あっても問題はありません。ただし、同じ作業を複数回行うことになるので、効率は低下します。INTER-Mediatorの内部ではIEやそのバージョンの把握を行っているので、たとえば、次のように記述すれば、IE8の場合だけ、前述のメソッド呼び出しが実装できます。

if (INTERMediator.isIE && INTERMediator.ieVersion < 9) {
    IMLibLocalContext.archive();
}

[IM]メールをポストする機能を追加

2014/2/15にコミットしたINTER-Mediatorでは、メールの送信を機能として組み込みました。つまり、メール送信の処理をプログラムを一切しなくても、宣言的な記述だけでできるようになりました。

まず、メールはサーバで送ります。送信方法は、PHPのmail関数を使う方法なので、UNIX系ならsendmailコマンドをたたく方法になります。一方、これだとSMTPサーバへの転送ができないので、qdsmtpも組み込みました(Thanks to Mr. Spok)。SMTP認証はPlainのみの対応となっています。Windowsの場合はmail関数がすでにSMTPですので、php.iniにサーバ情報などを書くことで対処できます。なお、Windows環境はチェックしていないので、何かあれば知らせてもらえると助かります。

ただ、これだけではだめだろうというのはご存知の方はお分かりかと思いますが、昔作っていたOMEというメールソフトにはPHPのクラスもあったので、それをUTF-8で動くように改造してエンコードなどをさせるようにしました。ちなみに、さらにその前に『メール送信システムの作り方大全』という書籍も書いていて、その中の一部のクラスを使いやすいようにしたのがOME.phpです。この本、もう10年以上前なのですね…。

定義ファイルに指定可能なキーワードは以下の通り全て列挙します。しかしながら、すべてを記述することはないです。

IM_Entry(
    array(   // Contexts
         array(
            'name' => 'request',
            'send-mail' => array(
                 'new' => array(
                    'from' => '',
                    'to' => 'email',
                    'cc' => '',
                    'bcc' => '',
                    'subject' => '',
                    'body' => '',
                    'from-constant' => 'Officer <info@msyk.net>',
                    'to-constant' => 'msyk@me.com',
                    'cc-constant' => 'businessmatching@cocoa-study.com',
                    'bcc-constant' => '',
                    'subject-constant' => 'Cocoa勉強会ビジネスマッチング申し込み',
                    'body-constant' => 'テストメール',
                    'body-template' => 'welcome.txt',
                    'body-fields' => 'name,compnay,email,tel',
                    'f-option' => true,
                    'body-wrap' => 68,
                 )
             )
         )
    ),
    array(   // Options
        'formatter' => array(...),
        'smtp' => array(
            'server' => 'mysmtp.msyk.net',
            'port' => 587,
             'username' => 'msyktest@msyk.net',
            'password' => 'oshienai',
        )
    ),
    array('db-class' => 'PDO'),
    false
);

メールを送信するタイミングの指定

‘send-mail’キーの配列の次のレベルのキーとして、’load’ ‘edit’ ‘new’のいずれかを指定できます。それぞれ、データベースからの読み込み時、更新時、新規レコード作成時を意味し、コンテキストに対するそれぞれのタイミングでメールを送信します。いずれも、データベース処理が終了してからメールの送信にかかります。上記の例では、新規レコード作成時に、メールが送信されます。

宛先や送信者の指定

宛先の指定は、’to’ないしは’to-constant’キーに指定します。もし、宛先が一定のものであれば、’to-constant’キーに指定をしてください。’to’キーにはフィールド名を指定します。データベース処理の結果、たとえば新規レコードの場合には新しいレコードが1つ作成されて、そのレコードの内容から’to’キーに指定したフィールドより宛先のデータが取り出されます。編集も原則は1レコードです。一方、読み出しの場合は1レコードにならないかもしれませんが、その場合は最初のレコードから取り出します。むしろ、1レコードに絞り込むコンテキストにするのがメールを送る場合には妥当だと考えられます。

メールアドレスは「名前 <アドレス>」ないしは「アドレス」の2つの形式のみのサポートになります。’to-constant’キーに対する値、あるいは’to’キーで指定されるフィールドの値は、このどちらかの形式にしてください。

cc、bccについてもまったく同様のルールです。’to’と’to-constant’の両方の指定があれば、’to-constant’が優先されます。cc、bccでも-constantが優先となります。

fromについても、fromとfrom-constantキーがあり、設定や動作等は同じです。ただし、UNIXでSMTPサーバを使わない場合だと、通常はソース側のFrom:は無視されて、UNIXアカウントそのものをFrom:として設定してしまいます。ただし、サーバ側で許可されていれば「’f-option’ => true」の指定を定義ファイル内に記述することで、sendmailコマンドの-fパラメータを指定して、送信者の指定が可能です。

件名と本文の指定

件名は、subjectあるいはsubject-constantのいずれかのキーに指定します。toなどと同じルールです。

メールの本文は、定義ファイル内に指定した通りに送信する’body-constant’、フィールドの内容をそのまま送信する’body’に加えて、テンプレートの処理も可能です。優先順はテンプレート、body-constant、bodyの順になりますので、不要なフィールドは消しておきましょう。

テンプレート処理をするには、テンプレートのファイル名をbody-templateキーに指定します。このとき、テンプレートのファイルは、定義ファイルのあるディレクトリを基準に検索します。つまり、定義ファイルといっしょに何らかのテキストファイルを億としたら、body-templateキーの値はファイル名だけでかまいません。

テンプレートのファイル内では、そのファイルの内容を本文にしますが、フィールドの値との置き換えも可能です。置き換えたい箇所に@@1@@、@@2@@のように、アットマーク2つに囲まれた1から始まる整数を指定します。テンプレートのファイルはUTF-8で保存します。

フィールドについては、’body-fields’キーに、半角のカンマで区切って指定します。最初が1で、順次番号が増えるようになります。例で言えば、emailフィールドの値は、テンプレート内の@@3@@と置き換わって表示されます。’body-fields’キーを省略すると、テンプレートのファイル通りにメールが送信されます。

本文は一定の長さで改行を入れます。既定値では72バイトですが、’body-wrap’キーで異なる値にできます。0に設定すると改行しません。ここで、バイト数ですが、実際のバイト数ではなく、日本語は2バイト、英語は1バイトと数えた結果で示しています。実際のエンコードはUTF-8なので、嘘と言えば嘘のカウントになりますが、おそらくこうして指定をすることに慣れている人が多いので、ここでは実態とは関係ない数値ではありますけども実用的という意味で「2バイトルール」でカウントしたものとします。

UNIXの場合のSMTPサーバの指定

定義ファイルのIM_Entry関数の第3引数のオプション領域に’smtp’キーで配列を指定します。その他のキーは、前記の例の通りで、キーを見れば意味は分かると思います。もし、SMTP認証をしない場合は、serverとportだけを指定します。認証する場合は、server, port, username, passwordを指定します。したがって、2つないしは4つの要素があるかのどちらかになります。

SMTPサーバの指定は、params.phpファイルでも指定が可能です。変数名として、$sendMailSMTPの定義し、値は’smtp’の右側の配列と同様に指定をします。params.phpファイルでの指定よりも、定義ファイルの指定が優先されます。どこにもSMTPサーバの設定がない場合には、mail関数での送信になります。

Windowsの場合は、’smtp’の指定やparams.phpファイルでの指定は一般には不要ですが、もし設定すれば、mail関数ではなく、qdsmtpによるメール送信ができます。

送信されるメールの文字セット

基本的にはメールはUTF-8でエンコードして送られます。ISO-2022-JPの指定はOME.phpではできるのですが、必要なら定義ファイルでの指定ができるようにしようと思います。リクエストがなければUTF-8固定で行きます。

ヘッダについては、base64のインラインエンコードを、ASCIIコード以外の文字について行います。本文はそのままですが、ヘッダのContent-TyleのcharsetにUTF-8という文字を付けます。つまり、本文はbase64等でのエンコードは行いません。

ファイルの添付は実装する予定はありません。ファイルを送りたいのなら、そのリンクを送るようなアプリケーションの動作にしましょう。

Internet Explorer 8にまた苦しめられる

Web系のお仲間の皆さんも同様にいつも苦しめられていると思われるIE8ですが、ここ最近にあったいくつかのはまりポイントを自分の備忘録としてもまとめておきます。まあ、あと数年はIE8からは逃れられないということで。

  • jQuery 2.0を入れたら動かない(対応ブラウザからはずれている)。慌てて1.10に戻す
  • JavaScriptではObject.keysという記述が使えない
  • 要素のid名と同一のグローバル変数が作られる

特に最後のは苦労しました。メッセージを見る限りは、「オブジェクトでサポートされていないプロパティまたはメソッドです。」と出ます。プログラムはこんな感じ。INPUTタグ要素で、idが「yourname」になっていると思ってください。

yourname = document.getElementById("yourname").value;

まさかgetElementByIdが使えないのかと思ったら、使えます。他の箇所では動いている。valueがない訳は絶対にない。当然右辺を疑うわけですが、必死に検索した結果、問題は左辺でした。つまり、以下の条件が満たされると発生されるトラブルだったのです。

  • IE8でJavaScriptでプログラムを組む
  • ページ内の要素のid属性と同一の変数を、何も定義しないでJavaScript側で使う

どうやら、IE8は、id属性と同一名のグローバル変数を勝手に定義するようです。上記のプログラムは、つまり、勝手に定義しているyourname変数が何らかのオブジェクトを記録していて、そのオブジェクトが書き換えに対応していないためにエラーが出ていると思っていいようです。

対処法は「var yourname」のように頭にvarを付けるか、ページにリンクされたjsファイル等でグローバルとして「var yourname;」のように変数定義することで回避できます。自分自身のグローバルでも、同一名称の変数は後から定義した方のストレージが有効になるので、IEが勝手に作るグローバル変数は無視されるようになるということです。つまり、変数定義を必ずしろというプラクティスをしていれば、エラーに合わないのですが、こう書いてしまったらエラーになってしまうよということですね。

[IM] バージョン入りのINTER-Mediatorを生成する

INTER-Mediatorを使って開発をするとき、レポジトリからプルした結果を使うという話を別の記事で書きました。もちろん、動く状態としてそのままをアップしてもいいのですが、ページ最後に出ているINTER-Mediatorの帯のバージョンが@@1@@などといった文字列になっていて、ちょっと雑な感じがすると思います。独自のバージョンと本日の日付を組み込んだ現状のINTER-Mediatorを作るには、dist_docs/buildup.shというスクリプトを使ってください。

たとえば、INTER-Mediatorのディレクトリがカレントディレクトリであれば、

cd dist_docs
./buildup.sh

とコマンドを入力します。すると、だーっと何か出て来て、INTER-Mediatorフォルダと同じ階層に、「INTER-Mediator-4_2.zip」などとバージョン入りのフレームワークが作られます。中身は現状のINTER-Mediatorフォルダの内容とほぼ同じですが、日付とバージョンが入ったものになります。

INTER-Mediator-4_2.zipファイルを展開すると、「INTER-Mediator-4_2」フォルダが出来上がります。現在のINTER-Mediatorフォルダの名前を変えるか削除して、「INTER-Mediator-4_2」フォルダの名前を「INTER-Mediator」フォルダすれば置き換え完了です。同じディレクトリにあるtempフォルダは一時領域なので、これも削除してOKです。

なお、このとき、INTER-Mediatorフォルダと同じフォルダに、yuicompressor-2.4.7.jarファイルがあれば、これを使ってJavaScriptのプログラムを圧縮します。ない場合には圧縮しないで、いくつかあるJavaScriptのファイルを1つにまとめることだけを行います。なお、別のバージョンのものを使う場合には、buildup.shファイルの最初の方にパスを記述する箇所があるので、それを修正してください。

バージョン番号については、同様に、buildup.shの最初の方に記述があるので、それを変更して利用してください。なお、バージョンのルールは次のようにします。

  • バージョン番号は、整数をピリオドで区切ることとする(例:3.4、3.11)
  • バージョン表記は、Ver.を基本とするが、vでもVersionでもversionでも基本的には構わない
  • 整数が2つのものを「正式バージョン」とする(例:3.4、3.11)
  • 開発者が独自にバージョンを付ける場合、3つ目の整数を必ず付ける(例:3.4.1、3.11.133)

つまり、INTER-Mediatorをダウンロードして、実際にアプリケーションに搭載する上で少し修正したとしたら、3つ目の整数で枝番号を付けてくださいということです。できれば、その状態をコミット/プッシュした上で、そのバージョンタグをつけていただけると、自分のバックアップ用にもなると思います。他の人とバージョンが重ならないように、レポジトリをチェックして異なる番号を付けてください。また、3桁目は飛び番号でもいいとしましょう。けっこう緩いルールですね。

 

 

[IM]レポジトリから取り出した結果で開発する

INTER-Mediatorはgithubでソースを公開しています。それとは別にサイトには特定のバージョンのファイルもアップロードしています。バージョンを固定し、フレームワークに手を入れないという前提なら、サイトからのダウンロードした結果をアプリケーションにコピーして使っていただいていいのですが、検証したりあるいはアップデートを取り込みたい場合、それだとファイルコピーを自分でしないといけなくなります。

非常に割り切った言い方をさせていただくと、サイトにある各バージョンのダウンロードファイルは、「それなりにアップデートしている」というアピールのものであると言っていいかと思います。githubにあるものと、サイトにあるものは若干違っていて、後者の方はJavaScriptのソースを一体化しつつYUIで圧縮かけたものですので、若干は動作効率は高いものです。

お勧めするのは、通常の開発は、レポジトリから取り出したもので進めておき、実際にアプリケーションを配備するときに、特定のバージョン向けにビルドしたものに置き換えるということになります。もちろん、それらの相違点に注意は必要かもしれません。また、そうした作業がしやすいように、Ver.3.11をリリース後にレポジトリの内容を大きく変更しました。gitの使い方はいろいろ難しいですが、gitを全く知らない方も、ともかく以下のようにしてみてください。

まず、あるフォルダに作成するWebアプリケーションのファイルをまとめておくとします。そのフォルダを作ります。以下は、Documentsの下にmywebappフォルダを作りました。

Dec 1, 2013, 14.44.15

次に、githubのINTER-Mdiatorのページに行きます。アカウントを持ってサインインしていただくのがいいのですが、なくてもソースのダウンロードは可能です。ここで、中央右付近にあるbranchがmasterになっているのを確認してください。ブランチのmasterがその時点でのコンプリートなソースです。そして、右の方にあるHTTPS clone URLをコピーしておきます。URLが薄く見える右のボタンをクリックすれば、コピーできます。

Dec 1, 2013, 14.45.25

そして、ターミナルを開いてください。次のように作業をします。#以降はコメントです。ファイルパスはもちろん、ご自分の作られているフォルダ構成に従って指定をしてください。

cd ~/Documents/mywebapp  # Webアプリケーションのフォルダをカレントにする
git clone https://github.com/msyk/INTER-Mediator.git  # サーバからレポジトリを複製する

これだけでOKです。すると、mywebappフォルダに、INTER-Mediatorフォルダが作られます。このINTER-Mediatorフォルダがフレームワーク本体となります。この階層に、たとえば、index.htmlファイルやら、アプリケーションのhtml/phpファイルを作って行けばOKです。後はこれをサーバにコピーして稼働を確認します。

Dec 1, 2013, 14.47.28

ここで、レポジトリからクローンしたINTER-Mediatorを参照するには、定義ファイルのcontext.phpはたとえば、次のように記述をします。

<?php
require_once('INTER-Mediator/INTER-Mediator.php');
IM_Entry( .... );
?>

なお、サンプルファイルは、INTER-Mediatorフォルダ内のSamplesフォルダにあります。

もし、レポジトリ側に新しいソースコードが加わっていれば、以下のようにコマンドを入れれば、最新版を取得できます。INTER-Mediatorフォルダをカレントにして、git pullとすればいいです。

cd ~/Documents/mywebapp/INTER-Mediator
git pull

念のために説明しておきますが、pullは最新版に更新するという使い方は非常に稀で、正しくは別のレポジトリの修正を取り込むという意味合いがあります。ソースコードをメンテする上では単なる更新ではないのですが、ソースを利用する側の方から見れば更新コマンドと思っていただいて概ねいいかと思います。もし、自分でフレームワーク側のソースを修正された場合には、単純ではありませんので、ここではソースは参照だけでタッチしないという前提で、説明をここまでとします。

gitには実に多彩な機能があり、ここまでに説明した手順はほんとうにその一端でしかないのですが、これだけを知っていれば、とりあえず大丈夫かと思います。もし、フレームワーク側の修正結果をコミットしていただけるなら、それなりにコマンドを打ち込むか、あるいはアプリケーションを使うことになるかと思いますが、取り出すだけなら上記の手順以上はほとんどないと思います。

gitを使った場合の質問がある場合は、FacebookのIMのグループで質問を出してください。

INTER-Mediatorのターゲット指定を汎用化する

INTER-Mediatorでのターゲット指定は、タグ要素のclass属性に、IM[table@field@target] といった形式で記述するものだ。これにより、tableで指定したコンテキストにあるfieldというフィールドのデータを、そのタグ要素に埋め込む。targetを省略すると、フォーム要素以外ではテキストノードとして追加し、フォーム要素ではvalueやcheckedなどの適切な属性に設定される。targetでは、Ver.3.8現在、属性名、innerHTML、nodeText、$target、#target、style.styleNameをサポートしている。

このところ、いろいろなものをINTER-Mediatorで作っていて、ある同じようなパタンが頻繁に発生していることに気付いた。それは、

あるフィールドのデータがあれば表示し、なければ表示しない

という動作である。稀に逆もあるのだが、基本は同じだ。これを実現するために、たとえば、FileMakerだと、計算フィールドを利用する。あるフィールドfieldに対して、計算フィールドdisplayField = if ( field != “”; “block” ; “none” )を定義する。そしてたとえば、こんな感じのHTMLを書く。

<div class=”IM[table@displayField@style.display]”>
データ:<span class=”IM[table@field]”></span>
</div>

SQLデータベースなら、ビューを利用するのがいいだろう。たとえば、こんな感じだろうか?

CREATE VIEW tableview AS
SELECT field, if(LENGTH(field)>0, ‘block’, ‘none’) AS displayField FROM table;

これで対処はできるとは言え、もう少し簡単にならないかと考えてしまう。こうしたよくあることを手軽に対処できるというのはフレームワークに要求されることだ。もちろん、1つの方法は、ターゲット指定の3つ目のパラメータを増やす事だ。たとえば、こんなのはどうだろう?

<div>
データ:<span class=”IM[table@field|table@field@parentVisibility]”></span>
</div>

parentVisibilityという名前の属性はないので、HTMLのルールとのコンフリクトはない。フィールドの値が “” あるいは長さが0であれば、visibilityだったら自分自身、parentVisibilityだったら1レベル上位のノードのdisplayスタイルをnoneにするというところだ。

ということで、機能を増やしました…というのはなんかこの段階に来てやることかなと疑問に考えた。もっと汎用的にすべきではないのか?

そこで考えたのが、次のような仕様である。tagetの代わりに、プログラムのステートメントのような書き方をするということ。たぶん、これがいちばん汎用的と思われる。

table@field@object.method(parameters)

だが、いきなりこれだけですというのは敷居を高くするだけじゃないのかと思われるだろう。もちろん、その解決策はある。object.method(parameters)に対するエイリアスを定義することで、従来通りの記述をサポートするようにする。つまり、…@$onclickや、…@innerHTMLという記述はサポートする。ただし、これらはエイリアスという位置づけにする。ただ、内部的には#や$の扱いが微妙だが、これはif文をがんばって書くしかないだろう。

objectは、次の書き方をサポートしようと考えている。(self) (parent) (enclosure) (repeater) つまり、カッコの中に決められたキーワードを記述する。methodとparametersは任意だ。ただ、parametersは(“a”,’b’)なんて書いたときにカッコ内をそのまま渡すのはセキュリティ上の問題が出そうな感じありありであるので、ここは制約を付けることにする。

ターゲットの3つ目に記述する処理のためのオブジェクトINTERMediatorTargetを作っておく。たとえば、そこに、次のように、setInnerHTMLメソッドがあるとしよう。ここでの関数の最初の引数は、フィールドのデータであるというルールを適用することにする。

INTERMediatorTarget {
setInnerHTML: function(d) {
this.innerHTML = d;
}
}

ターゲット指定は table@field@(self).setInnerHTML() と記述する。そして、[(self).setInnerHTML()」のエイリアスが「innerHTML」であるというわけである。

ノード展開の処理では、ターゲット指定のあるノードnodeに対して、その指定に従ったフィールドのデータfieldDataが得られている。ということは、以下のようにapplyを使えばいいということになる。これは、INTER-Mediator内部の話であって、アプリケーションを作る側は気にしなくてもいい。

INTERMediatorTarget.setInnerHTML.apply( node, [fieldData] );

このノードに展開する仕上げに相当する値をセットする部分が現状ではひどくifの応酬となっているので、まずはそれを緩和したい。また、上記のような仕組みだと、INTERMediatorTargetオブジェクトへのメソッド追加や既存メソッドの書き換えにより、フレームワークの動作を改変することも可能だし、独自のターゲット指定記述を作る事もできる。

INTERMediatorTargetに記述する関数では、前記のように第1引数にフィールド値を設定するのはいいとして、汎用性を高めるために、2、3引数はフィールド名とコンテキスト名にする。これで、コンテキストやフィールドに応じて動作を変える事もできてします。

こういう実装を考えているところである。

[IM]MediaAccessクラスと新たな拡張点(3)

こちらの文書、さらにこちらの文書の続きです。

データ生成を指定したクラスにさせ生成結果を返す

URLを使用して、別のシステムにデータを取りに行くことができるということはかなり汎用的になります。しかしながら、別システムとの連動ということになり、開発はやや大変になると同時にセキュリティ面への配慮する場面も増えることになります。そこで、INTER-Mediatorで完結させるために、定義ファイルの呼び出しにおいて、

context.php?media=class://ClassName/path/info

という呼び出しができるようになりました。この場合、ClassNameは定義ファイルと同じディレクトリなど、PHPが取得できる場所にあれば問題ありません。パス情報を付与できますが、mediaキーの値を利用することができるのです。

ClassNameで指定したクラスには、processingというメソッドを記述します。そして、ヘッダを含めた応答すべてをこのメソッドの中で完結させます。

class ClassName {
function processing($contextData, $options) { }
}

スペック上はprocessingというメソッドがあればいいのですが、引数については、とりあえず作ったアプリケーションで必要なものを並べました。$contextDataは、アクセス権の設定と同じ情報をパスに持たせることで、そのレコードのフィールドとデータがキーと値になった配列を設定します。つまり、関連付けられているレコードのデータをそのまま使えるようになっています。現状は、認証の設定がなされていないとこの変数には値は設定されません。$optionは、定義ファイルのIM_Entryの2つ目の値が設定されます。なお、メソッドの引数は今後は増えると思われます。

この仕組みを作って作ったのが『FileMaker as a Relational Database』のサイトです。このサイトでは、書籍を購入した人にPDFおよびePubでの書籍を配布していますが、それぞれパーソナライズをしています。PDFはヘッダに購入者やメールアドレスを入れ込み、ePubでは特定のページに同じような内容のテキストを埋め込んで圧縮・アーカイブします。media=class::…という記述は、そうしたドキュメント生成処理を記述できるような仕組みなのです。

MediaAccessクラスの解説は以上で終了です。

[IM]MediaAccessクラスと新たな拡張点(2)

こちらの文書の続きです。

FileMakerのオブジェクトフィールドを参照する

オブジェクトフィールドはデータベースごとに扱いが異なり、統一的にはやりにくい処理ではあります。MediaAccessクラスでは、FileMaker Serverをターゲットにオブジェクトフィールドに対応する機能を作成しました。

FIleMakerはFXを経由し、XML共有の仕組みを使います。テキスト型や数値型は、基本的にテキストでフィールドにあるデータが得られます。一方、オブジェクトフィールドはオブジェクトそのもののデータではなく、フィールドのデータに応じた以下のような「テキスト」が得られます。

/fmi/xml/cnt/photo.jpg
http://server:16000/…

PDFは完全なURLで、FileMaker Serverに16000ポートで接続して取り出すことが可能です。JPEGなどの画像の場合は、URLのパスに相当するものが得られますが、たとえばIMGタグのSRC属性にそのまま指定が可能な文字列になります。

いずれにしても、両方ともURLであると解釈すればいいわけです。この仕組みはFileMaker Serverに限らず、一般的なアクセスにも使えます。つまり、media=URLと指定をした場合は、そのURLにアクセスしたデータをMediaAccessクラスが取得し、さらにクラスを呼び出した元にそのデータを返します。/fmi/xml/cntで始まる物だけは特別にURLであるという処理が組み込まれています。また、URLかどうかはそれ以外には、httpあるいはhttpsで開始するものかどうかで判定しています。

MediaAccessでのアクセス権

認証が成立したらすべて参照可能となり、成立しなければ参照不可という単純なものなら非常に話が早く、前に説明したmedia-tokenの仕組みで事は足りるのです。なお、データはCRUDの4つの側面がありますが、メディアに関しては「編集」というのはWeb世界ではかなり難易度が高い世界であり、MediaAccessクラスはほぼ参照のみのサポートになっています。

ここで、メディアそのものがログインしたユーザごとのアクセス権を持たせたい場合が出てきます。レコードについては、特定のフィールドにユーザ名やグループ名を入力することで、そのユーザやグループに所属したユーザでないと参照や更新ができない仕組みを持っています。フィールドのデータはそれでいいのですが、メディアは実体はレコードと別に有ります。ここで、個々のメディアをコンテキストのレコードと結合させ、レコードごとのアクセス権をメディアにまで及ぼす仕組みを組み込みました。

まず、IM_Entry関数の2つ目の引数(オプション引数)に、キーが’media-context’で値がコンテキスト名(コンテキストのnameキーに対する値)を与えます。すると、メディアはこのコンテキストにある特定のレコードの、1つのフィールドのようなふるまいになります。

‘media-context’ => ‘context-name’,

実際にメディアにアクセスするパスは次のような形式にします。つまり、コンテキスト名、レコードの検索条件をパスに入れます。最初のfilesは特に意味はなく、相対パスの最初のキーワードです。context-nameは、media-contextの値と同じでなければなりません。そのコンテキストのkeyキーに対する値、つまり主キーがpidであるなら、たとえば、field=valueは、pid=312 のような値になります。

context.php?media=files/context-name/field=value/filename.png

ここでもし、media-root-dirが /var/www/media であるなら、実際に

/var/www/media/files/context-name/pid=312/finename.png

という絶対パスの画像ファイルが存在する必要があります。コンテキストの定義には、レコード単位のアクセス権設定があれば、メディアに対するアクセス権の認証の確認を行い、ユーザを特定します。pid=312のレコードがそのユーザにアクセス権があるのかどうかを確認することによって、アクセスの可否を決めます。従って、適当にpid=316などとパスを変えても、その条件で検索されたレコードが他のユーザに対する権利があるものであれば、400番台のレスポンスを返してデータは返しません。認証の確認を行い、そのユーザに対するアクセス権がないものは出力しないという仕様によりアクセス権は定義した通りに適用されます。

pid=312を、age=45のようにできると言えばできますが、おそらく、そういうディレクトリは存在せず、漏洩にはならないでしょう。

ファイルのアップロードのコンポーネントは、コンテキスト名やレコード検索条件のパスを自動的に作成するようにも作られています。

とまあ、ここまで書いたところで、ソースに間違いを発見! また、グループ名をフィールドに入れる場合はまだ実装していなかったことも思い出しました。今のところ、メディア関連処理は、開発している側で「必要の合った」状況しかうまく動かない可能性があります。ぜひとも、いろいろ試してフィードバックをください。

(まだ続く)

[IM]MediaAccessクラスと新たな拡張点

INTER-Mediatorはデータベースの内容を取り出し、Webページに展開し、場合によっては書き直したデータをデータベースに書き戻します。この一連のDBを中心としたデータの流れがあるのですが、Webアプリケーションではこれだけではすべてはまかなえません。HTMLでページを作るときは写真などの画像を別のファイルで供給します。このHTML外に実データが存在するようなものを「メディア」と呼ぶことにします。

メディアにはいろいろな種類があります。主要なものはIMGタグ要素で表示する画像、OBJECTタグなどで表示するFlashのコンテンツやビデオなど、そしてリンク先で得られるものとなるでしょうか。また、それぞれ、サーバ上にスタティックにあるものや、データベースのオブジェクト型フィールドに存在する場合もあります。WebページからこれらにアクセスするためのものがMediaAccessクラスです。概ね、次のような用途に使用方法は分類できます。

  • サーバ上にある画像ファイルを参照する
  • FileMakerのオブジェクトフィールドを参照する
  • データ生成を指定したクラスにさせる、生成結果を返す

ここで、単なる画像は普通にHTMLを書けばいいじゃないかと思うかもしれませんが、MediaAccessクラスを使う理由は、認証やアクセス権の設定との連携が可能になっているところです。

サーバ上にある画像ファイルを参照する

スタティックな画像で、認証が絡まない場合は、普通にIMGタグを記述します。また、画像ファイル名やパスがデータベースにある場合でも、必ずしもMediaAccessクラスは必要ないかもしれません。たとえば、あるテーブルのfilepathフィールドにファイル名が記録されているのなら、このようなIMGタグで表示できるでしょう。#srcにより、この要素のSRC属性にフィールドのデータが追加されます。

<img src=”figs/” class=”IM[context@filepath@#src]” />

ページの内容を認証しなければ参照できないようにしたとき、やはりページに埋め込んだ画像なども認証を経由したいと考えます。Webサーバレベルでの認証の場合は、ある意味、認証がない場合と概ね同じことで可能ですが、INTER-Mediatorの認証機能を使った場合、SRC要素がスタティックな画像を参照していれば、もしかしたら、認証しなくても画像だけは見えてしまうかもしれません。そんなことをしても大した情報流出にならないとも言えるのですが、ガードしたいものはガードするのが基本ですし、認証しているのに一部は誰でも見えるのは正しい運用ではありません。

そこで、IM_Entry関数の2つ目の引数(つまりオプション引数)に、’media-root-dir’というキーで、サーバ上にあるメディアファイルのフォルダへのルートからの絶対パスを指定しておきます。たとえば、

‘media-root-dir’ => ‘/var/www/images’,

となっていたとします。あるページファイルで使われている定義ファイルのパスがcontext.phpだったとします。すると、次の部分URLが、メディアを返します。ここで背後では、MediaAccessクラスが使われており、このクラスがメディアに対するプロキシになっているとも言えます。

context.php?media=ch1/shot345.png

たとえば、IMGタグのSRC属性に上記のURLを記述すれば、media-root-dirと合成して、「/var/www/images/ch1/shot345.png」というファイルをアクセスし、その内容を取り出して、適切なMIMEタイプのヘッダとともに出力をします。

前記のURLにある画像ファイル名「shot345.png」がフィールドpicfileにあるような場合には、ページファイルの要素には以下のような記述ができます。media=は決められたキーワードです。

<img src=”context.php?media=ch1/” class=”IM[table@picfile@#src]” />

このとき、定義ファイルで認証が必要な設定になっていると、SRC属性のURLからの取得時にMediaAccessクラス内部で、認証の確認を行います。ここで、一般の認証時に使っているクレデンシャルをそのまま使うことが実はできません。一般の認証では、1つのアクセスごとに異なるチャレンジデータを使うことで、認証の乗っ取りをしにくくしています。しかし、そのためにメディア処理ではその仕組みが使えません。なぜなら、メディアへのアクセスは並列的にブラウザから行うからです。

そこで、メディアアクセス時の認証のためだけのクレデンシャルを生成させるようにしています。たとえば、直前のimgタグ要素の場合、tableという名前のコンテキストからの取得となりますが、そのコンテキスト定義(IM_Entry関数の第1引数)の’authentication’キー内部の’media-handling’キーの値をtrueにします。すると、このコンテキストのデータを取得するときに、サーバ側からメディア用のクレデンシャルを出力します。

IMGタグ要素などから実際にメディア取得を定義ファイルに対して行うとき、media=があれば、MediaAccessクラスに処理をまかせます。そのとき、クレデンシャルがクッキーに記録されてサーバ側に到達し、それを発行したクレデンシャルと比較することで認証が通っているかどうかを判定します。つまり、コンテキストが得られるということは認証が通っているわけで、その信頼関係をもとに秘密の合い言葉をやりとりします。このクレデンシャルは繰り返し使われる可能性があります。クッキーに記録し、時間が来れば消去するようにはなっているものの、クレデンシャルを盗まれるのはそれだけでメディアアクセスを可能にしてしまうことになります。従って、HTTPS(SSL)でのサーバ運用は必須とも言えるでしょう。

普通のHTMLでMediaAccessを経由させる

Webページを作って画像を埋め込むと、<img src=”img/cover.jpg” /> などと記述します。これをMediaAccessクラスを使ってデータの取り出しを行いたいとしたら、ソースを全部変更しないといけないのかというとそうではありません。リダイレクトを使うことで、HTMLソースはそのままに、MediaAccessクラスを使うことができます。たとえば、.htaccessファイルを作り、たとえば次のような記述を作ります。

RedirectMatch img/(.+) http://host.name/myapp/context.php?media=$1

すると、<img src=”img/cover.jpg” /> は、<img src=”http://host.name/myapp/context.php?media=cover.jpg” /> と同じことになるわけです。

部分パスと絶対パス、それらを消したり追加したりといろいろ複雑にはなりますが、このスタティックなメディアを認証した上で表示できるようにしたのがMediaAccessの最初のインプリメントでした。

(この内容、続く)