meteor - how to code a bootstrap modal template to re-render html input values even if the user has typed in them - forms

I'm using iron router to pass data to a bootstrap modal template. The modal contains a html form including many text inputs. The modal is re-used for 3 different features. I use a Session variable to keep track of which modal type is in use. Type 0 = blank form, type 1 = partial edit, type 2 = full edit. The form itself remains the same visually for all types. The only thing that changes is which input boxes contain a value.
For a type 1 edit only 2 boxes would contain values. For a type 2 edit all boxes would contain values. And the type 0 would be empty boxes.
// routes.js
Router.route('/mypage', function () {
var mtype = Session.get("mtype");
this.layout('myLayout');
this.render('my_popup', {to:'my_popup', data: function() {
switch (mtype) {
case 1:
return {box1:'box 1 text', box2:'box 2 text', box3:''};
case 2:
return {box1:'box 1 text', box2:'box 2 text', box3:'box 3 value'};
default:
return {box1:'', box2:'', box3:''};
}
}});
});
// main.html
<template name="myLayout">
{{> yield "my_popup"}}
</template>
<template name="my_popup">
<div class="modal fade" id="my_popup">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<div class="modal-title label label-primary">Title</div>
</div>
<div class="modal-body">
<form class="js-form-submit" id="my_form" name="my_form">
<div class="form-group">
<input class="form-control" type="text" name="box1" maxlength="64" placeholder="something" value="{{box1}}"/>
</div>
<div class="form-group">
<input class="form-control" type="text" name="box2" maxlength="64" placeholder="something" value="{{box2}}"/>
</div>
<div class="form-group">
<input class="form-control" type="text" name="box3" maxlength="64" placeholder="something" value="{{box3}}"/>
</div>
</form>
</div>
<div class="modal-footer">
<button class="js-form-ok btn btn-success btn-sm">submit</button>
<button class="btn btn-warning btn-sm" data-dismiss="modal">cancel</button>
</div>
</div>
</div>
</div>
</template>
Initially I tried passing an object to the modal template that only contained the properties that would be displayed. That didn't overwrite existing input values so I had to use the same object for each modal type and use empty strings for unused properties. I tried calling the reset() method on the form prior to showing the modal. In that case it caused the entire template to stop re-rendering.
Prior to showing the modal I set the session variable to the type of modal that will be displayed.
Session.set('mtype', 1);
That triggers iron router into sending the proper data to the template, unused properties are cleared and the template successfully re-renders.
Unfortunately if I type in one of the html inputs the template does not reset its value when it's re-rendered. This seems to be related to the same problem I encountered with the reset() method. If the input contains custom text (value is typed) then the modal doesn't display the new data sent to the template when the Session variable is changed. It preserves the user entered text.
What's the best way to re-use a bootstrap modal form in meteor? Should I use a helper instead of iron router to get the data object? Something like...
{{#with getData}}
Why is the user entered text being preserved?
I've also tried using the defaultValue attribute instead of value. The same issue occurs with both attributes.
To test the bug:
open the web console
Session.set('mtype',1);
$('#my_popup').modal('show');
type something in the 3rd text box
click off the modal to hide it
Session.set('mtype',0);
$('#my_popup').modal('show');
You'll see that the value you typed is still visible despite having sent empty strings to each box.
Another way:
Session.set('mtype',2);
$('#my_form')[0].reset();
Session.set('mtype',1);
$('#my_popup').modal('show');
You'll see that none of the boxes contain values despite having sent new strings of text to each box.

The only solution I've found is to use defaultValue in the template and then loop through the form fields before modal is shown and set value = defaultValue.
<input class="form-control" type="text" name="box1" maxlength="64" placeholder="something" defaultValue="{{box1}}"/>
Template.my_popup.rendered = function() {
$("#my_popup").on('show.bs.modal', function() {
var elems = $('#my_form')[0].elements;
for (var i=0; i<elems.length; i++) {
if (elems[i].hasAttribute('defaultValue')) {
elems[i].value = elems[i].getAttribute('defaultValue');
}
}
});
};

Related

AMP autocomplete how get id from autocomplete results and display only text in input

view screen I am using https://amp.dev/documentation/components/amp-autocomplete/ and I am able show in results id and name concated in one string, but I need show only name and store id in hidden input.
code screen
<amp-autocomplete filter="substring" filter-value="name" min-characters="2" src="/ajax/get_active_clinics.php" class="name_autocomplete">
<input type="text" placeholder="Numele clinicii" name="clinic_name" id="clinic_name"
{literal}on="change:AMP.setState({clinic_name_validation: true, form_message_validation:true})"{/literal}>
<span class="hide"
[class]="formResponse.clinic_name && !clinic_name_validation ? 'show input_validation_error' : 'hide'">Clinica este obligatorie</span>
<template type="amp-mustache" id="amp-template-custom">
{literal}
<div class="city-item" data-value="ID - {{id}}, {{name}}">
<div class="autocomplete-results-item-holder">
<div class="autocomplete-results-item-img">
<amp-img src="{{link}}" alt="{{name}}" width="40" height="40"></amp-img>
</div>
<div class="autocomplete-results-item-text">{{name}}</div>
</div>
</div>
{/literal}
</template>
</amp-autocomplete>
You can use the select event on amp-autocomplete to get the event.value which will return the value of the data-value attribute of the selected item.
https://amp.dev/documentation/components/amp-autocomplete/#events
You can then call the split() string method on the result.
You'll need to modify the data-value in your mustache template like so:
<div class="city-item" data-value="{{id}},{{name}}">
Then add the following code to your autocomplete, this will assign the split values to 2 temporary state properties.
<amp-autocomplete
...
on="select: AMP.setState({
clinicName: event.value.split(',')[0],
clinicId: event.value.split(',')[1]
})"
>
Once these values are in state you can then access them using bound values. Note the [value] attribute, this will update the inputs value when state changes. It's worth mentioning that the change in value won't trigger the change event listener on your input here as it's only triggered on user interaction.
<input
type="text"
placeholder="Numele clinicii"
name="clinic_name"
id="clinic_name"
[value]="clinicName"
on="change:AMP.setState({
clinic_name_validation: true,
form_message_validation:true
})"
/>
Last thing you'll need to do is add the hidden input for Clinic ID, again this will need to be bound to the temporary state property clinicId.
<input
type="hidden"
name="clinic_id"
[value]="clinicId"
>

