Rookie Question about Vue with input and ref value. Display content of input only after click (not when typing) - vue.js

I have an input and a click button.
When I click I request a database (async/await) and finally display a table with requested data.
At the same time I display the input keyword like "{{input}} was found X times".
But then I want to do another research but the text of input is reactive and change while I'am typing. I would like to only display the new keyword input after the click.
I'm struggling to do that.
<div class="q-pa-md">
<div class="q-gutter-md" style="max-width:600px">
<q-input outlined bottom-slots v-model="gene" label="Gene Symbol :" >
<template v-slot:before>
<q-icon name="search" />
</template>
<template v-slot:hint>
Ligand or Receptor
</template>
<template v-slot:after>
<q-btn #click="search" label="Search :" />
</template>
</q-input>
</div>
Elsewhere I have this for the display:
<div class="text-h6">
<q-badge align="middle" style="background-color:#FF4F00;font-size:20px;padding:12px">
Interactions > </q-badge> {{ gene }} was found XX times.
</div>

When you use v-model, the value changes with any input change. To avoid it you can define a new variable to store the gene value whenever q-btn is clicked; Like this:
<template>
<input v-model="gene" type="text">
<button #click="search">search</button>
<div v-if="searchedFor">
{{ searchedFor }} was found X times
</div>
</template>
<script>
export default {
data() {
return {
gene: "",
searchedFor: ""
}
},
methods: {
search() {
// Clear the previous one
this.searchedFor = ""
// API fetch ...
this.searchedFor = this.gene
}
}
}
</script>

Related

Passing props dynamically to a component inside a v-for loop

I have a v-for loop that iterates through an array of meetings (meetings between sellers and potential buyers of used cars) and prints a card for each meeting, a basic display of who the meeting is with, what car it is about and the scheduled date. Now, I implemented a button that when clicked, opens a dialog with a Google Maps component that shows the marker for the agreed location of the meeting.
My problem is that no matter what card I click on, the dialog will always display the location of the LAST card, regardless of which has been clicked. I would think that since Im calling the component INSIDE the v-for loop it would pass props dynamically for each card, on each iteration, but that does not seem to be the case.
Here is the HTML:
<div
v-for="meeting in meetings"
:key="meeting.did"
class="col-12 col-md-6 col-lg-3 q-pa-md q-mx-xl"
>
<q-card class="my-card homeCard q-pa-md">
<q-dialog class="mapDialog flex column" v-model="mapDialog">
<MeetMapComponent
:key="componentKey"
:mapDiv="mapDiv"
:mapData="meeting.address"
:buyerName="meeting.name"
/>
</q-dialog>
<q-card-section
class="tipCardImage flex row justify-end"
:style="`background-image: url(${meeting.car.carImg})`"
>
<router-link
:to="`/user/meet/edit/${meeting.did}`"
style="text-decoration: none"
>
<q-icon
#click="fetchMeeting(meeting.did)"
name="fa-solid fa-pencil editNameIcon q-mb-sm q-ml-sm"
></q-icon>
</router-link>
<q-icon
name="fa-solid fa-trash editNameIcon q-mb-sm q-ml-sm"
#click="triggerDelete(meeting.did)"
></q-icon>
</q-card-section>
<q-card-section>
<div class="cardTitle">
<span>Encuentro Con</span> {{ truncateString(meeting.name, 30) }}
</div>
<div class="tipCardText">
<span>Agendado para el </span>
<p>{{ truncateString(meeting.date, 120) }}</p>
</div>
<div class="flex row justify-end">
<q-btn
#click="mapDialog = true"
class="text-white cardButton"
:class="{ cardButtonMobile: $q.screen.lt.md }"
>Ver UbicaciĆ³n</q-btn
>
</div>
</q-card-section>
</q-card>
</div>
And here is the code for the MeetMapComponent:
<template>
<div class="meetMapContainer">
<div ref="mapDiv" style="width: 100%; height: 500px" />
<h5 class="text-center text-white">{{ props.mapData.address }}</h5>
</div>
</template>
<script setup>
import { ref } from "vue";
import { useAuthStore } from "stores/auth";
import { storeToRefs } from "pinia";
import { Loader } from "#googlemaps/js-api-loader";
const props = defineProps({
mapData: Object,
buyerName: String,
});
const GOOGLE_MAPS_API_KEY = "...";
const loader = new Loader({ apiKey: GOOGLE_MAPS_API_KEY });
const mapDiv = ref(null);
async function mapRender() {
await loader.load();
const map = new google.maps.Map(mapDiv.value, {
mapTypeId: "roadmap",
center: props.mapData.coordinates,
zoom: 13,
});
console.log(map);
new google.maps.Marker({
position: props.mapData.coordinates,
map,
title: `Encuentro con ${props.buyerName}`,
});
}
mapRender();
</script>
I will help you as much as I understand. You use the mapDialog variable to open the dialogue. But even if this variable is used in v-for, its reference does not change. For this reason, when you want to open a modal, all modals may be opened and the last one may appear because it is the last one opened. Please check the dom.
I think this method can solve the problem.
in script
const meetings = [
{
did: 'some value',
address: 'some address',
name: 'some name',
// add modal flag
showMapModal: false
}
]
template
<div
v-for="meeting in meetings"
:key="meeting.did"
class="col-12 col-md-6 col-lg-3 q-pa-md q-mx-xl"
>
<q-card class="my-card homeCard q-pa-md">
<q-dialog class="mapDialog flex column" v-model="meeting.showMapModal">
<MeetMapComponent
:key="componentKey"
:mapDiv="mapDiv"
:mapData="meeting.address"
:buyerName="meeting.name"
/>
</q-dialog>
</q-card>
</div>

