How to update radio-buttons group based on selection - vue.js

I have a working on a Responsive yes/no questionnaire where user can select either yes/no on each of the posed questions.
I want each questionCard component to have a radio-button group (yes/no). The radio-circles should be hidden and only label should be visible. Clicking on the label should select the appropiate option.
ALL THIS WORKS
My issue comes in when I re-size the window! The layout changes appropietly but the radio selection is gone.
Any Ideas how I can keep the selection regardless the window size?
CodeSandbox
App.vue
<template>
<parent-component />
</template>
<script>
import parentComponent from "./components/parentComponent.vue";
export default {
name: "App",
components: {
parentComponent,
},
};
</script>
<style>
</style>
Parent.vue
<template>
<div>
<question-card
v-for="car in objectData.cars.data"
:key="car.id"
:carData="car"
></question-card>
</div>
</template>
<script>
import questionCard from "./questionCard.vue";
export default {
name: "App",
components: {
questionCard,
},
data() {
return {
objectData: {
cars: {
data: [
{
id: 1,
name: "Do You Like Tesla?",
},
{
id: 2,
name: "Do You Like BMW?",
},
{
id: 3,
name: "Do You Like AUDI?",
},
],
},
},
};
},
};
</script>
Questioncard.vue
<template>
<br />
<br />
<div class="hidden sm:block">
<h1>Lage Screen</h1>
<h2 class="font-bold">{{ carData.name }}</h2>
<div class="flex items-center justify-center">
<input
#change="onChange($event)"
type="radio"
:name="carData.id"
:id="carData.id + 'yes'"
/>
<label
:for="carData.id + 'yes'"
class="border text-center border-yellow-500 rounded-2xl w-11/12 py-0.5 hover:bg-red-500 hover:text-white hover:border-green-300"
>
<span>Yes</span>
</label>
</div>
<div class="flex items-center justify-center">
<input
#change="onChange($event)"
type="radio"
:name="carData.id"
:id="carData.id + 'no'"
/>
<label
:for="carData.id + 'no'"
class="border text-center border-yellow-500 rounded-2xl w-11/12 py-0.5 hover:bg-red-500 hover:text-white hover:border-green-300"
>
<span>No</span>
</label>
</div>
</div>
<div class="sm:hidden">
<h1>Small Screen</h1>
<h2 class="font-bold">{{ carData.name }}</h2>
<div class="flex items-center w-1/2 justify-center">
<input
#change="onChange($event)"
type="radio"
:name="carData.id"
:id="carData.id + 'yes'"
/>
<label
:for="carData.id + 'yes'"
class="border text-center border-yellow-500 rounded-2xl w-11/12 py-0.5 hover:bg-red-500 hover:text-white hover:border-green-300"
>
<span>Yes</span>
</label>
</div>
<div class="flex items-center w-1/2 justify-center">
<input
#change="onChange($event)"
type="radio"
:name="carData.id"
:id="carData.id + 'no'"
/>
<label
:for="carData.id + 'no'"
class="border text-center border-yellow-500 rounded-2xl w-11/12 py-0.5 hover:bg-red-500 hover:text-white hover:border-green-300"
>
<span>No</span>
</label>
</div>
</div>
</template>
<script>
export default {
props: ["carData"],
data() {
return {
selected: null,
};
},
methods: {
onChange(event) {
this.selected = event.target.value;
},
},
};
</script>
<style scoped>
input[type="radio"] {
display: none;
}
input[type="radio"]:checked + label {
background-color: #78be20;
}
</style>

I see you are using TailwindCSS. You can control the sizes with w-1/2 sm:w-full on the elements
You don't need different input for the different screen sizes. Like this
<div class="">
<h1>Any size Screen</h1>
<h2 class="font-bold">{{ carData.name }}</h2>
<div class="flex items-center justify-center w-1/2 sm:w-full">
<input
#change="onChange($event)"
type="radio"
:name="carData.id"
:id="carData.id + 'yes'"
/>
<label
:for="carData.id + 'yes'"
class="border text-center border-yellow-500 rounded-2xl w-11/12 py-0.5 hover:bg-red-500 hover:text-white hover:border-green-300"
>
<span>Yes</span>
</label>
</div>
<div class="flex items-center justify-center w-1/2 sm:w-full">
<input
#change="onChange($event)"
type="radio"
:name="carData.id"
:id="carData.id + 'no'"
/>
<label
:for="carData.id + 'no'"
class="border text-center border-yellow-500 rounded-2xl w-11/12 py-0.5 hover:bg-red-500 hover:text-white hover:border-green-300"
>
<span>No</span>
</label>
</div>
</div>

Related

Why I cant parse the {{task}} variable inside the v-for in the task-panel child?

