I'm working on a Vue/Quasar app and what I want to do is show the following layout only once when the page is loaded:
<template>
<div v-for="(section, index) in sections" :key="index">
<div class="row q-gutter-md">
<q-input v-model="sectionName" bottom-slots dense :label="labelText">
<template v-slot:append>
<q-btn flat dense icon="add" color="grey " #click="addNew" />
<q-btn flat dense icon="delete" color="grey " #click="removeSection" />
</template>
</q-input>
</div>
</div>
</template>
Here is the code snippet from the script section:
setup() {
const sections = ref(1);
const addNew = () => {
sections.value++
};
const removeSection = () => {
//...
};
return{
//...}
The addNew function works well: new sections will be added to the screen. But how could I remove a particular (which was clicked) section? What should I change?
You can send a parameter to your removeSection function (ex: index) after that once you clicked on delete button you will know which index should be deleted. You will be able to find the item in your sections that you wanted to delete.
You can define sections like array of objects with id and value:
const { ref } = Vue
const app = Vue.createApp({
setup() {
const sections = ref([{id: new Date(), sectionName: ''}]);
const addNew = (i) => {
sections.value = [...sections.value, {id: new Date(), sectionName: ''}]
};
const removeSection = (i) => {
sections.value.length > 1 && sections.value.splice(sections.value[i], 1)
};
return{ sections, addNew, removeSection }
}
})
app.use(Quasar)
app.mount('#q-app')
<link href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900|Material+Icons" rel="stylesheet" type="text/css">
<link href="https://cdn.jsdelivr.net/npm/quasar#2.10.1/dist/quasar.prod.css" rel="stylesheet" type="text/css">
<div id="q-app">
<div v-for="(section, index) in sections" :key="index">
<div class="row q-gutter-md">
<q-input v-model="section.sectionName" bottom-slots dense :label="labelText">
<template v-slot:append>
<q-btn flat dense icon="add" color="grey " #click.prevent="addNew" />
<q-btn flat dense icon="delete" color="grey " #click.prevent="removeSection(index)" />
</template>
</q-input>
</div>
</div>
{{sections}}
</div>
<script src="https://cdn.jsdelivr.net/npm/vue#3/dist/vue.global.prod.js"></script>
<script src="https://cdn.jsdelivr.net/npm/quasar#2.10.1/dist/quasar.umd.prod.js"></script>
Related
js and Im using composition API with script setup. Im doing a simple quote generator and fetching API. Its just that I get ALL quotes to show when I press the button. How can I do so only one random quote shows at the time?
<template>
<div class="about">
<div>
<h1 class="mb-6">This is a random Blog page</h1>
</div>
<div v-for="quote in listItems" class="justify-center flex flex-column text-center">
<h5> ID: {{ quote.id }}</h5>
<h2> Title: {{ quote.title }}</h2>
<h3> Body: {{ quote.body }}</h3>
</div>
<button #click="getData()" class="bg-orange-500">Get a new blog</button>
</div>
</template>
<script setup>
import {ref} from 'vue'
let id = ref(0)
let title = ref('hallo')
let body = ref('random blogs')
const listItems = ref([])
async function getData(){
const api = await fetch('https://jsonplaceholder.typicode.com/posts')
const finalApi = await api.json()
listItems.value = finalApi
const index = Math.floor(Math.random()*api.length)
const quoteOfTheDay = finalApi.value[index]
quoteOfTheDay.value = quoteOfTheDay.body
}
</script>
You're looping through all the api responses in your template. Instead just display one.
<template>
<div class="about">
<div>
<h1 class="mb-6">This is a random Blog page</h1>
</div>
<div v-if="quote" class="justify-center flex flex-column text-center">
<h5> ID: {{ quote.id }}</h5>
<h2> Title: {{ quote.title }}</h2>
<h3> Body: {{ quote.body }}</h3>
</div>
<button #click="getData()" class="bg-orange-500">Get a new blog</button>
</div>
</template>
<script setup>
import {ref} from 'vue'
let id = ref(0)
let title = ref('hallo')
let body = ref('random blogs')
const listItems = ref([])
let quote = ref(null)
async function getData(){
const api = await fetch('https://jsonplaceholder.typicode.com/posts')
const finalApi = await api.json()
const index = Math.floor(Math.random() * finalApi.length)
quote.value = finalApi[index]
}
</script>
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>
apologies if that's too basic, but I'm stuck.
I have created an object in vue with three properties (slug, title and content). I successfully console.logged the object. How can I now use the object in my page in order to render its content?
There is no need for me to loop through the object, at it has only one item in it.
<template>
<div class="relative py-16 overflow-hidden bg-white">
<div class="relative px-4 sm:px-6 lg:px-8">
<div class="mx-auto text-lg max-w-prose">
<h1>
<span
class="block text-base font-semibold tracking-wide text-center text-indigo-600 uppercase"
>Hello</span
>
<span
class="block mt-2 text-3xl font-extrabold leading-8 tracking-tight text-center text-gray-900 sm:text-4xl"
>Here is the name</span
>
</h1>
<p class="mt-8 text-xl leading-8 text-gray-700"></p>
<div
v-bind="this.data.content"
class="text-lg font-medium leading-6 text-gray-900"
></div>
</div>
</div>
</div>
</template>
<script>
const Cosmic = require("cosmicjs");
const api = Cosmic();
const bucket = api.bucket({
slug: "((BUCKETNAME))",
read_key: "((KEY))",
});
const data = bucket
.getObject({
id: "((BUCKET ID))", // Object ID
props: "slug,title,content", // get only what you need
})
.then((data) => {
const about = data.objects;
console.log(data);
});
export default {
name: "data",
data() {
return {
data,
};
},
};
</script>
As other commenters have suggested, it would be useful to read the Vue syntax guide here https://v2.vuejs.org/v2/guide/syntax.html
But to answer your question with the most minimal of code changes, you'd want to move your data request to the lifecycle hook of your vue component.
<template>
<h1>{{ dataObjects.title }}</h1>
<p>{{ dataObjects.slug }}</p>
<p>{{ dataObjects.content }}</p>
</template>
<script>
export default {
name: "data",
data() {
return {
dataObjects: null,
};
},
mounted() {
bucket.getObject({
id: "((BUCKET ID))", // Object ID
props: "slug,title,content", // get only what you need
})
.then((data) => {
// Assign the return value to the dataObjects propery of the vue instance.
this.dataObjects = data;
});
}
};
</script>
In the template section, you can see that I've used curly braces to render the contents of dataObjects (I wasn't sure what structure your data is in).
You can also learn from examples on the Vue Cookbook site
When a new account is registered, I display login and password details on the screen. The user can copy login&passwod details to clipboard and I would like to run animation when the append-icon (scale up, increase font or replace with another icon) is clicked. What would be the right way to do that?
<template>
<v-card class="col-12 col-md-8 col-lg-6 p-6 px-16" elevation="4">
<div class="title h2 mb-10 text-uppercase text-center">
Success
<v-icon color="green" x-large>
mdi-check-circle
</v-icon>
</div>
<v-text-field
:value="newAccount.login"
label="Login"
outlined
readonly
append-icon="mdi-content-copy"
ref='login'
#click:append="copy('login')"
></v-text-field>
<v-text-field
:value="newAccount.password"
label="Password"
outlined
readonly
append-icon="mdi-content-copy"
ref='login'
#click:append="copy('login')"
></v-text-field>
</v-card>
</template>
<script>
methods: {
copy(field) {
const input = this.$refs[field].$refs.input
input.select()
document.execCommand('copy')
input.setSelectionRange(0,0)
// apply append-icon animation
}
}
</script>
There is multiple answers to this, but to answer one of them: "replacing icon" would be pretty straight forward.
You would have to change the append-icon prop do be dynamic :, then assign a variable to it like copyIcon. You will then update this variable depending on the state of the copy.
new Vue({
el: '#app',
vuetify: new Vuetify(),
data: {
copyIcon: 'mdi-content-copy',
newAccount: {
login: 'GhostDestroyer69',
password: '',
},
},
methods: {
copy(field) {
const input = this.$refs[field].$refs.input
input.select()
document.execCommand('copy')
input.setSelectionRange(0, 0)
// Set icon to check
this.copyIcon = 'mdi-check'
// Reset icon to copy after 1 second
setTimeout(() => {
this.copyIcon = 'mdi-content-copy'
}, 1000)
}
}
})
<link href="https://cdn.jsdelivr.net/npm/#mdi/font#4.x/css/materialdesignicons.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/vuetify#2.x/dist/vuetify.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/vue#2.x/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vuetify#2.x/dist/vuetify.js"></script>
<div id="app">
<v-card class="col-12 col-md-8 col-lg-6 p-6 px-16" elevation="4">
<v-text-field
:value="newAccount.login"
label="Login"
outlined
readonly
:append-icon="copyIcon"
ref='login'
#click:append="copy('login')"
></v-text-field>
</v-card>
</div>
mdi has animation helpers like
append-icon="mdi-content-copy mdi-spin"
For example.
I'm trying to make the bootstrap-vue tabs initialization lazy and though it works if i set the lazy attribute on true, it render the component every time i'm visiting a specific tab:
BtabsWrapper.vue:
<b-tabs
:lazy="true"
>
<b-tab
v-for="(tab, index) in tabs"
:key="'li-tab-' + index"
:title="tab.title"
:href="'#' + tab.id"
>
<slot
:name="tab.id"
/>
</b-tab>
</b-tabs>
I need more of a lazy initialization of each tab(just once) rather than re rendering the tab's component every time the user visits it. Any ideas?
If you wrap the content of each tab in a v-if and change that condition once when they're loaded you should get your desired outcome.
And in your case if you're using a v-for, you can utilize the index in your v-for in the includes part as visitedTabs.includes(index)
window.onload = () => {
new Vue({
el: '#app',
data() {
return {
visitedTabs: []
}
},
methods: {
onInput(value) {
if(!this.visitedTabs.includes(value)){
this.visitedTabs.push(value)
}
}
}
})
}
body {
padding: 1em;
}
<link href="https://unpkg.com/bootstrap#4.3.1/dist/css/bootstrap.min.css" rel="stylesheet"/>
<link href="https://unpkg.com/bootstrap-vue#2.0.4/dist/bootstrap-vue.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.js"></script>
<script src="https://unpkg.com/bootstrap-vue#2.0.4/dist/bootstrap-vue.js"></script>
<div id="app">
<b-card clas no-body>
<b-tabs card #input="onInput">
<b-tab title="First tab">
<div v-if="visitedTabs.includes(0)">
Hello World
</div>
</b-tab>
<b-tab title="Second tab">
<div v-if="visitedTabs.includes(1)">
<b-input />
</div>
</b-tab>
<b-tab title="Third tab">
<div v-if="visitedTabs.includes(2)">
<b-checkbox />
</div>
</b-tab>
</b-tabs>
</b-card>
</div>