Creating a Dynamic Form in a Aurelia View - javascript

Problem Overview
I have a controller with a view model which contains initially an empty array which will be used for storing 'Test Inputs'. I want to provide the user a button to add a new Test Input on the form which adds a new Test Input object to the array and displays the fields needed to edit its properties.
This works when the button is pressed for the first time (but with incorrect binding) but fails to create additional form elements when pressed again.
The Controller with View Model
import {inject} from 'aurelia-framework';
import {HttpClient, json} from 'aurelia-fetch-client';
import {Router} from 'aurelia-router';
import 'fetch';
import toastr from 'toastr';
#inject(HttpClient, Router)
export class create {
constructor(http, router) {
this.vm = {
test: {
id: 0,
description: "",
testOutput: {
id: 0,
valueType: "",
value: ""
},
testInputs: []
}
};
}
}
The user will be able to add a Test Input to the array by pressing a button which delegates to this function:
addTestInput() {
this.vm.test.testInputs.push({
argumentName: "",
valueType: "",
value: ""
});
}
This function pushes to the Test Inputs array in my view model object a new testInput object.
View
In my view I have added a repeat for binding for each object in the TestInputs array. The loop is intending to create the form elements for editing the properties of each Test Input object in the TestInputs array.
<p if.bind="vm.test.testInputs.length === 0">This test has no inputs. Click the button below to add one.</p>
<template if.bind="vm.test.testInputs.length > 0" repeat.for="testInput of vm.test.testInputs">
<div class="form-group">
<label for="testInputArgumentName${$index}">Argument Name</label>
<input value.bind="testInput.argumentName" type="text" class="form-control" id="testInputArgumentName${$index}" placeholder="Argument Name">
</div>
<div class="form-group">
<div class="form-group">
<label for="testInputValueType${$index}">Expected Value Type</label>
<select class="form-control" value.bind="testInput.valueType">
<option repeat.for="valueType of valueTypeList" value.bind="valueType">${valueType}</option>
</select>
</div>
</div>
<div class="form-group">
<label for="testInputValue${$index}">Expected Value</label>
<template if.bind="testInput.valueType === 'Boolean'">
<select class="form-control" value.bind="testInput.valueType">
<option>true</option>
<option>false</option>
</select>
</template>
<template if.bind="testInput.valueType !== 'Boolean'">
<input value.bind="testInput.value" type="text" class="form-control" id="testInputValue${$index}" placeholder="Expected Value">
</template>
</div>
</template>
<button click.delegate="addTestInput()" type="submit" class="btn btn-success">Add Test Input</button> <button type="submit" class="btn btn-success">Create Test</button>
When I first press the Add Test Input button the form elements are added to the page as expected. However if I press the button again the additional from elements for the new object added to the array are not created.
Also the fields seem to be binding to the local loop variable testInput rather than the specific object in the array.
Attempted Solutions
I have had a go using the suggestions at:
Blog Post on Dynamic Forms in Aurelia
Two Way Binding Array in Aurelia
Aurelia Gitter Chat Log - Help
from jsobell
But they don't seem to have worked for me. Anyone have any ideas?

Your problem is simple. You cannot use if and repeat on the same element. Also in your case are redundant with the p on the first line.
Simple do this:
<template repeat.for="testInput of vm.test.testInputs">
...
</template>
You can find more info here

Related

Grab template form input in Vue 3 class components (like done in Angular)

