Context parameter not working in Tapestry 5.3.2 for Submit - submit

I have a loop displaying records, and I want to add submit buttons to all the rows. The reason they need to be submit is because there is a form at the bottom I would like to have persisted when the user selected one of the buttons.
I've seen the comments about using defer, etc, but nothing seems to work for me. The current submit code I'm trying is:
<input t:id="deleteItem" t:type="submit" t:context="item.number.content" value="deleteItem" class="deleteItem" />
To expand on the context:
The current context I have listed, is just a string within the number object within the item object. In fact, it's being displayed in the code above perfectly fine.
To test this differently, I've replace item.number.content with a getContext() method and had it return a hard-coded 1. I then debug this method and see it being called when the page is SUBMITTED, not when the page is rendered as I would have expected.
The context is never populated until after the button is pushed. Am I misunderstanding something??
Edit:
So my issue is with getting the context value. Take for instance my code:
<t:loop source="itemsList" value="item" formState="none">
<!-- Display info here -->
<input t:id="deleteItem" t:type="submit" t:context="myContext" value="deleteItem" class="deleteItem" />
</t:loop>
The definition for getMyContext is:
public String getMyContext() {
String context = item.getNumber().getContent();
return context;
}
The problem is, the method is not called until after the submit has been pressed at which time the variable "item" is null. I was expecting getMyContext to be called each time the submit button is rendered, and then when the submit is selected, the event would be triggered with the appropriate context. Does that make sense?

I finally figured out what my problem was:
formState="none"
I originally did this because it was having trouble translating between my Objects and a String. When this is selected, the submit buttons don't work properly. I ended up changing it to formState="iteration" and now it is working exactly as I expected it to (and I needed the defer as expected too).

Try adding the following to the page/component you add this t:submit to:
#OnEvent(component = "deleteItem")
private void handleSubmit(Integer contextValue) {
//Do whatever you need to do with the passed context value here.
//Most commonly you would store the context in a java page/component field to
//be used by the form eventhandler to do some sort of CRUD
}
The context value will not get written out while rendering the page, it will be passed as a parameter to your event handler while submitting the form. So what you are seeing is correct behavior.

Related

grails gsp with <input type="datetime-local" when posted to controller fails validation and wont save

