vuejs: ref tag in dom element not work when use with object attribute - vue.js

With this code:
<script setup lang="ts">
import { ref } from "vue"
const input1 = ref(null)
const group = {
input2: ref(null),
input3: ref(null),
}
function main() {
console.log("input1", input1?.value?.value)
console.log("input1", group.input3?.value?.value)
console.log("input3", group.input3?.value?.value)
document.getElementsByTagName("textarea")[0].value = `
input1: ${input1?.value?.value}
input2: ${group.input2?.value?.value}
input3: ${group.input3?.value?.value}
`.trim()
}
</script>
<template>
<input ref="input1" type="text" value="1" />
<input ref="group.input2" type="text" value="2" />
<input ref="group.input3" type="text" value="3" />
<button #click="main">click</button>
<p>
<textarea cols="30" rows="6"></textarea>
</p>
</template>
when you do click, you get input1 in input1.value, but yo don't get input2 and input3 in group.input2.value and group.input3.value, you get null instead.
Why input1.value works but group.input2.value and group.input3.value are always null?
How can I defined a one single object with one attribute for each input?
What I am really want to achieve is a single object that represent all inputs in a form.

Related

Pass data from component

I'm fairly new to Vue and I'm trying to pass data from a component to a view. I'm not sure if I'm using props right. I have a dialog and when I save, I want to insert the data to the database. I also want to reuse the addCustomer() function that's why I didn't place the function in the component.
pages/customers.vue
<template>
<div>
<div class="items-center justify-between md:flex">
<Heading
title="Customers"
desc="The list of customers or companies you work with."
/>
<button #click="openModal" class="btn-primary">Add New Customer</button>
</div>
<CustomerList class="mt-4" :customers="customers" />
</div>
<CustomerDialog
:is-open="isOpen"
:close-modal="closeModal"
:open-modal="openModal"
:name="name"
:address="address"
:email="email"
:add-customer="addCustomer"
/>
</template>
<script setup>
const client = useSupabaseClient();
const name = ref("");
const address = ref("");
const email = ref("");
const isOpen = ref(false);
function closeModal() {
isOpen.value = false;
}
function openModal() {
isOpen.value = true;
}
const { data: customers } = await useAsyncData("customers", async () => {
const { data } = await client.from("customers").select("*");
return data;
});
async function addCustomer() {
if (name.value == "" || address.value == "" || email.value == "") return;
const { data } = await client.from("customers").upsert({
name: name.value,
address: address.value,
email: email.value,
});
customers.value.push(data[0]);
name.value = "";
address.value = "";
email.value = "";
closeModal();
}
</script>
components/customer/Dialog.vue
<template>
<Dialog as="div" #close="closeModal" class="relative z-10">
<input type="text" id="name" v-model="name" />
<input type="text" id="address" v-model="address" />
<input type="email" id="email" v-model="email" />
<button type="button" #click="addCustomer">Save</button>
<button type="button" #click="closeModal">Cancel</button>
</Dialog>
</template>
<script setup>
defineProps([
"name",
"address",
"email",
"addCustomer",
"isOpen",
"closeModal",
"openModal",
]);
</script>
EDIT: The Cancel button in the Dialog works while Save button doesn't.
You cannot bind props directly to the v-model directive, in your case you've to use Multiple v-model bindings
<template>
<Dialog as="div" #close="closeModal" class="relative z-10">
<input type="text" id="name" :value="name" #input="$emit('update:name', $event.target.value)"/>
<input type="text" id="address" :value="adress" #input="$emit('update:address', $event.target.value)" />
<input type="email" id="email" :value="email" #input="$emit('update:email', $event.target.value)" />
<button type="button" #click="$emit('add-customer')">Save</button>
<button type="button" #click="closeModal">Cancel</button>
</Dialog>
</template>
<script setup>
defineProps([
"name",
"address",
"email",
"addCustomer",
"isOpen",
"closeModal",
"openModal",
]);
defineEmits(['update:name', 'update:email','update:address','add-customer'])
</script>
in parent component :
<CustomerDialog
:is-open="isOpen"
:close-modal="closeModal"
:open-modal="openModal"
v-model:name="name"
v-model:address="address"
v-model:email="email"
#add-customer="addCustomer"
/>
As addCustomer method is available in parent. What you can do is that emit an event on Save button click from the dialog component and then capture the event in parent and invoke addCustomer method.
I just created a below code snippet with Vue 2.* just for your understanding purpose.
Vue.component('customerdialog', {
data() {
return {
customerDetailObj: {}
}
},
props: ['name', 'address', 'email'],
mounted() {
this.customerDetailObj = {
name: this.name,
address: this.address,
email: this.email
}
},
template: `<div><input type="text" id="name" v-model="customerDetailObj.name" />
<input type="text" id="address" v-model="customerDetailObj.address" />
<input type="email" id="email" v-model="customerDetailObj.email" />
<button type="button" #click="$emit('add-customer', customerDetailObj)">Save</button></div>`
});
var app = new Vue({
el: '#app',
data: {
name: 'Alpha',
address: 'Street 1',
email: 'alpha#testmail.com',
customerList: []
},
methods: {
addCustomer(customerObj) {
// Created user details
this.customerList.push(customerObj);
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<CustomerDialog :name="name"
:address="address"
:email="email" #add-customer="addCustomer($event)"></CustomerDialog>
<pre>{{ customerList }}</pre>
</div>
You can defineEmits
<script setup>
import {
TransitionRoot,
TransitionChild,
Dialog,
DialogPanel,
DialogTitle,
} from "#headlessui/vue";
defineProps([
"name",
"address",
"email",
"addCustomer",
"isOpen",
"closeModal",
"openModal",
]);
let emit = defineEmits(["add"])
</script>
Or better with typescript:
<script lang="ts" setup>
import {
TransitionRoot,
TransitionChild,
Dialog,
DialogPanel,
DialogTitle,
} from "#headlessui/vue";
defineProps([
"name",
"address",
"email",
"addCustomer",
"isOpen",
"closeModal",
"openModal",
]);
let emit = defineEmits<{ (name: "add"): void }>()
</script>
Now you can use it in your button:
<button
type="button"
class="inline-flex justify-center px-4 py-2 text-sm font-medium text-red-900 bg-red-100 border border-transparent rounded-md hover:bg-red-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-red-500 focus-visible:ring-offset-2"
#click="emit('add')"
>
Save
</button>
Now you can listen to this add event and trigger your addCustomer function
<CustomerDialog
:is-open="isOpen"
:close-modal="closeModal"
:open-modal="openModal"
:name="name"
:address="address"
:email="email"
#add="addCustomer"
/>
You'll want to use event emitters:
In the dialog:
<button #click="$emit('addCustomer')">save</button>
In customers.vue:
<CustomerDialog
:is-open="isOpen"
:close-modal="closeModal"
:open-modal="openModal"
:name="name"
:address="address"
:email="email"
#add-customer="addCustomer"
/>

How to make input required dynamically?

I have a basic input component that looks like this:
<template>
<div c>
<label :class="required ? 'required' : ''">{{ label }}</label>
<div >
<input :value="modelValue" v-on:input="updateValue($event.target.value)"
type="text"
/>
</div>
<p v-if="note" v-text="note"></p>
</div>
</template>
<script setup>
defineProps({
label: String,
modelValue: String,
required: { type: Boolean, default: false }
})
const emit = defineEmits(['update:modelValue'])
function updateValue(value){
emit('update:modelValue', value);
}
</script>
I want that if the boolean required is passed that the "require" value is set on the input, i.e.
<input ... require>
How can I set that require option on the input? I don't know how to set it, because its not of the typical key/value form. I also didn't find anythign at https://vuejs.org/guide/essentials/forms.html#multiline-text
Just bind the required attribute to the required prop :
<input :value="modelValue" v-on:input="updateValue($event.target.value)"
type="text"
:required="required"
/>

Refresh v-model

I'm developing question paper application.
Once I type a question and hit the "+" button, the question goes to the question paper array and counter increased by one.
The problem is after I hit the "+" button, then also the question which I have entered previously is still in the fields of the UI. Because of I use v-model to bind the data fields.
What I want is a method to clear those previous question data in UI.
This is something similar to reset button function.
<template>
<div>
<div class="container" v-if="counter<=5">
<h2>Question {{counter}}</h2><hr>
<textarea rows="7" cols="75" v-model="question"></textarea><br><br>
1. Answer <input type="text" v-model="answer1"> <input type="radio" name="q1answer" value="1" v-model="correctAnswer"><br><br>
2. Answer <input type="text" v-model="answer2"> <input type="radio" name="q1answer" value="2" v-model="correctAnswer"><br><br>
3. Answer <input type="text" v-model="answer3"> <input type="radio" name="q1answer" value="3" v-model="correctAnswer"><br><br>
4. Answer <input type="text" v-model="answer4"> <input type="radio" name="q1answer" value="4" v-model="correctAnswer"><br>
<hr>
Knowledge Area <select v-model="knowledgeArea">
<option value="Maths">Mathematics</option>
<option value="Language">Language Skills</option>
<option value="gk">General Knowledge</option>
<option value="other">Other</option>
</select><br><br>
<button type="button" class="btn" #click="pushToArray" >
<span class="glyphicon glyphicon-plus"></span></button>
</div>
<div v-if="counter>5">
<button type="button" class="btn btn-primary" #click="onSubmit">Save Question Paper</button>
</div>
</div>
</template>
<script>
import axios from 'axios';
var questionPaper = [];
export default {
data () {
return {
question:'',
answer1:'',
answer2:'',
answer3:'',
answer4:'',
correctAnswer:'',
knowledgeArea:'',
counter:1,
show:true
}
},
methods: {
onSubmit () {
},
pushToArray(){
const formData = {
question: this.question,
correctAnswer: this.correctAnswer,
answer1: this.answer1,
answer2: this.answer2,
answer3: this.answer3,
answer4: this.answer4,
knowledgeArea:this.knowledgeArea
}
this.counter++;
questionPaper.push(formData);
}
}
}
</script>
Create a template data variable to use as a reset. For example
const templateData = {
question:'',
answer1:'',
answer2:'',
answer3:'',
answer4:'',
correctAnswer:'',
knowledgeArea:''
}
export default { // ...
use that to set your initial data
data() {
return {
counter: 1,
show: true,
...templateData
}
}
Now you can easily reset your data in the pushToArray method, eg
questionPaper.push(formData);
Object.assign(this, templateData);

vue js get multiple values from inputs

So I have 2 blocks of HTML, each containing 2 input fields and when submitting the form, I want to get all values from the inputs, and then create an object from the values...
As of know I've done it with plain vanilla JS and it works as it should, however if feels like to touching the DOM a bit to much, and also are very much depending on a specific DOM struckture, and therefore I was thinking there must be a better way, the VUE way so to speak, however im a bit stuck on how to do this the VUE way, which is why posting the question here in hope of getting some useful tips :)
HTML:
<form novalidate autocomplete="off">
<div class="input-block-container">
<div class="input-block">
<input type="text" placeholder="Insert name" name="name[]" />
<input-effects></input-effects>
</div>
<div class="input-block">
<input type="email" placeholder="Insert email address" name="email[]" />
<input-effects></input-effects>
</div>
</div>
<div class="input-block-container">
<div class="input-block">
<input type="text" placeholder="Insert name" name="name[]" />
<input-effects></input-effects>
</div>
<div class="input-block">
<input type="email" placeholder="Insert email address" name="email[]" />
<input-effects></input-effects>
</div>
</div>
<button class="button button--primary" #click.prevent="sendInvites"><span>Send</span></button>
</form>
JS:
methods: {
createDataObject() {
let emailValues = document.querySelectorAll('input[type="email"]');
emailValues.forEach((email) => {
let name = email.parentNode.parentNode.querySelector('input[type="text"]').value;
if(email.value !== "" && name !== "") {
this.dataObj.push({
email: email.value,
name
});
}
});
return JSON.stringify(this.dataObj);
},
sendInvites() {
const objectToSend = this.createDataObject();
console.log(objectToSend);
//TODO: Methods to send data to server
}
}
You can provide data properties for each of your inputs if you have static content.
data: function() {
return {
name1: '',
email1: '',
name2: '',
email2: ''
}
}
Then use them in your template:
<input type="text" placeholder="Insert name" v-model="name1" />
Access in method by this.name1
Try this
<div id="app">
<h1> Finds </h1>
<div v-for="find in finds">
<input name="name[]" v-model="find.name">
<input name="email[]" v-model="find.email">
</div>
<button #click="addFind">
New Find
</button>
<pre>{{ $data | json }}</pre>
</div>
Vue Component
new Vue({
el: '#app',
data: {
finds: []
},
methods: {
addFind: function () {
this.finds.push({ name: '', email: '' });
}
enter code here
}
});

Vue 2 v-if not working after input changed

Maybe I'm doing it wrong but while binding v-if to two distinct input fields it only works if the input values hasn't changed.
var app = window.app = new Vue({
el: '#admin-app',
data() {
return {
language: "en"
}
},
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.0.7/vue.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.0.7/vue.common.js"></script>
<div id="admin-app">
<select id="language" name="language" class="form-control" v-model="language">
<option value="ro">Romanian</option>
<option value="en">English</option>
</select>
<input type="text" class="form-control" value="Test RO" v-if="language == 'ro'" />
<input type="text" class="form-control" value="Test EN" v-if="language == 'en'" />
</div>
As soon as one of the input fields has changed the v-if does not seem to work anymore. Although the "language" value changes when another option is selected.
VueJS appears to be extrapolating the inline input value as needing to be bound. I suspect that the input value is bound to the language prop. As a result, you can no longer safely evaluate your if condition once a value has been changed.
A workaround to this is to give both inputs explicit models via v-model. This correctly allows for both your conditional display and your input binding.
var app = window.app = new Vue({
el: '#admin-app',
data() {
return {
language: "en",
test1: "Test RO",
test2: "Test EN"
}
},
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.0.7/vue.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.0.7/vue.common.js"></script>
<div id="admin-app">
<select id="language" name="language" class="form-control" v-model="language">
<option value="ro">Romanian</option>
<option value="en">English</option>
</select>
<input type="text" class="form-control" v-model="test1" v-if="language == 'ro'" />
<input type="text" class="form-control" v-model="test2" v-if="language == 'en'" />
</div>