Problem getting updated value from child component to the parent component in a Laravel 9 with Vue - laravel

I am using a Laravel 9 application with Vue 3. I have created a fresh install.
I want to create some components that I want to use in a parent component. The first component that I want to create is which will be passed a value (postal code) and the component will format and validate the passed value. The parent component should have access to the updated formatted value.
As a first step, I have created the postal-code-component using the documentation from vuejs.org.
<!-- postalcode.vue -->
<script>
export default {
props: ['modelValue'],
emits: ['update:modelValue'],
computed: {
value: {
get() {
return this.modelValue;
},
set(value) {
this.$emit('update:modelValue', value)
}
}
}
}
</script>
<template>
<input v-model="value" />
</template>
Next, I made a copy of the example-component, that comes with the Laravel Vue installation to create a data element "postalcode" and use as a v-model for ,
<div class="card-body">
<postal-code-input v-model="postalcode" /> {{ postalcode }}
</div>
</template>
<script>
export default {
data(){
return {
postalcode:'l3r3g',
}
},
}
When I run my app, it displays the initial value (l3r3g) in the input box and also the same value at {{postalcode}}. However, when I update the value in the input box, it does not update {{postalcode}} in the parent component. When I inspect the component in vue dev tools, I see that modelValue and computed 'value' are undefined, as shown
I just don't know what is going on. I shall appreciate any help to fix this issue.

I've tried by using watcher instead of computed property because the computed properties made cache and sometime it's set() method created complications in debugging reactivity.
Following snippet works for me:
// PostalCode.vue
<template>
<div class="input-group input-group-merge mb-3">
<input
v-model="postalCode"
type="text" class="form-control"
id="postal_code" name="postal_code"
/>
</div>
</template>
<script>
export default {
name: "PostalCode",
props: {
modelValue: String,
},
data() {
return {
postalCode: null
}
},
watch: {
// watching prop
modelValue: {
handler: function (newValue) {
if (newValue) {
this.postalCode = newValue;
}
},
immediate: true,
},
// watching data() property
postalCode: {
handler: function (newValue, old) {
this.$emit('update:modelValue', newValue)
},
immediate: true
}
}
}
</script>
Usage:
<postal-code v-model="user.postal_code"/>
Your can also place your formatting logic within any watcher also.
Hint/Suggestion:
depending on requirement, if you want to do formatting on props change by parent (for old and new both) then place formatting logic in modelValue watcher.
Note:
Following snippet works perfectly on Vue3

If you’re using v-model to bind a prop like this
<postal-code-input v-model="postalcode" />
The postal-code component should emit ‘input’ and have a value prop. You can use a different prop and event but then you should avoid the v-model binding and just do something like this
<postal-code-input :modelValue="postalcode" #modelValueUpdate=“handleUpdate” />

Related

How to create a simple modal for create & edit functions in vue.js?

