削除機能を実装する

削除するには、削除用パスワードとの照合を行います。
削除ページ(DeletePage)を作る事からはじめますが、メッセージの表示などは今まで作ったコンポーネントを使い回しましょう。
DeletePageには、削除対象のメッセージオブジェクトをバインドします。

DeletePage.html

<html>
	<head><title>DeletePage</title></head>
	<body>
   		<div align="center">
			以下の投稿を削除します<br/><br/>
		   	<span jwcid="messageComponent"/>
			<span jwcid="errorConditional">
				<span jwcid="errorMessage"/>
			</span>
			<form jwcid="@Form">
			削除用パスワード : <input type="password" jwcid="userPass" size="4" maxlength="4"/><br/>
			<input type="submit" jwcid="deleteButton" value="削除"/>
			<input type="submit" jwcid="cancelButton" value="キャンセル"/>
			</form>
		</div>
	</body>
</html>

DeletePage.page

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE page-specification PUBLIC
  "-//Apache Software Foundation//Tapestry Specification 3.0//EN"
  "http://jakarta.apache.org/tapestry/dtd/Tapestry_3_0.dtd">

<page-specification class="bbs.view.DeletePage">

	<property-specification name="userPass" type="java.lang.String" persistent="no"/>

    <component id="messageComponent" type="MessageComponent">
		<binding name="message" expression="deleteMessage"/>
	</component>
	
	<component id="errorConditional" type="Conditional">
		<binding name="condition" expression="passwordError"/>
	</component>
    
	<component id="errorMessage" type="Insert">
		<binding name="value" expression="errorMessage"/>
	</component>
	
	<component id="userPass" type="TextField">
		<binding name="value" expression="userPass"/>
		<binding name="hidden" expression="true"/>
	</component>
	
	<component id="deleteButton" type="Submit">
		<binding name="listener" expression="listeners.deleteAction"/>
	</component>

	<component id="cancelButton" type="Submit">
		<binding name="listener" expression="listeners.cancelAction"/>
	</component>
    
</page-specification>

今まで作ってきたコンポーネントからさほど代わり映えがありませんが、このページではConditionalという新しいコンポーネントを使用してみました。
ConditionalとはTapestry標準のコンポーネントで、WebObjects使いの方ならすぐにピンと来るはずです。そうです。WOConditionalそのものです(笑)。
Strutsにもタグがあったと思いますが、似たようなものです。いわゆる条件分岐になります。
バインドされたconditionの結果がtrueの場合に、Conditionalで囲まれた部分が表示される事になります。
上記のケースでは、パスワードが入力されていなかったり、合っていなかったりした場合にエラーメッセージが表示されるという寸法です。
それから、サブミットボタンが2つ用意されています。削除ボタンとキャンセルボタンです。
キャンセルボタンは何もしないでもとのページに戻るだけですから、フォームにする必要も無いのですが、ボタンも簡単に複数個入れる事ができるよ、という事を示したかっただけです・・・Strutsなんかだと、なかなかそうも行かないですから・・・


では、実際のコーディングの方をやってみたいと思います。

DeletePage.java

package bbs.view;

import org.apache.log4j.Level;
import org.apache.tapestry.IRequestCycle;
import org.apache.tapestry.html.BasePage;
import org.objectstyle.cayenne.access.DataContext;

import bbs.data.Message;

/**
 * 削除ページ
 * 
 * @author toolkit
 * @version $Revision$
 */
public abstract class DeletePage extends BasePage {

	private Message deleteMessage;
	private String errorMessage;

	protected void initialize() {
		super.initialize();
		this.errorMessage = null;
	}

	public abstract String getUserPass();
	public abstract void setUserPass(String userPass);

	public Message getDeleteMessage() {
		return this.deleteMessage;
	}

	public void setDeleteMessage(Message deleteMessage) {
		this.deleteMessage = deleteMessage;
	}


	public boolean isPasswordError() {
		if (errorMessage == null) return false;
		return true;
	}

	public String getErrorMessage() {
		return this.errorMessage;
	}

	public void cancelAction(IRequestCycle rc) {
		TestPage nextPage = (TestPage) rc.getPage("TestPage");
		rc.activate(nextPage);
	}

	public void deleteAction(IRequestCycle rc) {
		if (checkPassword()) {
			Visit visit = (Visit) getVisit();
			DataContext context = visit.getDataContext();
			context.deleteObject(this.deleteMessage);
			context.commitChanges(Level.WARN);
			TestPage nextPage = (TestPage) rc.getPage("TestPage");
			rc.activate(nextPage);
		}
	}

	private boolean checkPassword() {
		if (getUserPass() == null || getUserPass().length() == 0) {
			this.errorMessage = "パスワードが入力されていません。";
			return false;
		}

		String pass = this.deleteMessage.getUserPass();
		if (pass != null) {
			if (pass.equals(getUserPass())) {
				this.errorMessage = null;
				return true;
			}
		}

		this.errorMessage = "パスワードが違います";
		return false;
	}
}

削除ボタンが押された時には、deleteActionが呼ばれます。
このアクションの中で、checkPasswordメソッドを読んでいます。この中では、パスワードが入力されているかどうか、入力されていた場合、合致するかどうかチェックします。
このチェックに合格したら、実際にデータベースから削除する作業を行い、元のページに戻ります。
ON DELETE CASCADEしているので、削除対象メッセージが親の場合、子も一緒に削除されるはずです。
checkPasswordメソッドの中では、テストに合格しない場合にthis.errorMessageにエラーメッセージを入れる事をしています。
Conditionalが参照する、isPasswordErrorメソッドは、このエラーメッセージがあるかないかを見てbooleanを返す仕様にしています。
したがって、このページが初期化された時は必ずerrorMessageはnullになっている必要があります。初期化メソッドはinitializeで、この中でerrorMessageをnullにしています。
なぜ、initializeメソッド*1をオーバーライドする必要があるかというと、一般にTapestryでは一度インスタンス化されたコンポーネントやページはキャッシュ*2されるからなんです。オーバーヘッドのかかるインスタンス化を最小限にし、レスポンスを早める効果があります。これはWebObjectsでも同じ事です。
したがって、適切に初期化を行わないと、以前の実行結果が残ってしまっている場合があり、悩むことにもなりますのでご注意下さい。
なお、メッセージはハードコーディングしてますが、実際はやっちゃ駄目ですので(笑)。

*1:WebObjectsではawake()にあたります。

*2:たしかキャッシュされたオブジェクトは定期的に削除されるとどこかのドキュメントで見かけた気がしますが・・・