I'm trying to implement an AJAXfied Wicket list view. On my research I stumbled upon the code on this site and modified it a bit.
The model is not updated properly. So whenever a value is entered in a text field, it is forgotten if the AJAX submit link is invoked (the text field is empty). Why does this happen? I don't see any issue with this code. Wicket version is 1.5.2.
Here is the Java code:
// Initialization of form
...
// List all rows
ArrayList<String> rows = new ArrayList<String>(2);
rows.add(new String());
rows.add(new String());
final ListView<String> lv = new ListView<String>("rows", rows) {
#Override
protected void populateItem(ListItem<String> item) {
int index = item.getIndex() + 1;
item.add(new Label("index", index + "."));
TextField<String> text = new TextField<String>("text", item.getModel());
item.add(text);
}
};
rowPanel.add(lv);
AjaxSubmitLink addLink = new AjaxSubmitLink("addRow", form) {
#Override
protected void onError(AjaxRequestTarget target, Form<?> form) {
if (target != null) target.add(rowPanel);
}
#Override
protected void onSubmit(AjaxRequestTarget target, Form<?> form) {
lv.getModelObject().add(new String());
if (target != null) target.add(rowPanel);
}
};
addLink.setDefaultFormProcessing(false);
rowPanel.add(addLink);
...
And here is the mark up:
<div wicket:id="rowPanel">
<span wicket:id="rows">
<span wicket:id="index">1.</span>
<input type="text" wicket:id="text"/>
</span>
Add row
</div>
My previous comment as anwser:
You may need to call setReuseItems(true) on the listview.
Another way of ajaxifying a listview can be found at the Wicket in Action blog
Your example would work fine if you hadn't added the following line:
addLink.setDefaultFormProcessing(false);
Your link doesnt process the form like he would normally do (update models etc, see IFormSubmitter)
You could use nested Forms to updated only the area you need and set defaultFormProcessing back to true.
Like:
<form wicket:id="form">
(...) <!-- (other form elements you dont want to get updated -->
<form wicket:id="repeaterForm"> <!-- form for your repeater textfields-->
<div wicket:id="refreshDiv">
<input type="textfield" wicket:id="repeaterText" />
</div>
<a wicket:id="addMoreLink>add more</a>
</form>
</form>
Wicket will make sure you don't actually have nested forms within your markup (it will replace the nested form with a div) because it ain't valid HTML. But it would work as if the forms are nested nevertheless.
You have to use the model from the target like this:
IModel model = target.getModel();
Then cast to the listView. I don't know if this works, but I have some similar code here.
Related
I have a menu and a content area which displays content based on the selected item in the menu. Since the user is allowed to change the structure of the main menu, I decided that everything will be on page /home/index and will have a guid assigned to the content which needs to be shown. I started with the idea to introduce partial views, but realized that ASP.NET Core doesn't have RenderAction anymore and was replaced by ViewComponents.
So I used ViewComponents and everything works fine, except that I've stumbled on a situation where I need to have a component as a submit form.
In an example: One menu item is the menu is a component that shows a list of users. Another menu item is a component that creates a new user. On create user component I'll have a form that needs to be filled and on successful submit, I want to redirect the user to the component that shows a list of users. In the case of unsuccessful submit, error, wrong input I would of course not want to redirect the user to the list of users.
Since ViewComponents' job is to display view, how should I approach this issue? I'm looking for pointers in the right direction.
I have little experience in this field so any help would be appreciated.
UPDATE
In Index.cshtml:
<tbody>
#foreach (string item in Model.Components)
{
<tr>
<td>
<div class="col-md-3">
#await Component.InvokeAsync(#item)
</div>
</td>
</tr>
}
</tbody>
This is inside the content area. Components are string names of components I'd like to show in the content area (currently listed one after the other).
My ViewComponent which will get called when I click on the menu item to display the form:
public class TestFormViewComponent : ViewComponent
{
public async Task<IViewComponentResult> InvokeAsync()
{
return View("_TestForm", new TestModelPost());
}
}
My _TestForm.cshtml component:
#model TestModelPost
<form asp-controller="Home" asp-action="TestPost" method="post">
<label asp-for="Name"></label>
<input asp-for="Name" /><br />
<label asp-for="Surname"></label>
<input asp-for="Surname" /><br />
<button type="submit">Go</button>
</form>
And the action TestPost called:
[HttpPost]
public IActionResult TestPost(TestModelPost model)
{
// Save model data, etc
// !ModelState.IsValid -> go back to form
// Success -> go to specific id
return RedirectToAction("Index", new { id = 1 });
}
How should I approach this? Or rather, am I even on the right track? I'm not sure how I would go "back" to that view I created in my _TestForm component in case the input was incorrect.
View components, just like child actions before them in ASP.NET MVC do not support POSTs. You need a full fledged action to handle the form post, which will need to return a full view. Essentially, you just need to think about it more abstractly.
A view component is just ultimately a means of dumping some HTML into the eventual response. When the full response is returned to the client, there's no concept of what was a view component, a partial view, etc. It's just an HTML document. Part of that HTML document is your form. That form will have an action, which ultimately should be a route that's handled by one of your controller actions. A traditional form post cause the entire browser view to change, so the response from this action should either be a full view or a redirect to an action that returns a full view. The redirect is the more appropriate path, following the PRG pattern. It is then the responsibility of that view that is eventually returned to determine how the content is constructed, using view components, partials, etc. as appropriate.
Long and short, this really has nothing to do with your view component at all. All it is doing is just dropping the form code on the page.
For forms like this that are part of the layout, it's usually best to include a hidden input that contains a "return URL" in the form. The action that handles the form, then can redirect back to this URL after doing what it needs to do, in order to give the semblance that the user has stayed in the same place.
Kindly refer to the following link:
https://andrewlock.net/an-introduction-to-viewcomponents-a-login-status-view-component/
it might be of help
public class LoginStatusViewComponent : ViewComponent
{
private readonly SignInManager<ApplicationUser> _signInManager;
private readonly UserManager<ApplicationUser> _userManager;
public LoginStatusViewComponent(SignInManager<ApplicationUser> signInManager, UserManager<ApplicationUser> userManager)
{
_signInManager = signInManager;
_userManager = userManager;
}
public async Task<IViewComponentResult> InvokeAsync()
{
if (_signInManager.IsSignedIn(HttpContext.User))
{
var user = await _userManager.GetUserAsync(HttpContext.User);
return View("LoggedIn", user);
}
else
{
return View();
}
}
}
Our InvokeAsync method is pretty self explanatory. We are checking if the current user is signed in using the SignInManager<>, and if they are we fetch the associated ApplicationUser from the UserManager<>. Finally we call the helper View method, passing in a template to render and the model user. If the user is not signed in, we call the helper View without a template argument.
As per your code segment you can try to modify it as follows:
public class TestFormViewComponent : ViewComponent
{
public async Task<IViewComponentResult> InvokeAsync()
{
if (_signInManager.IsSignedIn(HttpContext.User))
{
//user list display
return View("_TestForm", new TestModelPost());
}
else
{
return View();
}
}
}
Hello again JSF Gurus.
I am looking for help on custom renderers and ajax handlers. Our project cannot use the normal radio button and check box renderers as they render in tables and this is against our corporate standards. Neither can we use third party component libraries.
We have a custom renderer that we have used in a few projects now that works fine. However, on this particular project, I need to render the ajax click handler for some radio buttons. Following the advice in the answer to this question I have added the call to RenderKitUtils.renderSelectOnclick(); and whilst it appears to render the same javaScript as the standard renderer, and indeed, firebug network panel shows the outgoing request, my value change listener is not being fired and I am looking for help as to why (it fires perfectly well with the standard renderer)
Some code:
Firstly, the creation of the ajax handler (the radio button is created programatically)
AjaxBehavior valueChangeAction = (AjaxBehavior) FacesUtils.getApplication().createBehavior(AjaxBehavior.BEHAVIOR_ID);
valueChangeAction.addAjaxBehaviorListener(new ProbeQuestionListener(currentQuestion, "probeDiv" + questionNumber,
probeDiv));
selectUI.addClientBehavior("valueChange", valueChangeAction);
valueChangeAction.setRender(Collections.singletonList("probeDiv" + questionNumber));
Wih the standard renderer, this produces:
<table id="answer_1" class="marginLeft1a">
<tbody>
<tr>
<td>
<input id="answer_1:0" type="radio" onclick="mojarra.ab(this,event,'valueChange',0,'probeDiv2')" value="0" name="answer_1">
<label for="answer_1:0"> Yes</label>
</td><td>
<input id="answer_1:1" type="radio" onclick="mojarra.ab(this,event,'valueChange',0,'probeDiv2')" value="1" name="answer_1">
<label for="answer_1:1"> No</label>
</td>
</tr>
and all is well. The ajax request is posted and my server side listener gets fired.
The relevant parts of my custom renderer:
public void encodeEnd(final FacesContext p_context, final UIComponent p_component) throws IOException {
/* set up convenience fields */
context = p_context;
component = p_component;
componentId = component.getClientId(context);
// rendering divs etc omitted.
while (iterator.hasNext()) {
final UIComponent childComponent = iterator.next();
// irrelevant code omited
if ("javax.faces.SelectItem".equals(childComponent.getFamily())) {
renderSelectItem(idIndex, childComponent);
idIndex++;
}
}
}
private void renderSelectItem(final int p_idIndex, final UIComponent p_childComponent) throws IOException {
// unrelated code omitted
writer.startElement("input", component);
writer.writeAttribute("type", getInputType(), null);
writer.writeAttribute("id", p_childComponentId, "id");
writer.writeAttribute("name", componentId, "clientId");
RenderKitUtils.renderSelectOnclick(context, component, false);
writer.endElement("input");
// unrelated code omitted
}
Update: The decode method in the base class for my renderer is:
#Override
public void decode(final FacesContext p_context, final UIComponent p_component) {
final Map<String, String> valuesMap = p_context.getExternalContext().getRequestParameterMap();
final UIInput input = (UIInput) p_component; // NOSONAR
final Object value = valuesMap.get(input.getClientId(p_context));
// System.out.println("radio button decode: found " + value);
input.setSubmittedValue(value);
}
This renders as :
<span class="marginLeft1a" id="answer_1">
<label for="answer_1:0">
<input type="radio" value="0" onclick="mojarra.ab(this,event,'valueChange',0,'probeDiv2')" name="answer_1" id="answer_1:0">
Yes
</label>
<label for="answer_1:1">
<input type="radio" value="1" onclick="mojarra.ab(this,event,'valueChange',0,'probeDiv2')" name="answer_1" id="answer_1:1">
No
</label>
</span>
As you can see, the javaScript click handler is identical, as are the name and id attribute values of the input tags. Although this triggers the Ajax request, the listener is not fired server side as it is with the first version.
Any ideas ?
The decode() method of the Renderer (or UIComponent if there's no renderer) is supposed to decode the ajax request based on javax.faces.behavior.event request parameter and trigger ClientBehavior#decode() on the matching client behaviors.
Basically, the logic is as follows:
Map<String, List<ClientBehavior>> behaviors = ((ClientBehaviorHolder) component).getClientBehaviors();
if (!behaviors.isEmpty()) {
Map<String, String> params = context.getExternalContext().getRequestParameterMap();
String behaviorEvent = params.get("javax.faces.behavior.event");
if (behaviorEvent != null) {
List<ClientBehavior> behaviorsForEvent = behaviors.get(behaviorEvent);
if (behaviorsForEvent != null && !behaviorsForEvent.isEmpty()) {
String behaviorSource = params.get("javax.faces.source");
if (isBehaviorSource(context, behaviorSource, component.getClientId())) {
for (ClientBehavior behavior: behaviorsForEvent) {
behavior.decode(context, component);
}
}
}
}
}
Given the fact that you're using RenderKitUtils and the generated HTML output contains mojarra.ab(), I'll assume that you're using Mojarra. In that case, it's perhaps wiser to extend your custom renderer from RadioRenderer instead which has all this logic already implemented in HtmlBasicRenderer and SelectManyCheckboxListRenderer so that you don't need to reinvent the wheel.
I am using liferay to develop a custom portlet (mvc portlet) my problem is that i have one form in jsp page in HTML and and what i want is to validate some field of the forms.and on submit button its redirecting to another page.the validation i have done is working but what happen is when am clicking the submit button its redirect to another page and on that page its just showing default error from liferay. What i want is to display same page if request not completed with the error message near the field which validation has false.
How can i solve this issue?
this is my code to checking validation while add method in action class
public void addRestaurant(ActionRequest request, ActionResponse response) {
log.info("Inside addRegistration");
List<String> errors = new ArrayList<String>();
restaurant rest = RestaurantActionUtil
.getRestaurantFromRequest(request);
boolean restValid = RestaurantValidator
.validateRestaurant(rest, errors);
if (restValid) {
try {
log.info(rest);
restaurant test = restaurantLocalServiceUtil
.addrestaurant(rest);
if (test == null) {
log.error("Restaurant was Found Null");
response.setRenderParameter("jspPage", errorJSP);
return;
}
} catch (SystemException e) {
log.error(e);
/*
* If there is an error then divert the control to the error
* page.
*/
response.setRenderParameter("jspPage", errorJSP);
return;
}
SessionMessages.add(request, "restaurant-added");
return;
} else {
for (String error : errors) {
SessionErrors.add(request, error);
}
SessionErrors.add(request, "error-while-adding");
request.setAttribute("rest", rest);
return;
}
}
This is my validator class
public class RestaurantValidator {
public static boolean validateRestaurant(restaurant rest, List errors) {
boolean valid=true ;
if (Validator.isNull(rest.getName())) {
errors.add("Name-required");
valid = false;
}
if (Validator.isNull(rest.getLicensekey())) {
errors.add("license-key-required");
valid = false;
}
following is my view.jsp code
Restaurant Name*
" />
<span class="help-block">help block</span>
</div>
</div>
<div class="row-fluid">
<div class="span12">
<label>License Key<span class="f_req">*</span></label>
<liferay-ui:error key="license-key-required" message="license-key-required" />
<input type="text" name="licensekey" class="span8" value="<%=restaurantOBJ.getLicensekey() %>"/>
</div>
</div>
the error message is deisplaying on the redirected page with following way rather then on same page i want the error near textbox of name with the error of "Name_required"
The error message is displaying on the redirected page the following way rather then on same page, I want the error near the name-textbox with the error of "Name_required".
what I want is when name is blank then it should not submit the form and give error near text box of name in my view.jsp page.
I would try to answer your question, try to set a actionResponse.setRenderParameter("valid", "NO"); in your addRestaurant method if the validation fails.
And get this parameter in the doView method and set the renderPage accordingly in this method: include(renderPage, renderRequest, renderResponse); at the end of the doView method.
If you can provide all the information in your question with nice formatting like the validator method, <aui:form> and the javascript method called on onSubmit then we can go ahead with looking for other solution.
In the mean-while you can try this out and see if it works. :-)
In my page there is one textbox by default and one add button beside it. I need to add the another textbox when user click Add button. And there should be two buttons Add and Remove beside newly added text box. And same process goes on i.e., user can add Textbox using Add button and remove it using remove button.
I am new to mvc 3 so i am confused how to proceed. Is there any way like placeholder in asp.net so that we can add control at runtime.
Any suggestion and idea will be helpful to me
MVC is a very "hands-off" framework compared to Web Forms, so you're free to add the new textboxes how you like. Note that "controls" don't exist in MVC.
Here's how I'd do it:
Model:
class MyModel {
public Boolean AddNewTextBox { get; set; }
public List<String> MultipleTextBoxes { get; set; } // this stores the values of the textboxes.
}
View (I prefer the Web Forms view engine, I'm not a fan of Razor):
<% for(int i=0;i<Model.MultipleTextBoxes.Count;i++) { %>
<%= Html.TextBoxFor( m => m.MultipleTextBoxes[i] ) /* this might look like magic to you... */ %>
<% } %>
<button type="submit" name="AddNewTextbox" value="true">Add New Textbox</button>
<button type="submit">Submit form</button>
Controller:
[HttpPost]
public ActionResult MyAction(MyModel model) {
if( model.AddNewTextBox ) model.MultipleTextBoxes.Add("Yet another");
else if( ModelState.IsValid ) {
// your regular processing
}
}
You can also add more textboxes with Javascript and it work perfectly fine. All that matters is the HTML input elements. There's no cryptic viewstate. MVC is stateless.
Note that because I used <button type="submit"> my example will not work reliably in Internet Explorer 6-8 (sucks, I know), but you can replace them with <input type="submit"> with no ill-effects.
This requires some Javascript/JQuery... The following is a sketch only, but will hopefully be useful as a general approach.
The remove button
You want to render a button that can target its own container for removal. To do that, use some markup like this:
<div class="item-container">
<input type="button" onclick="removeItem(this)" />
</div>
And the Javascript for removeItem:
<script>
function removeItem(element) {
// get the parent element with class "item-container" and remove it from the DOM
$(element).find(".item-container").remove();
}
</script>
The add button
You could either use a partial view with Ajax, or use straight Javascript; which one is best likely depends on whether you need a round-trip to the server to create a new item. Let's say you need to go the the server to generate a new ID or something.
First, create a partial view and corresponding controller action; this should contain the remove button as above, as well as the text box and add button.
Now, create an Ajax form on your main page that gets invoked when you click Add:
<script src="#Url.Content("~/Scripts/jquery.unobtrusive-ajax.min.js")" type="text/javascript"></script>
#using (Ajax.BeginForm("New", new AjaxOptions() { UpdateTargetId="ajaxTarget", HttpMethod = "GET" })) {
<input type='submit' value='Add New' />
}
<div id="ajaxTarget"></div>
This code fetches your partial view (from the action New in the current controller) and adds the result to the ajaxTarget element.
Note The Ajax form requires Unobtrusive Ajax, which you can install via Nuget: Install-Package JQuery.Ajax.Unobtrusive.
Consider the following scenario:
My form model
public class PersonForm {
#NotNull
private String name;
/*usual getters and setters*/
}
My controller:
#Controller
#SessionAttribute(types={ PersonForm.class })
public class MyController {
#RequestAttribute(...)
public String render(final ModelMap map) {
/* get list of info and for each info
* create a PersonForm and put it in the modelmap
* under key p0, p1, p2, ..., pn
*/
}
public String submit(final ModelMap map,
#Valid final PersonForm form,
final BindingResult result) {
if (result.hasErrors()) {
// return to page
} else {
// do necessary logic and proceed to next page
}
}
}
And finally my JSP view
...
<c:forEach ...>
<form:form commandName="p${counter}">
... other form:elements and submit button goes here
</form:form>
</c:forEach>
...
As you can see I am trying to handle multiple forms of the same class type. The submit works -- it gets me to the submit(...) method just fine, and so does the validation. However re-rendering the page does not show me the expected error messages!
Even worse -- I checked what is being passed in the submit header and there is no indication whatsoever which form submitted, so there is no way to discriminate between one form on another. This led me to believe multiple forms of the same class type is not possible ...
Is there any other way I could do this (apart from Ajax) ?
Many thanks.
I managed to get this 'hack' to work. It is as what jelies has recommended so the credit goes all to him.
In simple terms, the concept is to pre-fill your view using the traditional <c:forEach> construct. The tricky part is whenever the 'Submit' button of that respective row is pressed, all of the information must be injected into a hidden form and force-submitted to the Controller. If the screen is rendered again with some errors, the script must be responsible of injecting the values back to the respective rows including the errors.
1) My model
public class PersonForm {
private String id;
#NotNull
private String name;
/*usual getters and setters*/
}
2) My controller
#Controller
#SessionAttribute(/* the hidden form name, the person list */)
public class MyController {
#RequestAttribute(...)
public String render(final ModelMap map) {
/* get list of info and for each info
* create a PersonForm and put it in the modelmap
* under key p0, p1, p2, ..., pn
*/
}
public String submit(final ModelMap map,
#Valid final PersonForm form,
final BindingResult result) {
if (result.hasErrors()) {
// return to page
} else {
// do necessary logic and proceed to next page
}
}
}
3) My view
...
<form:form commandName="personForm" cssStyle="display: none;">
<form:hidden path="id"/>
<form:hidden path="name" />
<form:errors path="name" cssStyle="display: none;" />
</form:form>
...
<c:forEach var="p" items="${pList}">
<input type="text" id="${ p.id }Name" value="${ p.name }" />
<!-- to be filled in IF the hidden form returns an error for 'name' -->
<span id="${ p.id }nameErrorSpan"></span>
<button type="button" value="Submit" onclick="injectValuesAndForceSubmit('${ p.id }');" />
</c:forEach>
...
<script type="text/javascript">
injectValuesAndForceSubmit = function(id) {
$('#id').val( id ); // fill in the hidden form's id
$('#name').val( $('#'+id+'name').val() ); //fill in the hidden form's name
$('#personForm').submit(); //submit!
}
$(document).ready(function() {
var id = $('#id').val();
if (id.trim().length == 0) {
//Empty. Nothing to do here as this is a simple render.
} else {
//The page seems to be returning from some sort of error ... pre-fill the respective row!
$('#'+id+'name').val($('#name').val());
var hiddenNameErrorSpan = $('#name.errors');
if (hiddenNameErrorSpan) {
$('#'+id+'nameErrorSpan').text(hiddenNameErrorSpan.html());
}
} //else
}
</script>
As you can see the view has the hairiest parts -- hopefully it will still proves to be useful for anyone who (unfortunately) comes across the same situation as mine. Cheers!
IMHO having multiple forms makes things overcomplicated (or at least with spring). Also, you are using multiple forms but only one is going to be submitted.
So, I suggest that the easiest way to manage this is using a unique hidden external form with person properties. When one of the buttons is pressed, fill accordingly the person properties of form and submit it. With this you are achieving the tipical spring form submit/validation.
Maybe this solution requires a bit work with JavaScript, but I don't know how to handle spring-mvc with multiple forms, I always tried to avoid it, due to previous unsuccessful attemps.