I am trying to show the message Cart empty in my modal when the cart is empty. When a product is added to the cart, that message should be removed and replaced with the product.
The product being added to the modal is working, but when I delete, and the quantity is 0, the message does not show.
I am using Vuex, but when tryin to use v-if and v-else within the v-for loop, the Cart empty message does not show. Below is an example of my MinCart.vue modal.
<div class="modal-body" v-for="item in this.$store.state.cart">
<div v-if="noItemsInCart">Cart empty</div>
<div v-else>
<div class="card mb-3 border-0">
<div class="row no-gutters">
<div class="col-sm-4">
<img :src="item.productImage" width="120px" class="align-self-center mr-3" alt="">
</div>
<div class="col-sm-6">
<div class="card-block px-2">
<h6 class="card-title">{{ item.productName }}</h6>
<p class="card-text">{{ item.productPrice | currency }}</p>
<p class="card-text">Quantity: {{ item.productQuantity }}</p>
</div>
</div>
<div class="col-sm-2">
<div class="card-body pt-1">
<div class="d-flex justify-content-between align-items-center">
<a #click="$store.commit('removeFromCart', item)" type="button" class="card-link-secondary small text-uppercase mr-3">
<i class="fas fa-trash-alt mr-1"></i>
</a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
Here is my computed prop:
computed: {
noItemsInCart() {
return this.$store.getters.cartEmpty
}
}
And finally, my store.js with the getter method;
state: {
cart: cart ? JSON.parse(cart) : [],
},
getters: {
cartEmpty: state => {
let qty = 0;
state.cart.filter((item) => {
qty = item.productQuantity
})
}
}
Your Vuex getter doesn't return anything, so it's always falsy, which makes the computed prop (noItemsInCart) always truthy.
But your getter also isn't using Array.prototype.filter correctly. The filter is assigning qty instead of comparing it, and the filter callback returns nothing.
I'm guessing you were trying to count the items in the cart by checking productQuantity of each item. That would be done with Array.prototype.some like this:
{
getters: {
// find any item that has a positive `productQuantity` value
cartEmpty: state => state.cart.some(item => item.productQuantity > 0)
}
}
Also, your template should move the v-for loop into the v-else block. Otherwise, an empty cart will prevent the v-if block from rendering (thus no Cart empty message):
<div v-if="noItemsInCart">Cart empty</div>
<div v-else class="modal-body" v-for="item in this.$store.state.cart">
</div>
You don't need getter in this case. You just need to return in computed something like this:
computed: {
isEmptyCart() {
return Boolean(~this.$store.state.cart.length); // or use compare ...length > 0
}.
}
Or more beautiful example with mapState:
computed: {
...mapState({
cart: ({ cart }) => cart; // don't use this.$store.state.cart in template
isEmptyCart: ({ cart }) => Boolean(~cart.length),
}),
}
Related
i am doing a chatbox... I need to display array of chat convestation .. i could retrieve the data , and the card is looping , but i need to out in a row and display it chat title .. can anyone help me with it .
my converstation component
<template>
<div class="row w-100">
<div class="card" v-for="conversation in conversations" :key="i">
<div class="card-body">
<h5 class="card-title">Card name </h5>
<p class="card-text">Last Chat </p>
<p class="card-text"><small class="text-muted">Last updated 3 mins ago</small></p>
</div>
</div>
</div>
</template>
<script>
export default {
props: ['conversation_index_route'],
data() {
return {
conversations: []
}
},
mounted() {
this.getConversations();
},
methods: {
getConversations: function() {
let self = this;
axios.get(this.conversation_index_route)
.then(response => {
self.conversations = response.data.data;
})
}
}
}
</script>
this a the conversation objects
0:Object
centre_id:5
children:"3828,4197,7748,11591,12376,12394,12433,12441,12754,12755,12765,13284,14149,14602,14656,14941"
classes:"139"
cover_image:"https://via.placeholder.com/150x150"
created_at:"2020-06-09 19:14:20"
exited_users:null
id:258
latest:Object
latest_chat_id:1921
parent_users:"2413,3461,11690,11770,11786,12262,12263,13077,14232,15275,16713"
staff_users:"321,16707,12117,13488,14083"
status_id:1
title:"Class 0906"
unread:0
updated_at:"2020-06-09 19:14:20"
If you want to loop on conversation array add this into your inner elements.
<div class="card" v-for="conversation in conversations" :key="i">
<div class="card-body">
<h5 class="card-title"> {{ conversation.title }} </h5>
<p class="card-text"> {{ conversation.latest }} </p>
<p class="card-text"><small class="text-muted"> {{ conversation.updated_at || computeAgo }} </small></p>
</div>
</div>
Note that above updated_at function doesn't do what have to do. You should create a filter like computeAgo for it then use Date() to compute exactly how many minutes ago.
I have written the following code:
<div>
<div id="app" class="container">
<div class="grid">
<article v-for="tool in tools">
<div class="title">
<h3>{{capitalizeFirstLetter(tool.name)}}</h3>
</div>
<div class="description">
{{tool.description}}
</div>
<br>
<div>
<toggle-button :value=tool.status color="#82C7EB" :width=100 :height=30 :sync="true" :labels="{checked: 'Following', unchecked: 'Unfollowing'}" #change="onChange(tool)"/>
</div>
</article>
</div>
</div>
</div>
Recently I started using Bootstrap-Vue. I'm trying to figure out how to add pagination on the bottom.
I'm not sure how aria-controls works. My goal is to have 9 blocks of tools on each page. How should I add the pagination so I could move to the next 9 blocks of tools?
Since it wasn't clear if you needed client-side pagination or serverside i made an example of both in the snippet.
For simplicity I've made it 3 per page, but you could change it to 9.
The first one gets the initial page on load, and then calls the API every time the page changes by using a watcher, that calls a method with the new page which then retrieves that place and replaces our old data with the new.
The second one loads all the data on page load, and instead slices the data array based on the per_page property, so that only the items for that page is shown.
For this I've used a computed property which automatically updates based on the properties used inside it.
Both paginations have aria-controls defined with the id of the container of our page elements. This is used to tell screen readers what elements the pagination changes.
The classes row, col-*, border, mx-auto, h2 and text-center is classes used for styling and layout and isn't part of the actual solution, so you can freely change or remove them.
new Vue({
el: '#app',
computed: {
pagination2CurrentItems() {
const startIndex = (this.pagination2.current_page - 1) * this.pagination2.per_page;
const endIndex = startIndex + this.pagination2.per_page;
return this.pagination2.items.slice(startIndex, endIndex)
}
},
created() {
this.getPagination1Data()
this.getPagination2Data()
},
filters: {
capitalizeFirstLetter(value) {
return value.charAt(0).toUpperCase() + value.slice(1)
}
},
data() {
return {
pagination1: {
items: [],
per_page: 3,
total_rows: 0,
current_page: 1
},
pagination2: {
items: [],
per_page: 3,
total_rows: 0,
current_page: 1
}
}
},
methods: {
getPagination1Data(page = 1) {
fetch(`https://reqres.in/api/unknown?page=${page}&per_page=3`)
.then((response) => {
return response.json();
})
.then((data) => {
this.pagination1.total_rows = data.total;
this.pagination1.items = data.data;
});
},
getPagination2Data() {
/*
This endpoint only has 12 items total,
so this will get all in one call
*/
fetch(`https://reqres.in/api/unknown?per_page=12`)
.then((response) => {
return response.json();
})
.then((data) => {
this.pagination2.total_rows = data.total;
this.pagination2.items = data.data;
});
}
},
watch: {
'pagination1.current_page'(newPage) {
this.getPagination1Data(newPage)
}
}
})
<script src="https://unpkg.com/vue#2.6.11/dist/vue.min.js"></script>
<script src="https://unpkg.com/bootstrap-vue#2.5.0/dist/bootstrap-vue.js"></script>
<link href="https://unpkg.com/bootstrap#4.4.1/dist/css/bootstrap.min.css" rel="stylesheet"/>
<link href="https://unpkg.com/bootstrap-vue#2.5.0/dist/bootstrap-vue.css" rel="stylesheet"/>
<div id="app" class="container">
<div id="tools_list_1" class="row">
<div class="col-12 h2 text-center">
Server pagination
</div>
<article class="col-3 mx-auto border" v-for="tool in pagination1.items">
<div class="title">
<h3>{{ tool.name | capitalizeFirstLetter }}</h3>
</div>
<div class="description">
{{ tool.pantone_value }}
</div>
</article>
</div>
<div class="row mt-2">
<div class="col-12">
<b-pagination
v-model="pagination1.current_page"
:per-page="pagination1.per_page"
:total-rows="pagination1.total_rows"
aria-controls="tools_list_1"
>
</b-pagination>
</div>
</div>
<div id="tools_list_2" class="row">
<div class="col-12 h2 text-center">
Client pagination
</div>
<article class="col-3 mx-auto border" v-for="tool in pagination2CurrentItems">
<div class="title">
<h3>{{ tool.name | capitalizeFirstLetter }}</h3>
</div>
<div class="description">
{{ tool.pantone_value }}
</div>
</article>
</div>
<div class="row mt-2">
<div class="col-12">
<b-pagination
v-model="pagination2.current_page"
:per-page="pagination2.per_page"
:total-rows="pagination2.total_rows"
aria-controls="tools_list_2"
>
</b-pagination>
</div>
</div>
</div>
If you are interested in server side pagination with url changes of query params (using vue-router in history mode) and you need browser history (back/forward) to work properly, you can check my answer to a similar question. I think it can be adapted to not use b-table.
Place pagination number in url Vuejs
I have problems accessing this "name" property on the component. I can only access it statically.
<template>
<div class="col-md-12">
<p
v-for="channel in channels"
:key="channel.id"
class="channel"
:class="{ 'active': channel.id == activeChannel }"
#click="setChannel(channel.id)">
{{ channel.users[0].name }}
</p>
</div>
</template>
Here is an Image of my Vue Devtools
So I have an v-for loop over channels, and I want to: Access the Usernames for each channel (if it is not my own preferably as "username" is set on my own i think its easy to exclude it right?) So that in the end In Channel 1 when there are 2 Users , I want to show the corresponding username, so the "other username", the one i am chatting with, and he should see my name that is the initial goal.
I thought of doing something like this:
<template>
<div class="col-md-12">
<p
v-for="channel in channels"
:key="channel.id"
class="channel"
:class="{ 'active': channel.id == activeChannel }"
#click="setChannel(channel.id)">
<!-- {{ channel.users[0].name }} -->
<span v-for="user,key in channel">{{key}}</span>
</p>
</div>
it at least displays the content of the channels object for each channel, but something like this isnt gonna work: key.user.name , unfortunately im stuck here. please help :)
edit: here is a dd() of the view
click
EDIT 2: Parent Data Provided:
//chat-app.blade.php
<div id="app">
<div class="container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<div class="panel panel-default">
<div class="panel-heading">Chats</div>
<vue-chat :channels="{{ $channels }}" ></vue-chat>
</div>
</div>
</div>
</div>
</div>
</div>
//<vue-chat> component
<template>
<div class="chat">
<div class="container">
<div class="row">
<div class="col-md-3">
<vue-chat-channels
:channels="channels"
:active-channel="activeChannel"
#channelChanged="onChannelChanged"
:username="sername"
></vue-chat-channels>
</div>
<div class="col-md-3">
<vue-chat-messages :messages="messages"></vue-chat-messages>
</div>
<div class="col-md-3">participants</div>
</div>
<div class="message-input-wrapper col-md-12"><vue-chat-new-message :active-channel="activeChannel"
:username="username"></vue-chat-new-message></div>
</div>
</div>
</template>
<script>
export default {
props: ["channels"],
data() {
return {
activeChannel: this.channels[0].id,
messages: [],
username: ''
};
},
methods: {
fetchMessages() {
let endpoint = `/channels/${this.activeChannel}/messages`;
axios.get(endpoint).then(({ data }) => {
this.messages = data;
});
},
onChannelChanged(id) {
this.activeChannel = id;
this.fetchMessages();
}
},
created() {
this.fetchMessages();
axios.get('/userfetch').then( ({data}) => {
console.log("Current User: "+data.name);
this.username = data.name;
});
console.log(this.channels[0].name);
// for (let channel of this.channels) {
this.channels.forEach(channel => {
// Channelname
window.Echo.channel('presence-'+channel.name)
.listen('MessageSent', (channel) => {
console.log(channel.data.message);
this.messages.push({ message: channel.data.message, author_username: channel.data.author_username});
if (this.activeChannel == channel.id) {
console.log("received message");
}
});
});
}
};
</script>
<style>
</style>
//ChatController.php
public function index()
{
$channels = Channel::with('users')->whereHas('users', function($q) {
$q->where('user_id',Auth::id());
})->get();
$user = Auth::user()->name;
return view('chat-app' , compact('channels','user'));
}
Short Explanation: ChatController returns the blade view, which has the data channels and user (my username) , and then vue comes into play which should pass down the prop of my username but i couldnt get it to work just yet
So you need to access users in every channel.
You can try like this:
<div class="col-md-12">
<p
v-for="channel in channels"
:key="channel.id"
class="channel"
:class="{ 'active': channel.id == activeChannel }"
#click="setChannel(channel.id)">
<span v-for="user in channel.users">
{{ user.name }}
</span>
</p>
</div>
This should work. If you have errors provide it here.
If you need to compare every user you can do it simply with v-if:
<span v-for="user in channel.users">
<span v-if="user.name === parentdata">
{{ user.name }}
</span>
</span>
I am facing a problem in deleting item from an array. Array splice supposed to work but its not working like I want. Its always delete the item from last. I am using Vue.js . I am pushing item dynamically to an array. But after click remove its delete from the last. why I am facing this. I am attaching the codes.
<template>
<div>
<span class="badge badge-pill mb-10 px-10 py-5 btn-add" :class="btnClass" #click="addBtn"><i class="fa fa-plus mr-5"></i>Button</span>
<div class="block-content block-content-full block-content-sm bg-body-light font-size-sm" v-if="buttons.length > 0">
<div v-for="(item, index) in buttons">
<div class="field-button">
<div class="delete_btn"><i #click="remove(index)" class="fa fa-trash-o"></i></div>
<flow-button v-model="item.title" :showLabel="false" className="btn btn-block min-width-125 mb-10 btn-border" mainWrapperClass="mb-0" wrapperClass="pt-0" placeholder="Button Title"></flow-button>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import flowButton from '../assets/flow-button'
export default {
name: "textArea",
props:{
index : Number
},
data() {
return {
buttons : [],
btnClass : 'badge-primary',
}
}
components : {
flowButton
},
methods : {
addBtn () {
if(this.buttons.length >= 2) {
this.btnClass = 'btn-secondary'
}
if(this.buttons.length < 3) {
this.buttons.push({
title : ''
});
}
},
remove(index) {
this.buttons.splice(index, 1)
}
}
}
</script>
This must be because of your flow-button I have tried to replicate your error but endup to this code. I just replaced the flow-button with input and it works. Try the code below.
Use v-bind:key="index", When Vue is updating a list of elements rendered with v-for, by default it uses an “in-place patch” strategy. If the order of the data items has changed, instead of moving the DOM elements to match the order of the items, Vue will patch each element in-place and make sure it reflects what should be rendered at that particular index. This is similar to the behavior of track-by="$index"
You have missing comma between data and components, I remove the component here it won't cause any error now, and more tips don't mixed double quotes with single qoutes.
<template>
<div>
<span class="badge badge-pill mb-10 px-10 py-5 btn-add" :class="btnClass" #click="addBtn"><i class="fa fa-plus mr-5"></i>Button</span>
<div class="block-content block-content-full block-content-sm bg-body-light font-size-sm" v-if="buttons.length > 0">
<div v-for="(item, index) in buttons" v-bind:key="index">
<div class="field-button">
<div class="delete_btn"><i #click="remove(index)" class="fa fa-trash-o">sdfsdff</i></div>
<input type="text" v-model="item.title" :showLabel="false" className="btn btn-block min-width-125 mb-10 btn-border" mainWrapperClass="mb-0" wrapperClass="pt-0" placeholder="Button Title"/>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'textArea',
props: {
index: Number
},
data () {
return {
buttons: [],
btnClass: 'badge-primary'
}
},
methods: {
addBtn () {
if (this.buttons.length >= 2) {
this.btnClass = 'btn-secondary'
}
if (this.buttons.length < 3) {
this.buttons.push({
title: ''
})
}
},
remove (index) {
this.buttons.splice(index, 1)
}
}
}
</script>
I think that you may be facing a conflict with the index prop of your component. Try to use a different name for the index of your v-for loop:
<div v-for="(item, ind) in buttons">
<div class="field-button">
<div class="delete_btn"><i #click="remove(ind)" class="fa fa-trash-o"></i></div>
<flow-button v-model="item.title" :showLabel="false" className="btn btn-block min-width-125 mb-10 btn-border" mainWrapperClass="mb-0" wrapperClass="pt-0" placeholder="Button Title"></flow-button>
</div>
</div>
Try this. Removing an item correctly using this.
<div v-for="(item, ind) in buttons" :key="JSON.stringify(item)">
Given the next v-for:
<div class="container-fluid" id="networdapp" style="display:none;">
<div class="row" >
<div v-for="result in results" class="col-sm-6" >
<div class="card m-3 h-240 bg-light" >
<div class="card-header text-center" > {{ result.title }} </div>
<div class="card-body" style="height:200px" >
<p class="card-text" v-html="result.prevDesc"></p>
</div>
<div class="card-footer bg-transparent border-info">
<a href="/details" class="btn btn-info" #click="getData(result)" >Details</a>
</div>
</div>
</div>
</div>
</div>
And the next Vue.js script:
<script type="text/javascript">
const vm = new Vue({
el: '#networdapp',
data: {
results:[]
},
methods: {
getData: function(result){
window.alert($(this).parents("#networdapp").find(".card-header.text-center").outerHTML);
window.alert(document.getElementsByClassName("card-header").outerHTML);
window.alert(result.outerHTML);
}
},
mounted() {
axios.get('/getJson')
.then(response => {
this.results = response.data;
})
.catch( e => {
console.log(e);
});
}
});
</script>
I want to get data from a specific iteration,let's say if I click the "Details" button of the 3rd div from the v-for I want to get the {{result.title }} data from the 3rd for.Is it possible?I've been reading the Vue.js documentation but I didn't find anything about reading the data from DOM.If it is not possible,than how can I do that without Vue.js?Is there any other option?
The main goal is to get this data and to put it into a js object passing it to another webpage.
you have to pass index key and use is to get from results's position.
change the for loop div into
<div v-for="(result,i) in results" :key="i" class="col-sm-6" >
also chnange the methods parameter
<a href="/details" class="btn btn-info" #click="getData(i)" >Details</a>
and the method will get the index key and here i have used console to see the result.title that you have wanted. you can use it any how you want.
getData: function(key){
console.log(this.results[key].title)
}
so
Given the next v-for:
<div class="container-fluid" id="networdapp" style="display:none;">
<div class="row" >
<div v-for="(result,i) in results" :key="i" class="col-sm-6" >
<div class="card m-3 h-240 bg-light" >
<div class="card-header text-center" > {{ result.title }} </div>
<div class="card-body" style="height:200px" >
<p class="card-text" v-html="result.prevDesc"></p>
</div>
<div class="card-footer bg-transparent border-info">
<a href="/details" class="btn btn-info" #click="getData(i)" >Details</a>
</div>
</div>
</div>
</div>
And the next Vue.js script:
<script type="text/javascript">
const vm = new Vue({
el: '#networdapp',
data: {
results:[]
},
methods: {
getData: function(key){
console.log(this.results[key].title)
}
},
mounted() {
axios.get('/getJson')
.then(response => {
this.results = response.data;
})
.catch( e => {
console.log(e);
});
}
});
To get the data you want to access in the results array, you can use an index in your v-for loop
v-for="(result, index) in results"
you can check the docs here https://v2.vuejs.org/v2/guide/list.html
I also strongly recommend you to add a key attribute after the v-for to help vue.js
keep track of each result, see https://v2.vuejs.org/v2/guide/list.html#key