FileMaker 13で、XMLおよびXSLTによる出力

久しぶりにタイトルのような事をやりました。この記事のハイライトは、変数でXSLTファイルの位置を指定できるようになったものの、エラーが出るという事象に遭遇しますが、その対処方法が分かったということです。

XML出力は、FileMaker 5くらいからありますし、出力するXMLデータにXSLTを適用して、テキストやらHTMLやらを生成することができるので、ちまちまと文字列処理を書かなくてもいいので便利です。XSLTの習得が大変という話もありますが、今時この種の情報は「基本知識」でしょうから、勉強すればいいのです。私自身は幸いにも、今はなくなってしまったXSLTでのWeb公開の時代に、かなり勉強していたし実稼働させたソリューションもあったので、自分的にはXSLTはOKです。この記事では、XMLやXSLTあるいはそれらを利用するFileMakerの機能については詳細には説明しませんので、必要な方は予習をしてください。

現実にXML/XSLTを使うとき、当然ながらXMLはテーブルを出力するときのフォーマットなので、テーブルから生成されることになります。一方、XSLTはどうでしょうか? 確実は方法は、Webサーバ等に配置して、URLで指定することですが、データベースファイルと別の管理対象ができて不便です。そこで、XSLTのテキスト自体をテーブルのフィールドにテキストとして保存しておくことで、データベースと一体化して管理ができて便利です。ただし、そのXSLTのテキストを利用するときにはファイルにしなければなりません。現実にはスクリプトを組むとして、1フィールド、1レコードだけのタブ区切りで出力すれば、余計な “” などは含まれません。ただし、その1フィールドに、改行が入っていると、コード番号12のものに置き換えられます。後から変更するのは面倒なので、XSLTのテキストから改行を除去する計算式を設定した計算フィールドをエクスポートすればいいのです。もちろん、XSLT内では改行の有無で動作が変わらないにしなければなりません。

さて、そこで、このXSLTファイルをどこに保存するのかということですが、WindowsでもMacで稼働できるようするには、テンポラリフォルダがいいのではないでしょうかということで、次のようなスクリプトで変数にパスを入れることにしました。順当な方法かと思います。

スクリーンショット 2014-06-26 10.47.20

この変数$templateJの結果を利用して、XSLTのテキストをエクスポートします。前に説明したように、スクリプトで、1レコードのみを対象レコードとして、改行を省いたテキストのフィールドだけをタブ区切りで出力します。出力ファイルに、変数$templateJを指定します。

ここで、XSLTによる変換をスクリプトのどこかで組み込むとします。スクリプトステップの「レコードのエクスポート」で、「出力ファイルの指定」を行い、OKボタンを押すと、ファイルタイプが「XML」の場合はさらに次のダイアログボックスが表示されます。ここで「XSLスタイルシートを使用」を選択し、ファイルを指定するわけですが、「ファイル」だとダイアログボックスが出て来て、特定のファイルの指定が必要です。Get ( テンポラリパス ) は一定のパスではない模様ですし、Win/Macでパスは違います。なので、「計算」を選択して、前の変数$templateJを指定して、実際に動かしてみます。ボタンと計算式の文字列が重なっているところにFileMaker社のこの機能への姿勢が見て取れます。

スクリーンショット 2014-06-26 10.46.40

すると、XMLのエクスポートをするときに、次のようなエラーが出ます。SAX(XMLのパーサ)のエラーがそのまま見えていて意味が分かりにくいですが、ようするにファイルが見つからないということです。Windowsでは、実際に見つからなかったパスも見えています。

スクリーンショット 2014-06-26 10.49.33

変数$templateJで出力し、その直後に変数$templateJでファイル参照して見つからない。ようするにバグなのですが、バグで使えません、以上ということでは前に進みません。バグは直らない可能性もあるからです。

そこで、変数$templateJの内容を、MacとWindowsで調べ見ました。Macだと、「/Macintosh HD/var/tmp/….」みたいになっています。Windowsだと、「/C:/Users/msyk/….」となっています。まず、Macは、明らかにパスの最初のコンポーネントを取り去れば、存在可能なUNIXパスになります。Windowsは…ともかく、まず、XSLTのテキストをエクスポートした後、以下のような「変数を設定」スクリプトステップを追加して、最初のコンポーネントを省いてみました。

スクリーンショット 2014-06-26 10.46.58

Macでは、予想通り、それでエラーなく動きました。一方、Windowsは別の対策をしないといけないかと思ったのですが、なんと、ちゃんと動きました。UNIXみたいなパスの与え方でどうやら動くようです。

XSLTのTipsを少し書いておきましょう。関連レコードのフィールドは、TOさえあれば出力できると思っていたら、ポータルに展開しないと出力されません。ポータルがない場合は、ちゃんと警告メッセージを出して、最初のレコードの値だけが出力されると言われます。ということで、ポータル作成はさぼれません。

関連レコードのデータは、たとえば2つのフィールドがあるとして、関連レコードが3つあると、次のように出力します。このノードの上位ノードのタグはROWになります。

<COL><DATA>1</DATA><DATA>2</DATA><DATA>3</DATA></COL>
<COL><DATA>abc</DATA><DATA>def</DATA><DATA>ghi</DATA></COL>

上記の2つのフィールドの番号を1、2とします。ROWタグがカレントノードのとき、上記のデータを展開するのにfor-eachを使うのですが、同じポータルにある関連レコードがいくつかるとき、for-eachをそのうち1つのフィールドに対して行います。次のような感じです。ネームスペースの略号は、xsl、fmrとしています。

<xsl:for-each select="fmr:COL[1]/fmr:DATA">
    <div>
        <xsl:value-of select="." />
        <xsl:value-of select="../../fmr:COL[2]/fmr:DATA[position()]" />
    </div>
</xsl:for-each>

