Closed Gaelan closed 9 years ago
Can you post an example of how you're doing it now and how you would like it to work?
Let's use the classic employee management example:
A company can have one or more employees. In Mongo, we would express that with embedding.
EmployeeSchema = {
[...]
}
Companies = new Meteor.Collection();
Companies.attachSchema({
[...]
employees: { type: [EmployeeSchema] }
})
If you create a quickForm for a Company, it will automatically give you a fieldset for adding employees. However, it would be nicer to have separate add/update/remove forms for employees. Right now, I'm just using an onSubmit-based form to add employees. However, it would be awesome if we could do something like this:
{{> quickForm collection='Companies:employees' [...]}}
to get a form for a sub-schema.
OK, thanks for the good example. I'll have to think about how this could be done. I guess that on submission a type="insert"
form would actually be treated as a $push
update and an update form would $set
the values at the proper index (which would have to be provided to autoform somehow).
In my use case, we would also need to use the index for double-nesting.
Right. The simplest implementation, to support any level of arrays and objects nesting, would be something like
{{> quickForm collection='Companies' scope='employees.1' type="update"}}
for an update form
or
{{> quickForm collection='Companies' scope='employees.$' type="insert"}}
for an insert form
Would this work if employees wasn't an array, but an optional sub object employee that's possible to add/update/remove?
know this example doesn't make sense for this scenario but I'm thinking there's scenarios where you might want to add an optional object of some sub-schema. and want to use a separate form/modal form/show-hide form, but maybe this is possible with some html/css show/hide solution.
Yes, that's why I suggested a generic scope
attribute, which could be set to either an array field or an object field. A good example might be if you have an optional address
object with address.street
, address.city
, etc. and then you want to provide an update form that just includes the address fields and sets them into the correct object.
I think an insert
form would apply only when scoping to an array property, but an update
form could make sense for either an array or an object (scope="addresses.0"
or scope="address"
). Seems simple, but could get tricky in the implementation.
Note that this can use the new ss.pick() to get the subschema for the scope automatically, and then it's just a matter of adjusting the update modifier's set/push properly.
Am I getting this right: it is this issue that prevents me from partial update of a document?
Here's my example:
I have schema like that (just two simple string fields that are both optional):
mySchema = new SimpleSchema({optgroup: {type: Object, optional: true}, "optgroup.field1": {type: String, optional: true}, "optgroup.field2": {type: String, optional: true}});
And I have two separate forms (actually even on different routes):
{{#autoForm type="update" doc=mydoc}}
{{>afQuickField name="optgroup.field1"}}
{{/autoForm}}
{{#autoForm type="update" doc=mydoc}}
{{>afQuickField name="optgroup.field1"}}
{{/autoForm}}
Now what happens I when I submit one of the forms (let's say with optgroup.field2) with empty value, it overwrites whole document, even if it had optgroup.field1 set.
Can I work around that? It is related to this issue?
@neoromantic, no what you're doing should work fine. You have to update entire arrays at once, but updating only some props of an object should work just fine. You should create a simple app that reproduces the issue and create a new issue with a link to your reproduction app.
@aldeed I did just that: http://autoformbug.meteor.com
Steps to recreate:
Thanks!
Created separate issue: https://github.com/aldeed/meteor-autoform/issues/487
Do I understand it right, that partial updates do not work when using arrays? For example adapting the example from above:
mySchema = new SimpleSchema({optgroup: {type: [Object], optional: true}, "optgroup.$.field1": {type: String, optional: true}, "optgroup.$.field2": {type: String, optional: true}});
and
{{#autoForm type="update" doc=mydoc}}
{{>afQuickField name="optgroup.3.field1"}}
{{/autoForm}}
Doing this would delete all other fields of the array and only set "optgroup.3.field1", right? Is there a way to do this? (without having to get the entire array, changing one field and saving the entire array)
@nate331, did you try it? I don't recall if your example works or not.
The underlying mongo issue, for those that care, is that if you do {$set: { 'optgroup.3.field1': 'foo' }}
and optgroup
does NOT yet exist as an array, mongo actually creates it as an object instead of an array, like {optgroup: {3: {field1: "foo"}}}
. So because of this, autoform always tries to generate modifiers that work around this issue, essentially by updating the full array when possible.
You could also write a before
hook and/or your own submission logic to make it work as you need.
I tried it (with version 2.0.2, my project is still on this one). However it behaved as described earlier: It will override everything else from the array. So I tried this way:
before: {
update: function(docId, modifier, template) {
var newModifier = {
$set: {"optgroup.3.field1": "myNewValue"}
};
return newModifier;
}
}
This is working. It only updates the expected field. However validation does not work properly anymore then. If type of field1 is Number and the user enters a String, then the onSuccess hook gets called, however nothing gets saved into the database.
@nate331 @aldeed Is there anything new on how to update object which is part of array? All Arrays are overwritten :( I need to update just one.
As a workaround I am doing it the way I described in my previous post. Number validation is not working properly, so I changed the type of the Field to String. It is not ideal, but good enough.
This is the way I do it:
before: {
update: function(docId, modifier, template) {
var fieldIdentifier = Meteor.userId();
var path = getSubObjPath(template.data.doc, "optgroupA.$.optgroupB.$.identifierField", fieldIdentifier) + ".field1";
var mod = {};
mod[path] = AutoForm.getFieldValue("myFormId", path);
var newModifier = {
$set: mod
};
return newModifier;
}
},
/*
* eg. getSubObj(obj, "recommendedTeams.$.members.$.userId", "a32jrlwo3jk2j2")
* -> return something like: "recommendedTeams.2.members.0"
*/
//prevPath is optional parameter (needed for recursion)
getSubObjPath = function(obj, path, value, prevPath) {
if (prevPath === undefined)
prevPath = "";
if (!obj)
return undefined;
var parts = path.split(".");
if (parts.length === 1) {
if (obj[path] === value) {
return prevPath;
} else {
return undefined;
}
}
if (parts[0] === "$") {
for (var i = 0; i < obj.length; i++) {
var prevPathTmp = prevPath === "" ? "" : prevPath + ".";
var resultPath = getSubObjPath(obj[i], trimPath(path), value, prevPathTmp + i);
if (resultPath) {
return resultPath;
}
}
} else {
var prevPathTmp = prevPath === "" ? "" : prevPath + ".";
var resultPath = getSubObjPath(obj[parts[0]], trimPath(path), value, prevPathTmp + parts[0]);
if (resultPath) {
return resultPath;
}
}
};
Done in AutoForm 5.0, now released. See new form type update-pushArray plus other fixes related to this.
Is there a way to create a separate form for an embedded schema (as if it was its own document)? I'm doing it right now with onSumbit–is there a better way?