Chaining multiple select components in Tapestry 5.3 (Ajax update) - java

I’m using tapestry 5.3.7, and I want to use Ajax chaining select form elements : If I choose one option in a select element, another select appear to choose another element based on your first choice. I try a sample in the tapestry doc, and adapted for my project. Despite the fact that my custom code is very close to the sample, I always have the following error :
Render queue error in SetupRender[SelectZoneDemo:version]: Component SelectZoneDemo:version must be enclosed by a Form component.
Working sample from the doc (Chaining of select components CarMaker) http://tapestry.apache.org/5.3/apidocs/org/apache/tapestry5/corelib/components/Select.html)
Here is my code :
Template file (.tml)
<t:form>
<p>
<t:errors />
</p>
<p>
<t:select t:id="selectApplicatifs" t:model="selectApplicatifs"
t:value="selectedApplicatif" validate="required" zone= "VersionZone"
t:zone="versionZone" t:encoder="ApplicatifDtoEncoder" />
</p>
<t:zone t:id="versionZone" id="versionZone">
<t:if test="selectedApplicatif">
<p>
<t:select t:id="version" model="selectVersions" t:encoder="VersionDtoEncoder" />
</p>
</t:if>
<p>
<t:submit value="literal:Submit" />
</p>
</t:zone>
</t:form>
Java file
#Inject
private AjaxResponseRenderer ajaxResponseRenderer;
#Inject
private IServiceApplicatif serviceApplicatif;
#Inject
private SelectModelFactory selectModelFactory;
#Property
#Persist
private SelectModel selectApplicatifs;
#Property
#Persist
private String version;
#Property
#Persist
private SelectModel selectVersions;
#Inject
#Property
private ApplicatifDtoEncoder applicatifDtoEncoder;
#Inject
#Property
private VersionDtoEncoder versionDtoEncoder;
#Property
#Persist
private ApplicatifDto selectedApplicatif;
#InjectComponent
private Zone versionZone;
public void onActivate() {
List<ApplicatifDto> listApplicatifs = serviceApplicatif.findAllApplicatifDto();
List<VersionDto> listVersionApplicatifs = new ArrayList<VersionDto>();
selectApplicatifs = selectModelFactory.create(listApplicatifs, "nom");
if (selectVersions == null) {
selectVersions = selectModelFactory.create(listVersionApplicatifs,"version");
}
}
public void onValueChangedFromSelectApplicatifs(ApplicatifDto applicatifDto) {
List<VersionDto> versionList = applicatifDto.getVersionList();
selectVersions = selectModelFactory.create(versionList,"version");
ajaxResponseRenderer.addRender(versionZone);
}

Rendering zones within a form via ajax can get tricky as you have discovered. Fields require a FormSupport instance to be on the Environment stack. This is normally added to the environment as the parent form renders but as you have discovered, when rendering a zone within a form the FormSupport is not available.
Here's a few options:
Have a zone that wraps the entire form and update the whole form via ajax
Instead of refreshing a zone, use some javascript (via JavaScriptSupport) to update the options in an existing select menu
Use the Observe mixin to update zones in the form. Instead of using the core tapestry select component, you could render your own select. This might require you to use #RequestParameter to get the values serverside when the form is finally submitted.
Use the FormInjector. I've never used it myself but I think it somehow spoofs the environmental. I'm not sure how you'd pass the user's selection in the event. I think this component may have also been scrapped in tapestry 5.4.

Related

Spring form:radiobutton(s) having one selected on page load

<form:form method="post" action="/changeEnabledResource" modelAttribute="user">
<c:forEach items="${user.resources}" var="resource">
<form:radiobutton path="resources"
value="${resource}"
label="${resource.name}"
checked="${resource.enabled ? 'checked' : ''}/>
</c:forEach>
<input type="submit" value="Submit"/>
</form>
Resource class:
public class Resource {
private Long id;
private String name;
private boolean enabled;
//getters setters
}
User class:
public class User {
private List<Resource> resources;
....
//rest of code
}
With the above code all form:radiobuttons have checked="checked", so the last one is always checked when the form loads. Submitting works correctly and it changes the correct one to enabled and the rest to not enabled (service class handles this). But when the page loads all radio buttons have checked="checked".
Why is this happening, or what would be the correct way to do this so that the resource that IS enabled has the radio button checked?
The answer to this is to NOT use the form:radiobutton(s) tags since they apparently don't work very well. I have yet to find any good examples anywhere using this tag. My solution was just to use plain HTML input tags and use the #RequestParam in the controller method which will pass only the value of the radio button that is checked. And I can easily use ${resource.enabled ? 'checked' : ''} to ensure that the correct one is selected.
Set the selected value in a separate property:
public class User {
private int selectedResourceId;
private List<Resource> resources;
....
//rest of code
}
...
<form:radiobutton path="selectedResourceId"
value="${resource.id}"
label="${resource.name}"/>
...
On page load, initalize the selectedResourceId (based on the resource where enabled flag is true), so that the form:radiobutton tag knows which one is selected when the page is rendered.
Upon form submission, go in the other direction and set the appropriate resource.enabled flag based on the selectedResourceId.