最初のfor-eachは普通にDATAタグが複数あるので、それを1つ1つ取り出します。3行目の「.」によって、1つ目のフィールドの現在のDATAのテキストを取り出すので、「1」「2」「3」という値が順次取り出されます。2つ目のフィールドは、4行目にあるように、XPATHの記述で、カレントノードからROWまで「../..」でさかのぼり、そこから、COL、DATA集合へと下ります。そして「position()」というのは関数です。for-eachでループしているときに、何番目なのかを示す値を返します。これで、「abc」「def」「ghi」という文字列が順次得られます。

最後に大サービスで(笑)、FileMakerのXML出力向け(ただし、FMRXMLRESULTスキーマ)の汎用的なテンプレートを書いておきましょう。コピペして、あとはデータの順序に合わせてFMPXMLRESULTやROWタグ要素に対応するテンプレートにHTML等で出力テンプレートを書くだけです。

<?xml version="1.0" encoding="UTF-8"?>

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xhtml="http://www.w3.org/1999/xhtml"
    xmlns:fmr="http://www.filemaker.com/fmpxmlresult"
    exclude-result-prefixes="xhtml fmr">
<xsl:output encoding="UTF-8" method="xml" indent="yes"
   omit-xml-declaration="yes"/>

<xsl:template match="fmr:ERRORCODE | fmr:PRODUCT | fmr:METADATA">
</xsl:template>

<xsl:template match="fmr:FMPXMLRESULT">
    <html><body><table>
       <tr><th>Name</th><th>Activity</th></tr>
       <xsl:apply-templates />
    </table></body></html>
</xsl:template>

<xsl:template match="fmr:ROW">
    <tr>
        <td><xsl:value-of select="fmr:COL[3]/fmr:DATA[position()]" /></td>
        <td>
            <table>
                <xsl:for-each select="fmr:COL[1]/fmr:DATA">
                   <tr>
                       <td><xsl:value-of select="." /></td>
                       <td><xsl:value-of select="../../fmr:COL[2]/fmr:DATA[position()]" /></td>
                   </tr>
                </xsl:for-each>
             </table>
         </td>
    </tr>
</xsl:template>

FileMaker Server 13トラブル対策メモ

Mac OS X Mavericks+Server v3上で稼働させているFileMaker Server 13が、再起動後ちゃんと動かなくなりました。トラブル対策をして動くようになったので、「今度また再起動したとき」に慌てないように、メモっておきます。

そもそも、あるデータベースに対して、スケジュールでスクリプトを動かそうとしたのでした。そうしたら、正しいアカウントで認証しても実行できるスクリプトがないと言われました。そんなはずはありません。他のパネルを見てみると、公開しているデータベースの一覧には何も出ていないのに、FileMaker Proからはデータベースを開くことができます。つまり、一覧がおかしいということでしょうか。ここで、Admin-Console再起動をするか、サーバの再起動をするか、非常に迷ったのですが、いろいろあってサーバの再起動をしました。正確には、30分待っても落ちなかったのでディスクアクセスをしていないタイミングでリセットしました。

その後、起動はして、FileMaker Proからのデータベース接続はできましたが、Admin-Consoleは開かず(ログインパネルすら出ない)、そしてWeb公開も失敗しています。Webサーバのデーモンhttpdが1つも上がっていない。

まず、やってみたのは、以下のコマンドの入力です。Admin-Consoleにログイン可能な管理アカウントは、ユーザ名がadmin、パスワードはpassword(もちろん架空のもの)です。まず、ここまでで、Admin-Consoleは接続できるようになりました。

fmsadmin stop adminserver -u admin -p password
fmsadmin start adminserver -u admin -p password
fmsadmin stop wpe -u admin -p password
fmsadmin start wpe -u admin -p password

しかし、まだ、Webサーバがこれでも動きません。次に行ったのは、次のようなコマンドです。

cd /Library/FileMaker\ Server/HTTPServer/
sudo touch stop
sudo touch start

つまり、FileMaker ServerのディレクトリにあるHTTPServerというディレクトリの中のstopおよびstartというファイルの更新日時を現在にします。別の記事で紹介した通り、このファイルが変更されると、FileMaker ServerのWebサーバが停止あるいは開始します。ここまでやると、実際にWebサーバが動き出しました。

fmsadminによるwpeサービスのstart/stopは不要なのかもしれませんが、Webサーバのデーモンhttpdを強制的に動かさないといけないのかもしれません。

ノートをつけてみた

コンピュータ使いにとって、メモにはこだわりがあるのが普通だろう。情報機器を常日頃から使いこなしているのなら、スマートにメモを取るなんてことは当然のことだ。…といいたいところではあるが、実際にはどうだろう? あれこれ試しては新しいサービスやソフトに乗り換えてみたり、戻ってみたりと試行錯誤する日々ではないだろうか?

そんな中、STAP細胞の真偽を巡って「ノート」という存在が明らかになった。そういえば、大学のときも化学系の連中はノートを使っていたような記憶があるが、私は電気系であり、あまり実験ノート的な縛りはなかった記憶がある。工学系はやはり再現性が最も基本だから、ノートをエビデンスとして使うということより重要なことがあるのだが、それはさておき、ふと思って日々の仕事の内容を「ノート」を付けることでどうなるかを見てみた。ただ、1冊は以前からずっと使っているのだが、案件用に1冊追加し、それまでのノートはそれ以外の用途と2冊を使う事にした。それまでの記録の付け方は後に説明しよう。

さて、5月の1ヶ月間でどのくらいのページになったかと言えば、案件用23ページ、その他9ページとなった。もちろん、ノートにすべてを書いているわけでなく、Macに向かって打ち込む以外の「記述」をすべてノートでやってみた結果である。とりとめのないメモや、ざっくりとした図や表などをノートに書いて行った結果である。

スクリーンショット 2014-06-12 16.17.58

それまでどうしていたかと言うと、まず、案件ごとのメモは、きわめてシンプルに、その案件のファイルがあるフォルダにテキストファイルで残していた。URLやアカウント、情報の断片など、とにかくばんばんとコピペする。

