Struts2 forms following progressive enhancement to work without AJAX - java

I am reworking my forms so that they will work without javascript enabled but also work well with javascript by using jquery and AJAX. I am using struts which has proven to be both helpful and a pain. The form processing without javascript was handled with basic struts functionality by validating and executing the action and dispatching back to the page the user was on but with the result (actionMessage or actionError) set in the forms html. I had a hidden input with the url of the page which would be set in the form bean, and that is the dispatch location in the struts.xml
My problem is that I need to be able to have this functionality underneath some enhanced functionality such as JSON serialization and AJAX requests, and I can't figure out a way to have both. Below are my configurations and code for the no-script form which works fine.
The struts.xml file
<struts>
<package name="default" extends="struts-default,json-default">
<action name="contact" class="org.deadmandungeons.website.action.ContactAction" method="execute">
<result name="success">%{currentPage}</result>
<result name="input">%{currentPage}</result>
<result name="error">%{currentPage}</result>
</action>
</package>
</struts>
The No-script JSP
<%# tag language="java" pageEncoding="UTF-8"%>
<%# taglib uri='http://java.sun.com/jsp/jstl/core' prefix='c'%>
<%# taglib prefix="t" tagdir="/WEB-INF/tags"%>
<%# taglib prefix="s" uri="/struts-tags" %>
<%# taglib prefix="sj" uri="/struts-jquery-tags"%>
<s:form id="contact_form" action="contact" method="post" cssClass="clearfix">
<label for="contact_user">Username / In-game name:</label>
<input type="text" id="contact_user" name="contactBean.username"
class="field" data-enter-click="sendbutton" maxlength="16"
size="16" />
<label for="contact_email">Email:</label>
<input type="text" id="contact_email" name="contactBean.email" class="field" data-enter-click="sendbutton" />
<label for="contact_message">Message:</label>
<textarea id="contact_message" name="contactBean.message" rows="5" cols="35"></textarea>
<input id="no_script" name="contactBean.currentPage" type="hidden" value="${pageContext.request.requestURL}" />
<div id="contact_response" class="response">
<s:if test="hasActionErrors()">
<s:actionerror id="contact_fail" cssClass="fail" />
</s:if>
<s:if test="hasActionMessages()">
<s:actionmessage id="contact_success" cssClass="success" />
</s:if>
</div>
<s:submit type="submit" id="sendbutton" value="Send" />
</s:form>
The contactAction class is just a basic action class holding a ContactBean for the necessary fields, and adding ActionErrors when it doesnt validate or ActionMessages on success.
Now for the jquery AJAX approach on top of this, I have tried a couple things. I have the struts2 jquery plugin installed which is a great plugin, and I am able to get ajax working fin on forms with it, but not with the way my struts configuration is in the example above. For the dispatch location for the result in struts.xml, I had it set to a jsp page that looks like this:
<%# taglib prefix="s" uri="/struts-tags"%>
<s:property value="response" escapeHtml="false"/>
and I added a field called response to the action class which was just set with the actionError or actionMessage strings. The jquery plugin was able to take the result from that jsp, and append it to the target locations fine. But that means that if I do the same with javascript turned of, once the form is submited, the user would be dispatched to that jsp which only displays the result string rather than dispatching them to the same page and setting the message in the form.
I have also tried to use the struts2 json plugin (which is also great), but this also only works well with jquery ajax, and not when there is no javascript.
Any ideas on how to get a form to work in both settings?
Thanks!

I figured it out with the help of struts interceptors. I made a custom interceptor that would check the stack parameters if a 'noScript' parameter existed, and if it did, It would return a result of 'noScript'. The noScript result is of type 'chain' and would then forward to the action that handles the form response when there is no javascript. The noScript parameter is set through a hidden field in the form that has the url of the current page. when the page loads, jquery will remove this input anywhere on the page. So if javascript is disabled, the noScript input will not be removed. Here is my new struts.xml after:
<struts>
<package name="default" extends="struts-default,json-default">
<interceptors>
<interceptor name="noScriptInterceptor"
class="org.deadmandungeons.website.NoScriptInterceptor"></interceptor>
<interceptor-stack name="noScriptStack">
<interceptor-ref name="noScriptInterceptor" />
<interceptor-ref name="defaultStack" />
</interceptor-stack>
</interceptors>
<action name="contact" class="org.deadmandungeons.website.action.ContactAction" method="execute">
<interceptor-ref name="noScriptStack"/>
<result type="json" name="success" />
<result type="json" name="input" />
<result type="json" name="error"/>
<result type="chain" name="noScript">noScriptContact</result>
</action>
<action name="noScriptContact" class="org.deadmandungeons.website.action.ContactAction" method="execute">
<result name="success">%{noScript}</result>
<result name="input">%{noScript}</result>
<result name="error">%{noScript}</result>
</action>
</package>
</struts>
And here is the NoScriptInterceptor class:
public class NoScriptInterceptor implements Interceptor {
private static final long serialVersionUID = -1472114260682759961L;
#Override
public void destroy() {
}
#Override
public void init() {
}
#SuppressWarnings("unchecked")
#Override
public String intercept(ActionInvocation invocation) throws Exception {
final ActionContext context = invocation.getInvocationContext();
String actionName = Utils.toCamelCase(invocation.getAction().getClass().getName());
Map<String,Object> parameters = (Map<String,Object>)context.get(ActionContext.PARAMETERS);
Object noScriptParam = parameters.get(actionName + ".noScript");
if (noScriptParam != null) {
return Constants.NO_SCRIPT;
}
return invocation.invoke();
}
}