How to get control type in template driven form in Angular 4

I have a form in angular 4 component. The fields are generated dynamically, i.e., using ngFor to generate the inputs. The inputs are of different types, e.g. checkbox, text...
In the ts code, how can i know the type of the input control?
HTML
<form *ngIf="active" #easyForm="ngForm" (ngSubmit)="onSubmit()">
<div *ngFor="let i in items">
<input type="text" name="text-{{item.id}}" [(ngModel)]="item.text">
<input type="checkbox" name="checkbox-{{item.id}} (ngModel)]="item.checked">
</div>
</form>
TS
#ViewChild('easyForm') set content(easyForm: NgForm) {
this.easyForm = easyForm;
if (this.easyForm) {
const keys = Object.keys(this.easyForm.controls);
foreach(let key in keys) {
this.easyForm.controls[key] <---- am i able to get the type of the control here?
}
}

On Button Click Validate a template form in Angular 2

I am new to Angular 2.
I have created a simple template which has two text field, I want to required field validate those two fields.
Login Form
<form #loginForm="ngForm" (ngSubmit)="onSubmit(loginForm)" novalidate>
<div class="container">
<div class="form-group">
ooooo <label><b>Username</b></label>
<input type="text" placeholder="Enter Username" name="uname" required [(ngModel)]="UserData.uname" #uname="ngModel">
<div *ngIf="loginForm.invalid" class="alert alert-danger">
<div [hidden]="!uname.errors.required"> Name is required </div>
</div>
</div>
<div class="form-group">
<label><b>Password</b></label>
<input type="password" placeholder="Enter Password" name="pwd" required [(ngModel)]="UserData.pwd" #pwd="ngModel">
<div *ngIf="UserData.pwd.errors && (UserData.pwd.dirty || UserData.pwd.touched)" class="alert alert-danger">
<div [hidden]="!UserData.pwd.errors.required">Password is required </div>
</div>
<button type="submit" >Login</button>
</div>
</div>
</form>
My Component
import { Component } from "#angular/core"
import { User } from "./UserModel"
#Component({
selector: 'my-login',
templateUrl:"app/Login/login.html"
})
export class LoginComponent
{
//alert: any("hello");
UserData: User = new User("", "");
submitted = false;
onSubmit(form: any) {
alert("dfsdfsd" + form);
if (!form.invalid) {
alert(this.UserData.uname);
alert(this.UserData.pwd);
this.submitted = true;
}
}
}
What i want to implement is-
When the form loads no validation message should appear?
When user clicks on the submit button then the required message should appear?
In both the textbox i have applied different type of checks to show the message that is inconsistent? so there should be a consistent way to solve this.
Many thanks for the help.
Maybe make use of the submitted variable, and use that in template, to not show message, until submitted is true, which we set it as in the submit function.
Also you wouldn't really need the two-way-binding here, since the object your form produces is directly assignable to your UserData.
The validation messages I'd just set then simply like this, where we are targeting the username:
<div *ngIf="uname.errors?.required && submitted"> Name is required </div>
in your submit function I'd pass loginForm.value as parameter instead of just loginForm. This way you get the form object ready to be used :)
And in your function you can assign the object to your UserData variable.
onSubmit(form: any) {
this.submitted = true;
this.UserData = form;
}
If you do want to keep the two-way-binding, it's of course totally possible! :)
DEMO