そして、まとまった情報は、EvernoteとFileMakerで作ったデータベースに保存する。当初はFileMakerだけだったのだが、iOSで共有するとなるとEvernoteの方が手軽である。特に、URLを入れればリンクになるとか、そういうのはFileMakerではできないわけではないが、はっきり言ってたんに面倒になるだけなので、現在は作る文書によって使い分けている。FileMakerはどちらかと言えば書庫的であり、EvernoteはWiki的に使っている。複数人が関わるプロジェクトではBacklogを使い、場合によってはRedmineに入ることもある。

さらに、「紙切れ」を用意している。最近は、日めくりの裏を永年愛用している。スタートレックやギターの日めくりを買っており、翌年はメモとして活躍するのである。

思い付いたとき、そこでテキストファイルが開いていれば書き込み、Evernoteが開いていれば書き込み、それも面倒なときにはまずは紙切れに書いておく。紙切れに書いたものは、一定期間後に電子的な手段にコピーするが、その一定期間を過ぎて不要になったものまではわざわざ入力はしないで捨ててしまう。どうでもいい情報は適当にスクリーニングされるということだ。この「一定期間」を置くというのはなかなかみそで、メモ情報が爆発しないという利点もあると考える。もちろん、面倒を避けているという消極的な意味もあるが、短期間で情報価値がなくなるものは、長期間後に復活する可能性は極めて低いと言っていだろう。

ノートにして不便な事は、1つの案件ベースでのトレースがしづらくなるということだ。そうやってノートを分けるということをすると、きっと今度はノートだらけになるのだろう。案件事にテキストファイルを作っている理由は、トレースがしやすいからでもあるし、何年か後に開くことを考えての事だ。

一方、ノートを書くという習慣は、考えたことに対して自己評価をする機会が与えられる感じがする。ガシガシと打ち込んでしまうとそれで終わってしまうのだが、手で文字を書くという時間の幅が考える余地を生み出すように思える。それはそれで悪い事ではない。

さて、これからどうしようか? やはり全面的にノートにするより、今までの方法にじわじわ戻すつもりである。コンピュータ使いとしては「ノートは不要」と言いたいところだが、ノートにはノートのいいところもある。iPadでJot!で手書きしていた時期もあるものの、やはりノートの方が疲れない気がする。だけど、ノートで全て行うのは、むしろきちんと整理ができない。結局のところは使い分けということになるのだろう。そして、日々の記録方法を改善することも仕事のクオリティを高めることになるのじゃないかと改めて感じる。

米国からカルガリ経由で帰国

バンクーバー経由でアメリカに入った帰りは、やはりエアカナダだったのですが、サンフランシスコからカルガリーまでUnited Expressで行き、そこで乗り換えるというスケジュールでした。これも、2014年6月の状況ですが、メモとして残しておきます。

往路は先にエアカナダだったのでいろいろな意味でスムーズだったのでしょうけど、復路は先にエアカナダのコードシェア便でスタートです。そうすると、エアカナダの事前のチェックインは、カルガリーと成田間のフライトしかできず、最初にサンフランシスコで乗るフライトは、空港に行かないとチェックインができません。ちなみに、エアカナダはiOSのPassbookですので、紙はまったく使いませんが、その前には昔ながらのチケットを持ってカルガリーまで移動することになります。

まず、出発のサンフランシスコからの便は、ユナイテッドとエアカナダのコードシェア便で、運用はユナイテッドです。エアカナダのカウンターは国際線側にしかないため、この場合は運用しているユナイテッドの、しかも国内線のターミナル3に行って手続きをすることになります。カルガリーはもちろんカナダなのですが、国内線扱いですね。自動チェックイン機で、パスポートをかざせば、問題なく、チェックインがそこでできました。荷物もそこであずけて、成田行きというタグを貼ってもらいます。そして、セキュリティチェックを受けて普通に乗り込みます。23Aという席だったのですが、いちばん後ろの席でした。小さなジェットで約2時間半でカルガリーです。なお、最初に登場するときにバッグを預ければ、そのバッグの扱いがどうなるかを紙でもらえます。飛行機では税関深刻の用紙を記入しておきます。

カルガリーではまず入国審査をします。そこでけっこう並んだので焦ったのですが、幸か不幸かカルガリー発の飛行機も遅れたので、ダッシュしなくて済みました。アメリカからの便だと全員入国審査があり、たままた飛行機が重なったのか、かなりの列ができました。入国にはビザは不要で(乗り換えだけだから)、単にどこから来て、どこに行くのかを言って、滞在時間くらいを言っただけです。アメリカの入国のように、指紋や写真は撮らないのですぐに終わります。その後、ターンテーブルに行って、まず自分の荷物を持ち、関税申告の用紙の回収場所を通り抜けます。そこを出たすぐのところに、エアカナダのカウンタがあって、そこで荷物を再度預けます。単にチケットのチェックをして、ベルトコンベアに載せるだけです。この荷物の移動を自分でやらないといけないということです。

そして、後は飛行機に乗り込みますが、工事中なのか、迷路のように入り組んだところを通貨して乗り場に行きました。そして、カルガリー発の便に乗る時に、入り口の係の人がパスポートチェックをします。Passbookのチケットとパスポートを渡し、検索をして確認すればOKです。iPhoneとパスポートを返されるときも、紙のチケットと同じように、パスポートにiPhoneをはさんで返してくれます。まあいいのですが、習慣って変わらないのですね。カルガリーからの便は半分ほどしか席が埋まってない感じでした。

ちなみに、サンフランシスコ空港まではいつもBARTを使っているのですが、到着時に何も考えずに往復分のチャージをしました。しかし、今回、朝早い便だったのでBARTで行く事ができず、シャトルで移動しました。チャージされたBARTチケットが余ってしまって困っています(苦笑)。

Appleの新言語Swiftは普及するのか?

もちろん、月日を経ないと結論は出ませんが、今時点では今後普及すると考えます。その理由を考えてみました。

