この記事では、XPath を使って実現できるクールなことを利用した、レポート編集用のフォームを作成します。このフォームは、次の内容を表示します。
- XPath 関数の結果を使って自動的にノードにデータを追加する方法
- ラジオ・ボタンを使ったマスター詳細フォームを作成する方法
- 固有の項目のみをリストから表示する方法
- 複数の基準に基づいて結果をフィルターする方法
- XPath フィルターの基準をオプションにする方法
このフォームは、図 1 のようなものです。
図 1. 最終的なフォーム
まず、基本的なフォームを作成することから始めます。
まず、レポートのタイトルと本文を編集できる機能を持つフォームを作成します (リスト 1)。
リスト 1. 基本的なフォーム
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/D/tdxhtml1-strict.dtd">
<html
xmlns="http://www.w3.org/1999/xhtml"
xmlns:xforms="http://www.w3.org/2002/xforms"
xmlns:ev="http://www.w3.org/2001/xml-events"
>
<head>
<title>Report Entry Editor</title>
<link rel="stylesheet" href="style.css" type="text/css"/>
<xforms:model >
<xforms:instance id="editor" >
<entry xmlns="">
<id />
<status />
<title />
<body />
</entry>
</xforms:instance>
<xforms:bind nodeset="instance('editor')/title" id="titlebind"/>
<xforms:bind nodeset="instance('editor')/body" id="bodybind"/>
<!-- Dummy submission; we're just dealing with the client -->
<xforms:submission id="editsubmission" action="savechanges.php"
method="post"/>
</xforms:model>
</head>
<body>
<h2>Editor</h2>
<xforms:input bind="titlebind" >
<xforms:label><b>Title: </b></xforms:label>
</xforms:input><br/>
<xforms:textarea bind="bodybind">
<xforms:label><b>Body: </b></xforms:label>
</xforms:textarea><br />
<xforms:submit submission="editsubmission">
<xforms:label>Save changes</xforms:label>
</xforms:submit>
</body>
</html>
|
これは単なる基本的なフォームにすぎませんが、エディターに必要な内容を含んでいます。新しいレポートのタイトルと本文を追加することができ、そして送信することができますが、この記事では送信については触れません。この結果は図 2 のようなものになります。
図 2. 基本的なフォーム
このコードは要素に対するバインディングも含んでいるため、それらを直接参照することができます。この場合の XPath ステートメントは非常に単純で簡単なものですが、バインディングを使って「ショートカット」を作成できるため、面倒な XPathステートメントをコードの中で直接処理する必要がありません。つまりバインドは基本的に、それぞれの XPath 式に対する「ニックネーム」を作成します。
次のステップでは entry ID (入力 ID) フィールドを作成します。
entry ID 属性を使って入力を識別することができます。そのため、QName のような値が必要です。つまり空白や句読点などを持たないストリングが必要です。そのためには、サーバー・サイドで何かをランダムに生成するか、あるいはユーザーが入力するデータに基づく値を作成します (リスト 2)。
リスト 2. 自動的にコンテンツを追加する
...
<body />
</entry>
</xforms:instance>
<xforms:bind id="fixid" nodeset="instance('editor')/id"
calculate="translate(../title,'ABCDEFGHIJKLMNOPQRSTUVWXYZ ?!',
'abcdefghijklmnopqrstuvwxyz_') "/>
<xforms:bind nodeset="instance('editor')/id" id="sidbind"/>
<xforms:bind nodeset="instance('editor')/title" id="titlebind"/>
<xforms:bind nodeset="instance('editor')/body" id="bodybind"/>
...
<h2>Editor</h2>
<b>Entry ID:</b> <xforms:output bind="sidbind"/><br />
<xforms:input incremental="true" bind="titlebind" >
<xforms:label><b>Title: </b></xforms:label>
</xforms:input><br/>
...
|
この場合は、ある特定ノードを識別するだけではなく、そのノードの値の設定も行うバインディングを作成しました。この場合の値は、タイトル要素に入力されたテキストであり、大文字はすべて小文字に置き換えられ、空白はアンダーバーで置き換えられ、また疑問符と感嘆符は完全に削除されています。またタイトルの入力フィールドに incremental="true" という属性を追加したので、このフィールドはキー入力があるごとに更新されます (図 3)。
図 3. フィールドをインクリメンタルに更新する
この場合は XPath の translate() 関数を使いました。他にも、XPath 1.0 に定義されているすべての関数を使えるだけではなく、XForms 専用に作成されたnow() などの関数も使うことができます。XForms 勧告には次の関数が含まれています。
-
if() -
avg() -
min() -
max() -
count-non-empty() -
index() -
property() -
now() -
days-from-date() -
seconds-from-dateTime() -
seconds() -
months()
これらの関数のどれを使っても、インスタンス・ノードに自動的にデータを追加することができます。
しかしデータの作成は、XForms でできることの、ほんの一部でしかありません。XForms を使えば、既存のデータを操作することもできるのです。
XPath を使ってできる手軽なトリックが、「マスター詳細」フォームの作成です。考え方としては、ユーザーがクリックしたラジオ・ボタンの ID を取得し、エディターに該当するデータを追加する XPath ステートメントを、その ID を使って作成します。これを実現するためには、いくつかの既存のエントリーを新しいインスタンスに追加します (リスト 3)。
リスト 3. 既存のデータを処理する
...
<body />
</entry>
</xforms:instance>
<xforms:instance id="ui" xmlns="">
<ui>
<currentItem></currentItem>
<statusChoice> </statusChoice>
<authorId> </authorId>
<category> </category>
</ui>
</xforms:instance>
<xforms:instance id="entries" >
<entries>
<entry xmlns="" id="something_is_broken">
<status>1</status>
<category>General</category>
<author id="Nick">Nick Chase</author>
<title>Something is broken</title>
<body>I hear a lot of rattling around in the boxes when
I shake them up.</body>
</entry>
<entry xmlns="" id="glass_collection_broken">
<status>1</status>
<category>Specific</category>
<author id="Sarah">Sarah Chase</author>
<title>Glass collection broken</title>
<body>Who broke my glass collection? I had it packed in
a box but it looks like somebody just shook the
heck out of it.</body>
</entry>
<entry xmlns="" id="who_am_i">
<status>0</status>
<category>NonSensical</category>
<author id="Nick">Nick Chase</author>
<title>Who am I?</title>
<body>I'm sure there's some reason I'm here, but I just
... can't ... remember...</body>
</entry>
</entries>
</xforms:instance>
<xforms:bind id="fixid" nodeset="instance('editor')/id"
calculate="translate(../title,'ABCDEFGHIJKLMNOPQRSTUVWXYZ ?!',
'abcdefghijklmnopqrstuvwxyz_') "/>
...
<body>
<h2>Existing entries</h2>
<xforms:select1 incremental="true" appearance="full"
ref="instance('ui')/currentItem">
<xforms:itemset nodeset="instance('entries')/entry">
<xforms:label ref="title"/>
<xforms:value ref="@id"/>
</xforms:itemset>
</xforms:select1>
<h2>Editor</h2>
<b>Entry ID:</b> <xforms:output bind="sidbind"/><br />
<xforms:input incremental="true" bind="titlebind" >
<xforms:label><b>Title: </b></xforms:label>
</xforms:input><br/>
|
この場合は、実際には 2 つのインスタンスを追加しています。最初のインスタンス (ui) は、ユーザーが選んだものを保持します。最初はユーザーが編集しようとする項目のみを保持しますが、最終的にはこのインスタンスを使ってフィルター情報も保持します。
entries インスタンスは、いくつかの既存のエントリーを含んでいます。この場合には、これらのエントリーはエディターで見ると似たような構造をしていますが、必ずしもそうである必要はありません。後で値を手動で設定するからです。
このページは、既存の各エントリーのリストと、ユーザーに選択させるためのラジオ・ボタンも含んでいます (図 4)。
図 4. エントリーをリストする
ここで重要なことは、ユーザーがこれらのラジオ・ボタンの 1 つをクリックしたときに、そのデータを一方のインスタンスからもう一方のインスタンスに移動するための方法がフォームにわかるようにページを設定することです (リスト 4)。
リスト 4. データを移動する
...
<xforms:bind nodeset="instance('editor')/body" id="bodybind"/>
<xforms:bind nodeset="instance('editor')/title" calculate=
"instance('entries')/entry[@id=instance('ui')/currentItem]/title"/>
<xforms:bind nodeset="instance('editor')/body" calculate=
"instance('entries')/entry[@id=instance('ui')/currentItem]/body"/>
<xforms:bind nodeset="instance('editor')/@id"
calculate="instance('ui')/currentItem" />
<!-- Dummy submission; we're just dealing with the client -->
<xforms:submission id="editsubmission" action="savechanges.php"
method="post"/>
|
実際、XPath を使うと、エディター・フォームへのデータの入力は非常に単純です。それぞれがエディター・インスタンスのノードの 1 つを参照する、3 つの新しいバインディング・ステートメントを作成します。そして前と同じように calculate を使ってそれらにデータを入力しますが、この場合は ui インスタンスからの値 currentItem に基づいて entries インスタンスの中のエントリーを選択しています。ユーザーはその値を、ラジオ・ボタンをクリックする時に設定します (図 5)。
図 5. ラジオ・ボタンをクリックする
これでエディターはできあがりです。では、簡単に処理できる以上のエントリーがある場合にはどうするのでしょう。
次のステップでは、選択肢を狭めるためのフィルター・フィールドを作成します。まず、著者のプルダウン・メニューから始めます。
著者のドロップダウン・メニューを作成する方法としては 3 つの選択肢があります。手動でドロップダウン・メニューを作成する方法と、著者名のインスタンスを作成する方法、あるいは entries インスタンスの中の既存の著者名を使う方法です。ここでは 3 番目のオプションを選択することにしましょう (リスト 5)。
リスト 5. 著者でフィルターする
...
<h2>Existing entries</h2>
<xforms:select1 incremental="true" appearance="full"
ref="instance('ui')/currentItem">
<xforms:itemset nodeset="instance('entries')
/entry[author/@id=instance('ui')/authorId]">
<xforms:label ref="title"/>
<xforms:value ref="@id"/>
</xforms:itemset>
</xforms:select1>
<h4>Filter results by:</h4>
<xforms:select1 appearance="minimal" incremental="true"
ref="instance('ui')/authorId">
<xforms:label>Author: </xforms:label>
<xforms:itemset nodeset= "instance('entries')/entry">
<xforms:label ref="author" />
<xforms:value ref="author/@id" />
</xforms:itemset>
</xforms:select1>
<br />
<h2>Editor</h2>
...
|
この場合は、ドロップダウン・メニューを作成し、次に、itemsetノードセットに述部を追加することで、表示されているエントリーをドロップダウン・メニューの現在の値に基づいてフィルターしています。図 6 を見るとわかるように、この方法には 2 つの問題があります。第 1 に、このリストは著者の名前を重複して表示しています。第 2 に、フィルターがあるおかげで、もしユーザーが著者を選択しないとエントリーが何も表示されません。
図 6. 著者をリストする
では、最初の問題、つまり著者名の重複から修正を始めましょう。
重複の削除は、しばしば XPath の初心者ユーザーを困らせる問題です。こうした XPath ユーザーは多くの場合 SQL の世界の出身であり、一見すると where 節や having 節に比べて制限のある predicates に困惑しがちです。幸い、この問題の修正は難しくはありません (リスト 6)。
リスト 6. 重複を削除する
...
<xforms:select1 appearance="minimal" incremental="true"
ref="instance('ui')/authorId">
<xforms:label>Author: </xforms:label>
<xforms:itemset nodeset=
"instance('entries')/entry[not(author = preceding-sibling::entry/author)]">
<xforms:label ref="author" />
<xforms:value ref="author/@id" />
</xforms:itemset>
</xforms:select1>
...
|
要は、まだ表示されていない著者を持つエントリー要素のみを選択するということです。これは文字通り、実現したいことそのものです。図 7 を見るとわかるように、これで問題が解決されます。
図 7. 重複がなくなる
これを見るとわかるように、ある著者を選択すると、その著者のエントリーのみが表示されます。これでもまだ、選択するまで何も表示されないという問題への対応が必要ですが、その問題はしばらく保留にし、他のフィルターを追加しましょう。
さらにフィルターを追加するためには、単純に述部を追加します (リスト 7)。
リスト 7. 他のフィルターを追加する
...
<h2>Existing entries</h2>
<xforms:select1 incremental="true" appearance="full"
ref="instance('ui')/currentItem">
<xforms:itemset nodeset="instance('entries')/entry[author/@id=instance('ui')
/authorId][category=instance('ui')/category][status=instance('ui')/statusChoice]">
<xforms:label ref="title"/>
<xforms:value ref="@id"/>
</xforms:itemset>
</xforms:select1>
<h4>Filter results by:</h4>
...
<xforms:select1 appearance="minimal" ref="instance('ui')/category">
<xforms:label>Category: </xforms:label>
<xforms:choices>
<xforms:item>
<xforms:label>General</xforms:label>
<xforms:value>General</xforms:value>
</xforms:item>
<xforms:item>
<xforms:label>Specific</xforms:label>
<xforms:value>Specific</xforms:value>
</xforms:item>
<xforms:item>
<xforms:label>NonSensical</xforms:label>
<xforms:value>NonSensical</xforms:value>
</xforms:item>
</xforms:choices>
</xforms:select1>
<br />
<xforms:select1 appearance="minimal"
ref="instance('ui')/statusChoice">
<xforms:label>Status: </xforms:label>
<xforms:choices>
<xforms:item>
<xforms:label>Active</xforms:label>
<xforms:value>1</xforms:value>
</xforms:item>
<xforms:item>
<xforms:label>Inactive</xforms:label>
<xforms:value>0</xforms:value>
</xforms:item>
</xforms:choices>
</xforms:select1>
<h2>Editor</h2>
...
|
これで、(静的に作成された) さらに 2 つのプルダウン・メニューを追加しました。しかしここで注目すべき魔術は、itemset を定義する XPath ステートメントの中にあります。1 つの式の中に、いくつでも述部を追加できるのです。この場合の Xpath ステートメントは、Author フィルターを満足するすべてのエントリーを選択し、その中から Category フィルターを満足するものを選択し、さらにStatus フィルターを満足するもののみを選択しています (図 8)。
図 8. 実際のフィルター
この方法を使うと、好きなだけ多くの異なる場所から要件を引き出すことができます。
ここにはまだ、もう 1 つ問題があります。ユーザーがフィルターを有効にしない限り条件を容易に選択する方法がありません。そのためユーザーは、結果を得るためには 3 つのフィルターをすべて選択する必要があります。これを解決するためにはデフォルト値として「ワイルドカード」を使うことだと思われるかもしれませんが、XPath はこれも認識しません。そこで、ちょっとしたトリックを使う必要があります (リスト 8)。
リスト 8. デフォルト値を使う。
...
<xforms:instance id="ui" xmlns="">
<ui>
<currentItem></currentItem>
<statusChoice> </statusChoice>
<authorId> </authorId>
<category> </category>
</ui>
</xforms:instance>
...
<h2>Existing entries</h2>
<xforms:select1 incremental="true" appearance="full"
ref="instance('ui')/currentItem">
<xforms:itemset nodeset="instance('entries')/entry
[author/@id=instance('ui')/authorId or instance('ui')/authorId=' ']
[category=instance('ui')/category or instance('ui')/category=' ']
[status=instance('ui')/statusChoice or
instance('ui')/statusChoice=' ']">
<xforms:label ref="title"/>
<xforms:value ref="@id"/>
</xforms:itemset>
</xforms:select1>
<h4>Filter results by:</h4>
<xforms:select1 appearance="minimal" incremental="true"
ref="instance('ui')/authorId">
<xforms:label>Author: </xforms:label>
<xforms:choices>
<xforms:item>
<xforms:label>All</xforms:label>
<xforms:value> </xforms:value>
</xforms:item>
</xforms:choices>
<xforms:itemset nodeset=
"instance('entries')/entry[not(author = preceding-sibling::entry/author)]">
<xforms:label ref="author" />
<xforms:value ref="author/@id" />
</xforms:itemset>
</xforms:select1>
<br />
<xforms:select1 appearance="minimal" ref="instance('ui')/category">
<xforms:label>Category: </xforms:label>
<xforms:choices>
<xforms:item>
<xforms:label>All</xforms:label>
<xforms:value> </xforms:value>
</xforms:item>
<xforms:item>
<xforms:label>General</xforms:label>
<xforms:value>General</xforms:value>
</xforms:item>
...
</xforms:choices>
</xforms:select1>
<br />
<xforms:select1 appearance="minimal"
ref="instance('ui')/statusChoice">
<xforms:label>Status: </xforms:label>
<xforms:choices>
<xforms:item>
<xforms:label>All</xforms:label>
<xforms:value> </xforms:value>
</xforms:item>
<xforms:item>
<xforms:label>Active</xforms:label>
...
|
重要な注意: ここでは、このページに収めるためにステートメントを複数の行に分解してあります。これらのステートメントが正常に動作するためには、複数に分けた行を元に戻す必要があります。
さて、では実際には下から見て行きましょう。Status プルダウンには、値として空白を 1 つ持つ新しい選択肢 All があります。これで ui インスタンスは statusChoice 要素に対する値として、空白を持つことになります。そのため、これが最初に表示される値になります。同じコードが Category プルダウンにも追加されています。
Author プルダウンは、もう少し複雑です。Itemset は動的に作成された項目にしか使えませんが、choices は静的に作成された項目を定義します。幸いなことに、両方を一緒に使えるのです。
これらがすべて用意できれば、あとはエントリーを表示するための Xpath ステートメントを作るだけです。
この述部は次のように動作します。エンジンは各ノードに対して、そのコンテキストを使って述部を処理します。もし式が真であれば、そのノードは結果に追加されます。真でない場合には追加されません。つまりここでは、ある特定のフィルターに対する値をユーザーが選択したら適切なノードのみが「true」を返すような状況を作成したのです。一方、もしフィルターが「All」に設定されたままであれば、「or」式の 2 番目の部分は常に真なので、すべてのノードがテストにパスします。
最終的なページは図 9 のようになります。
図 9. 最終的なページ
そして、これで終わりです。
XForms は、XPath でできることに大きく依存しています。幸い、XPath でできることは大量にあります。この記事では、XPath の使い方として次のことを学びました。
- XPath 関数の結果を使って自動的にノードにデータを追加する方法
- ある場所のデータを、別の場所でのユーザーの選択に基づいて選択する方法
- リストから固有の項目のみを表示する方法
- 複数の基準に基づいて結果をフィルターする方法
- XPath 式に対してワイルドカード値を提供する方法
ここで説明したことは簡単に思えるかもしれませんが、簡単な目的にしか使えないと思うのは誤解です。これらの方法は、最も複雑な XForms ユーザー・インターフェースを作成する場合にも、変更せずにそのまま使えるのです。
| 内容 | ファイル名 | サイズ | ダウンロード形式 |
|---|---|---|---|
| Article source code | xpathinxforms_source.zip | 2KB | HTTP |
学ぶために
-
XForms の入門記事として「XForms 入門、第 1 回: フォームのための新しい Web 標準」 (Chris Herborth 著、developerWorks、2006年9月)、「XForms 入門、第 2 回: フォーム、モデル、コントロール、そして送信アクション」 (Chris Herborth 著、developerWorks、2006年9月)、「XForms 入門、第 3 回: アクションとイベントを使う」 (Chris Herborth 著、developerWorks、2006年9月) を読んでください。
-
IBM developerWorks の XML ゾーンで XForms について学んでください。
-
XForms が向かう方向を知ってください。
-
XPath が向かう方向を知ってください。
-
次世代のフォームを作成するための必須事項を「XForms の基本」 (Nicholas Chase 著、developerWorks、2006年10月) で学んでください。
-
これらの方法を実際のアプリケーションで使う究極的な例を developerWorks のシリーズ「Use XForms to create an accouting tool」で学んでください。
-
IBM XForms communityを訪れてください。
-
Mozilla 用の XForms 拡張機能をダウンロードしてください。
-
XForms データを受け付ける方法を学ぶために、「XForms のヒント: XForms データを Java で受け付ける」 (Nicholas Chase 著、developerWorks、2006年10月) と「XForms のヒント: XForms データを Perl で受け付ける」 (Tyler Anderson 著、developerWorks、2006年10月)、そして「XForms のヒント: PHP で XForms データを受け付ける」 (Nicholas Chase 著、developerWorks、2006年9月) を読んでください。
-
XML および関連技術において IBM 認証開発者になる方法については、 IBM XML certification を参照してください。
-
developerWorks の XML ゾーンは XML の技術ライブラリーとして、広範な話題を網羅した技術記事やヒント、チュートリアル、技術標準、IBM レッドブックなどを用意しています。
-
developerWorks technical events and webcastsで最新情報を入手してください。
-
developerWorks の XML ゾーンで XML について学んでください。
製品や技術を入手するために
議論するために