The for loop is working properly. When I add a new task panel shows but the {{task}} variable inside the component is not showing. It must be something with the component template.
<span class="col-span-3 bg-blue-200 p-2">{{task}}</span>
I have left all the code down here maybe is something that I don't see.
Any idea? Thanks
<body>
<div id="app">
<task-input></task-input>
</div>
<script>
let app = Vue.createApp({ });
app.component(
'task-input',
{
template:
`<div class=" container grid grid-cols-4 mb-5 border-2 border-gray-600 center mt-5 mx-auto bg-gray-400 ">
<input id="taskInput" v-model="task" class="bg-white col-span-3 p-3 text-black font-bold" type="text" placeholder="What you will do next" />
<button #click="addTask()" class="text-white font-extrabold uppercase">Add new task</button>
</div>
<div class="container container mx-auto rounded-top-lg">
<div class=" bg-gray-200 border-2 border-gray-600 center column-1 container mx-auto mt-5 mx-auto rounded-top-lg">
<h1 class="font-sans text-2xl text-center text-white bg-gray-500 uppercase font-extrabold px-4 py-4">
{{title}}
</h1>
<ul class="bg-white">
<task-panel v-for="task in tasks"/>
</ul>
</div>
</div>`,
data() {
return {
title:"Here is a nice title",
task: '',
tasks: [],
}
},
components:['task-panel'],
methods:{
addTask(){
this.tasks.push(this.task);
this.task='';
console.log(this.tasks);
}
}
},
);
app.component('task-panel',{
template:
`<li class="grid bg-gray-200 mt-1">
<div class="grid grid-cols-4">
<span class="col-span-3 bg-blue-200 p-2">{{task}}</span>
<span class="col-span-1 text-center self-center uppercase font-bold">test</span>
</div>
<div class="flex justify-end bg-gray-300 p-1">
<button class="bg-blue-500 text-white px-3 py-1 rounded-md m-1 uppercase font-bold">To Do</button>
<button class="bg-green-500 text-white px-3 py-1 rounded-md m-1 uppercase font-bold">Done</button>
<button class="bg-red-500 text-white px-3 py-1 rounded-md m-1 uppercase font-bold">Blocked</button>
</div>
</li>
`,
data() {
return { }
},
props: ['tasks', 'modelValue'],
computed:{
tasks:{
get(){
return this.tasks;
}
}
}
});
app.mount('#app');
</script>
</body>
The v-for is only in the scope of the parent component. The v-for's iterator prop does not get automatically passed into the task-panel.
You need to explicitly bind the iterator prop to task-panel's prop:
👇
<task-panel v-for="task in tasks" :task="task" />
Also, the prop in task-panel should have the same name. It's currently spelled tasks (with an s at the end). The last s should be removed so that it matches what the template is rendering:
// props: ['tasks', ⋯],
props: ['task', ⋯],
<script src="https://unpkg.com/vue#3.2.31/dist/vue.global.js"></script>
<script src="https://cdn.tailwindcss.com"></script>
<div id="app">
<task-input></task-input>
</div>
<script>
let app = Vue.createApp({ });
app.component(
'task-input',
{
template:
`<div class=" container grid grid-cols-4 mb-5 border-2 border-gray-600 center mt-5 mx-auto bg-gray-400 ">
<input id="taskInput" v-model="task" class="bg-white col-span-3 p-3 text-black font-bold" type="text" placeholder="What you will do next" />
<button #click="addTask()" class="text-white font-extrabold uppercase">Add new task</button>
</div>
<div class="container container mx-auto rounded-top-lg">
<div class=" bg-gray-200 border-2 border-gray-600 center column-1 container mx-auto mt-5 mx-auto rounded-top-lg">
<h1 class="font-sans text-2xl text-center text-white bg-gray-500 uppercase font-extrabold px-4 py-4">
{{title}}
</h1>
<ul class="bg-white">
<task-panel v-for="task in tasks" :task="task" />
</ul>
</div>
</div>`,
data() {
return {
title:"Here is a nice title",
task: '',
tasks: [],
}
},
components:['task-panel'],
methods:{
addTask(){
this.tasks.push(this.task);
this.task='';
console.log(this.tasks);
}
}
},
);
app.component('task-panel',{
template:
`<li class="grid bg-gray-200 mt-1">
<div class="grid grid-cols-4">
<span class="col-span-3 bg-blue-200 p-2">{{task}}</span>
<span class="col-span-1 text-center self-center uppercase font-bold">test</span>
</div>
<div class="flex justify-end bg-gray-300 p-1">
<button class="bg-blue-500 text-white px-3 py-1 rounded-md m-1 uppercase font-bold">To Do</button>
<button class="bg-green-500 text-white px-3 py-1 rounded-md m-1 uppercase font-bold">Done</button>
<button class="bg-red-500 text-white px-3 py-1 rounded-md m-1 uppercase font-bold">Blocked</button>
</div>
</li>
`,
data() {
return { }
},
props: ['task', 'modelValue'],
});
app.mount('#app');
</script>

Error in mounted hook: "TypeError: Cannot read properties of undefined (reading 'required')" in vuejs