まず、「開発言語の大きな変更」というのは、Mac OSからMac OS Xへの移行時にありました。その成り行きは非常に重要です。Mac OSは、CあるいはC++が主要言語だったと言えますが、Mac OS XはOPENSTEPからの名残でObjective-Cが開発言語になることが、当初NeXTとAppleが合流したときの事実でした。しかし、既存のMac OS開発者からは支持は得られず、AppleはMac OS Xの正式リリースが近づく頃にJavaに対応しました。1999〜2000年頃は、Javaが登場して4年程度の頃で、エンタープライズ分野で使われ始めるなど、ある意味では注目が集まっていた言語でもありました。また、その頃は、Flash vs Javaという構図でもまだ微妙に拮抗していた時期です。Appleは「Javaだとみんな納得してくれるだろうし、他のプラットフォームの開発者にもアピールできるだろう」と考えたのかもしれません。しかし、現実はそうではありませんでした。技術的な問題と、心理的な問題があったのです。

おそらく、いちばんの見込み違いは、これまでNeXTの世界でObjective-Cで開発して来た人たちよりも、Javaだから参入しようと思った人たちの方が少なかったからでしょう。いまでこそ当たり前のOS Xですが、移行時は「Mac OS Xは失敗してMac OSに戻すだろう」みたいな発言を堂々と公言する人たちも多く居たので惑わされたのでしょう。WWDCに当時参加していた人は、Mac OSへの逆戻りはあり得ないことは理解していたと思います。しかし、プラットフォーム外の人はそうした変な噂にも惑わされて、言語以前にMac OS X自体への注目がされなかったのでした。この点が大きく、AppleもMac OS X発売直後くらいからObjective-Cにフォーカスし、Java対応は順次縮小して現在に至ります。

技術的な側面でも、Javaでは参照渡しという仕組みがないと言えばなく、Objective-Cで培われたAPIに微妙にマッチしていませんでした。そして、メモリモデルが異なる2つの言語では、思わぬ落とし穴がどこかにあって、「なぜか動かない」という箇所に当たってしまうこともあり、安心して使えなかったことも事実です。また、Javaは遅いという世間の意識にもマイナス方向の力が働きました。

そして年月が経過しました。魅力的な製品iPhone/iPadで稼働するアプリケーションを作りたいために、それまでAppleに見向きもしなかったたくさんの人が、Objective-Cの学習をしたのです。これは、Mac OSからOS Xへの移行時とはすでに状況が違います。Appleは、OS Xリリース前のJava対応の混乱から、「他の世界のプログラマに魅力がある」ということよりも、「今現在プラットフォームにコミットしている人たちの満足度」がより重要であるということを学習しているのだと思います。

しかし、Objective-Cへの不満はいつもどこかでつぶやかれていました。そこで、まず取り組んだのが、複雑なメモリ管理へのテコ入れであり、ARC対応です。同時に、ブロック対応した点でも、コンピュータの世界の基準に追いつくことをしたということです。そして、今回のSwiftがあります。iPhoneアプリを作りたいことで、Objective-Cを学習するような人なら、Swiftの学習をすることくらいはさほど難しくないと言えます。しかも、文字列を+でつなげられるわけで、JavaScriptなど多くの人たちがObjective-C以外で経験して来た言語に近い訳です。Objective-Cが理解できるプログラマなら、Swiftは理解できるでしょう。しかも、?によってよりシンプルに記述できるとか、プロパティがオブザーバブルといったより良い特徴も目立ちます。

さらに、「すでに有名である言語である必要はない」という意識も、Appleには現在のObjective-Cプログラマの増加から出ているのでしょう。既存の言語、ruby/Python/JavaScript等ではない理由としては、すでにコミュニティがあって色が付いた言語だと、その世界のしきたりに引っ張られるのですが、「新言語」と言い切ってしまえば、Appleは独自にその中身を構築できるのです。これは、もちろん、Appleにとって有利に働きますし、そういうことでないと、Cocoaとの完全な統合はできないと言えます。Javaのときの技術的な問題に対する対処も、Appleの手中に最初からあるということです。

Mac OS X初期の頃と違い、たくさんの開発者がすでにコミットしている上では、新しい言語は十分に受け入れられるだろうと判断したのでしょう。また、iOSのアップデートがきわめて順調である点も追い風になるでしょう。これから開発するアプリケーションは、iOS 8以上対応という決定がなされるのがおそらく普通かと思われます。そうなら、思い切ってSwiftでやるぞという結論になる確率も高いということです。

そういうことで、Swiftは昔のJava for Mac OS Xのようにはならないと思います。OSのバージョンアップのスピードを考えれば、来年のWWDCの頃にはSwift率はけっこう高くなっているのではないでしょうか。半々くらいになるかな?(数字は出したくないのだけど〜笑)また、プログラミングと言えばいまだに「言語の理解」という間違った意識がありますが、実際には「フレームワークの理解」ということの方がよほど大変です。SwiftになってもCocoaの諸概念を理解して利用するという点では、Objective-Cと違いはありません。現在のiOS開発者にとっては、「ちょっと書き方が変わる」くらいの話であり、今までがんばって学習してきたCocoaの知識は十分に活かせるし、むしろそれらが必要なのです。Appleはきちんとレールを敷いた上で、新しい言語を持ち出したのです。

エアカナダで日本からバンクーバー経由でアメリカへ行くとき

周知の事実でしょうけど、自分のメモも含めて書いておきます。先月(2014年5月)、成田からエアカナダを使って、バンクーバー経由でサンフランシスコに行きました。ビザとかカナダ入国などはどうなるんだと思っていたのですが、事前に、「バンクーバーにはアメリカ入国管理事務所があり、カナダへ入国することはしない」と調べていましたが、思った以上にスムーズに行けました。

