blob: af022ace5903bffc4eb201b3abe3bc5014f28bc6 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.myfaces.html5.handler;
import java.util.Collection;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.el.MethodExpression;
import javax.faces.FacesException;
import javax.faces.application.Application;
import javax.faces.component.UIComponent;
import javax.faces.component.behavior.Behavior;
import javax.faces.component.behavior.ClientBehaviorHolder;
import javax.faces.context.FacesContext;
import javax.faces.view.facelets.BehaviorConfig;
import javax.faces.view.facelets.ComponentHandler;
import javax.faces.view.facelets.FaceletContext;
import javax.faces.view.facelets.TagAttribute;
import javax.faces.view.facelets.TagException;
import org.apache.myfaces.buildtools.maven2.plugin.builder.annotation.JSFFaceletAttribute;
import org.apache.myfaces.buildtools.maven2.plugin.builder.annotation.JSFFaceletTag;
import org.apache.myfaces.html5.behavior.DropTargetBehavior;
import org.apache.myfaces.html5.component.util.ComponentUtils;
import org.apache.myfaces.html5.event.DropEvent;
import org.apache.myfaces.html5.event.DropListener;
import org.apache.myfaces.html5.renderkit.behavior.DropTargetBehaviorRenderer;
import org.apache.myfaces.html5.renderkit.util.ClientBehaviorEvents;
/*
* Facelets tag handler for DropTargetBehavior.
*
*
* DO NOT JAVADOC here since we don't want this doc to show up in Facelets doc.
*/
@JSFFaceletTag(name = "fx:dropTarget", behaviorClass="org.apache.myfaces.html5.behavior.DropTargetBehavior")
public class DropTargetBehaviorHandler extends javax.faces.view.facelets.BehaviorHandler
{
private static final Logger log = Logger.getLogger(DropTargetBehaviorHandler.class.getName());
/**
* Action to allow for drop operation. Can be one of below:
* <ul>
* <li>copy: A copy of the source item may be made at the new location.</li>
* <li>move: An item may be moved to a new location.</li>
* <li>link: A link may be established to the source at the new location.</li>
* <li>copyLink: A copy or link operation is permitted.</li>
* <li>copyMove: A copy or move operation is permitted.</li>
* <li>linkMove: A link or move operation is permitted.</li>
* <li>all: All operations are permitted.</li>
* <li>none: The item may not be dropped.</li>
* </ul>
* <br/>
*
* If nothing is specified, any action will be accepted. Action is set by the hx:dragSource behavior, when the
* element is generated by a MyFaces-Html5 component that has hx:dragSource behavior. For other elements, action is
* set by the browser, and can be adjusted by pressing the modifier keys.
*
*/
@JSFFaceletAttribute(name = "action", className = "javax.el.ValueExpression", deferredValueType = "java.lang.String")
private final TagAttribute _action;
/**
* The type of the drop target. Can be comma separated set or String[] or Collection&lt;String&gt;. <br/>
* If defined, drags from elements that are generated by MyFaces-Html5 components with hx:dragSource behavior, will
* be filtered. The drag will be accepted if dropTargetTypes of hx:dragSource is one of the allowed. For the drags
* that are originated from other elements, this property is ignored. Please see acceptMimeTypes property for
* accepting/rejecting drags from non-MyFaces-Html5 components.
*/
@JSFFaceletAttribute(name = "types", className = "javax.el.ValueExpression", deferredValueType = "java.lang.Object")
private final TagAttribute _types;
/**
* If this property is set, only content dropped into this drop zone with defined mime type will be accepted and
* sent to server-side drop listener. Can be comma separated set or String[] or Collection&lt;String&gt;. <br/>
* <br/>
* HTML5 DnD allows us to drop anything into drop zone : files from desktop, images on some other document, etc. So
* this property is a filter. If value is "*", any content dropped into this zone will be accepted. <br/>
* <br/>
* All type info and data of dropped stuff will be sent to dropListener. For example, if value of this property is
* "*" and we drop some image from any Html page(even not generated by JSF), dropListener will be triggered with the
* following data:
* <code>
* &lt;table border="1"&gt;
* &lt;tr&gt;
* &lt;td&gt;content-type&lt;/td&gt;
* &lt;td&gt;value&lt;/td&gt;
* &lt;/tr&gt;
* &lt;tr&gt;
* &lt;td&gt;text/uri-list&lt;/td&gt;
* &lt;td&gt;http://example.org/someImage.png&lt;/td&gt;
* &lt;/tr&gt;
* &lt;tr&gt;
* &lt;td&gt;Text&lt;/td&gt;
* &lt;td&gt;http://example.org/someImage.png&lt;/td&gt;
* &lt;/tr&gt;
* &lt;tr&gt;
* &lt;td&gt;text/plain&lt;/td&gt;
* &lt;td&gt;http://example.org/someImage.png&lt;/td&gt;
* &lt;/tr&gt;
* &lt;tr&gt;
* &lt;td&gt;URL&lt;/td&gt;
* &lt;td&gt;http://example.org/someImage.png&lt;/td&gt;
* &lt;/tr&gt;
* &lt;/table&gt;
* </code>
* <br/>
*
* Mime type is "text/x-myfaces-html5-dnd-source" for drag and drop events that is generated by MyFaces-Html5 components
* and that mime type is defined at {@link DropTargetBehaviorRenderer#DEFAULT_MYFACES_MIME_TYPE}. Default value of
* this property is "text/x-myfaces-html5-dnd-source", thus only MyFaces generated components can be dropped into
* the drop target.
*/
@JSFFaceletAttribute(name = "acceptMimeTypes", className = "javax.el.ValueExpression", deferredValueType = "java.lang.Object")
private final TagAttribute _acceptMimeTypes;
/**
* Space separated ids of components to rerender. <br/>
* Value of this property will be passed through to jsf.ajax.request, thus semantics is same with jsf.ajax.request
* and f:ajax. Just like those, @all, @this etc. can be used.
*/
@JSFFaceletAttribute(name = "rerender", className = "javax.el.ValueExpression", deferredValueType = "java.lang.Object")
private final TagAttribute _rerender;
/**
* Drop listener to trigger when a successful drop event is happened into this drop target. <br/>
* Listener method must have a signature of :
* <code>public void m(org.apache.myfaces.html5.event.DropEvent evt) throws javax.faces.event.AbortProcessingException</code>
* <br/>
* In the listener, application can get the parameter sent and other data sent.
*/
@JSFFaceletAttribute(name = "dropListener", className = "javax.el.MethodExpression", deferredMethodSignature = "public void m(org.apache.myfaces.html5.event.DropEvent evt) throws javax.faces.event.AbortProcessingException")
private final TagAttribute _dropListener;
public DropTargetBehaviorHandler(BehaviorConfig config)
{
super(config);
_action = getAttribute("action");
_types = getAttribute("types");
_acceptMimeTypes = getAttribute("acceptMimeTypes");
_rerender = getAttribute("rerender");
_dropListener = getAttribute("dropListener");
}
@Override
public void apply(FaceletContext faceletContext, UIComponent parent)
{
if (!ComponentHandler.isNew(parent))
{
if (log.isLoggable(Level.FINE))
log.fine("Component" + ComponentUtils.getPathToComponent(parent)
+ " is not new, thus return without any operation.");
return;
}
if (parent instanceof ClientBehaviorHolder)
{
ClientBehaviorHolder holder = _getClientBehaviorHolder(parent);
FacesContext context = faceletContext.getFacesContext();
Application app = context.getApplication();
String behaviorId = getBehaviorId();
Behavior behavior = app.createBehavior(behaviorId);
if (!(behavior instanceof DropTargetBehavior))
{
throw new FacesException("Behavior is not a DropTargetBehavior");
}
// manually added all of the properties, so no need for this:
// setAttributes(faceletContext, behavior);
DropTargetBehavior dropTargetBehavior = (DropTargetBehavior) behavior;
if (_dropListener != null)
{
MethodExpression expr = _dropListener.getMethodExpression(faceletContext, Void.TYPE, new Class<?>[]
{
DropEvent.class
});
dropTargetBehavior.addDropTargetBehaviorListener(new DropListener(expr));
}
// see https://issues.apache.org/jira/browse/MYFACES-2616
// see the thread http://www.mail-archive.com/dev@myfaces.apache.org/msg46764.html
// using the same approach in DropSourceBehavior too... see there for explanation!
if (_action != null)
{
if (_action.isLiteral())
{
dropTargetBehavior.setAction(_action.getValue(faceletContext));
}
else
{
dropTargetBehavior.setValueExpression("action", _action.getValueExpression(faceletContext,
String.class));
}
}
if (_types != null)
{
if (_types.isLiteral())
{
dropTargetBehavior.setTypes(_types.getObject(faceletContext));
}
else
{
dropTargetBehavior.setValueExpression("types", _types.getValueExpression(faceletContext,
Object.class));
}
}
if (_acceptMimeTypes != null)
{
if (_acceptMimeTypes.isLiteral())
{
dropTargetBehavior.setAcceptMimeTypes(_acceptMimeTypes.getObject(faceletContext));
}
else
{
dropTargetBehavior.setValueExpression("acceptMimeTypes", _acceptMimeTypes.getValueExpression(
faceletContext, Object.class));
}
}
if (_rerender != null)
{
if (_rerender.isLiteral())
{
dropTargetBehavior.setRerender(_rerender.getObject(faceletContext));
}
else
{
dropTargetBehavior.setValueExpression("rerender", _rerender.getValueExpression(faceletContext,
Object.class));
}
}
holder.addClientBehavior(ClientBehaviorEvents.DRAGENTER_EVENT, dropTargetBehavior);
holder.addClientBehavior(ClientBehaviorEvents.DRAGOVER_EVENT, dropTargetBehavior);
holder.addClientBehavior(ClientBehaviorEvents.DROP_EVENT, dropTargetBehavior);
}
//XXX: try in a composite component!
/*
* else if (UIComponent.isCompositeComponent(parent)) { // COPIED FROM AjaxHandler! // It is supposed that for
* composite components, this tag should // add itself as a target, but note that on whole api does not exists
* // some tag that expose client behaviors as targets for composite // components. In RI, there exists a tag
* called composite:clientBehavior, // but does not appear on spec or javadoc, maybe because this could be //
* understand as an implementation detail, after all there exists a key // called
* AttachedObjectTarget.ATTACHED_OBJECT_TARGETS_KEY that could be // used to create a tag outside jsf
* implementation to attach targets. CompositeComponentResourceTagHandler.addAttachedObjectHandler(parent,
* this); }
*/
}
private ClientBehaviorHolder _getClientBehaviorHolder(UIComponent parent)
{
if (!(parent instanceof ClientBehaviorHolder))
{
throw new TagException(getTag(),
"DropTargetBehavior must be attached to a ClientBehaviorHolder parent. Component "
+ ComponentUtils.getPathToComponent(parent) + "is not a ClientBehaviorHolder");
}
ClientBehaviorHolder holder = (ClientBehaviorHolder) parent;
_checkEvent(holder, ClientBehaviorEvents.DRAGENTER_EVENT);
_checkEvent(holder, ClientBehaviorEvents.DRAGOVER_EVENT);
_checkEvent(holder, ClientBehaviorEvents.DROP_EVENT);
return holder;
}
private void _checkEvent(ClientBehaviorHolder holder, String eventName)
{
Collection<String> eventNames = holder.getEventNames();
if (!eventNames.contains(eventName))
{
StringBuilder message = new StringBuilder();
message.append("Unable to attach DropTargetBehavior. ");
message.append("DropTargetBehavior may only be attached to ");
message.append("ClientBehaviorHolders that support the '");
message.append(eventName);
message.append("' event. The parent ClientBehaviorHolder "
+ ComponentUtils.getPathToComponent((UIComponent) holder) + " only ");
message.append("supports the following events: ");
for (String supportedEventName : eventNames)
{
message.append(supportedEventName);
message.append(" ");
}
throw new TagException(getTag(), message.toString());
}
}
}