Select one or multiple programatically, I can do this for a select field but if I use then <div class="ui fluid multiple search selection dropdown"> then I can't set them, this is important since I want to format the items.
According to documentation you can set (selected([value1, value2]) Adds a group of values as selected)
http://semantic-ui.com/modules/dropdown.html#multiple-search-selection
If your template is
<div class="ui fluid search selection dropdown multiple countries">
<input type="hidden" name="country">
<i class="dropdown icon"></i>
<div class="default text">Select Country</div>
<div class="menu">
<div class="item" each={ c, co in countries } data-value={c.abbr}>
<i class={c.abbr + ' flag'}></i>
{c.name}
</div>
</div>
</div>
And you've got many items you must set the data-value passing an array, suppose we have the array
countries = [{name: 'Afganistan', abbr: 'af'},
{name: 'Bolivia', abbr: 'bo'},
{name: 'Brazil', abbr: 'br'},
{name: 'Canada', abbr: 'ca'},
{name: 'Colombia', abbr: 'co'},
{name: 'Ecuador', abbr: 'ec'},
{name: 'Peru', abbr: 'pe'},
{name: 'USA', abbr: 'us'}
]
// to select
$('.ui.fluid.multiple').dropdown('set selected', ['bo', 'ec'])
Related
I need to create a dynamic form with multiple nested items. I've found this example
but i'm not sure it's doing deep recursive since once i've tried to add more levels of nested items - the ui brakes down.
Here is the default json structure with my attempts :
{
key: "common",
title: "main fields",
group: [
{
key: "createdAt",
title: "Create Date",
type: "date"
},
// group:[{
// key: "foo",
// title: "Foo",
// type: "select",
// },
// {
// key: "goo",
// title: "Goo",
// type: "input",
// },
// ]
]
},
So as you can see under "common" - i've added 2 more levels of groups - the first group works fine - but the nested group with key "foo" and "goo" it's working.
I'm pretty sure the problem is in the template / markup
<form [formGroup]="filterForm" class="filter-form">
<ng-template #recursiveList let-filterFields let-fromGroup="fromGroup">
<ng-container *ngFor="let item of filterFields">
<ng-container *ngIf="item.group; else default;">
// in this area i'm not sure it's iterate over deeper nesting...
<p>{{item.key}} </p>
<div [formGroupName]="item.key">
<ng-container *ngTemplateOutlet="recursiveList; context:{ $implicit:
item.group, fromGroup: {name: item.key}, isChild:true }"></ng-container>
</div>
</ng-container>
<ng-template #default>
<div class="form-group" [formGroupName]="fromGroup.name">
<input [type]="item.type" [formControlName]="item.key"
[placeholder]="item.title" [name]="item.key" />
</div>
</ng-template>
</ng-container>
</ng-template>
<ng-container *ngTemplateOutlet="recursiveList; context:{ $implicit: filterFields
}">.
From my understanding, there are two issues in the example you provided:
The data structure.
The template.
Data Structure
These are the interfaces I understand from your example:
interface Base {
key: string;
title: string;
}
interface Field extends Base {
type: 'select' | 'input' | 'date' | ...
}
interface Group extends Base {
group: Array<Field | Group>
}
So the JSON example you provided should look something like this:
{
"key": "common",
"title": "main fields",
"group": [
{
"key": "createdAt",
"title": "Create Date",
"type": "date"
},
{
"key": "test",
"title": "Test"
"group": [
{
"key": "foo",
"title": "Foo",
"type": "select"
},
{
"key": "goo",
"title": "Goo",
"type": "input"
}
]
}
]
}
Template
Let's look at a very simplified version of the form:
<form [formGroup]="filterForm">
<ng-container formGroupName="common">
<ng-container *ngTemplateOutlet="control;
context:{ controlName: 'foo', group: 'test' }">
</ng-container>
</ng-container>
<ng-template #control let-group="group" let-controlName="controlName">
<div class="col-md-3">
<div class="form-group" [formGroupName]="group">
<input type="input" [formControlName]="controlName" />
</div>
</div>
</ng-template>
</form>
The code won't work, why? Think about the ng-template as a function. If you want it to know about the formGroupName="common" it needs to be declared within that scope. What matters is the declaration context and not the invocation context, just like regular functions.
This is the working version of the above example:
<form [formGroup]="filterForm">
<ng-container formGroupName="common">
<ng-container *ngTemplateOutlet="control;
context:{ controlName: 'foo', group: 'test' }">
</ng-container>
<ng-template #control let-group="group" let-controlName="controlName">
<div class="col-md-3">
<div class="form-group" [formGroupName]="group">
<input type="input" [formControlName]="controlName" />
</div>
</div>
</ng-template>
</ng-container>
</form>
Things get trickier when you have nested and you need to use recursion.
That's why I think that the approach of using the formGroupName and formControlName directives in this scenario makes things more complicated than they are.
I suggest passing the form control directly into the input by providing the right path to it.
Here is a working example of the idea based on your original example.
I am using Angular Material library multiselect. I added an element to this selection, when clicking on which all the elements of the list will be selected, but the problem is that in this case all these elements, together with All, fall into the select field and it looks like this "Value 1, Value 2, Value 3 , Value 4, All ". How can I make it so that in this field when selecting all elements, only the value "All" remains without the rest of the selected elements?
export class AppComponent {
searchUserForm: FormGroup;
userTypeFilters = [
{
key: 1, value: 'Value 1',
},
{
key: 2, value: 'Value 2',
},
{
key: 3, value: 'Value 3',
},
{
key: 4, value: 'Value 4',
}
];
#ViewChild('allSelected') private allSelected: MatOption;
constructor(private fb: FormBuilder){}
ngOnInit() {
this.searchUserForm = this.fb.group({
userType: new FormControl('')
});
}
toggleAllSelection() {
if (this.allSelected.selected) {
this.searchUserForm.controls.userType
.patchValue([...this.userTypeFilters.map(item => item.key), 0]);
} else {
this.searchUserForm.controls.userType.patchValue([]);
}
}
}
<form [formGroup]="searchUserForm" fxFlex fxLayout="column" autocomplete="off" style="margin: 30px">
<mat-select placeholder="User Type" formControlName="userType" multiple>
<mat-option *ngFor="let filters of userTypeFilters" [value]="filters.key">
{{filters.value}}
</mat-option>
<mat-option #allSelected (click)="toggleAllSelection()" [value]="0">All</mat-option>
</mat-select>
</form>
Demo is here
Here is the solution
<form [formGroup]="searchUserForm" fxFlex fxLayout="column" autocomplete="off" style="margin: 30px">
<mat-select placeholder="User Type" formControlName="userType" multiple>
<mat-select-trigger *ngIf="allSelected.selected">
<span>
ALL
</span>
</mat-select-trigger>
<mat-option *ngFor="let filters of userTypeFilters" [value]="filters.key">
{{filters.value}}
</mat-option>
<mat-option #allSelected (click)="toggleAllSelection()" [value]="0">All</mat-option>
</mat-select>
</form>
How do I get a <select> drop-down menu in the product varientForm?
Something like what we see here:
To accomplish adding a <select> to variantForm as seen above we need to edit or extend three files, variantForm.html, variantForm.js and the products.js schema:
reaction/imports/plugins/included/product-variant/client/templates/products/productDetail/variants/variantForm/variantForm.html
reaction/imports/plugins/included/product-variant/client/templates/products/productDetail/variants/variantForm/variantForm.js
reaction/lib/collections/schemas/products.js
In the AutoForm live example of <select> we see a schema that looks like this:
{
typeTest: {
type: String,
optional: true,
autoform: {
type: "select",
options: function () {
return [
{label: "2013", value: 2013},
{label: "2014", value: 2014},
{label: "2015", value: 2015}
];
}
}
}
}
and a Blaze template HTML that looks like this:
{{#autoForm id="types2" schema=typesSchema2}}
{{> afFormGroup name="typeTest" type="select" options=optionsHelper}}
<div class="form-group">
<button type="submit" class="btn btn-primary">Submit</button>
</div>
{{/autoForm}}
Step 1
Edit/extend products.js schema file adding your select except we only need to add these parts:
typeTest: {
type: String,
optional: true,
autoform: {
type: "select"
}
},
Reaction Commerce ignores the optionHelper function from AutoForm as we see in the above example. I keep the autoform: { type: "select" } just to express intension. For a real-world example of a product.js schema modified this way see here.
Step 2
Add your helper function to variantForm.js that returns your selection's options object. Inside Template.variantForm.helpers({}) add:
variantTypeOptions: function (){
return [
{label: "Default", value: 2013},
{label: "Height & Weight", value: "Height & Weight"}
];
},
Nice and simple (and similar to the AutoForm example), these become the selection options we see in the screenshot above. Real-world example here.
Step 3
Final step. Let's lastly add the Blaze template HTML to variantForm.html:
<div class="form-group{{#if afFieldIsInvalid name='variantType'}} has-error{{/if}}">
<label class="control-label">{{afFieldLabelText name='variantType'}}</label>
{{>afFieldInput name='variantType' type="select" options=variantTypeOptions}}
{{#if afFieldIsInvalid name='variantType'}}
<span class="help-block">{{afFieldMessage name='variantType'}}</span>
{{/if}}
</div>
With our focus on:
{{>afFieldInput name='variantType' type="select" options=variantTypeOptions}}
Real-world example here.
Closing Remarks
You may need to do a rc reset for the changes to the schema to take effect, but WARNING, this will wipe your local development database. See the note in the RC Docs about having to do frequent resets in the "Creating a Plugin" article.
I am trying to display my Orders collection. The Orders collection schema has a select field populated from the Items collection.
I cannot seem to get the Orders collection to display on my admin's template. I have verified that I am posting to the collection with Mongol and I'm not receiving any errors in console. I have also tried displaying it in a tabular table with no luck.
Any ideas? I'm still learning meteor and have been staring at this screen for hours.. maybe need some fresh air now and a fresh look later...
/collections/orders.js
Orders = new Mongo.Collection("orders");
Orders.attachSchema(new SimpleSchema({
station: {
type: String,
label: 'Station',
max: 2,
},
itemselect: {
type: [String],
label: 'Items',
optional: false,
autoform:{
type: "select",
options : function() {
return Items.find().map(function (c) {
return {label: c.name , value: c._id}
})
}
}
}
}));
/templates/admin.html
<template name="ordersTable">
<div class="admin">
<div class="panel panel-default">
<div class="panel-heading">
<h4 class="panel-title">
<a data-toggle="collapse" href="#collapse2">
<button type="button" class="btn btn-default navbar-btn">Orders</button>
</a>
</h4>
</div>
<div id="collapse2" class="panel-collapse collapse">
<div class="panel-body">
<ul>
{{#each orders}}
<li>{{> station}}</li>
{{/each}}
</ul>
</div>
<div class="panel-footer">
{{> addOrderFormAdmin}}
</div>
</div>
</div>
</div>
</template>
/templates/admin.js < This ended up being my problem..
Template.dashboard.rendered = function() {
return Orders.find();
};
**should be a helper.. so this instead:
Template.ordersTable.helpers({
orders: function () {
return Orders.find();
}
});
Insert Order Form
<template name="addOrderFormAdmin">
{{> autoformModals}} <!-- this is required for this modal to open -->
{{#afModal class="btn btn-primary" collection="Orders" operation="insert"}}
Add New Order
{{/afModal}}
</template>
Your code inside your dashboard rendered callback does not make any sense. I think you want to create a helper function for your ordersTable template instead:
Template.ordersTable.helpers({
orders: function () {
return Orders.find();
}
});
Furthermore, please note that Template.myTemplate.rendered is deprecated in Meteor version 1.0.4.2 (and later), use Template.myTemplate.onRendered instead.
Check publish and subscribe if you have removed the autopublish package. First, see if you can reach the collection through the console(on the web page, not the command line). Second, see if the collection is updated after your posts (for this you could use the command line by typing "meteor mongo" while the server is running or just download Robomongo).
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