まず、飛行機内で入国審査用の用紙を記述しますが、そこでは、カナダではなく、USA向けの用紙をもらってそちらに記入しておきます。飛行機から降りると、USAと書かれた方にずっとどんどんと歩きます。けっこう延々と歩きますが、同じ飛行機の人はほとんどはカナダ入国側に行き、閑散とした通路をひたすら歩きます。まず最初のところでは、パスポートとビザを聞かれます。ここではESTAであることを告げると、エレベータを降りてセキュリティチェックへ進みます。先にセキュリティチェックを受けます。その後、荷物確認があって、その後に入国審査が行われます。米国の空港に到着すると、ほぼ全員が入国審査にならび、ここですごく時間が取られる事があって焦る事もあるのですが、バンクーバーは、どこも、列ができても数分単位で終了して先に進めました。ほぼ移動時間です。

預ける荷物も、バンクーバーではピックアップしません。その代わりにまず、荷物確認のところで、バッグのデジカメ写真を見せてもらいます。そして、これでいいよね? 数は合っているね?と聞かれてYesと言えば、それだけです。また、入国審査でも、同じ事が聞かれます。自分のかばんはあまり他で見ない色のベルトをしているので一目瞭然ですが、逆に言えば、デジカメ写真で特定できるような何か目印があると安心でしょう。そして、荷物に触る事なく、米国行きの飛行機に積み込まれて送り届けられます。トランジットで預け荷物のピックアップをしなくていいのはけっこうらくちんです。到着後、1時間弱で、フリーとなり、のんびりショッピングモールみたいなところで時間ができ、食事も落ち着いて取る事ができました。経由便はユナイテッドでした。

[IM]PostgreSQL利用者のためのガイド

INTER-Mediatorの開発を、MySQLとFileMaker上で行っているので、他にPostgreSQL/SQLiteにも対応しているとは言え、ご不便をおかけしています。なお、サンプルサイトで、PosgreSQLは動かなかったのですが、今は動くようにしました。

まず、PostgreSQLのサンプルを動かすためのデータベースのスキーマファイルは、

dist-docs/sample_schema_pgsql.txt

というファイルにあります。冒頭にコマンド例がありますが、PostgreSQLがどのOSのどのディストリビューションで動いているかによって、いろいろ違いがあると思います。

OS X Serverの場合:PostgreSQLの稼働ユーザはpostgresではなく、_postgresです。-Uの引数を_postgresにします。

INTER-Mediatorとの絡みについては、MySQLとほとんど同じなのですが、1点違いがあります。PostgreSQLでは、主キーフィールドにシリアル値を入力する方法として、SEQUENCEオブジェクトを利用する方法と、SERIAL型を利用する方法があります。どちらの方法も、原則として、定義ファイルのIM_Entry関数の第1引数にあるコンテキストに、’sequence’ => ‘xxxx’ として、SEQUENCEオブジェクトを指定する必要があります。

sequenceキーに対する値がない場合の問題は、ページネーションコントロールを表示して1レコードずつ表示しているとき、「レコード追加」ボタンをクリックしてレコードを作成した場合、新たに作られたレコードが現在のレコードになっておらず、レコードの移動をしないといけません。他は問題ないのですが、これだけの問題ではありますが、使い勝手が変わるので注意が必要です。なお、検索して参照するだけなら、sequenceキーの指定はなくてもいいのかもしれません。

SEQUENCEオブジェクトを使用する場合

SEQUENCEオブジェクトを使用する場合、スキーマは以下のようになると思います。SCHEMAはim_sample、アクセスユーザはwebを想定しています。テーブルとシーケンスの両方のオブジェクトにアクセス権を与えるのを忘れないようにしましょう。

CREATE SEQUENCE serial START 1000;
CREATE TABLE person (
  id INTEGER DEFAULT nextval('serial'),
    :
}
GRANT ALL PRIVILEGES ON im_sample.serial TO web;
GRANT ALL PRIVILEGES ON im_sample.person TO web;

そして、定義ファイルでは、次のように、sequeceキーで、シーケンスオブジェクトの名前を指定します。

array(
 'records' => 1,
 'paging' => true,
 'name' => 'person',
 'view' => 'im_sample.person',
 'table' => 'im_sample.person',
 'key' => 'id',
 'repeat-control' => 'insert delete',
 'sequence' => 'im_sample.serial',
),

SERIAL型を利用する場合

SERIAL型を利用する場合、以下のように、主キーフィールドの型をSERIALにすると思われます。このとき、背後では、「テーブル名_フィールド名_seq」というシーケンスオブジェクトが自動的に作られて、初期値が1になっています。自動的に作られるオブジェクトとは言え、アクセス権の設定は記述する必要があるのが一般的でしょうから、im_sample.person_id_seqに対してwebアカウントのアクセス権も設定しなければなりません。

CREATE TABLE person (
id SERIAL PRIMARY KEY,
}
GRANT ALL PRIVILEGES ON im_sample.serial TO web;
GRANT ALL PRIVILEGES ON im_sample.person_id_seq TO web;

SERIAL型を使った場合、INSERTでレコードを新規に作るときに、ここでのidフィールドへの値を代入はしないようにします。もし、自分で値を設定したい場合は、シーケンスの値と当たらないようにしないといけませんが、そこまでの状況でSERIAL型を使う事はほぼないと思われます。

そして、定義ファイルでは、このときも、次のように、sequeceキーで、シーケンスオブジェクトの名前を指定します。これをしないと、1レコード表示時に新規レコードを作成しても、新規レコードが編集状態で開いてくれません。

 array(
 'records' => 1,
 'paging' => true,
 'name' => 'person',
 'view' => 'im_sample.person',
 'table' => 'im_sample.person',
 'key' => 'id',
 'repeat-control' => 'insert delete',
 'sequence' => 'im_sample.person_id_seq',
),

[IM]JavaScriptコンポーネントのプラグイン

以前から、HTMLエディタのtinyMCE、コードエディタのCodeMirrorや独自作成したファイルアップロードコンポーネントを使えるようにしていたのですが、なんとか「プラグイン」的に使える状態になったので、一度ドキュメントを作成します。JavaScriptで作ったコンポーネントに、データベースにあるフィールドの値を表示し、修正するとそれが書き戻される仕組みを提供します。ただし、コンポーネントごとに、初期化の方法は違うので、その部分を吸収するプラグインを作らないといけません。

