/* | |
* 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<String>. <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<String>. <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> | |
* <table border="1"> | |
* <tr> | |
* <td>content-type</td> | |
* <td>value</td> | |
* </tr> | |
* <tr> | |
* <td>text/uri-list</td> | |
* <td>http://example.org/someImage.png</td> | |
* </tr> | |
* <tr> | |
* <td>Text</td> | |
* <td>http://example.org/someImage.png</td> | |
* </tr> | |
* <tr> | |
* <td>text/plain</td> | |
* <td>http://example.org/someImage.png</td> | |
* </tr> | |
* <tr> | |
* <td>URL</td> | |
* <td>http://example.org/someImage.png</td> | |
* </tr> | |
* </table> | |
* </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()); | |
} | |
} | |
} |