/*
* Copyright 2009 Sven Meier
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package wicketdnd;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import org.apache.wicket.Component;
import org.apache.wicket.MarkupContainer;
import org.apache.wicket.WicketRuntimeException;
import org.apache.wicket.ajax.AbstractDefaultAjaxBehavior;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.markup.html.IHeaderResponse;
import org.apache.wicket.request.Request;
import org.wicketstuff.jslibraries.JSLib;
import org.wicketstuff.jslibraries.Library;
import org.wicketstuff.jslibraries.VersionDescriptor;
import wicketdnd.util.CollectionFormattable;
import wicketdnd.util.MarkupIdVisitor;
/**
* A target of drops. Can be configured for specific {@link Location}s via CSS
* selectors.
*
* @see #getTypes()
* @see #onDrag(AjaxRequestTarget, Location)
* @see #onDrop(AjaxRequestTarget, Transfer, Location)
*
* @author Sven Meier
*/
public class DropTarget extends AbstractDefaultAjaxBehavior
{
private static final long serialVersionUID = 1L;
private String centerSelector = Transfer.UNDEFINED;
private String topSelector = Transfer.UNDEFINED;
private String bottomSelector = Transfer.UNDEFINED;
private String leftSelector = Transfer.UNDEFINED;
private String rightSelector = Transfer.UNDEFINED;
private Set<Operation> operations;
/**
* Create a target for drop.
*
* @param operations
* allowed operations
*
* @see #getOperations()
*/
public DropTarget(Operation... operations)
{
this(Operation.of(operations));
}
/**
* Create a target for drop.
*
* @param operations
* allowed operations
*
* @see #getOperations()
*/
public DropTarget(Set<Operation> operations)
{
this.operations = operations;
}
/**
* Get possible types for a transfer.
*
* @return transfers
* @see Transfer#getType()
*/
public String[] getTypes()
{
return new String[] { Transfer.ANY };
}
/**
* Allow drop on the {@link Anchor#CENTER} of elements matching the given
* selector.
*
* Make sure all matching elements are configured to output their markup id.
*
* @param selector
* element selector
* @see Component#setOutputMarkupId(boolean)
*/
public DropTarget dropCenter(String selector)
{
this.centerSelector = selector;
return this;
}
/**
* Allow drop on the {@link Anchor#TOP} of elements matching the given
* selector.
*
* Make sure all matching elements are configured to output their markup id.
*
* @param selector
* element selector
* @see Component#setOutputMarkupId(boolean)
*/
public DropTarget dropTop(String selector)
{
this.topSelector = selector;
return this;
}
/**
* Allow drop on the {@link Anchor#RIGHT} of elements matching the given
* selector.
*
* Make sure all matching elements are configured to output their markup id.
*
* @param selector
* element selector
* @see Component#setOutputMarkupId(boolean)
*/
public DropTarget dropRight(String selector)
{
this.rightSelector = selector;
return this;
}
/**
* Allow drop on the {@link Anchor#BOTTOM} of elements matching the given
* selector.
*
* Make sure all matching elements are configured to output their markup id.
*
* @param selector
* element selector
* @see Component#setOutputMarkupId(boolean)
*/
public DropTarget dropBottom(String selector)
{
this.bottomSelector = selector;
return this;
}
/**
* Allow drop on the {@link Anchor#LEFT} of elements matching the given
* selector.
*
* Make sure all matching elements are configured to output their markup id.
*
* @param selector
* element selector
* @see Component#setOutputMarkupId(boolean)
*/
public DropTarget dropLeft(String selector)
{
this.leftSelector = selector;
return this;
}
/**
* @see #dropTop(String)
* @see #dropBottom(String)
*/
public DropTarget dropTopAndBottom(String selector)
{
this.topSelector = selector;
this.bottomSelector = selector;
return this;
}
/**
* @see #dropLeft(String)
* @see #dropRight(String)
*/
public DropTarget dropLeftAndRight(String selector)
{
this.leftSelector = selector;
this.rightSelector = selector;
return this;
}
@Override
public final void renderHead(Component component, IHeaderResponse response)
{
super.renderHead(component, response);
JSLib.getHeaderContribution(VersionDescriptor.alwaysLatest(Library.PROTOTYPE)).renderHead(
response);
renderDropHead(response);
}
private void renderDropHead(IHeaderResponse response)
{
response.renderJavaScriptReference(Transfer.JS);
final String id = getComponent().getMarkupId();
String initJS = String.format(
"new wicketdnd.DropTarget('%s','%s',%s,%s,'%s','%s','%s','%s','%s');", id,
getCallbackUrl(), new CollectionFormattable(getOperations()),
new CollectionFormattable(getTypes()), centerSelector, topSelector, rightSelector,
bottomSelector, leftSelector);
response.renderOnDomReadyJavaScript(initJS);
}
/**
* Get supported operations.
*
* @return operations
* @see Transfer#getOperation()
*/
public Set<Operation> getOperations()
{
return operations;
}
@Override
protected final void respond(AjaxRequestTarget target)
{
Request request = getComponent().getRequest();
final String type = readType(request);
final Location location = readLocation(request);
if ("drag".equals(type))
{
onDrag(target, location);
}
else if ("drop".equals(type))
{
try
{
final DragSource source = DragSource.read(getComponent().getPage(), request);
final Transfer transfer = readTransfer(request, source);
source.beforeDrop(request, transfer);
onDrop(target, transfer, location);
source.afterDrop(target, transfer);
}
catch (Reject reject)
{
onRejected(target);
}
}
else
{
throw new WicketRuntimeException("unkown type '" + type + "'");
}
}
private String readType(Request request)
{
return request.getRequestParameters().getParameterValue("type").toString();
}
private Transfer readTransfer(Request request, DragSource source)
{
Operation operation = Operation.valueOf(request.getRequestParameters().getParameterValue(
"operation").toString());
if (!hasOperation(operation) || !source.hasOperation(operation))
{
throw new Reject();
}
List<String> transfers = new ArrayList<String>();
for (String transfer : this.getTypes())
{
transfers.add(transfer);
}
transfers.retainAll(Arrays.asList(source.getTypes()));
if (transfers.size() == 0)
{
throw new Reject();
}
return new Transfer(transfers.get(0), operation);
}
final boolean hasOperation(Operation operation)
{
return getOperations().contains(operation);
}
private Location readLocation(Request request)
{
String id = getComponent().getRequest().getRequestParameters().getParameterValue(
"component").toString();
Component component = MarkupIdVisitor.getComponent((MarkupContainer)getComponent(), id);
Anchor anchor = Anchor.valueOf(request.getRequestParameters().getParameterValue("anchor")
.toString());
return new Location(component, anchor);
}
/**
* Notification that a drag happend over this drop target.
*
* @param target
* initiating request target
* @param location
* the location
*/
public void onDrag(AjaxRequestTarget target, Location location)
{
}
/**
* Notification that a drop happend on this drop target.
*
* The default implementation always rejects the drop.
*
* @param target
* initiating request target
* @param transfer
* the transfer
* @param location
* the location
* @throws Reject
* may reject the drop
*/
public void onDrop(AjaxRequestTarget target, Transfer transfer, Location location)
throws Reject
{
transfer.reject();
}
/**
* Hook method to handle rejected drops. Default implementation does
* nothing.
*
* @param target
* initiating request target
*/
public void onRejected(AjaxRequestTarget target)
{
}
}