挿入/更新機能
挿入と更新はよく似た作業です。どちらも同じデータ群を扱い、同じフォームを使用します。両者の違いは、挿入フォームはブランクで初期化される(つまり、入力要素が空である)のに対し、更新フォームはいくつかの値で初期化されるという点だけです。この類似性を生かし、挿入機能と更新機能を1つのXSLTスタイルシートと1つの表示関数にまとめ、呼び出しだけを別にすることにしました。
挿入/更新XSLTフォーム
フォーム作成のためのXSLTスタイルシートは次のとおりです。
<xsl:stylesheet version="1.1" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:dt="urn:schemas-microsoft-com:datatypes"> <xsl:param name="action"/> <xsl:param name="selectedEntity"/> <xsl:template match="entity"> <TABLE BORDER="0" CELLSPACING="0" CELLPADDING="1" WIDTH="100%"> <xsl:for-each select="*"> <xsl:if test="name() != 'contents'"> <TR> <TD CLASS="dataLabel" STYLE="border-right: 1px solid black; border-bottom: 1px solid black;"> <xsl:value-of select="name()"/> </TD> <TD STYLE="border-right: 1px solid black; border-bottom: 1px solid black;" WIDTH="100%"> <INPUT CLASS="dataInput" ONFOCUS="document.body.onselectstart = null" ONBLUR="document.body.onselectstart = returnFalse;"> <xsl:attribute name="NAME"> <xsl:value-of select="name()"/> </xsl:attribute> <xsl:if test="$action = 'update'"> <xsl:attribute name="VALUE"> <xsl:value-of select="."/> </xsl:attribute> </xsl:if> </INPUT> </TD> </TR> </xsl:if> </xsl:for-each> <TR> <TD STYLE="padding-right: 0px;"/> <TD STYLE="padding-top: 0px;padding-left: 0px;"> <TABLE BORDER="0" CELLSPACING="0" CELLPADDING="0"> <TR> <TD> <INPUT TYPE="button" CLASS="buttonOff" NAME="Save" VALUE="Save" ONFOCUS="this.blur();" ONMOUSEOVER="swapClass(this, 'buttonOver')" ONMOUSEOUT="swapClass(this, 'buttonOff')"> <xsl:attribute name="ONCLICK"> <xsl:value-of select="$action"/> Entity('<xsl:value-of select="$selectedEntity"/>') </xsl:attribute> </INPUT> </TD> <TD STYLE="padding-left: 2px;"> <INPUT TYPE="button" CLASS="buttonOff" NAME="Cancel" VALUE="Cancel" ONFOCUS="this.blur();" ONMOUSEOVER="swapClass(this, 'buttonOver')" ONMOUSEOUT="swapClass(this, 'buttonOff')" ONCLICK="content.innerHTML = '';"/> </TD> </TR> </TABLE> </TD> </TR> </TABLE> </xsl:template> </xsl:stylesheet>
このXSLTスタイルシートは、action
とselectedEntity
という2つのパラメータを受け付けます。action
パラメータで有効な値は"insert"と"update"の2つです。selectedEntity
パラメータはエンティティID値を受け取ります。
挿入/更新表示関数
この関数は、action
パラメータ値として"insert"または"update"を受け取ります。その後、どのエンティティに対して挿入または更新が行われるのかをselectedEntity
変数から判断し、上記スタイルシートを使って適切なフォームをレンダリングします。
function insertUpdateDisplay(action) { var xslDoc var xslTemplate; var xslProc; var entity; xslDoc = new ActiveXObject('MSXML2.FreeThreadedDOMDocument') xslDoc.async = false; xslTemplate = new ActiveXObject('MSXML2.XSLTemplate') xslDoc.load("admin/insertUpdate.xslt"); xslTemplate.stylesheet = xslDoc; xslProc = xslTemplate.createProcessor(); entity = xmlDoc.documentElement.selectSingleNode( "//entity[@id='" + selectedEntity +"']"); xslProc.input = entity; xslProc.addParameter("action", action); xslProc.addParameter("selectedEntity", selectedEntity); xslProc.transform(); content.innerHTML = xslProc.output; }
挿入動作
この関数は、「親になるエンティティ」のIDを受け取り、それを複製します。作成された子は、親のコンテンツコレクションに追加されます。
function insertEntity(parentEntityID) { var entity; var newEntity; var element; var attribute; var xslDoc; var i; xslDoc = new ActiveXObject('MSXML2.FreeThreadedDOMDocument') xslDoc.async = false; xslDoc.load("admin/tree.xslt"); entity = xmlDoc.documentElement.selectSingleNode( "//entity[@id='" + parentEntityID +"']"); newEntity = xmlDoc.createElement("entity"); attribute = xmlDoc.createAttribute("id"); attribute.text = document.uniqueID; newEntity.attributes.setNamedItem(attribute); for(i=0; i < entity.childNodes.length; i++) { element = xmlDoc.createElement(entity.childNodes(i).baseName); if(entity.childNodes(i).baseName != "contents") { element.text = eval(entity.childNodes(i).baseName + ".value") } newEntity.appendChild(element) } entity.selectSingleNode("contents").appendChild(newEntity); document.all[parentEntityID].insertAdjacentHTML( "beforeEnd", newEntity.transformNode(xslDoc)); document.all[parentEntityID].lastChild.style.display = "block"; if(document.all[parentEntityID].open == "false") { document.all[parentEntityID].onclick(); } saveXML(); }
更新動作
この関数は、更新を要するエンティティのIDを受け取り、各エンティティをループ処理しながら、最新フォームに基づいて値を更新します。エンティティXMLの更新が終わったら、それを再変換し、ブラウザDOMにある現在のノードと置き換えます。
function updateEntity(entityID) { var entity; var xslDoc; var container; var i; xslDoc = new ActiveXObject('MSXML2.FreeThreadedDOMDocument') xslDoc.async = false; xslDoc.load("admin/tree.xslt"); entity = xmlDoc.documentElement.selectSingleNode( "//entity[@id='" + entityID +"']"); for(i=0; i < entity.childNodes.length; i++) { if(entity.childNodes(i).baseName != "contents") { entity.childNodes(i).text = eval( entity.childNodes(i).baseName + ".value") } } container = document.createElement("DIV"); container.innerHTML = entity.transformNode(xslDoc) container.childNodes(0).style.display = "block"; document.all[entityID].swapNode(container.childNodes(0)); container = null; saveXML(); }
改名機能
ユーザーがエンティティの「Rename」オプションを選択すると、renameEntityBegin()
関数が呼び出されます。この関数は、どのエンティティの改名が要求されているのかをselectedEntity
変数によって判断します。
まず、当該エンティティの現在名を表示しているブラウザオブジェクトをname
変数で参照します。参照できたら、次にそのcontentEditable
プロパティをtrue
に設定します。これにより、ユーザーがこのスペースに自由に文字をタイプできる状態になります。
さらに、この要素にフォーカスを置き、カーソルを名前の先頭に移動させます。キーストロークを制限してテキストの選択を許可するためのイベントを発生させると、ユーザーによるエンティティの改名が可能になります。
function renameEntityBegin() { var name; name = document.all[selectedEntity + "name"] name.contentEditable = true; name.focus(); name.style.cursor = "text"; name.onkeypress = function anonymous() { renameKeyPress(selectedEntity) }; name.onblur = function anonymous() { renameEntityEnd(selectedEntity) }; name.onselectstart = null; document.all[selectedEntity].onclick = null; }
ユーザーが[Enter]キーを押すか、onBlur
イベントを発生させると、renameEntityEnd
メソッドが呼び出されます。
このメソッドは、改名すべきエンティティのIDを受け取り、XML DOM(ドキュメントオブジェクトモデル)とブラウザDOMの両方に適切な変更を加えます。
function renameEntityEnd(entityID) { var entity; var name; entity = xmlDoc.documentElement.selectSingleNode( "//entity[@id='" + entityID +"']"); name = document.all[entityID + "name"] name.style.cursor = "hand"; name.contentEditable = false; name.onselectstart = function anonymous() { return false }; entity.selectSingleNode("description").text = name.innerText; document.all[entityID].onclick = function anonymous() { clickOnEntity(document.all[entityID]) }; document.body.onselectstart = returnFalse; saveXML(); }
削除機能
deleteEntity()
関数は、削除すべきエンティティのIDを受け取ります。そのエンティティに子が含まれていないかどうか確認し、万一、子が含まれている場合はエラーを表示します。
子が含まれていない場合は、XML DOMとブラウザDOMの両方から当該エンティティノードを取り除きます。
function deleteEntity() { var entity; entity = xmlDoc.documentElement.selectSingleNode( "//entity[@id='" + selectedEntity +"']"); if(entity.selectSingleNode("contents").childNodes.length > 0) { displayError("You cannot remove an entity that contains children." + "First remove all children." ., 8000); } else { entity = entity.parentNode.removeChild(entity) document.all[selectedEntity].removeNode(true) saveXML(); } }
ユーザーのリダイレクト
本稿の新版フォルダツリーには簡単なリダイレクトメソッドが含まれていて、文字列型のURLパラメータを受け付けます。ツリーXMLファイルの内部からこのリダイレクトメソッドを呼び出してください。図3では、リダイレクトメソッドを呼び出すonClick
要素がハイライトされています。
図4では、「tree.xslt」テンプレート内で変換時にonClick
イベントを挿入する行がハイライトされています。
データベースとのインターフェイス
データベースとのインターフェイスが必要だという方は、init()
、insertEntity()
、updateEntity()
、deleteEntity()
、renameEntityEnd()
の各メソッドでXMLHTTPオブジェクトを使用したらどうでしょうか。これらのメソッドを使ってWebサービスにアクセスし、要求された操作の実行に必要なXMLだけを取得または送信するようにします。
この方法なら、サーバーにかかる負荷が大幅に減少します。サーバーとの間を往復してツリー全体を取得し、ツリー全体を描画し直す代わりに、データ更新のための簡単な呼び出しを舞台裏で1回行い、目的の操作に必要なクライアントブラウザ部分だけを更新すれば済みます。
終わりに
本稿の内容が、Webアプリケーションインターフェイスの質的向上に役立てば幸いです。質問・コメント・提案があれば、筆者紹介にあるアドレスまで遠慮なくメールをお送りください。
本連載のその他の記事
- パート1:高度なUIデザイン
- パート2:カスタムコンテキストメニューの作成
- パート4:フォルダツリーのドラッグ&ドロップ
- パート5:プログレスバーの作成
- パート6:プログレスバーの使用
- パート7:フォルダツリーの関係線
謝辞
Lee McGrawとRichard Spencerがこの原稿に目を通し、表現と内容の両方をチェックしてくれました。2人に感謝します。