| /* |
| * 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.tobago.internal.renderkit.renderer; |
| |
| import org.apache.myfaces.tobago.component.ClientBehaviors; |
| import org.apache.myfaces.tobago.internal.behavior.EventBehavior; |
| import org.apache.myfaces.tobago.internal.component.AbstractUICommand; |
| import org.apache.myfaces.tobago.internal.component.AbstractUIEvent; |
| import org.apache.myfaces.tobago.internal.component.AbstractUIOperation; |
| import org.apache.myfaces.tobago.internal.renderkit.Collapse; |
| import org.apache.myfaces.tobago.internal.renderkit.Command; |
| import org.apache.myfaces.tobago.internal.renderkit.CommandMap; |
| import org.apache.myfaces.tobago.internal.util.RenderUtils; |
| import org.apache.myfaces.tobago.internal.util.StringUtils; |
| import org.apache.myfaces.tobago.util.ComponentUtils; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| import javax.faces.component.ActionSource; |
| import javax.faces.component.EditableValueHolder; |
| import javax.faces.component.UIComponent; |
| import javax.faces.component.behavior.AjaxBehavior; |
| import javax.faces.component.behavior.ClientBehavior; |
| import javax.faces.component.behavior.ClientBehaviorContext; |
| import javax.faces.context.FacesContext; |
| import javax.faces.event.AjaxBehaviorEvent; |
| import javax.faces.event.PhaseId; |
| import java.lang.invoke.MethodHandles; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.List; |
| |
| public class TobagoClientBehaviorRenderer extends javax.faces.render.ClientBehaviorRenderer { |
| |
| private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); |
| |
| /** |
| * In standard JSF this method returns a JavaScript string. Because of CSP, Tobago doesn't render JavaScript |
| * into the HTML content. It transports the return value a bit hacky by {@link CommandMap#storeCommandMap}. |
| * |
| * @return "dummy" string or null, if nothing to do. |
| */ |
| @Override |
| public String getScript(final ClientBehaviorContext behaviorContext, final ClientBehavior behavior) { |
| |
| final FacesContext facesContext = behaviorContext.getFacesContext(); |
| final UIComponent uiComponent = behaviorContext.getComponent(); |
| final ClientBehaviors eventName = ClientBehaviors.valueOf(behaviorContext.getEventName()); |
| |
| //// TBD: is this nice? May be implemented with a JSF behavior? |
| Collapse collapse = createCollapsible(facesContext, uiComponent); |
| |
| String executeIds = null; |
| String renderIds = null; |
| Boolean transition = null; |
| String target = null; |
| String actionId = null; |
| boolean omit = false; |
| final String confirmation = ComponentUtils.getConfirmation(uiComponent); |
| if (behavior instanceof AjaxBehavior) { |
| final AjaxBehavior ajaxBehavior = (AjaxBehavior) behavior; |
| if (ajaxBehavior.isDisabled()) { |
| return null; |
| } |
| final Collection<String> execute = ajaxBehavior.getExecute(); |
| final Collection<String> render = ajaxBehavior.getRender(); |
| final String clientId = uiComponent.getClientId(facesContext); |
| |
| executeIds = ComponentUtils.evaluateClientIds(facesContext, uiComponent, execute.toArray(new String[0])); |
| if (executeIds != null) { |
| executeIds = executeIds + " " + clientId; |
| } else { |
| executeIds = clientId; |
| } |
| if (uiComponent instanceof AbstractUICommand) { // <f:ajax> inside of a command |
| final AbstractUICommand command = (AbstractUICommand) uiComponent; |
| transition = command.isTransition(); |
| target = command.getTarget(); |
| omit = command.isOmit() || StringUtils.isNotBlank(RenderUtils.generateUrl(facesContext, command)); |
| } |
| renderIds = ComponentUtils.evaluateClientIds(facesContext, uiComponent, render.toArray(new String[0])); |
| actionId = clientId; |
| } else if (behavior instanceof EventBehavior) { // <tc:event> |
| final EventBehavior eventBehavior = (EventBehavior) behavior; |
| final AbstractUIEvent event = RenderUtils.getAbstractUIEvent(uiComponent, eventBehavior); |
| |
| if (event != null) { |
| if (!event.isRendered() || event.isDisabled()) { |
| return null; |
| } else { |
| transition = event.isTransition(); |
| target = event.getTarget(); |
| actionId = event.getClientId(facesContext); |
| if (collapse == null) { |
| collapse = createCollapsible(facesContext, event); |
| } |
| omit = event.isOmit() || StringUtils.isNotBlank(RenderUtils.generateUrl(facesContext, event)); |
| } |
| } |
| } else { |
| LOG.warn("Unknown behavior '{}'!", behavior.getClass().getName()); |
| } |
| |
| final Command command = new Command( |
| actionId, |
| transition, |
| target, |
| executeIds, |
| renderIds, |
| null, |
| confirmation, |
| null, |
| collapse, |
| omit); |
| |
| final CommandMap map = new CommandMap(); |
| map.addCommand(eventName, command); |
| CommandMap.storeCommandMap(facesContext, map); |
| |
| // XXX the return value is a string, but we should use a CommandMap |
| return "dummy"; |
| } |
| |
| @Override |
| public void decode(final FacesContext facesContext, final UIComponent component, |
| final ClientBehavior clientBehavior) { |
| if (clientBehavior instanceof AjaxBehavior) { |
| AjaxBehavior ajaxBehavior = (AjaxBehavior) clientBehavior; |
| if (!component.isRendered() || ajaxBehavior.isDisabled()) { |
| return; |
| } |
| |
| dispatchBehaviorEvent(component, ajaxBehavior); |
| } else if (clientBehavior instanceof EventBehavior) { |
| final EventBehavior eventBehavior = (EventBehavior) clientBehavior; |
| final AbstractUIEvent abstractUIEvent = RenderUtils.getAbstractUIEvent(component, eventBehavior); |
| |
| if (!component.isRendered() || abstractUIEvent == null |
| || !abstractUIEvent.isRendered() || abstractUIEvent.isDisabled()) { |
| return; |
| } |
| |
| for (List<ClientBehavior> children : abstractUIEvent.getClientBehaviors().values()) { |
| for (ClientBehavior child : children) { |
| decode(facesContext, component, child); |
| } |
| } |
| dispatchBehaviorEvent(component, eventBehavior); |
| } |
| } |
| |
| private void dispatchBehaviorEvent(final UIComponent component, final ClientBehavior clientBehavior) { |
| final AjaxBehaviorEvent event = new AjaxBehaviorEvent(component, clientBehavior); |
| final boolean isImmediate = isImmediate(clientBehavior, component); |
| event.setPhaseId(isImmediate ? PhaseId.APPLY_REQUEST_VALUES : PhaseId.INVOKE_APPLICATION); |
| component.queueEvent(event); |
| } |
| |
| private boolean isImmediate(final ClientBehavior clientBehavior, final UIComponent component) { |
| if (clientBehavior instanceof AjaxBehavior) { |
| AjaxBehavior ajaxBehavior = (AjaxBehavior) clientBehavior; |
| if (ajaxBehavior.isImmediateSet()) { |
| return ajaxBehavior.isImmediate(); |
| } |
| } else if (clientBehavior instanceof EventBehavior) { |
| EventBehavior eventBehavior = (EventBehavior) clientBehavior; |
| if (eventBehavior.isImmediateSet()) { |
| return eventBehavior.isImmediate(); |
| } |
| } |
| if (component instanceof EditableValueHolder) { |
| return ((EditableValueHolder) component).isImmediate(); |
| } |
| if (component instanceof ActionSource) { |
| return ((ActionSource) component).isImmediate(); |
| } |
| return false; |
| } |
| |
| /** |
| * @deprecated TBD |
| */ |
| @Deprecated |
| public static Collapse createCollapsible(final FacesContext facesContext, final UIComponent component) { |
| //// TBD: is this nice? May be implemented with a JSF behavior? |
| //// BEGIN |
| |
| // XXX too complicated |
| final List<AbstractUIOperation> operations = new ArrayList<>(); |
| for (final UIComponent child : component.getChildren()) { |
| if (AbstractUIOperation.class.isAssignableFrom(child.getClass())) { |
| operations.add((AbstractUIOperation) child); |
| } |
| } |
| |
| Collapse collapse = null; |
| if (operations.size() > 0) { |
| final AbstractUIOperation operation = operations.get(0); |
| final String forId = ComponentUtils.evaluateClientId(facesContext, component, operation.getFor()); |
| collapse = new Collapse(Collapse.Action.valueOf(operation.getName()), forId); |
| } |
| |
| //// TBD: is this nice? |
| //// END |
| return collapse; |
| } |
| |
| } |