Form gets invalid after form.reset() - Angular2

I have a template based form in my Angular2 app for user registration. There, I am passing the form instance to the Submit function and I reset the from once the async call to the server is done.
Following are some important part from the form.
<form class="form-horizontal" #f="ngForm" novalidate (ngSubmit)="onSignUpFormSubmit(f.value, f.valid, newUserCreateForm, $event)" #newUserCreateForm="ngForm">
<div class="form-group">
<label class="control-label col-sm-3" for="first-name">First Name:</label>
<div class="col-sm-9">
<input type="text" class="form-control" placeholder="Your First Name" name="firstName" [(ngModel)]="_userCreateForm.firstName"
#firstName="ngModel" required>
<small [hidden]="firstName.valid || (firstName.pristine && !f.submitted)" class="text-danger">
First Name is required !
</small>
</div>
</div>
.......
.......
<div class="form-group">
<div class="col-sm-offset-3 col-sm-12">
<button type="submit" class="btn btn-default">Submit</button>
<button type="reset" class="btn btn-link">Reset</button>
</div>
</div>
</form>
In my component file, I have written following function.
onSignUpFormSubmit(model: UserCreateForm, isValid: boolean, form: FormGroup, event:Event) {
event.preventDefault();
if (isValid) {
this._userEmail = model.email;
this._authService.signUp(this._userCreateForm).subscribe(
res => {
console.log("In component res status = "+res.status);
if(res.status == 201){
//user creation sucess, Go home or Login
console.log("res status = 201");
this._status = 201;
}else if(res.status == 409){
//there is a user for given email. conflict, Try again with a different email or reset password if you cannot remember
this._status = 409;
}else{
//some thing has gone wrong, pls try again
this._serverError = true;
console.log("status code in server error = "+res.status);
}
form.reset();
alert("async call done");
}
);
}
}
If I submit an empty form, I get all validations working correctly. But, when I submit a valid form, Once the form submission and the async call to the server is done, I get all the fields of the form invalid again.
See the following screen captures.
I cannot understand why this is happening. If I comment out form.reset(), I do not get the issue. But form contains old data i submitted.
How can I fix this issue?
I solved this By adding these lines:
function Submit(){
....
....
// after submit to db
// reset the form
this.userForm.reset();
// reset the errors of all the controls
for (let name in this.userForm.controls) {
this.userForm.controls[name].setErrors(null);
}
}
You can just initialize a new model to the property the form is bound to and set submitted = false like:
public onSignUpFormSubmit() {
...
this.submitted = false;
this._userCreateForm = new UserCreateForm();
}
You need to change the button type submit to button as following.
<div class="form-group">
<div class="col-sm-offset-3 col-sm-12">
<button type="button" class="btn btn-default">Submit</button>
<button type="reset" class="btn btn-link">Reset</button>
</div>
</div>
Reseting the form in simple javascript is the solution for now.
var form : HTMLFormElement =
<HTMLFormElement>document.getElementById('id');
form.reset();
this is how finally I had achieved this. I am using Angular5.
I have created a form group named ="firstFormGrop".
If you are not using form groups you can name the form as follow:
<form #myNgForm="ngForm">
In the html doc:
<form [formGroup]="firstFormGroup">
<button mat-button (click)='$event.preventDefault();this.clearForm();'>
<span class="font-medium">Create New</span>
</button>
</form>
In the .ts file:
this.model = new MyModel();
this.firstFormGroup.reset();
if you where using #myNgForm="ngForm then use instead:
myNgForm.reset();
// or this.myNgForm.reset()
This is a very common issue that after clicking the reset button we created the validators are not reset to its initial state, and it looks ugly.
To avoid that we have two options,the button is outside the form, or we prevent the submission when the button is tagged inside the form.
To prevent this default behaviour we need to call $event.preventDefault() before whatever method we are choosing to clear the form.
$event.preventDefault() is the key point.
The solution:
TEMPLATE:
<form
action=""
[formGroup]="representativeForm"
(submit)="register(myform)"
#myform="ngForm"
>
*ngIf="registrationForm.get('companyName').errors?.required && myform.submitted"
COMPONENT:
register(form) {
form.submitted = false;
}
Try changing the button type from "submit" to "button", e.g. :
<button type="button">Submit</button>
And move the submit method to click event of the button. Worked for me!