Related

After migrating to struts 2.3.24 from struts 2.1.8, no response to action on login page

My application displays the login page, but cannot login.
On the login page, user enters the username and password but on pressing the login button nothing happens.
We have in our JSP :
<s:form id="loginForm" name="loginForm" action="login" namespace="/framework">
The struts-framework.xml file has the action defined as follows:
<global-results>
<result name="loginShow" type="redirect">loginShow.action</result>
<result name="toMain">/page/framework/main.jsp</result>
<result name="logout">/page/framework/logout.jsp</result>
</global-results>
<action name="login" class="com.abc.action.framework.LoginAction" method="loginCheck"> </action>
In our logs we get the Request start for the action framework/login.action, but no response is obtained thereafter and same is evident from the logs.
For reference I have included the following to my struts.xml after migrating struts to 2.3.24 :
<constant name="struts.enable.DynamicMethodInvocation" value="true" />
Thanks for all your efforts. It was an issue with struts-convention plugin.Works well for me on removing this plugin and mapped dependancies in struts.xml

Struts2- Persistence of objects values between different actions in the same Action class

Scenario: On click of a Submit button I have unmarshalled the XML to Java and then displaying the Java objects(created a formbean and mapped the JAXB java Objects to this formbean - PolicyForm and using this formbean to display in JSP) in the JSP. This is working fine.
Issue: I have one more button -Calculate on the same page with in the same form of Submit button. On click of the 'Calculate' button I need to get the acordform values which was unmarshalled during the click of Submit button. Here I am not able to get the acordform values instead I am getting the new object of acordform.
When I have googled I see - Struts 2 doesn't have thread-safety issues as "Action objects are instantiated for each request".
Please let me know here when I am sending the request again is the Action object- acordform being instantiated everytime? If yes, how can I avoid this? Since I need the acordformobject values even in the next request too.
Action Class:
public class RateAction extends ActionSupport implements
ServletRequestAware,SessionAware {
/* ... */
// ACORD xml form bean
private ACORD acordform = new ACORD();
//To display the values in the JSP
private PolicyForm policyForm;
public ACORD getAcordform() {
return acordform;
}
public void setAcordform(ACORD acordform) {
this.acordform = acordform;
}
public String doSubmit() {
/*...Unmatshalling from XML to Java -acordform is done..*/
}
public String doRateSubmimt()
{
/*..trying to get the acordform values which are being set
previously in doSubmit() method...*/
}
}
Strust.xml:
<struts>
<constant name="struts.enable.DynamicMethodInvocation" value="false" />
<constant name="struts.devMode" value="true" />
<constant name="struts.custom.i18n.resources" value="ApplicationResources" />
<package name="default" extends="struts-default" namespace="/" >
<action name="fileUploadAction"
class="com.main.common.action.RateAction" >
<interceptor-ref name="fileUpload">
<param name="allowedTypes">text/xml</param>
</interceptor-ref>
<interceptor-ref name="defaultStack"></interceptor-ref>
<interceptor-ref name="params"/>
<interceptor-ref name="prepare"/>
<interceptor-ref name="basicStack"/>
<result name="success">First.jsp</result>
<result name="input">First.jsp</result>
</action>
<action name="submitAction" class="com.main.common.action.RateAction" method="doSubmit">
<result name="success">First.jsp</result>
</action>
<action name="rateAction" class="com.main.common.action.RateAction" method="doRateSubmit">
<result name="success">First.jsp</result>
</action>
</package>
JSP:
<s:form id="rtr" action="fileUploadAction" method="POST"
enctype="multipart/form-data">
<table border="0" cellspacing="0" cellpadding="0">
<tr>
<s:file name="uploadFile" label="Upload Request XML" />
<s:submit value="Submit" action="submitAction" onclick="displayDetails();" />
<s:hidden name="submitSuccess" />
<s:submit value="Calculate" action="rateAction" />
</tr>
</table>
</s:form>
Can't you perform the operation in one click ? I mean Submit the XML, and draw the result with the unmarshalled XML...
If no, since you need to share that in-memory XML (not saved on HD nor DB, just posted to the Action) between multiple requests / actions, put it in the Session Map by implementing SessionAware in your Action.
Note that you are using too much Interceptors...
This should be enough:
<interceptor-ref name="defaultStack">
<param name="fileUpload.allowedTypes">text/xml</param>
</interceptor-ref>

