-
Notifications
You must be signed in to change notification settings - Fork 138
Description
From my understanding the default behavior of JS-Data is to validate on set
. This is unusual and if possible it would be great to have some guidelines for how this should be applied (and the use cases for it - documentation on validation
(3.0) is also missing at the moment).
Toy schema for reference below. Suppose as below I have defined minLength
and maxLength
on firstName
now if the name is being sourced from user input directly something like person.firstName = 'j'
will raise an error - is this tangibly useful? Is there an example somewhere of JS-Data glueing to input elements / error message handling ?
The possible use case I see is that the input element code would have to specify a try .. catch
in it's setter (and display a form / input as invalid
until the property has been successfully set on the object), in which case the firstName
would not be written to the model until it could validate min/max length.
Also there is a particularity to how this set
behaviour will work when using nested fields (nice up-side to jsonschema though that they can be validated). So emergencyContact
below allows 'null' so that it may be unset, but then expects to be filled in with required fields. That means that an input which attempts to set a valid contactNumber
on the person
instance will fail - and the entire form needs to be synced at once.
The later might look like this --
try {
person.emergencyContact = this.emergencyContact
} catch(e) {
e.errors.forEach((error) => {
this.$[error.path.split('.')[1]] && (this.$[error.path.split('.')[1]].invalid = true);
})
return
}
but being explicit in each field set is probably less appealing than validate()
- since the later could work generically and not need to be duplicated:
save(person) {
try {
person.validate()
} catch(e) {
e.errors.forEach((error) => {
this.$[error.path.split('.')[1]] && (this.$[error.path.split('.')[1]].invalid = true);
})
return
}
person.save()
}
An interesting point @jmdobry brought up on slack was the use of a viewModel - in case of the later you could consider an interface that looks something like:
class Ephemeral {
constructor(obj) {
/* Wraps a JS-Data model and proxies it's properties */
}
sync() {
/* syncs proxied properties to underlying JS-Data model */
}
}
I am inclined to think that in such a case the validation behaviour would like highly similar to the try .. validate() catch {..} save()
though only it would be try .. sync() catch {..} save()
. Not sure over-all how many advantages there are to separating view and data-models, but I suppose a lot of the 'heaviness' (e.g. changeTracking per set
rather than per 'persist') comes from the JS-Data models being used on the back-end as well?
const EmergencyContactSchema = new Schema({
'properties': {
'contactName': {'type': 'string'},
'contactNumber': {'type': 'string'},
'relationship': {'type': 'string'}
},
'required': ['contactName','contactNumber','relationship'],
'type': ['object', 'null']
})
const PersonSchema = new Schema({
$schema: 'http://json-schema.org/draft-04/schema#',
title: 'Person',
description: 'Schema for a Person Record.',
type: 'object',
properties: {
firstName: { type: 'string', default: '', minLength: 2, maxLength: 32 },
lastName: { type: 'string', default: '' },
emergencyContact: EmergencyContactSchema,
}
})
const Person = store.defineMapper('person', {
endpoint: 'person/',
schema: PersonSchema
})