レベル: 中級 Doug Tidwell (dtidwell@us.ibm.com), Cyber Evangelist for developerWorks, IBM
2007年 11月 06日
最近の記事で、XML トーナメントの結果 (対戦表) を表現した魅力的な HTML 表を作成できる XSLT 2.0 の関数について説明しました。その記事では、XML トーナメントの勝者と敗者を入力する方法については触れませんでした。この記事では、この XML トーナメントを再度取り上げ、不等号括弧をまったく使わずにトーナメントの結果を入力できる XForms 文書を作成します。その結果、Ajax のような効果を完備した、対戦表の文書を編集するための優れたエディターができあがります。何よりも良いこととして、XForms をこのように使えるということは、宣言型マークアップを使って、XML 文書そのもののデータ構造に基づいて、カスタム・エディターを作成できることを意味しています。
XForms を使ったトーナメント
前回の記事では、XSLT 2.0 を使って XML のトーナメント文書を HTML による対戦表に変換し、トーナメントの結果を表示する方法を説明しました。その記事では、そもそもどのようにトーナメントの結果を取得するかについては考慮しませんでした。この記事の目標は、XML 文書を編集するためのエディターが必要な場合には常に XForms を使うのがよいと、皆さんを納得させることです。ここで作成する XForms 文書は、第 1 回戦から決勝戦まで出場チーム間のすべての対戦カードを表現し、対戦が行われる中で、その対戦結果をすべて追跡してトーナメント全体の対戦表を表示し、XML 文書を作成します。
最終的な XForms 文書は下記のようなものです。
図 1. Colorado Software Summit のトーナメント第 1 回戦の対戦カードのひとつ
このアプリケーションは、XML 文書に書き込みを行う、使いやすい対話型の Web アプリケーションです。念のため、XML による対戦表 (bracket) のフォーマットを下記に記載しておきます。
リスト 1. トーナメントを表す XML 文書
<bracket>
<title>How I learned to stop worrying and love the data model</title>
<contestants>
<contestant seed="1" image="images/HomerSimpson.png">Donuts</contestant>
<contestant seed="2" image="images/CirqueDuSoleil.jpg">Cirque Du Soleil</contestant>
<contestant seed="3" image="images/ArtificialIntelligence.jpg">
AI (artificial intelligence)</contestant>
<contestant seed="4" image="images/RockyMountains.gif">Rocky Mountains</contestant>
<contestant seed="5" image="images/WMD.jpg">Weapons of Mass Destruction</contestant>
<contestant seed="6" image="images/HaroldPie.gif">Pie</contestant>
<contestant seed="7" image="images/GeorgeWBush.jpg">Misunderestimated</contestant>
<contestant seed="8" image="images/ColoradoAvalancheHockey.gif">
Colorado Avalanche (hockey team)</contestant>
<contestant seed="9" image="images/ColoradoAvalanche.jpg">
Colorado avalanche (natural disaster)</contestant>
<contestant seed="10" image="images/SnoopDogg.png">Biz-Implification</contestant>
<contestant seed="11" image="images/AjaxCan.png">AJAX</contestant>
<contestant seed="12" image="images/DarthVader.jpg">Darth Vader</contestant>
<contestant seed="13" image="images/Matterhorn.jpg">Swiss Alps</contestant>
<contestant seed="14" image="images/AllenIverson.jpg">
AI (natural disaster)</contestant>
<contestant seed="15" image="images/CircleK.png">Circle K</contestant>
<contestant seed="16" image="images/HealthyFood.jpg">Food</contestant>
</contestants>
<titles>
<round>Round</round>
<matchup>Matchup</matchup>
</titles>
<results>
<result round="1" firstSeed="1" secondSeed="16" winnerSeed="1"/>
<result round="1" firstSeed="8" secondSeed="9" winnerSeed="9"/>
<result round="1" firstSeed="5" secondSeed="12" winnerSeed="12"/>
<result round="1" firstSeed="4" secondSeed="13" winnerSeed="4"/>
<result round="1" firstSeed="6" secondSeed="11" winnerSeed="6"/>
<result round="1" firstSeed="3" secondSeed="14" winnerSeed="3"/>
<result round="1" firstSeed="7" secondSeed="10" winnerSeed="10"/>
<result round="1" firstSeed="2" secondSeed="15" winnerSeed="2"/>
<result round="2" firstSeed="1" secondSeed="9" winnerSeed="1"/>
<result round="2" firstSeed="12" secondSeed="4" winnerSeed="4"/>
<result round="2" firstSeed="6" secondSeed="3" winnerSeed="3"/>
<result round="2" firstSeed="10" secondSeed="2" winnerSeed="2"/>
<result round="3" firstSeed="1" secondSeed="4" winnerSeed="4"/>
<result round="3" firstSeed="3" secondSeed="2" winnerSeed="3"/>
<result round="4" firstSeed="4" secondSeed="3" winnerSeed="4"/>
</results>
</bracket>
|
(注意: この文書は、10 月に開催された Colorado Software Summit カンファレンスのトーナメントの実際の結果を表しています。このセッションに参加した皆さん全員に感謝します。)
このトーナメントの結果を記録するには、この文書の最下部にある <result> 要素の属性にデータを入力します。このデータは下記のように、いくつかの方法で収集することができます。
- テキスト・エディターで文書を編集する。
- Swing その他のグラフィカル・フレームワークを使ってカスタム・アプリケーションを作成する。
- JavaScript と適切な Ajax ライブラリーを使って HTML ページを作成する。
- XForms 文書を作成する。
ご想像のとおり、この記事では最後の方法について説明します。他の方法を使わない理由は下記のとおりです。
- テキスト・エディターは、XML 文書のフォーマットを完全に理解していない限り使えません。例えば、最初の
<result> 要素の winnerSeed 属性の値は、9 番目の <result> 要素 (第 2 回戦の最初の結果) の firstSeed 属性の値になります。たとえ文書フォーマットを知っていたとしても、テキスト・エディターを使うと非常に間違いをおかしやすくなります。
- カスタム・アプリケーションを作成する方法は、4 つの方法の中で最も時間がかかります。たとえ高度なグラフィカル開発ツールを使ったとしても、コントロールと XML 文書中の対応するデータとの接続ロジックを、すべて定義しなければなりません。
- HTML ページを作成する方法は、入力コントロール用に大量の JavaScript コードを作成する必要があります。Dojo のようなライブラリーを使ってクライアント・サイドでのグラフィック効果の作成や検証を行ったりすることはできますが、それでもやはり大量のコードを作成し、管理しなければなりません。最も都合の悪いことに、HTML の入力コントロールは XML のデータ・モデルとまったくリンクされていません。もし 2 つのデータの間に関係がある場合には、その関係を定義するのは開発者の仕事です。最後に、XML 文書を送信する際には、入力フィールドからデータを収集し、それを使って XML を作成しなければなりません。
これらとは対照的に、XForms を利用すると、XML 文書に基づく宣言型のモデルを作成することができます。各入力コントロールは XML 文書の特定の部分に直接リンクされているため、データ収集の問題がなくなります。もっと良いことに、インターフェースも宣言型で定義されます。画面の別の部分をクライアント・サイドの Ajax 効果で置き換える必要がある場合にも、コードを書かずにそれを行うことができます。
ここでの設計目標は下記のとおりです。
- トーナメント全体を 1 つの XForms 文書で表示でき、その文書の中で操作が行えること。
- 指定された対戦カードの結果が入力されたら、その情報がフォーム全体に反映されること。
- このトーナメントの第 1 回戦から決勝戦までの全 4 回戦に加えて、トーナメント全体を示す対戦表もフォームに含まれること。
- ユーザーがトーナメントのあちこちを見たとしても、このアプリケーションは結果を追跡するためにサーバーまたはファイル・システムとのやり取りを必要としないこと。
この記事では、下記のいくつかの作業について説明します。
- XHTML ページのレイアウトの定義
- XForms 文書へのデータ・モデル (XML 対戦表) のインポート
- 各対戦カードを表示するパネルの定義
- トーナメント全体の対戦表を表示するパネルの定義
- ナビゲーション・ボタンの定義
- トーナメントのデータを保存し、リセットするための XForms アクションの定義
XHTML ページのレイアウトを定義する
ページのレイアウトは単純です。ページ全体は、3 行からなる表で、最初の行はこのページのヘッダーを含み、最後の行はフッターを含んでいます。真ん中の行は XForms マークアップの大部分を含む、表の 1 セルになっており、大部分の作業はこのセルに対して行います。フッターには 5 つのナビゲーション・ボタンがあり、これらのボタンを使うことで、このトーナメントの第 1 回戦から決勝戦 (第 4 回戦) までの全 4 回戦 (Round 1 ~ Round 4) の間と、対戦表 (Bracket) の間を移動することができます。フッターには Save ボタンと Reset ボタンもあります。
このページのレイアウトは下記のとおりです。
図 2. XHTML ページのレイアウト
セルを定義する XHTML は単純です。
リスト 2. XHTML ページのレイアウト
<html xmlns="http://www.w3.org/1999/xhtml">
. . .
<head>
<title>Bracketology</title>
. . .
</head>
<body>
<xf:group nodeset="instance('default')">
<table cellpadding="5" cellspacing="0" width="95%">
<!-- Header row -->
<tr style="height: 100px;">
<td>
<!-- Conference logo goes here -->
</td>
<td>
<!-- Tournament title goes here -->
</td>
<td>
<img src="images/dwHoF.png" alt="developerWorks logo"/>
</td>
</tr>
<!-- The tournament area: The four rounds and the bracket -->
<tr>
<td colspan="3">
<xf:switch>
. . .
<!-- XForms <xf:case> elements here, -->
<!-- one for each of the 16 panels -->
. . .
</xf:switch>
</td>
</tr>
<!-- Footer row -->
<tr style="height: 100px;">
<td>
Bracketology
</td>
<td>
<!-- Round 1, 2, 3 and 4 buttons go here -->
</td>
<td>
<!-- Bracket, Save and Reset buttons go here -->
</td>
</tr>
</table>
</xf:group>
</body>
</html>
|
真ん中の行には、このトーナメント・データの XForms コントロールを配置します。ユーザーは、これらのコントロールによってトーナメントを更新することができます (これらのコントロールによって、2 つの対戦チームから勝者を選択することができ、次の対戦カードに進むことができます)。フッターでは、ボタン (XForms の <xf:trigger> 要素) と、それぞれのボタンのアクションを定義します。この結果できあがるアプリケーションは、XForms を使って宣言型で定義され、ユーザーにとって使いやすい、特定の XML 文書を編集するためのエディターとなります。
XForms 文書にデータ・モデルをインポートする
XHTML ページが定義できると、データ・モデルのインポートに進むことができます。この例では、下記の 3 つを含む XForms データ・モデルを作成します。
- ファイルから XML 文書をロードする
<xf:instance>
- データをファイルに書き出す
<xf:submission>
- 各チームのグラフィックを定義するための、いくつかの
<xf:bind> 要素
まず、tourney.xml という名前の XML 文書をインポートします。下記は、それを行う XForms の魔法です。
リスト 3. データ・モデルのインスタンスを作成する
<xf:instance id="default" src="tourney.xml"/>
|
この例の tourney.xml は先ほどこの記事の最初の方で見たファイルです。このファイルの内容が文書にロードされると、そのデータを表示したり変更したりすることができます。例えば、トーナメントのタイトルを取得するためには XPath 式 /bracket/title を使います。
ここで使用するサブミット (submission) は、単純にモデルのデータがファイルに書き込まれます。<xf:instance> を使ってデータをロードし、そのデータを好きなように変更し、そしてそれをファイル tourney.xml に書き戻します。下記は XForms によるサブミットを示しています。
リスト 4. XForms のサブミットを定義する
<xf:submission id="save" action="tourney.xml" method="put"
indent="true" includenamespaceprefixes="#default"
omit-xml-declaration="true"/> |
<xf:submission> 要素は XML データを同じファイルに再度書き込みます。ここでの属性が要求していることは、この XML ファイルをインデントすること、そして XML 宣言を省略することです。(XML 宣言は XML ファイルの先頭に現れる <?xml version="1.0... です。) ここで編集する XML 文書は名前空間を使わないため、属性 includenamespaceprefixes="#default" は XML ファイルに名前空間接頭辞を書き込みません。
このサブミットは save という ID を持っています。そのため、名前 save を使ってこのサブミットを呼び出すことができます。もっと複雑な XForms 文書では、ある 1 つの URL に XML データをサブミットするために複数のサブミットを持ち、別々のファイルに書き込む、といったことをする場合もあります。しかしこの例では単純なものにしておきます。
データ・モデルに必要なものの最後は、各チームのグラフィックを定義する、一連の <xf:bind> 要素です。
リスト 5. 画像データを xsd:anyURI にバインドする
<xf:bind type="xsd:anyURI"
nodeset="instance('default')/contestants/contestant[@seed='1']/@image"/>
<xf:bind type="xsd:anyURI"
nodeset="instance('default')/contestants/contestant[@seed='2']/@image"/>
. . .
|
<xf:bind> 要素は、image 属性の値を XML Schema のデータ型 anyURI に関連付けます。これらの値を <xf:output mediatype="image/*"> を使って表示すると、XForms エンジンはそのデータを画像として表示します。シード番号 6 の対戦チームに関連付けられた画像を表示したい場合には、それに対応する対戦チーム (contestant[@seed='6']) の image 属性を使います。バインディングと mediatype がないと、対戦表にはこれらの値が文字列として表示されます。
図 3. <xf:bind> と mediatype="image/*" がない場合に、画像の代わりに表示されるストリング
完全なデータ・モデルは、下記のようなものになります。
リスト 6. XForms のデータ・モデル
<xf:model id="theModel">
<xf:instance id="default" src="tourney.xml"/>
<xf:submission id="save" action="tourney.xml" method="put"
indent="true" includenamespaceprefixes="#default"
omit-xml-declaration="true"/>
<xf:bind type="xsd:anyURI"
nodeset="instance('default')/contestants/contestant[@seed='1']/@image"/>
<xf:bind type="xsd:anyURI"
nodeset="instance('default')/contestants/contestant[@seed='2']/@image"/>
. . .
</xf:model>
|
データ・モデルが用意できたので、今度はユーザー・インターフェース用の XForms コントロールを定義します。
各対戦カードを表示するパネルを定義する
対戦表とすべてのマークアップを表示するパネルは、XForms の <xf:switch> 要素と <xf:case> 要素を使って定義します。<xf:switch> はパネル・セットを定義し、一方それぞれの <xf:case> は 1 枚のパネルを定義します。どのパネルを表示するかは、XForms イベントを使って決定します。フッターにあるナビゲーション・ボタンは、第 1 回戦から決勝戦 (第 4 回戦) までの各回戦と、トーナメント全体を示す対戦表との間を移動するのに使われます。また各パネルのナビゲーション・ボタンは、各回戦内での対戦カード間を移動するために使われます。
この XForms 文書全体のレイアウトは下記のようなものになります。
図 4. XForms パネル (<xf:case>) のレイアウト
各パネルは実質的に別のパネルの上に積み重なっており、1 度に 1 つのパネルのみが表示されるようになっています。さまざまなパネルの XForms マークアップは下記のようになります。
リスト 7. トーナメント全体を示す対戦表パネルのための XForms マークアップ
<xf:switch>
<!-- Round 1, matchup 1 -->
<xf:case id="r1m1">
...
</xf:case>
<!-- Round 1, matchup 2 -->
<xf:case id="r1m2">
...
</xf:case>
...
<!-- Round 2, matchup 1 -->
<xf:case id="r2m1">
...
</xf:case>
...
<!-- Round 3, matchup 1 -->
<xf:case id="r3m1">
...
</xf:case>
...
<!-- Round 4 -->
<xf:case id="r4m1">
...
</xf:case>
...
<!-- The bracket -->
<xf:case id="bracket">
...
</xf:case>
</xf:switch>
|
トーナメントを表示するパネルは 16 枚あります。対戦は 15 回行われ、第 1 回戦では 8 回、第 2 回戦では 4 回、第 3 回戦では 2 回の対戦があり、そして第 4 回戦に決勝戦があります。(16 番目のパネルはトーナメント全体の対戦表を表示します。これについては次のセクションで説明します。)
各対戦カードのパネルにはタイトルが含まれ、タイトルの後には 3 つのセルから成る 1 行の表が続きます。タイトルは何回戦かを示す数字と対戦カードの番号を示します (例えば「Round 1, Matchup 4 (第 1 回戦の第 4 戦)」など。) この表では、左のセルと右のセルは各対戦チームのグラフィックを含み、中央のセルは対戦チームを選択するための XForms コントロールを含みます。また中央のセルは、現在の回戦の中で、前の対戦カードまたは次の対戦カードに移動するためのナビゲーション・ボタンも含みます。
ストリング「Round」と「Matchup」は、別の言語への翻訳のために、XML ファイルの中に <titles> 要素の子として保存されます。各パネルのタイトルは、これらのストリングを使って表の見出しを作成します。
リスト 8. XML ファイルのストリング・データを使って対戦カードの見出しを作成する
<xf:label>
<xf:output class="matchupHeading"
value="concat(titles/round, ' 1, ',
titles/matchup, ' 1')"/>
</xf:label>
|
このマークアップは <xf:output> を使ってタイトル「Round 1, Matchup 1」を生成します。これは Xforms 文書を翻訳するには、あまり向かない方法です。すべてのストリング (「Bracket」や「Save」、「Reset」、その他のテキスト) を XML 文書の中に移動したり、パネルの見出しの中での数字やストリングの順序を変えられるようにしたり (別の言語ではこれを「1 Round, 1 Matchup」と表示するかもしれません) する課題は、読者の演習問題とします。
グラフィックを持つセルは単純です。下記はシード番号 4 の対戦チームのグラフィックを mediatype="image/*" 属性を使って表示する、表のセルです。
リスト 9. <xf:output mediatype="image/*"> を使ってグラフィックを表示する
<td align="center" valign="center" width="35%" height="325px">
<xf:output mediatype="image/*"
ref="/bracket/contestants/contestant[@seed='4']/@image"/>
</td>
|
残りは表の中央のセルです。中央のセルは 2 つの対戦チームのシード番号と名前、そして同じ回戦の中での次の対戦カードあるいは前の対戦カードに移動するためのナビゲーション・ボタンを含みます。2 つの対戦チームは XForms の <xf:select1> 要素を使って表示されます。下記は第 1 回戦でのシード番号 4 のチームとシード番号 13 のチームとの対戦の勝者を選択するマークアップです。
リスト 10. <xf:select1> を使って 2 つの選択肢を表示する
<xf:select1 appearance="full"
ref="/bracket/results/result[4]/@winnerSeed">
<xf:item>
<xf:label>
<xf:output
value="concat('[4] ',
/bracket/contestants/contestant[@seed='4'])"/>
</xf:label>
<xf:value>4</xf:value>
</xf:item>
<xf:item>
<xf:label>
<xf:output
value="concat('[13] ',
/bracket/contestants/contestant[@seed='13'])"/>
</xf:label>
<xf:value>13</xf:value>
</xf:item>
</xf:select1>
|
ここには、説明する必要がある詳細の内容がかなりたくさんあります。まず、appearance="full" によって選択肢がラジオ・ボタンとして表示されます。(Firefox ではこのように動作します。XForms エンジンは、そのエンジンが選択する任意のコントロールを使用することができます。) <xf:select1> 要素の重要な属性が ref です。ref は XPath 式を使って XForms コントロールをデータ・モデルのいずれかにマッピングします。この場合では、このコントロールによって選択された値が 4 番目の <result> 要素の winnerSeed 属性の値になります。
<xf:select1> 要素を定義したら、この 2 つの選択肢を定義する必要があります。これらは <xf:item> 要素によって定義されます。それぞれの選択肢に対して、ラベルと値を生成します。ラベルは対象となる <contestant> 要素のテキストであり、値はそのチームのシード番号です。ご想像のとおり、ここでは <xf:output> を使って各チームのテキストを表示します。
第 1 回戦の対戦は、常に同じです。最初の対戦は必ずシード番号 1 とシード番号 16 のチームとの間で行われ、2 番目の対戦は必ずシード番号 8 とシード番号 9 のチームとの間で行われ、等々です。面倒なのは、第 1 回戦以外です。第 2 回戦の最初の対戦カードは、シード番号 1 またはシード番号 16 のチームと、シード番号 8 またはシード番号 9 のチームとの間で行われます。トーナメントをさらに進むと、各回戦での対戦チームを見つける作業は、より複雑になります。例えば、決勝戦の第 1 チームのシード番号は、1、16、8、9、5、12、4、または 13 です。ここでは複雑な XPath 述部 ([@seed='1' or @seed='16' or @seed='8' or...]) は作成せず、<result> 要素の順番を使います。最初の対戦カードの勝者 (result[1]/@winnerSeed) は、第 2 回戦の最初の対戦カードの第 1 チームになります (result[9]/@firstSeed)。このように結果を配置することで、コードがずっと単純になります。
各対戦カードと <result> 要素、そしてそれらのシード番号の間の関係を下記の表に示します。
表 1. 各対戦と <result> 要素、そしてそれらのシード番号の間の関係
| パネル (パネル名) | 回戦と対戦カードの番号 (Round - Matchup) or 回線 ― の試合番号 | 勝者が保存される場所 | 第 1 チームのシード番号の値/保存されている場所 | 第 2 チームのシード番号の値/保存されている場所 |
|---|
1 (r1m1) | 1 - 1 |
result[1]/@winnerSeed
| 1 | 16 | 2 (r1m2) | 1 - 2 |
result[2]/@winnerSeed
| 8 | 9 | 3 (r1m3) | 1 - 3 |
result[3]/@winnerSeed
| 5 | 12 | 4 (r1m4) | 1 - 4 |
result[4]/@winnerSeed
| 4 | 13 | 5 (r1m5) | 1 - 5 |
result[5]/@winnerSeed
| 6 | 11 | 6 (r1m6) | 1 - 6 |
result[6]/@winnerSeed
| 3 | 14 | 7 (r1m7) | 1 - 7 |
result[7]/@winnerSeed
| 7 | 10 | 8 (r1m8) | 1 - 8 |
result[8]/@winnerSeed
| 2 | 15 | 9 (r2m1) | 2 - 1 |
result[9]/@winnerSeed
|
result[1]/@winnerSeed
|
result[2]/@winnerSeed
| 10 (r2m2) | 2 - 2 |
result[10]/@winnerSeed
|
result[3]/@winnerSeed
|
result[4]/@winnerSeed
| 11 (r2m3) | 2 - 3 |
result[11]/@winnerSeed
|
result[5]/@winnerSeed
|
result[6]/@winnerSeed
| 12 (r2m4) | 2 - 4 |
result[12]/@winnerSeed
|
result[7]/@winnerSeed
|
result[8]/@winnerSeed
| 13 (r3m1) | 3 - 1 |
result[13]/@winnerSeed
|
result[9]/@winnerSeed
|
result[10]/@winnerSeed
| 14 (r3m2) | 3 - 2 |
result[14]/@winnerSeed
|
result[11]/@winnerSeed
|
result[12]/@winnerSeed
| 15 (r4m1) | 4 - 1 |
result[15]/@winnerSeed
|
result[13]/@winnerSeed
|
result[14]/@winnerSeed
|
第 1 回戦の 8 回の対戦のチームが変わることはありませんが、第 1 回戦を除くすべての対戦カードは、その前の対戦結果によって変わります。第 2 回戦の最初の対戦カードの勝者を選択するためのマークアップは、対戦チームのシード番号が事前にわからないため、より複雑です。これを下記に示します。
リスト 11. 第 2 回戦の対戦カードのチームを表示する
<xf:select1 appearance="full"
ref="/bracket/results/result[9]/@winnerSeed">
<xf:item>
<xf:label>
<xf:output
value="concat('[',
/bracket/results/result[9]/@firstSeed,
'] ',
/bracket/contestants/contestant
[@seed=/bracket/results/result[9]/@firstSeed])"/>
</xf:label>
<xf:value ref="/bracket/results/result[9]/@firstSeed"/>
</xf:item>
<xf:item>
<xf:label>
<xf:output
value="concat('[',
/bracket/results/result[9]/@secondSeed,
'] ',
/bracket/contestants/contestant
[@seed=/bracket/results/result[9]/@secondSeed])"/>
</xf:label>
<xf:value ref="/bracket/results/result[9]/@secondSeed"/>
</xf:item>
</xf:select1>
|
これは第 2 回戦の最初の対戦カードなので、これを 9 番目の <result> 要素の winnerSeed 属性に保存します。<xf:select1 ref="..."> 属性の中の XPath 式は、この値にコントロールを関連付けます。第 1 回戦でどの対戦チームが勝ったかはわからないため、もっと複雑な XPath 式を使って <xf:item> を定義する必要があります。この 2 つの対戦チームは、対象となる <result> 要素の firstSeed 属性と secondSeed 属性で識別されます。第 2 回戦の最初の対戦では、これは 9 番目の要素 (result[9]) です。
最初の <xf:item> のテキストは、seed 属性が firstSeed 属性と一致する対戦チームのテキストです。2 番目は、seed 属性が secondSeed 属性と一致する対戦チームのテキストです。この 2 つの項目に関連付けられた値は、firstSeed 属性と secondSeed 属性の値そのものです。
対戦チームの名前を XForms コントロールに表示できると、対戦パネルに残っているのはナビゲーション・ボタンのみです。ナビゲーション・ボタンは <xf:trigger> 要素と <xf:toggle> 要素を使って定義します。第 1 回戦の第 4 戦用のパネル (パネル r1m4) を定義する場合であれば、Previous ボタンは第 1 回戦の第 3 戦のパネル (パネル r1m3) に行く必要があり、Next ボタンは第 1 回戦の第 5 戦 (パネル r1m5) を表示するう必要があります。下記はそのマークアップです。
リスト 12. 同じ回戦での 3 つの対戦の間を移動するためのナビゲーション・ボタン
<tr style="vertical-align: bottom;">
<td style="text-align: right;" width="50%">
<xf:trigger>
<xf:label class="npButton">< Previous</xf:label>
<xf:toggle ev:event="DOMActivate" case="r1m3"></xf:toggle>
</xf:trigger>
</td>
<td style="text-align: left;" width="50%">
<xf:trigger>
<xf:label class="npButton">Next ></xf:label>
<xf:toggle ev:event="DOMActivate" case="r1m5"></xf:toggle>
</xf:trigger>
</td>
</tr>
|
<xf:trigger> 要素はボタンを作成し、<xf:label> 要素はそのボタンのテキストです。<xf:toggle> 要素によって、表示は別のパネル (別の <xf:case>) に切り替わります。応答しているイベントは DOMActivate です。DOMActivate は単純に、ユーザーがボタンをクリックしたらこのアクションを行う、という意味です。
このボタンは下記のようなものです。
図 5. 対戦パネル上のナビゲーション・ボタン
注意: 各回戦の最初のパネルには Previous ボタンはなく、また最後のパネルには Next ボタンはありません。Round 4 (第 4 回戦) は決勝戦のみなので、ナビゲーション・ボタンはありません。
トーナメント全体の対戦表を表示するパネルを定義する
トーナメント全体を示す対戦表パネルは、前回の記事で説明したHTML レイアウトを含んでいます。
図 6. トーナメント全体を示す対戦表
XForms による対戦表が前回の記事での HTML による対戦表と異なるところは、XForms の場合は対戦結果が表のセル内に <xf:output> 要素を使って表示されることです。例えば、Rocky Mountains と Swiss Alps との対戦は下記のように表示されます。
リスト 13. <xf:output> を使ってトーナメントの結果を表示する
<td>
<xf:output
value="concat('[',
/bracket/results/result[4]/@winnerSeed,
'] ',
/bracket/contestants/contestant
[@seed=/bracket/results/result[4]
/@winnerSeed])"/>
</td>
|
XPath の concat() 関数に渡される値は、左大括弧、勝者のシード番号、右大括弧、そして勝者の名前です。勝者である <contestant> の seed 属性が現在の <result> の winnerSeed 属性に等しいことに注目してください。この複雑な XPath 式が必要な理由は、この表のセルの内容が、この対戦の勝者が決まるまでわからないためです。下記は、この対戦表パネルの表の 7 つのセルを示しています。
図 7. <xf:output> を使って対戦表の中に表示された、トーナメントの結果
左端の列の 4 つの対戦チームのシード番号は変わらないため、これらを表示するのは簡単です。他の列に関しては、<xf:output> と XPath 式を使って対戦チームの名前とシード番号を表示する必要があります。
ナビゲーション・ボタンを定義する
次の作業はナビゲーション・ボタンを定義することです。このボタンによって、各回戦と、トーナメント全体を示す対戦表パネルとの間を移動します。同じ回戦内である対戦から別の対戦に移動するのは難しくありませんが、ある回戦から別の回戦に移動するためには少し工夫が必要です。問題は、ある対戦での勝者を次の回戦での対戦のシードにコピーする必要があることです。例えば、第 1 回戦の第 1 戦の勝者は第 2 回戦の第 1 戦の firstSeed 属性になります。
第 2 回戦の場合には、第 1 戦の勝者を 9 番目の <result> 要素の firstSeed 属性にコピーする必要があります。すべてを同期させるためには、もしユーザーが、トーナメント全体の対戦表を表示するナビゲーション・ボタンや、第 2 回戦、第 3 回戦、あるいは第 4 回戦 (決勝戦) に進むナビゲーション・ボタンのどれかをクリックした時は、すべての winnerSeed 属性、firstSeed 属性、そして secondSeed 属性をコピーする必要があります。
ある場所から別の場所に値をコピーするためには <xf:setvalue> 要素を使います。下記は winnerSeed 属性を別の <result> 要素の firstSeed 属性にコピーするマークアップです。
リスト 14. 第 1 回戦の勝者を第 2 回戦の対戦にコピーする
<xf:setvalue ev:event="DOMActivate"
ref="/bracket/results/result[9]/@firstSeed"
value="/bracket/results/result[1]/@winnerSeed"/>
|
ユーザーがどの回戦から移動してきたかは追跡しないので、ユーザーが Round 1-4 ボタンの 1 つ、あるいは Bracket ボタンをクリックしたら、<xf:setvalue> を使ってすべての値をコピーします。下記は「Round 2 (第 2 回戦)」ボタンのマークアップを示しています。
リスト 15. Round 2 に切り替える際に、関連するすべての値をコピーする
<xf:trigger>
<xf:label class="buttonText">Round 2</xf:label>
<!-- First round winners, eight of them in all -->
<xf:setvalue ev:event="DOMActivate"
ref="/bracket/results/result[9]/@firstSeed"
value="/bracket/results/result[1]/@winnerSeed"/>
<xf:setvalue ev:event="DOMActivate"
ref="/bracket/results/result[9]/@secondSeed"
value="/bracket/results/result[2]/@winnerSeed"/>
<xf:setvalue ev:event="DOMActivate"
ref="/bracket/results/result[10]/@firstSeed"
value="/bracket/results/result[3]/@winnerSeed"/>
<xf:setvalue ev:event="DOMActivate"
ref="/bracket/results/result[10]/@secondSeed"
value="/bracket/results/result[4]/@winnerSeed"/>
. . .
<!-- Second round winners, the final four -->
<xf:setvalue ev:event="DOMActivate"
ref="/bracket/results/result[13]/@firstSeed"
value="/bracket/results/result[9]/@winnerSeed"/>
<xf:setvalue ev:event="DOMActivate"
ref="/bracket/results/result[13]/@secondSeed"
value="/bracket/results/result[10]/@winnerSeed"/>
. . .
<xf:toggle ev:event="DOMActivate" case="r2m1"/>
</xf:trigger>
|
<xf:trigger> 要素はボタンを作成します。この要素はいくつかの要素を含んでおり、それらの要素はすべて DOMActivate イベントによってトリガーされます。つまりボタンがクリックされると、<xf:trigger> の中のすべての要素が処理されます。<xf:setvalue> 要素は値をコピーし、そして <xf:toggle> 要素はインターフェースを第 2 回戦の最初のパネルに切り替えます。
トーナメントのデータを保存および、リセットする XForms アクションを定義する
最後の作業は、トーナメントのデータを保存および、リセットするための XForms アクションを定義することです。データを保存するためには、データ・モデルの一部として定義した <xf:submission> 要素を使います。データをリセットするためには、<xf:setvalue> を使ってすべての winnerSeed 属性を空のストリング (winnerSeed="") に設定し、そして第 2 回戦、第 3 回戦、第 4 回戦の firstSeed 属性と secondSeed 属性も空のストリングに設定します。
ユーザーはいつでも Save ボタンをクリックすることができるため、ここではナビゲーション・ボタン用に使用したすべての <xf:setvalue> 要素を再利用します。ここでの主な違いは、<xf:submit> 要素を使って Save ボタンが作成されている点です。下記はそのマークアップです。
リスト 16. Save ボタンのためのマークアップ
<xf:submit submission="save">
<xf:label class="buttonText">Save</xf:label>
<xf:action ev:event="DOMActivate">
<!-- First round winners, eight of them in all -->
<xf:setvalue ref="/bracket/results/result[9]/@firstSeed"
value="/bracket/results/result[1]/@winnerSeed"/>
<xf:setvalue ref="/bracket/results/result[9]/@secondSeed"
value="/bracket/results/result[2]/@winnerSeed"/>
. . .
<xf:message level="modal">
Tournament results saved in tourney.xml.
</xf:message>
</xf:action>
</xf:submit>
|
このマークアップが、先ほどデータ・モデルで定義した <xf:submission> 要素を参照していることに注目してください。この Save ボタンとナビゲーション・ボタンとの、もう 1 つの違いは、すべての <xf:setvalue> 要素が <xf:action> 要素の内側にあることです。<xf:action> イベントは DOMActivate イベントに応答し、ユーザーがボタンをクリックすると、<xf:action> イベント内に記述されているすべてのものが実行されます。最後に、このコードは <xf:message> を表示し、データが保存されたことをユーザーに伝えます。
図 8. データが保存されたことを示すメッセージ
Reset ボタンは単純です。対象となるすべての値を空のストリングにリセットし、データがリセットされたことを示すメッセージを表示するだけです。Reset ボタンは、データ・モデル内の値が変更されたときに XForms 文書がどのように表示を更新するかを示すためだけに存在しています。これが実際のアプリケーションで使用される可能性はありません。このマークアップは下記のようになります。
リスト 17. Reset ボタンのためのマークアップ
<xf:trigger>
<xf:label class="buttonText">Reset</xf:label>
<!-- Round 1, reset the winnerSeed attribute only -->
<xf:setvalue ev:event="DOMActivate"
ref="/bracket/results/result[1]/@winnerSeed" value=""/>
<xf:setvalue ev:event="DOMActivate"
ref="/bracket/results/result[2]/@winnerSeed" value=""/>
. . .
<!-- Round 2, reset all attributes -->
<xf:setvalue ev:event="DOMActivate"
ref="/bracket/results/result[9]/@firstSeed" value=""/>
<xf:setvalue ev:event="DOMActivate"
ref="/bracket/results/result[9]/@secondSeed" value=""/>
<xf:setvalue ev:event="DOMActivate"
ref="/bracket/results/result[9]/@winnerSeed" value=""/>
. . .
<xf:message level="modal">
Tournament results are now reset.
</xf:message>
</xf:trigger>
|
第 1 回戦の <result> 要素の場合には、winnerSeed 属性のみをリセットします。第 2 回戦、第 3 回戦、第 4 回戦 (<result> 要素 9 から 15 まで) の場合には、firstSeed 属性と secondSeed 属性もリセットします。データ・モデルの中の値をリセットすると、表示は自動的に更新されます。
図 9. データ・モデル内の値をリセットすると表示は自動的に更新される
結果がまだ確定していないセルには、そのセルの内容は左と右の大括弧になります ([])。
XSLT を使ってコードをリファクタリングする
これで XForms 文書を作成できましたが、ここには繰り返しのコードが大量にあります。最後のステップとして、対戦表を生成する XSLT スタイルシートを作成し、管理しなければならないコードをもっと単純にします。例えば、XForms 文書で 15 の対戦パネルを管理する代わりに XSLT スタイルシートで 1 つの対戦パネルを管理すればよくなります。またこうすることによって、XForms 文書全体にわたる変更も行いやすくなります。例えば対戦パネルの見栄えを変更したい場合には、スタイルシートの中でコードの 1 ヵ所を変更すれば、すべてのパネルを再生成することができます。
XForms ソースから XSLT に移行すると、XForms のマークアップのうち繰り返しではない部分は変更されずにスタイルシートに移行されます。ここでは、手動で管理したくない面倒なマークアップを XSLT を使ってリファクタリングします。まず 16 個の <xf:bind> 要素から始めます。
リスト 18. XForms のバインディングを作成する XSLT コード
<xsl:for-each select="1 to 16">
<xf:bind type="xsd:anyURI"
nodeset="instance('default')/contestants/contestant[@seed='{.}']/@image"/>
</xsl:for-each>
|
各 <xf:bind> 要素で変更されるのは、対戦チームのシード番号のみです。ここでは XSLT 2.0 の to 演算子を使って 1 から 16 までの範囲の整数を作成します。nodeset 属性内の {中括弧} は属性値のテンプレートです。この中括弧の中のコードは評価され、出力文書に書き込まれます。中括弧に囲まれているドットは、この範囲の中の現在の項目を表します。この結果として、[@seed='1']、[@seed='2']、などを含む 16 個の <xf:bind> 要素が作成されます。
次の重要な作業は、15 枚の対戦カードのパネルを作成することです。ここでは XSLT を使って、対戦パネルの作成を行う名前付きテンプレートを作成します。このテンプレートを呼び出すには、下記のようにします。
リスト 19. 対戦パネルを作成する、名前付きテンプレートを呼び出す
<xsl:for-each select="1 to 15">
<xsl:call-template name="createMatchupPanel">
<xsl:with-param name="resultNumber" select="."/>
</xsl:call-template>
</xsl:for-each>
|
ここでも to 属性を使って一連の整数を作成します。各整数は対戦カードのの番号です。この番号を、対戦パネルを作成するテンプレートに渡します。コードのリファクタリングを説明する前に、対戦パネルの 1 つを詳しく調べてみましょう。
リスト 20. 典型的な対戦パネル
<xf:case id="r2m2">
<xf:label>
<xf:output class="matchupHeading"
value="concat(titles/round,
' 2 , ',
titles/matchup,
' 2')"/>
</xf:label>
<table cellpadding="3" cellspacing="0" width="95%">
<tr>
<td align="center" valign="center" width="35%" height="325px">
<xf:output mediatype="image/*"
ref="/bracket/contestants/
contestant[@seed=/bracket/results/result[10]
/@firstSeed]/@image"/>
</td>
<td style="line-height: 35px; vertical-align: center;
padding: 15px; width: 30%;">
<table width="100%" height="300px">
<tr>
<td colspan="2">
<xf:select1 ref="results/result[10]/@winnerSeed"
required="true()" appearance="full" xf:navindex="1">
<xf:item>
<xf:label>
<xf:output
value="concat('[',
/bracket/results/result[10]/@firstSeed,
'] ',
/bracket/contestants/contestant
[@seed=/bracket/results/result[10]
/@firstSeed])"/>
</xf:label>
<xf:value ref="/bracket/results/result[10]/@firstSeed"/>
</xf:item>
<xf:item>
<xf:label>
<xf:output
value="concat('[',
/bracket/results/result[10]
/@secondSeed,
'] ',
/bracket/contestants/contestant
[@seed=/bracket/results/result[10]
/@secondSeed])"/>
</xf:label>
<xf:value ref="/bracket/results/result[10]/@secondSeed"/>
</xf:item>
</xf:select1>
</td>
</tr>
<tr style="vertical-align: bottom;">
<td style="text-align: right;" width="50%">
<xf:trigger>
<xf:label class="npButton">< Previous</xf:label>
<xf:toggle ev:event="DOMActivate" case="r2m1"></xf:toggle>
</xf:trigger>
</td>
<td style="text-align: left;" width="50%">
<xf:trigger>
<xf:label class="npButton">Next ></xf:label>
<xf:toggle ev:event="DOMActivate" case="r2m3"></xf:toggle>
</xf:trigger>
</td>
</tr>
</table>
</td>
<td align="center" valign="center" width="35%" height="325px">
<xf:output mediatype="image/*"
ref="/bracket/contestants/contestant
[@seed=/bracket/results/result[10]/@secondSeed]/@image"/>
</td>
</tr>
</table>
</xf:case>
|
パネルによって変化するのは、上記で太字になっている部分のみです。このパネルは、第 2 回戦の第 2 戦目、トーナメント全体では 10 番目の対戦です。このパネル特有の情報の大部分が数字の 10 であることに注目してください。これはつまり、このコードの大部分は容易に生成できるということです。あとは対戦番号を組み込めばよいだけであり、それによってコードの大部分は完成です。
対戦パネルごとに変化するものとして、他に 4 つのものがあります。1 つ目はパネルの ID です。2 つ目はパネルの上の方にあるタイトルです。対戦番号 10 は「Round 2, Matchup 2 (第 2 回戦の第 2 戦)」というタイトルを持つ必要があります。変化するものの 3 つ目と 4 つ目は、(前後にパネルがある場合の) 次のパネルと前のパネルの ID です。10 番目の対戦カードは第 2 回戦 (round 2) の 2 番目の対戦 (2nd matchup) なので、ID は r2m2 です。この対戦カードが対戦番号 10 であることはわかっているので、回戦番号と、その回戦内での対戦番号を示す関数が必要です。これを行うためには、2 つのシーケンスと 2 つの関数を使います。
リスト 21. 回戦番号と、その回戦内での対戦番号を判断する
<xsl:variable name="rounds" as="xsd:integer*"
select="1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 4"/>
<xsl:function name="dw:getRound" as="xsd:integer">
<xsl:param name="resultNumber" as="xsd:integer"/>
<xsl:value-of
select="subsequence($rounds, $resultNumber, 1)"/>
</xsl:function>
<xsl:variable name="matchups" as="xsd:integer*"
select="1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 1, 2, 1"/>
<xsl:function name="dw:getMatchup" as="xsd:integer">
<xsl:param name="resultNumber" as="xsd:integer"/>
<xsl:value-of
select="subsequence($matchups, $resultNumber, 1)"/>
</xsl:function>
|
シーケンス $rounds は各対戦の回戦番号を定義します。最初の 8 回の対戦は第 1 回戦 (Round 1) であり、次の 4 回の対戦は第 2 回戦 (Round 2)、その次の 2 回の対戦は第 3 回戦 (Round 3)、そして最後の対戦は第 4 回戦 (Round 4) です。関数dw:getRound は、対戦番号を入力すると、その対戦の回戦番号を返します。例えば今取り上げている10 番目の対戦カードでは、シーケンスでの 10 番目の項目は数字 2 です。シーケンス $matchups と関数 dw:getMatchup も、同様のことを回戦内での対戦番号に対して行います。対戦番号を指定し、この 2 つの関数を使えば、それ以外の知っておく必要があることもすべて判断することができます。
ここで説明した関数は、スタイルシートの中でグローバルに定義されます。対戦パネルを作成するテンプレートが開始されると、回戦番号と、その回戦での対戦番号の計算が開始されます。
リスト 22. 対戦パネルを生成するテンプレートを開始する
<xsl:template name="createMatchupPanel">
<xsl:param name="resultNumber" as="xsd:integer" required="yes"/>
<xsl:variable name="round" as="xsd:integer"
select="dw:getRound($resultNumber)"/>
<xsl:variable name="matchup" as="xsd:integer"
select="dw:getMatchup($resultNumber)"/>
|
回戦番号は変数 $round に保存され、回戦内での対戦の番号は変数 $matchup の中に保存されます。パネルの ID を生成するためには、コード concat('r', $round, 'm', $matchup) を使います。また見出し「Round 2, Matchup 2」も生成する必要があります。これにも concat() 関数を使います。
最後に、Next ボタンと Previous ボタンを生成する必要があります。そのための最初のステップは、このパネルがその回戦の最初のパネルかどうか、あるいはその回戦の最後のパネルかどうかを判断することです (決勝戦では、1 つの対戦パネルが最初のパネルでもあり、最後のパネルでもあります)。もし最初のパネルであれば、Previous ボタンを作成しません。また最後のパネルであれば、Next ボタンを作成しません。ここではこれらのプロパティーを、XSLT 2.0 の <xsl:function> 要素を使って判断します。
リスト 23. ある対戦が、その回戦の第 1 戦か最終戦かを判断する
<xsl:function name="dw:isNotFirstMatchup" as="xsd:boolean">
<xsl:param name="resultNumber" as="xsd:integer"/>
<xsl:value-of
select="not($resultNumber eq 1) and not($resultNumber eq 9) and
not($resultNumber eq 13) and not($resultNumber eq 15)"/>
</xsl:function>
<xsl:function name="dw:isNotLastMatchup" as="xsd:boolean">
<xsl:param name="resultNumber" as="xsd:integer"/>
<xsl:value-of
select="not($resultNumber eq 8) and not($resultNumber eq 12) and
not($resultNumber eq 14) and not($resultNumber eq 15)"/>
</xsl:function>
|
第 1 戦の対戦番号は 1、9、13、15 であり、一方最終戦の番号は 8、12、14、15 です。これらの単純な関数によって、XSLT のコードが単純になります。下記は Next ボタンと Previous ボタンをどのようにして生成するかを示しています。
リスト24. Next ボタンと Previous ボタンを生成する
<td style="text-align: right;" width="50%">
<xsl:if test="dw:isNotFirstMatchup($resultNumber)">
<xf:trigger>
<xf:label class="npButton">< Previous</xf:label>
<xf:toggle ev:event="DOMActivate"
case="{concat('r', $round, 'm', $matchup - 1)}"/>
</xf:trigger>
</xsl:if>
</td>
<td style="text-align: left;" width="50%">
<xsl:if test="dw:isNotLastMatchup($resultNumber)">
<xf:trigger>
<xf:label class="npButton">Next ></xf:label>
<xf:toggle ev:event="DOMActivate"
case="{concat('r', $round, 'm', $matchup + 1)}"/>
</xf:trigger>
</xsl:if>
</td>
|
もし、その回戦の第 1 戦でなければ、Previous ボタンを作成します。もし、その回戦の最終戦でなければ、Next ボタンを作成します。最後の詳細事項は、次の、あるいは前のパネルの ID を生成することです。Next ボタンと Previous ボタンによって別の回戦を表示することはないので、回戦番号は既にパネル ID でわかっています。回戦内での対戦に関しては、$matchup - 1 は現在の回戦での、前のパネルの位置であり、$matchup + 1 は次のパネルの位置です。リスト 24 で強調されている 2 つの concat() 関数は、これを示しています。
このテンプレートに関する注意として、すべてのパネルを手動で作成した場合には、最初の 8 回の対戦用の XPath 式は、このテンプレートよりも単純でした。つまり最初の 8 回の対戦のシード番号は既にわかっているので、それらのシード番号を XForms 文書の中にハードコーディングしました。(リスト 10 をリスト 11 と比較してください。) リファクタリングした後の、このテンプレートは、15 回の対戦に対して同じタイプのコードを生成するので、最初の 8 回の対戦に関しては他の対戦よりも少し効率が悪くなります。XSLT テンプレートにロジックを追加し、1 から 8 までのパネルのマークアップをもっと単純にするための作業は、読者の演習問題とします。
これで対戦パネルを生成するテンプレートはできたので、繰り返しコードを持つ、リファクターする必要のある領域は、あと 3 つです。つまりトーナメント全体の対戦表を示すパネルと、トーナメントの結果をリセットするコード、そして 1 つの属性から別の属性に値をコピーするコード (先ほど触れた <xf:setvalue> 要素) です。対戦表パネルを生成するコードについては developerWorks の以前の記事で説明したので、ここではそのコードを再掲することはしません。トーナメントのすべてのシードをリセットするコードは、下記のように単純です。
リスト 25. トーナメントの結果をリセットする
<xsl:template name="resetData">
<xsl:for-each select="1 to 8">
<xf:setvalue ev:event="DOMActivate"
ref="/bracket/results/result[{.}]/@winnerSeed" value=""/>
</xsl:for-each>
<xsl:for-each select="9 to 15">
<xf:setvalue ev:event="DOMActivate"
ref="/bracket/results/result[{.}]/@firstSeed" value=""/>
<xf:setvalue ev:event="DOMActivate"
ref="/bracket/results/result[{.}]/@secondSeed" value=""/>
<xf:setvalue ev:event="DOMActivate"
ref="/bracket/results/result[{.}]/@winnerSeed" value=""/>
</xsl:for-each>
</xsl:template>
|
最初の 8 つのシードの場合は、winnerSeed 属性をリセットすればよいだけです。他の結果の場合は、firstSeed 属性と secondSeed 属性もリセットします。属性値テンプレート {.} は、リセットしたい結果の番号を挿入します。
これであとは、ある回戦の結果から次の回戦の結果に属性をコピーするコードを生成する作業が残るのみです。この繰り返しコードは、<result> 要素の数字のパターンを探すようにすれば大幅に単純化することができます。この情報を単純化したバージョンを表 2 に示します。
表 2. <result> 要素間の関係
| 勝者のシード番号 | コピー先 | 位置の差 | コピー先となる位置 |
|---|
| result[1]/@winnerSeed | result[9]/@firstSeed | 8 (9 - 1) | 9 | | result[2]/@winnerSeed | result[9]/@secondSeed | 7 (9 - 2) | 9 | | result[3]/@winnerSeed | result[10]/@firstSeed | 7 (10 - 3) | 10 | | result[4]/@winnerSeed | result[10]/@secondSeed | 6 (10 - 4) | 10 | | result[5]/@winnerSeed | result[11]/@firstSeed | 6 (11 - 5) | 11 | | result[6]/@winnerSeed | result[11]/@secondSeed | 5 (11 - 6) | 11 | | result[7]/@winnerSeed | result[12]/@firstSeed | 5 (12 - 7) | 12 | | result[8]/@winnerSeed | result[12]/@secondSeed | 4 (12 - 8) | 12 | | result[9]/@winnerSeed | result[13]/@firstSeed | 4 (13 - 9) | 13 | | result[10]/@winnerSeed | result[13]/@secondSeed | 3 (13 - 10) | 13 | | result[11]/@winnerSeed | result[14]/@firstSeed | 3 (14 - 11) | 14 | | result[12]/@winnerSeed | result[14]/@secondSeed | 2 (14 - 12) | 14 | | result[13]/@winnerSeed | result[15]/@firstSeed | 2 (15 - 13) | 15 | | result[14]/@winnerSeed | result[15]/@secondSeed | 1 (15 - 14) | 15 |
最初の 8 戦の結果に関しては、これらの対戦の 2 つのシード番号は事前にわかっているため、変更する必要がありません。残る 7 戦の結果を見ると、対戦後の結果にコピーされる勝者のシード番号は、1 から 14 の順に現れます。したがって数学公式として、1 から 9、2 から 9、3 から 10、などを得られるものを作成すればよいだけです。下記はそれを行ってくれるテンプレートです。
リスト 26. <xf:setvalue> 要素を生成するテンプレート
<xsl:template name="updateData">
<xsl:for-each select="1 to 14">
<xf:setvalue ev:event="DOMActivate"
ref="{concat('/bracket/results/result[',
. + (((16 - .) idiv 2) + . mod 2),
']',
if ((. mod 2) eq 1) then '/@firstSeed'
else '/@secondSeed')}"
value="/bracket/results/result[{.}]/@winnerSeed"/>
</xsl:for-each>
</xsl:template>
|
これは気持ちが委縮しそうなコードに見えるかもしれません。これをステップごとに調べてみます。まず、ref 属性のための XPath 式を作成しています。生成される式は、/bracket/results/result[9]/@firstSeed という形式です。ここでは結果の位置を計算し、firstSeed 属性にコピーするのか secondSeed 属性にコピーするのかを判断しなければなりません。
表 2 を見直してみると、現在の項目に対して追加する数字 (3 列目と 4 列目の数字) にはパターンがあります。リスト 26 の計算では、16 から現在の項目を引き、次にその結果を、XPath 2.0 の idiv (整数除算) 演算子を使って 2 で割っています。これに対して、もし現在の項目が奇数なら 1 を追加します (奇数なら mod 1 は 1 ですが、偶数なら mod 1 はゼロです)。下記はこの計算の動作を示す表です。
表 3. <result> 要素の位置を計算する
| 項目 (.) | (16 - .) | ((16 - .) idiv 2) | . mod 2 | (((16 - .) idiv 2) + . mod 2) | 項目 + (((16 - .) idiv 2) + . mod 2) |
|---|
| 1 | 15 | 7 | 1 | 8 | 9 | | 2 | 14 | 7 | 0 | 7 | 9 | | 3 | 13 | 6 | 1 | 7 | 10 | | 4 | 12 | 6 | 0 | 6 | 10 | | 5 | 11 | 5 | 1 | 6 | 11 | | 6 | 10 | 5 | 0 | 5 | 11 | | 7 | 9 | 4 | 1 | 5 | 12 | | 8 | 8 | 4 | 0 | 4 | 12 | | 9 | 7 | 3 | 1 | 4 | 13 | | 10 | 6 | 3 | 0 | 3 | 13 | | 11 | 5 | 2 | 1 | 3 | 14 | | 12 | 4 | 2 | 0 | 2 | 14 | | 13 | 3 | 1 | 1 | 2 | 15 | | 14 | 2 | 1 | 0 | 1 | 15 |
表 3 の最後の 2 つの列にある数字は、表 2 の最後の 2 つの列にある数字と一致します。この XPath 式は少しややこしいですが、適切に作業を行います。1 から 15 までの整数を与えると、この式は 8、7、7、6、6、5、5、4、4、3、3、2、2、1 というシーケンスを生成します。これは表 2 の 3 列目の数字のシーケンスと同じです。
このテンプレートでしなければならないこととして他に残っているのは、値を firstSeed 属性にコピーするのか secondSeed 属性にコピーするのかを判断することのみです。これにも . mod 2 を使うことができます。構文を単純にしておくために、XPath 2.0 の if 演算子を使います。式 if (. mod 2) then '/@firstSeed' else '/@secondSeed' を使うと適切な属性が返されます。この式は、ゼロではない数字は真、ゼロは偽、という事実を利用しています。奇数番号の項目はすべて firstSeed に行き、偶数番号の項目はすべて secondSeed に行きます。
最後の注意点は、各対戦パネルのための XForms コードが 3 ヵ所で concat() 関数を使っていることです。これはつまり、concat() 関数を使って XForms 文書の中で concat() 関数を生成しなければならない、ということです。下記は、「Round x, Matchup y (x 回戦の第 y 戦)」という見出しを作成する XPath 式を生成する例です。
リスト 27. concat() 関数を使って concat() 関数を生成する
<xf:label>
<xf:output class="matchupHeading"
value="{concat('concat(titles/round, '' ', $round,
', '', titles/matchup, '' ', $matchup, ''')')}"/>
</xf:label>
|
これによって、$round と $matchup の値がどちらも 1 の場合に値 concat(titles/round, ' 1, ', titles/matchup, ' 1') が生成されます。この式全体が属性値のテンプレートであることに注目してください。
このスタイルシートを実行し、XForms 文書を生成するためには、下記のコマンドを使います。
リスト 28. XSLT を使って XForms 文書を生成する
java net.sf.saxon.Transform -o tournament.xhtml tourney.xml MatchupBuilder.xsl
|
リファクタリングされた XForms コードは XSLT スタイルシートによって生成されます。今や XForms 文書の繰り返し要素はすべて XSLT によって生成されるため、作成する必要のある、また管理する必要のあるコードはずっと少なくなります。XForms 文書のルック・アンド・フィールに対する全体的な変更は、どのような変更であっても非常に簡単に行うことができます。スタイルシートから生成される XForms のマークアップは、元の XForms 文書より 70 % 小さくなっています。この手法によって、オープン・スタンダードの上で構築を行いながら、低い維持管理コストで大量のコードを作成できるようになります。
まとめ
この記事では、XML データを操作するための、魅力的で使いやすい XForms インターフェースを作成しました。15 回行われる対戦の勝者をユーザーが選択すると、完全で妥当な XML 文書が自動的に作成されます。この記事の例では、この文書をファイルに書き出しただけですが、この XML 文書を Web アプリケーションにサブミットすることも同じように簡単に行えます。開発と管理を単純化するために XForms 文書のマークアップをリファクタリングする手段として、XSLT スタイルシートを使ってマークアップを生成しました。何よりも良いことに、XForms 文書の中にあるものが、すべて直接 XML データ・モデルにリンクされています。
XML データを表示したり、作成したり、あるいは操作したりする必要がある場合には、カスタムのユーザー・インターフェースを作成するための優先技術として、XForms を考慮してみてください。
謝辞
この記事の心臓部となる文書を作成する際に、多くの質問に答えてくださり、助力と好意、そして XForms に関する専門知識を提供してくださった Keith Wells 氏に感謝いたします。
ダウンロード | 内容 | ファイル名 | サイズ | ダウンロード形式 |
|---|
| XML and XHTML samples from this article | xfbracket-samples.zip | 196KB | HTTP |
|---|
参考文献 学ぶために
製品や技術を入手するために
議論するために
著者について  | 
|  | Doug Tidwell は IBM の Software Group Strategy 組織の Technology Evangelist です。彼の現在、SCA (Service Component Architecture) や SDO (Service Data Objects)、そして XForms などの新興技術を中心とした業務を行っています。彼は O'Reilly 刊の『XSLT』の著者であり、この第 2 版が事前注文を受け付け中です。彼は1997年に行われた最初のXMLコンファレンスの講演者であり、それ以来マークアップ言語を (非常に大ざっぱですが) 約 20年間扱ってきました、現在は、combo meal (セット・メニュー) の発明者であるイギリスのファーストフードの大物、William "Add-a-Piece" Thackeray に関する本を執筆中です。彼はノースキャロライナ州の Chapel Hill に、彼の妻と娘、そして犬と住んでいます。 |
記事の評価
|