I started vue 3 today, and opted for the class-based approach in the cli. I'm from the Angular background, so forgive me for thinking like Angular. Every example I see (even in the docs) is still using the Vue({...}) thing, however, I wanna do something like this (still thinking Angular-ish)
In angular, I can do this
<form #formData="ngForm" (ngSubmit)="onSubmit(formData.value)">
<input (ngModel)="name" name="name" placeholder="name">
</form>
Then in component
...
export class AppComponent {
public name!: string;
onSubmit(formData: string) {
console.log(formData)
}
}
What would be the vue 3 class components approach like the above?
I currently have this in vue 3
export default class Welcome extends Vue {
name!: string;
onSubmit(formData: any) {
console.log(formData)
}
}
<template>
<div>
<form #submit.prevent="onSubmit">
<p>
<label for="name">Name</label> <br>
<input type="text" placeholder="name" id="name" name="name" v-model="name"/>
</p>
<button type="submit">Send</button>
</form>
</div>
</template>
What changes do I need to do to the above to connect the form in template to the component?
Without a third party library there isn't the same type of functionality that angular provides. Angular is doing a bunch of additional things to enhance the form object for validation and value tracking, and Vue natively does not do that. However you could instead put your data properties in an object to group them together. That way when you need to access them in something like the submit event to perhaps send all the values to an API, you can simply refer to that object instead of having to handle/build each property separately:
Class:
export class AppComponent {
// create object with bound form properties
public values: { name: string; } = { name: '' };
onSubmit() {
console.log(this.values); // { name: '' }
// axios.post('/api', this.values).then(res => console.log(res.data));
}
}
Template:
<template>
<div>
<form v-on:submit.prevent="onSubmit">
<p>
<label for="name">Name</label> <br>
<input type="text" placeholder="name" id="name" name="name" v-model="values.name"/>
</p>
<button type="submit">Send</button>
</form>
</div>
</template>
That being said, if you do need advanced form features like validation, sanitization, and similar there are plenty of libraries that do it well.
Hopefully that helps!
The native FormData constructor takes a <form> element, which creates the values of the form for all named inputs (<input>s or <textarea>s with name attribute).
So you could update your onSubmit method to retrieve the form data values:
export default class Welcome extends Vue {
name!: string;
onSubmit(formData: any) {
const form = e.target
const formData = new FormData(form)
}
}
Example using the Options API:
<template>
<div>
<form #submit.prevent="onSubmit">
<p>
<label for="name">Name</label> <br>
<input type="text" placeholder="name" id="name" name="name" v-model="name"/>
</p>
<button type="submit">Send</button>
</form>
</div>
</template>
<script>
export default {
methods: {
onSubmit(e) {
const form = e.target
const formData = new FormData(form)
console.log({ formData: Array.from(formData.entries()) })
}
}
}
</script>
demo

What is the easiest way to make editForm in Angular?

In my database i have many users which has many recipes.
Every recipe has some properties and collection of ingredients.
Below is screenshot
Recipe with all properties
So when user display recipe to edit on page should appear (form) recipe with loaded current data. This is kind of working because i can see the data but i think it's no done good.
I have form which is working fine without array (ingredients). Could you tell me how i should add ingredients to my edit form?
I'd be grateful if you see at my code and give me feedback and hints what i should change.
export class RecipeEditComponent implements OnInit {
#ViewChild('editForm') editForm: NgForm;
recipe: IRecipe;
photos: IPhoto[] = [];
ingredients: IIngredient[] = [];
uploader: FileUploader;
hasBaseDropZoneOver = false;
baseUrl = environment.apiUrl;
currentMain: IPhoto;
constructor(private route: ActivatedRoute, private recipeService: RecipeService,
private toastr: ToastrService) { }
ngOnInit(): void {
this.loadRecipe();
}
loadRecipe() {
this.recipeService.getRecipe(this.route.snapshot.params.id).subscribe(recipe => {
this.recipe = recipe;
this.initializeUploader();
})
}
updateRecipe(id: number) {
this.recipeService.editRecipe(id, this.recipe).subscribe(next => {
this.toastr.success('Recipe updated successfully');
this.editForm.reset(this.recipe);
}, error => {
this.toastr.error(error);
});
}
}
HTML
<div class="container mt-4 border" *ngIf="recipe">
<form #editForm="ngForm" id="editForm" (ngSubmit)="updateRecipe(recipe.id)" >
<h5 class=" text-center mt-2">Recipe details:</h5>
<div class="form-group mt-3">
<label for="city">Name</label>
<input class="form-control" type="text" name="name" [(ngModel)]="recipe.name">
</div>
<div class="form-group">
<app-ingredient-editor [ingredients] = "recipe.ingredients"></app-ingredient-editor>
<div *ngFor="let ingredient of recipe.ingredients; let i = index">
<input class="form-control" type="text" name="{{ingredient.name}}" [(ngModel)]="ingredient.name">
<input class="form-control" type="text" name="{{ingredient.amount}}" [(ngModel)]="ingredient.amount">
</div>
</div>
<div class="form-group">
<br>
<p>Add recipes</p>
</div>
<h5 class=" text-center mt-4">Description</h5>
<angular-editor cols=100% rows="6" [placeholder]="'Your description'" [(ngModel)]="recipe.description" name="description"></angular-editor>
</form>
<button [disabled]="!editForm.dirty" form="editForm" class="btn btn-success btn-block mb-5 mt-5">Save changes</button>
</div>
For now it's look like:
Form on page
When i delete ingredient name while changing on the console i have following error:
recipe-edit.component.html:12 ERROR Error: If ngModel is used within a form tag, either the name attribute must be set or the form
control must be defined as 'standalone' in ngModelOptions.
Problem is that part of code:
<div *ngFor="let ingredient of recipe.ingredients; let i = index">
<input class="form-control" type="text" name="{{ingredient.name}}" [(ngModel)]="ingredient.name">
<input class="form-control" type="text" name="{{ingredient.amount}}" [(ngModel)]="ingredient.amount">
</div>
</div>
But i don't know how to make it working..
How to add add array to template-driven form?
In my case i need to display current ingredients and be able to edit them.
I have tried something like this :
<input class="form-control" type="text" name="ingredient[i].name" [(ngModel)]="ingredient[i].name">
<input class="form-control" type="text" name="ingredient[i].amount" [(ngModel)]="ingredient[i].amount">
But id doesn't work
The problem is that the property name on the form must be defined in order for angular to know which input to update. You're binding name to the same property that the editable model is set to which means the user can edit it and in fact delete it, which isn't good.
The solution is to change it to a unique value that doesn't change. This should work:
<div *ngFor="let ingredient of recipe.ingredients; let i = index">
<input class="form-control" type="text" name="name{{ingredient.id}}" [(ngModel)]="ingredient.name">
<input class="form-control" type="text" name="amount{{ingredient.id}}" [(ngModel)]="ingredient.amount">
</div>
</div>
Link to stackblitz showing it working: https://stackblitz.com/edit/angular-10-base-template-q243lw?file=src%2Fapp%2Fapp.component.html
Edit: fixed bug in original post and added link to stackblitz