Current URL /web/guest/HTML generates exception: null in Liferay 6.0.6

I develop an example of Struts2 with Jasper Reports and every thing work fine. In my index.jsp I have
index.jsp:
<body>
<a href="HTML.action" target="_blank" >click to generate pdf report</a>
</body>
struts.xml:
<struts>
<package name="default" extends="struts-default,jasperreports-default">
<action name="HTML" class="com.tutorialspoint.DataBeanList" method="exporte">
<result name="success">/index.jsp</result>
</action>
</package>
</struts>
but when I want to do that with Liferay 6.0.6 I got this error:
INFO [PortalImpl:3829] Current URL /web/guest/HTML.action generates exception: null
In my struts.xml I have:
<!-- Action pour générer un rapport pdf -->
<package namespace="/reporting" extends="struts-portlet-default,json-default,jasperreports-default" name="reportingview">
<action name="HTML" class="com.xxxxxx.struts2.actions.genererQPpdf" method="exporte">
<result name="success">/JSPs/reporting/resultQP.jsp</result>
</action>
</package>
so any one can help me.
Better use the struts tags to exclude errors in the code. For example url tag could be used to construct the correct link that point to the action.
<a href="<s:url namespace="/reporting" action="HTML"/>" target="_blank" >click to generate pdf report</a>

How to remove warning from the server log: [WARN] No configuration found for the specified action: '/' in namespace: ''

I have developed a application using struts 2.0. On the server log I am getting certain warning lines. I want to fix this warnings but I am not able to do it.
[WARN] No configuration found for the specified action: '/' in namespace: ''. Form action defaulting to 'action' attribute's literal value.
In my struts.xml file I have something like this
<struts>
<constant name="struts.devMode" value="true" />
<package name="basicstruts2" extends="struts-default" namespace="/">
<global-results>
<result name="error">/error.jsp </result>
</global-results>
<action name="Login" class="com.login.LoginAction">
<result name="input">/login.jsp</result>
<result name="success">/dashboard.jsp</result>
</action>
</package>
</struts>
In the jsp page I am calling following javascript function on click of a button
function submitloginform()
{
document.loginform.action = "Login.action";
document.loginform.submit();
}
Can anyone please help me for this.
just keep action name in struts.xml and action of form to be submitted same.
function submitloginform()
{
document.loginform.action = "Login";
document.loginform.submit();
}
This warning message has nothing to do with the javascript or the form submission. It happens when the tag is rendered to the it's representation in HTML.
You must be missing the action attribute in your in your case. Make sure that your code looks like the following.
<s:form name="loginform" method="post" action="Login">
If you are trying to render a form in another namespace, for example: /public, you should be doing
<s:form name="loginform" method="post" action="Login" namespace="/public">
instead of
<s:form name="loginform" method="post" action="/public/Login">

Struts 2 DisplayTag only shows table of DB information only when form is submitted

