mb_language("uni"); mb_internal_encoding("utf-8"); mb_http_input("auto"); mb_http_output("utf-8"); ?>
このセクションでは、バックエンドシステムとしてWebサービスを利用したシステムを構築します。 このサンプル用のWebサービスとして、マイクロソフト社のWebサイトからダウンロードできる受注・出荷業務Webサービスを利用します。このWebサービスはVisual Studio Tools for Office向けのサンプルとして公開されているものですが、InfoPathから直接利用することもできます。
このWebサービスはローカルコンピュータ上で稼動するものなので、InfoPath上での開発を開始する前に、ファイルのインストールやデータベースのセットアップ、Webサービスのセットアップなどの作業が必要になります。必要なファイルのダウンロード、動作環境の確認も含め、このWebサービスの詳細についてはマイクロソフト社のWebサイトにある「スマートクライアント デモキット1:Visual Studio Tools for Officeの実現するスマートクライアント
(http://www.microsoft.com/japan/msdn/vstudio/office/productinfo/IntelliNet/)」をご参照ください。
このセクションでは、いよいよInfoPathとWebサービスを組み合わせたシステムを構築してみましょう。
今回作成するのは、オフィス用品の販売を行っている会社で受注した情報を登録するためのフロントエンドです。オペレータが受注した情報を登録し、サーバーに送信すると、Webサービスがその情報を受け取り、在庫情報の更新などの処理を行います。さらに、スクリプトを使った動的なデータ更新や既定値の設定などの方法も確認していきます。
今回作成するフォームの主な機能を図14に示します。
図14:今回作成する受注登録フォームの主な機能
今回のように送受信するためのWebサービスがすでに存在している場合、そのWebサービスのWSDLを起点としてInfoPathのフォーム(データソース)を作成できます。
サンプルWebサービスのインストールが完了していれば、WSDLは、http://localhost/IntelliNetWS/EntryPoint.asmx?WSDLからアクセスできます(インストール先やIISの設定などによって異なる場合があります。そのときは適宜変更してください)。ブラウザからWSDLにアクセスできるURIを確認し、次のステップに進みます。
InfoPathを起動し、新しいフォームのデザインを開始します。[フォームのデザイン]作業ウィンドウで[データソースから新規作成]を選択し、[データソースセットアップウィザード]を使って設定していきます(図15を参照)。
図15:Webサービスを利用したデータソースのセットアップ
ウィザードによってWebサービスのWSDLが解析され、このフォームで使用されるデータソースが自動的に作成されます。ウィザードが完了した段階で、データソースが図16のように作成されているはずです。いかがですか?
図16:WSDLから自動的に作成されたデータソース
データソースのdataFieldsの中にあるs0:Orderは、InfoPathから送信される注文情報をあらわしています。その中にはOrderIdやCustomerId、CustomerNameなど、注文情報のフィールドが含まれています。フィールド名の横に赤いアスタリスク(*)が表示されているフィールドは入力必須であることを示しています。
S0:OrdersにはItemsグループが含まれており、その中にはItemグループが含まれています。Itemグループのアイコンには小さな青い四角と白い三角のアイコンがついていることにお気づきでしょうか。これは、ItemグループがItemsグループ内で繰り返し出現可能であることを示しています。これにより、一度の注文で複数の商品を注文できるようになっていることがわかります。
WSDLの中には、各メソッドを呼び出すときに必要な引数の情報が含まれています。また、その引数がどういったデータ構造になっているのかという情報も含んでいます。InfoPathは選択したWebサービスのメソッド(今回であればOrderConfirmメソッド)の引数に関する情報をWSDLから読み込み、メソッドの呼び出しに必要なデータをデータソースとして自動的に定義してくれるのです。
さて、データソースの定義はあっけないほど簡単に終わりました。
しかし、このデータソースの中にあるCustomerIdとCustomerName、EmployeeIdとEmployeeNameのようなフィールドから、顧客や担当者に関するマスターデータが存在していることが推察できます。商品のProductIdとProductNameも同様ですし、おそらく標準価格についても商品マスターに含まれていることでしょう。
では、フォームの設計に入る前に、このWebサービスで利用するデータベースを確認し、どのようにデータが扱われているのかを確認しておきましょう。
今回のサンプルでは、データベースとしてSQL Server(またはMSDE)を使用しています。サンプルWebサービスのインストール時に「ASPNETKIT」という名前のデータベースが作成され、データベーススキーマやサンプルデータが作成されています。
このデータベースのテーブル設計についてはダウンロードしてきたファイルに含まれる「開発者ガイド」にも記載されていますが、このフォームで扱う発注処理で関係しそうなのは「Customers」、「Employees」、「Products」の3つのマスターテーブルです。
■ Customersテーブル
顧客マスターです。以下のようなテーブル構成になっており、サンプルデータとして2件の顧客が登録されています。
CustomerID | int | 4 | NULL 不可 |
CustomerName | varchar | 64 | NULL 可 |
ContactName | varchar | 32 | NULL 可 |
Title | varchar | 32 | NULL 可 |
Post | varchar | 32 | NULL 可 |
Address | varchar | 128 | NULL 可 |
Phone | varchar | 16 | NULL 可 |
Fax | varchar | 16 | NULL 可 |
varchar | 64 | NULL 可 | |
EmployeeID | int | 4 | NULL 不可 |
■ Employeesテーブル
担当者(従業員)マスターです。以下のようなテーブル構成になっており、サンプルデータとして3人の担当者が登録されています。
EmployeeID | int | 4 | NULL 不可 |
Name | varchar | 64 | NULL 可 |
Title | varchar | 32 | NULL 可 |
Post | varchar | 32 | NULL 可 |
Phone | varchar | 16 | NULL 可 |
FAX | varchar | 16 | NULL 可 |
varchar | 64 | NULL 可 | |
EmployeeID | int | 4 | NULL 不可 |
Name | varchar | 64 | NULL 可 |
Title | varchar | 32 | NULL 可 |
■ Productsテーブル
商品マスターです。以下のようなテーブル構成になっており、サンプルデータとして13の商品が登録されています。
ProductId | int | 4 | NULL 不可 |
ProductName | varchar | 64 | NULL 不可 |
Price | money | 8 | NULL 不可 |
Stock | int | 4 | NULL 不可 |
これらのテーブルはマスターテーブルであり、フォーム上でのデータ入力の際に利用されます(「機能2:選択肢をデータベースから取得し、対応するデータを自動的に入力する」で取り上げます)。一般的に、ほとんどのシステムではデータベースを利用していますし、大半のシステムではマスターテーブルが存在します。事前にマスターテーブルの構成を確認しておくとフォームの作成がスムーズに進むので、前もってチェックしておくことをお勧めします。
InfoPathのフォーム設計において、既定値として固定の値を設定するのは非常に簡単です。データソースのフィールドを追加(または編集)するときに、[既定値]フィールドにその値を指定するだけです。
しかし、この方法では可変の値を設定できません。たとえば、データを入力した日や現在のユーザー名、ユニークなIDなど、そのときによって値が変わる場合には[既定値]フィールドを使って自動的に入力することができません。となると、作成日などの簡単なデータもオペレータが毎回入力しなければならないことになり、フォームの使い勝手がかなり悪くなってしまいます。
このような場合には、InfoPathのスクリプトを使って既定値を設定します。では、一例として、「注文コード」フィールドにユニークなIDを自動的に入力する方法を確認してみましょう。
まず、「注文コード」フィールドに対応するデータソースの「OrderId」をダブルクリックし、フィールドのプロパティを確認します。すると、このフィールドは単純な連番ではなく、GUIDを使っていることがわかりますね。では、GUIDを生成し、その値を「注文コード」フィールドに設定するスクリプトを作成しましょう。
柔軟に既定値を設定するには、フォームのOnLoadイベントを使うべし!
可変の値を既定値として設定するには、フォームのOnLoadイベントを記述します。このイベントはフォームの読み込み時に動作しますので、ユーザーが入力を開始する前に既定値を設定することができます。
InfoPathのメニューから[ツール]-[スクリプト]-[読み込み時のイベント]を選択すると、Script Editorが起動して、XDocument::OnLoadの処理を記述できるようになります。
フィールドを操作するスクリプトを作成するときは、XML(DOM)を意識せよ!
さて、フォームのOnlLoadイベントを使うとなれば、読み込み時に特定のフィールドに値を設定するスクリプトを書けばよい、というところまではすぐに結びつきます。では、実際にはどのようにスクリプトを記述するのでしょうか
実はInfoPathのスクリプトでは、フォーム上のデータはXDocumentオブジェクトというXMLデータとして扱い、各フィールドへのアクセスは DOMを使って行います。したがって、「フィールドの値を設定する」という一見単純な動作においても、対象となるフィールドにはDOMを使ってアクセスしなければなりません。
そのため、フィールドの値を参照したり、設定したりするだけの単純な動作であっても、記述するコードの量は比較的多くなります。それで、そのような面倒で定型的な処理は関数にしておき、必要な箇所でその関数を呼び出すようにするのが得策です。
実際、InfoPathの標準サンプルの中でも、ノードの基本的な操作は関数として記述されており、各機能から必要に応じて呼び出されています。ノードの操作において、特に使用頻度が高いと思われる関数をリスト2に示します(これらの関数はInfoPathの標準サンプルから抜き出したものです)。
// ----------------------------------------------------------------
// initializeNodeValue(): フィールドの既定値を設定する。
// ----------------------------------------------------------------
function initializeNodeValue(xpath, strValue)
{
var xmlNode = getNode(xpath);
// そのフィールドの値が入っていない場合にのみ、新しい値を設定する
if (xmlNode.text == "") setNodeValue(xmlNode, strValue);
}
// ----------------------------------------------------------------
// setNodeValue (xpath, value): XML のノードの値を設定する。
// ----------------------------------------------------------------
function setNodeValue(xpath, value)
{
var xmlNode = getNode(xpath);
if (!xmlNode) return;
// 値を設定する前に、xsi:nil を削除しておく必要があります。
if (value != "" && xmlNode.getAttribute("xsi:nil"))
xmlNode.removeAttribute("xsi:nil");
// 以前の値と変わった場合にのみ、実際に値を設定します。
If (xmlNode.text != value)
xmlNode.text = value;
}
// ----------------------------------------------------------------
// getNode(xpath): XML のノードを取得する。
// ----------------------------------------------------------------
function getNode(xpath)
{
// XML Node、XPath のどちらでも対応できます。
if (typeof(xpath) == "string")
return XDocument.DOM.selectSingleNode(xpath);
else
return xpath;
}
これらの関数を利用して、注文コードを自動的に設定する処理をリスト3に示します。ここではActiveXオブジェクトを使ってGUIDを生成し、initializeNodeValueを使ってOrderIdに値を設定します。ノードの特定にXPathを使用していることに注意してください。
//=======
// 次の関数ハンドラが自動的に作成されます。
// 関数の名前や引数の名前と数を変更しないでください。
// この関数は次のフィールドまたはグループ(XPath) に
// 関連しています: /dfs:myFields/dfs:dataFields/s0:Order/s0:OrderId
// 注意: このコメントの情報は、関数ハンドラの作成後、更新されません。
//=======
function XDocument::OnLoad(eventObj)
{
// 読み取り専用の場合はそのまま終了する。
if ( XDocument.IsDOMReadOnly ) return;
initializeNodeValue( "/dfs: マイフィールド /dfs:dataFields/s0:Order/s0:OrderId", getGUID() );
}
// -------------------------------------------------------------------------
// getGUID
// GUID(8 桁-4 桁-4 桁-4 桁-12 桁)を生成します。
// -------------------------------------------------------------------------
function getGUID()
{
var ax = new ActiveXObject("Scriptlet.Typelib") ;
// ここでActiveX オブジェクトを使用しているため、セキュリティの確認ダイアログが表示されます。
var reg = / ¥{| ¥}/g ;
var guid = ax.guid ;
return ( guid.replace( reg, "" ) ) ;
}
今回のように既定値を設定する場合だけでなく、あるフィールドの入力値に応じて別のフィールドの値を変更するとき、また計算などを行うときなど、フィールドを扱う操作はすべてこのDOMを使った方法が基本になります。ぜひマスターしておかれることをお勧めします。
次に取り組むのは、リストボックスの選択肢をデータベース内のマスターから取得し、その値に応じて別のフィールドを設定する、という機能です。たとえば、「顧客」フィールドで顧客マスターから顧客名を選択すると、選択した顧客の「顧客コード」フィールドに顧客コードが自動的に設定される、といった動作です。
リストボックスの選択肢や関連した値をデータベースから取得するには、セカンダリデータソースを設定する必要があります。
セカンダリデータソースを活用すべし!
フォームの(メインの)データソースは、フォームに記入したデータそのものを送受信する際に利用されるデータ構造です。それに対し、ドロップダウンリストボックスの選択肢など、フォームで二次的に利用されるデータソースを「セカンダリデータソース」と呼びます。セカンダリデータソースには、メインのデータソースと同様、Webサービスやデータベースを利用できます。
では、今回のサンプルフォームを使い、「CustomerName」フィールドで顧客マスター内の顧客をリストする方法を確認しましょう。
先ほど、ASPNETKITデータベース内のCustomersテーブルが顧客マスターであることを確認しました。このテーブルをセカンダリデータソースとして設定します。
この作業の前提として、「CustomerName」フィールドをドロップダウンリストボックスとして設定する必要があります。「CustomerName」フィールドのようなテキスト(string)のフィールドは既定のままだとテキストボックスになりますので、ドロップダウンリストボックスに変更しておいてください。その後、「CustomerName」フィールドをダブルクリックし、フィールドのプロパティを開いて、リストボックスのデータ設定を開始します(図17を参照)。
図17:リストボックスの選択肢をデータベースから取得する
これで、データベースの値をドロップダウンリストの選択肢として表示できるようになりました。
データベースの値を参照するにも、セカンダリデータソースを活用するべし!
ドロップダウンリストで選択した値によって、データベース内の値を参照し、フィールドに設定するにはどうすればいいでしょうか? たとえば、「CustomerName」フィールドを選択したとき、自動的にその顧客のIDを「CustomerId」フィールドに入れるような場合です。この場合には、スクリプトとセカンダリデータソースを組み合わせて活用します。
まず、「CustomerName」フィールドが更新された時にスクリプトが呼び出されるようにします。それには、「CustomerName」フィールドのプロパティを開き、[データの入力規則]をクリックしてから、[スクリプト]の[イベント]で「OnAfterChange」を選択して[編集]をクリックします。
すると、msoxd_s0_CustomerName::OnAfterChangeの処理を記述できるようになりますので、リスト4のように入力します。
function msoxd_s0_CustomerName::OnAfterChange(eventObj)
{
if (eventObj.IsUndoRedo)
{
return;
}
var CustomerName = getNode("/dfs: マイフィールド/dfs:dataFields/s0:Order/s0:CustomerName").text ;
UpdateCustomerId( CustomerName ) ;
}
function UpdateCustomerId( CustomerName )
{
var dataObject = XDocument.DataObjects.Item("Customers") ;
dataObject.DOM.setProperty("SelectionNamespaces",
'xmlns:dfs="http://schemas.microsoft.com/of.ce/infopath/2003/dataFormSolution"' +
'xmlns:d="http://schemas.microsoft.com/of.ce/infopath/2003/ado/dataFields"');
var queryNode = dataObject.DOM.selectSingleNode(
"/dfs: マイフィールド/dfs:dataFields/d:Customers[@CustomerName='"+CustomerName + "']" );
var queryValue = queryNode.getAttribute( "CustomerID" ) ;
setNodeValue("/dfs: マイフィールド/dfs:dataFields/s0:Order/s0:CustomerId",queryValue ) ;
}
フィールドの値の設定方法は、既定値を設定するときの方法と大きくは異なりませんが、ここでの最大のポイントは、UpdateCustomerIdの最初にあるXDocument.DataObjectsです。
XDocument.DataObjectsを使うと、フォームで定義されているセカンダリデータソースのデータを参照できます。ここでは先ほど定義した「Customers」という名前のセカンダリデータソースを参照しています。
セカンダリデータソースを取得できれば、あとはXPathを使って条件に合致するノードの値を取り出すだけです。データベースへの再接続などの処理は一切必要ありません。InfoPathがセカンダリデータソースに接続したときに、そのテーブルの内容をXMLとして保持しているため、それを再度参照するだけなのです。こんなところでもXMLが活躍していますね。
今回は「CustomerName」と「CustomerId」の組み合わせのみを取り上げましたが、「EmployeeName」と「EmployeeId」、さらには「ProductName」と「ProductId」、「Price」に関しても同じ仕組みで応用できます。データソースをそれぞれ設定し、あとはスクリプトを使って対応するフィールドを更新します。
セカンダリデータソースとスクリプトをフル活用して、使いやすいフォームを作成してください。
読者の方もお気づきのとおり、これは機能1の応用編です。商品ごとに個数(Quantity)と単価(Price)を掛けて、小計を設定します。リストは掲載しませんので、ぜひ自力で取り組んでみてください。重要なヒントを2つ、お教えしましょう。
→ どのイベントで動作させるか、はお分かりですよね? 「ProductName」フィールド、「Quantity」フィールド、「Price」フィールドのOnAfterChangeイベントで動作させましょう。
→ Itemのような繰り返しフィールドの場合、XPathでノードの位置を特定するには、OnAfterChangeの引数(eventObj)を活用できます。eventObjを起点にして、parentなどを使って相対的に取得することになります。ブレークポイントなどを活用して、eventObjの内容も探ってみてください。
フォームが完成したら、あとはテストです。Webサービスに接続し、注文処理が正しく行われるかどうか、確認してみてください。もし注文処理が正しく処理されていれば、データベース内の商品の在庫数が更新されるはずです。
このセクションでは、Webサービスを利用する場合の基本的な作業手順と、スクリプトを利用したフォームデータの操作を扱いました。利用する用途やWebサービスによって細部は異なりますが、基本的には同じような手順で進めていきます。
今回の特集記事はいかがだったでしょうか?
紙面の都合上、扱えなかった機能はまだまだたくさんあります。データを別のレイアウトで見せるための「ビュー」、特定の条件に当てはまるデータを取り出すための「クエリビュー」などは、残念ながら今回取り上げることができませんでした。これらの機能についてはヘルプやサンプルの中で頻繁に取り上げられていますので、ぜひ参考にしてみてください。
最後のセクションのスクリプトを見ていくとわかるように、InfoPathは内部的には完全にXMLをベースにしています。 しかし、データを入力するエンドユーザーやオペレータにはそのことはまったく見えません。他のシステムとの連携性などのXMLのメリットを生かしつつ、エンドユーザーの敷居は低く抑えることができていますし、開発作業も極力抑えることができています。この点には注目できるでしょう。
今回のOfficeから新しく加わった生まれたてのアプリケーションとして、InfoPathにはさらに使いやすくなってほしい部分も確かにあります。可変の既定値をもう少し手軽に設定できたら楽なのに(これはInfoPathのデータを極力シンプルなXMLデータファイルにまとめあげるために切り捨てられたのかもしれませんが)、とか、SOAP RPCが利用できたらいろいろな処理系とつなぎやすくなるのに、といった要望はありますが、現時点でもInfoPathは強力なポテンシャルを秘めており、非常に興味深く、取り組み甲斐のあるアプリケーションといえるでしょう。
今後、Office製品の一角として企業内にどのように導入されていくのか、非常に楽しみです。
▲このページのTOPへ