JavaScriptコンポーネントの使い方

まず、JavaScriptのコンポーネントを使いたい場合には、次のように、data-im-widget属性にキーワードを書きます。プラグインができていればこれだけです。

<div data-im="testtable@text1" data-im-widget="tinymce"></div>

ここで、tinyMCEを使うにはプラグインが必要ですが、これについては、すでにINTER-Mediatorの中にあります。Samples/Sample_webpage/tinymce_im.jsがそれなので、たとえば、ページファイルのヘッダ部に、次のような記述を行ってtinyMCE自身の読み込みと、プラグインの読み込みを行っておきます。もちろん、パスは適切なものを指定してください。

<script type="text/javascript" src="tinymce/js/tinymce/tinymce.min.js"></script>
<script type="text/javascript" src="tinymce_im.js"></script>

Ver.4.4現在、tinyMCE、CodeMirror、それから独自に作ったファイルアップロードコンポーネント(Samples/Sample_webpage/fileupload_MySQL.htmlがサンプル)が利用可能です。

JavaScriptコンポーネントプラグインの作り方

プラグイン(前記のtinymce_im.jsに相当)は、JavaScriptで記述します。もちろん、tinymce_im.jsもサンプルとして参照する必要があるでしょう。

プラグインのファイルでは、IMParts_Catalog変数のオブジェクトに、プラグインのオブジェクトを追加します。このときのキーが、data-im-widget属性に指定するキーワードとなります。以下はその基本構造です。

IMParts_Catalog["tinymce"] = {
    instanciate: function (parentNode) { },
    ids: [],
    finish: function (update) { }
}

右辺のオブジェクトは、instanciateとfinishという2つのメソッドを持つ事が重要です。INTER-Mediatorは、ページ合成時に、im-data-widget属性があるノードを見つけると、そのノードを引数にとって、instanciateメソッドを呼び出します。

一方、ページ合成の最終段階、つまり、DOMオブジェクトが確定してページ上に存在する状態になった後に、finishメソッドが呼び出されます。結果的に、im-data-widget属性が設定された要素×レコード数の回数instanciateメソッドが呼び出され、最後に1回finishが呼び出されます。

プラグインの作業として必要なこと

この2つのメソッドが行うことは、コンポーネントに対するゲッタおよびセッタメソッドをそれぞれ展開したコンポーネントに対して設定することです。また、対応コンポーネントのid属性値を得るメソッドも実装します。たとえば、instanciateメソッドに記述するとしたら、次のようになります。instanciateメソッドを呼び出されたときの引数parentNodeあるいはコンポーネントのルートの要素に対して、以下の決められた名称のメソッドを実装します。メソッドの中身はtinyMCEの場合の例です。

parentNode._im_getComponentId = function () { // data-im-widgetのある要素に設定
    var theId = newId;
    return theId;
};
parentNode._im_setValue = function (str) { // data-im-widgetのある要素に設定
    var targetNode = newNode;
    targetNode.innerHTML = str;
};
targetNode._im_getValue = (function () { // コンポーネントのルートの要素に設定
    var thisId = targetId;
    return function () {
        return tinymce.EditorManager.get(thisId).getContent();
    }
})();

instanciateメソッドでの作業

通常、JavaScriptのコンポーネントは、特定の要素にidやclass値を適当に与えて、その要素の中に必要なオブジェクトを詰め込むといった動作をします。つまり、起点となる要素を用意しておき、そこに必要なオブジェクトを追加します。tinyMCEだと、手軽な作り方はTEXTAREAタグ要素を用意することですが、ページファイル上に記述した要素がどんな種類のタグ要素でもいいように、data-im-widget属性がある要素の子要素にTEXTAREAタグ要素を作り、その要素をtinyMCEで初期化するようにしています。

そのTEXTAREA要素のid属性は、適当に付けます。targetNodeで参照されるリピーター内の要素は、すでにid属性が設定されているので、そのid値に適当な文字を追加すれば、一意なid属性になります。_im_getComponentIdメソッドは、ここでのTEXTAREA要素に付けたidを返します。

なお、instanticateメソッド中は、まだ、リピーターはエンクロージャーに挿入されておらず、documentからたどれない状態になっています。その場合、初期化をしてもうまく動作しないと思われます。従って、instanciateメソッドでは、元になるTEXTAREA要素を作り、id番号を振り、そのid番号をidsプロパティの配列に追加して、必須のメソッドを定義するところまでしかできないのが一般的かと思います。idsプロパティはなくてもいいのですが、この後のfinalizeメソッドでそれぞれの要素を初期化するために、初期化すべき要素を後から特定できるようにするために、instanciateで作成した要素のidを残します。

instanciateメソッドの段階で実際に利用されるのは、_im_getComponentIdメソッドと_im_setValueメソッドなので、_im_getValueメソッドは実際には設定する必要はありません。_im_setValueメソッドについては、JavaScriptコンポーネントが初期化前であることを考慮して、機能する前の状態での値設定が可能なプログラムを記述する必要があります。

finalizeメソッドでの作業

この段階では、全ての要素がdocument配下にいるので、JavaScriptコンポーネントの初期化を実際に行います。なお、初期化した結果、ゲッタやセッタの動作を変えたい場合は、設定をしなおします。JavaScriptのコンポーネント内で修正した結果をデータベースに書き戻すには、_im_getValueメソッドを、初期化が終わった後の状態でのゲッタとして動作するように設定をする必要があります。単にデータベースに書き戻すだけでいいのなら、ゲッタの設定のみでかまいません。以下は、tinyMCEの例で、idsプロパティにコンポーネントのid属性値が配列として残されています。1つ1つの要素に対して、_im_getValueメソッドを追加しています。

 for (var i = 0; i < this.ids.length; i++) {
     var targetNode = document.getElementById(this.ids[i]);
     var targetId = this.ids[i];
     if (targetNode) {
         targetNode._im_getValue = (function () {
             var thisId = targetId;
             return function () {
                 return tinymce.EditorManager.get(thisId).getContent();
             }
         })();
     }
 }