grails v3.3.9, and bootstrap.
I have been wrestling with getting grails, fields and bootstrap to work happily when processing Java LocalDateTime. Nearly there but not quite ...
I have created a /fields/localdateTime/_widget.gsp like this
<div class="input-group date col-sm-8" >
<input id="${property}-label" name="${property}" type='datetime-local' class="form-control" value="${value}" placeholder="<empty>"/>
<div class="input-group-append" >
<button class="btn btn-icon-fixed-width btn-outline-secondary btn-block" disabled aria-disabled="true" type="button" >
<i class="fas fa-calendar"></i>
</button>
</div>
</div>
this uses bootstrap
when the edit action is triggered on show view, it correctly renders this field in the edit form, and fields plugin, reads the _widget.gsp and renders the field correctly.
The control (in chrome) seems to work ok, and i can select a date, and set the time in the input field
however when i submit the update button, the data posted back to the update action does not pick up my changed date value, but rather sends the original value that was in the domain object before hand.
worst however is that the controller gets the object passed to it and in the debugger the domain object arrives with correct id and what looks like a valid LocalDateTime in the debugger (albeit the original and not the edited value )
def update(BootstrapTest bootstrapTest) {
if (bootstrapTest == null) {
notFound()
return
}
if (!bootstrapTest.validate()){
println "object delivered to update action from edit form doesnt validate "
respond bootstrapTest.errors, view:'edit'
return
}
however whilst it looks ok - when you try and validate it fails
and whats rendered on the browser is "Property ldtProp is type-mismatched". What is wrong with the LocalDateTime validation?
I can create grails bootstrap data and save to db so its not the domain class nor validation when saving that way - its just when the record is delivered to the controller that it breaks
I've tried an equivalent field with LocalDate - and that seems to work and validates correctly, and i can save the object posted to the controller update action.
This is just so frustrating can any one elucidate on what this fails and what to do to correct it ?
this last bit has me stumped.
in order to wrestle with what fields plugin is actually doing, i've got a private version here private plugin copy with adjusted content
i've run run this as private clone of fields plugin because i couldnt watch what was happening under the covers, so this has a number of libraries, style sheets imported back in so i can do in situ debug. various /fields/xxx/*gsp defined to render the bootstrap form /display bits.
Blimey, another nest of worms...
First, the <input type="date" or <input type="datetime-local" as interpreted by chrome renders the browser value as 'mm/dd/yyyy' for date and for datetime-local as mm/dd/yyyy HH:mm:(ss:SSS - greyed out )
When you provide a value="string" it ignores your string - so you have to pass the actual value into input as value="${value}". If you convert that into a formatted field before, then it gets ignored, and default template is shown.
Next problem - when you submit the form the value sent to your params block is actually a ISO formatted string - not a LocalDateTime etc!
So when Grails tries to load the record from the db for you and inject it into your controller, its already tried to update the record once.
The problem is that this has fired a validation which has already failed. So when you get the domain object in your 'Update (Domain xxx) {...}' method the object already has its errors set.
So I tried to convert the strings sent in params to LocalDateTime, LocalDate, and then update my domain object in the controller - but they failed when using validate() as the preexisting errors where already there.
So I had to clear the errors first, then convert the params strings to the right Java date types and then update the domain object and then validate. This then is happy.
So the controller action now has to look like this
def update(BootstrapTest bootstrapTest) {
if (bootstrapTest == null) {
notFound()
return
}
LocalDateTime ldtProp
LocalDate dtProp
if (bootstrapTest.hasErrors()) {
bootstrapTest.clearErrors()
try {
ldtProp = LocalDateTime.parse(params.ldtProp?.toString()) //ISO_LOCAL_DATE_TIME
dtProp = LocalDate.parse(params.dtProp?.toString(), DateTimeFormatter.ISO_LOCAL_DATE) //ISO_LOCAL_DATE
bootstrapTest.ldtProp = ldtProp
bootstrapTest.dtProp = dtProp
bootstrapTest.validate()
} catch (ex) {
println "exception $ex.message"
respond bootstrapTest.errors, view:'edit'
return
}
}
try {
bootstrapTestService.save(bootstrapTest)
} catch (ValidationException e) {
respond bootstrapTest.errors, view:'edit'
return
} ...
So clear the errors first - then correct/convert the params map format to domain object format thats ok for the domain class property (or define set method in the domain that accepts a String and does the conversion internally)
Once you do that the re validation succeeds this time and your controller/db updates start to work.
Without using custom date pickers etc ( and for scaffolding that's overkill ) you have to live with the present data handling for
I have not tried this on another browser but Chrome is my 'normal' dev browser and behaves as indicated above.
--PS - additional trace. In the background when the Domain object is located and loaded, grails seems to have a convertor from String to DateTime, because i built a setDtProp (dt) setter - and this is called with converted value before the object is delivered to the controller - which is why I saw the dtProp change originally described, but no change to the ldtProp (where I have added a setter also in the domain object).
So however Grails is doing the injection process it doesn't do it for LocalDateTime as string from the browser (but it does put string into params for you ), but it will call a setter for LocalDate. Either way when the domain object is loaded its errors() has been set - so you need to handle this from the controller as shown. Trying to fix with setters in Domain class doesn't work for LocalDateTime as your setter is never called!
This can only to be handled in the controller once the object is injected as shown above, as I'd started to do in the first place.

Play Framework - Variables in URL instead of as ? parameters

I'm a just starting to learn Play, so this is a basic question, but I've searched every term I can think of and can't find the answer.
All I want to do is have a page that on submit takes an ID from a text field and puts it in the URL directly (e.g. /myservice/person/123). Instead, the URL being generated contains the ID as a parameter (e.g. /myservice/person?id=123).
I know my controller is being invoked correctly if I type the URL in by hand, so I'm inclined to think my routes file is correct. This is what my entry looks like:
GET /person/:id controllers.PersonActions.getPerson(id: String)
So I'm assuming something is going wrong in my template, which looks like this:
#form(routes.PersonActions.getPerson(personID)) {
#* #inputText(personForm("id")) *#
<input type="text" name="id" value="#personID">
<input type="submit" value="Get">
}
You can see that I've commented out another way using #inputText also, but it behaves identically for me. I'm not tied to either method.
I've also tried this using POST, which removes the ID from the URL entirely, which I don't understand either, but since I'm just doing a simple query, I'd rather use GET.
Can anyone help me understand what's going on here? I feel like there's something fundamental about how routing/URLgeneration works that I'm not understanding, but I've gone through the tutorial and the docs so many times today I'm at a loss.
Thanks in advance.
Oh, and I'm using Java 7, Play 2.1, and Eclipse Kepler
I think you are trying to skip a step and map form request data from a view directly to a controller method. You will need to submit the form, have a controller parse the data and then render the next appropriate view based on the data you parsed from the form.
Remember that the personId in your view is a parameter that is bound server-side when the view is rendered. In this case, the submit url of the form is hard coded to whatever personId is passed in to the view at render time -- it doesn't change dynamically when the input box changes.
To fix, add a controller method to accept requests from /person (I'm guessing this based on the part in your question that says the form is being submitted to /person?id=123, in any case it should be the URL of the form you've shown above)
e.g. if you want to use a GET method for the form add:
GET /person controllers.PersonActions.findPerson(id: String)
and in your PersonActions controller (I'm assuming you're using Java, if scala I'm sure you can adapt it from the docs)
public static Result findPerson(String id){
/*
* I'm assuming this method exists and works because you say
* when you type in /person/123 manually the routing works
* and your router says getPerson is the method name for that URL.
*/
return getPerson(id);
}

Confused about JSF Lifecycle

I want to show a page that renders a list of 'Group' entities, so my page (xhtml) has this in it
<f:event type="preRenderView" listener="#{groupController.loadGroups}" />
This is called when the page loads, and yesterday it was returning a list as expected. The method looks like this
public void loadGroups() {
allGroups = groupDao.findAll();
}
Now I added a form to save a new group (on the same page). When I save that form, it calls this method:
public String save() {
groupDao.create(group);
return "/admin/groups?faces-redirect=true";
}
I have confirmed that the group is saved in the database. But, I was expecting the "return" statement to reload the page as if entering it for the first time, calling the 'preRenderView' method etc etc.
When it comes back into the page it does call that method, but am using a datalist to display the collection of groups returned and when that makes a call to
public Collection<Group> getAllGroups() {
return allGroups;
}
... the 'allGroups' variable has been reset back to null.
I haven't touched JSF for a couple of months and I think I used to understand all this but I obviously don't. Could anyone give me a clue what I'm doing wrong?
If I find the answer (digging my books out right now) I will post an update. Thanks though.
Update: just to clarify, I thought the loadGroups method would be executed before the getAllGroups method, since it's a preRenderView event. But they seem to be getting executed the wrong way around (getAllGroups is called first, before loadGroups() has populated the collection). It's a ViewScoped bean by the way.
I'm not sure why my original version doesn't work, but if I get rid of the preRenderView statement in my page, and add a #PostConstruct annotation on the loadGroups method, it works fine. I understand why the #PostConstruct method would work, but not why the preRenderView doesn't work. Anyway, its working if I do that...

Updating value provider prior to TryUpdateModel

Lets say we have a class with a property called PetsName. If it is left blank on the screen I want to update the value provider so if the user doesn't enter a pet name, we force 'unnamed'. This isn't the actual scenario.. this is of course a sample, so answers like 'just set default values on a webpage, etc' won't fit this scenario : )
The main issue is we want to update the values so when you update the model it will use whatever you have overridden. I guess one idea is to remove the value and add it. When I check ModelState, it does have the updated value, however when I call TryUpdateModel, the value is not updated. Its possible what Im doing below is indeed correct and there is another issue here but I figured I'd try this first. Thanks!
//Sample case:
[HttpPost]
public ActionResult Edit(PetOwner petOwner)
{
//If pets name is not set, force to "Unknown"
if(petOwner.PetsName=="")
{
//Tried this too ModelState.Remove("PetsName");
//ModelState.Add("PetsName", new ModelState());
ModelState["PetsName"].Value = new ValueProviderResult("Unnamed", "Unnamed", CultureInfo.CurrentCulture);
}
//Get the record/relationships from DB to merge with ModelState
PetOwner petOwnerToSave = from o in ctx.PetOwners where o.PetOwnerId == petOwner.PetOwnerId select o;
TryUpdateModel(petOwnerToSave);
//Save petOwnerToSave
}
The real issue behind the scenes here is that Html.HiddenFor wasn't displaying the correct value even though TryUpdateModel was updating a value in the model to give to the view.
The issue here is that the Html helpers assume if you are rendering a view after a post, there mustve been an error (otherwise you wouldve redirected back to the view with a GET method - hence the Post Redirect Get issue)
This is described in detail at:
http://blogs.msdn.com/b/simonince/archive/2010/05/05/asp-net-mvc-s-html-helpers-render-the-wrong-value.aspx

MVC - How to change the value of a textbox in a post?

After a user clicks the submit button of my page, there is a textbox that is validated, and if it's invalid, I show an error message using the ModelState.AddModelError method. And I need to replace the value of this textbox and show the page with the error messages back to the user.
The problem is that I can't change the value of the textbox, I'm trying to do ViewData["textbox"] = "new value"; but it is ignored...
How can I do this?
thanks
You can use ModelState.Remove(nameOfProperty) like:
ModelState.Remove("CustomerId");
model.CustomerId = 123;
return View(model);
This will work.
I didn't know the answer as well, checked around the ModelState object and found:
ModelState.SetModelValue()
My model has a Name property which I check, if it is invalid this happens:
ModelState.AddModelError("Name", "Name is required.");
ModelState.SetModelValue("Name", new ValueProviderResult("Some string",string.Empty,new CultureInfo("en-US")));
This worked for me.
I have a situation where I want to persist a hidden value between POST's to the controller. The hidden value is modified as other values are changed. I couldn't get the hidden element to update without updating the value manually in ModelState.
I didn't like this approach as it felt odd to not be using a strongly typed reference to Model value.
I found that calling ModelState.Clear directly before returning the View result worked for me. It seemed to then pick the value up from the Model rather than the values that were submitted in the previous POST.
I think there will likely be a problem with this approach for situations when using Errors within the ModelState, but my scenario does not use Model Errors.

Resources