You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@myfaces.apache.org by hn...@apache.org on 2018/03/13 18:31:26 UTC
[myfaces-tobago] branch master updated: TOBAGO-1852 Sheet row to
open a popup does not work
This is an automated email from the ASF dual-hosted git repository.
hnoeth pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/myfaces-tobago.git
The following commit(s) were added to refs/heads/master by this push:
new fa9d600 TOBAGO-1852 Sheet row to open a popup does not work
fa9d600 is described below
commit fa9d600c94068b46d3508e88065bd100fb80effd
Author: Henning Noeth <hn...@apache.org>
AuthorDate: Tue Mar 13 19:25:34 2018 +0100
TOBAGO-1852 Sheet row to open a popup does not work
* <tc:event> can now have children of type <f:ajax> and <tc:operation>
* Fix a NPE for merging command maps
* Fix log message when evaluateClientId
* EventBehavior now have an id to identify related <tc:event> component
* 'disabled' attribute of <tc:event> is now supported - does the same as rendered
* A component can now have both <tc:event> and <f:ajax> if only one is enabled
* Better description for the <tc:event> tag.
* Improve demo for behavior
* Add test for behavior.xhtml and sheet-event.xhtml
---
.../myfaces/tobago/facelets/EventHandler.java | 44 ++--
.../tobago/internal/behavior/EventBehavior.java | 9 +
.../tobago/internal/component/AbstractUIEvent.java | 4 +-
.../tobago/internal/renderkit/CommandMap.java | 18 +-
.../internal/renderkit/renderer/RowRenderer.java | 6 +-
.../renderer/TobagoClientBehaviorRenderer.java | 75 ++++--
.../taglib/component/EventTagDeclaration.java | 20 ++
.../myfaces/tobago/internal/util/RenderUtils.java | 93 +++++--
.../apache/myfaces/tobago/util/ComponentUtils.java | 2 +-
.../tobago/example/demo/BehaviorController.java | 19 +-
.../example/demo/BehaviorTestController.java | 213 +++++++++++++++
.../080-sheet/30-event/sheet-event.test.js | 109 ++++++++
.../080-sheet/30-event/sheet-event.xhtml | 10 +-
.../content/30-concept/30-behavior/behavior.xhtml | 46 +++-
.../content/40-test/6500-behavior/behavior.test.js | 292 +++++++++++++++++++++
.../content/40-test/6500-behavior/behavior.xhtml | 205 +++++++++++++++
.../myfaces/tobago/example/demo/QUnitTests.java | 6 +
17 files changed, 1063 insertions(+), 108 deletions(-)
diff --git a/tobago-core/src/main/java/org/apache/myfaces/tobago/facelets/EventHandler.java b/tobago-core/src/main/java/org/apache/myfaces/tobago/facelets/EventHandler.java
index f6c5c41..9e6cb3a 100644
--- a/tobago-core/src/main/java/org/apache/myfaces/tobago/facelets/EventHandler.java
+++ b/tobago-core/src/main/java/org/apache/myfaces/tobago/facelets/EventHandler.java
@@ -23,12 +23,11 @@ import org.apache.myfaces.tobago.component.Attributes;
import org.apache.myfaces.tobago.component.ClientBehaviors;
import org.apache.myfaces.tobago.component.UIEvent;
import org.apache.myfaces.tobago.internal.behavior.EventBehavior;
+import org.apache.myfaces.tobago.internal.component.AbstractUIEvent;
import javax.el.MethodExpression;
import javax.faces.component.PartialStateHolder;
import javax.faces.component.UIComponent;
-import javax.faces.component.behavior.AjaxBehavior;
-import javax.faces.component.behavior.ClientBehavior;
import javax.faces.component.behavior.ClientBehaviorHolder;
import javax.faces.context.FacesContext;
import javax.faces.event.AbortProcessingException;
@@ -42,8 +41,6 @@ import javax.faces.view.facelets.TagAttribute;
import javax.faces.view.facelets.TagAttributeException;
import javax.faces.view.facelets.TagException;
import java.io.IOException;
-import java.util.List;
-import java.util.Map;
/**
* This tag creates an instance of AjaxBehavior, and associates it with the nearest
@@ -141,37 +138,26 @@ public class EventHandler extends TobagoComponentHandler implements BehaviorHold
final FaceletContext faceletContext = (FaceletContext) context
.getAttributes().get(FaceletContext.FACELET_CONTEXT_KEY);
- // cast to a ClientBehaviorHolder
- final ClientBehaviorHolder cvh = (ClientBehaviorHolder) parent;
+ final ClientBehaviorHolder clientBehaviorHolder = (ClientBehaviorHolder) parent;
+ final UIComponent lastChild = parent.getChildren().stream().skip(parent.getChildCount() - 1).findFirst().orElse(null);
+ final AbstractUIEvent abstractUIEvent = lastChild instanceof AbstractUIEvent ? (AbstractUIEvent) lastChild : null;
- String eventName = getEventName();
- if (eventName == null) {
- eventName = cvh.getDefaultEventName();
+ if (abstractUIEvent != null) {
+ String eventName = getEventName();
if (eventName == null) {
- throw new TagAttributeException(event, "eventName could not be defined for f:ajax tag with no wrap mode.");
- }
- } else if (!cvh.getEventNames().contains(eventName)) {
- throw new TagAttributeException(event, "event it is not a valid eventName defined for this component");
- }
-
- final Map<String, List<ClientBehavior>> clientBehaviors = cvh.getClientBehaviors();
-
- final List<ClientBehavior> clientBehaviorList = clientBehaviors.get(eventName);
- if (clientBehaviorList != null && !clientBehaviorList.isEmpty()) {
- for (final ClientBehavior cb : clientBehaviorList) {
- if (cb instanceof AjaxBehavior) {
- // The most inner one has been applied, so according to
- // jsf 2.0 spec section 10.4.1.1 it is not necessary to apply
- // this one, because the inner one has precendece over
- // the outer one.
- return;
+ eventName = clientBehaviorHolder.getDefaultEventName();
+ if (eventName == null) {
+ throw new TagAttributeException(event, "eventName could not be defined for f:ajax tag with no wrap mode.");
}
+ } else if (!clientBehaviorHolder.getEventNames().contains(eventName)) {
+ throw new TagAttributeException(event, "event it is not a valid eventName defined for this component");
}
- }
- final EventBehavior ajaxBehavior = createBehavior(context);
+ final EventBehavior eventBehavior = createBehavior(context);
+ eventBehavior.setId(abstractUIEvent.getId());
- cvh.addClientBehavior(eventName, ajaxBehavior);
+ clientBehaviorHolder.addClientBehavior(eventName, eventBehavior);
+ }
}
protected EventBehavior createBehavior(final FacesContext context) {
diff --git a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/behavior/EventBehavior.java b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/behavior/EventBehavior.java
index a112688..8b342d3 100644
--- a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/behavior/EventBehavior.java
+++ b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/behavior/EventBehavior.java
@@ -56,6 +56,7 @@ public class EventBehavior extends ClientBehaviorBase {
private static final String ATTR_RENDER = "render";
private static final String ATTR_DISABLED = "disabled";
private static final String ATTR_IMMEDIATE = "immediate";
+ private static final String ATTR_ID = "id";
/**
* special render and execute targets
@@ -189,6 +190,14 @@ public class EventBehavior extends ClientBehaviorBase {
return (getStateHelper().get(ATTR_IMMEDIATE) != null) || (getValueExpression(ATTR_IMMEDIATE) != null);
}
+ public String getId() {
+ return (String) getStateHelper().eval(ATTR_ID);
+ }
+
+ public void setId(final String clientId) {
+ getStateHelper().put(ATTR_ID, clientId);
+ }
+
@Override
public Set<ClientBehaviorHint> getHints() {
return EnumSet.of(ClientBehaviorHint.SUBMITTING);
diff --git a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/component/AbstractUIEvent.java b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/component/AbstractUIEvent.java
index ee71ad2..6265e85 100644
--- a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/component/AbstractUIEvent.java
+++ b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/component/AbstractUIEvent.java
@@ -21,10 +21,12 @@ package org.apache.myfaces.tobago.internal.component;
import org.apache.myfaces.tobago.component.ClientBehaviors;
+import javax.faces.component.behavior.ClientBehaviorHolder;
+
/**
* {@link org.apache.myfaces.tobago.internal.taglib.component.EventTagDeclaration}
*/
-public abstract class AbstractUIEvent extends AbstractUICommandBase {
+public abstract class AbstractUIEvent extends AbstractUICommandBase implements ClientBehaviorHolder {
public abstract ClientBehaviors getEvent();
diff --git a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/CommandMap.java b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/CommandMap.java
index 8c82b08..b2816f0 100644
--- a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/CommandMap.java
+++ b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/CommandMap.java
@@ -104,14 +104,16 @@ public class CommandMap {
c1.merge(c2);
}
} else {
- for (final Map.Entry<ClientBehaviors, Command> entry : m2.getOther().entrySet()) {
- final ClientBehaviors key = entry.getKey();
- final Command value = entry.getValue();
- if (m1.other.containsKey(key)) {
- final Command command = m1.other.get(key);
- command.merge(value);
- } else {
- m1.addCommand(key, value);
+ if (m1.other != null && m2.getOther() != null) {
+ for (final Map.Entry<ClientBehaviors, Command> entry : m2.getOther().entrySet()) {
+ final ClientBehaviors key = entry.getKey();
+ final Command value = entry.getValue();
+ if (m1.other.containsKey(key)) {
+ final Command command = m1.other.get(key);
+ command.merge(value);
+ } else {
+ m1.addCommand(key, value);
+ }
}
}
}
diff --git a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/RowRenderer.java b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/RowRenderer.java
index 9c1bc12..209e05a 100644
--- a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/RowRenderer.java
+++ b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/RowRenderer.java
@@ -33,7 +33,11 @@ public class RowRenderer extends DecodingCommandRendererBase {
AbstractUIEvent event = null;
for (final UIComponent uiComponent : component.getChildren()) {
if (uiComponent instanceof AbstractUIEvent) {
- event = (AbstractUIEvent) uiComponent;
+ AbstractUIEvent abstractUIEvent = (AbstractUIEvent) uiComponent;
+ if (abstractUIEvent.isRendered() && !abstractUIEvent.isDisabled()) {
+ event = (AbstractUIEvent) uiComponent;
+ break;
+ }
}
}
if (event != null) {
diff --git a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/TobagoClientBehaviorRenderer.java b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/TobagoClientBehaviorRenderer.java
index c3b8130..e5d68e6 100644
--- a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/TobagoClientBehaviorRenderer.java
+++ b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/renderkit/renderer/TobagoClientBehaviorRenderer.java
@@ -53,6 +53,7 @@ public class TobagoClientBehaviorRenderer extends javax.faces.render.ClientBehav
/**
* 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
@@ -63,7 +64,7 @@ public class TobagoClientBehaviorRenderer extends javax.faces.render.ClientBehav
final ClientBehaviors eventName = ClientBehaviors.valueOf(behaviorContext.getEventName());
//// TBD: is this nice? May be implemented with a JSF behaviour?
- final Collapse collapse = createCollapsible(facesContext, uiComponent);
+ Collapse collapse = createCollapsible(facesContext, uiComponent);
String executeIds = null;
String renderIds = null;
@@ -73,7 +74,7 @@ public class TobagoClientBehaviorRenderer extends javax.faces.render.ClientBehav
boolean omit = false;
if (behavior instanceof AjaxBehavior) {
final AjaxBehavior ajaxBehavior = (AjaxBehavior) behavior;
- if (ajaxBehavior.isDisabled()){
+ if (ajaxBehavior.isDisabled()) {
return null;
}
final Collection<String> execute = ajaxBehavior.getExecute();
@@ -97,14 +98,19 @@ public class TobagoClientBehaviorRenderer extends javax.faces.render.ClientBehav
ComponentUtils.evaluateClientIds(facesContext, uiComponent, render.toArray(new String[render.size()]));
actionId = clientId;
} else if (behavior instanceof EventBehavior) { // <tc:event>
- final AbstractUIEvent event = findEvent(uiComponent, eventName);
+ final EventBehavior eventBehavior = (EventBehavior) behavior;
+ final AbstractUIEvent event = RenderUtils.getAbstractUIEvent(uiComponent, eventBehavior);
+
if (event != null) {
- if (!event.isRendered()) {
+ 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));
}
}
@@ -132,39 +138,52 @@ public class TobagoClientBehaviorRenderer extends javax.faces.render.ClientBehav
return "dummy";
}
- private AbstractUIEvent findEvent(final UIComponent component, final ClientBehaviors eventName) {
- for (final UIComponent child : component.getChildren()) {
- if (child instanceof AbstractUIEvent) {
- final AbstractUIEvent event = (AbstractUIEvent) child;
- if (eventName == event.getEvent()) {
- return event;
- }
+ @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;
}
- }
- return null;
- }
- @Override
- public void decode(final FacesContext context, final UIComponent component, final ClientBehavior behavior) {
- final AjaxBehavior ajaxBehavior = (AjaxBehavior) behavior;
- if (ajaxBehavior.isDisabled() || !component.isRendered()) {
- return;
- }
+ dispatchBehaviorEvent(component, ajaxBehavior);
+ } else if (clientBehavior instanceof EventBehavior) {
+ final EventBehavior eventBehavior = (EventBehavior) clientBehavior;
+ final AbstractUIEvent abstractUIEvent = RenderUtils.getAbstractUIEvent(component, eventBehavior);
- dispatchBehaviorEvent(component, ajaxBehavior);
- }
+ if (!component.isRendered() || abstractUIEvent == null
+ || !abstractUIEvent.isRendered() || abstractUIEvent.isDisabled()) {
+ return;
+ }
- private void dispatchBehaviorEvent(final UIComponent component, final AjaxBehavior ajaxBehavior) {
+ for (List<ClientBehavior> children : abstractUIEvent.getClientBehaviors().values()) {
+ for (ClientBehavior child : children) {
+ decode(facesContext, component, child);
+ }
+ }
+ dispatchBehaviorEvent(component, eventBehavior);
+ }
+ }
- final AjaxBehaviorEvent event = new AjaxBehaviorEvent(component, ajaxBehavior);
- final boolean isImmediate = isImmediate(ajaxBehavior, component);
+ 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 AjaxBehavior ajaxBehavior, final UIComponent component) {
- if (ajaxBehavior.isImmediateSet()) {
- return ajaxBehavior.isImmediate();
+ 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();
diff --git a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/taglib/component/EventTagDeclaration.java b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/taglib/component/EventTagDeclaration.java
index 4d69cc6..abb6466 100644
--- a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/taglib/component/EventTagDeclaration.java
+++ b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/taglib/component/EventTagDeclaration.java
@@ -19,10 +19,12 @@
package org.apache.myfaces.tobago.internal.taglib.component;
+import org.apache.myfaces.tobago.apt.annotation.Behavior;
import org.apache.myfaces.tobago.apt.annotation.Tag;
import org.apache.myfaces.tobago.apt.annotation.TagAttribute;
import org.apache.myfaces.tobago.apt.annotation.UIComponentTag;
import org.apache.myfaces.tobago.apt.annotation.UIComponentTagAttribute;
+import org.apache.myfaces.tobago.component.ClientBehaviors;
import org.apache.myfaces.tobago.component.RendererTypes;
import org.apache.myfaces.tobago.internal.taglib.declaration.HasAction;
import org.apache.myfaces.tobago.internal.taglib.declaration.HasActionListener;
@@ -42,6 +44,7 @@ import javax.faces.component.UICommand;
/**
* Add an event behavior to the component.
+ * It can contain f:ajax and tc:operation tags.
*/
@Tag(name = "event")
@UIComponentTag(uiComponent = "org.apache.myfaces.tobago.component.UIEvent",
@@ -53,6 +56,22 @@ import javax.faces.component.UICommand;
// As long as no behavior event names are defined, ClientBehaviorHolder must be implemented for Majorra.
"javax.faces.component.behavior.ClientBehaviorHolder"
},
+ behaviors = {
+ @Behavior(name = ClientBehaviors.CHANGE),
+ @Behavior(
+ name = ClientBehaviors.CLICK,
+ description = "Behavior of a click event.",
+ isDefault = true),
+ @Behavior(name = ClientBehaviors.DBLCLICK),
+ @Behavior(name = ClientBehaviors.FOCUS),
+ @Behavior(name = ClientBehaviors.BLUR),
+ @Behavior(name = ClientBehaviors.MOUSEOUT),
+ @Behavior(name = ClientBehaviors.MOUSEOVER),
+ @Behavior(name = ClientBehaviors.COMPLETE),
+ @Behavior(name = ClientBehaviors.LOAD),
+ @Behavior(name = ClientBehaviors.RELOAD),
+ @Behavior(name = ClientBehaviors.RESIZE)
+ },
faceletHandler = "org.apache.myfaces.tobago.facelets.EventHandler")
public interface EventTagDeclaration
extends HasIdBindingAndRendered, HasAction, HasActionListener, IsImmediateCommand, HasConfirmation,
@@ -60,6 +79,7 @@ public interface EventTagDeclaration
/**
* The name of the event as an instance of {@link org.apache.myfaces.tobago.component.ClientBehaviors}
+ * This will be also overwrite events of possible f:ajax children.
*/
@TagAttribute
@UIComponentTagAttribute(type = "org.apache.myfaces.tobago.component.ClientBehaviors")
diff --git a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/util/RenderUtils.java b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/util/RenderUtils.java
index 36db432..50c6efe 100644
--- a/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/util/RenderUtils.java
+++ b/tobago-core/src/main/java/org/apache/myfaces/tobago/internal/util/RenderUtils.java
@@ -20,9 +20,11 @@
package org.apache.myfaces.tobago.internal.util;
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.AbstractUICommandBase;
import org.apache.myfaces.tobago.internal.component.AbstractUIData;
+import org.apache.myfaces.tobago.internal.component.AbstractUIEvent;
import org.apache.myfaces.tobago.internal.component.AbstractUITreeNodeBase;
import org.apache.myfaces.tobago.internal.renderkit.Command;
import org.apache.myfaces.tobago.internal.renderkit.CommandMap;
@@ -51,6 +53,7 @@ import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
public final class RenderUtils {
@@ -289,41 +292,79 @@ public final class RenderUtils {
return url;
}
- public static CommandMap getBehaviorCommands(final FacesContext facesContext, final ClientBehaviorHolder holder) {
- CommandMap map = null;
- final Map<String, List<ClientBehavior>> behaviors = holder.getClientBehaviors();
- for (final Map.Entry<String, List<ClientBehavior>> behaviorMap : behaviors.entrySet()) {
- final String key = behaviorMap.getKey();
- final ClientBehaviorContext context = ClientBehaviorContext.createClientBehaviorContext(
- facesContext, (UIComponent) holder, key, ((UIComponent) holder).getClientId(facesContext), null);
- for (final ClientBehavior clientBehavior : behaviorMap.getValue()) {
- if (clientBehavior instanceof ClientBehaviorBase) {
- String type = ((ClientBehaviorBase) clientBehavior).getRendererType();
- // XXX this is to use a different renderer for Tobago components and other components.
- if (type.equals(AjaxBehavior.BEHAVIOR_ID)) {
- type = "org.apache.myfaces.tobago.behavior.Ajax";
- }
- final ClientBehaviorRenderer renderer = facesContext.getRenderKit().getClientBehaviorRenderer(type);
- final String dummy = renderer.getScript(context, clientBehavior);
- if (dummy != null) {
- map = CommandMap.merge(map, CommandMap.restoreCommandMap(facesContext));
+ public static CommandMap getBehaviorCommands(final FacesContext facesContext,
+ final ClientBehaviorHolder clientBehaviorHolder) {
+ CommandMap commandMap = null;
+
+ for (final Map.Entry<String, List<ClientBehavior>> entry : clientBehaviorHolder.getClientBehaviors().entrySet()) {
+ final String eventName = entry.getKey();
+ final ClientBehaviorContext clientBehaviorContext
+ = getClientBehaviorContext(facesContext, clientBehaviorHolder, eventName);
+
+ for (final ClientBehavior clientBehavior : entry.getValue()) {
+ if (clientBehavior instanceof EventBehavior) {
+ final EventBehavior eventBehavior = (EventBehavior) clientBehavior;
+ final AbstractUIEvent abstractUIEvent = getAbstractUIEvent((UIComponent) clientBehaviorHolder, eventBehavior);
+
+ if (abstractUIEvent != null && abstractUIEvent.isRendered() && !abstractUIEvent.isDisabled()) {
+ for (List<ClientBehavior> children : abstractUIEvent.getClientBehaviors().values()) {
+ for (ClientBehavior child : children) {
+ final CommandMap childMap = getCommandMap(facesContext, clientBehaviorContext, child);
+ commandMap = CommandMap.merge(commandMap, childMap);
+ }
+ }
}
- } else {
- LOG.warn("Ignoring: '{}'", clientBehavior);
}
+
+ final CommandMap map = getCommandMap(facesContext, clientBehaviorContext, clientBehavior);
+ commandMap = CommandMap.merge(commandMap, map);
}
}
// if there is no explicit behavior (with f:ajax or tc:event), use the command properties as default.
- // tbd: think about refactoring: put this into TobagoClientBehaviorRenderer
- if ((map == null || map.isEmpty()) && holder instanceof AbstractUICommand) {
- if (map == null) {
- map = new CommandMap();
+ if ((commandMap == null || commandMap.isEmpty()) && clientBehaviorHolder instanceof AbstractUICommand) {
+ if (commandMap == null) {
+ commandMap = new CommandMap();
}
- map.addCommand(ClientBehaviors.click, new Command(facesContext, (AbstractUICommand) holder));
+ commandMap.addCommand(ClientBehaviors.click, new Command(facesContext, (AbstractUICommand) clientBehaviorHolder));
}
- return map;
+ return commandMap;
+ }
+
+ private static ClientBehaviorContext getClientBehaviorContext(final FacesContext facesContext,
+ final ClientBehaviorHolder clientBehaviorHolder, final String eventName) {
+ UIComponent component = (UIComponent) clientBehaviorHolder;
+ return ClientBehaviorContext.createClientBehaviorContext(facesContext, component, eventName,
+ component.getClientId(facesContext), null);
+ }
+
+ public static AbstractUIEvent getAbstractUIEvent(final UIComponent parent,
+ final EventBehavior eventBehavior) {
+ return (AbstractUIEvent) parent.getChildren().stream()
+ .filter(child -> child instanceof AbstractUIEvent)
+ .filter(child -> Objects.equals(child.getId(), eventBehavior.getId()))
+ .findFirst().orElse(null);
+ }
+
+ private static CommandMap getCommandMap(final FacesContext facesContext,
+ final ClientBehaviorContext clientBehaviorContext, final ClientBehavior clientBehavior) {
+ if (clientBehavior instanceof ClientBehaviorBase) {
+ String type = ((ClientBehaviorBase) clientBehavior).getRendererType();
+
+ // this is to use a different renderer for Tobago components and other components.
+ if (type.equals(AjaxBehavior.BEHAVIOR_ID)) {
+ type = "org.apache.myfaces.tobago.behavior.Ajax";
+ }
+ final ClientBehaviorRenderer renderer = facesContext.getRenderKit().getClientBehaviorRenderer(type);
+ final String dummy = renderer.getScript(clientBehaviorContext, clientBehavior);
+ if (dummy != null) {
+ return CommandMap.restoreCommandMap(facesContext);
+ }
+ } else {
+ LOG.warn("Ignoring: '{}'", clientBehavior);
+ }
+ return null;
}
public static void decodeClientBehaviors(final FacesContext facesContext, final UIComponent component) {
diff --git a/tobago-core/src/main/java/org/apache/myfaces/tobago/util/ComponentUtils.java b/tobago-core/src/main/java/org/apache/myfaces/tobago/util/ComponentUtils.java
index ad83ddb..c9045b1 100644
--- a/tobago-core/src/main/java/org/apache/myfaces/tobago/util/ComponentUtils.java
+++ b/tobago-core/src/main/java/org/apache/myfaces/tobago/util/ComponentUtils.java
@@ -692,7 +692,7 @@ public final class ComponentUtils {
return clientId;
}
LOG.error("No component found for id='{}', search base component is '{}'",
- component != null ? component.getClientId(context) : "<null>");
+ componentId, component != null ? component.getClientId(context) : "<null>");
return null;
}
diff --git a/tobago-example/tobago-example-demo/src/main/java/org/apache/myfaces/tobago/example/demo/BehaviorController.java b/tobago-example/tobago-example-demo/src/main/java/org/apache/myfaces/tobago/example/demo/BehaviorController.java
index 51bcfd4..9942530 100644
--- a/tobago-example/tobago-example-demo/src/main/java/org/apache/myfaces/tobago/example/demo/BehaviorController.java
+++ b/tobago-example/tobago-example-demo/src/main/java/org/apache/myfaces/tobago/example/demo/BehaviorController.java
@@ -19,16 +19,19 @@
package org.apache.myfaces.tobago.example.demo;
-import javax.enterprise.context.RequestScoped;
+import javax.enterprise.context.SessionScoped;
+import javax.faces.event.ActionEvent;
+import javax.faces.event.AjaxBehaviorEvent;
import javax.inject.Named;
import java.io.Serializable;
-@RequestScoped
+@SessionScoped
@Named
public class BehaviorController implements Serializable {
private String ajax;
private String event;
+ private int counter;
public String getAjax() {
return ajax;
@@ -45,4 +48,16 @@ public class BehaviorController implements Serializable {
public void setEvent(final String event) {
this.event = event;
}
+
+ public int getCounter() {
+ return counter;
+ }
+
+ public void countUp(final AjaxBehaviorEvent event) {
+ counter++;
+ }
+
+ public void countUp(final ActionEvent event) {
+ counter++;
+ }
}
diff --git a/tobago-example/tobago-example-demo/src/main/java/org/apache/myfaces/tobago/example/demo/BehaviorTestController.java b/tobago-example/tobago-example-demo/src/main/java/org/apache/myfaces/tobago/example/demo/BehaviorTestController.java
new file mode 100644
index 0000000..42ed101
--- /dev/null
+++ b/tobago-example/tobago-example-demo/src/main/java/org/apache/myfaces/tobago/example/demo/BehaviorTestController.java
@@ -0,0 +1,213 @@
+/*
+ * 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.example.demo;
+
+import javax.enterprise.context.SessionScoped;
+import javax.faces.event.ActionEvent;
+import javax.faces.event.AjaxBehaviorEvent;
+import javax.inject.Named;
+import java.io.Serializable;
+
+@SessionScoped
+@Named
+public class BehaviorTestController implements Serializable {
+
+ private int buttonActionCounter;
+ private int buttonActionListenerCounter;
+ private int actionCounter1;
+ private int actionListenerCounter1;
+ private int ajaxListenerCounter1;
+ private int actionCounter2;
+ private int actionListenerCounter2;
+ private int ajaxListenerCounter2;
+ private int actionCounter3;
+ private int actionListenerCounter3;
+ private int ajaxListenerCounter3;
+ private int selector;
+ private boolean eventEnabled1;
+ private boolean eventEnabled2;
+ private boolean eventEnabled3;
+ private boolean ajaxEnabled1;
+ private boolean ajaxEnabled2;
+ private boolean ajaxEnabled3;
+
+ public BehaviorTestController() {
+ reset();
+ }
+
+ public void reset() {
+ buttonActionCounter = 0;
+ buttonActionListenerCounter = 0;
+ actionCounter1 = 0;
+ actionListenerCounter1 = 0;
+ ajaxListenerCounter1 = 0;
+ actionCounter2 = 0;
+ actionListenerCounter2 = 0;
+ ajaxListenerCounter2 = 0;
+ actionCounter3 = 0;
+ actionListenerCounter3 = 0;
+ ajaxListenerCounter3 = 0;
+ }
+
+ public void countButtonAction() {
+ buttonActionCounter++;
+ }
+
+ public void countButtonActionListener(ActionEvent actionEvent) {
+ buttonActionListenerCounter++;
+ }
+
+ public void countAction1() {
+ actionCounter1++;
+ }
+
+ public void countActionListener1(ActionEvent actionEvent) {
+ actionListenerCounter1++;
+ }
+
+ public void countAjaxListener1(AjaxBehaviorEvent ajaxBehaviorEvent) {
+ ajaxListenerCounter1++;
+ }
+
+ public void countAction2() {
+ actionCounter2++;
+ }
+
+ public void countActionListener2(ActionEvent actionEvent) {
+ actionListenerCounter2++;
+ }
+
+ public void countAjaxListener2(AjaxBehaviorEvent ajaxBehaviorEvent) {
+ ajaxListenerCounter2++;
+ }
+
+ public void countAction3() {
+ actionCounter3++;
+ }
+
+ public void countActionListener3(ActionEvent actionEvent) {
+ actionListenerCounter3++;
+ }
+
+ public void countAjaxListener3(AjaxBehaviorEvent ajaxBehaviorEvent) {
+ ajaxListenerCounter3++;
+ }
+
+ public int getSelector() {
+ return selector;
+ }
+
+ public void setSelector(int selector) {
+ this.selector = selector;
+ }
+
+ public void submitSelection() {
+ eventEnabled1 = false;
+ eventEnabled2 = false;
+ eventEnabled3 = false;
+ ajaxEnabled1 = false;
+ ajaxEnabled2 = false;
+ ajaxEnabled3 = false;
+
+ switch (selector) {
+ case 1:
+ eventEnabled1 = true;
+ break;
+ case 2:
+ eventEnabled2 = true;
+ ajaxEnabled3 = true;
+ break;
+ case 3:
+ eventEnabled3 = true;
+ ajaxEnabled1 = true;
+ ajaxEnabled2 = true;
+ ajaxEnabled3 = true;
+ break;
+ }
+ }
+
+ public int getButtonActionCounter() {
+ return buttonActionCounter;
+ }
+
+ public int getButtonActionListenerCounter() {
+ return buttonActionListenerCounter;
+ }
+
+ public int getActionCounter1() {
+ return actionCounter1;
+ }
+
+ public int getActionListenerCounter1() {
+ return actionListenerCounter1;
+ }
+
+ public int getAjaxListenerCounter1() {
+ return ajaxListenerCounter1;
+ }
+
+ public int getActionCounter2() {
+ return actionCounter2;
+ }
+
+ public int getActionListenerCounter2() {
+ return actionListenerCounter2;
+ }
+
+ public int getAjaxListenerCounter2() {
+ return ajaxListenerCounter2;
+ }
+
+ public int getActionCounter3() {
+ return actionCounter3;
+ }
+
+ public int getActionListenerCounter3() {
+ return actionListenerCounter3;
+ }
+
+ public int getAjaxListenerCounter3() {
+ return ajaxListenerCounter3;
+ }
+
+ public boolean isEventEnabled1() {
+ return eventEnabled1;
+ }
+
+ public boolean isEventEnabled2() {
+ return eventEnabled2;
+ }
+
+ public boolean isEventEnabled3() {
+ return eventEnabled3;
+ }
+
+ public boolean isAjaxEnabled1() {
+ return ajaxEnabled1;
+ }
+
+ public boolean isAjaxEnabled2() {
+ return ajaxEnabled2;
+ }
+
+ public boolean isAjaxEnabled3() {
+ return ajaxEnabled3;
+ }
+}
diff --git a/tobago-example/tobago-example-demo/src/main/webapp/content/20-component/080-sheet/30-event/sheet-event.test.js b/tobago-example/tobago-example-demo/src/main/webapp/content/20-component/080-sheet/30-event/sheet-event.test.js
index 3ab16ad..2194cb5 100644
--- a/tobago-example/tobago-example-demo/src/main/webapp/content/20-component/080-sheet/30-event/sheet-event.test.js
+++ b/tobago-example/tobago-example-demo/src/main/webapp/content/20-component/080-sheet/30-event/sheet-event.test.js
@@ -201,3 +201,112 @@ QUnit.test("On double click with full request", function (assert) {
}
});
});
+
+QUnit.test("Open popup on click with ajax", function (assert) {
+ assert.expect(12);
+ var done = assert.async(7);
+
+ var $radioButton = jQueryFrame("#page\\:mainForm\\:changeExample\\:\\:3");
+ var $venus = jQueryFrame("#page\\:mainForm\\:s1\\:2\\:sample3");
+ var $jupiter = jQueryFrame("#page\\:mainForm\\:s1\\:5\\:sample3");
+ var $saturn = jQueryFrame("#page\\:mainForm\\:s1\\:6\\:sample3");
+ var $popup = jQueryFrame("#page\\:mainForm\\:popup");
+ var $name = jQueryFrame("#page\\:mainForm\\:popup\\:popupName\\:\\:field");
+ var $cancel = jQueryFrame("#page\\:mainForm\\:popup\\:cancel");
+
+ $radioButton.click();
+
+ waitForAjax(function () {
+ $venus = jQueryFrame($venus.selector);
+ $jupiter = jQueryFrame($jupiter.selector);
+ $saturn = jQueryFrame($saturn.selector);
+ return $venus.length === 1 && $jupiter.length === 1 && $saturn.length === 1;
+ }, function () {
+ $venus = jQueryFrame($venus.selector);
+ $jupiter = jQueryFrame($jupiter.selector);
+ $saturn = jQueryFrame($saturn.selector);
+ assert.equal($venus.length, 1);
+ assert.equal($jupiter.length, 1);
+ assert.equal($saturn.length, 1);
+ done();
+
+ $venus.click();
+
+ waitForAjax(function () {
+ $popup = jQueryFrame($popup.selector);
+ $name = jQueryFrame($name.selector);
+ return $popup.hasClass("show") && $name.val() === "Venus";
+ }, function () {
+ $popup = jQueryFrame($popup.selector);
+ $name = jQueryFrame($name.selector);
+ assert.ok($popup.hasClass("show"));
+ assert.equal($name.val(), "Venus");
+ done();
+
+ waitForAjax(function () {
+ $cancel = jQueryFrame($cancel.selector);
+ $popup = jQueryFrame($popup.selector);
+
+ $cancel.click();
+ return !$popup.hasClass("show");
+ }, function () {
+ $popup = jQueryFrame($popup.selector);
+ assert.notOk($popup.hasClass("show"));
+ done();
+
+ $jupiter.click();
+
+ waitForAjax(function () {
+ $popup = jQueryFrame($popup.selector);
+ $name = jQueryFrame($name.selector);
+ return $popup.hasClass("show") && $name.val() === "Jupiter";
+ }, function () {
+ $popup = jQueryFrame($popup.selector);
+ $name = jQueryFrame($name.selector);
+ assert.ok($popup.hasClass("show"));
+ assert.equal($name.val(), "Jupiter");
+ done();
+
+ waitForAjax(function () {
+ $cancel = jQueryFrame($cancel.selector);
+ $popup = jQueryFrame($popup.selector);
+
+ $cancel.click();
+ return !$popup.hasClass("show");
+ }, function () {
+ $popup = jQueryFrame($popup.selector);
+ assert.notOk($popup.hasClass("show"));
+ done();
+
+ $saturn.click();
+
+ waitForAjax(function () {
+ $popup = jQueryFrame($popup.selector);
+ $name = jQueryFrame($name.selector);
+ return $popup.hasClass("show") && $name.val() === "Saturn";
+ }, function () {
+ $popup = jQueryFrame($popup.selector);
+ $name = jQueryFrame($name.selector);
+ assert.ok($popup.hasClass("show"));
+ assert.equal($name.val(), "Saturn");
+ done();
+
+
+ waitForAjax(function () {
+ $cancel = jQueryFrame($cancel.selector);
+ $popup = jQueryFrame($popup.selector);
+
+ $cancel.click();
+ return !$popup.hasClass("show");
+ }, function () {
+ $popup = jQueryFrame($popup.selector);
+ assert.notOk($popup.hasClass("show"));
+ done();
+ });
+ });
+ });
+ });
+ });
+ });
+ });
+});
diff --git a/tobago-example/tobago-example-demo/src/main/webapp/content/20-component/080-sheet/30-event/sheet-event.xhtml b/tobago-example/tobago-example-demo/src/main/webapp/content/20-component/080-sheet/30-event/sheet-event.xhtml
index 5530d00..cd358ae 100644
--- a/tobago-example/tobago-example-demo/src/main/webapp/content/20-component/080-sheet/30-event/sheet-event.xhtml
+++ b/tobago-example/tobago-example-demo/src/main/webapp/content/20-component/080-sheet/30-event/sheet-event.xhtml
@@ -24,7 +24,8 @@
xmlns:f="http://java.sun.com/jsf/core">
<ui:param name="title" value="#{demoBundle.sheet_event} <tc:row>"/>
<p>A <code class="language-markup"><tc:sheet/></code> can contain a
- <code class="language-markup"><tc:row/></code> tag with a containing <code class="language-markup"><tc:event/></code> tag.
+ <code class="language-markup"><tc:row/></code> tag with a containing <code
+ class="language-markup"><tc:event/></code> tag.
The event type can be set by the <code>event</code> attribute. Possible values are 'click' and 'dblclick'.
</p>
@@ -62,8 +63,9 @@
</tc:row>
<tc:row id="sample3" rendered="#{sheetController.columnEventSample == 3}">
- <f:ajax execute=":::popup" render=":::popup" listener="#{sheetController.selectSolarObject}" id="columnEventPopup"/>
- <tc:event omit="true">
+ <tc:event>
+ <f:ajax id="columnEventPopup" execute=":::popup" render=":::popup"
+ listener="#{sheetController.selectSolarObject}"/>
<tc:operation name="show" for=":::popup"/>
</tc:event>
</tc:row>
@@ -94,7 +96,7 @@
<tc:popup id="popup">
<tc:box label="Details">
- <tc:in label="Name" value="#{sheetController.selectedSolarObject.name}" required="true"/>
+ <tc:in id="popupName" label="Name" value="#{sheetController.selectedSolarObject.name}" required="true"/>
<tc:in label="Number" value="#{sheetController.selectedSolarObject.number}"/>
<tc:in label="Orbit" value="#{sheetController.selectedSolarObject.orbit}"/>
<tc:in label="Distance" value="#{sheetController.selectedSolarObject.distance}"/>
diff --git a/tobago-example/tobago-example-demo/src/main/webapp/content/30-concept/30-behavior/behavior.xhtml b/tobago-example/tobago-example-demo/src/main/webapp/content/30-concept/30-behavior/behavior.xhtml
index 0ee5657..d233191 100644
--- a/tobago-example/tobago-example-demo/src/main/webapp/content/30-concept/30-behavior/behavior.xhtml
+++ b/tobago-example/tobago-example-demo/src/main/webapp/content/30-concept/30-behavior/behavior.xhtml
@@ -28,7 +28,6 @@
<li><code class="language-markup"><f:ajax/></code> send an ajax request</li>
<li><code class="language-markup"><tc:event/></code> do a full page reload</li>
</ul>
- <p>You find more <tc:link label="examples" outcome="/content/40-test/6000-event/event.xhtml"/> in the test section.</p>
<p>Tag Library Documentation:
<tc:link label="<f:ajax/>" image="#{request.contextPath}/image/feather-leaf.png"
link="http://docs.oracle.com/javaee/6/javaserverfaces/2.1/docs/vdldocs/facelets/f/ajax.html"/>
@@ -36,7 +35,7 @@
<tc:link label="<tc:event/>" image="#{request.contextPath}/image/feather-leaf.png"
link="#{demoBundle.tagDocUrl}/#{info.stableVersion}/tld/tc/event.html"/></p>
- <tc:section label="Examples">
+ <tc:section label="Basic examples">
<p>Type a text into the input field. After leaving the input field, the given text is shown in the output field.</p>
<tc:section label="f:ajax">
<pre><code class="language-markup"><tc:in label="Ajax Input" value="\#{behaviorController.ajax}">
@@ -60,11 +59,42 @@
</tc:section>
</tc:section>
- <tc:section label="Limitations">
- <p>The <code class="language-markup"><f:ajax></code> tag cannot inserted along with the
- <code class="language-markup"><tc:event></code> tag if both have the same <code>event</code> attribute value.
- <br/>
- The latter also applies if <code class="language-markup"><f:ajax></code> is disabled
- or if <code class="language-markup"><tc:event></code> is not rendered.</p>
+ <tc:section label="Change the event name">
+ <p>It's possible to change the event name. For example if you want a double click event, change the event name to
+ <code>dblclick</code>. This is done for the following two buttons.</p>
+
+ <pre><code class="language-markup"><tc:button label="Ajax Double Click">
+ <f:ajax event="dblclick" render="outCounter" listener="..."/>
+</tc:button>
+<tc:button label="Event Double Click">
+ <tc:event event="dblclick" actionListener="..."/>
+</tc:button></code></pre>
+ <tc:button label="Ajax Double Click">
+ <f:ajax event="dblclick" render="outCounter" listener="#{behaviorController.countUp}"/>
+ </tc:button>
+ <tc:button label="Event Double Click">
+ <tc:event event="dblclick" actionListener="#{behaviorController.countUp}"/>
+ </tc:button>
+ <tc:out id="outCounter" label="Counter" value="#{behaviorController.counter}"/>
+ </tc:section>
+
+ <tc:section label="Advanced">
+ <p><code class="language-markup"><tc:event></code> may contain <code class="language-markup"><f:ajax></code>
+ and <code class="language-markup"><tc:operation></code> tags.</p>
+ <p>This is helpfull if the parent component cannot handle operations by itself.</p>
+ <p><tc:badge value="Important" markup="danger"/>
+ The <code>event</code> attribute of the ajax children will be ignored.</p>
+
+ <tc:section label="Example">
+ <p>An example can be found for
+ <tc:link label="<tc:row>" outcome="/content/20-component/080-sheet/30-event/sheet-event.xhtml"/>
+ Option: 'open popup on click with AJAX'.</p>
+ <pre><code class="language-markup"><tc:row>
+ <tc:event>
+ <f:ajax execute=":::popup" render=":::popup" listener="..."/>
+ <tc:operation name="show" for=":::popup"/>
+ </tc:event>
+</tc:row></code></pre>
+ </tc:section>
</tc:section>
</ui:composition>
diff --git a/tobago-example/tobago-example-demo/src/main/webapp/content/40-test/6500-behavior/behavior.test.js b/tobago-example/tobago-example-demo/src/main/webapp/content/40-test/6500-behavior/behavior.test.js
new file mode 100644
index 0000000..56ce70e
--- /dev/null
+++ b/tobago-example/tobago-example-demo/src/main/webapp/content/40-test/6500-behavior/behavior.test.js
@@ -0,0 +1,292 @@
+/*
+ * 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.
+ */
+
+QUnit.test("Simple Event", function (assert) {
+ assert.expect(11);
+ var done = assert.async();
+ var step = 1;
+
+ var $button = jQueryFrame("#page\\:mainForm\\:simpleEvent");
+ var oldCounterValues = getCounterValues();
+
+ $button.click();
+
+ jQuery("#page\\:testframe").load(function () {
+ if (step === 1) {
+ compareCounterValues(assert, oldCounterValues, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0);
+
+ step++;
+ done();
+ }
+ });
+});
+
+QUnit.test("Simple Ajax", function (assert) {
+ assert.expect(11);
+ var done = assert.async();
+ var step = 1;
+
+ var $button = jQueryFrame("#page\\:mainForm\\:simpleAjax");
+ var oldCounterValues = getCounterValues();
+
+ $button.click();
+
+ waitForAjax(function () {
+ var newCounterValues = getCounterValues();
+ return step === 1 && oldCounterValues !== newCounterValues;
+ }, function () {
+ compareCounterValues(assert, oldCounterValues, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0);
+
+ step++;
+ done();
+ });
+});
+
+QUnit.test("Simple EventAjax", function (assert) {
+ assert.expect(11);
+ var done = assert.async();
+ var step = 1;
+
+ var $button = jQueryFrame("#page\\:mainForm\\:simpleEventAjax");
+ var oldCounterValues = getCounterValues();
+
+ $button.click();
+
+ waitForAjax(function () {
+ var newCounterValues = getCounterValues();
+ return step === 1 && oldCounterValues !== newCounterValues;
+ }, function () {
+ compareCounterValues(assert, oldCounterValues, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0);
+
+ step++;
+ done();
+ });
+});
+
+QUnit.test("Advanced Button: Option 1", function (assert) {
+ var $option = jQueryFrame("#page\\:mainForm\\:advancedSelector\\:\\:0");
+ var $button = jQueryFrame("#page\\:mainForm\\:advancedButton");
+ testEventOption(assert, $option, $button, "dblclick", 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0);
+});
+
+QUnit.test("Advanced Button: Option 2", function (assert) {
+ var $option = jQueryFrame("#page\\:mainForm\\:advancedSelector\\:\\:1");
+ var $button = jQueryFrame("#page\\:mainForm\\:advancedButton");
+
+ testAjaxOption(assert, $option, $button, "dblclick", 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1);
+});
+
+QUnit.test("Advanced Button: Option 3", function (assert) {
+ var $option = jQueryFrame("#page\\:mainForm\\:advancedSelector\\:\\:2");
+ var $button = jQueryFrame("#page\\:mainForm\\:advancedButton");
+ testAjaxOption(assert, $option, $button, "click", 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1);
+});
+
+QUnit.test("Row: Option 1", function (assert) {
+ var $option = jQueryFrame("#page\\:mainForm\\:advancedSelector\\:\\:0");
+ var $row = jQueryFrame("#page\\:mainForm\\:sheet\\:0\\:row");
+ testEventOption(assert, $option, $row, "dblclick", 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0);
+});
+
+QUnit.test("Row: Option 2", function (assert) {
+ var $option = jQueryFrame("#page\\:mainForm\\:advancedSelector\\:\\:1");
+ var $row = jQueryFrame("#page\\:mainForm\\:sheet\\:0\\:row");
+ testAjaxOption(assert, $option, $row, "dblclick", 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1);
+});
+
+QUnit.test("Row: Option 3", function (assert) {
+ var $option = jQueryFrame("#page\\:mainForm\\:advancedSelector\\:\\:2");
+ var $row = jQueryFrame("#page\\:mainForm\\:sheet\\:0\\:row");
+ testAjaxOption(assert, $option, $row, "click", 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1);
+});
+
+QUnit.test("Input: Click Event", function (assert) {
+ var $input = jQueryFrame("#page\\:mainForm\\:inputClick\\:\\:field");
+ testInputSection(assert, $input, "click");
+});
+
+function testEventOption(assert, $option, $component, event,
+ buttonActionPlus, buttonActionListenerPlus,
+ action1Plus, actionListener1Plus, ajaxListener1Plus,
+ action2Plus, actionListener2Plus, ajaxListener2Plus,
+ action3Plus, actionListener3Plus, ajaxListener3Plus) {
+ assert.expect(13);
+ var done = assert.async(3);
+ var step = 1;
+
+ var $hide = jQueryFrame("#page\\:mainForm\\:hideOperationTextBox");
+ var $operationOut = jQueryFrame("#page\\:mainForm\\:operationOut");
+ var oldCounterValues = getCounterValues();
+
+ $hide.click();
+
+ jQuery("#page\\:testframe").load(function () {
+ if (step === 1) {
+ $operationOut = jQueryFrame($operationOut.selector);
+ $option = jQueryFrame($option.selector);
+ assert.equal($operationOut.length, 0, "Content of operation test box must be hidden.");
+
+ $option.click();
+
+ step++;
+ done();
+ } else if (step === 2) {
+ $component = jQueryFrame($component.selector);
+ $component.trigger(event);
+
+ step++;
+ done();
+ } else if (step === 3) {
+ $operationOut = jQueryFrame($operationOut.selector);
+ assert.equal($operationOut.length, 1, "Content of operation test box must be shown.");
+
+ compareCounterValues(assert, oldCounterValues,
+ buttonActionPlus, buttonActionListenerPlus,
+ action1Plus, actionListener1Plus, ajaxListener1Plus,
+ action2Plus, actionListener2Plus, ajaxListener2Plus,
+ action3Plus, actionListener3Plus, ajaxListener3Plus);
+
+ step++;
+ done();
+ }
+ });
+}
+
+function testAjaxOption(assert, $option, $component, event,
+ buttonActionPlus, buttonActionListenerPlus,
+ action1Plus, actionListener1Plus, ajaxListener1Plus,
+ action2Plus, actionListener2Plus, ajaxListener2Plus,
+ action3Plus, actionListener3Plus, ajaxListener3Plus) {
+ assert.expect(13);
+ var done = assert.async(3);
+ var step = 1;
+
+ var $hide = jQueryFrame("#page\\:mainForm\\:hideOperationTextBox");
+ var $operationOut = jQueryFrame("#page\\:mainForm\\:operationOut");
+ var oldCounterValues = getCounterValues();
+
+ $hide.click();
+
+ jQuery("#page\\:testframe").load(function () {
+ if (step === 1) {
+ $operationOut = jQueryFrame($operationOut.selector);
+ $option = jQueryFrame($option.selector);
+ assert.equal($operationOut.length, 0, "Content of operation test box must be hidden.");
+
+ $option.click();
+
+ step++;
+ done();
+ } else if (step === 2) {
+ $component = jQueryFrame($component.selector);
+ $component.trigger(event);
+
+ step++;
+ done();
+
+ waitForAjax(function () {
+ var newCounterValues = getCounterValues();
+ return step === 3 && oldCounterValues !== newCounterValues;
+ }, function () {
+ $operationOut = jQueryFrame($operationOut.selector);
+ assert.equal($operationOut.length, 1, "Content of operation test box must be shown.");
+
+ compareCounterValues(assert, oldCounterValues,
+ buttonActionPlus, buttonActionListenerPlus,
+ action1Plus, actionListener1Plus, ajaxListener1Plus,
+ action2Plus, actionListener2Plus, ajaxListener2Plus,
+ action3Plus, actionListener3Plus, ajaxListener3Plus);
+
+ step++;
+ done();
+ });
+ }
+ });
+}
+
+function testInputSection(assert, $input, eventName) {
+ assert.expect(2);
+ var done = assert.async(2);
+ var step = 1;
+
+ var $hide = jQueryFrame("#page\\:mainForm\\:hideOperationTextBox");
+ $hide.click();
+
+ jQuery("#page\\:testframe").load(function () {
+ if (step === 1) {
+ $input = jQueryFrame($input.selector);
+ assert.ok(isOperationTestBoxCollapsed(), "Content of operation test box must be hidden.");
+
+ $input.trigger(eventName);
+
+ step++;
+ done();
+ } else if (step === 2) {
+ assert.notOk(isOperationTestBoxCollapsed(), "Content of operation test box must be shown.");
+
+ step++;
+ done();
+ }
+ });
+}
+
+function getCounterValues() {
+ var buttonActionCounter = jQueryFrame("#page\\:mainForm\\:buttonActionCounter.tobago-out").text();
+ var buttonActionListenerCounter = jQueryFrame("#page\\:mainForm\\:buttonActionListenerCounter.tobago-out").text();
+
+ var actionCount1 = jQueryFrame("#page\\:mainForm\\:actionCounter1.tobago-out").text();
+ var actionListenerCount1 = jQueryFrame("#page\\:mainForm\\:actionListenerCounter1.tobago-out").text();
+ var ajaxListenerCount1 = jQueryFrame("#page\\:mainForm\\:ajaxListenerCounter1.tobago-out").text();
+
+ var actionCount2 = jQueryFrame("#page\\:mainForm\\:actionCounter2.tobago-out").text();
+ var actionListenerCount2 = jQueryFrame("#page\\:mainForm\\:actionListenerCounter2.tobago-out").text();
+ var ajaxListenerCount2 = jQueryFrame("#page\\:mainForm\\:ajaxListenerCounter2.tobago-out").text();
+
+ var actionCount3 = jQueryFrame("#page\\:mainForm\\:actionCounter3.tobago-out").text();
+ var actionListenerCount3 = jQueryFrame("#page\\:mainForm\\:actionListenerCounter3.tobago-out").text();
+ var ajaxListenerCount3 = jQueryFrame("#page\\:mainForm\\:ajaxListenerCounter3.tobago-out").text();
+
+ return [buttonActionCounter, buttonActionListenerCounter,
+ actionCount1, actionListenerCount1, ajaxListenerCount1,
+ actionCount2, actionListenerCount2, ajaxListenerCount2,
+ actionCount3, actionListenerCount3, ajaxListenerCount3]
+}
+
+function compareCounterValues(assert, oldCounterValues,
+ buttonActionPlus, buttonActionListenerPlus,
+ action1Plus, actionListener1Plus, ajaxListener1Plus,
+ action2Plus, actionListener2Plus, ajaxListener2Plus,
+ action3Plus, actionListener3Plus, ajaxListener3Plus) {
+ var newCounterValues = getCounterValues();
+
+ assert.equal(parseInt(newCounterValues[0]), parseInt(oldCounterValues[0]) + buttonActionPlus);
+ assert.equal(parseInt(newCounterValues[1]), parseInt(oldCounterValues[1]) + buttonActionListenerPlus);
+ assert.equal(parseInt(newCounterValues[2]), parseInt(oldCounterValues[2]) + action1Plus);
+ assert.equal(parseInt(newCounterValues[3]), parseInt(oldCounterValues[3]) + actionListener1Plus);
+ assert.equal(parseInt(newCounterValues[4]), parseInt(oldCounterValues[4]) + ajaxListener1Plus);
+ assert.equal(parseInt(newCounterValues[5]), parseInt(oldCounterValues[5]) + action2Plus);
+ assert.equal(parseInt(newCounterValues[6]), parseInt(oldCounterValues[6]) + actionListener2Plus);
+ assert.equal(parseInt(newCounterValues[7]), parseInt(oldCounterValues[7]) + ajaxListener2Plus);
+ assert.equal(parseInt(newCounterValues[8]), parseInt(oldCounterValues[8]) + action3Plus);
+ assert.equal(parseInt(newCounterValues[9]), parseInt(oldCounterValues[9]) + actionListener3Plus);
+ assert.equal(parseInt(newCounterValues[10]), parseInt(oldCounterValues[10]) + ajaxListener3Plus);
+}
+
+function isOperationTestBoxCollapsed() {
+ var $operationOut = jQueryFrame("#page\\:mainForm\\:operationOut");
+ return $operationOut.length === 0;
+}
diff --git a/tobago-example/tobago-example-demo/src/main/webapp/content/40-test/6500-behavior/behavior.xhtml b/tobago-example/tobago-example-demo/src/main/webapp/content/40-test/6500-behavior/behavior.xhtml
new file mode 100644
index 0000000..3a976c5
--- /dev/null
+++ b/tobago-example/tobago-example-demo/src/main/webapp/content/40-test/6500-behavior/behavior.xhtml
@@ -0,0 +1,205 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+ * 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.
+-->
+
+<ui:composition template="/main.xhtml"
+ xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:f="http://java.sun.com/jsf/core"
+ xmlns:tc="http://myfaces.apache.org/tobago/component"
+ xmlns:ui="http://java.sun.com/jsf/facelets">
+ <ui:param name="title" value="#{demoBundle.behavior}"/>
+
+
+ <tc:box id="counter" label="Counter">
+ <f:facet name="bar">
+ <tc:button id="reset" label="Reset" action="#{behaviorTestController.reset}"/>
+ </f:facet>
+ <tc:segmentLayout extraSmall="3seg 2seg 4seg 3seg">
+ <tc:panel/>
+ <tc:out value="Action"/>
+ <tc:out value="ActionListener"/>
+ <tc:out value="AjaxListener"/>
+
+ <tc:out value="Button Counter" labelLayout="skip"/>
+ <tc:out id="buttonActionCounter" value="#{behaviorTestController.buttonActionCounter}" labelLayout="skip"/>
+ <tc:out id="buttonActionListenerCounter" value="#{behaviorTestController.buttonActionListenerCounter}"
+ labelLayout="skip"/>
+ <tc:panel/>
+
+ <tc:out value="Counter 1" labelLayout="skip"/>
+ <tc:out id="actionCounter1" value="#{behaviorTestController.actionCounter1}" labelLayout="skip"/>
+ <tc:out id="actionListenerCounter1" value="#{behaviorTestController.actionListenerCounter1}" labelLayout="skip"/>
+ <tc:out id="ajaxListenerCounter1" value="#{behaviorTestController.ajaxListenerCounter1}" labelLayout="skip"/>
+
+ <tc:out value="Counter 2" labelLayout="skip"/>
+ <tc:out id="actionCounter2" value="#{behaviorTestController.actionCounter2}" labelLayout="skip"/>
+ <tc:out id="actionListenerCounter2" value="#{behaviorTestController.actionListenerCounter2}" labelLayout="skip"/>
+ <tc:out id="ajaxListenerCounter2" value="#{behaviorTestController.ajaxListenerCounter2}" labelLayout="skip"/>
+
+ <tc:out value="Counter 3" labelLayout="skip"/>
+ <tc:out id="actionCounter3" value="#{behaviorTestController.actionCounter3}" labelLayout="skip"/>
+ <tc:out id="actionListenerCounter3" value="#{behaviorTestController.actionListenerCounter3}" labelLayout="skip"/>
+ <tc:out id="ajaxListenerCounter3" value="#{behaviorTestController.ajaxListenerCounter3}" labelLayout="skip"/>
+ </tc:segmentLayout>
+ </tc:box>
+ <tc:box id="operationTestBox" label="<tc:operation> Test" collapsedMode="absent">
+ <f:facet name="bar">
+ <tc:button id="hideOperationTextBox" label="Hide">
+ <tc:operation name="hide" for="operationTestBox"/>
+ </tc:button>
+ </f:facet>
+ <tc:out id="operationOut" value="Content"/>
+ </tc:box>
+
+ <tc:section label="<tc:button>">
+ <tc:section label="Basics">
+ <tc:button id="simpleEvent" label="Simple Event">
+ <tc:event action="#{behaviorTestController.countAction1}"
+ actionListener="#{behaviorTestController.countActionListener1}"/>
+ </tc:button>
+ <tc:button id="simpleAjax" label="Simple Ajax">
+ <f:ajax render="counter operationTestBox" listener="#{behaviorTestController.countAjaxListener1}"/>
+ </tc:button>
+
+ <tc:button id="simpleEventAjax" label="Simple EventAjax">
+ <tc:event>
+ <f:ajax render="counter operationTestBox" listener="#{behaviorTestController.countAjaxListener1}"/>
+ </tc:event>
+ </tc:button>
+ <p/>
+ </tc:section>
+
+ <tc:section label="Advanced">
+ <tc:selectOneRadio id="advancedSelector" value="#{behaviorTestController.selector}">
+ <tc:selectItem itemValue="1" itemLabel="Event 1 + no Ajax enabled"/>
+ <tc:selectItem itemValue="2" itemLabel="Event 2 + Ajax 3 enabled"/>
+ <tc:selectItem itemValue="3" itemLabel="Event 3 + all Ajax enabled"/>
+ <tc:event action="#{behaviorTestController.submitSelection}"/>
+ </tc:selectOneRadio>
+ <tc:separator/>
+
+ <tc:button id="advancedButton" label="Advanced Button"
+ action="#{behaviorTestController.countButtonAction}"
+ actionListener="#{behaviorTestController.countButtonActionListener}">
+ <tc:event event="dblclick" action="#{behaviorTestController.countAction1}"
+ actionListener="#{behaviorTestController.countActionListener1}"
+ rendered="#{behaviorTestController.eventEnabled1}">
+ <f:ajax execute="operationTestBox" render="counter operationTestBox"
+ listener="#{behaviorTestController.countAjaxListener1}"
+ disabled="#{!behaviorTestController.ajaxEnabled1}"/>
+ <f:ajax event="blur" execute="operationTestBox" render="counter operationTestBox"
+ listener="#{behaviorTestController.countAjaxListener2}"
+ disabled="#{!behaviorTestController.ajaxEnabled2}"/>
+ <f:ajax event="focus" execute="operationTestBox" render="counter operationTestBox"
+ listener="#{behaviorTestController.countAjaxListener3}"
+ disabled="#{!behaviorTestController.ajaxEnabled3}"/>
+ <tc:operation name="show" for="operationTestBox"/>
+ </tc:event>
+ <tc:event event="dblclick" action="#{behaviorTestController.countAction2}"
+ actionListener="#{behaviorTestController.countActionListener2}"
+ disabled="#{!behaviorTestController.eventEnabled2}">
+ <f:ajax execute="operationTestBox" render="counter operationTestBox"
+ listener="#{behaviorTestController.countAjaxListener1}"
+ disabled="#{!behaviorTestController.ajaxEnabled1}"/>
+ <f:ajax event="blur" execute="operationTestBox" render="counter operationTestBox"
+ listener="#{behaviorTestController.countAjaxListener2}"
+ disabled="#{!behaviorTestController.ajaxEnabled2}"/>
+ <f:ajax event="focus" execute="operationTestBox" render="counter operationTestBox"
+ listener="#{behaviorTestController.countAjaxListener3}"
+ disabled="#{!behaviorTestController.ajaxEnabled3}"/>
+ <tc:operation name="show" for="operationTestBox"/>
+ </tc:event>
+ <tc:event action="#{behaviorTestController.countAction3}"
+ actionListener="#{behaviorTestController.countActionListener3}"
+ rendered="#{behaviorTestController.eventEnabled3}">
+ <f:ajax execute="operationTestBox" render="counter operationTestBox"
+ listener="#{behaviorTestController.countAjaxListener1}"
+ disabled="#{!behaviorTestController.ajaxEnabled1}"/>
+ <f:ajax event="blur" execute="operationTestBox" render="counter operationTestBox"
+ listener="#{behaviorTestController.countAjaxListener2}"
+ disabled="#{!behaviorTestController.ajaxEnabled2}"/>
+ <f:ajax event="focus" execute="operationTestBox" render="counter operationTestBox"
+ listener="#{behaviorTestController.countAjaxListener3}"
+ disabled="#{!behaviorTestController.ajaxEnabled3}"/>
+ <tc:operation name="show" for="operationTestBox"/>
+ </tc:event>
+ </tc:button>
+ </tc:section>
+ </tc:section>
+
+ <tc:section label="<tc:row>">
+ <tc:sheet id="sheet" value="#{sheetController.solarList}" var="object" markup="small">
+ <tc:style maxHeight="100px"/>
+ <tc:column label="Solarobjects">
+ <tc:out value="#{object.name}" labelLayout="skip"/>
+ </tc:column>
+ <tc:row id="row">
+ <tc:event id="e1" event="dblclick" action="#{behaviorTestController.countAction1}"
+ actionListener="#{behaviorTestController.countActionListener1}"
+ rendered="#{behaviorTestController.eventEnabled1}">
+ <f:ajax execute=":::operationTestBox" render=":::counter :::operationTestBox"
+ listener="#{behaviorTestController.countAjaxListener1}"
+ disabled="#{!behaviorTestController.ajaxEnabled1}"/>
+ <f:ajax event="blur" execute=":::operationTestBox" render=":::counter :::operationTestBox"
+ listener="#{behaviorTestController.countAjaxListener2}"
+ disabled="#{!behaviorTestController.ajaxEnabled2}"/>
+ <f:ajax event="focus" execute=":::operationTestBox" render=":::counter :::operationTestBox"
+ listener="#{behaviorTestController.countAjaxListener3}"
+ disabled="#{!behaviorTestController.ajaxEnabled3}"/>
+ <tc:operation name="show" for=":::operationTestBox"/>
+ </tc:event>
+ <tc:event id="e2" event="dblclick" action="#{behaviorTestController.countAction2}"
+ actionListener="#{behaviorTestController.countActionListener2}"
+ disabled="#{!behaviorTestController.eventEnabled2}">
+ <f:ajax execute=":::operationTestBox" render=":::counter :::operationTestBox"
+ listener="#{behaviorTestController.countAjaxListener1}"
+ disabled="#{!behaviorTestController.ajaxEnabled1}"/>
+ <f:ajax event="blur" execute=":::operationTestBox" render=":::counter :::operationTestBox"
+ listener="#{behaviorTestController.countAjaxListener2}"
+ disabled="#{!behaviorTestController.ajaxEnabled2}"/>
+ <f:ajax event="focus" execute=":::operationTestBox" render=":::counter :::operationTestBox"
+ listener="#{behaviorTestController.countAjaxListener3}"
+ disabled="#{!behaviorTestController.ajaxEnabled3}"/>
+ <tc:operation name="show" for=":::operationTestBox"/>
+ </tc:event>
+ <tc:event id="e3" action="#{behaviorTestController.countAction3}"
+ actionListener="#{behaviorTestController.countActionListener3}"
+ rendered="#{behaviorTestController.eventEnabled3}">
+ <f:ajax execute=":::operationTestBox" render=":::counter :::operationTestBox"
+ listener="#{behaviorTestController.countAjaxListener1}"
+ disabled="#{!behaviorTestController.ajaxEnabled1}"/>
+ <f:ajax event="blur" execute=":::operationTestBox" render=":::counter :::operationTestBox"
+ listener="#{behaviorTestController.countAjaxListener2}"
+ disabled="#{!behaviorTestController.ajaxEnabled2}"/>
+ <f:ajax event="focus" execute=":::operationTestBox" render=":::counter :::operationTestBox"
+ listener="#{behaviorTestController.countAjaxListener3}"
+ disabled="#{!behaviorTestController.ajaxEnabled3}"/>
+ <tc:operation name="show" for=":::operationTestBox"/>
+ </tc:event>
+ </tc:row>
+ </tc:sheet>
+ </tc:section>
+
+ <tc:section label="<tc:in>">
+ <tc:in id="inputClick" label="Event Input (click)">
+ <tc:event id="inputEventClick" event="click">
+ <tc:operation for="operationTestBox" name="show"/>
+ </tc:event>
+ </tc:in>
+ </tc:section>
+</ui:composition>
diff --git a/tobago-example/tobago-example-demo/src/test/java/org/apache/myfaces/tobago/example/demo/QUnitTests.java b/tobago-example/tobago-example-demo/src/test/java/org/apache/myfaces/tobago/example/demo/QUnitTests.java
index f49ef59..8f44840 100644
--- a/tobago-example/tobago-example-demo/src/test/java/org/apache/myfaces/tobago/example/demo/QUnitTests.java
+++ b/tobago-example/tobago-example-demo/src/test/java/org/apache/myfaces/tobago/example/demo/QUnitTests.java
@@ -555,4 +555,10 @@ public class QUnitTests {
final String page = "content/40-test/50000-java/30-ajax-special-character/ajax-special-character.xhtml";
runStandardTest(page);
}
+
+ @Test
+ public void testBehavior() throws UnsupportedEncodingException, InterruptedException {
+ final String page = "content/40-test/6500-behavior/behavior.xhtml";
+ runStandardTest(page);
+ }
}
--
To stop receiving notification emails like this one, please contact
hnoeth@apache.org.