MeteorJS update AutoForm not loading doc - meteor

I have a template with an update Autoform:
<template name = "editLocationPage">
<div class="flow-text">
{{#if Template.subscriptionsReady}}
<div>
<br>
{{#autoForm collection="Locations" doc=currentDoc id="editLocationPage" type="update"}}
<fieldset>
{{testDoc}}
<legend>Edit Location / Asset</legend>
{{> afQuickField name='id'}}
{{> afQuickField name='text'}}
{{> afQuickField name='description' rows=3}}
{{> afQuickField name='type'}}
{{> afQuickField name='parent'}}
</fieldset>
<button type="submit" class="btn waves-effect waves-light">Submit</button>
<button type="reset" class="btn waves-effect waves-light red">Reset</button>
{{/autoForm}}
</div>
{{/if}}
</div>
</template>
Some helpers to subscribe and pass the doc to the template:
Template.editLocationPage.onCreated(function() {
var self = this;
self.autorun(function() {
self.subscribe('singleLocation', Session.get("idTreeView").toString());
});
});
Template.editLocationPage.helpers({
currentDoc: function() {
return Locations.find({"id":Session.get("idTreeView").toString()}).fetch()[0];
}
});
A schema:
// Data subset subscribed to on client
Meteor.publish('locations', function() {
return Locations.find({}, {fields: {
text: true,
id: true,
type:true,
parent:true
}});
});
Meteor.publish('singleLocation', function(locationId) {
return Locations.find({id:locationId});
});
The document is ok (findOne returns a valid doc) but the form does not work. Any ideas?

I just found the problem. I needed to add the schema:
{{#autoForm schema=locationFormSchema id="editLocationPage" type="update" collection=Locations doc=currentDoc}}
with a helper:
Template.editLocationPage.helpers({
currentDoc: function() {
return Locations.findOne({"id":Session.get("idTreeView").toString()});
},
locationFormSchema: function() {
return Schema.locations;
}
});

Related

Meteor template rendered twice

This Meteor client Template.payment.onRendered gets fired twice, which calls the server side method twice, How can I only get it to fire the backend method once if it must do the rendering twice, else how can it get it to render only once? thx
//client
Template.mainMenu.events({
'click .menuItem': (event) => {
let menuShortName = event.currentTarget.dataset.template;
Session.set('taskSelected', menuShortName);
Meteor.call('mainAction', menuShortName);
}
});
Template.index.helpers({
'taskInputs': function () {
let task = Session.get('taskSelected');
let tasks = task ? task.split(',') : [];
let data = DisplayCol.find({action: {$in: tasks}}, {sort: {createdAt: 1}}).fetch();
return {items: data};
}
});
//server
'mainAction': function (menuShortName) {
DisplayCol.remove({userId: this.userId});
lib.displayMakeUp({'action': menuShortName});
},
'displayMakeUp': (doc) => {
for (let i = 0; i < attrItems.length; i++) {
if (attrItems[i].action === doc.action || attrItems[i].action.indexOf(doc.action + '_') >= 0) {
let group = {};
group = attrItems[i];
group.userId = Meteor.userId();
console.log(JSON.stringify(group));
DisplayCol.insert(group);
}
}
},
Template.payment.onRendered(function () {
Meteor.call('getClientToken', function (error, clientToken) {
if (error) {
console.log(error);
} else {
braintree.setup(clientToken, "dropin", {
container: "payment-form", // Injecting into <div id="payment-form"></div>
onPaymentMethodReceived: function (response) {
var nonce = response.nonce;
console.log(nonce);
}
});
}
});
});
Templates:
<body>
{{> header}}
{{#if currentUser}}
{{#if isVerified}}
{{> index}} <-------------------------- (1)
{{else}}
<br><br><br><br>
<p>Check your email for your verification link!</p>
{{/if}}
{{else}}
{{> terms}}
{{/if}}
</body>
<template name="index">
<div id="main">
{{#if (display 'mainMenu')}}
{{> mainMenu}}
{{else}} {{#if (display 'content')}}
{{> Template.dynamic template="content" data=taskInputs}} <------------------- (2)
{{#if (session 'showFooter')}}
{{> footer}}
{{/if}}
{{/if}}{{/if}}
</div>
</template>
<template name="content">
{{> subMenu}}
<div id="main">
<div id="content">
<form>
<button type="submit" style="display:none"></button>
{{#if Template.subscriptionsReady}}
{{#each this.items}}
{{> sub}} <---------------------------------- (3)
{{/each}}
{{/if}}
</form>
</div>
</div>
</template>
<template name="sub">
{{#if isEqual element "payment"}}
{{> payment}} <--------------------------------------- (4)
{{/if}}
</template>
<template name="payment">
<form role="form">
<div class="row">
<div class="col-md-6 col-xs-12">
<br>
<div id="payment-form"></div>
<button type="submit" class="btn btn-success">Submit</button>
</div>
</div>
</form>
</template>
Based on what I see, it looks like you are rendering the template inside an each block. This will render one instance of your template for each each iteration.
{{#each this.items}}
{{> sub}} <---------------------------------- (3)
{{/each}}
Keep in mind that each template instance will fire all its callbacks (including onRendered). If you only want one instance then take it out of the each
Try using this package to limit rendering. You can also yield sub to payment (instead of checking the element) try checking if this is the first run inside your route.
//html
{{> yield sub}}
//router
if (Tracker.currentComputation.firstRun) {
this.render("payment",{to:"sub"});
}

meteoric autoform select-radio does not work

The field does not exists in the Schema of form,
i am using ionic template in form, this code does not work to me:
Template.example.helpers({
cardOptions: [
{ value: 0, label:'Visa' },
{ value: 1, label:'MasterCard' },
{ value: 2, label:'Elo' },
{ value: 3, label:'Hipercard' },
{ value: 4, label:'Hiper' }
]
});
<template name="example">
{{> afQuickField name="payment.card" type="select-radio" options=cardOptions }}
</template>
You need to use the autoForm component if you want to define fields individually.
For example:
<template name="example">
{{#autoForm collection="Payments" id="insertPaymentForm" type="insert"}}
<fieldset>
<legend>Add a Payment Method</legend>
{{> afQuickField name='title'}}
{{> afQuickField name="card" type="select-radio" options=cardOptions }}
</fieldset>
<button type="submit" class="btn btn-primary">Insert</button>
{{/autoForm}}
</template>
Here is a MeteorPad.

Meteor Flow Router issue with update

I have a Meteor app and I'm transitioning from IronRouter to FlowRouter. So far so good, but there are aspects I don't understand yet.
I have a route as follows:
FlowRouter.route('/documents/:docId/edit', {
name: 'documentEdit',
subscriptions: function (params, queryParams) {
this.register('documentEdit', Meteor.subscribe('documentSingle', params.docId));
},
action: function (params, queryParams) {
BlazeLayout.render('layout', { top: 'header', main: 'documentEdit' });
},
});
First option:
Then I also have a template:
<template name="documentEdit">
<div class="container">
<h1>Edit document</h1>
{{#if isReady 'documentEdit'}}
{{#autoForm collection="Documents" doc=this id="documentForm" type="update" meteormethod="documentUpdateMethod"}}
<fieldset>
{{> afQuickField name='title'}}
{{> afQuickField name='content' rows=6}}
</fieldset>
<button type="submit" class="btn btn-primary">Update</button>
<a class="btn btn-link" role="button" href="{{pathFor 'documentsList'}}">Back</a>
{{/autoForm}}
{{/if}}
</div>
</template>
with a template helper as follows:
Template.documentEdit.helpers({
isReady: function(sub) {
if(sub) {
return FlowRouter.subsReady(sub);
} else {
return FlowRouter.subsReady();
}
}
});
This is as it is mentioned here, but I'm not getting the values pre-filled in the textboxes on the UI (which is normal when editing fields).
Second option:
When I do the following it works and I don't really understand why it works (found it browsing in different forums and tried it out):
<template name="documentEdit">
<div class="container">
<h1>Edit document</h1>
{{#with getDocument }}
{{#autoForm collection="Documents" doc=this id="documentForm" type="update" meteormethod="documentUpdateMethod"}}
<fieldset>
{{> afQuickField name='title'}}
{{> afQuickField name='content' rows=6}}
</fieldset>
<button type="submit" class="btn btn-primary">Update</button>
<a class="btn btn-link" role="button" href="{{pathFor 'documentsList'}}">Back</a>
{{/autoForm}}
{{/with}}
</div>
</template>
and the helper:
Template.documentEdit.helpers({
getDocument: function () {
return Documents.findOne();
}
});
So the questions are:
for the 1st option: any idea why it does not work. I would prefer that one as it's the documented way of doing things
for the 2nd option: not sure why I need (in the template helper) to do a Document.findOne() without even having to pass the id of the doc I want to edit:
You want to do template level subscriptions with Flow Router, that's one of the primary pattern changes.
So you'd do:
Setup the subscription at the template level. Autorun so it'll resubscribe on route changes.
Template.documentEdit.onCreated(function() {
var self = this;
this.autorun(function() {
var docId = FlowRouter.getParam('docId');
self.subscribe('documentSingle', docId));
};
};
Setup the template helper to pick up from the route, and grab the id and populate the helper/document.
Template.documentEdit.helpers({
getDocument: function () {
var docId = FlowRouter.getParam('docId');
var doc = Documents.findOne(docId) || {};
return doc;
}
});
Do a template level load check and if it's there render it, otherwise show loading...
<template name="documentEdit">
<div class="container">
<h1>Edit document</h1>
{{#if Template.subscriptionReady}}
{{#with getDocument }}
{{#autoForm collection="Documents" doc=this id="documentForm" type="update" meteormethod="documentUpdateMethod"}}
<fieldset>
{{> afQuickField name='title'}}
{{> afQuickField name='content' rows=6}}
</fieldset>
<button type="submit" class="btn btn-primary">Update</button>
<a class="btn btn-link" role="button" href="{{pathFor 'documentsList'}}">Back</a>
{{/autoForm}}
{{/with}}
{{else}}
Loading...
{{/if}}
</div>
</template>
Not knowing how this worked, seeing that all the tutorials I have read that deals with update used Iron router, I have spent 7 days worth of trying retrying, looking through others code, reading tutorials. Happy it now works.

meteor autoform _id field

I have this Schema :
Schemas = {};
Schemas.Id = {
type: String,
optional: true,
label: 'Id',
//max: ID_LENGTH,
defaultValue : Random.id(ID_LENGTH),
denyUpdate: true
};
Schemas.Name = {
type: String,
label: 'Name',
max: 75
};
Schemas.Description = {
type: String,
label: 'Description',
max: 500,
optional: true,
autoform: {
rows: 5
}
};
//-- Attribute
Schemas.Attribute = {
_id: Schemas.Id,
attribute_name : Schemas.Name,
attribute_description : Schemas.Description
};
Collections.Attributes.attachSchema(Schemas.Attribute);
i have a request from client they can insert _id manually.
but autoform cant permit it,
this the template :
<template name="pg_attr_insert">
{{#autoForm _id="afInsertDemo" type="insert" collection=Collections.Attributes}}
<div class="form-group {{#if afFieldIsInvalid name='_id'}}has-error{{/if}}">
<label class="control-label">{{afFieldLabelText name='_id'}}</label>
{{> afFieldInput name='_id'}}
{{#if afFieldIsInvalid name='_id'}}
<span class="help-block">{{{afFieldMessattribute_description name='_id'}}}</span>
{{/if}}
</div>
<div class="form-group {{#if afFieldIsInvalid name='attribute_name'}}has-error{{/if}}">
<label class="control-label">{{afFieldLabelText name='attribute_name'}}</label>
{{> afFieldInput name='attribute_name'}}
{{#if afFieldIsInvalid name='attribute_name'}}
<span class="help-block">{{{afFieldMessattribute_description name='attribute_name'}}}</span>
{{/if}}
</div>
<div class="form-group {{#if afFieldIsInvalid name='attribute_description'}}has-error{{/if}}">
<label class="control-label">{{afFieldLabelText name='attribute_description'}}</label>
{{> afFieldInput name='attribute_description'}}
{{#if afFieldIsInvalid name='attribute_description'}}
<span class="help-block">{{{afFieldMessattribute_description name='attribute_description'}}}</span>
{{/if}}
</div>
<div class="form-group">
<button type="submit" class="btn btn-primary">Add Person</button>
<button type="reset" class="btn btn-default">Reset Form</button>
</div>
{{/autoForm}}
</template>
this is the error :
Uncaught Error: Every autoForm and quickForm must have an "id"
attribute set to a unique string.
is there anyway autoform permit insert _id manually? and how?
Oke i know my false, i create
{{#autoForm _id="afInsertDemo" type="insert" collection=Collections.Attributes}}
i just convert it to :
{{#autoForm id="afInsertDemo" type="insert" collection=Collections.Attributes}}

Autoform: How to dynamically show and add fields of a sub-schema depending on another field?

What is the best approach to dynamically show fields of a sub-schema (Object) depending on another field?
In the following example a document (Schemas.Main) can include several items defined in Schemas.Items. The fields that are needed to fill in items are dependendent on the selected type.
For example if a user selects type=="type1", fields "type1_field1" and "type1_field2" need to be filled.
A solution probably needs to use autoForm and combine AutoForm.getFieldValue and setting fields of an afArrayField, correct? I have tried a lot of combinations but either the ability to add additional items is lost (missing plus-sign) or I cannot add different items (i.e. all items are type1). Any hints how to solve this?
//Schemas.js
Schemas = {}; Collections = {};
Main = Collections.Main = new Mongo.Collection("Main");
Main.attachSchema(Schemas.Main);
Meteor.isClient && Template.registerHelper("Schemas", Schemas);
Schemas.Main = new SimpleSchema({
name: {
type: String
},
items: {
type: [Schemas.Items]
}
});
Schemas.Items = new SimpleSchema({
type: { //FormActions...
type: String,
allowedValues: ['type1', 'type2', 'type3'],
autoform: {
type: "selectize"
}
},
type1_field1: {
type: String
},
type1_field2: {
type: String
},
type2_field1: {
type: String
},
type2_field2: {
type: String
},
type3_field1: {
type: String
},
type3_field2: {
type: String
}
});
//form.html
{{#autoForm id="testForm" type="insert" collection=Collections.Main}}
{{> afFieldInput name='name'}}
{{> afArrayField name='items' fields="items.$.type, items.$.type1_field1"}} //How to set these fields dynamically depending on type?
<div class="form-group">
<button type="submit" class="btn btn-primary">Create</button>
</div>
{{/autoForm}}
I finally used another approach and created an own template based on afArrayField, which uses
{{> UI.dynamic template=currentFieldValue}}
Not sure if this is the best approach but seems to be working for my situation:
Template.registerHelper("currentFieldValue", function() {
return AutoForm.getFieldValue("testForm", this.current.type) || "no type so far";
});
{{#autoForm id="testForm" type="insert" collection=Collections.Main}}
{{> afFieldInput name='name'}}
{{> afArrayField name='items' id="something" template="mycustom"}}
<div class="form-group">
<button type="submit" class="btn btn-primary">Create</button>
</div>
{{/autoForm}}
<template name="afArrayField_mycustom">
<div class="panel panel-default">
<div class="panel-heading">{{afFieldLabelText name=this.atts.name}}</div>
{{#if afFieldIsInvalid name=this.atts.name}}
<div class="panel-body has-error">
<span class="help-block">{{{afFieldMessage name=this.atts.name}}}</span>
</div>
{{/if}}
<ul class="list-group">
{{#afEachArrayItem name=this.atts.name minCount=this.atts.minCount maxCount=this.atts.maxCount}}
<li class="list-group-item autoform-array-item">
<div>
<div class="autoform-remove-item-wrap">
{{#if afArrayFieldHasMoreThanMinimum name=../atts.name minCount=../atts.minCount maxCount=../atts.maxCount}}
<button type="button" class="btn btn-primary autoform-remove-item"><span class="glyphicon glyphicon-minus"></span>
</button>
{{/if}}
</div>
<div class="autoform-array-item-body">
<!--all actions have a type -->
{{> afFieldInput name=this.current.type label=false options="auto"}}
<!--branch here for other fields that depend on type -->
{{> UI.dynamic template=currentFieldValue}}
</div>
</div>
</li>
{{/afEachArrayItem}}
{{#if afArrayFieldHasLessThanMaximum name=this.atts.name minCount=this.atts.minCount maxCount=this.atts.maxCount}}
<li class="list-group-item">
<button type="button" class="btn btn-primary autoform-add-item" data-autoform-field="{{this.atts.name}}" data-autoform-minCount="{{this.atts.minCount}}" data-autoform-maxCount="{{this.atts.maxCount}}"><span class="glyphicon glyphicon-plus"></span>
</button>
</li>
{{/if}}
</ul>
</div>
</template>
<template name="type1">
<!--include type1 fields here-->
</template>
<template name="type2">
<!--include type2 fields here-->
</template>
<template name="type3">
<!--include type3 fields here-->
</template>
Based on Miriam's answer, I would also like to share what i did to get things working. May be it could benifit.
Schemas -> actions
Schemas.actions = new SimpleSchema({
actions : {
type : Array,
optional: false,
minCount: 1,
autoform: {
name: "actions"
}
},
"actions.$" : {
type: Object
},
"actions.$.action_type": {
type : String,
optional: false,
label : "Action Type",
autoform: {
type : "select",
class : "action_type form-control",
name : "action_type",
label : "Select type of action",
options: function()
{
let returnValue = [
{label: "Action 1", value: "action_1"},
{label: "Action 2", value: "action_2"},
];
return returnValue;
}
}
},
"actions.$.action_1" : {
type : Schemas.action1,
minCount: 1,
optional: true,
label : "Action 1",
}
});
Schemas -> action1
Schemas.action1 = new SimpleSchema({
"action1_to" : {
type : String,
optional: false,
label : "Email To",
autoform: {
type : "text",
label : "Email To",
placeholder: "Comma seperated values...",
class : "form-control"
}
},
"action1_cc" : {
type : String,
optional: true,
label : "Email Cc",
autoform: {
type : "text",
label : "Email Cc",
placeholder: "Comma seperated values...",
class : "form-control"
}
},
"action1_subject": {
type : String,
optional: false,
label : "Subject",
autoform: {
type : "text",
label : "Subject",
placeholder: "Subject for the Email",
class : "form-control"
}
},
"action1_body" : {
type : String,
optional: false,
label : "Email Content",
autoform: {
label : "Email Content",
rows : 6,
class : "form-control auto-size",
placeholder: "Email Content goes here...",
style : "font-size: 120%;"
}
}
});
Please note that Schemas.action1 should be loaded before Schemas.actions else you would only have a textbox rendered instead of the form
Templates
<template name="actions">
{{#autoForm id="actions-form" doc=step.data schema=schema}}
{{> afArrayField name="actions" template="actions" step=step}}
{{> wizardButtons}}
{{/autoForm}}
</template>
<template name="afArrayField_actions">
<div class="panel panel-default">
<div class="panel-heading">{{afFieldLabelText name=this.atts.name}}</div>
{{#if afFieldIsInvalid name=this.atts.name}}
<div class="panel-body has-error">
<span class="help-block">{{{afFieldMessage name=this.atts.name}}}</span>
</div>
{{/if}}
<ul class="list-group">
{{#afEachArrayItem name=this.atts.name minCount=this.atts.minCount maxCount=this.atts.maxCount}}
<li class="list-group-item autoform-array-item">
<div>
<div class="autoform-remove-item-wrap">
{{#if afArrayFieldHasMoreThanMinimum name=../atts.name minCount=../atts.minCount maxCount=../atts.maxCount}}
<button type="button" class="btn btn-primary autoform-remove-item"><span
class="glyphicon glyphicon-minus"></span></button>
{{/if}}
</div>
<div class="autoform-array-item-body">
{{> afQuickField name=this.current.action_type label=false options=actionOptions}}
{{> UI.dynamic template=currentFieldValue data=this }}
</div>
</div>
</li>
{{/afEachArrayItem}}
{{#if afArrayFieldHasLessThanMaximum name=this.atts.name minCount=this.atts.minCount maxCount=this.atts.maxCount}}
<li class="list-group-item">
<button type="button" class="btn btn-primary autoform-add-item"
data-autoform-field="{{this.atts.name}}" data-autoform-minCount="{{this.atts.minCount}}"
data-autoform-maxCount="{{this.atts.maxCount}}"><span
class="glyphicon glyphicon-plus"></span></button>
</li>
{{/if}}
</ul>
</div>
</template>
<template name="action_1">
{{> afQuickField name=this.current.action_1 }}
</template>
You would see {{> wizardButtons}} in the template which is because I am using the form-wizard package
and a Template.registerHelper for currentFieldValue helper
Template.registerHelper("currentFieldValue", function()
{
let val = AutoForm.getFieldValue(this.current.action_type, "actions-form");
return val || null;
});
Hope this helps some-one and saves time.
Thanks Mariam for this solution

Resources