Trying to think about how to build a multi step form in angular 2 - forms

I am trying to build a small, 3 step form. It would be something similar to this:
The way I did this in react was by using redux to track form completion and rendering the form body markup based on the step number (0, 1, 2).
In angular 2, what would be a good way to do this? Here's what I am attempting at the moment, and I'm still working on it. Is my approach fine? Is there a better way to do it?
I have a parent component <app-form> and I will be nesting inside it <app-form-header> and <app-form-body>.
<app-form>
<app-header [step]="step"></app-header>
<app-body [formData]="formData"></app-body>
</app-form>
In <app-form> component I have a step: number and formData: Array<FormData>. The step is just a index for each object in formData. This will be passed down to the header. formData will be responsible the form data from user. Each time the form input is valid, user can click Next to execute nextStep() to increment the index. Each step has an associated template markup.
Is there a better way to do something like this?

don't overdo it, if it is a simple form you don't need to use the router or a service to pass data between the steps.
something like this will do:
<div class="nav">
</div>
<div id="step1" *ngIf="step === 1">
<form></form>
</div>
<div id="step2" *ngIf="step === 2">
<form></form>
</div>
<div id="step3" *ngIf="step === 3">
<form></form>
</div>
It's still a small template, and you kan keep all of the form and all the data in one component, and if you want to you can replace the ngIf with something that switches css-classes on the step1,2,3 -divs and animate them as the user moves to the next step

If you want to keep things extensible, you could try something like this:
<sign-up>
<create-account
[model]="model"
[hidden]="model.createAccount.finished">
</create-account>
<social-profiles
[model]="model"
[hidden]="model.socialProfiles.finished">
</social-profiles>
<personal-details
[model]="model"
[hidden]="model.personalDetails.finished">
</personal-details>
</sign-up>
export class SignUpVm {
createAccount: CreateAccountVm; //Contains your fields & finished bool
socialProfiles: SocialProfilesVm; //Contains your fields & finished bool
personalDetails: PersonalDetailsVm; //Contains your fields & finished bool
//Store index here if you want, although I don't think you need it
}
#Component({})
export class SignUp {
model = new SignUpVm(); //from sign_up_vm.ts (e.g)
}
//Copy this for personalDetails & createAccount
#Component({})
export class SocialProfiles {
#Input() model: SignUpVm;
}

Related

React JS - Composing generic form with dynamic childrens

