Reputation: 31
I'm wanting to disable an option if it has already been selected in one of the object groups.
So, if I selected "2013" then added another sample, "2013" would not be available in that group, unless that option is changed in the original group.
Is there an easy way to do this that I'm missing? Do I need to reactively update the schema when a selection is made?
samples:{
type: Array,
optional: true,
maxCount: 5
},
"samples.$":{
type: Object,
optional: true
},
"samples.$.sample":{
type:[String],
autoform: {
type: "select",
options: function () {
return [
{
optgroup: "Group",
options: [
{label: "2013", value: 2013},
{label: "2014", value: 2014},
{label: "2015", value: 2015}
]
}
];
}
}
},
Upvotes: 3
Views: 784
Reputation: 8423
I know this post is about 3 years old. However, I came across the same issue and want to provide an answer for all those who also stumbled over this post.
This answer is only a proof of concept and does not provide a full generic and performant solution, that could be used on production apps.
A fully generic solution would require a deep change in the code of how select field options are generated and updated in AutoForm
.
I am using Autoform >=6 which provides a good API to instantly obtain field and form values in your SimpleSchema without greater trouble. SimpleSchema
is included as npm package and Tracker
has to be passed to it in order to ensure Meteor reactivity.
Functions like AutoForm.getFieldValue are reactive, which is a real great improvement. However, reactively changing the select options based on a reactive value causes a lot of update cycles and slows the performance (as we will see later).
Using AutoForm.getFormValues is not working, when using it within options of an Object field. While working within Array field, it will not behave reactively in Object fields, thus not update the filtering on them.
You can't use it with array types of fields. It's because if you change the select options, it applies for all your select instances in the array. It will therefore also apply to your already selected values and strips them away, too. This makes your select looks like it is always "not selected"
You can test that yourself with the following example code:
new SimpleSchema({
samples:{
type: Array,
optional: true,
maxCount: 5
},
"samples.$":{
type: String,
autoform: {
type: "select",
options: function () {
const values = AutoForm.getFormValues('sampleSchemaForm') || {};
const samples = values && values.insertDoc && values.insertDoc.samples
? values.insertDoc.samples
: [];
const mappedSamples = samples.map(x => x.sample);
const filteredOpts = [
{label: "2013", value: "2013"},
{label: "2014", value: "2014"},
{label: "2015", value: "2015"}
].filter(y => mappedSamples.indexOf(y.value) === -1);
return [
{
optgroup: "Group",
options:filteredOpts,
}
];
}
}
},
}, {tracker: Tracker});
when taking a closer look at the schema, I saw the maxCount
property. This made me think, that if you anyway have a list of max options, you could solve this by having fixed properties on a samples
object (by the way: maxCount: 5
makes no sense, when there are only three select options).
This causes each select to have it's own update, that does not interfere the others. It requires an external function, that keeps track of all selected values but that came out be very easy.
Consider the following code:
export const SampleSchema = new SimpleSchema({
samples:{
type: Object,
optional: true,
},
"samples.a":{
type: String,
optional:true,
autoform: {
type: "select",
options: function () {
const samples = AutoForm.getFieldValue("samples");
return getOptions(samples, 'a');
}
}
},
"samples.b":{
type: String,
optional:true,
autoform: {
type: "select",
options: function () {
const samples = AutoForm.getFieldValue("samples");
return getOptions(samples, 'b');
}
}
},
"samples.c":{
type: String,
optional:true,
autoform: {
type: "select",
options: function () {
const samples = AutoForm.getFieldValue("samples");
return getOptions(samples, 'c');
}
}
},
}, {tracker: Tracker});
The code above has three sample entries (a, b and c) which will let their options be computed by an external function.
This function needs to fulfill certain requirements:
samples
selectThe code for this function is the following:
function getOptions(samples={}, prop) {
// get keys of selections to
// determine, for which one
// we will filter options
const sampleKeys = Object.keys(samples);
// get sample values to
// determine which values
// to filter here
const sampleValues = Object.values(samples);
const filteredOptiond = [
// note that values are stored as strings anyway
// so instead of parsing let's make them strings
{label: "2013", value: "2013"},
{label: "2014", value: "2014"},
{label: "2015", value: "2015"}
].filter(option => {
// case 1: nothing is selected yet
if (sampleKeys.length === 0) return true;
// case2: this selection has a
// selected option and current option
// is the selected -> keep this option
if (sampleKeys.indexOf(prop) > -1 && option.value === samples[prop])
return true;
// case 3: this selection has no value
// but others may have selected this option
return sampleValues.indexOf(option.value) === -1;
});
return [
{
optgroup: "Group",
options: filteredOptiond,
}
]
};
Good:
-it works
-you can basically extend and scale it to your desired complexity (optgroups, more fields on samples
, checking against other fields with other fields etc.)
Bad: - performance - bound to a given (or the nearest) form context (see here) - much more code to write, than for an array.
Upvotes: 0