Eclipse+JSF+JPAで作るアプリ(20)―Primefaces ドラッグ&ドロップ

今回は、カート画面でのドラッグ&ドロップです。
借りたい映画のエリアと、カートに戻す映画のエリア2つの領域をドラッグ&ドロップで移動させます。

先に画面を見せます。
f:id:tshix:20150806002123p:plain
f:id:tshix:20150806002132p:plain

  • ドラッグすると、ドロップできるエリアがハイライトされ、このテーマでは薄い青色になります。

f:id:tshix:20150806002406p:plain

  • ドラッグもドロップもしていないと、ハイライトは戻り、薄い灰色になります。

画像は英語版Wikipediaのポスター画像から借用しています。

それでは実際のソースを見ていきます。

xhtmlでのドラッグ&ドロップの定義

cart.xhtmlで定義しています。

先に全体を載せ、細かいパーツごとの説明は後に行います。

	<h:form id="movieCartForm">
		<p:fieldset id="cartField" legend="カートの中の映画">
		<p:outputPanel id="cartPanel">
			<h:outputText value="カートに戻す映画をドロップしてください。"
				rendered="#{empty movieCartView.moviesInCart}"
				style="font-size:24px;" />
			<p:dataGrid id="cartGrid" var="movie"
				value="#{movieCartView.moviesInCart}" columns="5"
				rendered="#{not empty movieCartView.moviesInCart}">
				<p:panel id="cartPnl" header="#{movie.title}"
					style="text-align:center">
					<h:panelGrid columns="1">
						<p:graphicImage url="./resource/image/#{movie.image}"/>
					</h:panelGrid>
				</p:panel>
				
				<p:draggable for="cartPnl" revert="true" handle=".ui-panel-titlebar"
					stack=".ui-panel" />
			</p:dataGrid>
		</p:outputPanel>
		</p:fieldset>
		
		<p:fieldset id="rentField" legend="借りる映画">
		<p:outputPanel id="rentPanel">
			<h:outputText value="借りたい映画をドロップしてください。"
				rendered="#{empty movieCartView.moviesToBeLent}"
				style="font-size:24px;" />
			<p:dataGrid id="rentGrid" var="movie"
				value="#{movieCartView.moviesToBeLent}" columns="3"
				rendered="#{not empty movieCartView.moviesToBeLent}">
				<p:panel id="rentPnl" header="#{movie.title}"
					style="text-align:center">
					<h:panelGrid columns="1">
						<p:graphicImage url="./resource/image/#{movie.image}"/>
					</h:panelGrid>
				</p:panel>
				
				<p:draggable for="rentPnl" revert="true" handle=".ui-panel-titlebar"
					stack=".ui-panel" />
					
			</p:dataGrid>
		</p:outputPanel>
		</p:fieldset>

		<p:droppable for="rentField" tolerance="touch"
			activeStyleClass="ui-state-highlight" datasource="cartGrid">
			<p:ajax listener="#{movieCartView.onDropToRent}"
				update="cartPanel rentPanel" />
		</p:droppable>

		<p:droppable for="cartField" tolerance="touch"
			activeStyleClass="ui-state-highlight" datasource="rentGrid">
			<p:ajax listener="#{movieCartView.onDropToCart}"
				update="cartPanel rentPanel" />
		</p:droppable>
		
		<h:panelGrid columns="2">
			<p:commandButton id="rent" value="映画を借りる" action="#{movieCartView.rentMovies}" update="@form"/>
			<p:commandButton id="back" value="戻る" action="#{movieCartView.back}" update="@form"/>
		</h:panelGrid>
		<p:messages />
	</h:form>
ドロップを促すガイド
	<h:outputText value="カートに戻す映画をドロップしてください。"
		rendered="#{empty movieCartView.moviesInCart}"
		style="font-size:24px;" />
  • 通常のWebアプリケーションでドラッグ&ドロップできるとは考えないので出したほうがよいでしょう。
  • renderedアトリビュートでリストが空のときだけ表示するようにします。
draggableタグ
	<p:draggable for="cartPnl" revert="true" handle=".ui-panel-titlebar"
		stack=".ui-panel" />
droppableタグ
	<p:droppable for="rentField" tolerance="touch"
		activeStyleClass="ui-state-highlight" datasource="cartGrid">
		<p:ajax listener="#{movieCartView.onDropToRent}"
			update="cartPanel rentPanel" />
	</p:droppable>

javaソース内でのハンドル

Primefaces Showcaseのドラッグ&ドロップは、1つの領域のサンプルしかありませんが、
この例では、2つの領域があります。そのため、ドロップイベントで

  • 自分の領域から出て、自分の領域に戻る

という個所をハンドルする必要があります。

	public void onDropToRent(DragDropEvent ddEvent) {
		Movie movie = ((Movie) ddEvent.getData());

		if (ddEvent.getDragId().startsWith("movieCartForm:cartGrid")
				&& "movieCartForm:cartField".equals(ddEvent.getDropId()))
			return;
		if (ddEvent.getDragId().startsWith("movieCartForm:rentGrid")
				&& "movieCartForm:rentField".equals(ddEvent.getDropId()))
			return;
		if (!moviesToBeLent.contains(movie)) {
			moviesToBeLent.add(movie);
			moviesInCart.remove(movie);
		}
	}

	public void onDropToCart(DragDropEvent ddEvent) {
		Movie movie = ((Movie) ddEvent.getData());

		if (ddEvent.getDragId().startsWith("movieCartForm:cartGrid")
				&& "movieCartForm:cartField".equals(ddEvent.getDropId()))
			return;
		if (ddEvent.getDragId().startsWith("movieCartForm:rentGrid")
				&& "movieCartForm:rentField".equals(ddEvent.getDropId()))
			return;
		if (!moviesInCart.contains(movie)) {
			moviesInCart.add(movie);
			moviesToBeLent.remove(movie);
		}
	}
  • droppableタグ内のajaxタグで指定したメソッドには、引数DragDropEventが渡されます。
  • DragDropEventの、getDragId()と、getDropId()というAPIで、ウジッドのIDを取得できますので、これで、どのコントロールから出てきて、どのコントロールに落とされているかを判別することができます。
  • この例では、同じ領域に落とされている場合は、何もしないでreturnするようにしてあります。

以上です。

リッチコンポーネントを使えば、このように比較的簡単にドラッグ&ドロップを実装できます。