Problem with add and delete elements dinamically in angularjs

I have multiple select with different values. When the user selects the options that he wishes, press add button and must be added as elements to an array and that new line of added options be displayed in the view.
This does not work correctly, since each new selection steps on the values of the previous one. Only the last chosen option prevails. Example of my wrong array of values:
[{"product":"Product3","type":"A"},{"product":"Product3","type":"A"},{"type":"Product3","type":"A"}]
Here is my code:
<div class="col-md-3">
<label for="product" class="control-label">Product</label>
<select class="form-control pull-right" name="product" ng-model="data.product" >
<option ng-repeat='(key,value) in productList' value="{{key}}">{{value}}</option>
</select>
</div>
<div class="col-md-3">
<label for="type" class="control-label">Type</label>
<select class="form-control pull-right" name="type" ng-model="data.type" >
<option ng-repeat='i in typeList' value="{{i}}">{{i}}</option>
</select>
</div>
<button type="button" class="btn" ng-click="addElements()" >Add</button>
<div class="col-md-3">{{elementsList}}</div>
And in my angular controller:
$scope.elementsList = [];
$scope.addElements = function() {
$scope.elementsList.push($scope.data);
}
What's my problem? I am new in angular and js and it is difficult for me to see it ... how can I add and remove elements dynamically? Thanks!
When you add the $scope.data object to the array in addElements, it adds a reference to that object because that's how Javascript objects are stored: by reference. What that means is that any time that $scope.data changes in the future, the value you see in the array will also change. So you can do something like this instead:
$scope.addElements = function() {
const clone = Object.assign({}, $scope.data);
$scope.elementsList.push(clone);
}
This creates a new object clone each time.

is there any way to bind ng-model to multiple input fields uniquely inside a directive?