in my laravel vue application I have following vue modal to upload a document
<template>
<div>
<modal-md :identifier="identifier" :has-multiple-actions="false">
<template slot="modal_title">
<div class="flex justify-between items-center border-b border-certstyle-border px-10 py-3">
<h3 class="text-2xl leading-10 text-certstyle-titles font-bold my-2 flex items-center">
<span class="border-l-2 border-certstyle-orange inline-block h-6 mr-2"></span>
<span>Add document</span>
</h3>
<div class="h-full flex items-center cs--reveal transition-all duration-500"></div>
</div>
</template>
<template slot="modal_content">
<!--Modal content-->
<div class="bg-certstyle-background-light w-full h-full px-10 pt-8 pb-10">
<!--Name-->
<div class="mb-5 relative z-30">
<p class="text-certstyle-titles font-bold text-sm mb-1">Name</p>
<div class="h-12 relative z-10">
<input value="" name="name" id="add_name_input" type="text" class=" form-input w-full relative z-10" >
</div>
<validator identifier="addDocument" name="add_name_input" selector="id" ></validator>
</div>
<div class="flex justify-between">
<!--Completed at-->
<div class="w-1/2 mr-2">
<p class="text-certstyle-titles font-bold text-sm mb-1">Date of issue</p>
<validator-v2 identifier="addDocument" name="issued_at" :rules="{ required: { message: 'Date of issue is required.'}}">
<div class="h-12">
<cs-date-picker
id="minimumDate"
v-model="dateOfIssue"
:default-selection="true"
name="issued_at">
</cs-date-picker>
</div>
</validator-v2>
</div>
<!--Expiration date-->
<div class="w-1/2 ml-2">
<p class="text-certstyle-titles font-bold text-sm mb-1">Expiry date</p>
<validator-v2 identifier="addDocument" name="expires_at" depends-on-name="does_not_expire" :rules="{ required: { message: 'Expiry date is required.'}}">
<div class="h-12">
<cs-date-picker
id="maximumDate"
v-model="expiryDate"
:default-selection="true"
:disable="doesNotExpire"
name="expires_at">
</cs-date-picker>
</div>
</validator-v2>
</div>
</div>
<div class="flex justify-between">
<!--Completed at-->
<div class="w-1/2 mr-2">
<div class="mb-4">
<label class="inline-flex items-center focus:outline-none checkbox--container mt-2">
<input type="checkbox" name="does_not_expire" v-model="doesNotExpire">
<span class="checkbox--checkmark flex items-center justify-center ">
<svg class="icon text-certstyle-orange feather feather-check" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="20 6 9 17 4 12"></polyline>
</svg>
</span>
<span style="bottom: 4px;" class="text-certstyle-text font-normal text-sm relative">This document does not expire</span>
</label>
</div>
</div>
</div>
<!--Upload document-->
<div class="mb-1">
<dashboard-input-label identifier="document" :settings="{help: true, helpDirection: 'left'}">
Upload document
<template slot="helpText">
Maximum filesize is 5 MB.
The default allowed file extensions are: pdf, jpeg and png.
</template>
</dashboard-input-label>
<validator-v2 :identifier="identifier" name="document_file" :rules="{ required: { message: 'Document is required.'}}">
<custom-file-upload :max-upload-file-size="5" name="document_file" v-model="documentFile" ref="documentDocument"></custom-file-upload>
</validator-v2>
</div>
<!--Document number-->
<div class="mb-1">
<p class="inline-block text-certstyle-titles font-bold text-sm mb-1">Document Number</p>
<div class="relative">
<input v-model="documentNumber" name="document_number" id="add_document_number_input" :disabled="doesNotHaveDocumentNumber" type="text" class=" form-input w-full relative z-10" >
<input-annotation identifier="document_number">
Fill in your document number here.
<span class="font-bold"> Note!</span>
Use the document number mentioned on the qualification you upload. Don\'t use your WINDA-ID.
</input-annotation>
<label class="inline-flex items-center focus:outline-none checkbox--container mt-2">
<input type="checkbox" name="no_document_number" v-model="doesNotHaveDocumentNumber">
<span class="checkbox--checkmark flex items-center justify-center">
<svg class="icon text-certstyle-orange feather feather-check" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="20 6 9 17 4 12"></polyline>
</svg>
</span>
<span style="bottom: 4px;" class="text-certstyle-text font-normal text-sm relative">
This document has no document number.
</span>
</label>
</div>
</div>
</div>
</template>
<template slot="modal_actions">
<div class="flex items-center justify-between space-x-4">
<disables-submit-on-errors
:identifier="identifier"
#proceed="addNewDocument"
>
<loading-button ref="submitBtnSave" size="normal">
Save & Close
</loading-button>
</disables-submit-on-errors>
<disables-submit-on-errors
:identifier="identifier"
#proceed="storeDocumentAndAddNew"
>
<loading-button ref="submitBtn" size="normal">
Save & Add new
</loading-button>
</disables-submit-on-errors>
</div>
</template>
</modal-md>
<div class="flex flex-col fixed top-0 right-0 z-50">
<toastr-alert
v-if="message !== ''"
:message="message"
:type="type"
#hidden="resetToastrMessage">
</toastr-alert>
</div>
</div>
</template>
<script>
import ModalMd from "#/components/dashboard/sharedComponents/modals/modal-md";
import CheckboxWarning from "#/components/inputs/checkbox-warning/checkbox-warning";
import CsDataPicker from "#/components/inputs/cs-data-picker/cs-data-picker";
import HasToastrMessage from "#/Mixins/HasToastrMessage";
import Helper from "#/Support/Helper";
var url = window.location.href;
var url_split = url.split("/");
var emp_id_location = url_split.length - 2;
var emp_id = url_split[emp_id_location];
export default {
name: "add-document",
props: ['identifier', 'selectedDocumentId'],
mixins: [HasToastrMessage],
components: {CsDataPicker, CheckboxWarning, ModalMd},
data() {
return {
certificadocumentId: null,
dateOfIssue: null,
expiryDate: null,
documentNumber: null,
doesNotHaveDocumentNumber: false,
doesNotExpire: false,
documentFile: null,
doesOptIn: false,
isAddNew: false,
}
},
watch: {
doesNotHaveDocumentNumber() {
if(this.doesNotHaveDocumentNumber) {
this.documentNumber = null
}
}
},
mounted() {
if(this.selectedDocumentId) {
this.documentId = this.selectedDocumentId
}
},
methods: {
addDocument(documentId) {
this.documentId = documentId
},
storeDocumentAndAddNew() {
this.isAddNew = true
const loader = this.$refs.submitBtn;
this.storeDocument(loader)
},
addNewDocument() {
const loader = this.$refs.submitBtnSave;
this.storeDocument(loader)
},
storeDocument(loader) {
loader.startLoading();
if(!this.documentNumber && !this.doesNotHaveDocumentNumber) {
this.showToastrErrorMessage("Document number is required unless you confirm it does not apply")
loader.stopLoading();
return
}
if(!this.expiryDate && !this.doesNotExpire) {
this.showToastrErrorMessage("Expiry date is required unless you confirm it does not apply")
loader.stopLoading();
return
}
const formData = new FormData()
formData.append('emp_id', emp_id)
formData.append('issued_at', moment(this.dateOfIssue).format('DD-MM-YYYY'))
formData.append('expires_at', moment(this.expiryDate).format('DD-MM-YYYY'))
formData.append('document_number', !this.doesNotHaveDocumentNumber
? this.documentNumber
: null)
if(this.doesNotExpire) {
formData.append('does_not_expire', this.doesNotExpire)
}
formData.append('opt_in', this.doesOptIn)
formData.append('document_file',this.$refs.documentDocument.file)
this.$store.dispatch('addDocument', formData).then((data) => {
this.showToastrSuccessMessage("Document added successfully")
if(!this.isAddNew) {
eventsHub.$emit(`overlay:close:${this.identifier}`)
}
this.reset()
this.$emit('created')
}).catch((error) => {
if(Helper.isset(error.response.data) &&Helper.isset(error.response.data.errors)) {
collect(error.response.data.errors).each((error) => {
this.showToastrErrorMessage(error[0])
});
}
}).finally(() => { loader.stopLoading()})
},
reset() {
this.documentNumber = null
this.doesNotHaveDocumentNumber = false
this.doesNotExpire = false
this.documentFile = null
this.doesOptIn = false
this.isAddNew = false
},
}
}
</script>
<style scoped>
</style>
but when I run this, it gives me an console error saying, Error in mounted hook: "TypeError: Cannot read properties of undefined (reading 'required')"
I'm using the same code for another model and it works fine, and apart from the console error once when I hit the submit, it's just showing me the loading spinner, without saving the data to db
What's I'm doing wrong and how to fix it?

