Support for Ajax based redirects, and modal view states.
This commit is contained in:
@@ -104,6 +104,23 @@ SpringFaces.DojoAjaxHandler.prototype = {
|
||||
handleResponse: function(response, ioArgs) {
|
||||
//alert("handling the response");
|
||||
|
||||
//First check if this response should redirect
|
||||
var redirectURL = ioArgs.xhr.getResponseHeader('Flow-Redirect-URL');
|
||||
var modalViewHeader = ioArgs.xhr.getResponseHeader('Flow-Modal-View');
|
||||
var modalView = dojo.isString(modalViewHeader) && modalViewHeader.length > 0;
|
||||
|
||||
if (dojo.isString(redirectURL) && redirectURL.length > 0) {
|
||||
if (modalView) {
|
||||
//render a popup with the new URL
|
||||
SpringFaces.AjaxHandler.renderURLToModalDialog(redirectURL, ioArgs);
|
||||
return response;
|
||||
}
|
||||
else {
|
||||
window.location.pathname = redirectURL;
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
//Extract and store all <script> elements from the response
|
||||
var scriptPattern = '(?:<script.*?>)((\n|\r|.)*?)(?:<\/script>)';
|
||||
var extractedScriptNodes = [];
|
||||
@@ -131,6 +148,12 @@ SpringFaces.DojoAjaxHandler.prototype = {
|
||||
var newNodes = tempContainer.addContent(response, "first").query("#ajaxResponse > *").orphan();
|
||||
tempContainer.orphan();
|
||||
|
||||
//For a modal view, just dump the new nodes into a modal dialog
|
||||
if (modalView) {
|
||||
SpringFaces.AjaxHandler.renderNodeListToModalDialog(newNodes);
|
||||
return response;
|
||||
}
|
||||
|
||||
//Insert the new DOM nodes and update the Form's action URL
|
||||
newNodes.forEach(function(item) {
|
||||
if (item.id != null && item.id != "") {
|
||||
@@ -151,6 +174,24 @@ SpringFaces.DojoAjaxHandler.prototype = {
|
||||
//alert("handling an error");
|
||||
console.error("HTTP status code: ", ioArgs.xhr.status);
|
||||
return response;
|
||||
},
|
||||
|
||||
renderURLToModalDialog: function(url, ioArgs) {
|
||||
dojo.require("dijit.Dialog");
|
||||
|
||||
url = url + "?"+dojo.objectToQuery(ioArgs.args.content);
|
||||
|
||||
var dialog = new dijit.Dialog({href: url});
|
||||
dialog.show();
|
||||
|
||||
},
|
||||
|
||||
renderNodeListToModalDialog: function(nodes) {
|
||||
dojo.require("dijit.Dialog");
|
||||
|
||||
var dialog = new dijit.Dialog({});
|
||||
dialog.setContent(nodes);
|
||||
dialog.show();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,252 @@
|
||||
SpringUI.DojoValidatingFieldAdvisor = function(config){
|
||||
|
||||
dojo.mixin(this, config);
|
||||
};
|
||||
|
||||
SpringUI.DojoValidatingFieldAdvisor.prototype = {
|
||||
|
||||
targetElId : "",
|
||||
decoratorType : "",
|
||||
decorator : null,
|
||||
decoratorAttrs : "",
|
||||
|
||||
apply : function(){
|
||||
|
||||
this.decorator = eval("new "+ this.decoratorType + "(" + this.decoratorAttrs +", dojo.byId('"+this.targetElId+"'));" );
|
||||
this.decorator.startup();
|
||||
|
||||
//return this to support method chaining
|
||||
return this;
|
||||
},
|
||||
|
||||
validate : function(){
|
||||
var isValid = this.decorator.isValid(false);
|
||||
if (!isValid) {
|
||||
this.decorator.state = "Error";
|
||||
this.decorator._setStateClass();
|
||||
}
|
||||
return isValid;
|
||||
}
|
||||
};
|
||||
|
||||
SpringUI.ValidatingFieldAdvisor = SpringUI.DojoValidatingFieldAdvisor;
|
||||
|
||||
SpringUI.DojoRemoteEventAdvisor = function(config){
|
||||
dojo.mixin(this, config);
|
||||
};
|
||||
|
||||
SpringUI.DojoRemoteEventAdvisor.prototype = {
|
||||
|
||||
event : "",
|
||||
targetId : "",
|
||||
sourceId : "",
|
||||
formId : "",
|
||||
processIds : "",
|
||||
renderIds : "",
|
||||
params : [],
|
||||
connection : null,
|
||||
|
||||
apply : function() {
|
||||
connection = dojo.connect(dojo.byId(this.targetId), this.event, this, "submit");
|
||||
return this;
|
||||
},
|
||||
|
||||
cleanup : function(){
|
||||
dojo.disconnect(this.connection);
|
||||
},
|
||||
|
||||
submit : function(event){
|
||||
if (this.sourceId == ""){
|
||||
this.sourceId = this.targetId;
|
||||
}
|
||||
if(this.formId == ""){
|
||||
SpringUI.RemotingHandler.getResource(this.sourceId, this.processIds, this.renderIds);
|
||||
} else {
|
||||
SpringUI.RemotingHandler.submitForm(this.sourceId, this.formId, this.processIds, this.renderIds, this.params);
|
||||
}
|
||||
dojo.stopEvent(event);
|
||||
}
|
||||
};
|
||||
|
||||
SpringUI.RemoteEventAdvisor = SpringUI.DojoRemoteEventAdvisor;
|
||||
|
||||
SpringUI.DojoRemotingHandler = function(){};
|
||||
|
||||
SpringUI.DojoRemotingHandler.prototype = {
|
||||
|
||||
submitForm : function(/*String */ sourceId, /*String*/formId, /*String*/ processIds, /*String*/renderIds, /*Array*/ params) {
|
||||
var content = new Object();
|
||||
var sourceComponent = dojo.byId(sourceId);
|
||||
content['processIds'] = processIds;
|
||||
content['renderIds'] = renderIds;
|
||||
|
||||
if (sourceComponent != null){
|
||||
if(sourceComponent.value) {
|
||||
content[sourceComponent.name] = sourceComponent.value;
|
||||
} else {
|
||||
content[sourceComponent.name] = sourceId;
|
||||
}
|
||||
}
|
||||
|
||||
dojo.forEach(params, function(param){
|
||||
content[param.name] = param.value;
|
||||
});
|
||||
|
||||
content['ajaxSource'] = sourceId;
|
||||
|
||||
dojo.xhrPost({
|
||||
|
||||
content: content,
|
||||
|
||||
form: formId,
|
||||
|
||||
handleAs: "text",
|
||||
|
||||
headers: {"Accept" : "text/html;type=ajax"},
|
||||
|
||||
// The LOAD function will be called on a successful response.
|
||||
load: this.handleResponse,
|
||||
|
||||
// The ERROR function will be called in an error case.
|
||||
error: this.handleError
|
||||
});
|
||||
|
||||
},
|
||||
|
||||
getResource: function(/*String */ sourceId, /*String*/ processIds, /*String*/renderIds) {
|
||||
var content = new Object();
|
||||
var sourceComponent = dojo.byId(sourceId);
|
||||
content['processIds'] = processIds;
|
||||
content['renderIds'] = renderIds;
|
||||
content['ajaxSource'] = sourceId;
|
||||
|
||||
dojo.xhrGet({
|
||||
|
||||
url: sourceComponent.href,
|
||||
|
||||
content: content,
|
||||
|
||||
handleAs: "text",
|
||||
|
||||
headers: {"Accept" : "text/html;type=ajax"},
|
||||
|
||||
load: this.handleResponse,
|
||||
|
||||
error: this.handleError
|
||||
});
|
||||
},
|
||||
|
||||
handleResponse: function(response, ioArgs) {
|
||||
//alert("handling the response");
|
||||
|
||||
//Extract and store all <script> elements from the response
|
||||
var scriptPattern = '(?:<script.*?>)((\n|\r|.)*?)(?:<\/script>)';
|
||||
var extractedScriptNodes = [];
|
||||
var matchAll = new RegExp(scriptPattern, 'img');
|
||||
var matchOne = new RegExp(scriptPattern, 'im');
|
||||
|
||||
var scriptNodes = response.match(matchAll);
|
||||
if (scriptNodes != null)
|
||||
{
|
||||
for (var i=0; i<scriptNodes.length; i++)
|
||||
{
|
||||
var script = (scriptNodes[i].match(matchOne) || ['',''])[1];
|
||||
script = script.replace(/<!--/mg,'').replace(/\/\/-->/mg,'');
|
||||
extractedScriptNodes.push(script);
|
||||
}
|
||||
}
|
||||
response = response.replace(matchAll, '');
|
||||
|
||||
//Extract the new DOM nodes from the response
|
||||
var tempDiv = dojo.doc.createElement("div");
|
||||
tempDiv.id="ajaxResponse";
|
||||
tempDiv.style.visibility= "hidden";
|
||||
document.body.appendChild(tempDiv);
|
||||
var tempContainer = new dojo.NodeList(tempDiv);
|
||||
var newNodes = tempContainer.addContent(response, "first").query("#ajaxResponse > *").orphan();
|
||||
tempContainer.orphan();
|
||||
|
||||
//Insert the new DOM nodes and update the Form's action URL
|
||||
newNodes.forEach(function(item) {
|
||||
if (item.id == 'flowExecutionUrl'){
|
||||
dojo.query("form").forEach(function (formNode) {
|
||||
formNode.action = item.firstChild.nodeValue;
|
||||
});
|
||||
} else if (item.id != null && item.id != "") {
|
||||
var target = dojo.byId(item.id);
|
||||
target.parentNode.replaceChild(item, target);
|
||||
}
|
||||
});
|
||||
|
||||
//Evaluate any script code
|
||||
dojo.forEach(extractedScriptNodes, function(script){
|
||||
dojo.eval(script);
|
||||
});
|
||||
|
||||
return response;
|
||||
},
|
||||
|
||||
handleError: function(response, ioArgs) {
|
||||
//alert("handling an error");
|
||||
console.error("HTTP status code: ", ioArgs.xhr.status);
|
||||
return response;
|
||||
}
|
||||
};
|
||||
|
||||
SpringUI.RemotingHandler = new SpringUI.DojoRemotingHandler();
|
||||
|
||||
SpringUI.DojoSubmitLinkAdvisor = function(config){
|
||||
dojo.mixin(this, config);
|
||||
};
|
||||
|
||||
SpringUI.DojoSubmitLinkAdvisor.prototype = {
|
||||
|
||||
targetElId : "",
|
||||
|
||||
linkHtml : "",
|
||||
|
||||
apply : function(){
|
||||
var advisedNode = dojo.byId(this.targetElId);
|
||||
if (!dojo.hasClass(advisedNode, "progressiveLink")) {
|
||||
//Node must be replaced
|
||||
var nodeToReplace = new dojo.NodeList(advisedNode);
|
||||
nodeToReplace.addContent(this.linkHtml, "after").orphan("*");
|
||||
//Get the new node
|
||||
advisedNode = dojo.byId(this.targetElId);
|
||||
}
|
||||
advisedNode.submitFormFromLink = this.submitFormFromLink;
|
||||
//return this to support method chaining
|
||||
return this;
|
||||
},
|
||||
|
||||
submitFormFromLink : function(/*String*/ formId, /*String*/ sourceId, /*Array of name,value params*/ params){
|
||||
var addedNodes = [];
|
||||
var formNode = dojo.byId(formId);
|
||||
var linkNode = document.createElement("input");
|
||||
linkNode.name = sourceId;
|
||||
linkNode.value = "submitted";
|
||||
addedNodes.push(linkNode);
|
||||
|
||||
dojo.forEach(params, function(param){
|
||||
var paramNode = document.createElement("input");
|
||||
paramNode.name=param.name;
|
||||
paramNode.value=param.value;
|
||||
addedNodes.push(paramNode);
|
||||
});
|
||||
|
||||
dojo.forEach(addedNodes, function(nodeToAdd){
|
||||
dojo.addClass(nodeToAdd, "SpringUILinkInput");
|
||||
dojo.place(nodeToAdd, formNode, "last");
|
||||
});
|
||||
|
||||
if ((formNode.onsubmit ? !formNode.onsubmit() : false) || !formNode.submit()) {
|
||||
dojo.forEach(addedNodes, function(hiddenNode){
|
||||
formNode.removeChild(hiddenNode);
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
SpringUI.SubmitLinkAdvisor = SpringUI.DojoSubmitLinkAdvisor;
|
||||
|
||||
dojo.addOnLoad(SpringUI.applyAdvisors);
|
||||
85
spring-faces/src/main/java/META-INF/spring-faces/SpringUI.js
Normal file
85
spring-faces/src/main/java/META-INF/spring-faces/SpringUI.js
Normal file
@@ -0,0 +1,85 @@
|
||||
SpringUI = {};
|
||||
|
||||
SpringUI.advisors = [];
|
||||
|
||||
SpringUI.advisorsApplied = false;
|
||||
|
||||
SpringUI.applyAdvisors = function(){
|
||||
if (!SpringUI.advisorsApplied) {
|
||||
for (var x=0; x<SpringUI.advisors.length; x++) {
|
||||
SpringUI.advisors[x].apply();
|
||||
}
|
||||
SpringUI.advisorsApplied = true;
|
||||
}
|
||||
};
|
||||
|
||||
SpringUI.validateAll = function(){
|
||||
var valid = true;
|
||||
for(x in SpringUI.advisors) {
|
||||
if (SpringUI.advisors[x].decorator &&
|
||||
!SpringUI.advisors[x].validate()) {
|
||||
valid = false;
|
||||
}
|
||||
}
|
||||
return valid;
|
||||
};
|
||||
|
||||
SpringUI.validateRequired = function(){
|
||||
var valid = true;
|
||||
for(x in SpringUI.advisors) {
|
||||
if(SpringUI.advisors[x].decorator &&
|
||||
SpringUI.advisors[x].isRequired() &&
|
||||
!SpringUI.advisors[x].validate())
|
||||
valid = false;
|
||||
}
|
||||
return valid;
|
||||
};
|
||||
|
||||
SpringUI.ValidatingFieldAdvisor = function(){};
|
||||
|
||||
SpringUI.ValidatingFieldAdvisor.prototype = {
|
||||
|
||||
targetElId : "",
|
||||
decoratorType : "",
|
||||
decorator : null,
|
||||
decoratorAttrs : "",
|
||||
|
||||
apply : function(){},
|
||||
|
||||
validate : function(){},
|
||||
|
||||
isRequired : function(){}
|
||||
};
|
||||
|
||||
SpringUI.RemoteEventAdvisor = function(){};
|
||||
|
||||
SpringUI.RemoteEventAdvisor.prototype = {
|
||||
|
||||
event : "",
|
||||
targetId : "",
|
||||
sourceId : "",
|
||||
formId : "",
|
||||
processIds : "",
|
||||
renderIds : "",
|
||||
params : [],
|
||||
connection : null,
|
||||
|
||||
apply : function(){},
|
||||
|
||||
cleanup : function(){},
|
||||
|
||||
submit : function(event){}
|
||||
};
|
||||
|
||||
SpringUI.RemotingHandler = function(){};
|
||||
|
||||
SpringUI.RemotingHandler.prototype = {
|
||||
|
||||
submitForm : function(/*String */ sourceId, /*String*/formId, /*String*/ processIds, /*String*/renderIds, /*Array*/ params){},
|
||||
|
||||
getResource : function(/*String */ sourceId, /*String*/ processIds, /*String*/renderIds) {},
|
||||
|
||||
handleResponse : function() {},
|
||||
|
||||
handleError : function() {}
|
||||
};
|
||||
@@ -35,6 +35,8 @@ public class AjaxViewRoot extends DelegatingViewRoot {
|
||||
|
||||
protected static final String FORM_RENDERED = "formRendered";
|
||||
|
||||
protected static final String PROCESS_ALL = "*";
|
||||
|
||||
private List events = new ArrayList();
|
||||
|
||||
private String[] processIds;
|
||||
@@ -150,8 +152,12 @@ public class AjaxViewRoot extends DelegatingViewRoot {
|
||||
private void processRequestParams(FacesContext context) {
|
||||
|
||||
String processIdsParam = (String) context.getExternalContext().getRequestParameterMap().get(PROCESS_IDS_PARAM);
|
||||
processIds = StringUtils.delimitedListToStringArray(processIdsParam, ",", " ");
|
||||
processIds = removeNestedChildren(context, processIds);
|
||||
if (StringUtils.hasText(processIdsParam) && processIdsParam.contains("*")) {
|
||||
processIds = new String[] { context.getViewRoot().getClientId(context) };
|
||||
} else {
|
||||
processIds = StringUtils.delimitedListToStringArray(processIdsParam, ",", " ");
|
||||
processIds = removeNestedChildren(context, processIds);
|
||||
}
|
||||
|
||||
String renderIdsParam = (String) context.getExternalContext().getRequestParameterMap().get(RENDER_IDS_PARAM);
|
||||
renderIds = StringUtils.delimitedListToStringArray(renderIdsParam, ",", " ");
|
||||
|
||||
@@ -59,7 +59,7 @@ public class JsfUtils {
|
||||
}
|
||||
|
||||
public static boolean isAsynchronousFlowRequest() {
|
||||
if (isFlowRequest() && RequestContextHolder.getRequestContext().getRequestParameters().contains("ajaxSource")) {
|
||||
if (isFlowRequest() && RequestContextHolder.getRequestContext().getExternalContext().isAjaxRequest()) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
|
||||
@@ -118,7 +118,7 @@ public class JsfViewFactory implements ViewFactory {
|
||||
}
|
||||
|
||||
private JsfView createJsfView(UIViewRoot root, Lifecycle lifecycle, RequestContext context) {
|
||||
if (JsfUtils.isAsynchronousFlowRequest()) {
|
||||
if (context.getExternalContext().isAjaxRequest()) {
|
||||
return new JsfView(new AjaxViewRoot(root), lifecycle, context);
|
||||
} else {
|
||||
return new JsfView(root, lifecycle, context);
|
||||
|
||||
@@ -19,11 +19,13 @@ import org.easymock.EasyMock;
|
||||
import org.jboss.el.ExpressionFactoryImpl;
|
||||
import org.springframework.binding.expression.ExpressionParser;
|
||||
import org.springframework.binding.expression.support.ParserContextImpl;
|
||||
import org.springframework.webflow.context.ExternalContext;
|
||||
import org.springframework.faces.ui.AjaxViewRoot;
|
||||
import org.springframework.webflow.core.collection.AttributeMap;
|
||||
import org.springframework.webflow.core.collection.LocalAttributeMap;
|
||||
import org.springframework.webflow.core.collection.LocalParameterMap;
|
||||
import org.springframework.webflow.core.expression.el.WebFlowELExpressionParser;
|
||||
import org.springframework.webflow.definition.FlowDefinition;
|
||||
import org.springframework.webflow.definition.StateDefinition;
|
||||
import org.springframework.webflow.execution.RequestContext;
|
||||
import org.springframework.webflow.execution.RequestContextHolder;
|
||||
import org.springframework.webflow.execution.View;
|
||||
@@ -50,7 +52,7 @@ public class JsfViewFactoryTests extends TestCase {
|
||||
|
||||
private ExpressionParser parser = new WebFlowELExpressionParser(new ExpressionFactoryImpl());
|
||||
|
||||
private ExternalContext extContext = new MockExternalContext();
|
||||
private MockExternalContext extContext = new MockExternalContext();
|
||||
|
||||
private String event = "foo";
|
||||
|
||||
@@ -60,7 +62,6 @@ public class JsfViewFactoryTests extends TestCase {
|
||||
EasyMock.expect(context.getFlashScope()).andStubReturn(flashMap);
|
||||
EasyMock.expect(context.getExternalContext()).andStubReturn(extContext);
|
||||
EasyMock.expect(context.getRequestParameters()).andStubReturn(new LocalParameterMap(new HashMap()));
|
||||
EasyMock.replay(new Object[] { context });
|
||||
}
|
||||
|
||||
protected void tearDown() throws Exception {
|
||||
@@ -88,6 +89,8 @@ public class JsfViewFactoryTests extends TestCase {
|
||||
newRoot.setViewId(VIEW_ID);
|
||||
((MockViewHandler) viewHandler).setCreateView(newRoot);
|
||||
|
||||
EasyMock.replay(new Object[] { context });
|
||||
|
||||
View newView = factory.getView(context);
|
||||
|
||||
assertNotNull("A View was not created", newView);
|
||||
@@ -110,6 +113,8 @@ public class JsfViewFactoryTests extends TestCase {
|
||||
existingRoot.setViewId(VIEW_ID);
|
||||
((MockViewHandler) viewHandler).setRestoreView(existingRoot);
|
||||
|
||||
EasyMock.replay(new Object[] { context });
|
||||
|
||||
View restoredView = factory.getView(context);
|
||||
|
||||
assertNotNull("A View was not restored", restoredView);
|
||||
@@ -119,6 +124,36 @@ public class JsfViewFactoryTests extends TestCase {
|
||||
assertTrue("The lifecycle should have been invoked", ((NoEventLifecycle) lifecycle).executed);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ajax Request - View already exists in flash scope and must be restored and the lifecycle executed, no event
|
||||
* signaled
|
||||
*/
|
||||
public final void testGetView_Restore_Ajax_NoEvent() {
|
||||
|
||||
lifecycle = new NoEventLifecycle(jsfMock.lifecycle());
|
||||
factory = new JsfViewFactory(parser.parseExpression(VIEW_ID, new ParserContextImpl().eval(RequestContext.class)
|
||||
.expect(String.class)), null, lifecycle);
|
||||
|
||||
UIViewRoot existingRoot = new UIViewRoot();
|
||||
existingRoot.setViewId(VIEW_ID);
|
||||
((MockViewHandler) viewHandler).setRestoreView(existingRoot);
|
||||
|
||||
extContext.setAjaxRequest(true);
|
||||
|
||||
EasyMock.expect(context.getCurrentState()).andReturn(new ModalViewState());
|
||||
|
||||
EasyMock.replay(new Object[] { context });
|
||||
|
||||
View restoredView = factory.getView(context);
|
||||
|
||||
assertNotNull("A View was not restored", restoredView);
|
||||
assertTrue("A JsfView was expected", restoredView instanceof JsfView);
|
||||
assertTrue("An AjaxViewRoot was not set", ((JsfView) restoredView).getViewRoot() instanceof AjaxViewRoot);
|
||||
assertEquals("View name did not match", VIEW_ID, ((JsfView) restoredView).getViewRoot().getViewId());
|
||||
assertFalse("An unexpected event was signaled,", restoredView.eventSignaled());
|
||||
assertTrue("The lifecycle should have been invoked", ((NoEventLifecycle) lifecycle).executed);
|
||||
}
|
||||
|
||||
/**
|
||||
* View already exists in flowscope and must be restored and the lifecycle executed, an event is signaled
|
||||
*/
|
||||
@@ -132,6 +167,8 @@ public class JsfViewFactoryTests extends TestCase {
|
||||
existingRoot.setViewId(VIEW_ID);
|
||||
((MockViewHandler) viewHandler).setRestoreView(existingRoot);
|
||||
|
||||
EasyMock.replay(new Object[] { context });
|
||||
|
||||
View restoredView = factory.getView(context);
|
||||
|
||||
assertNotNull("A View was not restored", restoredView);
|
||||
@@ -142,35 +179,6 @@ public class JsfViewFactoryTests extends TestCase {
|
||||
assertTrue("The lifecycle should have been invoked", ((EventSignalingLifecycle) lifecycle).executed);
|
||||
}
|
||||
|
||||
/**
|
||||
* View is restored, and then the same view-state is re-entered at the end of the request
|
||||
* @throws Exception
|
||||
*/
|
||||
/*
|
||||
* public final void testGetView_RestoreTwice() throws Exception {
|
||||
*
|
||||
* lifecycle = new EventSignalingLifecycle(jsfMock.lifecycle()); factory = new JsfViewFactory(lifecycle,
|
||||
* parser.parseExpression(VIEW_ID));
|
||||
*
|
||||
* UIViewRoot existingRoot = new UIViewRoot(); existingRoot.setViewId(VIEW_ID); ((MockViewHandler)
|
||||
* viewHandler).setRestoreView(existingRoot);
|
||||
*
|
||||
* View restoredView = factory.getView(context);
|
||||
*
|
||||
* assertNull("FacesContext was not released", FacesContext.getCurrentInstance());
|
||||
*
|
||||
* configureJsf();
|
||||
*
|
||||
* View recursiveView = factory.getView(context);
|
||||
*
|
||||
* assertNotNull("A View was not restored", restoredView); assertTrue("A JsfView was expected", restoredView
|
||||
* instanceof JsfView); assertEquals("View name did not match", VIEW_ID, ((JsfView)
|
||||
* restoredView).getViewRoot().getViewId()); assertSame("Re-entered view should be the same instance", ((JsfView)
|
||||
* restoredView).getViewRoot(), ((JsfView) recursiveView).getViewRoot()); assertTrue("No event was signaled,",
|
||||
* restoredView.eventSignaled()); assertEquals("Event should be " + event, event, restoredView.getEvent().getId());
|
||||
* assertTrue("The lifecycle should have been invoked", lifecycle.executed); }
|
||||
*/
|
||||
|
||||
/**
|
||||
* Third party sets the view root before RESTORE_VIEW
|
||||
*/
|
||||
@@ -185,6 +193,8 @@ public class JsfViewFactoryTests extends TestCase {
|
||||
jsfMock.facesContext().setViewRoot(newRoot);
|
||||
jsfMock.facesContext().renderResponse();
|
||||
|
||||
EasyMock.replay(new Object[] { context });
|
||||
|
||||
View newView = factory.getView(context);
|
||||
|
||||
assertNotNull("A View was not created", newView);
|
||||
@@ -254,4 +264,58 @@ public class JsfViewFactoryTests extends TestCase {
|
||||
|
||||
}
|
||||
|
||||
private class ModalViewState implements StateDefinition {
|
||||
|
||||
AttributeMap attrs = new LocalAttributeMap();
|
||||
|
||||
public ModalViewState() {
|
||||
attrs.asMap().put("modal", Boolean.TRUE);
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
throw new UnsupportedOperationException("Auto-generated method stub");
|
||||
}
|
||||
|
||||
public FlowDefinition getOwner() {
|
||||
throw new UnsupportedOperationException("Auto-generated method stub");
|
||||
}
|
||||
|
||||
public AttributeMap getAttributes() {
|
||||
return attrs;
|
||||
}
|
||||
|
||||
public String getCaption() {
|
||||
throw new UnsupportedOperationException("Auto-generated method stub");
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
throw new UnsupportedOperationException("Auto-generated method stub");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class NormalViewState implements StateDefinition {
|
||||
|
||||
public String getId() {
|
||||
throw new UnsupportedOperationException("Auto-generated method stub");
|
||||
}
|
||||
|
||||
public FlowDefinition getOwner() {
|
||||
throw new UnsupportedOperationException("Auto-generated method stub");
|
||||
}
|
||||
|
||||
public AttributeMap getAttributes() {
|
||||
throw new UnsupportedOperationException("Auto-generated method stub");
|
||||
}
|
||||
|
||||
public String getCaption() {
|
||||
throw new UnsupportedOperationException("Auto-generated method stub");
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
throw new UnsupportedOperationException("Auto-generated method stub");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
|
||||
<web:flow-executor id="flowExecutor" flow-registry="flowRegistry">
|
||||
<web:flow-execution-attributes>
|
||||
<web:alwaysRedirectOnPause value="false"/>
|
||||
<web:alwaysRedirectOnPause value="true"/>
|
||||
</web:flow-execution-attributes>
|
||||
<web:flow-execution-listeners>
|
||||
<web:listener ref="jpaFlowExecutionListener" criteria="*"/>
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<h2>Hotel Results</h2>
|
||||
<p>
|
||||
<b>Search Criteria:</b> #{searchCriteria}<br/>
|
||||
<sf:commandLink value="Edit Criteria" onclick="dijit.byId('dialog1').show();" action="changeSearch" renderIds="hotelSearchFragment"/>
|
||||
<sf:commandLink value="Edit Criteria" action="changeSearch" renderIds="hotelSearchFragment"/>
|
||||
</p>
|
||||
<h:outputText id="noHotelsTxt" value="No Hotels Found" rendered="#{hotels.rowCount==0}"/>
|
||||
<h:dataTable id="hotels" styleClass="summary" value="#{hotels}" var="hotel" rendered="#{hotels.rowCount > 0}">
|
||||
@@ -36,7 +36,7 @@
|
||||
<h:column>
|
||||
<f:facet name="header">Action</f:facet>
|
||||
<!-- @TODO - THIS LINK DOES NOT WORK ON MYFACES 1.2.0 WHEN AJAX IS ENABLED BECAUSE THEY HAVE NOT IMPLEMENTED UIData.invokeOnComponent -->
|
||||
<sf:commandLink id="viewHotelLink" value="View Hotel" ajaxEnabled="false" action="selectHotel"/>
|
||||
<sf:commandLink id="viewHotelLink" value="View Hotel" action="selectHotel"/>
|
||||
</h:column>
|
||||
</h:dataTable>
|
||||
<div class="next">
|
||||
@@ -51,12 +51,5 @@
|
||||
</div>
|
||||
</div>
|
||||
</h:form>
|
||||
<script type="text/javascript">
|
||||
dojo.require("dijit.Dialog");
|
||||
dojo.require("dojo.parser");
|
||||
</script>
|
||||
<div dojoType="dijit.Dialog" id="dialog1" title="First Dialog">
|
||||
<div id="hotelSearch"> </div>
|
||||
</div>
|
||||
</ui:define>
|
||||
</ui:composition>
|
||||
@@ -33,7 +33,7 @@
|
||||
</h:selectOneMenu>
|
||||
</div>
|
||||
<div class="searchButton">
|
||||
<sf:commandButton id="findHotels" value="Find Hotels" ajaxEnabled="false" actionListener="#{searchCriteria.findHotelsListener}" action="findHotels"/>
|
||||
<sf:commandButton id="findHotels" value="Find Hotels" processIds="*" actionListener="#{searchCriteria.findHotelsListener}" action="findHotels"/>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
@@ -25,7 +25,12 @@
|
||||
<action method="findHotels" bean="mainActions" />
|
||||
</render-actions>
|
||||
<transition on="selectHotel" to="displayHotel"/>
|
||||
<transition on="changeSearch" to="displayMain"/>
|
||||
<transition on="changeSearch" to="editSearchPopup"/>
|
||||
</view-state>
|
||||
|
||||
<view-state id="editSearchPopup" view="main.xhtml">
|
||||
<attribute name="modal" value="true" type="java.lang.Boolean"/>
|
||||
<transition on="findHotels" to="findHotels" />
|
||||
</view-state>
|
||||
|
||||
<view-state id="displayHotel" view="hotelDetails.xhtml">
|
||||
|
||||
@@ -35,6 +35,7 @@ import org.springframework.webflow.core.collection.SharedAttributeMap;
|
||||
*
|
||||
* @author Keith Donald
|
||||
* @author Erwin Vervaet
|
||||
* @author Jeremy Grelle
|
||||
*/
|
||||
public interface ExternalContext {
|
||||
|
||||
@@ -205,4 +206,12 @@ public interface ExternalContext {
|
||||
*/
|
||||
public boolean isResponseCommitted();
|
||||
|
||||
/**
|
||||
* Returns true if the current request is an Ajax request, determined by the value of the http accept header.
|
||||
* @return true if the current request is an Ajax request
|
||||
*/
|
||||
public boolean isAjaxRequest();
|
||||
|
||||
public void setResponseHeader(String name, String value);
|
||||
|
||||
}
|
||||
@@ -29,6 +29,7 @@ import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.springframework.core.style.ToStringCreator;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.webflow.context.AbstractFlowRequestInfo;
|
||||
import org.springframework.webflow.context.ExternalContext;
|
||||
import org.springframework.webflow.context.ExternalContextHolder;
|
||||
@@ -49,12 +50,22 @@ import org.springframework.webflow.executor.FlowExecutor;
|
||||
*
|
||||
* @author Keith Donald
|
||||
* @author Erwin Vervaet
|
||||
* @author Jeremy Grelle
|
||||
*/
|
||||
public class ServletExternalContext implements ExternalContext {
|
||||
|
||||
/** The default encoding scheme: UTF-8 */
|
||||
private static final String DEFAULT_ENCODING_SCHEME = "UTF-8";
|
||||
|
||||
/** The accept header value that signifies an Ajax request */
|
||||
private static final String AJAX_ACCEPT_CONTENT_TYPE = "text/html;type=ajax";
|
||||
|
||||
/** Alternate request paramater to indicate an ajax request for cases when control of the header is not available */
|
||||
private static final String AJAX_SOURCE_PARAM = "ajaxSource";
|
||||
|
||||
/** The response header to be set on an Ajax redirect */
|
||||
private static final String FLOW_REDIRECT_URL_HEADER = "Flow-Redirect-URL";
|
||||
|
||||
/**
|
||||
* The context.
|
||||
*/
|
||||
@@ -412,7 +423,11 @@ public class ServletExternalContext implements ExternalContext {
|
||||
}
|
||||
|
||||
private void sendRedirect(String targetUrl) throws IOException {
|
||||
response.sendRedirect(response.encodeRedirectURL(targetUrl));
|
||||
if (isAjaxRequest()) {
|
||||
setResponseHeader(FLOW_REDIRECT_URL_HEADER, response.encodeRedirectURL(targetUrl));
|
||||
} else {
|
||||
response.sendRedirect(response.encodeRedirectURL(targetUrl));
|
||||
}
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
@@ -446,7 +461,7 @@ public class ServletExternalContext implements ExternalContext {
|
||||
public void issueRedirect() throws IOException {
|
||||
FlowExecutionRequestInfo requestInfo = (FlowExecutionRequestInfo) getRequestInfo();
|
||||
String targetUrl = buildFlowExecutionUrl(requestInfo, true);
|
||||
response.sendRedirect(response.encodeRedirectURL(targetUrl));
|
||||
sendRedirect(targetUrl);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -458,7 +473,21 @@ public class ServletExternalContext implements ExternalContext {
|
||||
public void issueRedirect() throws IOException {
|
||||
FlowDefinitionRequestInfo requestInfo = (FlowDefinitionRequestInfo) getRequestInfo();
|
||||
String targetUrl = buildFlowDefinitionUrl(requestInfo);
|
||||
response.sendRedirect(response.encodeRedirectURL(targetUrl));
|
||||
sendRedirect(targetUrl);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isAjaxRequest() {
|
||||
String acceptHeader = request.getHeader("Accept");
|
||||
String ajaxParam = request.getParameter(AJAX_SOURCE_PARAM);
|
||||
if (AJAX_ACCEPT_CONTENT_TYPE.equals(acceptHeader) || StringUtils.hasText(ajaxParam)) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void setResponseHeader(String name, String value) {
|
||||
response.setHeader(name, value);
|
||||
}
|
||||
}
|
||||
@@ -33,6 +33,10 @@ import org.springframework.webflow.execution.ViewFactory;
|
||||
*/
|
||||
public class ViewState extends TransitionableState {
|
||||
|
||||
private static final String FLOW_MODAL_VIEW_HEADER = "Flow-Modal-View";
|
||||
|
||||
private static final String MODAL_ATTR = "modal";
|
||||
|
||||
/**
|
||||
* The list of actions to be executed when this state is entered.
|
||||
*/
|
||||
@@ -79,6 +83,10 @@ public class ViewState extends TransitionableState {
|
||||
|
||||
protected void doEnter(RequestControlContext context) throws FlowExecutionException {
|
||||
context.assignFlowExecutionKey();
|
||||
if (context.getExternalContext().isAjaxRequest()
|
||||
&& Boolean.TRUE.equals(context.getCurrentState().getAttributes().getBoolean(MODAL_ATTR))) {
|
||||
context.getExternalContext().setResponseHeader(FLOW_MODAL_VIEW_HEADER, "true");
|
||||
}
|
||||
if (shouldRedirect(context)) {
|
||||
context.sendFlowExecutionRedirect();
|
||||
} else {
|
||||
|
||||
@@ -17,6 +17,7 @@ package org.springframework.webflow.test;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.binding.collection.SharedMapDecorator;
|
||||
import org.springframework.webflow.context.ExternalContext;
|
||||
@@ -73,6 +74,10 @@ public class MockExternalContext implements ExternalContext {
|
||||
|
||||
private FlowException exceptionResult;
|
||||
|
||||
private boolean ajaxRequest;
|
||||
|
||||
private Map responseHeaders = new HashMap();
|
||||
|
||||
/**
|
||||
* Creates a mock external context with an empty request parameter map. Allows for bean style usage.
|
||||
*/
|
||||
@@ -302,4 +307,20 @@ public class MockExternalContext implements ExternalContext {
|
||||
public FlowException getExceptionResult() {
|
||||
return exceptionResult;
|
||||
}
|
||||
|
||||
public boolean isAjaxRequest() {
|
||||
return ajaxRequest;
|
||||
}
|
||||
|
||||
public void setAjaxRequest(boolean ajaxRequest) {
|
||||
this.ajaxRequest = ajaxRequest;
|
||||
}
|
||||
|
||||
public void setResponseHeader(String name, String value) {
|
||||
this.responseHeaders.put(name, value);
|
||||
}
|
||||
|
||||
public String getResponseHeader(String name) {
|
||||
return (String) responseHeaders.get(name);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user