Dynamic Placeholder in Vue 3 with Global Component

I am trying to set dynamic text for the placeholder attribute on my search bar. Depending on the page, I want the text in the search bar to be different (I will define it in data()).
However, since the search bar component is a global component, it doesn't seem to be editable.
(As you see below is my try, I did it with v-model based on Vue docs, however when I try with placeholder it doesn't work...)
Snippet 1 - Search bar component
<template>
<!-- Search Componenet -->
<div class="mx-5 mb-3 form-group">
<br>
<input class="mb-5 form-control" type="search" :placeholder="placeholderValue" :value="modelValue" #load="$emit('update:placeholderValue', $event.target.value)" #input="$emit('update:modelValue', $event.target.value)" />
</div>
</template>
<script>
export default {
props: ['modelValue', 'placeholderValue'],
emits: ['update:modelValue', 'update:placeholderValue']
}
</script>
Snippet 2 - Album.vue
<template>
<div class="AlbumView">
<h1>{{header}}</h1>
<h2>{{header2}}</h2>
<br>
<!-- Search Componenet -->
<SearchComponent :placeholder="placeholderValue" v-model="searchQuery" />
<!-- Dynamic Song Route Button -->
<div class="button-container-all mx-5 pb-5">
<div v-for="item in datanew" :key="item.id">
{{ item.album }}
</div>
</div>
</div>
</template>
<script>
import { datatwo } from '#/data2'
export default {
data() {
return {
placeholderValue: "Search for Albums here...",
datanew: datatwo,
searchQuery: null,
header: "Browse by Album",
header2: "Select an Album:",
publicPath: process.env.BASE_URL
};
},
}
</script>
If this is possible?
If you want to do it with v-model (the Childcomponent changes the value of the placeholder) you have to use v-model:placeholder for it to work.
And also placeholderValue is not the way to go the "Value" at the end of a prop is only needed for modelValue which is the default v-model-binding (v-model="") but if you want named v-model-binding (v-model:placeholder="") you do not want to add the "Value" in the props and emits arrays.
Example:
usage of SearchComponent
<SearchComponent :placeholder="'placeholderValue'" v-model="searchQuery" />
instead of 'placeholderValue' you can put any string you want or variable. I just put the string 'placeholderValue' as an example.
SearchComponent
<template>
<!-- Search Componenet -->
<div class="mx-5 mb-3 form-group">
<br>
<input class="mb-5 form-control" type="search" :placeholder="placeholder" :value="modelValue" #load="$emit('update:placeholderValue', $event.target.value)" #input="$emit('update:modelValue', $event.target.value)" />
</div>
</template>
<script>
export default {
name: "SearchComponent",
props: ['modelValue', 'placeholder'],
emits: ['update:modelValue'],
}
</script>
<style scoped>
</style>

Executing js on slot

I'm a beginner in web development and I'm trying to help out friends restarting an old game. I'm in charge of the tooltip component but I hit a wall...
There are many Vue components and in a lot of them I want to call a child component named Tooltip, I'm using vue-tippy for easy configuration. This is the component:
<template>
<tippy class="tippy-tooltip">
<slot name='tooltip-trigger'></slot>
<template #content>
<slot name='tooltip-content'>
</slot>
</template>
</tippy>
</template>
<script>
import { formatText } from "#/utils/formatText";
export default {
name: "Tooltip",
methods:{
formatContent(value) {
if (! value) return '';
return formatText(value.toString());
}
},
}
</script>
In one of the other components I try to use the tooltip:
<template>
<a class="action-button" href="#">
<Tooltip>
<template #tooltip-trigger>
<span v-if="action.movementPointCost > 0">{{ action.movementPointCost }}<img src="#/assets/images/pm.png" alt="mp"></span>
<span v-else-if="action.actionPointCost > 0">{{ action.actionPointCost }}<img src="#/assets/images/pa.png" alt="ap"></span>
<span v-if="action.canExecute">{{ action.name }}</span>
<span v-else><s>{{ action.name }}</s></span>
<span v-if="action.successRate < 100" class="success-rate"> ({{ action.successRate }}%)</span>
</template>
<template #tooltip-content>
<h1>{{action.name}}</h1>
<p>{{action.description}}</p>
</template>
</Tooltip>
</a>
</template>
<script>
import Tooltip from "#/components/Utils/ToolTip";
export default {
props: {
action: Object
},
components: {Tooltip}
};
</script>
From here everything is fine, the tooltip is correctly displayed with the proper content.
The thing is, the text in the {{ named.description }} needs to be formatted with the formatContent content. I know I can use the props, the components would look like that:
Tooltip.vue:
<template>
<tippy class="tippy-tooltip">
<slot name='tooltip-trigger'></slot>
<template #content>
<h1 v-html="formatContent(title)" />
<p v-html="formatContent(content)"/>
</template>
</tippy>
</template>
<script>
import { formatText } from "#/utils/formatText";
export default {
name: "Tooltip",
methods:{
formatContent(value) {
if (! value) return '';
return formatText(value.toString());
}
},
props: {
title: {
type: String,
required: true
},
content: {
type: Array,
required: true
}
}
}
</script>
Parent.vue:
<template>
<a class="action-button" href="#">
<Tooltip :title="action.name" :content="action.description">
<template v-slot:tooltip-trigger>
<span v-if="action.movementPointCost > 0">{{ action.movementPointCost }}<img src="#/assets/images/pm.png" alt="mp"></span>
<span v-else-if="action.actionPointCost > 0">{{ action.actionPointCost }}<img src="#/assets/images/pa.png" alt="ap"></span>
<span v-if="action.canExecute">{{ action.name }}</span>
<span v-else><s>{{ action.name }}</s></span>
<span v-if="action.successRate < 100" class="success-rate"> ({{ action.successRate }}%)</span>
</template>
</Tooltip>
</a>
</template>
<script>
import Tooltip from "#/components/Utils/ToolTip";
export default {
props: {
action: Object
},
components: {Tooltip}
};
</script>
But I need to use a slot in the tooltip component because we'll have some "extensive" lists with v-for.
Is there a way to pass the data from a slot into a JS function?
If I understand you correctly, you're looking for scoped slots here.
These will allow you to pass information (including methods) from child components (the components with <slot> elements) back to the parents (the component(s) filling those slots), allowing parents to use chosen information directly in the slotted-in content.
In this case, we can give parents access to formatContent(), which will allow them to pass in content that uses it directly. This allows us to keep the flexibility of slots, with the data passing of props.
To add this to your example, we add some "scope" to your content slot in Tooltip.vue. This just means we one or more attributes to your <slot> element, in this case, formatContent:
<!-- Tooltip.vue -->
<template>
<tippy class="tippy-tooltip">
<slot name='tooltip-trigger'></slot>
<template #content>
<!-- Attributes we add or bind to this slot (eg. formatContent) -->
<!-- become available to components using the slot -->
<slot name='tooltip-content' :formatContent="formatContent"></slot>
</template>
</tippy>
</template>
<script>
import { formatText } from "#/utils/formatText";
export default {
name: "Tooltip",
methods: {
formatContent(value) {
// Rewrote as a ternary, but keep what you're comfortable with
return !value ? '' : formatText(value.toString());
}
},
}
</script>
Now that we've added some scope to the slot, parents filling the slot with content can use it by invoking a slot's "scope":
<!-- Parent.vue -->
<template>
<a class="action-button" href="#">
<Tooltip>
. . .
<template #tooltip-content="{ formatContent }">
<!-- Elements in this slot now have access to 'formatContent' -->
<h1>{{ formatContent(action.name) }}</h1>
<p>{{ formatContent(action.description) }}</p>
</template>
</Tooltip>
</a>
</template>
. . .
Sidenote: I prefer to use the destructured syntax for slot scope, because I feel it's clearer, and you only have to expose what you're actually using:
<template #tooltip-content="{ formatContent }">
But you can also use a variable name here if your prefer, which will become an object which has all your slot content as properties. Eg.:
<template #tooltip-content="slotProps">
<!-- 'formatContent' is now a property of 'slotProps' -->
<h1>{{ slotProps.formatContent(action.name) }}</h1>
<p>{{ slotProps.formatContent(action.description) }}</p>
</template>
If you still need the v-html rendering, you can still do that in the slot:
<template #tooltip-content="{ formatContent }">
<h1 v-html="formatContent(title)" />
<p v-html="formatContent(content)"/>
</template>

How to get the value of one input field in a "v-for" of multiple inputs

I have multiple text inputs generated in v-for directive which i have attached to one v-model variable as show below. I have a button by the respective inputs which prints the value of the current working input. Ultimately I want to extract value of the selected input without affecting the other inputs.
But apparent any change make in one input affect all the input. I super confused as to how I will achieve this. Any help will be much appreciated.
My attempted code is shown below.
<template>
<div id="app">
<div v-for="i in 5" :key="i">
<input v-model="text" type="text" :key="i" />
<button #click="printText">print</button> <span>{{ text }}</span>
</div>
</div>
</template>
<script>
export default {
data() {
return {
text: "",
};
},
methods: {
printText() {
console.log(this.text);
},
},
};
</script>
Take an array instead of simple variable when you use v-model in v-for
And on click pass the index with function call
Try to use
<template>
<div id="app">
<div v-for="i in 5" :key="i">
<input v-model="text[i]" type="text"/>
<button #click="printText(i)">print</button> <span>{{ text[i] }}</span>
</div>
</div>
</template>
<script>
export default {
data() {
return {
text: [],
};
},
methods: {
printText(index) {
console.log(this.text[index]);
},
},
};
</script>

Vue.js: Loading template (or div) when user clicks button?

So I currently have a template sitting in a ".vue" file like so:
<template>
<div id="dataAttachToMe"></div>
</template>
I don't want this to load, unless a user clicks a button, something like
<button #click="loadTheTemplateAbove">See Data</button>
I've tried using this example:https://v2.vuejs.org/v2/guide/conditional.html#Controlling-Reusable-Elements-with-key. But it says something like "Component template should contain exactly one root element" in the error message.
I need more than a show/hide here I think, something that can initiate the template dynamically.
<template>
<div id="data">
<button #click="loadTemplate">Load the template</button>
<div v-if="buttonClicked">
<div id="dataAttachedToThisDiv"></div>
</div>
</div>
</template>
The error you are getting, means that there is more than one root element inside <template></template> tag.
It is required in Vue.js (and other template based frameworks/libraries) to have only one root element.
This will NOT work:
<template>
<div id="dataAttachToMe"></div>
<button #click="loadTheTemplateAbove">See Data</button>
</template>
This will work:
<template>
<div id="someRootDiv">
<div id="dataAttachToMe">Some data</div>
<button #click="loadTheTemplateAbove">See Data</button>
</div>
</template>
Here is a code example (App.vue) of what you are trying to achieve:
Basic idea: we have to create a variable, that will be changed upon button click. We add v-if directive that depends on that variable and will handle element's visibility.
Welcome to StackOverflow. When you get the error Component template should contain exactly one root element it means that you can only have one root element in your template. You can fix that error by wrapping everything in a blank div like so
<template>
<div>
<template v-if="loginType === 'username'">
<label>Username</label>
<input placeholder="Enter your username">
</template>
<template v-else>
<label>Email</label>
<input placeholder="Enter your email address">
</template>
</div>
</template>
Please edit your post and place you <script> tag. Conditional Rendering requires a data field of a boolean that you can place in your if statement on your template
<template>
<div>
<div v-if="show">{{message}}</div>
<div v-if="#show">Not Showing when show is set to false</div>
<button v-on:click="show = true">Show</button>
</div>
</template>
<script>
module.exports {
data: function () {
message: 'Hello Vue!',
show: false
}
}
</script>