/**
* Copyright 2010 Marko Lavikainen
*
* 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 net.contextfw.web.application.component;
import java.util.HashSet;
import java.util.Set;
import net.contextfw.web.application.WebApplicationException;
import net.contextfw.web.application.internal.component.ComponentBuilder;
/**
* The base class of a component
*
*/
@Buildable
public abstract class Component {
private final Set<String> partialUpdates = new HashSet<String>();
private String partialUpdateName;
private RefreshMode refreshMode = RefreshMode.NONE;
private Component parent = null;
private Set<Component> children = null;
private Set<Component> waitingToRegister = null;
private boolean enabled = true;
private enum RefreshMode {
NONE, PASS, UPDATE, PARTIAL_UPDATE
};
@Attribute
private String id;
/**
* Assigns an id for the component
*
* <p>
* Id is generated automatically by the web framework so setting an id is
* not necesssary. the id is of a form <code>el[n]</code> where
* <code>[n]</code> is an incrementing value.
* </p>
* <p>
* It is also possible to set a custom id, but to be sure that setting
* succceeds it must be don during initialization.
* </p>
*/
public void setId(String id) {
if (this.id != null) {
throw new WebApplicationException("Component id can be set only once");
}
this.id = id;
}
/**
* Get's component id
*
* <p>
* The <code>id</code> is <code>null</code> initially, but when component is
* registered to thes system id is generated automatically. This means that
* <code>id</code> may not be in use at initialization phase and developer
* should not rely on using <code>id</code> in class properties.
* </p>
*/
public String getId() {
return id;
}
/**
* Adds a component to be a child of this component.
*
* <p>
* Registering child components is a mandatory task so that the framework is
* able to register all components in the page and assign proper ids for
* them.
* </p>
*
* <p>
* Component registering is lazy. If parent component has not yet been
* registered then registering for child components is delayd untit parent
* is also registered.
* </p>
*
* @param <T>
* Component type
* @param el
* The Component
* @return The added component
*/
public <T extends Component> T registerChild(T el) {
if (children == null) {
children = new HashSet<Component>();
waitingToRegister = new HashSet<Component>();
}
children.add(el);
el.parent = this;
if (bubbleRegisterUp(el)) {
el.registerChildren();
} else {
waitingToRegister.add(el);
}
return el;
}
private void registerChildren() {
if (waitingToRegister != null) {
for (Component comp : waitingToRegister) {
registerChild(comp);
}
waitingToRegister.clear();
}
}
/**
* For internal use only
*/
protected boolean bubbleRegisterUp(Component el) {
if (parent != null) {
return parent.bubbleRegisterUp(el);
} else {
return false;
}
}
/**
* For internal use only
*/
protected void bubbleUnregisterUp(Component el) {
if (parent != null) {
parent.bubbleUnregisterUp(el);
}
}
/**
* Removes component (this) from its parent, if parent exists.
*/
public void detach() {
if (parent != null) {
parent.unregisterChild(this);
}
}
/**
* Removes child component from the framework
*/
public void unregisterChild(Component el) {
if (children != null) {
children.remove(el);
bubbleUnregisterUp(el);
el.parent = null;
}
}
/**
* Refreshes component state to web client
*
* <p>
* When component needs to update its state on web client, this method must
* be called. Framework recognizes the request and creates property update
* during rendering phase.
* </p>
*
* <p>
* <b>Note!</b> If paren component also requests update, then the update of
* this component is canceled, because in normal circumstances this
* component if fully redrawn by the parent component.
* </p>
*/
public void refresh() {
refresh(RefreshMode.UPDATE);
}
private void refresh(RefreshMode mode) {
if (id != null) {
refreshMode = mode;
Component p = parent;
while (p != null) {
if (p.refreshMode == RefreshMode.NONE) {
p.refreshMode = RefreshMode.PASS;
p = p.parent;
} else {
p = null;
}
}
}
}
/**
* For internal use only
*/
public final void buildComponentUpdate(DOMBuilder domBuilder, ComponentBuilder builder) {
if (refreshMode == RefreshMode.UPDATE) {
builder.buildUpdate(domBuilder, this, "update");
} else if (refreshMode == RefreshMode.PARTIAL_UPDATE) {
builder.buildPartialUpdate(domBuilder, this, partialUpdateName, partialUpdates);
}
if ((refreshMode == RefreshMode.PASS || refreshMode == RefreshMode.PARTIAL_UPDATE) && children != null) {
for (Component child : children) {
child.buildComponentUpdate(domBuilder, builder);
}
}
clearCascadedUpdate();
}
/**
* Requests a partial update for component
*
* <p>
* When there is a need to only partially update component, then this method
* is used
* </p>
*
*
* @param buildName
* @param updates
*/
public void partialRefresh(String buildName, String... updates) {
if (partialUpdates != null && refreshMode != RefreshMode.UPDATE) {
this.partialUpdateName = buildName;
for (String partialUpdate : updates) {
partialUpdates.add(partialUpdate);
}
partialUpdates.add("id");
refresh(RefreshMode.PARTIAL_UPDATE);
}
}
/**
* For internal use only
*/
public void clearCascadedUpdate() {
if (refreshMode == RefreshMode.NONE) {
return;
}
refreshMode = RefreshMode.NONE;
if (children != null) {
for (Component child : children) {
child.clearCascadedUpdate();
}
}
partialUpdates.clear();
partialUpdateName = null;
}
/**
* Defines if this component is enabled or disabled.
*
* <p>
* If component is disabled then it is not added to DOM-tree during
* rendering phase. Also disabled component does not listen remote calls.
* </p>
*
* @param enabled
*/
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public boolean isEnabled() {
return enabled;
}
}