tinyMCEのプラグインは、ページ上にあるすべてTEXTAREAをHTMLエディタにしてしまう動作で初期化するので、tinyMCE.initメソッドを呼び出すだけです。コンポーネントによっては、個別にオブジェクトをidsプロパティから取得したid値で参照して、それぞれに何らかのメソッドを適用しないといけないかもしれません。

結果的に、それぞれのJavaScriptコンポーネントごとにうまく初期化をしないといけませんが、場合によってはフレームワークの更新も必要になるかもしれません。

自分でJavaScriptコンポーネントを作る場合

自分で1からコンポーネントを作る場合は、以下のプラグインの骨格を作り、後は自由につくっていいでしょう。このクラスにメソッドを集めてもいいですし、他のファイルから参照してもかまいません。

IMParts_Catalog["myjscomponent"] = {
    instanciate: function (parentNode) { },
    ids: [],
    finish: function (update) { }
}

[IM]Postオンリーモードと「確認画面」が不要な理由

INTER-Mediatorでは、Postオンリーモードという動作をするエンクロージャーを定義でき、一般的なフォームのように入力をして、ボタンをクリックすると対応するコンテキストに新しいレコードを作るという動作を行います。ちなみに、INTER-MediatorではFORMタグを使わないで実装をしています。

通常、フォームで入力するときには、入力結果を改めて別の画面に明示し、入力者に確認をさせて、OKかあるいは修正するというユーザインタフェースが一般的です。そのサポートをもちろんPostオンリーモードでやってもいいのですが、私は不要と考えます。

なぜ、確認画面が必要なのか?

検索すると、やはり否定的な意見(例えばこちら)がいくらかある一方で、確認画面の作り方というサイトは大量に出てきます。現状、確認画面は作って当たり前というのがどうも業界の一般常識なのでしょうか? ただし、なぜ、必要なのかという積極的な説明はざっと見た限りでは見つかりませんでした。一方、不要というのは、「出したところで見ている人はほとんどいないだろう」という消極的な側面が理由としてはよく見られる内容です。

まず、一般的なWebフォームではなぜ確認が必要なのかということがあります。当然の理由として、「確認したいから」あるいは「確認する必要があるから」ということになりますが、理由はもう少し掘り下げて考えないといけません。おそらく、過去からのいろいろな経験の積み重ねで次のような理由があるからでしょう。

  1. フォームのページでは入力した情報がすべて見ているとは限らない
  2. 入力した情報以外のものを表示したい(販売サイトの合計金額や送料など)
  3. ReturnキーやEnterキーによって、submitボタンが押されたのと同じになり、意図せずサブミットしてしまうときにやり直しが効かない
  4. 積極的な理由はない、一般にそうだからとか、とにかく確認させたいといった理由

これらを順次検討しましょう。

フォーム上で見えていないのはデザインが悪い

理由1は、大昔の解像度の低いWebページを作っていた時代では確かにあったかもしれません。テキストフィールドが40文字としても、50文字の入力があるような場合、ユーザに対しては全項目を入力した後の状態の画面で全部が見えていないのは確かにあります。ユーザは垂直方向はもちろん、水平方向にもスクロールしないと文字が見えません。そのような状況で、念のため一度全部、ページに見せるということは確かに必要でした。

しかしながら、現在は解像度が高いディスプレイが一般的です。また、スマホでも何でも1ページに押し込めるなんてことはしないようにするという積極的な対応が普通に行われます。現在はデザインが重視されており、入力中に今自分が入力したものが見えないようなレイアウトは、一般にはデザインが悪いと言わざるを得ないでしょう。もし、この理由でフォームの確認画面が必要なら、まず、フォーム自体を見直す必要があり、その結果、確認画面は不要という結論になると言えます。

別の情報の確認は入力結果の確認ではない

理由2です。これは、入力した結果の確認画面の話でしょうか? 違います。これは、システムが生成した結果を利用者が確認する画面のことで、入力の確認画面の議論と混同してはいけません。この件は最後に別の確度でも検討します。

Returnキーの動作はFORMタグ特有の動作

古い時代のフォームでは、入力途中にReturnキーに触れてしまって意図せずサブミットされて困った人も多いかもしれません。理由3のように、テキストフィールドにフォーカスがあるときにReturnキーを押すと、自動的にそれを含むFORMタグのsubmitボタンがクリックしたものとみなされるのは一般的な動作です。

他のフレームワークならともかく、INTER-MediatorはテキストフィールドでReturnを押しても、submit的な動作はもちろん、設定やあるいはプログラムを追加しない限りは何も起こりません。つまり、INTER-Mediatorであれば、Returnで意図せずサブミットされることはありません。逆に、Returnでサブミットしたいのなら、何かしらの記述を追加しなければなりません。

考えましょう

理由4に対してはエクスキューズの必要すらないと考えます。利用者の事を真剣に考えていないサイトの存在理由って何でしょうか? そうか、失礼しました。そういうことを考えないから、個別の動作についても理由がないのですね。

作っているものは「入力フォーム」なのかを考える

以上のように、入力結果を新規レコードとして残すような入力フォームでは、INTER-Mediatorで適切なデザインをしていれば、確認画面が必要な積極的な理由はありません。しかし、理由2も含めて、いわゆる入力フォームにはそういう単純なパターンではない場合もあります。

理由2に記載したように、たとえば、商品発注の入力フォームがあって、サブミットしたら合計金額と送料を表示させたいとします。これは、実は、入力+システム処理結果の確認なのです。こういうものは必要と言えば必要ですが、これは入力の確認というよりも、システム処理結果の確認です(例1)。

