http://blogs.sqlpassj.org/masatotaniguchi/archive/2005/07/09/12119.aspx
の続きになります。
前回は、aspファイルからデータアクセスを分離してみましたが、そのデータアクセスオブジェクト(DAO)はaspから呼ばれるとは限りません。別のActiveX dllから呼ばれ、例えばWEBの画面を動的に構成するということも考えられます。その例として取り上げたのが連動(連結)プルダウン(コンボボックス)です。部署を選択すると、その部署に属する社員だけが選択されるといったVisual Basic 6.0やVBAのプログラミングではおなじみのインターフェースです。WEBアプリケーションでは連動プルダウンがいくつもある場合、部署を選択するたびにリクエストを行うと、ユーザーにもサーバーにも負担をかけてしまいます。
そこで、例えばこのようなActiveX dllを作ってみました。
ソースファイル:linkedlist.asp
<HTML>
<HEAD>
<TITLE>連動プルダウン</TITLE>
<%
Set EmpLinkedListBox = Server.CreateObject("ScottView.clsEmpLinkedListBox")
EmpLinkedListBox.InitFunctionName = "onInit"
EmpLinkedListBox.ParentOnChangeFunctionName = "parentOnChange"
EmpLinkedListBox.ParentListName = "parentList"
EmpLinkedListBox.ChildListName = "childList"
%>
<%=empLinkedListBox.GetJavaScript%>
</HEAD>
<BODY onLoad="onInit(document.linkedList.parentList, document.linkedList.childList)">
<FORM name="linkedList">
部署 <SELECT name="parentList" onChange="parentOnChange(document.linkedList.parentList, document.linkedList.childList)">
</SELECT><BR>
社員 <SELECT name="childList">
</SELECT>
</FORM>
</BODY>
</HTML>
上のようなaspが変換後、下のようになれば成功です。
<HTML>
<HEAD>
<TITLE>連動プルダウン</TITLE>
<SCRIPT LANGUAGE="JavaScript1.1">
<!--
function onInit(parentList, childList) {
childList.options[0] = new Option("CLARK", "7782")
childList.options[1] = new Option("KING", "7839")
childList.options[2] = new Option("MILLER", "7934")
parentList.options[0] = new Option("ACCOUNTING", "10")
parentList.options[1] = new Option("RESEARCH", "20")
parentList.options[2] = new Option("SALES", "30")
parentList.options[3] = new Option("OPERATIONS", "40")
}
function parentOnChange(parentList, childList) {
var parentListValue = parentList.value;
childList.length = 0;
if (parentListValue == "10") {
childList.options[0] = new Option("CLARK", "7782")
childList.options[1] = new Option("KING", "7839")
childList.options[2] = new Option("MILLER", "7934")
} else if (parentListValue == "20") {
childList.options[0] = new Option("SMITH", "7369")
childList.options[1] = new Option("JONES", "7566")
childList.options[2] = new Option("SCOTT", "7788")
childList.options[3] = new Option("ADAMS", "7876")
childList.options[4] = new Option("FORD", "7902")
} else if (parentListValue == "30") {
childList.options[0] = new Option("ALLEN", "7499")
childList.options[1] = new Option("WARD", "7521")
childList.options[2] = new Option("MARTIN", "7654")
childList.options[3] = new Option("BLAKE", "7698")
childList.options[4] = new Option("TURNER", "7844")
childList.options[5] = new Option("JAMES", "7900")
} else if (parentListValue == "40") {
}
}
// -->
</SCRIPT>
</HEAD>
<BODY onLoad="onInit(document.linkedList.parentList, document.linkedList.childList)">
<FORM name="linkedList">
部署 <SELECT name="parentList" onChange="parentOnChange(document.linkedList.parentList, document.linkedList.childList)">
</SELECT><BR>
社員 <SELECT name="childList">
</SELECT>
</FORM>
</BODY>
</HTML>
この例では、連動プルダウンは一つですが、SELECTタグの名前と、それにあわせて関数を呼ぶ時の引数を変えれば部署-社員の連動プルダウンはいくつも作ることができます(ただし初期化関数は個数分繰り返して呼ぶ必要があります)。変換後のHTMLソースをじっと眺めると、onInit関数は親子の初期化、parentOnChange関数はif分の条件式と、その{}の中に特徴があります。そこで、初期化関数(onInit)のオプション生成をコレクションにしたものと、親プルダウン変更時関数(parentOnChange)のIFブロック(部署ごとにグループ化された社員のオプション生成)をコレクションにしたものを渡せば、上記JavaScriptを生成するクラスを作成してみました。ScottViewという名前のActiveX dllプロジェクトをつくり、プロジェクトメニューの参照設定で、ScottDAOを追加します。そして以下のソースファイルを追加します。JavaScript用標準モジュール以外全てクラスモジュールですがclsEmpLinkedListBox以外はInstancingプロパティをPrivateにします。エラー処理、データのチェックは省略してあります。
' ソースファイル clsLinkedListBoxBuilder:クラスモジュール
Private m_ParentListName As String
Private m_ChildListName As String
Private m_InitFunctionName As String
Private m_ParentOnChangeFunctionName As String
Private m_InitFunctionOptions As Collection
Private m_ParentOnChangeIfBlocks As Collection
' 初期化関数名
Public Property Let InitFunctionName(strInitFunctionName As String)
m_InitFunctionName = strInitFunctionName
End Property
' 親リスト変更イベント関数名
Public Property Let ParentOnChangeFunctionName( _
strParentOnChangeFunctionName As String)
m_ParentOnChangeFunctionName = strParentOnChangeFunctionName
End Property
' 親リスト名
Public Property Let ParentListName(strParentListName As String)
m_ParentListName = strParentListName
End Property
Public Property Get ParentListName() As String
ParentListName = m_ParentListName
End Property
' 子リスト名
Public Property Let ChildListName(strChildListName As String)
m_ChildListName = strChildListName
End Property
Public Property Get ChildListName() As String
ChildListName = m_ChildListName
End Property
' 初期化関数本体
Public Property Set InitFunctionOptions(Options As Collection)
Set m_InitFunctionOptions = Options
End Property
' ParentOnChange関数用If Block
Public Property Set ParentOnChangeIfBlocks(IfBlocks As Collection)
Set m_ParentOnChangeIfBlocks = IfBlocks
End Property
' 初期化関数作成
Private Function CreateInitFunctionBody() As String
Dim strRet As String
Dim strOption As Variant
strRet = ""
For Each strOption In m_InitFunctionOptions
strRet = strRet & JS_INDENT & strOption & vbCrLf
Next
CreateInitFunctionBody = strRet
End Function
' ParentOnChange関数作成
Private Function CreateParentOnChangeFunctionBody() As String
Dim strRet As String
Dim IfBlock As clsParentOnChangeIfBlock
Const FIRST_IF_TEMPLATE = JS_INDENT & "if (parentListValue == ""?"") {"
Const ELSE_IF_TEMPLATE = JS_INDENT & "} else if (parentListValue == ""?"") {"
Const CLOSE_IF = JS_INDENT & "}"
' 選択されている親リストのキーをparentListValue変数に代入するjavaScriptコード
strRet = JS_INDENT & "var parentListValue = " & m_ParentListName & ".value;" & vbCrLf
' 子リストの内容をクリア(length = 0)
strRet = strRet & JS_INDENT & m_ChildListName & ".length = 0;" & vbCrLf
' IFブロックの作成
Dim blnFirstBlock As Boolean
blnFirstBlock = True
For Each IfBlock In m_ParentOnChangeIfBlocks
If blnFirstBlock Then
strRet = strRet & Replace(FIRST_IF_TEMPLATE, "?", IfBlock.Key) & vbCrLf
strRet = strRet & IfBlock.Body
Else
strRet = strRet & Replace(ELSE_IF_TEMPLATE, "?", IfBlock.Key) & vbCrLf
strRet = strRet & IfBlock.Body
End If
blnFirstBlock = False
Next
' IFブロックを閉じる
strRet = strRet & CLOSE_IF
CreateParentOnChangeFunctionBody = strRet
End Function
' JavaScript作成
Public Function GetJavaScript() As String
Dim strRet As String
' 初期化関数引数
Dim strInitVar As String
' ParentOnChange関数引数
Dim strParentOnChangeVar As String
strInitVar = m_ParentListName & ", " & m_ChildListName
strParentOnChangeVar = m_ParentListName & ", " & m_ChildListName
' JSタグ
strRet = JS_START_TAG
' 初期化関数
strRet = strRet & CretateJSFunction(m_InitFunctionName, _
strInitVar, CreateInitFunctionBody()) & vbCrLf
' ParentOnChange関数
strRet = strRet & CretateJSFunction(m_ParentOnChangeFunctionName, _
strParentOnChangeVar, CreateParentOnChangeFunctionBody()) & vbCrLf
strRet = strRet & JS_END_TAG
GetJavaScript = strRet
End Function
ただし、親プルダウン変更時関数(parentOnChange)のIFブロックを保存するために以下のクラスを用いました。
' ソースファイル clsParentOnChangeIfBlock:クラスモジュール
Private m_Key As String
Private m_Body As String
Public Property Let Key(strKey As String)
m_Key = strKey
End Property
Public Property Get Key() As String
Key = m_Key
End Property
Public Property Let Body(strBody As String)
m_Body = strBody
End Property
Public Property Get Body() As String
Body = m_Body
End Property
またJavaScript用の共通関数として、以下の標準モジュールを使用しています。Active X dllではグローバル変数の使用はご法度なので注意が必要です。
' ソースファイル JavaScript:標準モジュール
' JavaScriptタグ
Public Const JS_START_TAG = "
" & vbCrLf
' インデント
Public Const JS_INDENT = " "
' JavaScript関数を作成
Public Function CretateJSFunction(strFunctionName As String, _
strVar As String, strFunctionBody As String) As String
CretateJSFunction = "function " & _
strFunctionName & "(" & strVar & ") {" & _
vbCrLf & strFunctionBody & vbCrLf & "}"
End Function
' オプションを作成
Public Function CreateJSOption( _
strSelectName As String, intOptionIndex As Integer, _
strText As String, strValue As String)
CreateJSOption = strSelectName & ".options" & "[" & intOptionIndex & "]"
CreateJSOption = CreateJSOption & _
" = new Option(""" & strText & """, """ & strValue & """)"
End Function
clsLinkedListBoxBuilderを包み込むような感じで、clsEmpLinkedListBoxクラスを作成します。
ScottDAOで作成したclsEmpDaoのほかに、部署用のclsDeptDaoが追加されています。
clsDeptDaoの作り方はclsEmpDaoと全く同じです。
' ソースファイル clsEmpLinkedListBox:クラスモジュール
Private LinkedListBoxBuilder As clsLinkedListBoxBuilder
' 初期化関数名
Public Property Let InitFunctionName(strInitFunctionName As String)
LinkedListBoxBuilder.InitFunctionName = strInitFunctionName
End Property
' 親リスト変更イベント関数名
Public Property Let ParentOnChangeFunctionName( _
strParentOnChangeFunctionName As String)
LinkedListBoxBuilder.ParentOnChangeFunctionName = _
strParentOnChangeFunctionName
End Property
' 親リスト名
Public Property Let ParentListName(strParentListName As String)
LinkedListBoxBuilder.ParentListName = strParentListName
End Property
' 子リスト名
Public Property Let ChildListName(strChildListName As String)
LinkedListBoxBuilder.ChildListName = strChildListName
End Property
' clsLinkedListBoxBuilderクラス初期化
Private Sub Class_Initialize()
Set LinkedListBoxBuilder = New clsLinkedListBoxBuilder
End Sub
Public Function GetJavaScript() As String
Dim ScottDaoFactory As clsScottDAOFactory
Dim EmpDao As clsEmpDao
Dim DeptDao As clsDeptDao
Dim Dept As Collection
Dim DeptVo As clsDeptVo
' LinkedListBoxBuilderに渡す
' InitFunctionOptionsプロパティと
' ParentOnChangeIfBlocksプロパティ
Dim InitFunctionOptions As Collection
Dim ParentOnChangeIfBlocks As Collection
Set InitFunctionOptions = New Collection
Set ParentOnChangeIfBlocks = New Collection
' 部署リストボックスの配列のINDEX用
Dim intDeptCount As Integer
' 初期化関数オプション一時保存用
Dim InitFunctionOption As String
' DAOFactoryインスタンス作成
Set ScottDaoFactory = New clsScottDAOFactory
' 部署、社員DAO取得
Set DeptDao = ScottDaoFactory.DeptDao
Set EmpDao = ScottDaoFactory.EmpDao
' 部署を全て選択
Set Dept = DeptDao.SelectDept
' 全選択された部署ごとのループ
intDeptCount = 0
For Each DeptVo In Dept
Dim Emp As Collection
Dim EmpVo As clsEmpVo
' 部署(親)リスト変更イベント関数IFブロック作成
Dim ParentOnChangeIfBlock As clsParentOnChangeIfBlock
Set ParentOnChangeIfBlock = New clsParentOnChangeIfBlock
ParentOnChangeIfBlock.Key = DeptVo.DeptNo
ParentOnChangeIfBlock.Body = ""
intDeptCount = intDeptCount + 1
Set Emp = EmpDao.SelectEmp(DeptVo.DeptNo)
' 社員番号リストボックスの配列のINDEX用
Dim intEmpCount As Integer
intEmpCount = 0
' 部署に属する社員のループ
For Each EmpVo In Emp
intEmpCount = intEmpCount + 1
If intDeptCount = 1 Then
' 初期化関数は、社員(子)リストボックスに
' 最初のINDEXの部署の社員をリストする
InitFunctionOption = JS_INDENT & _
CreateJSOption(LinkedListBoxBuilder.ChildListName, _
intEmpCount - 1, EmpVo.Ename, EmpVo.EmpNo)
Call InitFunctionOptions.Add(InitFunctionOption)
End If
' ParentOnChangeのIfブロック内のオプション(INDENT二つ)
ParentOnChangeIfBlock.Body = ParentOnChangeIfBlock.Body & _
JS_INDENT & JS_INDENT & _
CreateJSOption(LinkedListBoxBuilder.ChildListName, _
intEmpCount - 1, EmpVo.Ename, EmpVo.EmpNo) & vbCrLf
Next
' 初期化関数の本体を作成
InitFunctionOption = JS_INDENT & _
CreateJSOption(LinkedListBoxBuilder.ParentListName, intDeptCount - 1, _
DeptVo.DName, DeptVo.DeptNo)
Call InitFunctionOptions.Add(InitFunctionOption)
' その部署の社員分のオプションをBodyにつめ終えたので、
' ParentOnChangeIfBlocksコレクションに追加
Call ParentOnChangeIfBlocks.Add(ParentOnChangeIfBlock)
Next
ScottDaoFactory.CloseConnection
Set LinkedListBoxBuilder.InitFunctionOptions = InitFunctionOptions
Set LinkedListBoxBuilder.ParentOnChangeIfBlocks = ParentOnChangeIfBlocks
GetJavaScript = LinkedListBoxBuilder.GetJavaScript
End Function
もし、他の連動プルダウンがほしくなった時は、同じようにclsLinkedListBoxBuilderを使い新たにクラスを作成します。