In my Project i Got a Issue like.I need to bind the user hobbies in the text field.if the user comes with a single hobby he can directly enter the hobby that he has. but when he had multiple then he had to click add multiple hobbies button.that working fine when i am displaying input fields dynamically using directives.but the issue is the value that coming from ng-model for that input field is binding to all input fields.
Here is my code.
Thanks in advance!
these are the images
this is how i am getting
this is what i need
In HTML
<div>
<div id="showHobbyfield"></div>
<input type="number" class="form-control" placeholder="ADD HOBBIES"
ng-click="addHoby()">
</div>
In controller
$scope.addHoby= function(){
var compiledeHTML = $compile("<div my-hobby></div>")($scope);
$("#showHobbyfield").append(compiledeHTML);
};
$scope.addUser = function(){
$scope.Users= [];
var obj = {
userhobby : $scope.user.morehobies
};
$scope.Users.push(obj);
menuStorage.put($scope.Users);
//menustorage is service to store user in localStorage.
In directive
'use strict';
angular.module('myApp')
.directive('myHobby', function() {
return {
scope : false,
templateUrl: 'views/my-hobby.html'
};
});
this is template: my-hobby.html
<div class="input-group">
<input type="text" ng-model="user.morehobies" class="form-control" placeceholder="type your hobbies here">
<div class="close-icon">
<span class="glyphicon glyphicon-remove" style="padding-left: 6px;"> </span>
</div>
</div>
For this i would suggest some other way if its ok with you.
If your hobbies is coming in array, like
user.morehobies = ['Reading', 'Writing']
or create array for storing hobbies.
then inside directive you can pass that object in directive.
I will use ng-repeat inside directive.
<div class="input-group" ng-repeat="h in hobies">
<input type="text" ng-model="h" class="form-control" placeceholder="type your hobbies here">
<div class="close-icon">
<span class="glyphicon glyphicon-remove" style="padding-left: 6px;"> </span>
</div>
</div>
so whenever user clicks on "Add hobbies" then we can add empty string in hobbies object in directive.
and whenever user clicks on remove you can remove that item from array.

Validate a form field with a dynamically given name in angular

I create a form dynamically in the view by iterating through an object that has the different questions to be asked to the user. One of the attributes of every question is formFieldName which is a a random string I use to give each form field a different name.
<form name="includedForm.newRequestForm" class="form-horizontal" role="form" novalidate>
<div ng-if="message.question.attributes.structure.type == 'object'">
<div ng-repeat="(index,objField) in message.question.attributes.structure.properties">
<div ng-if="objField.type == 'array'" class="form-group" show-errors>
<label for="{{objField.formFieldName}}" class="control-label col-sm-6">{{objField.title}}
<br /><i><small>{{objField.description}}</small></i></label>
<div class="col-sm-6">
<select class="form-control" name="{{objField.formFieldName}}" multiple ng-model="objField.userValue" ng-required="objField.required">
<option ng-repeat="option in objField.items.enum" value="{{option}}">{{option}}</option>
</select>
</div>
</div>
<div ng-if="objField.type == 'boolean'" class="form-group" show-errors>
<label for="{{objField.formFieldName}}" class="control-label col-sm-6">{{objField.title}}</label>
<div class="col-sm-6">
<input class="form-control" name="{{objField.formFieldName}}" ng-model="objField.userValue" type="checkbox" ng-value="option" ng-checked="message.question.attributes" />
</div>
</div>
</div>
</div>
<div class="col-sm-12">
<button ng-click="markAsDone(message)" class="btn btn-primary">Done</button>
</div>
<form>
In the controller I'm able to get the formFieldName attribute but I can't figure out how to use it to do the validation.
var MarkAsDone = function(message) {
$scope.includedForm = {};
var formField = message.question.attributes.formFieldName;
if ($scope.includedForm.newRequestForm.{{formField}}.$valid){
//submit the form
}
}
to answer you question:
first, {{}} is === $scope so you don't use that anywhere other than HTML. You use $scope in your JS and {{}} in HTML which creates a pipe (2-way binding) so that $scope.variable.property has bidirectional binding to {{variable.property }} in HTML.
$scope.includeForm.email === {{ includeForm.email }} === ng-model="includeForm.email" === ng-bind="includeForm.email"
if you set anyone of those all are set so if you set $scope it will show up in HTML and obviously as user input gets captured it is already in $scope ... all connected
when attempting to get the value from HTML back into JS you would need create and set a $scope i.e so if you create $scope.dataModel.dataProperty and use that in ng-model=dataModel.dataProperty (example) you again have two way binding ... you don't need to do anything as angular is taking care of the data pipeline. So if you want to extract the value to var, which is probably a waste as the $scope is already set as soon as the user checks the box
var formField = $scope.dataModel.dataProperty;
// but like I said no need as $scope.dataModel.dataProperty; is your var
In JS if you want to use a dynamic property as an object property key you would place the dynamic value in [] e.g.
$scope.variable[dynamicProperty].method;
// you can set a static property as a key with dot notation i.e.
$scope.variable.staticProperty = val;
Hope that helps

Categories