blob: 5cdc433c9da2c0de8ba86c16734cfb03e5312085 [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.renderkit.behavior;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.faces.FacesException;
import javax.faces.component.UIComponent;
import javax.faces.component.behavior.ClientBehavior;
import javax.faces.component.behavior.ClientBehaviorContext;
import javax.faces.context.FacesContext;
import javax.faces.event.PhaseId;
import javax.faces.render.ClientBehaviorRenderer;
import org.apache.commons.lang.StringUtils;
import org.apache.myfaces.html5.behavior.DropTargetBehavior;
import org.apache.myfaces.html5.event.DropEvent;
import org.apache.myfaces.html5.renderkit.util.BehaviorScriptUtils;
import org.apache.myfaces.html5.renderkit.util.ClientBehaviorEvents;
import org.apache.myfaces.html5.renderkit.util.Html5RendererUtils;
/**
* Renderer of the fx:dropTarget behavior.
*
* @author Ali Ok
*
*/
public class DropTargetBehaviorRenderer extends ClientBehaviorRenderer
{
private static final Logger log = Logger.getLogger(DropTargetBehaviorRenderer.class.getName());
/**
* Mime type to define the source of the drag. If Javascript transfer data has a data set with this key, than we can
* assume the source of the drag is a MyFaces Html5 component.
*/
public static final String DEFAULT_MYFACES_MIME_TYPE = "text/x-myfaces-html5-dnd-source";
/**
* On a succesful drop, types of the data sent to server will be post to server with this key.
*/
private static final String MYFACES_DND_FOUND_MIME_TYPES_KEY = "org.apache.myfaces.dnd.foundMimeTypes";
/**
* Mime type of the param sent to server. Param defined in fx:dragSource is passed to drag&drop events with this key
* and sent to server with this key.
*/
private static final String MYFACES_HTML5_DND_PARAM_MIME_TYPE = "text/x-myfaces-html5-dnd-param";
private static Set<String> ALLOWED_ACTIONS = new HashSet<String>(Arrays.asList("copy", "move", "link", "copyLink",
"copyMove", "linkMove", "all", "none"));
private static final String ACCEPT_ALL_MIME_TYPES = "*";
@Override
public void decode(FacesContext context, UIComponent component, ClientBehavior behavior)
{
if (!(behavior instanceof DropTargetBehavior))
{
throw new FacesException("The behavior must be an instance of DropTargetBehavior");
}
super.decode(context, component, behavior);
DropTargetBehavior dropTargetBehavior = (DropTargetBehavior) behavior;
// XXX: do we need a disabled attr on dropTargetBehavior?
// if (dropTargetBehavior.isDisabled() || !component.isRendered()) {
if (!component.isRendered())
{
return;
}
dispatchBehaviorEvent(context, component, dropTargetBehavior);
}
/**
* Dispatches the {@link DropEvent} with appropriate parameter and data information.
*/
protected void dispatchBehaviorEvent(FacesContext context, UIComponent component,
DropTargetBehavior dropTargetBehavior)
{
Map<String, String> requestParameterMap = context.getExternalContext().getRequestParameterMap();
// get value of param set in fx:dragSource
String param = requestParameterMap.get(MYFACES_HTML5_DND_PARAM_MIME_TYPE);
if (param == null || param.isEmpty())
param = null;
// get other data values with accepted mime types
Map<String, String> dropDataMap = null;
String strReceivedDataMimeTypes = requestParameterMap.get(MYFACES_DND_FOUND_MIME_TYPES_KEY);
if (strReceivedDataMimeTypes != null && !strReceivedDataMimeTypes.isEmpty())
{
dropDataMap = new HashMap<String, String>();
//TODO: allow multiple values for mime-types
//try with link DnDs
String[] receivedDataMimeTypes = Html5RendererUtils.resolveStrings(strReceivedDataMimeTypes);
for (String mimeType : receivedDataMimeTypes)
{
if (mimeType.equals(MYFACES_HTML5_DND_PARAM_MIME_TYPE))
// param is set already, pass
continue;
String data = requestParameterMap.get(mimeType);
if (data != null && !data.isEmpty())
{
dropDataMap.put(mimeType, data);
}
}
}
DropEvent event = new DropEvent(component, dropTargetBehavior, dropDataMap, param);
// XXX: do we need immediate stuff on drop event?
// PhaseId phaseId = dropTargetBehavior.isImmediate() || isComponentImmediate(component) ?
// PhaseId.APPLY_REQUEST_VALUES :
// PhaseId.INVOKE_APPLICATION;
event.setPhaseId(PhaseId.INVOKE_APPLICATION);
component.queueEvent(event);
}
@Override
public String getScript(ClientBehaviorContext behaviorContext, ClientBehavior behavior)
{
if (!(behavior instanceof DropTargetBehavior))
{
throw new FacesException("Behavior is not a DropTargetBehavior");
}
String eventName = behaviorContext.getEventName();
if (eventName.equals(ClientBehaviorEvents.DRAGENTER_EVENT)
|| eventName.equals(ClientBehaviorEvents.DRAGOVER_EVENT))
{
return _getDragEnterOrOverScript((DropTargetBehavior) behavior);
}
else if (eventName.equals(ClientBehaviorEvents.DROP_EVENT))
{
return _getDropScript(behaviorContext, (DropTargetBehavior) behavior);
}
else
{
if (log.isLoggable(Level.WARNING))
log.warning("Event "
+ eventName
+ " is not one of "
+ Arrays.toString(new String[]
{
ClientBehaviorEvents.DRAGENTER_EVENT, ClientBehaviorEvents.DRAGOVER_EVENT,
ClientBehaviorEvents.DROP_EVENT
}) + ". Ignoring the script.");
return null;
}
}
private String _getDragEnterOrOverScript(DropTargetBehavior behavior)
{
String action = behavior.getAction() != null ? behavior.getAction().toString() : null;
String[] types = Html5RendererUtils.resolveStrings(behavior.getTypes());
String[] acceptMimeTypes = _resolveAcceptMimeTypes(behavior);
// it is better to check the action is one of allowed, isn't it? So, if user types 'cpy' instead of 'copy',
// he/she will easily understand what is wrong. Other way, he/she has to watch the browser's javascript console.
_checkAction(action);
String jsAction = BehaviorScriptUtils.convertToSafeJavascriptLiteral(action);
String jsTypes = BehaviorScriptUtils.convertToSafeJavascriptLiteralArray(types);
String jsAcceptMimeTypes = BehaviorScriptUtils.convertToSafeJavascriptLiteralArray(acceptMimeTypes);
// sample:: return dragEnterOrOver(event,'move',['firstdropTargetType'], ['text/x-myfaces-html5-dnd-source']);
String format = "return myfaces.html5.dnd.dragEnterOrOver(event, %s, %s, %s);";
String script = String.format(format, jsAction, jsTypes, jsAcceptMimeTypes);
return script;
}
private String _getDropScript(ClientBehaviorContext behaviorContext, DropTargetBehavior behavior)
{
String sourceId = behaviorContext.getSourceId();
String[] rerender = Html5RendererUtils.resolveStrings(behavior.getRerender());
String[] acceptMimeTypes = _resolveAcceptMimeTypes(behavior);
String jsSourceId = (sourceId == null) ? "this" : BehaviorScriptUtils.convertToSafeJavascriptLiteral(sourceId);
String jsAcceptMimeTypes = BehaviorScriptUtils.convertToSafeJavascriptLiteralArray(acceptMimeTypes);
String jsRerender = BehaviorScriptUtils.convertToSpaceSeperatedJSLiteral(rerender);
// sample:: return myfaces.html5.dnd.drop(event, 'drop_zone', '@this someId',
// ['text/x-myfaces-html5-dnd-source','text/plain']);
String format = "return myfaces.html5.dnd.drop(event, %s, %s, %s);";
String script = String.format(format, jsSourceId, jsRerender, jsAcceptMimeTypes);
return script;
}
private String[] _resolveAcceptMimeTypes(DropTargetBehavior behavior) {
String[] acceptMimeTypes;
if(behavior.getAcceptMimeTypes()!=null && ACCEPT_ALL_MIME_TYPES.equals(behavior.getAcceptMimeTypes())){
acceptMimeTypes = new String[]{ACCEPT_ALL_MIME_TYPES};
}
else
{
acceptMimeTypes = Html5RendererUtils.resolveStrings(behavior.getAcceptMimeTypes(), new String[]
{
DEFAULT_MYFACES_MIME_TYPE
});
}
return acceptMimeTypes;
}
private void _checkAction(String action)
{
if (action == null || action.isEmpty())
return;
if (ALLOWED_ACTIONS.contains(action))
return;
else
throw new FacesException("Action of dropTarget is not one of allowed values "
+ Arrays.toString(ALLOWED_ACTIONS.toArray(new String[0])) + " but " + action);
}
}