Play Framework Form Submission

I have followed the java play framework tutorial and created the todo task list that is the result of this tutorial:
http://www.playframework.org/documentation/2.0.4/JavaTodoList
I wanted to modify this project slightly so that instead of having label input the form I had two other inputs, starting location and end location.
Firstly I included the extra variables I required in the java class in Models/
#Id
public Long id;
#Required
public String starting_location;
#Required
public String end_location;
I also added this to the index.scala.html file:
#form(routes.Application.newJourney()) {
#inputText(journeyForm("Starting Location"))
#inputText(journeyForm("End Location"))
<input type="submit" value="Create">
}
I get the form fields "Starting Location" and "End Location" displaying as expected but when I click on submit the total number of journeys (or tasks for as described in the play example) does not increment and does not get added to the list of current journeys that can be deleted.
Any help is appreciated and I can post full source if required.
Thanks
The helper inputText is documented here.
You put there a label where the field name should be.
This should work:
#inputText(
journeyForm("starting_location"),
'_label -> "Starting Location"
)

Wicket link replay attack

I have a ListView composed by different fragment which contains text and a link (inside another fragment). The link is visible depending on the state of the listview model.
For simplicity let's say the link is visible depending on a boolean field of the listview model, if it's true is visible, invisible otherwise.
At first the link is visible, I copy the link location (encrypted), I wait for my model to change (i.e. boolean to false) and after I refresh the page the link is gone. (correct!)
If I try to give the URL (copied before) back in the browser I receive a WicketRuntimeException telling me that the listener for this link was not found.
To be more complete the link is inside a fragment:
<wicket:fragment wicket:id="reservationRatingFragment">
<li>
<div>
<img src="/img/good.png" />
</div>
<p>
<a wicket:id="ratingGoodLink" href="#"> <wicket:message
key="messaging.reservation.rating.good" />
</a>
</p>
</li>
</wicket:fragment>
And when I say invisible I mean that I set the markup container of the fragment as .setVisible(false);
Why is this happening? I'm supposing that if I recall a link which is not visible anymore the framework should just skip it and refresh the page I'm currently on (or redirect me to the base page).
If for example I copy the link and I change BasePage (go to the homepage for example), the exception still occurs when I'm recalling the copied URL.
EDITED:
In the first fragment:
WebMarkupContainer msgRatingContainer = new WebMarkupContainer("messageRatingContainer") {
private static final long serialVersionUID = 1L;
#Override
public void onConfigure() {
setVisible(message.getType() == MessageType.RATING);
}
};
if (msgRatingContainer.isVisible()) {
if (message.getType() == MessageType.RATING) {
msgRatingContainer.add(new ReservationRatingFragment("messageRatingSection",
"reservationRatingFragment", this, item, message));
}
The nested fragment (ReservationRatingFragment):
public ReservationRatingFragment(String id, String markupId,MarkupContainer markupContainer, Item item, Message msg) {
super(id, markupId, markupContainer, new Model<Message>(msg));
/* Avoid render container */
setRenderBodyOnly(true);
/* Load button components */
Link<Void> ratingGoodLink = new Link<Void>("ratingGoodLink"){
private static final long serialVersionUID = 1L;
#Override
public void onClick() {
processRating(ReservationEvaluationResult.GOOD);
}
};
add(ratingGoodLink);
Link<Void> ratingBadLink = new Link<Void>("ratingBadLink"){
private static final long serialVersionUID = 1L;
#Override
public void onClick() {
processRating(ReservationEvaluationResult.BAD);
}
};
add(ratingBadLink);
}
Markup for both fragments:
<wicket:fragment wicket:id="messageFragment">
Some content...
<!-- Here goes my fragment with link -->
<ul wicket:id="messageRatingContainer">
<div wicket:id="messageRatingSection"></div>
</ul>
<wicket:fragment wicket:id="reservationRatingFragment">
<li><div>
<img src="/img/messaging/good.png" />
</div>
<p>
<a wicket:id="ratingGoodLink" href="#"> <wicket:message
key="messaging.reservation.rating.good" />
</a>
</p></li>
<li><div>
<img src="/img/messaging/bad.png" />
</div>
<p>
<a wicket:id="ratingBadLink" href="#"> <wicket:message
key="messaging.reservation.rating.bad" />
</a>
</p></li>
</wicket:fragment>
</wicket:fragment>
EDITED:
The processRating just perform a call to a controller (which handle the change in the backend). In the controller I check for the replay attack (if this action is already performed) and if so I throw a runtime exception that lead the user to a warning page (You already rated this message). The fact is, in this case it don't get to this point, since the link is not available it doesn't call the controller and it just throw the InvalidUrlException since the link is not visible.
Wicket version: 1.4.19
Thanks
Your assumption that an invalid link will be ignored or redirect you to the base page is wrong.
Why is that?
If we take a step back, what happens when you click a link? The state of your application changes. However this is only safe to do so if the application is in the state it was when the link was created. If this rule wasn't enforced, you would need to make sure that every single potential state transition is either acceptable or explicitly marked as invalid. This would be highly impractical, if not impossible in most systems. But neglecting this would not only be a security risk but it could result in corrupt data.
It's best to think of it as a case of optimistic locking. (Mostly because it is :)) When the link is created, it is given the version number of the internal state at the time of creation. When the link is clicked, that version number is compared to the current version of the internal state. If the two match, the link is accepted as valid, the internal state is updated and its version number is incremented. If the two numbers don't match, the link is rejected and an exception is thrown because an invalid state transition attempt can't be ignored.
I won't explain how to get around this limitation as it's already been told in another answer, I just wanted to answer the "why" question.
I am not sure I understand the exact reason for your implementation. That said, I would recommend using the BookmarkablePageLink() with PageParameters set to perform your processRating() method upon loading the destination page.
Add your link components:
PageParameters ppGood = new PageParameters("0="+ReservationEvaluationResult.GOOD);
PageParameters ppBad = new PageParameters("0="+ReservationEvaluationResult.BAD);
add(new BookmarkablePageLink("ratingGoodLink", DestinationPage.class, ppGood));
add(new BookmarkablePageLink("ratingBadLink", DestinationPage.class, ppBad));
Then create a new constructor in your DestinationPage:
public class DestinationPage extends WebPage {
public DestinationPage(PageParameters param) {
if(param.getString("0")!=null){
String rating = param.getString("0");
processRating(rating);
}
...
This will give you a link that will be persistent and should allow you to copy and paste the URL.
The only viable solution I found was to extend the RequestCycle and override the onRuntimeException method this way:
#Override
public Page onRuntimeException(Page page, RuntimeException e) {
if(e instanceof InvalidUrlException) {
return new HomePage();
} else {
return super.onRuntimeException(page, e);
}
}

Struts2 - Dynamic form fields and database data retrieval

I am developing a registration web app using Struts2 and need some guidance.
Background:
On the registration form, there is a set of five form fields: 1 text box, and 4 drop down select boxes. The five fields describe a person's primary position in an educational setting: the text field allows the user to insert their job title, and the drop down menus allow the user to select what school, institution, department, and division they belong to. The drop down menus are initialized with options that are stored in a database (inside the registration action, array lists are initialized with these values before the form is displayed). For example:
<s:select emptyOption="true" key="school1.schoolId" list="schoolList" listKey="schoolId" listValue="schoolName" required="true" />
Problem:
I need to provide the user with the ability add an X number of secondary positions. On the registration form, a user can click an "add another affiliation" button, and a new set of the 5 form fields are displayed. These fields will also need to be validated, and saved when the user clicks the form's submit button.
What would be the best approach to tackling this problem?
So far, I have only declared array lists for each form field, like so:
private List<String> jobTitles = new ArrayList<String>();
private List<School> schools = new ArrayList<School>();
private List<Institution> institutions = new ArrayList<Institution>();
private List<Department> departments = new ArrayList<Department>();
private List<Division> divisions = new ArrayList<Division>();
But I do not know how to proceed. How do I display the initial 5 fields for the primary position? If I use Javascript to insert new form fields dynamically, how do I initialize the dynamic drop down menus with the options stored in the database? How do I retain these values if the page is reloaded?
Any help is appreciated - thanks!
The basic problem you need to tackle is how to get an indexed list of request parameters into your action class. This is quite simple, and I think you are on the right track by starting off by creating Lists of input parameters. I found a bit of documentation on the subject here. Basically you can have form fields with names like jobTitles[0], jobTitles[1] which would be used to populate the jobTitles List.
However, I think the concept of 'Affiliation' deserves a class of it's own:
class UserAffiliation {
private String title;
private String schoolId;
private String institutionId;
private String departmentId;
private String divisionId;
// Make sure that there is a no-args constructor (default or explicit) for Struts to create instances.
// Add getters and setters
}
In your action class:
private List<UserAffiliation> affiliations;
...
// getter and setter for affiliations
Would be enough to capture the user input.
Your jsp could look something like:
<form action=".." method="post">
<div class="affiliation">
<s:textfield name="affiliations[0].title"/>
<s:select name="affiliations[0].schoolId" list="schools" listKey="schoolId" listValue="schoolName"/>
...
</div>
<s:if test="affiliations != null && affiliations.size > 1">
<s:iterator value="affiliations" begin="1" status="status">
<s:textfield name="affiliations[%{#status.index + 1}].title"/>
<s:select name="affiliations[%{#status.index + 1}].schoolId" list="schools" listKey="schoolId" listValue="schoolName"/>
...
</s:iterator>
</s:if>
....
</form>
<div id="affilationTemplate" style="display:none;">
<div class="affiliation">
<s:textfield name="affiliations[__IDX__].title"/>
<s:select name="affiliations[__IDX__].schoolId" list="schools" listKey="schoolId" listValue="schoolName"/>
</div>
...
</div>
Note the div affilationTemplate. You could use JS to get the html of this template, replace __IDX__ with the appropriate index, and append to the form contents when the user clicks on the 'add another affiliation' button. This makes sure that the newly added select boxes are pre-populated with appropriate values.
The iterator block displays what ever the values the user had already submitted (with the exception of the 'primary affiliation', which is already displayed above it).
NOTE: You should of course, try to get rid of the repeated form elements if possible. I would try with extracting them into an include.

Do Spring property editors only work on forms?

I'm working on a Spring application that simply searches a set of data for things that match some criteria. There are two main views: one is a simple form that lets the user configure search criteria, while the other displays the results as a table.
One of the search fields is a closed set of options (about 10). Lower down in the code, I want to handle this as an enum class. The web form includes a drop-down that allows the user to select an option from this set. I've used a form:select to do this, populated with a set of strings that describe the values.
To keep the presentation and business logic separate, the enum class musn't have any knowledge of these strings, so I've created a Property Editor to convert between the two. When I load the form, the select control is set to the string associated with the enum value I gave it; when the form is submitted, the string is converted back to my enum type. This is all working fine.
For the results page (which isn't a form), I simply add the data to be displayed to a ModelMap. At the moment, I have to explicitly convert my enum type to a string before I add it to the map. What I'd like to do is just add the enum to the map and have the property editor convert it for me in the background, like it does for the form. I can't work out how though. Is it possible to do this? Maybe I'm missing something really obvious?
You can use Spring Tablib
<%#taglib uri="http://www.springframework.org/tags" prefix="spring"%>
And use transform markup
<!--If you have a command which command name is account-->
<!--Default command name used in Spring is called command-->
<spring:bind path="account.balance">${status.value}</spring:bind>
Or
<spring:bind path="account.balance">
<spring:transform value="${account.balance}"/>
</spring:bind>
Or
<!--Suppose account.balance is a BigDecimal which has a PropertyEditor registered-->
<spring:bind path="account.balance">
<spring:transform value="${otherCommand.anotherBigDecimalProperty}"/>
</spring:bind>
About value attribute
The value can either be a plain value to transform (a hard-coded String value in a JSP or a JSP expression), or a JSP EL expression to be evaluated (transforming the result of the expression). Like all of Spring's JSP tags, this tag is capable of parsing EL expressions itself, on any JSP version.
Its API
Provides transformation of variables to String, using an appropriate custom PropertyEditor from BindTag (can only be used inside BindTag)
If you use MultiActionController i advice you to use a Dummy Command class as bellow
public class Command {
public BigDecimal bigDecimal;
public Date date;
/**
* Other kind of property which needs a PropertyEditor
*/
// getter's and setter's
}
Inside your MultiActionController
public class AccountController extends MultiActionController {
#Autowired
private Repository<Account, Integer> accountRepository;
public AccountController() {
/**
* You can externalize this WebBindingInitializer if you want
*
* Here it goes as a anonymous inner class
*/
setWebBindingInitializer(new WebBindingInitializer() {
public void initBinder(WebDataBinder dataBinder, WebRequest request) {
binder.registerCustomEditor(BigDecimal.class, new CustomNumberEditor(BigDecimal.class, numberFormat, true));
binder.registerCustomEditor(Date.class, new CustomDateEditor(new SimpleDateFormat("dd/MM/yyyy"), true));
}
});
}
public ModelAndView(HttpServletRequest request, HttpServletResponse response) throws Exception {
return new ModelAndView()
.addAllObjects(
createBinder(request, new Command())
.getBindingResult().getModel())
.addObject(accountRepository.findById(Integer.valueOf(request.getParameter("accountId"))));
}
}
Inside your JSP
<c:if test="{not empty account}">
<!--If you need a BigDecimal PropertyEditor-->
<spring:bind path="command.bigDecimal">
<spring:transform value="${account.balance}"/>
</spring:bind>
<!--If you need a Date PropertyEditor-->
<spring:bind path="command.date">
<spring:transform value="${account.joinedDate}"/>
</spring:bind>
</c:if>
It is useful when your Target command does not provide a PropertyEditor which needs to be used in another command.

Categories

Resources