I've product listing page where I use multiple ProductCard component. I need to add Spinner to Add to cart button when clicking on it, but if I just add Spinner to v-else I've Spinners in every ProductCard. How to control only this ProductCard where I click Add to cart button?
Parent's component:
<ProductCard
v-for="product in products"
:key="product.id"
:product="product"
/>
Child's component:
<template>
<div id="productCart">
<div>
<NuxtLink :to="`/product/${item.slug}`" />
<NuxtLink v-if="item.name" :to="`/product/${item.slug}`">
<h5
id="productName"
v-text="item.name"
/>
</NuxtLink>
<Button
v-if="!loading"
#click="addToBuyCart({ product: item })"
v-text="$t('labels.buy')"
/>
<Spinner v-else />
</div>
</div>
</template>
loading is defined in composable, and it's updating in addToBuyCart function by making it true, then adding to cart (api), then making it false
As pointed by IVO, in such situations you should check against a value specific for each item in the loop, item ID for example, instead of a boolean which lacks specificity and therefore cannot be used for such scenarios.
<template>
<div id="productCart">
<div>
<NuxtLink :to="`/product/${item.slug}`" />
<NuxtLink v-if="item.name" :to="`/product/${item.slug}`">
<h5
id="productName"
v-text="item.name"
/>
</NuxtLink>
<Button
v-if="loading !== item.id"
#click="addToBuyCart({ product: item })"
v-text="$t('labels.buy')"
/>
<Spinner v-else />
</div>
</div>
</template>
const addToBuyCart = ({product}) => {
loading = product.id
// some logic or request
loading = null
}
Related
I am trying to create button-group component in vue using typescript:
<template>
<slot>
<CButton
v-for="(vnode, index) in $slots.default || []"
:key="index"
:class="[
{ 'first-index': index === 0 },
{ 'last-index': index === ($slots.default ? $slots.default.length : 0) - 1 },
]"
>
{{ index }}
{{ vnode }}
</CButton>
</slot>
</div>
</template>
Here index comes null but I am expecting it turns as much as button I added.
Here is the parent:
<ButtonGroup>
<CustomButton>1</CustomButton>
<CustomButton>2</CustomButton>
<CustomButton>3</CustomButton>
</ButtonGroup>
Here is how you need to use slots in Vue:
<!-- ButtonGroup -->
<template>
<div class="button-group">
<slot></slot> <!-- Define the default slot -->
</div>
</template>
<template>
<ButtonGroup>
<!-- Fill in the slot with whatever you want -->
<CustomButton class="my-class">Test</CustomButton>
<button class="regular-button">Normal</button>
<CustomButton />
</div>
</template>
Example playground
basically what I'm trying to do is, I get the DateTime coming from my database along with some information of a possible meeting, if people are busy, they will have the option to extend that time through a combobox that pulls a enum in my backend which has values in milliseconds, description and id. As I get these values coming from my enum, I can extend the meeting, but I did this validation on the frontend through a function... where I get the value of the combobox, and add it to the dateTime coming from the BE, however, I can't change the database (legacy) to add a field to save the enum, and that's why I had to make an adapted function in the frontend, and the extension method is hardcoded and doesn't have a field in the BE it replicates the same combobox for all. pointing out that I can't pull the data still coming from my v-for to mounted() or data().
Please help me. I'm using Primevue.
i tried to catch the index/code coming from the v-for by #click the dropdown by creating the function in mounted(), but without success. When trying to manipulate this data, it returns me an undefined value enter image description here
my v-for loop
`<div
class="mt-2"
v-for="(item, index) in listaAlarme"
:key="index">
<!-- {{ item.codigo }}
{{ index }} :{{ listaAlarme[index].referencia }} -->
<Card class="cardContainer">
<template #content>
<div class="formgrid grid">
<UnoFormField
id="dateTimeReference"
:cols="4"
label="Date and Time">
<UnoFormCalendar
v-model="listAlarm[index].reference"
#click="handleAlterTime(index)"
:withTime="true" />
</UnoFormField>
<UnoFormField
id="extended"
:cols="3"
label="Extended">
<Dropdown
v-model="typeActivitiesExtend"
:options="typeActivitiesExtendList"
option-label="description"
option-value="milliSeconds"
placeholder="Extended"
:class="{
'w-full': true,
}"
class="p-dropdown-uno-trigger-black"></Dropdown>
</UnoFormField>
<UnoFormField
id="contact.name"
:cols="3">
<div class="phoneIcon">
<fa-icon
icon="phone"
#click="handleCall" />
</div>
</UnoFormField>
</div>
</template>
<template #footer>
<div class="formgrid grid">
<!-- <div class="flex"> -->
<UnoFormField
id="meeting"
:cols="3">
<Button
icon="pi pi-search"
class="p-button-uno-black"
#click="handleMeeting"></Button>
</UnoFormField>
<UnoFormField
id="description"
:cols="3">
<p class="text">
{{ listAlarm[index].shortDescription }}
</p>
</UnoFormField>
<div class="iconButtons">
<fa-icon
icon="ban"
class="banIcon"
#click="excludeItem" />
<fa-icon
icon="check"
class="checkIcon"
#click="onBtnSave" />
</div>
<!-- </div> -->
</div>
</template>
</Card>
`
the UnoFormField, is a label as you can see, that me return the label name and fied col just for display de fields with a size col in template
my function to extend the hours in methods:
i'm try to catch the value of datatime in v-for
`
so everything i can find about scoped slots and passing props dont work for my specific situation:
i have following component order:
Home/List/ListItem
now i desided to replace the ListItem with a slot and because i use the List in a other Component too, but in there i need the ListOptionsItem.
in my home component i did this:
<list
class="mapkeyList"
:content="displayList"
:filterbar="true"
#handleSelection="addSelection"
#delete="deleteElement"
#editItem="editItem"
header="Mapkeys"
:key="mapkeyListKey"
>
<list-item>
</list-item>
</list>
in my List component i have this:
<template>
<div>
<h2 v-if="header">{{header}}</h2>
<div class="listContainer" v-if="showedContent.length > 0">
<div v-for=" (item, index) in showedContent" :key="index">
<slot
:item="item"
:index="index"
:dragable="dragableItems"
#auswahl="auswahlHandle"
#deleteElement="deleteElement"
#editItem="editItem"
:dontShowButtons="dontShowButtons"
#dragStart="handleOverDragStart"
:dragItem="dragItem"
#position="$emit('emitPosition',item)"
:deaktivierbar="deaktivierbar"
>
</slot >
</div>
finaly the listItem and the listOptionsItem need to access this props in the slot:
listItem:
<template>
<div class= "flexSpaceBetween" #click="$emit('auswahl',item)">
<div class="textFett">
{{item[0]}}
</div>
<div>
{{item[1]}}
</div>
</div>
i dont want to write all the neccessarry code in the home component because the listOptionsItem does need more informations and more space to write code.
my goal was it to define in the Home component that i want the list to use the listItem component and in the Options component the list should use the listItemOptions component. in the future there could be added new listItem versions.
Any component used inside scoped slot has no implicit access to the slot props. To make them available inside the component, you must pass it down to that component as props explicitly...
<list
class="mapkeyList"
:content="displayList"
:key="mapkeyListKey">
<template v-slot:default="{ item }">
<list-item :item="item">
</list-item>
</template>
</list>
If you have a lot of props/events you want to pass along, the ability of both v-bind and v-on to take an object as an argument is very useful because you can pass all the data and event handlers at the same time:
// List component
<template>
<div>
<h2 v-if="header">{{header}}</h2>
<div class="listContainer" v-if="showedContent.length > 0">
<div v-for=" (item, index) in showedContent" :key="index">
<slot :props="slotProps" :on="slotEventsHandlers"
</slot >
</div>
</div>
</div>
</template>
<script>
export default {
computed: {
slotProps() {
return {
item: this.item,
dragable: this.dragableItems
}
},
slotEventsHandlers() {
return {
deleteElement: this.deleteElement,
dragStart: this.handleOverDragStart
}
}
}
}
</script>
And use it in parent:
<list
class="mapkeyList"
:content="displayList"
:key="mapkeyListKey">
<template v-slot:default="{ props, on }">
<list-item v-bind="props" v-on="on">
</list-item>
</template>
</list>
I am using Bootstrap-Vue Modals to open an "Edit" modal when clicking the "Edit" button that is attached to each item I have rendered in a list from v-for rendered list.
Each time, however, when I click on the edit button, it opens all of the modals, stacked on top of eachother with the last element in the list being the top modal.
How can I specify it to only open the modal/information for the item that is clicked to be edited?
//Parent Component
<div class="dataList">
<div v-bind:key="item.id" v-for="item in this.$store.getters.data">
<Child v-bind:item="item"></Child>
</div>
</div>
//Child Component
<div>
{{this.item.name}}
{{this.item.details}}
{{this.item.completedBy}}
{{this.item.status}}
<button v-b-modal.modal-1>Edit</button>
<button v-on:click="deleteItem">Delete</button>
<div>
<b-modal id="modal-1" title="BootstrapVue">
<form #submit="editItem">
<input v-model="name">
<input v-model="details">
<input v-model="completedBy">
<select v-model="status">
<option>Fail</option>
<option>Warn</option>
<option>Pass</option>
</select><br>
<input type="submit" value="Submit" #click="$bvModal.hide('modal-1')">
</form>
</b-modal>
</div>
</div>
Now each modal shows the correct information (like the proper name, details, status, etc), but I just need it only the specific modal.
I imagine it has something to do with the 'v-b-modal.modal-1' but I'm not sure how to dynamically set the id of each modal...is there a way to easily set each modal id to match the item.id?
Here is the documentation for Bootstrap-Vue Modals, but I wasn't to find what I needed.
I'd recommend moving the <b-modal> out of your v-for.
This way you only have one.
Then you instead set the user/item you want to edit to a variable, and utilize that in your modal to show the data for the selected user.
Example
new Vue({
el: "#app",
data() {
return {
selectedUser: null,
items: [{
name: "Name 1",
status: "Fail",
details: "Details 1"
},
{
name: "Name 2",
status: "Warn",
details: "Details 2"
}
]
}
},
methods: {
editUser(user) {
this.selectedUser = user;
this.$bvModal.show("edit-modal");
}
}
})
<link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap#4.5.3/dist/css/bootstrap.min.css" />
<link href="https://unpkg.com/bootstrap-vue#2.21.2/dist/bootstrap-vue.css" rel="stylesheet" />
<script src="//unpkg.com/vue#2.6.12/dist/vue.min.js"></script>
<script src="//unpkg.com/bootstrap-vue#2.21.2/dist/bootstrap-vue.min.js"></script>
<div id="app" class="p-4">
<div v-for="item in items" class="mt-3">
{{item.name}} {{item.details}} {{item.status}}
<b-btn variant="primary" #click="editUser(item)">Edit</b-btn>
<b-btn variant="danger">Delete</b-btn>
</div>
<b-modal id="edit-modal" title="Edit User">
<form v-if="selectedUser">
<input v-model="selectedUser.name">
<input v-model="selectedUser.details">
<input v-model="selectedUser.completedBy">
<select v-model="selectedUser.status">
<option>Fail</option>
<option>Warn</option>
<option>Pass</option>
</select><br>
<input type="submit" value="Submit">
</form>
</b-modal>
</div>
If you want to keep the modal inside your v-for, you will have to give it a unique id. If you item has one i would recommend using that, otherwise you can use the index from the v-for.
Simplified example
<div v-for="item in items">
<button v-b-modal:[`edit-modal-${item.id}`]>Edit Item</button>
<b-modal :id="`edit-modal-${item.id}`">
<!-- Modal Content-->
</b-modal>
</div>
One of the most quick way but maybe not the best is Conditional Rendering the modals.
You can toggle the active modal number/index when the Edit is clicked by setting a prop/data/var as activeModal: 1 or n.
Then at the section where you iterate modals you can use v-if directive for modal like: <v-modal v-if="activeModal"> that'd be one way to ignore/not render other modals but would cost re-rendering on each Edit click.
Best/Final solution would be to vue-portal the content to a single universal modal.
I want to implement a pagination component b-pagination w/bootstrap-vue but it will only display page one. I am following the setup in documentation but they only show an example using a table not an unordered list. I have :
<template>
<div class="results overflow-auto" v-cloak>
<h3>Search Results</h3>
<modal v-if="showModal" #close="showModal = false">
<!--
you can use custom content here to overwrite
default content
-->
<template v-slot:header>
<h1>NASA Image</h1>
</template>
<template v-slot:body>
<b-img class="modal-image" v-bind:src="attribute"></b-img>
</template>
</modal>
<!-- ======== Pagination Markup ============ -->
<b-pagination
v-model="currentPage"
:total-rows="rows"
:per-page="perPage"
:items="items"
aria-controls="my-list"
></b-pagination>
<p class="mt-3">Current Page: {{ currentPage }}</p>
<!-- ==========End Pagination Markup ======== -->
<!-- Limit output to 100 items -->
<ul
class="search-results"
id="my-list"
>
<li v-for="(item, index) in propsResults.items.slice(0,100)" :key="index">
{{ item.data[0].title}}
<span>
<b-img
thumbnail
class="thumbnail"
:src="item.links[0].href"
alt="Fluid image"
id="show-modal"
v-on:click="imageModal"
></b-img>
</span>
</li>
</ul>
</div>
</template>
and my javascript is :
export default {
name: "SearchResults",
props: ["propsResults"],
data() {
return {
showModal: false,
attribute: "",
perPage: 10,
currentPage: 1,
items: this.$props.propsResults.items
};
},
computed: {
rows() {
return this.$props.propsResults.items.length;
}
}
The pagination component is displaying all 100 items of items array on one page. I should also note I do not see the items array in the b-pagination props object per Vue dev tools in FF. is this normal? Any insight appreciated..
You should still be using currentPage to choose which items to show. The b-pagination component only changes that number.
Try using this line:
<li v-for="(item, index) in items.slice(10*(currentPage-1),10*(currentPage))" :key="index">