Closed etshi closed 8 years ago
It's been a while since I've done this, but if I recall correctly, I think all the fields will be given to you in the locals
argument, and you can validate and render the error fields yourself.
var options = {
template: function(locals) {
let error = [];
//.... push the errors into the error array.
return <span className='field'>
<input className='form-control' type='text' value={locals.value} />
{error}
}
}
Thanks for the prompt response. But from what I understand that this will allow me to display several errors for the same field, which doesn't solve my problem.
My issue is say for example I have a list of IDs. The first one is invalid because it is too short while the second is invalid as it is too long. What I would like to achieve is render the appropriate message for each field displaying "ID is too short" and "ID too long" for first and second fields respectively. Now I can only display one of the two messages for both fields depending on which error occurred later. So if the user entered the short ID after the long one both fields will show an error message of "ID too short" and vice versa.
Thanks
Let me know how it goes or if you need more help.
Giulio
I had already tried using factories taking issue (#177)[https://github.com/gcanti/tcomb-form/issues/177] as a reference before I posted my question, but failed. The thing is I want something more general without any prior knowledge of the field info. To use in a project skeleton.
What I am currently doing is kind of expensive by calling a function every time an error occurs to create and returns the options nested object with the updated error message. And it works fine for all cases except for that stated above.
I am sure that there is a better way of doing it. so do you think for this should I try the template/locale or the factories approach again?
My issue is say for example I have a list of IDs. The first one is invalid because it is too short while the second is invalid as it is too long
In general, if you can express the validation rules synchronously you can use the error
option implemented as a function:
https://github.com/gcanti/tcomb-form/blob/master/GUIDE.md#error-message
Example:
function isTooShort(s) {
return s.length < 2;
}
function isTooLong(s) {
return s.length > 4;
}
var ID = t.subtype(t.Str, function (s) {
return !isTooShort(s) && !isTooLong(s);
});
var Type = t.list(ID);
var options = {
item: {
error: function (s) {
if (t.Str.is(s)) {
if (isTooLong(s)) {
return 'is too long';
}
if (isTooShort(s)) {
return 'is too short';
}
}
}
}
};
const App = React.createClass({
onSubmit(evt) {
evt.preventDefault();
var value = this.refs.form.getValue();
if (value) {
console.log(value);
}
},
render() {
return (
<form onSubmit={this.onSubmit}>
<t.form.Form
ref="form"
type={Type}
options={options}
/>
<button className="btn btn-primary">Save</button>
</form>
);
}
});
In general, if you can express the validation rules synchronously
Otherwise let's use this playground:
function fakeServerCheck(ids, callback) {
setTimeout(() => {
var errors = [];
ids.forEach(function (id) {
if (['a', 'b'].indexOf(id) !== -1) {
errors.push(id);
}
});
callback(errors);
}, 500);
}
var Type = t.list(t.Str);
const App = React.createClass({
getInitialState() {
return {
value: ['a', 'b', 'c'],
options: {}
};
},
onChange(value) {
this.setState({value});
},
onSubmit(evt) {
evt.preventDefault();
var value = this.refs.form.getValue();
if (value) {
fakeServerCheck(value, (errors) => {
//
// how to set the proper error for each item??? (i.e. in this case 'a' and 'b')
//
});
}
},
render() {
return (
<form onSubmit={this.onSubmit}>
<t.form.Form
ref="form"
type={Type}
options={this.state.options}
value={this.state.value}
onChange={this.onChange}
/>
<button className="btn btn-primary">Save</button>
</form>
);
}
});
How to set the proper error for each item
At the moment I've just a workaround:
onSubmit(evt) {
evt.preventDefault();
var value = this.refs.form.getValue();
if (value) {
fakeServerCheck(value, (errors) => {
this.setState({
options: {
item: {
hasError: errors.length > 0,
error: function (value) {
if (errors.indexOf(value) !== -1) {
return 'my error';
}
}
}
}
});
});
}
},
I'll think about a clean solution
Two other observations then I must run to the office, I'll read your replies during the weekend...
1.
The thing is I want something more general without any prior knowledge of the field info. To use in a project skeleton.
This is interesting! Could you elaborate a little bit on your use case?
2.
What I am currently doing is kind of expensive by calling a function every time an error occurs to create and returns the options nested object with the updated error message
What do you mean by "expensive"? If your options
object is really big you could use the t.update
function:
https://github.com/gcanti/tcomb/blob/master/GUIDE.md#updateinstance-object-spec-object-object
Example
var options = {
...other options
item: {
fields: {
name: {},
... other fields
}
}
};
// let's say you just want to disable the name field...
options = t.update(options, {
item: {
fields: {
name: {
disabled: {$set: true}
}
}
}
});
1) what I mean is that I want to use it not for only one form but for a project with many forms, so instead of coding the options object for every form I want it to be done through a function in the project 'util' that generates the options object with the updated error msg.
2) I already use t.update. But as explained in the first point I am trying to use a more generic approach so depending on the path of field in question the function iterates through the current object and if the path exists it just updates the error message if it doesn't exist then it adds it to the object. All by using either '$set' or '$merge'
2) function similar to the following:
function _updateOptions(message,pathList,options) {
let updatedOptions = options || {};
// Remove any numbers from path array in case of a list
let path = pathList.filter(function(value) {
return isNaN(value);
});
// Create a nested object with respect to the given path.
path.reduce(function(prev, currentValue,index) {
let isList = !(isNaN(pathList[index + 1]));
prev['fields'] = (prev['fields']) ? t.update(prev['fields'], {'$merge': {}}) : {};
prev['fields'][currentValue] = (prev['fields'][currentValue]) ?
t.update(prev['fields'][currentValue], {'$merge': {}})
: {};
if (index === path.length - 1) {
if (isList) {
return prev['fields'][currentValue]['item'] = (prev['fields'][currentValue]['item']) ?
t.update(prev['fields'][currentValue]['item'], {error : {'$set': message} })
: {'error' : message};
} else {
return prev['fields'][currentValue] = (prev['fields'][currentValue]) ?
t.update(prev['fields'][currentValue], {error : {'$set': message} })
: {'error' : message};
}
} else {
if (isList) {
return prev['fields'][currentValue]['item'] = (prev['fields'][currentValue]['item']) ?
t.update(prev['fields'][currentValue]['item'], {'$merge': {}})
: {};
} else {
return prev['fields'][currentValue]
}
}
}.bind(this), updatedOptions);
return updatedOptions;
}
@gcanti: Thank you. Just figured it out. Solved by passing the message in the above function as a Function instead of just a String.
Great. I'll leave this open for the moment as a reminder
I have a form with a list of bank accounts.
For now I managed to dynamically customize and set the error message in options. The problem is when displaying errors for the same field of different items in a list.
Let me explain on my form example, the IBAN's error message can be either invalid (returns 'Error msg x') or not accepted depending on country (returns 'Error msg y'). Say we have a list of two BankAccount items. The Iban is wrong in both items but for different reasons. Item[0] should display 'Error msg x' and Item[1] should display 'Error msg y'. Unfortunately this is not what happens they both display the same error msg.
Is there a way to set the options of each item separately :
If not Is there another way to implement this ?