I am just a beginner in reactjs. I feel so good about using it, but I have been stuck for a while on a concept that I wanna implement, that would solve me whole lot of other issues.
Concept
I want to create a form component that can have dynamic children passed to it. It won't be a specific form component like LoginForm, ContactForm etc. Rather it would just be Form.
Approach 1
class LoginPage extends Rect.Component {
constructor(props) {
super(props)
this.submit = this.submit.bind(this);
this.validate = this.validate.bind(this);
this.reset = this.reset.bind(this);
}
submit(...args) {
this.refs.form.submit(args);
}
validate(...args) {
this.refs.form.validate(args);
}
reset(...args) {
this.refs.form.reset(args);
}
render() {
return (
<LoginPage>
<Form ref="form" onSubmit={this.submit}>
<Input value="email" pattern="/email_pattern/" onValidate={this.validate} />
<Input value="password" pattern="/password_pattern/" onValidate={this.validate} />
<Button value="submit" action={this.submit} />
<Button value="reset" action={this.reset} />
</Form>
</LoginPage>
}
}
Input onChange calls the validate function that just passes on the args to the Form's validate function. For the Form to know if all it's children's are validated. I pass isValid and targetInputComponent as args to the form.
Button Submit onClick calls the submit function likewise and LoginPage (acts as middleware) passes the call to the Form component. Form check it's state for inValid inputs etc.
Button Reset onClick call is passed to the Form component likewise. But how do the form handle this reset functionality? Input's value is controlled by LoginPage. Form can't change the input's value.
Aproach 2
What I did was add the input's data and validity to the LoginPage state. Now both Form and Inputs just call the Login Page to update it's state. Form and Inputs components are using this state as prop.
class LoginPage extends React.Component {
constructor(props) {
super(props)
this.state = {
formSchema: this.initSchema()
}
this.validateField = this.validateField.bind(this);
this.submitForm = this.submitForm.bind(this);
}
initSchema() {
const formSchema = {
email: {
isValid: false,
value: ''
},
password: {
isValid: false,
value: ''
},
password2: {
isValid: false,
value: ''
}
}
return formSchema;
}
validateField(dataObj, targetComponent) {
const formSchema = Object.assign(this.state.formSchema, dataObj);
this.setState({ formSchema })
}
submitForm(isSuccess) {
console.log(isSuccess);
console.log('Form Submit: ', this.state.formSchema);
throw new Error('Submition Error');
}
reset() {
// Loop through each object in formSchema and set it's value to empty, inputs will validate themselves automatically.
}
render() {
return <div>
<Form ref="formLogin" className="auth-form" onSubmit={this.submitForm} formSchema={this.state.formSchema}>
<h1>
Switch Accounts
</h1>
<Input type="email" name="email" onValidate={this.validateField} value={this.state.formSchema.email.value}
isValid={this.state.formSchema.email.isValid}/>
<Input type="password" name="password" onValidate={this.validateField} value={this.state.formSchema.password.value}
isValid={this.state.formSchema.password.isValid}/>
<Button value="Submit" type="submit" />
</Form>
</div>
}
}
Problem
This approach is making the LoginPage Component quite messy. How will the LoginPage component will handle the forms if I have more than 1 form on the page? There will be even more features to LoginPage like Lists, Grid, Modals etc. This LoginPage shouldn't Handle these situations. Form should be responsible for all the inputs, submition, etc functionality. This form component should be generic to all types of forms. I don't want to create feature forms like LoginForm, ContactForm, etc.
The solution to this issue will aid me a lot in whole lot of other components.
Your approach 2 is the standard way of handling this problem.
Why wouldn't you create a separate <LoginForm>? Doing so can abstract the fields and validation away from other unrelated functionalities on your page.
If later you need a <ContactForm> or any other type of form, it will have different fields and different validation logic, meaning you'll need to build it out regardless.
Short of using something like redux, you're on the right track.
There are numerous ways to make ‘generic’ forms. But in my opinion it is best to keep the forms seperatly.
The reason why I say this, is because one way or another, you still have to implement various validation condition for most fields, such as e-mailaddress or what ever you will use.
I think the best approach is to make the components individual. For example: make a TextField component that handles input stuff like validation. Maybe a Button component for submit and callbacks.
So my advise: keep the forms seperatie. No need to overthink and lose useful time in trying to make things ‘pretty’.
Goodluck!

Submit form array with Vue to Laravel

Before I started using Vue I had a simple form that would update 1 value on 1 column # 1 request at a time. Now I am using Vue and my form has a new 'middle' button that is being used to build up an array of items to submit 1 request to update multiple columns dynamically.
Problem is now that prevent default is enabled, my original form submission no longer works and I need to either submit the request with Vue or is there a way to re-enable default action on a button? This would be great.
<form #submit.prevent="newbutton">
// new button
<button #click="newbutton"></button>
// original button
<button #click="submit" :id="{{ $element->id }}></button> // #submit.enableDefault ??
The prevent is just a helper method on the #submit. To allow this variance you will need to move the logic into your newbutton method
// in template remove prevent
<form #submit="newbutton" action="/where-this-should-post">
// in script move logic to your newbutton method
methods {
newbutton(event) {
if (formNotValid) {
event.preventDefault()
}
}
}

dynamically create bound datas with polymer

I would like to create a dynamic form using polymer, meaning that everytime the user press "add" button,it will add a new field in the form. Or, more specifically, it will add a paper-dropdown-menu, where all of the options come from a dom-repeat fed by an ajax call.
this is what i've done so far:
<div id="filterContainer">
<div class="flex rulesForm" id="filter1">
<paper-dropdown-menu name="rule1A" no-label-float>
<paper-listbox attr-for-selected="value" selected="{{filter1A}}" class="dropdown-content" id="thirdPartyFilter1A">
<template is="dom-repeat" items="{{rule1A}}">
<paper-item value="[[item]]">[[item]]</paper-item>
</template>
</paper-listbox>
</paper-dropdown-menu>
</div>
</div>
<paper-button raised on-tap="addFilterField">Add</paper-button>
<div>
and in the JS:
addFilterField: function () {
let dropdown = document.createElement('paper-dropdown-menu');
dropdown.name = "";
dropdown.noLabelFloat = true;
let listbox = document.createElement('paper-listbox');
listbox.class = "dropdown-content";
listbox.attrForSelected = "value";
listbox.selected = "{{filter1A}}";
let paperItem = document.createElement('paper-item');
paperItem.value = "[[item]]";
var itemNode = document.createTextNode('[[item]]');
paperItem.appendChild(itemNode);
listbox.appendChild(paperItem);
dropdown.appendChild(listbox);
console.log(dropdown);
filterContainer.appendChild(dropdown);
my problem is about the data-binding... If I use createTextNode with [[item]], it will simply write it as a string in the document. Is there a way to fix this? (or a way easier solution to add field in a form?)
first of all you cannot use binding notation in javascript. it is markup
2nd, polymer doesn't yet support creating data bindings dynamically. however I'm sure you can accomplish what you are trying to do.
3rd,
you have to use the Polymer Dom API. https://www.polymer-project.org/1.0/docs/devguide/local-dom#dom-api
instead of paperItem.appendChild(itemNode)
you would use
Polymer.dom(listbox).appendChild(itemNode);

infinite loop when trying to retrieve data from controller in view using angular $resource.Query() method

I'm building a small sails.js + angular.js app.
Here is a fiddle that roughly shows what my code looks like: http://jsfiddle.net/WEk3F/
index: function(req, res, next) {
Food.find({}, function foundFoods(err, foods) {
if (err) return next(err);
var data = {
name1: "test1",
name2: "test2"
}
res.view(
'food/index', {
foods: data
});
});
},
<div ng-app="myApp">
<div ng-controller="FoodController">
<ul>
<li ng-repeat="food in foods">
{{food.name}}
</li>
</ul>
<form>
<label for="name">Name:</label>
<input name="name" ng-model="editableFood.name" />
</form>
</div>
</div>
My problem is, that whenever i try to retrieve the data from my controller, i don't get those 2 items but instead it renders more and more items and just doesn't stop. even the page gets slow and unresponsive and almost freezes.
When i say
$scope.foods = [{"name": "test1"},{"name": "test2"}];
instead of
$scope.foods = Food.query();
it works. but i want the data to be coming from the backend via the controller.
the other methods (add, update etc) of the angular.js $resource module work fine for me.
/food maps to the index action of my FoodController and just returns some fixed test data
i found out what the problem was.
the angular.js $resource should only be used in a restful way, so the GET request to my food/index should return an array of objects.
in my case this wasn't the case. instead i used the index action of my controller to render a view and the result was therefor html.
so my options are to define a new route, that the $resource takes for the Query() command or define a new action that i use for the view/html stuff and use the index action again for pure restful response.
thx #sam hunter for pointing me to the right direction
the infinite loop that i received is explained here: One dimensional array of strings being parsed to 2d by angular resource
i love stack overflow

clear modal bootstrap form in angular

what is the best way to clear bootstrap modal form data without using jquery?
I could use this, but I would like to know the angular way of clearing modal form data.
$('#myModal').on('hidden', function () {
$('#Username').val("");
});
Edit:
I have multiple elements on the form. I found out the easiest way is to create a service to reset the object and updates the ng-models on the form.
To reset the form object, call the shared service to reset: mySharedService.resetObj(); To clear the form in controller:
$scope.myObj = mySharedService.getObj();
$scope.myForm={};
$scope.myForm.myData = angular.copy($scope.myObj);
all the elements is under the 'myData' item.
Use ng-bind to set the value of whatever inside the <div class="modal-body"> equal to a $scope variable, then change the $scope variable on whatever action you want, most likely on the cancel event, like so:
$scope.cancel = function () {
$scope.yourVariable = "";
$modalInstance.dismiss('cancel');
};
the binding is like this:
<div class="modal-body">
<div ng-bind="yourVariable"></div>
</div>