I'm currently using vue 2 (with Nuxt).
I have 2 custom components (Form and ErrorMessage) that can be used like this:
<Form>
<div>
<input type="text" name="Name" autocomplete="off" v-model="name">
<ErrorMessage v-model="name" required minlength="4"></ErrorMessage>
<div>
</Form>
In <ErrorMEssage>, I have a validate method:
export default {
methods: {
validate() {
// someLogic
}
}
Inside <Form>, I have a submitHandler method that will loop through every <ErrorMessage> to call its validate:
<template>
<form ref="form" #submit.prevent="submitHandler">
<slot></slot>
</form>
</template>
<script>
export default {
submitHandler() {
this.$children.forEach(c => {
c.validate()
})
}
}
</script>
This works fine as $children can loop through all <ErrorMessage> even if they are nested deeply in multiple divs.
So my question is, how can I do the same in vue3 since $children is removed?
In Vue 3 the $children attribute has been removed and replaced by using $refs
To forEach through a child component you need a ref attribute on your element and then use this.$refs.refName.forEach since you already have ref=form then this.$refs.form.forEach will work for your application
Related
I have successfully bound data in a two way format in vue 3 like this :
<template>
<p for="">Year of Incorporation</p>
<input type="text" v-model="input" />
<div>
{{ input }}
</div>
</template>
<script>
export default {
setup() {
let input = ref("")
return {input};
},
};
</script>
What I want to do now is put the input inside a child component like this:
Parent Component:
<template>
<Child v-model="input" />
{{input}}
</template>
Child Component:
<template>
<input type="text" v-model="babyInput" />
</template>
<script>
export default {
setup() {
let babyInput = ref("")
return {babyInput};
},
};
</script>
The Vue.js documentation presents a nice way of handling v-model-directives with custom components:
Bind the value to the child component
Emit an event when the Child's input changes
You can see this example from the docs with the composition when you toggle the switch in the right-top corner (at https://vuejs.org/guide/components/events.html#usage-with-v-model).
I am using a template based form in angular. I also use bootstrap (v4) and I wish to show some validation messages when the form was submitted.
This is my form:
<form [ngClass]="{'was-validated': wasValidated}">
<div class="form-group">
<label for="name">Name</label>
<input type="text" id="name" name="name" class="form-control" [(ngModel)]="category.name" #name="ngModel" required maxlength="100"/>
<div *ngIf="name.invalid" class="invalid-feedback">
<div *ngIf="name.errors.required">
Name is required.
</div>
</div>
</div>
<button type="submit" class="btn btn-success" (click)="save()">Save</button>
</form>
My component looks as follows:
category: Category;
wasValidated: boolean = false;
ngOnInit() {
this.reset();
}
save() {
this.wasValidated = true;
this.categoriesService.createCategory(this.category).subscribe(
() => {
this.notificationService.add(notifications.category_saved, {name: this.category.name});
this.reset();
},
() => this.notificationService.add(notifications.save_category_failed)
);
}
reset() {
this.wasValidated = false;
this.category = {} as Category;
}
This works, but I have a feeling it's overly complex and more like a workaround rather than the right way. What is the best way to accomplish this?
Note: the class was-validated must be present on the form element in order to show the div with class invalid-feedback. I'm using this: https://getbootstrap.com/docs/4.0/components/forms/#validation
Note 2: I have currently no mechanism yet to prevent form submission on error. I'd like to know a good solution for that as well!
With the answer from #Chellappan V I was able to construct the solution I wanted.
I have applied to following changes:
First added #form="ngForm" to the form tag in the template. Secondly I changed the ngClass expression to reference the submitted state of the form, rather than referring to a boolean which was set to true manually when form was submitted. Last but not least I pass the form in the submit method on the save button.
<form novalidate #form="ngForm" [ngClass]="{'was-validated': form.submitted}">
<!-- form controls -->
<button type="submit" class="btn btn-success" (click)="submit(form)">Save</button>
</form>
In the component I injected the template variable in the component with #ViewChild.
#ViewChild("form")
private form: NgForm;
The submit method now takes a form parameter of type NgForm which is used to check if the form was valid before sending a request to the backend:
submit(form: NgForm) {
if (form.valid) {
this.categoriesService.createCategory(this.category).subscribe(
() => {
this.notificationService.add(notifications.category_saved, {name: this.category.name});
this.reset();
},
() => this.notificationService.add(notifications.save_category_failed)
);
} else {
this.notificationService.add(notifications.validation_errors);
}
}
Finally the reset method resets the form and the model so it can be re-entered to submit a next instance:
reset() {
this.form.resetForm();
this.category = {} as NewCategoryDto;
}
How do I apply any type of CSS to redux-form Field components? className and class are silently ignored:
<div>
<label>Name</label>
<div>
<Field
className="form-input"
name="formContactName"
component="input"
type="text"
placeholder="Person to contact"
/>
</div>
</div>
I was able to the apply the styles by creating a custom component:
<div>
<label>Name</label>
<div>
<Field
name="formContactName"
component={ customInput }
/>
</div>
</div>
but that's a major PITA and also largely negates the gains of using redux-form in the first place. Am I missing something? Note I added the className assignments directly in the custom component - I realize I can send them through as props in Field.
I tried setting input styles globally but they were ignored as well. The redux-form website docs tell you everything you need to know to use the rig but make no mention of CSS that I can see...
Thanks,
JB
Edit: this is not a duplicate - the answer pointed to uses a custom input component. As stated above, I can get that to work, but then there's really no need for redux-form in the first place.
#Jay: I was able to get this to work with a custom component by grabbing the code from the online docs:
class MyCustomInput extends Component {
render() {
const { input: { value, onChange } } = this.props
return (
<div>
<label htmlFor="formContactName" className="col-sm-2 control-label">Name:</label>
<div className="col-sm-10">
<input
id="formContactName"
placeholder="Person to contact"
className="form-control"
type="text"
/>
</div>
</div>
)
}
}
and then, in the redux-form Form code:
<div>
<label>Name</label>
<div>
<Field
name="formContactName"
component={ MyCustomInput }
/>
</div>
</div>
The bootstrap settings via className worked fine using this method.
All your custom props would be passed to the input component, so you can do
const CustomTextField = props => {
const { input, label, type, meta: { touched, error }, ...other } = props
return (
<TextField
label={label}
type={type}
error={!!(touched && error)}
helperText={touched && error}
{ ...input }
{ ...other }
/>
)
}
And then you can pass any props, including className to the CustomTextField
<Field
name="some"
component={CustomTextField}
className="css-applied"
/>
I am trying to use the last version of vuejs with Laravel 5.3 ! The idea I am trying to fulfill is make a component foreach user. So that I have all users listed and foreach one there is a button "edit" , when I click this button I should see the form to update this user.
So this is how I defined the component :
<script>
new Vue({
el: '.view-wrap',
components: {
user-view: {
template: '#user-view',
props: ['user']
}
},
data: {
users: <?php echo json_encode($users); ?>,
},
methods: {
showForm: function(number){
$('div.update-user-'+number).css({'display':'block'});
},
getClassName: function (index) {
return "update-user-"+index;
},
getUpdateUrl: function(id){
return '/users/update/'+id;
},
}
});
This is the template for the "user-view" which take a class name "updateClass" which contains the id of every user (for show/hide purposes), an "updateUrl" which is the url to update the user to bind it with each form action and finally the object user :
<template id="user-view">
<div>
<div class="updateclass">
<form class="form-horizontal" method="PUT" action="updateUrl">
{{ csrf_field() }}
<ul>
<li>
<label for="name"> Name </label>
<input type="text" name="name" :value="user.name">
</li>
<li>
{!! Form::submit('Save', ['class' => 'button-green smaller right']) !!}
</li>
</ul>
{!! Form::close() !!}
</div>
and This is finally how I call the template :
<user-view v-for="user in users" :updateclass="getClassName(user.id)" :user="user" :updateUrl="getUpdateUrl(user.id)"></user-view>
The issue then : it seems that for example [class="updateclass"] doesn't change the value of updateclass with the result of getClassName(user.id) as defined in template call that is binded to. When I try it with [:class="updateclass"] in the template I get : Property or method "updateclass" is not defined on the instance ...
and the same thing applies to all other binded attributes.
The syntax you are using to assign a class dynamically is wrong. from the getClassName method you have to return a object having className like this : {"className: true} , like following
getClassName: function (index) {
var tmp = {}
var className = 'update-user-'+index
tmp[className] = true
return tmp
}
Than you can assign it like following as is in documentation:
<div :class="updateclass"></div>
How can I avoid submitted state (and ng-submitted class & submitted scope property as true boolean) in a form when it is submitted?
Updated:
( function() {
angular
.module( 'app', [] )
.controller( 'SubmitController', SubmitController );
function SubmitController() {
var vm = this;
vm.submit = submit;
function submit( e ) {
console.log( 'Submit!' );
e.stopPropagation();
}
}
} )();
form,
[ng-form] {
padding: 1em;
border: 1px solid black;
}
.ng-submitted {
border: 1px solid red;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.6/angular.min.js"></script>
<div ng-app="app">
<div ng-controller="SubmitController as vm">
<div ng-form>
<form novalidate ng-submit="vm.submit($event)">
<input type="text">
<button type="submit">Submit</button>
</form>
</div>
</div>
</div>
The main objective is that submit event was not fired and not arrived to parent element ([ng-form]).
Simple way to prevent the form to submit, add this type of logical condition with your Angular code:
<form name="formName" novalidate ng-submit="formName.$valid && ANY_LOGIC_CONDITION_LIKE_LOGIN.submit()">
This will help to prevent the form submission
See this.
Use formName.$valid to prevent submit form and add ng-model in your text box.
( function() {
angular.module( 'app', [] )
.controller( 'FormsController', FormsController );
function FormsController() {
var vm = this;
vm.submit = submit;
function submit( e ) {
console.log( 'Submit!' );
e.preventDefault();
e.stopPropagation();
}
}
} )();
[ng-form],
form {
padding: 10px;
border: 1px solid #000;
&.ng-submitted {
border: 1px solid #f00;
}
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app">
<div ng-controller="FormsController as vm">
<div ng-form>
<form name="test" novalidate name="test" ng-submit="test.$valid && vm.submit($event)">
<input type="text" ng-model="vm.test" required>
<button type="submit">Submit</button>
</form>
</div>
</div>
</div>
See #Taylor Buchanan's answer here, once you added the directive. you can simply call it form your JavaScript function.
What you have to do:
Add that directive to your code.
Add a name to your parent-form: ng-form="myForm".
pass your form to your submit function: `vm.submit(myForm)'.
And then modify your function:
function submit( form ) {
console.log( 'Submit!' );
form.$setUnsubmitted();
}
P.S. Since I wasn't sure which one of the forms you wanted to unSubmit, So I assumed you want both. But Obviously, you are getting full controll, so you can modify the code accordingly.
UPDATED
Seeing you code/behavior (after the update)...
Why must you submit the child form? What is the benefit? I'm seeing that you are using novalidate, so browser validation won't work.
Why you don't simply use a ng-click="vm.click(myForm)". You won't have any troubles with parent form, and angularjs won't add/set any $submitted state or ng-selected class, so you can handle this manually as you want passing a simple form instance.
PLUNKER DEMO
HTML
<div ng-form>
<form id="myForm" name="myForm" novalidate >
<label>My Input:</label>
<input type="text" name="myInput" ng-model="vm.item.myInput" required>
<button type="button" ng-click="vm.click(myForm)">Submit</button>
</form>
</div>
CONTROLLER/JS
app.controller('MainCtrl', function($scope) {
vm.click = function (myForm) {
alert('submitted');
//myForm.$valid
//call to server
}
});
If this way fits for your scenario, here you are an awnser... If not, I hope some of others solutions works for you :)
You can trigger $setPristine() method of FormController. You just need to pass the form itself as an arguement to your submit function and trigger its built in method.
e.g.
View:
<form name="MyForm" ng-submit="submitForm(MyForm)">
<button type="submit">Submit</button>
</form>
Controller:
$scope.submitForm = (form) => {
form.$setPristine();
alert("Form was submitted")
}
Here's a working example of the above code snippet.
Update
It doesn't matter if you have nested forms. Propagation doesn't work like you think it does. You should also give your ng-form a name. Then pass it down to the submit function and call its built in $setPristine() method once more.
Check this code chunk:
View:
<div ng-form name="outterForm">
<form name="myForm" ng-submit="vm.submit($event,myForm, outterForm)">
<button type="submit">Submit</button>
</form>
</div>
Controller:
function submit( e, form1, form2 ) {
console.log( 'Submit!' );
e.stopPropagation();
form1.$setPristine();
form2.$setPristine();
}
Here's an the updated example based on your code chunk demonstrating the nested forms case.