VueJS 3 - How to use draggable nested element but prevent duplicate item

I am trying to create a simple survey builder with Vue3 and Vue Draggable. All is going well until I try to create a multiple choice question. This type of questions has its own sortable list of possible answers. When I add another multiple choice question it then pulls from the same list which makes sense but I have tried to use a v-if to check the parents ID to match the choice ID..
Basically if I add a new choice it adds to the all multiple choice questions, which makes sense, but how to I keep it to the current item I am in?
Any ideas? I know the code is mess, it will be refactored once it works.
<template>
<div class="p-4">
<div class="container mx-auto rounded-md grid grid-cols-12 gap-4 h-full">
<div class="col-span-4 p-3 flex flex-col min-h-screen bg-gray-200 shadow-md rounded-md">
<div class="text-lg font-bold w-full bg-blue-600 text-white rounded-md p-2 mb-4">Builder your survey</div>
<div class="sticky top-4">
<div class="text-lg font-bold">Components</div>
<draggable
class="p-2 rounded-md"
:list="componentsList"
:group="{ name: 'questions', pull: 'clone', put: false, sort: false }"
:clone="cloneItem"
sort: false
#change="log"
item-key="id"
>
<template #item="{ element }">
<div
class="bg-gray-300 p-4 rounded-md mt-2 shadow-sm hover:shadow-md cursor-pointer border border-blue-800 border-dashed"
>
{{ element.name }}
</div>
</template>
</draggable>
</div>
</div>
<div class="col-span-8 flex p-3 flex-col bg-white shadow-md rounded-md">
<div class="text-lg font-bold pt-4">Survey</div>
<draggable
class="w-full h-full border border-blue-400 rounded-md p-2 flex flex-col flex-1"
:list="questionsList"
group="questions"
#change="log"
handle=".handle"
itemKey="name + index"
>
<template #item="{ element, index }">
<div>
<div v-if="element.name == 'Single Line of Text'" class="bg-gray-300 p-2 rounded-md mt-2 mb-2 shadow-lg hover:shadow-md">
<div class="w-full text-sm text-left">{{ element.name }} {{element.id}}</div>
<div class="flex justify-between items-center p-2 bg-gray-200 rounded-md mb-4">
<div class="w-10 font-bold hidden">Q{{ index + 1 }}</div>
<div class="w-full pr-4">
<input
type="text"
class="w-full p-2 bg-transparent flex-grow"
placeholder="Question title here..."
v-model="element.text"
/>
</div>
<div class="flex ">
<div class="cursor-pointer">
<i class="handle las la-arrows-alt la-2x mr-2"></i>
</div>
<div #click="remove(index)">
<i class="las la-trash-alt text-red-800 la-2x cursor-pointer"></i>
</div>
</div>
</div>
<div>
<input
type="text"
class="w-full p-2 rounded-md border border-gray-400"
placeholder="User response will go here"
/>
</div>
<div class="text-left flex justify-between items-center p-2 bg-gray-200 rounded-md mt-2">
<div class="flex items-center">
<div><input type="checkbox" class="mr-2" /></div>
<div>Required?</div>
</div>
</div>
</div>
<!-- START problem area -->
<div v-else-if="element.name == 'Multiple Choice'" class="bg-gray-300 p-2 rounded-md mt-2 mb-2 shadow-lg hover:shadow-md">
<div class="w-full text-sm text-left">{{ element.name }} {{element.id}}</div>
<div class="flex justify-between items-center p-2 bg-gray-200 rounded-md mb-4">
<div class="w-10 font-bold hidden">Q{{ index + 1 }}</div>
<div class="w-full pr-4">
<input
type="text"
class="w-full p-2 bg-transparent flex-grow"
placeholder="Question title here..."
v-model="element.text"
/>
</div>
<div class="flex ">
<div class="cursor-pointer">
<i class="handle las la-arrows-alt la-2x mr-2"></i>
</div>
<div #click="remove(index)">
<i class="las la-trash-alt text-red-800 la-2x cursor-pointer"></i>
</div>
</div>
</div>
<div class="flex items-center ">
<draggable
class="p-2 rounded-md w-full"
:list="multipleChoiceList"
:group="{ name: 'choice', pull: false, put: false, sort: true }"
sort: true
handle=".handle"
#change="log"
item-key="question"
>
<template #item="{ element }">
<div
class="bg-blue-100 p-4 flex items-center justify-start rounded-md mt-2 shadow-sm hover:shadow-md cursor-pointer w-full"
>
<div class="flex items-center flex-grow"
>
<input type="checkbox" class="w-6 h-6">
<input
type="text"
class="p-2 bg-transparent flex-grow"
placeholder="Add choice here"
v-model="element.text"
/>
</div>
<div class="flex ">
<div class="cursor-pointer">
<i class="handle las la-arrows-alt la-1x mr-2"></i>
</div>
<div #click="remove(index)">
<i class="las la-trash-alt text-red-800 la-1x cursor-pointer"></i>
</div>
</div>
</div>
</template>
<template #footer>
<div>
<button class="p-2 bg-blue-300 mt-2 rounded-md" #click="addChoice(element.id)">Add</button>
</div>
</template>
</draggable>
</div>
<div class="text-left flex justify-between items-center p-2 bg-gray-200 rounded-md mt-2">
<div class="flex items-center">
<div><input type="checkbox" class="mr-2" /></div>
<div>Required?</div>
</div>
</div>
</div>
<!-- END problem area -->
<div v-else-if="element.name == 'Open Ended'" class="bg-gray-300 p-2 rounded-md mt-2 mb-2 shadow-lg hover:shadow-md">
<div class="w-full text-sm text-left">{{ element.name }} {{element.id}}</div>
<div class="flex justify-between items-center p-2 bg-gray-200 rounded-md mb-4">
<div class="w-10 font-bold hidden">Q{{ index + 1 }}</div>
<div class="w-full pr-4">
<input
type="text"
class="w-full p-2 bg-transparent flex-grow"
placeholder="Question title here..."
v-model="element.text"
/>
</div>
<div class="flex ">
<div class="cursor-pointer">
<i class="handle las la-arrows-alt la-2x mr-2"></i>
</div>
<div #click="remove(index)">
<i class="las la-trash-alt text-red-800 la-2x cursor-pointer"></i>
</div>
</div>
</div>
<div>
<textarea
class="h-32 w-full w-full p-2 rounded-md border border-gray-400"
></textarea>
</div>
<div class="flex items-center">
<div>Max Length</div>
<div>
<input
type="number"
class="mr-2 w-20 border border-gray-400 p-2 rounded-md ml-2"
/>
</div>
</div>
<div class="text-left flex justify-between items-center p-2 bg-gray-200 rounded-md mt-2">
<div class="flex items-center">
<div><input type="checkbox" class="mr-2" /></div>
<div>Required?</div>
</div>
</div>
</div>
<div v-else-if="element.name == 'Divider'">
<div class="flex items-center">
<div class="flex-grow border-t border-black mx-4"> </div>
<div class="flex ">
<div class="cursor-pointer">
<i class="handle las la-arrows-alt la-2x mr-2"></i>
</div>
<div #click="remove(index)">
<i class="las la-trash-alt text-red-800 la-2x cursor-pointer"></i>
</div>
</div>
</div>
</div>
</div>
</template>
</draggable>
</div>
</div>
</div>
</template>
See Clone method which assigns a random number as the id
<script>
import draggable from "vuedraggable";
export default {
name: "Survey",
components: {
draggable,
},
data() {
return {
drag: false,
componentsList: [
{ name: "Single Line of Text", type: "question", text: "", id: 1 },
{ name: "Multiple Choice", type: "question", text: "", id: 2 },
{ name: "Matrix", type: "question", text: "", id: 3 },
{ name: "Open Ended", type: "question", text: "", id: 4 },
{ name: "Divider", type: "component", id: 9 },
],
questionsList: [],
multipleChoiceList: [
{text: "text A", type:"choice", question:"32"},
{text: "text B", type:"choice", question:"1"},
{text: "text A", type:"choice", question:"2"} ]
};
},
methods: {
onEnd: function(evt) {
console.log(evt);
},
log: function(evt) {
console.log(evt);
},
addChoice(id) {
this.multipleChoiceList.push({ text: "Choice " + id, type: "choice", question:id });
console.log(this.multipleChoiceList);
},
remove(index) {
this.questionsList.splice(index, 1);
},
cloneItem({ id, name, type }) {
return {
name: name,
id: Math.ceil(Math.random()*100),
text: "",
type: type,
};
}
},
mounted() {
// console.log("mounted");
},
};
</script>
https://github.com/SortableJS/Vue.Draggable/issues/687#issuecomment-1153083717
I just answered to a similar question on github.
Not sure would this help as Vue.Draggable and vue.draggable.next is slightly different.

