Get all values of custom input inside v-for vuejs - laravel

I created a vuejs custom input that I wanted to use to dynamically display inputs by using props within the custom input. I haven't shown them here because it would be too long.
By clicking on the submit button, which is also part of the custom input, I wanna be able to get the values of each input, but for some reason, I have only been able to get the value of the last input.
What am I doing wrong?
Custom input:
<template>
<div class="form-input">
<label :label="label" :for="name" v-if="label && type !='submit' ">{{label}} <span v-if="required">*</span></label>
<a v-if="multiple" href="#" class="btn">Upload</a>
<input v-model="inputVal" :multiple="multiple" v-if="type != 'textarea' && type != 'submit'" class="form-control" :required="required" :class="classes" :type="type" :name="name" :placeholder="placeHolder">
<textarea v-model="inputVal" :multiple="multiple" v-else-if="type != 'submit'" class="form-control" :required="required" :class="classes" :type="type" :name="name" :placeholder="placeHolder"></textarea>
<button :multiple="multiple" :name="name" v-else type="submit">{{label}}</button>
</div>
</template>
<script>
export default {
name: "Input",
data () {
return {
inputVal: null
}
},
watch: {
inputVal: {
handler: function(newValue, oldValue) {
this.$emit('input', newValue);
},
deep: true,
}
}
}
</script>
Form where custom input is used:
<template>
<div class="form container">
<form v-on:submit.prevent="sendMail" method="post" class="d-flex row shadow bg-dark border-right border-dark">
<h3 class="col-12">Contact me</h3>
<Input v-model="formInput" v-for="input in inputs" v-bind:key="input.name" :label="input.label" :multiple="input.multiple" :type="input.type" :name="input.name" :class="input.classes" :required="input.required"></Input>
</form>
</div>
</template>
<script>
import Input from "../components/Input";
export default {
name: "Contact",
components: {Input},
data() {
return {
formInput: null,
}
},
methods: {
sendMail () {
console.log(this.formInput);
}
}
}
</script>

The issue I see in your code is, you are using only one variable "formInput" ( in case of Contact component ) and "inputVal" ( in case of Input component ) but you have number of input fields from where you need data right.
The simplest way to deal with these kind of cases is to create a datastructure and loop through that.
For eg.
// Contact component ( i am making it simple to make you understand the scenario )
<template>
<div class="form container">
<form v-on:submit.prevent="sendMail" method="post" class="d-flex row shadow bg-dark border-right border-dark">
<h3 class="col-12">Contact me</h3>
<!-- we are looping through our data structure and binding each inputVal to this input -->
<input v-for="(input, i) in formInputs" :key="i" v-model="input.inputVal">
</form>
</div>
</template>
<script>
import Input from "../components/Input";
export default {
name: "Contact",
components: {Input},
data() {
return {
formInputs: [
{inputVal: ''},
{inputVal: ''},
{inputVal: ''},
],
}
},
methods: {
sendMail () {
// You can extract the data from formInputs as per your need
}
}
}
</script>

Related

How to style vue-datetime picker in laravel vue?

I am trying to use the vue-datetime component inside an other component here is the vue
<template>
<div class="container p-3 bg-gray-100 flex flex-col">
<div>{{ current_post.title }}</div>
<div v-if="edit == false" id="mode-diplay">
<div v-html="current_post.body" class="text-gray-700"></div>
</div>
<div v-else id="mode-edit">
<form class="flex flex-col" action>
<datetime type="datetime" v-model="datetime"></datetime>
<input type="text" val="details.body" />
<textarea
class="markdown"
name="editor"
id="editor"
v-model="raw_post.body"
rows="50"
></textarea>
</form>
</div>
</div>
</template>
<script>
import { Datetime } from "vue-datetime";
export default {
name: "PostDetails",
props: ["current_post", "raw_post", "edit"],
components: {
datetime: Datetime,
},
data() {
return {
datetime:this.raw_post.beg_date
};
},
};
</script>
The vue-date time is displayed but when I click into the field input, the picker shows up vertically with very huge images of the < char.
I followed the example given here https://mariomka.github.io/vue-datetime/ and I don't know what else to do. The trouble is the same on Firefox and Chromium.
You need a specific loader for CSS files
Import in your parent component the style for the datepicker
import 'vue-datetime/dist/vue-datetime.css';
import { Datetime } from "vue-datetime";
import 'vue-datetime/dist/vue-datetime.css';
export default {
name: "PostDetails",
props: ["current_post", "raw_post", "edit"],
components: {
datetime: Datetime,
},
data() {
return {
datetime:this.raw_post.beg_date
};
},
};