I'm trying to create a model to be able to edit existing data? How would I go about doing that?
I'm using a <el-form> to create a new entry in this case I'm creating questions, I want to reuse this for the edit and have the data from the entry added into the form.
This is my form right now
<el-dialog title="Nuevo" :loading="loading" :visible="dialogFormVisible" :visible.sync="dialogFormVisible">
<el-form label-position="top" ref="form" :model="form" :rules="rules">
<el-row>
<el-form-item label="Pregunta" prop="question">
<el-input v-model="form.question"></el-input>
</el-form-item>
</el-row>
<el-row>
<el-col :span="8">
<el-form-item label="Seccion" prop="survey_section_id">
<el-select v-model="form.survey_section_id" placeholder="Selecionar">
<el-option v-for="section in survey_section" :key="section.id" :label="section.title"
:value="section.id">
</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="Tipo de Respuesta" prop="response_type">
<el-select v-model="form.response_type_id" placeholder="Selecionar">
<el-option v-for="type in response_type" :key="type.id" :label="type.type" :value="type.id">
</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="4">
<el-form-item label="Opcional" prop="optional">
<el-switch v-model="form.optional"></el-switch>
</el-form-item>
</el-col>
</el-row>
</el-form>
<span slot="footer">
<el-button type="info" #click="dialogFormVisible = false">Cancelar
</el-button>
<el-button type="primary" :loading="loading" #click="submit('form')">Guardar
</el-button>
</span>
</el-dialog>
What do I need to do to turn this into a modal and use it for my editing as well?
Ask me anything, I can provide any code that I have that is needed.
Thanks
This is a common problem. Of course, a lot of these things come down to personal preference or opinion, but here are a few things to consider:
Usually when solving this problem, you display all of your existing models in some way (ie. a list), with an "edit" button. When I am doing this, I use an object that maps model IDs to their corresponding model objects as the underlying data model for this list. This makes it easy to replace a model with an updated version (models[model.id] = model).
Binding the model to the form directly (eg. :model="the model you want to edit") is usually a bad implementation. This is because any changes that are made during editing would be immediately written to the model. The problem with that is, if your save() function fails, then you have to change everything back. A better implementation is to clone the model, and bind that object to the form.
You can use a computed property for the modal's title/header. I usually have a data property called mode, which will be either "Create" or "Edit", and then I have a computed property that returns mode + ' Model', where 'Model' is the name of the model – in your case, "Pregunta".
Since the create and update functions are usually use different API endpoints (and HTTP methods), your "Save / Update" button needs to call the right one. Again, I use the mode property to handle this (eg. <button #click="mode == 'edit' ? update : save">)
The following code should give you a good starting point for having a modal you can use for both creating & editing models, as well as the basic structure for most CRUD components.
<template>
<div>
<template v-for="model in modelsArray">
... <!-- Display your existing models however you want -->
Edit
</template>
<button #click="create">Create New Model</button>
<el-dialog
:title="modalTitle"
:loading="loading"
:visible.sync="dialogFormVisible"
>
<el-form :model="formModel">
...
</el-form>
<span slot="footer">
<el-button type="info" #click="cancel">Cancelar</el-button>
<el-button
type="primary"
:loading="loading"
#click="mode == 'edit' ? update : save"
>
{{ mode }}
</el-button>
</span>
</el-dialog>
</div>
</template>
<script>
export default {
data() {
mode: '',
dialogFormVisible: false,
loading: false,
formModel: {},
models: { // an object mapping your existing model IDs
1: { id: 1, ...}, // to their corresponding model objects
2: { id: 2, ...},
3: { id: 3, ...},
...
}
},
...,
computed: {
modelsArray() {
return Object.values(this.models);
},
modalTitle() {
return `${ this.mode } Model`;
}
},
methods: {
create() {
this.mode = 'Create';
this.dialogFormVisible = true;
},
edit(model) {
this.mode = 'Edit';
this.formModel = _.cloneDeep(model); // See note below
this.dialogFormVisible = true;
},
save() {
// POST the new model (this.formModel) to your server using your ajax library of choice
// after the ajax call returns, do the following:
this.models.push(the newly created model); // usually returned from the server as part of this ajax call
this.reset();
},
update() {
// PUT the new model (this.formModel) to your server using your ajax library of choice
// After the ajax call returns, replace the updated model in the `models` object, and reset the form:
this.models[this.formModel.id] = _.cloneDeep(this.formModel);
this.reset();
},
reset() {
this.dialogFormVisible = false;
this.loading = false;
this.formModel = {
... // put default form values here
};
}
},
mounted() {
this.reset();
}
}
</script
NOTE: _.cloneDeep is from lodash. If you're not using lodash, you can use one of these methods to clone the model.

Output user first name

I want to get the name of the user to put it on an h1.
What dies this line stand for?
#select="option => selected = option">
I'm using Buefy for the vue components.
<template>
<section>
<div class="field">
<b-switch v-model="keepFirst">
Keep-first <small>(will always have first option pre-selected)</small>
</b-switch>
</div>
<p class="content"><b>Selected:</b> {{ selected }}</p>
<b-field label="Find a name">
<b-autocomplete
v-model="name"
placeholder="e.g. Anne"
:keep-first="keepFirst"
:data="filteredDataObj"
field="user.first_name"
#select="option => selected = option">
</b-autocomplete>
</b-field>
</section>
</template>
<script>
import data from '#/assets/data_test.json'
// Data example
// [{"id":1,"user":{"first_name":"Jesse","last_name":"Simmons"},"date":"2016-10-15 13:43:27","gender":"Male"},
// {"id":2,"user":{"first_name":"John","last_name":"Jacobs"},"date":"2016-12-15 06:00:53","gender":"Male"},
// {"id":3,"user":{"first_name":"Tina","last_name":"Gilbert"},"date":"2016-04-26 06:26:28","gender":"Female"},
// {"id":4,"user":{"first_name":"Clarence","last_name":"Flores"},"date":"2016-04-10 10:28:46","gender":"Male"},
// {"id":5,"user":{"first_name":"Anne","last_name":"Lee"},"date":"2016-12-06 14:38:38","gender":"Female"}]
export default {
data() {
return {
data,
keepFirst: false,
name: '',
selected: null
}
},
computed: {
filteredDataObj() {
return this.data.filter((option) => {
return option.user.first_name
.toString()
.toLowerCase()
.indexOf(this.name.toLowerCase()) >= 0
})
}
}
}
</script>
# is shorthand for v-on:, so it's handling a select event with a function that receives option as a parameter and assigns it to selected.
Since v-model is bound to name, you should be able to do <h1>{{name}}</h1> to have the same value show up in an H1.
The data section has the main variables for your object. name is there. There is also a computed (named filteredDataObj) that should return an array (length of zero or one) with the matching test data. If you want other fields (like id) you would need to look there. Something like
{{filteredDataObj.length ? filteredDataObj.id : ''}}
would give the id if name matched anything in the data set.

How to retrieve an input form value with redux-form and formValueSelector

I am trying to retrieve a single input value and log it. This is an edit form with existing name value prop.
I am not setting state 'name' with field input's value for some reason. I am not sure how to structure the connect part which I think is my problem. In particular, I am not clear on how to write mapStateToProps to include both my non-form state and form state.
partial scaled down code:
import { Field, reduxForm, formValueSelector } from 'redux-form';
import { connect } from 'react-redux';
import { updateStable } from '../../actions/index';
const selector = formValueSelector('myEditForm');
class EditStuff extends Component {
constructor(props) {
super(props);
this.state = {
name: this.props.name
};
}
componentDidMount() {
this.props.initialize({
name: this.props.name || ''
});
}
componentDidUpdate() {
// this.state.name is not getting set from input value
this.props.updateLocalActiveStuffData(this.state.name);
}
render() {
const { handleSubmit } = this.props;
return (
<div>
<form onSubmit={handleSubmit(this.onSubmit.bind(this))}>
<Field
label="Name"
name="name"
class="name"
type="text"
component={renderField}
/>
<button type="submit" className="btn btn-primary">
Submit
</button>
</form>
</div>
</div>
);
}
}
function mapStateToProps(state) {
return {
displayEditForm: state.displayEditForm, //my own non-form related state
name: selector(state, 'name') //form input 'name'
};
}
export default connect(mapStateToProps, { updateStuff })(
reduxForm({
form: 'myEditForm'
})(EditStuff)
);
I think if you can't retrieve the value it's because the name of your form in reduxForm and the formValueSelector name are different 'StableEditForm' != 'myEditForm'
You have more info on selectors here
If you want to initialise your form with values, you should set it from your state in mapStateToProps with the initialValues props, something like this:
function mapStateToProps(state) {
return {
initialValues: state.displayEditForm, // value of your form
};
}
A great exemple here
I hope this can help you

Angular 5 - Reactive forms doesn't validate form on submit

I have a simple form as below:
some.component.html
<form class="example-form" novalidate (ngSubmit)="onSubmit()" autocomplete="off" [formGroup]="testform">
<input type="text" formControlName="name" class="form-control" placeholder="Enter name" required/>
<app-show-errors [control]="claimform.controls.name"></app-show-errors>
<button type="submit" (click)="onSubmit()">Next</button>
</form>
some.component.ts
ngOnInit() {
this.testform= new FormGroup({
name: new FormControl('', { validators: Validators.required})
}, {updateOn: 'submit'});
}
onSubmit() {
if (this.testform.valid) {
alert('saving data');
} else {
this._validationService.validateAllFormFields(this.testform);
}
}
validationService.ts
validateAllFormFields(formGroup: FormGroup) {
Object.keys(formGroup.controls).forEach(field => {
const control = formGroup.get(field);
if (control instanceof FormControl) {
control.markAsTouched({ onlySelf: true });
} else if (control instanceof FormGroup) {
this.validateAllFormFields(control);
}
});
}
Reference
Problem
The form will validate on submit if left blank, but even after filling the value when I check this.testform.valid it returns false. But if I remove updateOn:'submit' on form then it validates on blur of input control and when value is entered it validates form return true. Not sure if updateOn is working fine or not or whether I've implemented this in a proper way. Could someone point me in the right direction.
in your HTML you have two calls to onSubmit() function, from submit button:
<button type="submit" (click)="onSubmit()">Next</button>
and from the form:
<form class="example-form"
ovalidate
(ngSubmit)="onSubmit()"
autocomplete="off"
[formGroup]="testform">
The first call to be triggered is the button's trigger, which actually does nothing in terms of updating your reactive form, since you set FormGroup's option to {updateOn: 'submit'}. The second call to be triggered is the form's trigger, which does actual form update.
Here is FormGroup directive config:
#Directive({
selector: '[formGroup]',
providers: [formDirectiveProvider],
host: {'(submit)': 'onSubmit($event)', '(reset)': 'onReset()'},
exportAs: 'ngForm'
})
as we can see in host property DOM form's submit (triggered by hitting ENTER while focused within form or clicking form's submit button) will call onSubmit() function:
onSubmit($event: Event): boolean {
(this as{submitted: boolean}).submitted = true;
syncPendingControls(this.form, this.directives);
this.ngSubmit.emit($event);
return false;
}
which then will call syncPendingControls() function:
export function syncPendingControls(form: FormGroup, directives: NgControl[]): void {
form._syncPendingControls();
directives.forEach(dir => {
const control = dir.control as FormControl;
if (control.updateOn === 'submit' && control._pendingChange) {
dir.viewToModelUpdate(control._pendingValue);
control._pendingChange = false;
}
});
}
which updates a model at last.
So, in your case, just remove (click)="onSubmit()" from the submit button:
<button type="submit">Next</button>
also you do not need required DOM element property on your input, since you set it using Reactive Forms API validators: Validators.required and since you set your form to novalidate which cancels HTML5 form validation.

vue.js: vue-multiselect, clearing input value after selected

I am working on my project in laravel/vue.js and I decided to use simple vue-multiselect to choose categories from my database. Thing is I want to clear the value from input field (to look for item in list).
My multiselect component:
<multiselect
v-model="value"
placeholder="Find the category"
label="category_name"
:options="categories"
#input="addReceiversFromCategory">
</multiselect>
I try to clear an v-model but it work only on first select after page load (and also it is not a smart way..).
Last thing i try was the :clear-on-select="true" but it works onlu when multiple is true (which I dont want to be true).
I think its simple to do but I didn't find any way in documentation doc
If your v-model is just modeling the value selected, then you need to use that value however you want and reset value to null. I don't really know how your component is set up but it would look something like this:
<template>
<select v-model="value" v-on:change="doSomething()">
<option :value="null">-- Select --</option>
<option value="foo">Foo</option>
<option value="bar">Bar</option>
</select>
</template>
<script>
module.exports = {
data: function(){
return {
value: null
};
},
methods: {
doSomething: function() {
if( this.value ) {
var data = this.value; // now you can work with data
this.value = null; // reset the select to null
}
}
}
}
</script>

Resources