SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

japan.internet.com翻訳記事

XMLとXSLによるフォルダツリーの管理

XMLとXSLを使った高度なUIデザイン:パート3

  • X ポスト
  • このエントリーをはてなブックマークに追加

挿入/更新機能

 挿入と更新はよく似た作業です。どちらも同じデータ群を扱い、同じフォームを使用します。両者の違いは、挿入フォームはブランクで初期化される(つまり、入力要素が空である)のに対し、更新フォームはいくつかの値で初期化されるという点だけです。この類似性を生かし、挿入機能と更新機能を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スタイルシートは、actionselectedEntityという2つのパラメータを受け付けます。actionパラメータで有効な値は"insert"と"update"の2つです。selectedEntityパラメータはエンティティID値を受け取ります。

図1 XSLT変換の結果
図1 XSLT変換の結果

挿入/更新表示関数

 この関数は、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();
}
図2 エンティティの改名
図2 エンティティの改名

削除機能

 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要素がハイライトされています。

図3 onClick要素の所在
図3 onClick要素の所在

 図4では、「tree.xslt」テンプレート内で変換時にonClickイベントを挿入する行がハイライトされています。

図4 onClickイベントの挿入
図4 onClickイベントの挿入

データベースとのインターフェイス

 データベースとのインターフェイスが必要だという方は、init()insertEntity()updateEntity()deleteEntity()renameEntityEnd()の各メソッドでXMLHTTPオブジェクトを使用したらどうでしょうか。これらのメソッドを使ってWebサービスにアクセスし、要求された操作の実行に必要なXMLだけを取得または送信するようにします。

 この方法なら、サーバーにかかる負荷が大幅に減少します。サーバーとの間を往復してツリー全体を取得し、ツリー全体を描画し直す代わりに、データ更新のための簡単な呼び出しを舞台裏で1回行い、目的の操作に必要なクライアントブラウザ部分だけを更新すれば済みます。

終わりに

 本稿の内容が、Webアプリケーションインターフェイスの質的向上に役立てば幸いです。質問・コメント・提案があれば、筆者紹介にあるアドレスまで遠慮なくメールをお送りください。

本連載のその他の記事

謝辞

 Lee McGrawとRichard Spencerがこの原稿に目を通し、表現と内容の両方をチェックしてくれました。2人に感謝します。

この記事は参考になりましたか?

  • X ポスト
  • このエントリーをはてなブックマークに追加
japan.internet.com翻訳記事連載記事一覧

もっと読む

この記事の著者

japan.internet.com(ジャパンインターネットコム)

japan.internet.com は、1999年9月にオープンした、日本初のネットビジネス専門ニュースサイト。月間2億以上のページビューを誇る米国 Jupitermedia Corporation (Nasdaq: JUPM) のニュースサイト internet.comEarthWeb.com からの最新記事を日本語に翻訳して掲載するとともに、日本独自のネットビジネス関連記事やレポートを配信。

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

Joe Slovinski(Joe Slovinski)

1993年以来、Webアプリケーションの開発に継続的に携わる。本稿で紹介したコードについてのお問い合わせはJoe Slovinskiまで。

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

  • X ポスト
  • このエントリーをはてなブックマークに追加
CodeZine(コードジン)
https://codezine.jp/article/detail/240 2006/04/11 19:06

おすすめ

アクセスランキング

アクセスランキング

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

アクセスランキング

アクセスランキング