How to use the selection from my vue component in my blade file

I have build a vue-component which takes a list of objects and two criteria lists as props. The select lists are passed to two select inputs in the template. When either one is changed the list is filtered using the selected criteria. How do I get access to this filtered list in my blade file?
Here is my code.
Blade file:
<subjecttable-select :data-subjecttable="{{$subjectslessons->toJson()}}"
:data-departments="{{$departments->toJson()}}"
:data-subjects="{{$subjects->toJson()}}" #input="selectedsubjects">
</subjecttable-select>
#{{selectedsubjects}}
Vue-component
<template>
<div >
<div class="row mb-2 mx-2">
<form class="form-inline justify-content-between" >
<div class="form-group row mb-1">
<label class="col-auto col-form-label text-md-left" for="department">Leerjaar</label>
<div class="col-auto">
<select id= "department" class="form-control form-control-sm custom-select" v-model="department" #change="select()">
<option v-for="department_item in dataDepartments" :value="department_item['id']">
{{department_item["name"]}}
</option>
</select>
</div>
</div>
<div class="form-group row">
<label class="col-auto col-form-label text-md-leftt" for="subject">Vak</label>
<div class="col-auto">
<select id="subject" class="form-control form-control-sm custom-select" v-model="subject" #change="select()">
<option v-for="subject_item in dataSubjects" :value="subject_item['id']">
{{subject_item["description"]}}
</option>
</select>
</div>
</div>
<button class="btn-outline-primary" #click="reset()">Reset</button>
</form>
</div>
</div>
</template>
<script>
export default {
name:"subjecttable-select",
props: {
dataDepartments: { type: Array, required: true },
dataSubjects:{ type: Array, required: true},
dataSubjecttable: {type: Array, required: true },
value:{},
},
data() {
return {
selected:this.dataSubjecttable,
subject:"",
department:"",
}
},
methods:{
select(){
var item;
console.log(this.subject);
this.selected=[];
for(item of this.dataSubjecttable){
if(//get the subbejctlessons who are in the selected department
(this.department==="" || item["department_id"]===this.department) &&
//whose subject is the selected subject
(this.subject===""|| item["subject_id"]===this.subject)
){
this.selected.push(item);
}
}
this.$emit('input',this.selected);
},
reset(){
this.value = this.dataSubjecttable;
this.subject = "";
this.department="";
},
},
created(){
this.select();
},
filters: {
dateFilter(value){
var isDate = !isNaN(Date.parse(value));
if (isDate ){
var dateValue=new Date(value);
return dateValue.toLocaleDateString();
}
else{
return value;
}
}
},
};
</script>
app.js
Vue.component('subjecttable-select', require('./components/SubjectSelection.vue').default);
/**
* Next, we will create a fresh Vue application instance and attach it to
* the page. Then, you may begin adding components to this application
* or customize the JavaScript scaffolding to fit your unique needs.
*/
const app = new Vue({
el: '#app',
});
As you can see I emit an input event in my component but I have trouble accessing the value in the blade file.
Ideally, I think you want to do is load your SelecttableSelect component within another parent Vue component. This will allow you to trickle your events down to the parent component and use the data much more easily.
I have not tested this, but it's along the lines of what I would do to get started. You would need to format the output to your needs.
Lessons.vue
<template>
<div>
<!-- YOUR SELECT IS NOW DEFINED HERE, NOT IN THE BLADE FILE -->
<!-- Select -->
<subjecttable-select :data-subjecttable="dataSubjecttable"
:data-departments="dataDepartments"
:data-subjects="dataSubjects"
#input="updateResults"
>
</subjecttable-select>
<!-- End Select -->
<!-- Department -->
<div>
<h1>Department</h1>
<div v-if="results.department_id > 0">
<ul>
<li v-for="(value, index) in findElementById(dataDepartments, results.department_id)">
{{ index }} : {{ value }}
</li>
</ul>
</div>
</div>
<!-- End Department -->
<!-- Subject -->
<div>
<h1>Subject</h1>
<div v-if="results.subject_id > 0">
<ul>
<li v-for="(value, index) in findElementById(dataSubjects, results.subject_id)">
{{ index }} : {{ value }}
</li>
</ul>
</div>
</div>
<!-- End Subject -->
</div>
</template>
<script>
// import your select component
import SubjecttableSelect from './SubjecttableSelect';
export default {
components: {
// register the component
SubjecttableSelect,
},
props: {
dataDepartments: { type: Array, required: true },
dataSubjects:{ type: Array, required: true},
dataSubjecttable: {type: Array, required: true },
},
name: "Lessons",
data() {
return {
results: {
subject_id: 0,
department_id: 0,
},
}
},
methods: {
updateResults(data) {
this.results = data;
},
findElementById(element, id) {
return element.find(el => el.id === id);
}
},
}
</script>
<style scoped>
</style>
app.js
// register the new component
Vue.component('lessons', require('./components/Lessons.vue').default);
// subjecttable-select can now be imported within lessons
const app = new Vue({
el: '#app',
});
your.blade.php (please note the single quotes)
<lessons :data-subjecttable='#json($subjectslessons)'
:data-departments='#json($departments)'
:data-subjects='#json($subjects)'>
</lessons>

