Permalink
Browse files

Utility component that can be used to move components and behaviors to

various locations in the component tree.
  • Loading branch information...
1 parent bce5f0c commit da6940a84b1375d179838d3408e66c5a9e71128e arjan.tijms committed Jul 9, 2014
@@ -0,0 +1,233 @@
+/*
+ * Copyright 2014 OmniFaces.
+ *
+ * Licensed 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.omnifaces.component.util;
+
+import static java.lang.String.format;
+import static org.omnifaces.component.util.MoveComponent.Destination.ADD_LAST;
+import static org.omnifaces.component.util.MoveComponent.Destination.BEHAVIOR;
+import static org.omnifaces.component.util.MoveComponent.PropertyKeys.behaviorDefaultEvent;
+import static org.omnifaces.component.util.MoveComponent.PropertyKeys.behaviorEvents;
+import static org.omnifaces.component.util.MoveComponent.PropertyKeys.destination;
+import static org.omnifaces.component.util.MoveComponent.PropertyKeys.facet;
+import static org.omnifaces.util.Components.findComponentRelatively;
+import static org.omnifaces.util.Events.subscribeToViewEvent;
+import static org.omnifaces.util.Faces.isPostback;
+import static org.omnifaces.util.Utils.csvToList;
+import static org.omnifaces.util.Utils.isEmpty;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import javax.faces.component.FacesComponent;
+import javax.faces.component.UIComponent;
+import javax.faces.component.UIViewRoot;
+import javax.faces.component.behavior.ClientBehavior;
+import javax.faces.component.behavior.ClientBehaviorHolder;
+import javax.faces.event.AbortProcessingException;
+import javax.faces.event.PostAddToViewEvent;
+import javax.faces.event.PreRenderViewEvent;
+import javax.faces.event.SystemEvent;
+import javax.faces.event.SystemEventListener;
+
+import org.omnifaces.util.State;
+
+/**
+ * <strong>MoveComponent</strong> is a utility component via which a components and behaviors can be moved to
+ * a target component at various ways.
+ *
+ * @since 2.0
+ * @author Arjan Tijms
+ */
+@FacesComponent(MoveComponent.COMPONENT_TYPE)
+public class MoveComponent extends UtilFamily implements SystemEventListener, ClientBehaviorHolder {
+
+ public static final String COMPONENT_TYPE = "org.omnifaces.component.util.MoveComponent";
+
+ private static final String ERROR_COMPONENT_NOT_FOUND =
+ "A component with ID '%s' as specified by the 'for' attribute of the MoveComponent with Id '%s' could not be found.";
+
+ public static final String DEFAULT_SCOPE = "facelet";
+
+ private final State state = new State(getStateHelper());
+
+ enum PropertyKeys {
+ /* for */ facet, destination, behaviorDefaultEvent, behaviorEvents
+ }
+
+ public enum Destination {
+ BEFORE, // Component is moved right before target component, i.e. as a sibling with an index that's 1 position lower
+ ADD_FIRST, // Component is added as the first child of the target component, any other children will have their index increased by 1
+ ADD_LAST, // Component is added as the last child of the target component, any other children will stay at their original location
+ FACET, // Component will be moved to the facet section of the target component under the name denoted by "facet"
+ BEHAVIOR, // A Behavior will be moved to the behavior section of the target component
+ AFTER // Component is moved right after target component, i.e. as a sibling with an index that's 1 position higher
+ }
+
+ private String attachedEventName;
+ private ClientBehavior attachedBehavior;
+
+ // Used to fool over-eager tag handlers that check in advance whether a given component indeed
+ // supports the event for which a behavior is attached.
+ private List<String> containsTrueList = new ArrayList<String>() {
+ private static final long serialVersionUID = 1L;
+ @Override
+ public boolean contains(Object o) {
+ return true;
+ }
+ };
+
+
+ // Actions --------------------------------------------------------------------------------------------------------
+
+ public MoveComponent() {
+ if (!isPostback()) {
+ subscribeToViewEvent(PreRenderViewEvent.class, this);
+ } else {
+ // Behaviors added during the PreRenderViewEvent aren't properly restored, so we add them again
+ // in the PostAddToViewEvent during a postback.
+ subscribeToViewEvent(PostAddToViewEvent.class, this);
+ }
+ }
+
+ @Override
+ public boolean isListenerForSource(Object source) {
+ return source instanceof UIViewRoot;
+ }
+
+ @Override
+ public void processEvent(SystemEvent event) throws AbortProcessingException {
+ if (event instanceof PreRenderViewEvent || (event instanceof PostAddToViewEvent && getDestination() == BEHAVIOR)) {
+ doProcess();
+ }
+ }
+
+ @Override
+ public void addClientBehavior(String eventName, ClientBehavior behavior) {
+ attachedEventName = eventName;
+ attachedBehavior = behavior;
+ }
+
+ @Override
+ public String getDefaultEventName() {
+ return getBehaviorDefaultEvent();
+ }
+
+ @Override
+ public Collection<String> getEventNames() {
+ if (isEmpty(getBehaviorEvents())) {
+ return containsTrueList;
+ }
+
+ return csvToList(getBehaviorEvents());
+ }
+
+ public void doProcess() {
+ String forValue = getFor();
+
+ if (!isEmpty(forValue)) {
+ UIComponent component = findComponentRelatively(this, forValue);
+
+ if (component == null) {
+ component = findComponent(forValue);
+ }
+
+ if (component == null) {
+ throw new IllegalArgumentException(format(ERROR_COMPONENT_NOT_FOUND, forValue, getId()));
+ }
+
+ switch (getDestination()) {
+ case BEFORE:
+ for (int i = 0; i < getChildren().size(); i++) {
+ component.getParent().getChildren().add(i, getChildren().get(i));
+ }
+ break;
+ case ADD_FIRST:
+ for (int i = 0; i < getChildren().size(); i++) {
+ component.getChildren().add(i, getChildren().get(i));
+ }
+ break;
+ case ADD_LAST:
+ for (UIComponent childComponent : getChildren()) {
+ component.getChildren().add(childComponent);
+ }
+ break;
+ case FACET:
+ for (UIComponent childComponent : getChildren()) {
+ component.getFacets().put(getFacet(), childComponent);
+ }
+ break;
+ case BEHAVIOR:
+ if (component instanceof ClientBehaviorHolder) {
+
+ ClientBehaviorHolder clientBehaviorHolder = (ClientBehaviorHolder) component;
+ List<ClientBehavior> behaviors = clientBehaviorHolder.getClientBehaviors().get(attachedEventName);
+
+ if (behaviors == null || !behaviors.contains(this)) { // Guard against adding ourselves twice
+ clientBehaviorHolder.addClientBehavior(attachedEventName, attachedBehavior);
+ }
+ }
+ break;
+ case AFTER:
+ for (UIComponent childComponent : getChildren()) {
+ component.getParent().getChildren().add(childComponent);
+ }
+ break;
+ }
+
+ }
+ }
+
+
+ // Attribute getters/setters --------------------------------------------------------------------------------------
+
+ public String getFor() {
+ return state.get("for");
+ }
+
+ public void setFor(String nameValue) {
+ state.put("for", nameValue);
+ }
+
+ public Destination getDestination() {
+ return state.get(destination, ADD_LAST);
+ }
+
+ public void setDestination(Destination destinationValue) {
+ state.put(destination, destinationValue);
+ }
+
+ public String getFacet() {
+ return state.get(facet);
+ }
+
+ public void setFacet(String facetValue) {
+ state.put(facet, facetValue);
+ }
+
+ public String getBehaviorDefaultEvent() {
+ return state.get(behaviorDefaultEvent, "");
+ }
+
+ public void setBehaviorDefaultEvent(String behaviorDefaultEventValue) {
+ state.put(behaviorDefaultEvent, behaviorDefaultEventValue);
+ }
+
+ public String getBehaviorEvents() {
+ return state.get(behaviorEvents);
+ }
+
+ public void setBehaviorEvents(String behaviorEventsValue) {
+ state.put(behaviorEvents, behaviorEventsValue);
+ }
+}
@@ -2010,6 +2010,88 @@ public class ValidateValuesBean implements MultiFieldValidator {
<type>java.lang.String</type>
</attribute>
</tag>
+
+ <tag>
+ <description>
+ <![CDATA[
+ <strong>MoveComponent</strong> is a utility component via which a components and behaviors can be moved to
+ a target component at various ways.
+ ]]>
+ </description>
+
+ <tag-name>moveComponent</tag-name>
+ <component>
+ <component-type>org.omnifaces.component.util.MoveComponent</component-type>
+ </component>
+
+ <attribute>
+ <description>
+ <![CDATA[
+ ID of the target component for which the component moving will be done
+ ]]>
+ </description>
+ <name>for</name>
+ <required>true</required>
+ <type>java.lang.String</type>
+ </attribute>
+
+ <attribute>
+ <description>
+ <![CDATA[
+ The destination relative to the target component where the source component(s) are move to. Valid values are
+ <ul>
+ <li> <code>BEFORE</code> - Component is moved right before target component, i.e. as a sibling with an index that's 1 position lower
+ <li> <code>ADD_FIRST</code> - Component is added as the first child of the target component, any other children will have their index increased by 1
+ <li> <code>ADD_LAST</code> - Component is added as the last child of the target component, any other children will stay at their original location
+ <li> <code>FACET</code> - Component will be moved to the facet section of the target component under the name denoted by "facet"
+ <li> <code>BEHAVIOR</code> - A Behavior will be moved to the behavior section of the target component
+ <li> <code>AFTER</code> - Component is moved right after target component, i.e. as a sibling with an index that's 1 position higher
+ </ul>
+ ]]>
+ </description>
+ <name>destination</name>
+ <required>false</required>
+ <type>java.lang.String</type>
+ </attribute>
+
+ <attribute>
+ <description>
+ <![CDATA[
+ In case the <code>destination</code> is set to FACET, the name of the facet in the target component to which the components should be moved
+ ]]>
+ </description>
+ <name>facet</name>
+ <required>false</required>
+ <type>java.lang.String</type>
+ </attribute>
+
+ <attribute>
+ <description>
+ <![CDATA[
+ In case the <code>destination</code> is set to BEHAVIOR, the name of the default event that the <b>target</b> component is 'supposed' to have.
+ This normally does not need to be set, but might be needed for some over-eager tag handlers associated with a behavior that in advance try
+ to check whether the behavior event matches with what the component supports.
+ ]]>
+ </description>
+ <name>behaviorDefaultEvent</name>
+ <required>false</required>
+ <type>java.lang.String</type>
+ </attribute>
+
+ <attribute>
+ <description>
+ <![CDATA[
+ In case the <code>destination</code> is set to BEHAVIOR, the comma separated list events that the <b>target</b> component is 'supposed' to support.
+ This normally does not need to be set, but might be needed for some over-eager tag handlers associated with a behavior that in advance try
+ to check whether the behavior event matches with what the component supports.
+ ]]>
+ </description>
+ <name>behaviorEvents</name>
+ <required>false</required>
+ <type>java.lang.String</type>
+ </attribute>
+ </tag>
+
<tag>
<description>

0 comments on commit da6940a

Please sign in to comment.