b-table render all the data without respecting the fields

I am building a project using laravel and Vue JS to create a list of companies with pagination etc..
I am using bootstrap-table and the problem the table render all the data without respecting the fields that I am giving as a param. I want to use scope field to show a custom css inside every row. I use the same table with other data it works fine.
This is the component code
<template>
<div class="row">
<div class="col-lg-3 mt-lg-5">
<div class="card bord-rad-5">
<div class="card-header purple-background top-borders">
<h4
class="title-align font-montserrat text-light white-text-color mt-3 position-filter"
>
Filter
</h4>
</div>
<div class="card-body">
<div class="d-flex flex-column">
<div
class="gray-text-color font-weight-bold font-montserrat-regular mb-2"
>
<h5>{{ $t('labels.frontend.filters.companyName') }}</h5>
</div>
<div class="form-group">
<b-form-group class="ml-1">
<b-input-group>
<b-form-input v-model="filter" placeholder="Type to search">
</b-form-input>
<b-input-group class="mr-2">
<b-button
class="mr-left-90 mr-4 mt-2 font-montserrat-regular"
:disabled="!filter"
#click="filter = ''"
>
{{ $t('labels.frontend.companies.clear') }}
</b-button>
</b-input-group>
</b-input-group>
</b-form-group>
</div>
<div
class="gray-text-color font-weight-bold font-montserrat-regular mt-2 mb-2"
>
<h5>{{ $t('labels.frontend.filters.companyType') }}</h5>
</div>
<div class="form-check form-check-inline mt-1 mb-1">
<input
class="form-check-input"
type="checkbox"
id="ac1"
value="AssemblyCompany"
/>
<label
class="form-check-label gray-text-color font-weight-bold font-montserrat-thin"
for="ac1"
>
{{ $t('labels.frontend.filters.dismantling') }}
</label>
</div>
<div class="form-check form-check-inline mt-1 mb-1">
<input
class="form-check-input"
type="checkbox"
id="gd1"
value="garagebusiness"
/>
<label
class="form-check-label gray-text-color font-weight-bold font-montserrat-thin"
for="gd1"
>
{{ $t('labels.frontend.filters.garage') }}
</label>
</div>
<div class="form-check form-check-inline mt-1 mb-1">
<input
class="form-check-input"
type="checkbox"
id="rb1"
value="RevisionCompany"
/>
<label
class="form-check-label gray-text-color font-weight-bold font-montserrat-thin"
for="rb1"
>
{{ $t('labels.frontend.filters.revision') }}
</label>
</div>
</div>
</div>
</div>
</div>
<div class="mt-5 col-lg-9">
<div class="card-header purple-background bord-top-lr-5">
<b-row class="mt-2">
<b-col>
<h4 class="title-align font-montserrat text-light white-text-color">
{{ items.count }} {{ $t('labels.frontend.companies.results') }}
</h4>
</b-col>
<b-form-group label-cols-sm="3" class="mb-2 w-25 mr-4">
<b-form-select v-model="perPage" :options="pageOptions">
</b-form-select>
</b-form-group>
</b-row>
</div>
<div class="card-body white-bg">
<div class="grid-x grid-padding-x m-2 border-0">
<div class="border-0 mb-2">
<b-table
striped
hover
:items="items.data"
:fields="columns"
:filter="filter"
:current-page="currentPage"
:per-page="perPage"
:outlined="outlined"
responsive
>
<template slot="name" class="m-3" slot-scope="item">
<h5 class="title-align font-montserrat" style="color: #5b2557">
<a :href="data.item.url" :title="data.item.name">
{{ data.item.name }}
</a>
</h5>
<div class="row">
<div class="col">
<p
class="gray-text-color font-montserrat-thin font-weight-bold"
>
{{ data.item.street }}
{{ data.item.building_nr }} {{ data.item.postal }}
{{ data.item.city }} {{ data.item.state }}
</p>
</div>
<div class="col ml-lg-5">
<p
class="font-montserrat-thin blue-light-color font-weight-bold"
>
T. {{ data.item.phone }}<br />
<a
:href="data.item.website"
target="_blank"
:title="data.item.name"
class="gray-text-color"
>
{{ $t('labels.frontend.companies.goTo') }}
</a>
</p>
</div>
<div class="col ml-lg-5">
<a
class="font-montserrat-regular"
:href="
$app.route('frontend.companies.show', data.item.slug)
"
style="color: #74aee0"
>
{{ $t('labels.frontend.companies.moreInfo') }} »
</a>
</div>
</div>
<button
class="mb-3 blue-light-bg btn bord-rad-5 white-text-color font-montserrat-regular"
href="#"
>
{{ $t('labels.frontend.companies.stock') }}
</button>
<br />
</template>
</b-table>
<b-row>
<b-col md="6" class="my-1">
<b-pagination
v-model="currentPage"
:total-rows="totalRows"
:per-page="perPage"
class="my-0"
></b-pagination>
</b-col>
</b-row>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'SearchCompaniesTable',
props: {
companyName: {
type: String,
required: false,
default: () => ''
}
},
data() {
return {
filter: null,
totalRows: 1,
currentPage: 1,
perPage: 10,
pageOptions: [5, 10, 25],
outlined: true,
columns: [
{
field: 'name',
label: 'Name'
}
],
items: []
}
},
computed: {
rows() {
return Object.keys(this.items).length
}
},
mounted() {
axios.get('/companies/search').then(response => {
this.items = response.data
this.totalRows = this.items.count
console.log(this.fields)
})
}
}
</script>
<style>
.hidden_header {
display: none;
}
thead {
border: none !important;
}
tbody {
border: none !important;
border-color: white;
}
table {
border: none !important;
}
th {
border: none !important;
}
tr {
border: none !important;
}
td {
border: none !important;
}
.mr-left-90 {
margin-left: 68px;
}
.position-filter {
position: relative;
top: -8px;
}
</style>
could you please help me to find a solution for that ?
Here you can find the code for the component
<template>
<div class="row">
<div class="col-lg-3 mt-lg-5">
<div class="card bord-rad-5">
<div class="card-header purple-background top-borders">
<h4
class="title-align font-montserrat text-light white-text-color mt-3 position-filter"
>
Filter
</h4>
</div>
<div class="card-body">
<div class="d-flex flex-column">
<div
class="gray-text-color font-weight-bold font-montserrat-regular mb-2"
>
<h5>{{ $t('labels.frontend.filters.companyName') }}</h5>
</div>
<div class="form-group">
<b-form-group class="ml-1">
<b-input-group>
<b-form-input v-model="filter" placeholder="Type to search">
</b-form-input>
<b-input-group class="mr-2">
<b-button
class="mr-left-90 mr-4 mt-2 font-montserrat-regular"
:disabled="!filter"
#click="filter = ''"
>
{{ $t('labels.frontend.companies.clear') }}
</b-button>
</b-input-group>
</b-input-group>
</b-form-group>
</div>
<div
class="gray-text-color font-weight-bold font-montserrat-regular mt-2 mb-2"
>
<h5>{{ $t('labels.frontend.filters.companyType') }}</h5>
</div>
<div class="form-check form-check-inline mt-1 mb-1">
<input
class="form-check-input"
type="checkbox"
id="ac1"
value="AssemblyCompany"
/>
<label
class="form-check-label gray-text-color font-weight-bold font-montserrat-thin"
for="ac1"
>
{{ $t('labels.frontend.filters.dismantling') }}
</label>
</div>
<div class="form-check form-check-inline mt-1 mb-1">
<input
class="form-check-input"
type="checkbox"
id="gd1"
value="garagebusiness"
/>
<label
class="form-check-label gray-text-color font-weight-bold font-montserrat-thin"
for="gd1"
>
{{ $t('labels.frontend.filters.garage') }}
</label>
</div>
<div class="form-check form-check-inline mt-1 mb-1">
<input
class="form-check-input"
type="checkbox"
id="rb1"
value="RevisionCompany"
/>
<label
class="form-check-label gray-text-color font-weight-bold font-montserrat-thin"
for="rb1"
>
{{ $t('labels.frontend.filters.revision') }}
</label>
</div>
</div>
</div>
</div>
</div>
<div class="mt-5 col-lg-9">
<div class="card-header purple-background bord-top-lr-5">
<b-row class="mt-2">
<b-col>
<h4 class="title-align font-montserrat text-light white-text-color">
{{ items.count }} {{ $t('labels.frontend.companies.results') }}
</h4>
</b-col>
<b-form-group label-cols-sm="3" class="mb-2 w-25 mr-4">
<b-form-select v-model="perPage" :options="pageOptions">
</b-form-select>
</b-form-group>
</b-row>
</div>
<div class="card-body white-bg">
<div class="grid-x grid-padding-x m-2 border-0">
<div class="border-0 mb-2">
<b-table
striped
hover
:items="items.data"
:fields="columns"
:filter="filter"
:current-page="currentPage"
:per-page="perPage"
:outlined="outlined"
responsive
>
<template slot="name" class="m-3" slot-scope="item">
<h5 class="title-align font-montserrat" style="color: #5b2557">
<a :href="data.item.url" :title="data.item.name">
{{ data.item.name }}
</a>
</h5>
<div class="row">
<div class="col">
<p
class="gray-text-color font-montserrat-thin font-weight-bold"
>
{{ data.item.street }}
{{ data.item.building_nr }} {{ data.item.postal }}
{{ data.item.city }} {{ data.item.state }}
</p>
</div>
<div class="col ml-lg-5">
<p
class="font-montserrat-thin blue-light-color font-weight-bold"
>
T. {{ data.item.phone }}<br />
<a
:href="data.item.website"
target="_blank"
:title="data.item.name"
class="gray-text-color"
>
{{ $t('labels.frontend.companies.goTo') }}
</a>
</p>
</div>
<div class="col ml-lg-5">
<a
class="font-montserrat-regular"
:href="
$app.route('frontend.companies.show', data.item.slug)
"
style="color: #74aee0"
>
{{ $t('labels.frontend.companies.moreInfo') }} »
</a>
</div>
</div>
<button
class="mb-3 blue-light-bg btn bord-rad-5 white-text-color font-montserrat-regular"
href="#"
>
{{ $t('labels.frontend.companies.stock') }}
</button>
<br />
</template>
</b-table>
<b-row>
<b-col md="6" class="my-1">
<b-pagination
v-model="currentPage"
:total-rows="totalRows"
:per-page="perPage"
class="my-0"
></b-pagination>
</b-col>
</b-row>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'SearchCompaniesTable',
props: {
companyName: {
type: String,
required: false,
default: () => ''
}
},
data() {
return {
filter: null,
totalRows: 1,
currentPage: 1,
perPage: 10,
pageOptions: [5, 10, 25],
outlined: true,
columns: [
{
field: 'name',
label: 'Name'
}
],
items: []
}
},
computed: {
rows() {
return Object.keys(this.items).length
}
},
mounted() {
axios.get('/companies/search').then(response => {
this.items = response.data
this.totalRows = this.items.count
console.log(this.fields)
})
}
}
</script>
What mistake i am doing and thank you for answering
In your first code snippet your style is not using the scoped attribute, which may mean the rest of your project is doing he same thing. This could mean that your CSS is being overwritten at the global level, If you inspect the element after render can you see if your CSS is being overwritten somewhere.
Also, if you happen to be using SCSS the bootstrap-vue library supports it very well and I have found it to be easier to adjust the bootstrap css easier.
I fixed the problem by changing the code inside the template tag from <template slot="name" slot-scope="item"> to <template v-slot:cell(name)="data">