また、これと同様なものとして、入力情報を、複数のページで入力する場合です。アンケートなどで1問ずつページが変わるものがあります。これも、過去のページを保持するという意味では、システムの処理が絡みます(例2)。

こうしたページをINTER-Mediatorで作る場合は、2つのアプローチがあると考えます。

  1. 必要なページをすべて1ページに作り、CSSのdisplay属性を利用して順次見せるような仕組みにして、最後にPostオンリーモードで新規レコードを作成する
  2. ページの最初にレコードを作ってしまい、それ以降のページは実際にはそのページの編集状態で提示する

例2のように、ステップで変わるページの結果を覚えるだけなら、1の手法だと比較的楽でしょう。戻るのも、そんなに労せず可能です。ただし、例1のようなECサイトなら、たとえば、選択した商品の単価や在庫情報、合計に対する送料の計算などロジックが絡みます。もちろん、AJAXで必要な情報を取得してクライアントサイドで計算することも実装の1つの手法ですが、いったんデータベースに入力した結果を参照する編集ページを遷移させる方がスキーマに組み込んだロジックが使える点では便利です。ただし、発注情報にステータス管理、つまり、確定前か後かなどの情報をきちんと設定するなど、やや複雑にはなります。

いずれにしても、これらの状況は、利用者としては入力フォームなのかもしれませんが、システムの動作を考えれば、入力だけでなく、システムの動作が絡むものです。そこをきちんと把握しないと、効率的な開発はできません。サービス提供側はユーザの視点も必要ですが、システム開発の視点を都合良く忘れてしまうのは良くありません。

それでも従来手法を求められたら?

INTER-Mediatorでシステムを作る場合、それでも「確認ページ」を求められたどうしましょうか?まず、よくある対処として「ボタンを押したらダイアログボックスで短いメッセージを出して確認」があります。まあ、その程度でいいでしょうという要求のような場合です。そのとき、Postオンリーモードでデータベースへの書き込みに行く前に実行するメソッドを定義して、そこでダイアログボックスを表示します。例えば、こんな感じです。INTERMediator.construct(true)の直前あたりに記述します。falseを返せば、何もしません。

INTERMediatorOnPage.processingBeforePostOnlyContext = function(node) {
    return confirm("本当に入力していいでしょうか? しつこいようですが、やっちゃいますよ");
}

あるいは、「入力フォーム」と「確認ページ」をそれぞれdiv要素で1ページで作っておいて、JavaScriptで切り替えます。入力結果を確認ページ側に転記するなど、地道なプログラムは必要ですが、困難さはほとんどないと思います。

ちなみに、値の確認は、バリデーションの仕組みがPostオンリーモードでも使えるので、それを活用すれば、未入力の確認等は定義ファイルへの記述のみで可能です。

[IM]ローカルコンテキストのキャッシュと悩みポイント

Ver.4.4を目前にして足踏みしている理由について、だいぶんと頭が整理されてきたので、一度ドキュメントを書きます。

INTER-Mediatorでは、コンテキストに対する検索条件を、プログラムから指定する仕組みを持っています。ユーザが入力した条件で再検索するといった仕組みを作りやすくするためものもです。INTERMediatorオブジェクトのadditonalConditionプロパティです。この種のプロパティはいくつかありますが、「additonalConditionプロパティ」で代表して議論します。

今まで、このプロパティは、ページを再合成すると消えてなくなりました。しかしながら、あるページで検索して、別のページに行き、さらに後ほど前のページに戻ったら、同じ検索条件で検索されていて欲しいと思うのではないでしょうか。そこから紆余曲折を端折ると、データベース関連したコンテキストとは別に、クライアント内部だけで利用可能なキーバリューストアの「ローカルコンテキスト」に、additonalConditionプロパティの値も入れて、ローカルコンテキスト自体をキャッシュする仕組みを作りました。キャッシュインやクリア等、APIも用意されています。要約すると、次のような実装になっているはずです(ま、そこもデバッグポイント)。

  1. 定義ファイルの最初の読み出し(SCRIPTタグで読み込む部分)時において、additonalConditionプロパティがnullであれば、{} で初期化する。ただし、ここでは、キャッシュへの書き込みは行わない実装になっている。
  2. INTERMediator.construct(); でページ合成を行うときには、キャッシュの値を取り出して、additonalConditionプロパティにセットする。
  3. additonalConditionプロパティに値を設定すれば、その都度キャッシュをするのが通常動作である。ただし、IE8のみ、1行呼び出しが必要になる。

条件を入力し、検索ボタンを押したときに、上記3のプログラムが動いて、additonalConditionプロパティは設定され、キャッシュもされます。もちろん、検索時に条件として、追加されます。そして、ブラウザを閉じて同じURLを開くと、キャッシュがあるので上記2のメカニズムにより、以前の検索条件が復活して、条件が適用されたクエリー結果が得られます。

そこで、また、別の条件で検索したとします。ここからが問題です。このときに、以前の条件をクリアして、additonalConditionプロパティを {} から構築しなおせば、おそらく問題はないと思います。つまり、キャッシュの内容と、additonalConditionプロパティへの設定プログラムが、同じ意図を持って存在している場合です。検索条件で使うフィールドがいつも同じだと、この条件が成り立つでしょう。

しかしながら、フィールドAで検索するというキャッシュと、フィールドBで検索するというプログラムの両方があったらどうでしょうか? Aか、Bか、はたまたAとBの合成か? 合成といってもどうするか? ここに明確な答えはないと思われます。アプリケーション次第なのではないでしょうか。そうなると、フレームワークとしてはどういう「仕様」を「サポートする」のかというのが非常に決めづらくなります。

additonalConditionプロパティへの設定は、必ずクリアしてから行ってください…というのもありなんですが、残っていることを利用して効率的に設定を適用したい場合もあります。前者は原則で、後者は理解した方はどうぞということになるのでしょうか? あるいは、キャッシュするかしないかというようなフラグを別途作る事で解決するのか、あるいはより混乱するのか。

この辺りを決めかねています。