Permalink
Browse files

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

various locations in the component tree.
  • Loading branch information...
arjantijms committed Jul 9, 2014
1 parent bce5f0c commit da6940a84b1375d179838d3408e66c5a9e71128e
@@ -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.