I'm using Struts 2 and the Display Tag table formatting tool to display query results from a database (a SELECT * query). I'm running into an odd error. After submitting a record to the database from Add.jsp, it takes me to View.jsp, where the table is correctly displayed. However, if I go to View.jsp directly, or via a hyperlink from the Add.jsp page WITHOUT submitting an entry to the database, it seemingly does not execute the methods necessary to retrieve the database entries into a variable to let the table display. In that case, I end up with a phrase, "nothing to display" where the table should be.
I was told this is most likely a problem with my method names, and the Struts 2 side of things, not any functionality on the Java side. I'm extremely new to Struts, and would appreciate some help at identifying and fixing the bugs. Some code excerpts are below, but I can post the whole thing if required. Also- no errors are shown when View.jsp is loaded, either after form submission or via direct link.
Add.jsp
<%# page contentType="text/html; charset=UTF-8"%>
<%# taglib prefix="s" uri="/struts-tags"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<title>Add Development Environment</title>
</head>
<body>
<div id="main">
<h2>Add Development Environment</h2>
<p><s:a href="View.jsp">View Entries</s:a></p>
<s:actionerror />
<s:form action="doEntries.action" method="post" validate="true">
<s:textfield name="OS" key="label.OS" size="20" />
<s:textfield name="OSVersion" key="label.OSVersion" size="20" />
<s:textfield name="Note" key="label.note" size="20" />
<s:submit method="doEntries" key="label.submit" />
</s:form>
</div>
</body>
</html>
View.jsp
<%# page contentType="text/html; charset=UTF-8" language="java" %>
<%# taglib prefix="s" uri="/struts-tags"%>
<%# taglib prefix="display" uri="http://displaytag.sf.net" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/"WebContent/View.jsp"DTD/xhtml1-transitional.dtd">
<html>
<head>
<title>View Development Environments</title>
</head>
<body>
<div id="main">
<h2>View Development Environments</h2>
<p><s:a href="Add.jsp">Add Entry</s:a></p>
<!--<s:set name="items" value="entries" scope="request"/>-->
<display:table name="entries" class="DevEnvironment" requestURI="" id="itemsList" export="false" pagesize="15">
<display:column property="did" title="id"/>
<display:column property="OS" title="Operating System"/>
<display:column property="OSVersion" title="Operating System Version" />
<display:column property="note" title="Note"/>
</display:table>
</div>
</body>
</html>
DatabaseAction.java
public class DatabaseAction extends ActionSupport{
private static final Logger logger = Logger.getLogger(DatabaseAction.class);
DBO myDBO;
private String OS;
private String OSVersion;
private String note;
private ArrayList<DevEnvironment> entries;
private double offset;
private double limit;
public DatabaseAction(){
super();
//connect to DB
myDBO = new DBO("com.mysql.jdbc.Driver", "jdbc:mysql://localhost:3306/devenvironments?user=root&password=mysqliscool");
}
public String doEntries(){
logger.info("puting stuff in DB! =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-==-==-=-=");
if(myDBO.connect() && myDBO.setEntry(OS, OSVersion, note)){
return "success";
} else {
return "error";
}
}
public ArrayList<DevEnvironment> getEntries() {
logger.info("-==-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=getEntries called");
entries = myDBO.getEntry(0, -1);
return entries;
}
public void setEntries(ArrayList<DevEnvironment> entries){
this.entries = entries;
}
public String retrieveEntries(){
logger.info("-==-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=retrieveEntries called");
if(myDBO.connect()){
logger.info("-==-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=EXECUTING SQL SELECT METHOD");
//entries = myDBO.getEntry(0, -1);
//offset, limit
return "success";
} else {
return "input";
}
}
Struts.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">
<struts>
<constant name="struts.enable.DynamicMethodInvocation" value="false" />
<constant name="struts.devMode" value="false" />
<constant name="struts.custom.i18n.resources" value="ApplicationResources" />
<package name="default" extends="struts-default" namespace="/">
<action name="doEntries" method="doEntries" class="net.josh.devenvironments.DatabaseAction" >
<result name="success">View.jsp</result>
<result name="input">Add.jsp</result>
</action>
<!--
<action name="retrieveEntries" method="retrieveEntries" class="net.josh.devenvironments.DatabaseAction" >
<result name="success">View.jsp</result>
<result name="input">Add.jsp</result>
</action>
-->
</package>
</struts>
You can get to the View.jsp because it is publicly accessible. As a rule put all JSP's under /WEB-INF the reason for this is that View.jsp will not make sense unless it is backed by an action. If you just go to View.jsp as you are now it does not go though an action it just hits the JSP directly and tries to parse it, the tags not finding any data probably gracefully fail (S2 tags typically do this, can't find the property just give up).
Now with the JSP under the /WEB-INF folder the only way to access it is with "doEntries" (you can leave off the dot action unless you're only filtering .action with s2 but if you don't have good reason you shouldn't be). Of course don't forget to update the action mapping to /WEB-INF/View.jsp
Now you could create another action called view to get there but if all it is going to do is the same thing as "doEntries" there really isn't any point, just use "doEntries".
When you get sick of writing XML (which is good practice but isn't really necessary) add the struts2-conventions-plugin to your class path. After it is added do the following:
Create a new package called struts2, create a class in it like this:
package struts2;
class HelloWorld extends ActionSupport{
public String greetings = "Hello from HelloWorld Class!";
}
Then create a jsp called "hello-world.jsp" under /WEB-INF/content
that has a
<s:property value="greeting"/>
tag now if you enter "hello-world" where index.html is probably located you'll see a little hello message... For most simple cases by following a simple Class to JSP naming convention you can avoid XML all together. If you don't like the convention you can easily override what you don't like, but if you add the conventions plugin to your class path both your xml and future conventions action will live in harmony.
PS: Yes I suppose the HelloWorld class should have getters and setters but it works just fine that way, and makes the example shorter.

Categories

Resources