I have to say, that I am not sure is that a problem, but I think so. I have a from where is two submit buttons. With first one I fetch product infos via serial number and with two I submit the whole form again.
Search button and code:
BUTTON 1
<p:column><h:outputLabel value="#{bundle.newRma_label_sarjanumero}" for="sarjanro" /></p:column>
<p:column><p:inputText id="sarjanro" value="#{MainController.selected.sarjanro}" style="width: 200px;" /></p:column>
<p:column><p:commandButton icon="ui-icon-search"
action="#{MainController.haeTiedotSarjanrolla}" style="width: 100px;" update=":messagePanel"/></p:column>
//Fetching product info with serial number
public String haeTiedotSarjanrolla() {
log.info("sarjanro=" + current.getSarjanro());
Sarjanumerot s = null;
s = helper.getSerialInfo(current.getSarjanro());
if (s != null) {
current.setAn8(s.getAn8());
current.setDsc1(s.getDsc1());
current.setDsc2(s.getDsc2());
current.setSarjanro(s.getId().getLotn());
current.setTuotenro(s.getId().getLitm());
}
// Back to same page!
return "newRma";
}
Another button (save)
<p:column><p:commandButton action="#{MainController.talletaUusiRma}" value="#{bundle.newRma_tallenna}" immediate="true" style="width: 220px;"/></p:column>
Save (Button 2) is working, BUT request is empty, there is not any data. I tried to check it like that and it is always NULL:
another field what is not populated after first submit:
<p:row>
<p:column></p:column>
<p:column><p:outputLabel value="#{bundle.newRma_shortdesc}" for="shortdesc"/></p:column>
<p:column><p:inputTextarea rows="4" cols="30" id="shortdesc" value="#{MainController.selected.shortdesc}" style="width: 200px;"/></p:column>
</p:row>
Map<String, String> parameterMap = (Map<String, String>) FacesContext.getCurrentInstance()
.getExternalContext().getRequestParameterMap();
String temp = parameterMap.get("shortdesc");
temp is always NULL!!
Is there any best practise to handle this with primefaces, ajax, js or something else?
Thanks!
Sami
This works only if the bean is #ViewScoped and if you return null or void from action method.
If you return non-null or void, such as follows
// Back to same page!
return "newRma";
then a brand new view scoped bean instance will be created, hereby trashing the initial one.
Further, I'm not sure why you used immediate="true" on the second button, this makes no sense, but are you aware that this way the data of non-immediate inputs won't be processed at all and thus you should do it with the data of the previous non-immediate submit?
By the way, the way how you collected submitted data makes also no sense. You don't need to traverse the request parameter map (you used an invalid parameter name by the way). Just directly access the bean properties which are been bound to input values.
Related
Determine if NgForm Looks Exactly As It Did Before Any User-Input
It seems that form.dirty doesn't redact its value after it has been changed, and form.touched seems to always be false no matter what: dirty is touched, and touched is tetched.
template.html
<form #form="ngForm" (ngSubmit)="handleSubmission($event, {}, form)">
...
<input
#input
type="text"
[name]="item.title"
[(ngModel)]="item.estimate"
(ngModelChange)="handleEstimateChange(item, item.estimate, input, form)"
/>
...
</form>
component.ts
export class LeComponent {
#Input('data') public data: any;
public handleEstimateChange: Function;
constructor(private $: Sandbox) {
this.handleEstimateChange = $.debounce(this.handleEstimate.bind(this), (1000*0.2));
}
handleEstimate(item: any, estimate: number, input: HTMLInputElement, form: NgForm) {
if (!estimate) delete item.esitmate;
(this, item, estimate, input, form);
// Why does form.dirty never change back to pristine again???
}
}
In the TypeScript, I'm debouncing the ngModelChange handler to give Angular a chance to change the form.dirty value before I check it. This is because ngModelChange gets triggered before the NgForm object has been modified.
If !estimate, because estimate === "", then set it back to its original value of undefined. In this case, the form should look exactly like it did before any user-input had occurred.
However, when I put a breakpoint on the line right above the comment and I output form.dirty to the console, the NgForm never changes dirty back to false.
Is it possible to determine if the form looks exactly like it did before any user-input?
Obviously, I can write my own dirty logic, but wouldn't that mean that NgForm is kind of useless? There's got to be something I'm missing, right? How could dirty not mean dirty?
I've taken a look at some other SO questions -- the first one being similar but definitely not the question I am asking. They are asking if this is intentional -- I don't care; I'd like to know how to accomplish the goal above.
Close, but no cigar:
angular2 formcontrol stays dirty even if set to original value
Block routing if form is dirty [ Angular 2 ]
Angular 2 getting only the dirty values in a controlgroup
How do I programmatically set an Angular 2 form control to dirty?
Angular 2.x/4.x & bootstrap: patchValue does not alter dirty flag. Possible bug?
With template-driven forms and a very flat data model, I implemented it like this:
private currentProduct: IProduct;
private originalProduct: IProduct;
get isDirty(): boolean {
return JSON.stringify(this.originalProduct) !== JSON.stringify(this.currentProduct);
}
get product(): IProduct {
return this.currentProduct;
}
set product(value: IProduct) {
this.currentProduct = value;
// Clone the object to retain a copy
this.originalProduct = Object.assign({}, value);
}
But this only works for very simple cases.
As I mentioned in the comments, using reactive forms gives you more flexibility in managing your data model separate from your user entries.
What Was Most Useful
template.html
<form #form="ngForm" (ngSubmit)="handleSubmission($event, {}, form)">
...
<input
#input
type="text"
[name]="item.title"
[attr.name]="item.title"
[(ngModel)]="item.estimate"
(ngModelChange)="handleEstimateChange(item, item.estimate, input, form)"
/>
...
</form>
component.ts
export class LeComponent {
#Input('data') public section: any;
public handleEstimateChange: Function;
private resetFormControl = (input: HTMLInputElement, form: NgForm) => {
var name = input.name, control = form.controls[name];
control.reset();
// control.markAsPristine();
// control.setValue(undefined);
// control.updateValueAndValidity();
};
constructor(private $: Sandbox) {
this.handleEstimateChange = $.debounce(this.handleEstimate.bind(this), (1000*0.2));
}
handleEstimate(item: any, estimate: number, input: HTMLInputElement, form: NgForm) {
if (!estimate) this.resetFormControl(input, form);
(this, item, estimate, input, form);
// Why does form.dirty never change back to pristine again???
}
}
Note
[attr.name]="..." (template.html)
resetFormControl
Basically, simply deleteing the value was not enough because it was still present on the FormControl object (form.controls). To clear it properly, invoke control.reset() for the individual control -- this in-turn invokes .markAsPristine() which communicates to the parent NgForm. Also, input.name was empty as it was only represented by ng-reflect-name unless [attr.name] elucidated the same value -- [name] is really just there because its required by Angular.
Now, anytime an <input /> value changes -- and its falsey -- we reset the input ensuring that if all are falsey, Angular will automatically handle the NgForm's dirty-state correctly.
I have a "Thing" domain class, where each Thing has an record number (which is not the automatically generated id), that the user will use to access a Thing:
class Thing {
...
String recordNumber
...
}
There is a form to look for a Thing, knowing its recordNumber:
<g:form action="search">
<input name="recordNumber">
<g:submitButton name="btn" value="go to this Thing"/>
</g:form>
I would like to use a validation process in this form: if the recordNumber is not found (Thing.findByRecordNumber(recordNumber) == null), then the input field must turn in red, and a tooltip must show the error message "record number not found".
As far as I know/read (I'm a grails rookie), this has to be written as a constraint in the Thing class:
static constraints = {
recordNumber validator: { n -> Thing.findByRecordNumber(recordNumber) }
}
The problem is: I do not have in this form all the "Thing" properties to populate, just the recordNumber one, so I just can't call
new Thing(params).validate()
How to call validation on just one field, not on the whole object ?
If this is your main question, although I see others there:
"How to call validation on just one field, not on the whole object ?"
You can pass a list of values to validate and it will only validate those properties
new Thing(params).validate(["recordNumber"])
http://grails.org/doc/latest/ref/Domain%20Classes/validate.html
Validation is for constraints for domain class properties. You need an action in your controller:
def search = {
if(params.recordNumber && Thing.findByRecordNumber(params.recordNumber)){
redirect(action: "show", params:[id:Thing.findByRecordNumber(params.recordNumber).id])
}else{
flush.message = "No record found"
render(view:'VIEW_WITH_SEARCH_FORM')
}
}
If you want to validate without refreshing page, write a javascript code.
I have a knockout viewmodel getting populated from a JSON call.
In a select element in a form, I have a set of options (also coming from viewmodel) and the value, part of observableArray.
The issue is only with the select element and not with input ones -- when submitting the form, only the values that have been assigned to in select contain proper values. So the ones that have been successfully loaded from JSON and presented in form, but left unchanged, will be sent back to server as the first value from the options array.
HTML Form:
<form>
<table >
<thead>
...
</thead>
<tbody data-bind='foreach: ScaledCostEntries'>
<tr>
<td><input data-bind='value: StartDateString' class="startdate" type="text"/></td>
<td><select data-bind='value: InvoiceType, options: $root.InvoiceTypes'></select></td>
<td><a href='#' data-bind='click: $root.removeCost'>Delete</a></td>
</tr>
</tbody>
</table>
<button data-bind='click: addCost'>Add New Row</button>
<button data-bind='click: save' >Update</button>
</form>
In this code above the problem is with InvoiceType, part of the viewmodels ScaledCostEntries observableArray. (Also, if I swap the order of value and options, that will not put a selected value in the select element).
and the JS:
<script type="text/javascript">
$(function () {
var scaledCostModel = function () {
var self = this;
self.ScaledCostEntries = ko.observableArray([]);
self.InvoiceTypes = ko.observableArray([]);
self.addCost = function () {
self.ScaledCostEntries.push({
StartDateString: ko.observable(),
InvoiceType: ko.observable()
});
};
self.removeCost = function (cost) {
cost.IsDeleted = true;
self.ScaledCostEntries.destroy(cost);
};
self.save = function (form) {
jQuery.ajax({
url: '#Request.Url.PathAndQuery',
type: "POST",
dataType: "json",
contentType: "application/json; charset=utf-8",
data: ko.toJSON(self.ScaledCostEntries)
});
};
};
jQuery.getJSON('#Request.Url.PathAndQuery', function (data) {
ko.mapping.fromJS(data, {}, viewModel);
});
var viewModel = new scaledCostModel();
ko.applyBindings(viewModel);
});
</script>
So, to summarize, the issue is with viewmodel's property bound to a select element. When the select is left unchanged (not reselected), the viewmodel will have it's value as the first item from the options (InvoiceTypes) array, when posting to server.
In the end, I might be forgetting something trivial and this is my first more serious knockout.js attempt.
Note: InvoiceType is part of the ScaledCostEntries, which is observableArray.
InvoiceTypes is observableArray.
Both InvoiceTypes and ScaledCostEntries, come from JSON and ScaledCostEntries is sent back.
My assumption is that this is due to how the ScaledCostEntries are being handled on the server on the form submission.
I've run into the problem before (across various server-side frameworks) when I have a main model with a list of dependent models that are being added and removed, and a form submission is done against the main model to update everything.
The problem is that during form submission, when the values in the request are being mapped to the server-side model, blank values are taken to mean "no change" as opposed to "delete this". This works well with properties directly on a model, but doesn't work for lists of dependent models.
There are a couple of ways I've found of dealing with this: use Ajax to delete the underlying model and update the relationship with the main model when the 'remove' or 'delete' button is pressed in the view; or explicitly send the whole list of models each time and explicitly delete and rebuild the list on the server for each form submission. Each are applicable in different situations, and there are probably other approaches that may work well, too.
I've searched my way, but can't figure this out. I made a directive manyToOneSelect (custom component) that loads items from the server, shows them to the user and lets the user pick one. That works well, but I cannot figure out how to prevent the form from being submitted if no item is picked by the user, i.e. how to invalidate the form.
Below is pretty much the directive:
angular.module('myApp.directives').
directive('manyToOneSelect', function(entityService) {
return {
restrict:'E',
templateUrl:'partials/control/n21select.html',
scope:{
entityName:'#',
entityField:'#',
bindVariable:'='
},
compile:function (tElement, tAttrs, transclude) {
return function (scope, element, attrs) {
var inner = element.children("#n21select");
scope.entities = [];
scope.$watch('entityName', function ($new, $old) {
entityService.getList(scope.entityName, function (data) {
scope.entities = data;
}, []);
}, true);
scope.lookup = function(uuid) {
for(var i in scope.entities) {
if(scope.entities[i].uuid == uuid) {
return scope.entities[i];
}}}}}}});
Here is the corresponding partial partials/control/n21select.html:
<select ng-hide="disable" ng-options="entity.uuid as entity[entityField] for entity in entities" ng-model="bindVariable" required></select>
<span ng-show="disable">{{lookup(bindVariable)[entityField]}}</span>
Here is how I use the directive:
<form ng-href="#/" ng-submit="save()">
<many-to-one-select entity-name="customer" entity-field="name"
bind-variable="entity.customerUuid"></many-to-one-select>
...
My problem seems lack of strategy, rather than "not entirely getting it to work", hence you don't see any attempt in the code I posted above. Let this be then a fairly open question: how to do it? :) Much appreciated already!
There's a few ways to do this.
Considering how you've already built out the directive, one way is to add a scope attribute for the form itself. something like:
scope: {
form: '='
}
Then you'd pass your form element in like so:
<form name="myForm" ng-submit="whatever()">
<my-directive-name form="myForm"></my-directive-name>
</form>
And in circumstance in your directive you wish to invalidate your form, you'd just call $setValidity on it:
link: function(scope, elem, attr) {
if(somethingIsWrong) scope.form.$setValidity('reason', false);
}
That's ONE way to do it, here's a BETTER way to do it if you can re-engineer your directive:
The other way, which is probably preferred, is to have your directive require ngModel. Then you'll have more grainular control over your validation, as ngModel's controller will be passed in and you can use that to invalidate both your form, and a singular field on your form:
app.directive('bettererWay', function() {
return {
require: 'ngModel',
restrict: 'E',
link: function(scope, elem, attr, ngModel) {
if(somethingIsBad()) ngModel.$setValidity('somethingIsBad', false);
}
};
});
And that's how you do it, in a nutshell. Hopefully that gets you started in the right direction.
EDIT: Weird issue with submission regardless of validity (in comments)
This is apparently an issue caused by Angular trying to adhere to the HTML specs.
From the comments in their code approx. line 214 here:
* To prevent double execution of the handler, use only one of ngSubmit or ngClick directives. This
* is because of the following form submission rules coming from the html spec:
*
* - If a form has only one input field then hitting enter in this field triggers form submit
* (`ngSubmit`)
* - if a form has has 2+ input fields and no buttons or input[type=submit] then hitting enter
* doesn't trigger submit
* - if a form has one or more input fields and one or more buttons or input[type=submit] then
* hitting enter in any of the input fields will trigger the click handler on the *first* button or
* input[type=submit] (`ngClick`) *and* a submit handler on the enclosing form (`ngSubmit`)
So, given the above, it might be a good idea to have your directive tied to an input element of type hidden on the page rather than being it's own element. If you have more than one element on the form, invalidity prevents submission just fine.
I have a scala form with several fields.The fields in the form map to the member variables of a Java class. I want to bind one of the fields(say userId) with a value (I dont want the user to enter values for this field. Instead i want to pass this as a parameter to the scala template). However, i was unable to manually bind a form field. Any help is highly appreciated.
See the sample below for easier understanding :
`#(itemForm: Form[Item], user: User)
#import helper._
#main("Item list") {
#if(user != null) {
#form(routes.Application.newItem()) {
#itemForm("userId") = #user.id /**I want to bind the userId form field */
#inputText(itemForm("title"))
#inputText(itemForm("description"))
#inputText(itemForm("price"))
<input type="submit" value="Create">
}
}
}`
In this case it would be better to pass it as action's argument (remember to modify routes declaration)
#form(routes.Application.newItem(user.id)){
....
you can also just use common html
<input type="hidden" name="userId" value="#user.id" />
edit:
Validation in action.Note: it doesn't make sense to display errors on the page next to hidden field, so you do not need placeholders for error messages. It's up to you to pass VALID value into the hidden field. Displaying validation errors to user who can not change the value of hidden field is bad conception.
public static Result newItem(){
Form<ItemModel> itemForm = form(ItemModel.class).bindFromRequest();
if (itemForm.hasErrors(){
return badRequest(newItemView.render(itemForm));
}
itemForm.get().save();
return ok("Your new item is saved...");
}