Is there a way to get the data from multiple selects as array?

I'm currently stuck on a task on VueJS. Basically I'm trying to create an array from multiple select inside a form to get all the data as an array or Object to process it further. I just searched but got no results or an idea on how to solve my issue.
Parent Component
<template>
<div id="product" class="mt-12">
<div class="flex flex-wrap mb-4">
<div class="w-1/2 pr-12">
<img src="https://placehold.it/800" alt="">
</div>
<div class="w-1/2">
<div class="flex justify-between content-center h-12 mb-4">
<div class="text-gray-700 py-2">
<h2 v-if="product.brand" class="leading-none font-bold text-sm">{{ product.brand }}</h2>
<h1 class="text-xl leading-snug">{{ product.name }}</h1>
</div>
<div class="text-gray-700 py-2">
<p class="text-xl font-bold leading-none">{{ product.price }}</p>
<p class="text-xs text-right leading-none">Inkl. 19% MwSt.</p>
</div>
</div>
<div class="divider bg-gray-400 mt-4 mb-4" />
<div id="variations">
<form action="">
<ProductVariation v-for="(variations, type) in product.variations" :type="type" :variations="variations" :key="type" v-model="form.variation"/>
<div class="flex w-full">
<button class="w-full leading-none bg-blue-500 hover:bg-blue-600 text-white py-2 rounded" type="submit">Add to cart</button>
</div>
</form>
</div>
</div>
</div>
</div>
</template>
<script>
import ProductVariation from '#/components/products/ProductVariation'
export default {
data () {
return {
product: null,
form: {
variation: null
}
}
},
components: {
ProductVariation
},
async asyncData({ params, app }) {
let response = await app.$axios.$get(`products/${params.slug}`)
return {
product: response.data
}
}
}
</script>
ProductVariation Component
<template>
<div class="flex mb-4">
<div class="w-full">
<label class="block text-gray-700 w-full text-sm font-bold">{{ type }}</label>
<select class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight" :name="type">
<option value="0">Please select</option>
<option v-for="variation in variations" :key="variation.id" :value="variation.id">{{ variation.name }} <span>( +{{ variation.price }} )</span></option>
</select>
</div>
</div>
</template>
<script>
export default {
props: {
type: {
required: true,
type: String
},
variations: {
required: true,
type: Array
}
},
}
</script>