Trigger validation of all fields in Angular Form submit

I'm using this method: http://plnkr.co/edit/A6gvyoXbBd2kfToPmiiA?p=preview to only validate fields on blur. This works fine, but I would also like to validate them (and thus show the errors for those fields if any) when the user clicks the 'submit' button (not a real submit but a data-ng-click call to a function)
Is there some way to trigger validation on all the fields again when clicking that button?
What worked for me was using the $setSubmitted function, which first shows up in the angular docs in version 1.3.20.
In the click event where I wanted to trigger the validation, I did the following:
vm.triggerSubmit = function() {
vm.homeForm.$setSubmitted();
...
}
That was all it took for me. According to the docs it "Sets the form to its submitted state." It's mentioned here.
I know, it's a tad bit too late to answer, but all you need to do is, force all forms dirty. Take a look at the following snippet:
angular.forEach($scope.myForm.$error.required, function(field) {
field.$setDirty();
});
and then you can check if your form is valid using:
if($scope.myForm.$valid) {
//Do something
}
and finally, I guess, you would want to change your route if everything looks good:
$location.path('/somePath');
Edit: form won't register itself on the scope until submit event is trigger. Just use ng-submit directive to call a function, and wrap the above in that function, and it should work.
In case someone comes back to this later... None of the above worked for me. So I dug down into the guts of angular form validation and found the function they call to execute validators on a given field. This property is conveniently called $validate.
If you have a named form myForm, you can programmatically call myForm.my_field.$validate() to execute field validation. For example:
<div ng-form name="myForm">
<input required name="my_field" type="text" ng-blur="myForm.my_field.$validate()">
</div>
Note that calling $validate has implications for your model. From the angular docs for ngModelCtrl.$validate:
Runs each of the registered validators (first synchronous validators and then asynchronous validators). If the validity changes to invalid, the model will be set to undefined, unless ngModelOptions.allowInvalid is true. If the validity changes to valid, it will set the model to the last available valid $modelValue, i.e. either the last parsed value or the last value set from the scope.
So if you're planning on doing something with the invalid model value (like popping a message telling them so), then you need to make sure allowInvalid is set to true for your model.
You can use Angular-Validator to do what you want. It's stupid simple to use.
It will:
Only validate the fields on $dirty or on submit
Prevent the form from being submitted if it is invalid
Show custom error message after the field is $dirty or the form is submitted
See the demo
Example
<form angular-validator
angular-validator-submit="myFunction(myBeautifulForm)"
name="myBeautifulForm">
<!-- form fields here -->
<button type="submit">Submit</button>
</form>
If the field does not pass the validator then the user will not be able to submit the form.
Check out angular-validator use cases and examples for more information.
Disclaimer: I am the author of Angular-Validator
Well, the angular way would be to let it handle validation, - since it does at every model change - and only show the result to the user, when you want.
In this case you decide when to show the errors, you just have to set a flag:
http://plnkr.co/edit/0NNCpQKhbLTYMZaxMQ9l?p=preview
As far as I know there is a issue filed to angular to let us have more advanced form control. Since it is not solved i would use this instead of reinventing all the existing validation methods.
edit: But if you insist on your way, here is your modified fiddle with validation before submit. http://plnkr.co/edit/Xfr7X6JXPhY9lFL3hnOw?p=preview
The controller broadcast an event when the button is clicked, and the directive does the validation magic.
One approach is to force all attributes to be dirty. You can do that in each controller, but it gets very messy. It would be better to have a general solution.
The easiest way I could think of was to use a directive
it will handle the form submit attribute
it iterates through all form fields and marks pristine fields dirty
it checks if the form is valid before calling the submit function
Here is the directive
myModule.directive('submit', function() {
return {
restrict: 'A',
link: function(scope, formElement, attrs) {
var form;
form = scope[attrs.name];
return formElement.bind('submit', function() {
angular.forEach(form, function(field, name) {
if (typeof name === 'string' && !name.match('^[\$]')) {
if (field.$pristine) {
return field.$setViewValue(field.$value);
}
}
});
if (form.$valid) {
return scope.$apply(attrs.submit);
}
});
}
};
});
And update your form html, for example:
<form ng-submit='justDoIt()'>
becomes:
<form name='myForm' novalidate submit='justDoIt()'>
See a full example here: http://plunker.co/edit/QVbisEK2WEbORTAWL7Gu?p=preview
Here is my global function for showing the form error messages.
function show_validation_erros(form_error_object) {
angular.forEach(form_error_object, function (objArrayFields, errorName) {
angular.forEach(objArrayFields, function (objArrayField, key) {
objArrayField.$setDirty();
});
});
};
And in my any controllers,
if ($scope.form_add_sale.$invalid) {
$scope.global.show_validation_erros($scope.form_add_sale.$error);
}
Based on Thilak's answer I was able to come up with this solution...
Since my form fields only show validation messages if a field is invalid, and has been touched by the user I was able to use this code triggered by a button to show my invalid fields:
// Show/trigger any validation errors for this step
angular.forEach(vm.rfiForm.stepTwo.$error, function(error) {
angular.forEach(error, function(field) {
field.$setTouched();
});
});
// Prevent user from going to next step if current step is invalid
if (!vm.rfiForm.stepTwo.$valid) {
isValid = false;
}
<!-- form field -->
<div class="form-group" ng-class="{ 'has-error': rfi.rfiForm.stepTwo.Parent_Suffix__c.$touched && rfi.rfiForm.stepTwo.Parent_Suffix__c.$invalid }">
<!-- field label -->
<label class="control-label">Suffix</label>
<!-- end field label -->
<!-- field input -->
<select name="Parent_Suffix__c" class="form-control"
ng-options="item.value as item.label for item in rfi.contact.Parent_Suffixes"
ng-model="rfi.contact.Parent_Suffix__c" />
<!-- end field input -->
<!-- field help -->
<span class="help-block" ng-messages="rfi.rfiForm.stepTwo.Parent_Suffix__c.$error" ng-show="rfi.rfiForm.stepTwo.Parent_Suffix__c.$touched">
<span ng-message="required">this field is required</span>
</span>
<!-- end field help -->
</div>
<!-- end form field -->
Note: I know this is a hack, but it was useful for Angular 1.2 and earlier that didn't provide a simple mechanism.
The validation kicks in on the change event, so some things like changing the values programmatically won't trigger it. But triggering the change event will trigger the validation. For example, with jQuery:
$('#formField1, #formField2').trigger('change');
I like the this approach in handling validation on button click.
There is no need to invoke anything from controller,
it's all handled with a directive.
on github
You can try this:
// The controller
$scope.submitForm = function(form){
//Force the field validation
angular.forEach(form, function(obj){
if(angular.isObject(obj) && angular.isDefined(obj.$setDirty))
{
obj.$setDirty();
}
})
if (form.$valid){
$scope.myResource.$save(function(data){
//....
});
}
}
<!-- FORM -->
<form name="myForm" role="form" novalidate="novalidate">
<!-- FORM GROUP to field 1 -->
<div class="form-group" ng-class="{ 'has-error' : myForm.field1.$invalid && myForm.field1.$dirty }">
<label for="field1">My field 1</label>
<span class="nullable">
<select name="field1" ng-model="myresource.field1" ng-options="list.id as list.name for list in listofall"
class="form-control input-sm" required>
<option value="">Select One</option>
</select>
</span>
<div ng-if="myForm.field1.$dirty" ng-messages="myForm.field1.$error" ng-messages-include="mymessages"></div>
</div>
<!-- FORM GROUP to field 2 -->
<div class="form-group" ng-class="{ 'has-error' : myForm.field2.$invalid && myForm.field2.$dirty }">
<label class="control-label labelsmall" for="field2">field2</label>
<input name="field2" min="1" placeholder="" ng-model="myresource.field2" type="number"
class="form-control input-sm" required>
<div ng-if="myForm.field2.$dirty" ng-messages="myForm.field2.$error" ng-messages-include="mymessages"></div>
</div>
</form>
<!-- ... -->
<button type="submit" ng-click="submitForm(myForm)">Send</button>
I done something following to make it work.
<form name="form" name="plantRegistrationForm">
<div ng-class="{ 'has-error': (form.$submitted || form.headerName.$touched) && form.headerName.$invalid }">
<div class="col-md-3">
<div class="label-color">HEADER NAME
<span class="red"><strong>*</strong></span></div>
</div>
<div class="col-md-9">
<input type="text" name="headerName" id="headerName"
ng-model="header.headerName"
maxlength="100"
class="form-control" required>
<div ng-show="form.$submitted || form.headerName.$touched">
<span ng-show="form.headerName.$invalid"
class="label-color validation-message">Header Name is required</span>
</div>
</div>
</div>
<button ng-click="addHeader(form, header)"
type="button"
class="btn btn-default pull-right">Add Header
</button>
</form>
In your controller you can do;
addHeader(form, header){
let self = this;
form.$submitted = true;
...
}
You need some css as well;
.label-color {
color: $gray-color;
}
.has-error {
.label-color {
color: rgb(221, 25, 29);
}
.select2-choice.ui-select-match.select2-default {
border-color: #e84e40;
}
}
.validation-message {
font-size: 0.875em;
}
.max-width {
width: 100%;
min-width: 100%;
}
To validate all fields of my form when I want, I do a validation on each field of $$controls like this :
angular.forEach($scope.myform.$$controls, function (field) {
field.$validate();
});