-Initial AJAX support

-Upgrading Dojo to version 1.0
This commit is contained in:
Jeremy Grelle
2007-12-03 21:35:18 +00:00
parent fa7364fe84
commit 433e003356
386 changed files with 26226 additions and 10192 deletions

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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);
}

View File

@@ -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.");
}
}
}
}

View File

@@ -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);
}
}

View File

@@ -0,0 +1,7 @@
package org.springframework.faces.ui;
import javax.faces.component.UICommand;
public class AjaxEventInterceptor extends UICommand {
}

View File

@@ -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));
}
}
}

View File

@@ -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()]);
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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);

View File

@@ -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");
}
}

View File

@@ -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];
}
}

View File

@@ -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();
}
}

View File

@@ -0,0 +1,9 @@
package org.springframework.faces.ui;
public class ProgressiveCommandLink extends ProgressiveCommandButton {
public String getRendererType() {
return "spring.faces.ProgressiveCommandLink";
}
}

View File

@@ -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);
}
}
}

View File

@@ -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;
}

View File

@@ -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());
}
}
}

View File

@@ -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);

View File

@@ -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()) {