-Initial AJAX support
-Upgrading Dojo to version 1.0
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
package org.springframework.faces.model;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.faces.model.DataModel;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A {@link DataModel} implementation that tracks the currently selected rows, allowing any number of rows to be
|
||||
* selected at one time.
|
||||
*
|
||||
* @author Jeremy Grelle
|
||||
*/
|
||||
public class ManySelectionTrackingListDataModel extends SerializableListDataModel implements SelectionAware {
|
||||
|
||||
private List selections = new ArrayList();
|
||||
|
||||
public ManySelectionTrackingListDataModel(List list) {
|
||||
super(list);
|
||||
}
|
||||
|
||||
public List getSelections() {
|
||||
return selections;
|
||||
}
|
||||
|
||||
public boolean isCurrentRowSelected() {
|
||||
return selections.contains(getRowData());
|
||||
}
|
||||
|
||||
public void selectAll() {
|
||||
selections.clear();
|
||||
selections.addAll((List) getWrappedData());
|
||||
}
|
||||
|
||||
public void setSelected(boolean rowSelected) {
|
||||
if (rowSelected && !selections.contains(getRowData())) {
|
||||
selections.add(getRowData());
|
||||
} else {
|
||||
selections.remove(getRowData());
|
||||
}
|
||||
}
|
||||
|
||||
public void setSelections(List selections) {
|
||||
this.selections = selections;
|
||||
}
|
||||
|
||||
public void select(Object rowData) {
|
||||
Assert.isTrue(((List) getWrappedData()).contains(rowData),
|
||||
"The object to select is not contained in this DataModel.");
|
||||
if (!selections.contains(rowData)) {
|
||||
selections.add(rowData);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package org.springframework.faces.model;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.faces.model.DataModel;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A {@link DataModel} implementation that tracks the currently selected row, allowing only one selection at a time.
|
||||
*
|
||||
* @author Jeremy Grelle
|
||||
*/
|
||||
public class OneSelectionTrackingListDataModel extends SerializableListDataModel implements SelectionAware {
|
||||
|
||||
private List selections = new ArrayList();
|
||||
|
||||
public OneSelectionTrackingListDataModel(List list) {
|
||||
super(list);
|
||||
}
|
||||
|
||||
public List getSelections() {
|
||||
return selections;
|
||||
}
|
||||
|
||||
public boolean isCurrentRowSelected() {
|
||||
return selections.contains(getRowData());
|
||||
}
|
||||
|
||||
public void select(Object rowData) {
|
||||
Assert.isTrue(((List) getWrappedData()).contains(rowData),
|
||||
"The object to select is not contained in this DataModel.");
|
||||
selections.clear();
|
||||
selections.add(rowData);
|
||||
}
|
||||
|
||||
public void selectAll() {
|
||||
if (((List) getWrappedData()).size() > 1) {
|
||||
throw new UnsupportedOperationException("This DataModel only allows one selection.");
|
||||
}
|
||||
}
|
||||
|
||||
public void setSelected(boolean rowSelected) {
|
||||
if (rowSelected && !selections.contains(getRowData())) {
|
||||
selections.clear();
|
||||
selections.add(getRowData());
|
||||
} else if (!rowSelected) {
|
||||
selections.clear();
|
||||
}
|
||||
}
|
||||
|
||||
public void setSelections(List selections) {
|
||||
Assert.isTrue(selections.size() <= 1, "This DataModel only allows one selection.");
|
||||
this.selections = selections;
|
||||
}
|
||||
|
||||
public Object getSelectedRow() {
|
||||
if (selections.size() == 1) {
|
||||
return selections.get(0);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package org.springframework.faces.model;
|
||||
|
||||
import java.util.List;
|
||||
import javax.faces.model.DataModel;
|
||||
|
||||
/**
|
||||
* Interface for {@link DataModel} implementations that need to track selected rows.
|
||||
*
|
||||
* @author Jeremy Grelle
|
||||
*/
|
||||
public interface SelectionAware {
|
||||
|
||||
public boolean isCurrentRowSelected();
|
||||
|
||||
public void setSelected(boolean rowSelected);
|
||||
|
||||
public void setSelections(List selections);
|
||||
|
||||
public List getSelections();
|
||||
|
||||
public void selectAll();
|
||||
|
||||
public void select(Object rowData);
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package org.springframework.faces.model;
|
||||
|
||||
import javax.faces.component.UIComponent;
|
||||
import javax.faces.component.UIData;
|
||||
import javax.faces.component.UIViewRoot;
|
||||
import javax.faces.event.AbortProcessingException;
|
||||
import javax.faces.event.ActionEvent;
|
||||
import javax.faces.event.ActionListener;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.faces.webflow.FlowActionListener;
|
||||
|
||||
/**
|
||||
* Custom {@link ActionListener} that inspects the {@link UIComponent} that signaled the current {@link ActionEvent} to
|
||||
* determine whether it is a child of a {@link UIData} component that uses a {@link SelectionAware} data model
|
||||
* implementation. If a containing SelectionAware model is found, the row containing the event-signaling component
|
||||
* instance will be selected. This enables convenient access to the selected model state at any time through EL
|
||||
* expressions such as #{model.selectedRow.id} without having to rely on the whether or not the current row index is
|
||||
* pointing to the desired row as it would need to be to use an expression such as #{model.rowData.id}
|
||||
*
|
||||
* @author Jeremy Grelle
|
||||
*/
|
||||
public class SelectionTrackingActionListener implements ActionListener {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(FlowActionListener.class);
|
||||
|
||||
private ActionListener delegate;
|
||||
|
||||
public SelectionTrackingActionListener(ActionListener delegate) {
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
public void processAction(ActionEvent event) throws AbortProcessingException {
|
||||
trackSelection(event.getComponent());
|
||||
delegate.processAction(event);
|
||||
}
|
||||
|
||||
private void trackSelection(UIComponent component) {
|
||||
// Find parent UIData instance if it exists
|
||||
UIData table = null;
|
||||
UIComponent currentComponent = component;
|
||||
while (!(currentComponent.getParent() instanceof UIViewRoot)) {
|
||||
if (currentComponent.getParent() instanceof UIData) {
|
||||
table = (UIData) currentComponent.getParent();
|
||||
break;
|
||||
}
|
||||
currentComponent = currentComponent.getParent();
|
||||
}
|
||||
if (table != null && table.getValue() instanceof SelectionAware) {
|
||||
SelectionAware selectableModel = (SelectionAware) table.getValue();
|
||||
selectableModel.setSelected(true);
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Row selection has been set on the current SelectionAware data model.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package org.springframework.faces.model;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
import javax.faces.model.DataModel;
|
||||
import javax.faces.model.DataModelEvent;
|
||||
import javax.faces.model.DataModelListener;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A simple List-to-JSF-DataModel adapter that is also serializable.
|
||||
*/
|
||||
public class SerializableListDataModel extends DataModel implements Serializable {
|
||||
|
||||
private int rowIndex = 0;
|
||||
|
||||
private List data;
|
||||
|
||||
/**
|
||||
* Adapt the list to a data model;
|
||||
* @param list the list
|
||||
*/
|
||||
public SerializableListDataModel(List list) {
|
||||
setWrappedData(list);
|
||||
}
|
||||
|
||||
public int getRowCount() {
|
||||
return data.size();
|
||||
}
|
||||
|
||||
public Object getRowData() {
|
||||
Assert.isTrue(isRowAvailable(), getClass()
|
||||
+ " is in an illegal state - no row is available at the current index.");
|
||||
return data.get(rowIndex);
|
||||
}
|
||||
|
||||
public int getRowIndex() {
|
||||
return rowIndex;
|
||||
}
|
||||
|
||||
public Object getWrappedData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
public boolean isRowAvailable() {
|
||||
return rowIndex >= 0 && rowIndex < data.size();
|
||||
}
|
||||
|
||||
public void setRowIndex(int newRowIndex) {
|
||||
if (newRowIndex < -1) {
|
||||
throw new IllegalArgumentException("Illegal row index for " + getClass() + ": " + newRowIndex);
|
||||
}
|
||||
int oldRowIndex = rowIndex;
|
||||
rowIndex = newRowIndex;
|
||||
if (data != null && oldRowIndex != rowIndex) {
|
||||
Object row = isRowAvailable() ? getRowData() : null;
|
||||
DataModelEvent event = new DataModelEvent(this, rowIndex, row);
|
||||
DataModelListener[] listeners = getDataModelListeners();
|
||||
for (int i = 0; i < listeners.length; i++) {
|
||||
listeners[i].rowSelected(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setWrappedData(Object data) {
|
||||
Assert.notNull(data, "A non-null List is required for " + getClass());
|
||||
Assert.isInstanceOf(List.class, data, "The data object for " + getClass() + " must be a List");
|
||||
this.data = (List) data;
|
||||
int newRowIndex = 0;
|
||||
setRowIndex(newRowIndex);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package org.springframework.faces.ui;
|
||||
|
||||
import javax.faces.component.UICommand;
|
||||
|
||||
public class AjaxEventInterceptor extends UICommand {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package org.springframework.faces.ui;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.faces.component.UIComponent;
|
||||
import javax.faces.context.FacesContext;
|
||||
import javax.faces.context.ResponseWriter;
|
||||
import javax.faces.event.ActionEvent;
|
||||
|
||||
import org.springframework.faces.webflow.JsfUtils;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
public class AjaxEventInterceptorRenderer extends DojoRenderer {
|
||||
|
||||
public void encodeEnd(FacesContext context, UIComponent component) throws IOException {
|
||||
String event = (String) component.getAttributes().get("event");
|
||||
Assert.hasText(event, "The event attribute is required on " + component);
|
||||
Assert.isTrue(component.getChildCount() == 1, "Exactly one child component is required for " + component);
|
||||
|
||||
ResponseWriter writer = context.getResponseWriter();
|
||||
writer.startElement("script", component);
|
||||
|
||||
String processIds = (String) component.getAttributes().get("processIds");
|
||||
String renderIds = (String) component.getAttributes().get("renderIds");
|
||||
if (StringUtils.hasText(processIds) && !processIds.contains(component.getClientId(context))) {
|
||||
processIds = component.getClientId(context) + ", " + processIds;
|
||||
} else if (!StringUtils.hasText(processIds)) {
|
||||
processIds = component.getClientId(context);
|
||||
}
|
||||
if (!StringUtils.hasText(renderIds)) {
|
||||
renderIds = processIds;
|
||||
}
|
||||
StringBuffer script = new StringBuffer();
|
||||
script.append("SpringFaces.advisors.push(new SpringFaces.DojoAjaxEventAdvisor({");
|
||||
script.append("event:'" + event + "'");
|
||||
script.append(", targetId: '" + ((UIComponent) component.getChildren().get(0)).getClientId(context) + "'");
|
||||
script.append(", sourceId : '" + component.getClientId(context) + "'");
|
||||
script.append(", formId : '" + RendererUtils.getFormId(context, component) + "'");
|
||||
script.append(", processIds : '" + processIds + "'");
|
||||
script.append(", renderIds : '" + renderIds + "'})");
|
||||
if (JsfUtils.isAsynchronousFlowRequest()) {
|
||||
script.append(".apply()");
|
||||
}
|
||||
script.append(");");
|
||||
|
||||
writer.writeText(script.toString(), null);
|
||||
writer.endElement("script");
|
||||
}
|
||||
|
||||
public void decode(FacesContext context, UIComponent component) {
|
||||
if (context.getExternalContext().getRequestParameterMap().containsKey("ajaxSource")
|
||||
&& context.getExternalContext().getRequestParameterMap().get("ajaxSource").equals(
|
||||
component.getClientId(context))) {
|
||||
component.queueEvent(new ActionEvent(component));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -143,17 +143,20 @@ public class AjaxViewRoot extends DelegatingViewRoot {
|
||||
|
||||
private String[] removeNestedChildren(FacesContext context, String[] ids) {
|
||||
List idList = Arrays.asList(ids);
|
||||
List trimmedIds = new ArrayList(idList);
|
||||
for (ListIterator i = trimmedIds.listIterator(); i.hasNext();) {
|
||||
final List trimmedIds = new ArrayList(idList);
|
||||
|
||||
for (final ListIterator i = trimmedIds.listIterator(); i.hasNext();) {
|
||||
String id = (String) i.next();
|
||||
UIComponent component = findComponent(id);
|
||||
Assert.notNull(component, "Component with id " + id + " does not exist.");
|
||||
while (!(component.getParent() instanceof UIViewRoot)) {
|
||||
component = component.getParent();
|
||||
if (trimmedIds.contains(component.getClientId(context))) {
|
||||
i.remove();
|
||||
invokeOnComponent(context, id, new ContextCallback() {
|
||||
public void invokeContextCallback(FacesContext context, UIComponent component) {
|
||||
while (!(component.getParent() instanceof UIViewRoot)) {
|
||||
component = component.getParent();
|
||||
if (trimmedIds.contains(component.getClientId(context))) {
|
||||
i.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
return (String[]) trimmedIds.toArray(new String[trimmedIds.size()]);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
package org.springframework.faces.ui;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.faces.component.UIComponent;
|
||||
import javax.faces.context.FacesContext;
|
||||
|
||||
import org.springframework.faces.ui.resource.FlowResourceHelper;
|
||||
|
||||
public abstract class BaseDojoParentComponentRenderer extends BaseSpringFacesParentComponentRenderer {
|
||||
|
||||
private String dojoJsResourceUri = "/dojo/dojo.js";
|
||||
|
||||
private String dijitThemePath = "/dijit/themes/";
|
||||
|
||||
private String dijitTheme = "tundra";
|
||||
|
||||
private String springFacesDojoJsResourceUri = "/spring-faces/SpringFaces-Dojo.js";
|
||||
|
||||
private FlowResourceHelper resourceHelper = new FlowResourceHelper();
|
||||
|
||||
public void encodeBegin(FacesContext context, UIComponent component) throws IOException {
|
||||
resourceHelper.renderStyleLink(context, dijitThemePath + dijitTheme + "/" + dijitTheme + ".css");
|
||||
|
||||
resourceHelper.renderScriptLink(context, dojoJsResourceUri);
|
||||
|
||||
resourceHelper.renderScriptLink(context, springFacesDojoJsResourceUri);
|
||||
|
||||
super.encodeBegin(context, component);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
package org.springframework.faces.ui;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.faces.component.UIComponent;
|
||||
import javax.faces.context.FacesContext;
|
||||
import javax.faces.context.ResponseWriter;
|
||||
import javax.faces.render.Renderer;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
abstract class BaseHtmlParentTagRenderer extends Renderer {
|
||||
|
||||
protected Log log = LogFactory.getLog(BaseHtmlParentTagRenderer.class);
|
||||
|
||||
/**
|
||||
* Default {@link RenderAttributeCallback} that just renders the tag attribute as a pass-through value if the value
|
||||
* is not null.
|
||||
*/
|
||||
private RenderAttributeCallback defaultRenderAttributeCallback = new RenderAttributeCallback() {
|
||||
public void doRender(FacesContext context, ResponseWriter writer, UIComponent component, String attribute,
|
||||
Object attributeValue, String property) throws IOException {
|
||||
if (attributeValue != null) {
|
||||
writer.writeAttribute(attribute, attributeValue, property);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Renders the opening portion of the tag, prior to any children.
|
||||
*/
|
||||
public void encodeBegin(FacesContext context, UIComponent component) throws IOException {
|
||||
ResponseWriter writer = context.getResponseWriter();
|
||||
writer.startElement(getRenderedTagName(null), component);
|
||||
writeAttributes(context, component);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the attributes for this tag.
|
||||
* @param context the current {@link FacesContext}
|
||||
* @param component the {@link UIComponent} being rendered
|
||||
* @throws IOException
|
||||
*/
|
||||
protected void writeAttributes(FacesContext context, UIComponent component) throws IOException {
|
||||
for (int i = 0; i < getAttributesToRender(component).length; i++) {
|
||||
try {
|
||||
String attribute = getAttributesToRender(component)[i];
|
||||
String property = attribute;
|
||||
if (getAttributeAliases(component).containsKey(attribute)) {
|
||||
property = (String) getAttributeAliases(component).get(attribute);
|
||||
}
|
||||
Object attributeValue = component.getAttributes().get(property);
|
||||
|
||||
RenderAttributeCallback callback = defaultRenderAttributeCallback;
|
||||
if (getAttributeCallbacks(null).containsKey(attribute)) {
|
||||
callback = (RenderAttributeCallback) getAttributeCallbacks(component).get(attribute);
|
||||
}
|
||||
callback.doRender(context, context.getResponseWriter(), component, attribute, attributeValue, property);
|
||||
} catch (IllegalArgumentException ex) {
|
||||
// Attribute not found - Skip this attribute and continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the tag after children have been rendered.
|
||||
*/
|
||||
public void encodeEnd(FacesContext context, UIComponent component) throws IOException {
|
||||
ResponseWriter writer = context.getResponseWriter();
|
||||
writer.endElement(getRenderedTagName(null));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param component TODO
|
||||
* @return the name of the tag to be rendered.
|
||||
*/
|
||||
protected abstract String getRenderedTagName(UIComponent component);
|
||||
|
||||
/**
|
||||
* @param component TODO
|
||||
* @return an array of the tag attributes to be rendered
|
||||
*/
|
||||
protected abstract String[] getAttributesToRender(UIComponent component);
|
||||
|
||||
/**
|
||||
* @param component TODO
|
||||
* @return a map that returns the bean property name for any attribute that doesn't map directly (i.e., the 'class'
|
||||
* attribute maps to the 'styleClass' bean property)
|
||||
*/
|
||||
protected Map getAttributeAliases(UIComponent component) {
|
||||
return HTML.STANDARD_ATTRIBUTE_ALIASES;
|
||||
};
|
||||
|
||||
/**
|
||||
* @param component TODO
|
||||
* @return a map of registered {@link RenderAttributeCallback}s for attributes that require special rendering logic
|
||||
*/
|
||||
protected Map getAttributeCallbacks(UIComponent component) {
|
||||
return Collections.EMPTY_MAP;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package org.springframework.faces.ui;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.faces.component.UIComponent;
|
||||
import javax.faces.context.FacesContext;
|
||||
import javax.faces.context.ResponseWriter;
|
||||
|
||||
public abstract class BaseParentComponentRenderer extends BaseHtmlParentTagRenderer {
|
||||
|
||||
private Map attributeCallbacks;
|
||||
|
||||
private RenderAttributeCallback idCallback = new RenderAttributeCallback() {
|
||||
public void doRender(FacesContext context, ResponseWriter writer, UIComponent component, String attribute,
|
||||
Object attributeValue, String property) throws IOException {
|
||||
writer.writeAttribute(attribute, component.getClientId(context), property);
|
||||
}
|
||||
};
|
||||
|
||||
private RenderAttributeCallback disabledCallback = new RenderAttributeCallback() {
|
||||
public void doRender(FacesContext context, ResponseWriter writer, UIComponent component, String attribute,
|
||||
Object attributeValue, String property) throws IOException {
|
||||
if (Boolean.TRUE.equals(attributeValue)) {
|
||||
writer.writeAttribute(attribute, "disabled", property);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
protected Map getAttributeCallbacks(UIComponent component) {
|
||||
if (attributeCallbacks == null) {
|
||||
attributeCallbacks = new HashMap();
|
||||
attributeCallbacks.put("id", idCallback);
|
||||
attributeCallbacks.put("name", idCallback);
|
||||
attributeCallbacks.put("disabled", disabledCallback);
|
||||
}
|
||||
return attributeCallbacks;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package org.springframework.faces.ui;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.faces.component.UIComponent;
|
||||
import javax.faces.context.FacesContext;
|
||||
|
||||
import org.springframework.faces.ui.resource.FlowResourceHelper;
|
||||
|
||||
public abstract class BaseSpringFacesParentComponentRenderer extends BaseParentComponentRenderer {
|
||||
|
||||
private String springFacesJsResourceUri = "/spring-faces/SpringFaces.js";
|
||||
|
||||
private FlowResourceHelper resourceHelper = new FlowResourceHelper();
|
||||
|
||||
public void encodeBegin(FacesContext context, UIComponent component) throws IOException {
|
||||
|
||||
resourceHelper.renderScriptLink(context, springFacesJsResourceUri);
|
||||
|
||||
super.encodeBegin(context, component);
|
||||
}
|
||||
}
|
||||
@@ -38,7 +38,7 @@ public class DojoRenderer extends SpringFacesRenderer {
|
||||
|
||||
super.encodeBegin(context, component);
|
||||
|
||||
resourceHelper.renderStyleLink(context, dijitThemePath + "/" + dijitTheme + "/" + dijitTheme + ".css");
|
||||
resourceHelper.renderStyleLink(context, dijitThemePath + dijitTheme + "/" + dijitTheme + ".css");
|
||||
|
||||
resourceHelper.renderScriptLink(context, dojoJsResourceUri);
|
||||
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
package org.springframework.faces.ui;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
final class HTML {
|
||||
|
||||
/**
|
||||
* Standard HTML attributes
|
||||
*/
|
||||
public static final String[] STANDARD_ATTRIBUTES = new String[] { "id", "class", "style", "title", "dir", "lang",
|
||||
"accesskey", "tabindex" };
|
||||
|
||||
public static final Map STANDARD_ATTRIBUTE_ALIASES = new HashMap();
|
||||
|
||||
/**
|
||||
* Standard window events - only valid in body and frameset elements
|
||||
*/
|
||||
public static final String[] WINDOW_EVENTS = new String[] { "onload", "onunload" };
|
||||
|
||||
/**
|
||||
* Standard form events
|
||||
*/
|
||||
public static final String[] FORM_EVENTS = new String[] { "onsubmit", "onreset" };
|
||||
|
||||
/**
|
||||
* Standard form element events
|
||||
*/
|
||||
public static final String[] COMMON_ELEMENT_EVENTS = new String[] { "onchange", "onselect", "onblur", "onfocus" };
|
||||
|
||||
/**
|
||||
* Standard keyboard events
|
||||
*/
|
||||
public static final String[] KEYBOARD_EVENTS = new String[] { "onkeydown", "onkeypress", "onkeyup" };
|
||||
|
||||
/**
|
||||
* Standard mouse events
|
||||
*/
|
||||
public static final String[] MOUSE_EVENTS = new String[] { "onclick", "ondblclick", "onmousedown", "onmousemove",
|
||||
"onmouseout", "onmouseover", "onmouseup" };
|
||||
|
||||
/**
|
||||
* Button attributes
|
||||
*/
|
||||
public static final String[] BUTTON_ATTRIBUTES = new String[] { "disabled", "name", "type", "value" };
|
||||
|
||||
/**
|
||||
* Anchor attributes
|
||||
*/
|
||||
public static final Object[] ANCHOR_ATTRIBUTES = new String[] { "charset", "coords", "href", "hreflang", "name",
|
||||
"rel", "rev", "shape", "target", "type" };
|
||||
|
||||
static {
|
||||
STANDARD_ATTRIBUTE_ALIASES.put("class", "styleClass");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package org.springframework.faces.ui;
|
||||
|
||||
import javax.faces.component.UICommand;
|
||||
import javax.faces.context.FacesContext;
|
||||
import javax.faces.el.ValueBinding;
|
||||
|
||||
public class ProgressiveCommandButton extends UICommand {
|
||||
|
||||
private String type = "submit";
|
||||
|
||||
public String getRendererType() {
|
||||
return "spring.faces.ProgressiveCommandButton";
|
||||
}
|
||||
|
||||
private Boolean disabled;
|
||||
|
||||
private Boolean ajaxEnabled = Boolean.TRUE;
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(String type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public Boolean getDisabled() {
|
||||
if (disabled != null) {
|
||||
return disabled;
|
||||
}
|
||||
ValueBinding vb = getValueBinding("disabled");
|
||||
return vb != null ? (Boolean) vb.getValue(getFacesContext()) : Boolean.FALSE;
|
||||
}
|
||||
|
||||
public void setDisabled(Boolean disabled) {
|
||||
this.disabled = disabled;
|
||||
}
|
||||
|
||||
public Boolean getAjaxEnabled() {
|
||||
return ajaxEnabled;
|
||||
}
|
||||
|
||||
public void setAjaxEnabled(Boolean ajaxEnabled) {
|
||||
this.ajaxEnabled = ajaxEnabled;
|
||||
}
|
||||
|
||||
public Object saveState(FacesContext context) {
|
||||
Object[] values = new Object[4];
|
||||
values[0] = super.saveState(context);
|
||||
values[1] = type;
|
||||
values[2] = disabled;
|
||||
values[3] = ajaxEnabled;
|
||||
return values;
|
||||
}
|
||||
|
||||
public void restoreState(FacesContext context, Object state) {
|
||||
Object values[] = (Object[]) state;
|
||||
super.restoreState(context, values[0]);
|
||||
type = (String) values[1];
|
||||
disabled = (Boolean) values[2];
|
||||
ajaxEnabled = (Boolean) values[3];
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
package org.springframework.faces.ui;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.faces.component.UIComponent;
|
||||
import javax.faces.component.UIParameter;
|
||||
import javax.faces.context.FacesContext;
|
||||
import javax.faces.context.ResponseWriter;
|
||||
import javax.faces.event.ActionEvent;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
public class ProgressiveCommandButtonRenderer extends BaseDojoParentComponentRenderer {
|
||||
|
||||
private static String[] ATTRIBUTES_TO_RENDER;
|
||||
|
||||
private static String BUTTON_TAG_NAME = "button";
|
||||
|
||||
static {
|
||||
List tempList = new ArrayList();
|
||||
tempList.addAll(Arrays.asList(HTML.STANDARD_ATTRIBUTES));
|
||||
tempList.addAll(Arrays.asList(HTML.BUTTON_ATTRIBUTES));
|
||||
tempList.addAll(Arrays.asList(HTML.COMMON_ELEMENT_EVENTS));
|
||||
tempList.addAll(Arrays.asList(HTML.KEYBOARD_EVENTS));
|
||||
tempList.addAll(Arrays.asList(HTML.MOUSE_EVENTS));
|
||||
ATTRIBUTES_TO_RENDER = new String[tempList.size()];
|
||||
ListIterator i = tempList.listIterator();
|
||||
while (i.hasNext()) {
|
||||
ATTRIBUTES_TO_RENDER[i.nextIndex()] = (String) i.next();
|
||||
}
|
||||
}
|
||||
|
||||
private Map attributeCallbacks;
|
||||
|
||||
private RenderAttributeCallback onclickCallback = new RenderAttributeCallback() {
|
||||
|
||||
public void doRender(FacesContext context, ResponseWriter writer, UIComponent component, String attribute,
|
||||
Object attributeValue, String property) throws IOException {
|
||||
StringBuffer onclick = new StringBuffer();
|
||||
if (attributeValue != null) {
|
||||
String originalOnclick = attributeValue.toString().trim();
|
||||
if (!originalOnclick.endsWith(";")) {
|
||||
originalOnclick += ";";
|
||||
}
|
||||
onclick.append(originalOnclick);
|
||||
}
|
||||
|
||||
Boolean ajaxEnabled = (Boolean) component.getAttributes().get("ajaxEnabled");
|
||||
String processIds = (String) component.getAttributes().get("processIds");
|
||||
String renderIds = (String) component.getAttributes().get("renderIds");
|
||||
if (Boolean.TRUE.equals(ajaxEnabled)) {
|
||||
if (StringUtils.hasText(processIds) && !processIds.contains(component.getClientId(context))) {
|
||||
processIds = component.getClientId(context) + ", " + processIds;
|
||||
} else if (!StringUtils.hasText(processIds)) {
|
||||
processIds = component.getClientId(context);
|
||||
}
|
||||
if (!StringUtils.hasText(renderIds)) {
|
||||
renderIds = processIds;
|
||||
}
|
||||
onclick.append("SpringFaces.AjaxHandler.submitForm('" + component.getClientId(context) + "', ");
|
||||
onclick.append("'" + RendererUtils.getFormId(context, component) + "', ");
|
||||
onclick.append("'" + processIds + "', '" + renderIds + "', " + encodeParams(context, component)
|
||||
+ "); return false;");
|
||||
} else {
|
||||
onclick.append(getOnClickNoAjax(context, component));
|
||||
}
|
||||
|
||||
if (onclick.length() > 0) {
|
||||
writer.writeAttribute(attribute, onclick.toString(), property);
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
protected Map getAttributeCallbacks(UIComponent component) {
|
||||
if (attributeCallbacks == null) {
|
||||
attributeCallbacks = new HashMap();
|
||||
attributeCallbacks.putAll(super.getAttributeCallbacks(component));
|
||||
attributeCallbacks.put("onclick", onclickCallback);
|
||||
}
|
||||
return attributeCallbacks;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is a hook for subclasses to provide special onclick behavior in the non-ajax case
|
||||
* @return
|
||||
*/
|
||||
protected String getOnClickNoAjax(FacesContext context, UIComponent component) {
|
||||
// No special behavior necessary for CommandButton
|
||||
return "";
|
||||
}
|
||||
|
||||
protected String[] getAttributesToRender(UIComponent component) {
|
||||
return ATTRIBUTES_TO_RENDER;
|
||||
}
|
||||
|
||||
protected String getRenderedTagName(UIComponent component) {
|
||||
return BUTTON_TAG_NAME;
|
||||
}
|
||||
|
||||
public void decode(FacesContext context, UIComponent component) {
|
||||
if (context.getExternalContext().getRequestParameterMap().containsKey(component.getClientId(context))) {
|
||||
component.queueEvent(new ActionEvent(component));
|
||||
}
|
||||
}
|
||||
|
||||
public void encodeChildren(FacesContext context, UIComponent component) throws IOException {
|
||||
// If the button has no children, render out the "value" as text.
|
||||
ResponseWriter writer = context.getResponseWriter();
|
||||
String valueAttr = "value";
|
||||
if (component.getAttributes().get(valueAttr) != null) {
|
||||
writer.writeText(component.getAttributes().get(valueAttr), valueAttr);
|
||||
}
|
||||
super.encodeChildren(context, component);
|
||||
}
|
||||
|
||||
public boolean getRendersChildren() {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected String encodeParams(FacesContext context, UIComponent component) {
|
||||
StringBuffer paramArray = new StringBuffer();
|
||||
paramArray.append("[");
|
||||
for (int i = 0; i < component.getChildCount(); i++) {
|
||||
if (component.getChildren().get(i) instanceof UIParameter) {
|
||||
UIParameter param = (UIParameter) component.getChildren().get(i);
|
||||
Assert.hasText(param.getName(),
|
||||
"UIParameter requires a name when used as a child of a UICommand component");
|
||||
if (paramArray.length() > 1) {
|
||||
paramArray.append(", ");
|
||||
}
|
||||
paramArray.append("{name : '" + param.getName() + "'");
|
||||
paramArray.append(", value : '" + param.getValue() + "'}");
|
||||
}
|
||||
}
|
||||
paramArray.append("]");
|
||||
return paramArray.toString();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package org.springframework.faces.ui;
|
||||
|
||||
public class ProgressiveCommandLink extends ProgressiveCommandButton {
|
||||
|
||||
public String getRendererType() {
|
||||
return "spring.faces.ProgressiveCommandLink";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,228 @@
|
||||
package org.springframework.faces.ui;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.StringWriter;
|
||||
import java.io.Writer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.faces.component.UIComponent;
|
||||
import javax.faces.context.FacesContext;
|
||||
import javax.faces.context.ResponseWriter;
|
||||
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.faces.webflow.JsfUtils;
|
||||
|
||||
public class ProgressiveCommandLinkRenderer extends ProgressiveCommandButtonRenderer {
|
||||
|
||||
private static String[] ATTRIBUTES_TO_RENDER;
|
||||
|
||||
private static String ANCHOR_TAG_NAME = "a";
|
||||
|
||||
static {
|
||||
List tempList = new ArrayList();
|
||||
tempList.addAll(Arrays.asList(HTML.STANDARD_ATTRIBUTES));
|
||||
tempList.addAll(Arrays.asList(HTML.ANCHOR_ATTRIBUTES));
|
||||
tempList.addAll(Arrays.asList(HTML.COMMON_ELEMENT_EVENTS));
|
||||
tempList.addAll(Arrays.asList(HTML.KEYBOARD_EVENTS));
|
||||
tempList.addAll(Arrays.asList(HTML.MOUSE_EVENTS));
|
||||
ATTRIBUTES_TO_RENDER = new String[tempList.size()];
|
||||
ListIterator i = tempList.listIterator();
|
||||
while (i.hasNext()) {
|
||||
ATTRIBUTES_TO_RENDER[i.nextIndex()] = (String) i.next();
|
||||
}
|
||||
}
|
||||
|
||||
private Map attributeCallbacks;
|
||||
|
||||
private RenderAttributeCallback hrefCallback = new RenderAttributeCallback() {
|
||||
public void doRender(FacesContext context, ResponseWriter writer, UIComponent component, String attribute,
|
||||
Object attributeValue, String property) throws IOException {
|
||||
writer.writeAttribute(attribute, "#", property);
|
||||
}
|
||||
};
|
||||
|
||||
private RenderAttributeCallback classCallback = new RenderAttributeCallback() {
|
||||
public void doRender(FacesContext context, ResponseWriter writer, UIComponent component, String attribute,
|
||||
Object attributeValue, String property) throws IOException {
|
||||
String classToAdd = "progressiveLink";
|
||||
if (attributeValue != null) {
|
||||
attributeValue = attributeValue.toString() + " " + classToAdd;
|
||||
} else {
|
||||
attributeValue = classToAdd;
|
||||
}
|
||||
writer.writeAttribute(attribute, attributeValue, property);
|
||||
}
|
||||
};
|
||||
|
||||
public void encodeBegin(FacesContext context, UIComponent component) throws IOException {
|
||||
// No need to be progressive if this is an AJAX request since it can be assumed JavaScript is enabled
|
||||
if (!JsfUtils.isAsynchronousFlowRequest()) {
|
||||
// Render a plain submit button first if this is not an ajax request
|
||||
ProgressiveCommandButton button = new ProgressiveCommandButton();
|
||||
button.getAttributes().putAll(component.getAttributes());
|
||||
BeanUtils.copyProperties(component, button);
|
||||
button.setAjaxEnabled(Boolean.FALSE);
|
||||
button.encodeBegin(context);
|
||||
button.encodeChildren(context);
|
||||
button.encodeEnd(context);
|
||||
|
||||
// Now render the link's HTML into a javascript variable
|
||||
ResponseWriter writer = context.getResponseWriter();
|
||||
writer.startElement("script", component);
|
||||
String scriptVarStart = "var " + component.getClientId(context).replaceAll(":", "_") + "_link = \"";
|
||||
writer.writeText(scriptVarStart, null);
|
||||
writer = new DoubleQuoteEscapingWriter(writer);
|
||||
context.setResponseWriter(writer);
|
||||
}
|
||||
super.encodeBegin(context, component);
|
||||
}
|
||||
|
||||
public void encodeEnd(FacesContext context, UIComponent component) throws IOException {
|
||||
super.encodeEnd(context, component);
|
||||
|
||||
StringBuffer advisorParams = new StringBuffer();
|
||||
advisorParams.append("{");
|
||||
advisorParams.append("targetElId : '" + component.getClientId(context) + "'");
|
||||
|
||||
ResponseWriter writer = context.getResponseWriter();
|
||||
// Close the script variable started in encodeBegin if this is not an AJAX request
|
||||
if (!JsfUtils.isAsynchronousFlowRequest()) {
|
||||
DoubleQuoteEscapingWriter tempWriter = (DoubleQuoteEscapingWriter) writer;
|
||||
String scriptVarValue = tempWriter.escapeResult();
|
||||
context.setResponseWriter(tempWriter.original);
|
||||
writer = tempWriter.original;
|
||||
writer.writeText(scriptVarValue, null);
|
||||
|
||||
String scriptVarEnd = "\";\n";
|
||||
writer.writeText(scriptVarEnd, null);
|
||||
|
||||
advisorParams.append(", linkHtml : " + component.getClientId(context).replaceAll(":", "_") + "_link");
|
||||
writer.endElement("script");
|
||||
}
|
||||
|
||||
advisorParams.append("}");
|
||||
StringBuffer advisorScript = new StringBuffer();
|
||||
advisorScript.append("SpringFaces.advisors.push(new SpringFaces.DojoCommandLinkAdvisor("
|
||||
+ advisorParams.toString() + ")");
|
||||
// Apply the advisor immediately if this is an AJAX request
|
||||
if (JsfUtils.isAsynchronousFlowRequest()) {
|
||||
advisorScript.append(".apply()");
|
||||
}
|
||||
advisorScript.append(");");
|
||||
writer.startElement("script", component);
|
||||
writer.writeText(advisorScript, null);
|
||||
writer.endElement("script");
|
||||
}
|
||||
|
||||
protected String[] getAttributesToRender(UIComponent component) {
|
||||
return ATTRIBUTES_TO_RENDER;
|
||||
}
|
||||
|
||||
protected String getRenderedTagName(UIComponent component) {
|
||||
return ANCHOR_TAG_NAME;
|
||||
}
|
||||
|
||||
protected Map getAttributeCallbacks(UIComponent component) {
|
||||
if (attributeCallbacks == null) {
|
||||
attributeCallbacks = new HashMap();
|
||||
attributeCallbacks.putAll(super.getAttributeCallbacks(component));
|
||||
attributeCallbacks.put("href", hrefCallback);
|
||||
attributeCallbacks.put("class", classCallback);
|
||||
}
|
||||
return attributeCallbacks;
|
||||
}
|
||||
|
||||
protected String getOnClickNoAjax(FacesContext context, UIComponent component) {
|
||||
String params = encodeParams(context, component);
|
||||
StringBuffer onclick = new StringBuffer();
|
||||
onclick.append("this.submitFormFromLink('" + RendererUtils.getFormId(context, component) + "','"
|
||||
+ component.getClientId(context) + "', " + params + "); return false;");
|
||||
return onclick.toString();
|
||||
}
|
||||
|
||||
private class DoubleQuoteEscapingWriter extends ResponseWriter {
|
||||
|
||||
private ResponseWriter original;
|
||||
|
||||
private ResponseWriter clonedWriter;
|
||||
|
||||
private StringWriter buffer = new StringWriter();
|
||||
|
||||
public DoubleQuoteEscapingWriter(ResponseWriter original) {
|
||||
this.original = original;
|
||||
this.clonedWriter = original.cloneWithWriter(buffer);
|
||||
}
|
||||
|
||||
public String escapeResult() {
|
||||
String result = buffer.toString();
|
||||
result = result.replaceAll("\\\"", "\\\\\"");
|
||||
return result;
|
||||
}
|
||||
|
||||
public ResponseWriter cloneWithWriter(Writer arg0) {
|
||||
return clonedWriter.cloneWithWriter(arg0);
|
||||
}
|
||||
|
||||
public void endDocument() throws IOException {
|
||||
clonedWriter.endDocument();
|
||||
}
|
||||
|
||||
public void endElement(String arg0) throws IOException {
|
||||
clonedWriter.endElement(arg0);
|
||||
}
|
||||
|
||||
public void flush() throws IOException {
|
||||
clonedWriter.flush();
|
||||
}
|
||||
|
||||
public String getCharacterEncoding() {
|
||||
return clonedWriter.getCharacterEncoding();
|
||||
}
|
||||
|
||||
public String getContentType() {
|
||||
return clonedWriter.getContentType();
|
||||
}
|
||||
|
||||
public void startDocument() throws IOException {
|
||||
clonedWriter.startDocument();
|
||||
}
|
||||
|
||||
public void startElement(String arg0, UIComponent arg1) throws IOException {
|
||||
clonedWriter.startElement(arg0, arg1);
|
||||
}
|
||||
|
||||
public void writeAttribute(String arg0, Object arg1, String arg2) throws IOException {
|
||||
clonedWriter.writeAttribute(arg0, arg1, arg2);
|
||||
}
|
||||
|
||||
public void writeComment(Object arg0) throws IOException {
|
||||
clonedWriter.writeComment(arg0);
|
||||
}
|
||||
|
||||
public void writeText(char[] arg0, int arg1, int arg2) throws IOException {
|
||||
clonedWriter.writeText(arg0, arg1, arg2);
|
||||
}
|
||||
|
||||
public void writeText(Object arg0, String arg1) throws IOException {
|
||||
clonedWriter.writeText(arg0, arg1);
|
||||
}
|
||||
|
||||
public void writeURIAttribute(String arg0, Object arg1, String arg2) throws IOException {
|
||||
clonedWriter.writeURIAttribute(arg0, arg1, arg2);
|
||||
}
|
||||
|
||||
public void close() throws IOException {
|
||||
clonedWriter.close();
|
||||
}
|
||||
|
||||
public void write(char[] cbuf, int off, int len) throws IOException {
|
||||
clonedWriter.write(cbuf, off, len);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package org.springframework.faces.ui;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.faces.component.UIComponent;
|
||||
import javax.faces.context.FacesContext;
|
||||
import javax.faces.context.ResponseWriter;
|
||||
|
||||
public interface RenderAttributeCallback {
|
||||
|
||||
public void doRender(FacesContext context, ResponseWriter writer, UIComponent component, String attribute,
|
||||
Object attributeValue, String property) throws IOException;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package org.springframework.faces.ui;
|
||||
|
||||
import javax.faces.FacesException;
|
||||
import javax.faces.component.UIComponent;
|
||||
import javax.faces.component.UIForm;
|
||||
import javax.faces.component.UIViewRoot;
|
||||
import javax.faces.context.FacesContext;
|
||||
|
||||
public class RendererUtils {
|
||||
|
||||
public static String getFormId(FacesContext context, UIComponent component) {
|
||||
if (component.getParent() instanceof UIForm) {
|
||||
return component.getParent().getClientId(context);
|
||||
} else if (component.getParent() instanceof UIViewRoot) {
|
||||
throw new FacesException("Could not render " + component.getClass().getName() + " component with id "
|
||||
+ component.getId() + " - no enclosing UIForm was found.");
|
||||
} else {
|
||||
return getFormId(context, component.getParent());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -28,7 +28,7 @@ import org.springframework.webflow.execution.RequestContextHolder;
|
||||
*
|
||||
* @author Jeremy Grelle
|
||||
*/
|
||||
class JsfUtils {
|
||||
public class JsfUtils {
|
||||
|
||||
public static void notifyAfterListeners(PhaseId phaseId, Lifecycle lifecycle, FacesContext context) {
|
||||
PhaseEvent afterPhaseEvent = new PhaseEvent(context, phaseId, lifecycle);
|
||||
|
||||
@@ -89,8 +89,8 @@ public class JsfViewFactory implements ViewFactory {
|
||||
logger.debug("View root restored for '" + viewName + "'");
|
||||
}
|
||||
view = createJsfView(viewRoot, lifecycle, context);
|
||||
facesContext.setViewRoot(viewRoot);
|
||||
processComponentBinding(facesContext, viewRoot);
|
||||
facesContext.setViewRoot(view.getViewRoot());
|
||||
processComponentBinding(facesContext, view.getViewRoot());
|
||||
restored = true;
|
||||
} else {
|
||||
if (logger.isDebugEnabled()) {
|
||||
|
||||
Reference in New Issue
Block a user