How to empty input fields from a pop-up window after submitting - Vue - laravel?

My page exist of a table where I can add new rows. If you want to add a new row a pop-up window appear where the new values can be added.
This new data is then saved to the database after submitting. If I again want to add a new row the input fields, they should be cleared.
The method I use, is working but isn't very clear.
Note: My code shows only a part of the input fields, to make it more clear. My pop-up window actually contains 20 input fields.
I would like to clear them all at once instead of clearing them one by one (like I am doing now).
Because I am already doing this for defining the v-model, pushing the new data to the database directly on the page and via post axios request.
Is there a cleaner way to do this?
Thanks for any input you could give me.
This is my code:
html part
<div class="col-2 md-2">
<button class="btn btn-success btn-sx" #click="showModal('add')">Add New</button>
<b-modal :ref="'add'" hide-footer title="Add new" size="lg">
<div class="row" >
<div class="col-4">
<b-form-group label="Category">
<b-form-input type="text" v-model="newCategory"></b-form-input>
</b-form-group>
</div>
<div class="col-4">
<b-form-group label="Name">
<b-form-input type="text" v-model="newName" placeholder="cd4"></b-form-input>
</b-form-group>
</div>
<div class="col-4">
<b-form-group label="Amount">
<b-form-input type="number" v-model="newAmount" ></b-form-input>
</b-form-group>
</div>
</div>
<div class="row" >
<div class="col-8">
</div>
<div class="col-4">
<div class="mt-2">
<b-button #click="hideModal('add')">Close</b-button>
<b-button #click="storeAntibody(antibodies.item)" variant="success">Save New Antibody</b-button>
</div>
</div>
</div>
</b-modal>
</div>
js part
<script>
import { async } from 'q';
export default {
props: ['speciedata'],
data() {
return {
species: this.speciedata,
newCategory: '',
newName: '',
newAmount:'',
}
},
computed: {
},
mounted () {
},
methods: {
showModal: function() {
this.$refs["add"].show()
},
hideModal: function(id, expId) {
this.$refs['add'].hide()
},
addRow: function(){
this.species.push({
category: this.newCategory,
name: this.newName,
amount: this.newAmount,
})
},
storeSpecie: async function() {
axios.post('/specie/store', {
category: this.newCategory,
name: this.newName,
amount: this.newAmount,
})
.then(this.addRow())
// Clear input
.then(
this.newName = '',
this.newCategory = '',
this.newAmount = '',
)
.then(this.hideModal('add'))
},
}
}
</script>
in your data of vuejs app , you have to set one object for displaying modal data like modalData then to reset data you can create one function and set default value by checking type of value using loop through modalData object keys
var app = new Vue({
el: '#app',
data: {
message:"Hi there",
modalData:{
key1:"value1",
key2:"value2",
key3:"value3",
key4:5,
key5:true,
key6:"val6"
}
},
methods: {
resetModalData: function(){
let stringDefault="";
let numberDefault=0;
let booleanDefault=false;
Object.keys(this.modalData).forEach(key => {
if(typeof(this.modalData[key])==="number"){
this.modalData[key]=numberDefault;
}else if(typeof(this.modalData[key])==="boolean") {
this.modalData[key]=booleanDefault;
}else{
// default type string
this.modalData[key]=stringDefault;
}
});
}
}
})
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<div id="app">
{{modalData}}
<br/>
<button #click="resetModalData">Reset Modal Data</button>
</div>
update : in your case :
data:{
species: this.speciedata,
modalData:{
newCategory: '',
newName: '',
newAmount:''
}
},
and after storing data :
storeSpecie: async function() {
axios.post('/specie/store', {
category: this.newCategory,
name: this.newName,
amount: this.newAmount,
})
.then(()=>{
this.addRow();
this.resetModalData();
this.hideModal('add')
}
},
In native Javascript you get the reset() method.
Here is how it is used :
document.getElementById("myForm").reset();
It will clear every input in the form.

Displaying error messages in vuetify when validating nested object with vuelidate

I am using vuelidate to validate my form input and display the error messages using vuetifyjs. I managed to do the basic object validation and am able to show the error messages.
However I'm having issues with displaying the error messages when I validate a collection.
ISSUE
Example data structure:
contact: {
websites: [
{
url: 'http://www.something.com',
label: 'Website',
}
]
}
Example validation:
validations: {
websites: {
$each: {
url: {
url,
}
}
},
}
Example template:
<template v-for="(website, index) in websites">
<v-layout row :key="`website${index}`">
<v-flex xs12 sm9 class="pr-3">
<v-text-field
label="Website"
:value="website.url"
#input="$v.websites.$touch()"
#blur="$v.websites.$touch()"
:error-messages="websiteErrors"
></v-text-field>
</v-flex>
</v-layout>
</template>
Example computed error message:
websiteErrors() {
console.log('websites',this.$v.websites) // contains $each
const errors = []
if (!this.$v.websites.$dirty) {
return errors
}
// Issue is that all of them show must be valid, even if they are valid.
// Validation is basically broken.
// I also tried this.$v.websites.$each.url
!this.$v.websites.url && errors.push('Must be valid url')
return errors
},
Example method (Update, also tried method with passing index):
websiteErrors(index) {
console.log('this.$v.entity.websites', this.$v.entity.websites.$each.$iter, this.$v.entity.websites.$each.$iter[index], this.$v.entity.websites.minLength, this.$v.entity.websites.$each.$iter[index].url)
const errors = []
if (!this.$v.entity.websites.$dirty) {
return errors
}
!this.$v.entity.websites.$each.$iter[index].url && errors.push('Must be valid url')
return errors
},
However when I do this, it will always be true and therefore never show the error.
EXPECTED
I would like to have the same example working as seen in vuelidate sub-collection validation The difference is instead of looping in the template I would like to generate the message programmatically.
REFERENCE
Example provided by vuelidate:
import { required, minLength } from 'vuelidate/lib/validators'
export default {
data() {
return {
people: [
{
name: 'John'
},
{
name: ''
}
]
}
},
validations: {
people: {
required,
minLength: minLength(3),
$each: {
name: {
required,
minLength: minLength(2)
}
}
}
}
}
<div>
<div v-for="(v, index) in $v.people.$each.$iter">
<div class="form-group" :class="{ 'form-group--error': v.$error }">
<label class="form__label">Name for {{ index }}</label>
<input class="form__input" v-model.trim="v.name.$model"/>
</div>
<div class="error" v-if="!v.name.required">Name is required.</div>
<div class="error" v-if="!v.name.minLength">Name must have at least {{ v.name.$params.minLength.min }} letters.</div>
</div>
<div>
<button class="button" #click="people.push({name: ''})">Add</button>
<button class="button" #click="people.pop()">Remove</button>
</div>
<div class="form-group" :class="{ 'form-group--error': $v.people.$error }"></div>
<div class="error" v-if="!$v.people.minLength">List must have at least {{ $v.people.$params.minLength.min }} elements.</div>
<div class="error" v-else-if="!$v.people.required">List must not be empty.</div>
<div class="error" v-else-if="$v.people.$error">List is invalid.</div>
<button class="button" #click="$v.people.$touch">$touch</button>
<button class="button" #click="$v.people.$reset">$reset</button>
<tree-view :data="$v.people" :options="{rootObjectKey: '$v.people', maxDepth: 2}"></tree-view>
</div>
WHAT WENT WRONG
Shared computed property which causes the issue where all siblings share the same error message. (Solved by writing it inline)
Reactivity not triggered due to the array not being updated in a "reactive way" (Make sure to take note of Change Detection Caveats in this case instead of updating the index: I copy the array, replace item and then set the whole array.)
Wrong place to use vuelidate $each.$iter: Moved it from computed error message to v-for
SOLUTION
This is how to do it (Fixes 1 & 3):
<template v-for="(v, index) in $v.websites.$each.$iter">
<v-layout row :key="`website${index}`">
<v-flex xs12 sm9 class="pr-3">
<v-text-field
label="Website"
:value="v.$model.url"
#input="$v.websites.$touch()"
#blur="$v.websites.$touch()"
:error-messages="v.$dirty && !v.required ? ['This field is required'] : !v.url ? ['Must be a valid url'] : []"
/>
</v-flex>
</v-layout>
</template>
This is how my update method is now (Fixes 2):
updateWebsite(index, $event) {
const websites = [...this.websites];
websites[index] = $event;
this.updateVuex(`websites`, websites)
this.$v.websites.$touch()
},
Originally it was like this:
updateWebsite(index, $event) {
this.updateVuex(`websites[${index}]`, $event)
this.$v.websites.$touch()
},
ALTERNATIVE
There is another option, which is to wrap in this case website inside a component. That way you can keep the computed error message as it will not be shared.

How can I dynamically nest vue components?

I want to add one component inside other when user clicks a button. but how can we render the component in the virtual dom.
I tried using v-html but its not working.
Whats the best way to solve this issue?
export default {
data(){
return{
elements : {
hotel : '<hotel-form></hotel-form>'
},
}
},
methods:{
addHotel(){
console.log('add');
}
}
}
<template>
<div class="container" style="margin-top:300px;">
<div class="row" id="mainform">
<div v-for="item in elements">
<div v-html="item"></div>
</div>
</div>
<button #click="addHotel()">add hotel</button>
</div>
</template>
I would bind an array (hotels) to a <hotel-form> component tag via v-for. This way, no hotel-form components will be initially rendered, and then you can push an object (with any data to want bound to the hotel-form component) to the hotels array and the DOM will automatically render a new corresponding hotel-form component.
Here's a simple example:
Vue.component('hotel-form', {
template: '#hotel-form',
props: { id: Number, name: String },
});
new Vue({
el: '#app',
data() {
return { hotels: [], count: 0 }
},
methods: {
addHotel() {
this.hotels.push({ name: 'foo', id: this.count++ })
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.13/vue.min.js"></script>
<div id="app">
<div id="mainform">
<hotel-form v-for="hotel in hotels" :key="hotel.id" v-bind="hotel">
</hotel-form>
</div>
<button #click="addHotel">add hotel</button>
</div>
<template id="hotel-form">
<div>
<h4>Hotel Form #{{ id }}</h4>
<div>{{ name }}</div>
</div>
</template>

Resources