Related
I am developing an e-commerce window and it was working perfectly until the part where I needed to add items to the cart. the browser console showed an error when I wanted to display the items in the cart. However, the strange thing is that it works perfectly in the incognito window. What could be the issue?
Below are my two files:
Code for the cart.vue file:
<template>
<div class="page-cart">
<div class="columns is-multiline">
<div class="column is-12">
<h1 class="title">Cart</h1>
</div>
<div class="column is-12 box">
<table class="table is-fullwidth" v-if="cartTotalLength">
<thead>
<tr>
<th>Product</th>
<th>Price</th>
<th>Quantity</th>
<th>Total</th>
<th></th>
</tr>
</thead>
<tbody>
<CartItem
v-for="item in cart.items"
v-bind:key="item.product.id"
v-bind:initialItem="item"
v-on:removeFromCart="removeFromCart"/>
</tbody>
</table>
<p v-else>Are you gonna stay hungry?...😕</p>
</div>
<div class="column is-12 box">
<h2 class="subtitle">Summary</h2>
<strong>Ksh{{ cartTotalPrice.toFixed(2) }}</strong>, {{ cartTotalLength }} items
<hr>
<router-link to="/cart/checkout" class="button is-dark">Proceed to checkout</router-link>
</div>
</div>
</div>
</template>
<script>
import axios from 'axios'
import CartItem from '#/components/CartItem.vue'
export default {
name: 'Cart',
components: {
CartItem
},
data() {
return {
cart: {
items: []
}
}
},
mounted() {
this.cart = this.$store.state.cart
},
methods: {
removeFromCart(item) {
this.cart.items = this.cart.items.filter(i => i.product.id !== item.productid)
}
},
computed: {
cartTotalLength() {
return this.cart.items.reduce((acc, curVal) => {
return acc += curVal.quantity
}, 0)
},
cartTotalPrice() {
return this.cart.items.reduce((acc, curVal) => {
return acc += curVal.product.price * curVal.quantity
}, 0)
},
}
}
</script>
The following code is for a component, cartItem.vue:
<template>
<tr>
<td><router-link :to="item.product.get_absolute_url">{{ item.product.name }}</router-link></td>
<td>Ksh{{ item.product.price }}</td>
<td>
{{ item.quantity }}
<a #click="decrementQuantity(item)">-</a>
<a #click="incrementQuantity(item)">+</a>
</td>
<td>Ksh{{ getItemTotal(item).toFixed() }}</td>
<td><button class="delete" #click="removeFromCart(item)"></button></td>
</tr>
</template>
<script>
export default {
name: 'CartItem',
props: {
initialItem: Object
},
data() {
return {
item: this.initialItem
}
},
methods: {
getItemTotal(item) {
return item.quantity * item.product.price
},
decrementQuantity(item) {
item.quantity -= 1
if (item.quantity === 0) {
this.$emit('removeFromCart', item)
}
this.updateCart()
},
incrementQuantity(item) {
item.quantity += 1
this.updateCart()
},
updateCart() {
localStorage.setItem('cart', JSON.stringify(this.$store.state.cart))
},
removeFromCart(item) {
this.$emit('removeFromCart', item)
this.updateCart()
},
},
}
</script>
I also noted that the phone browser could not open some contents of the website on the network. Especially the contents from the server are not opened in my phone's browser. I am using Bulma CSS framework.
As I don't know the error from the console, I could only guess the problem.
You should try to use a different browser, or at least remove the data (cookies, storage) for your site, and see what happens. Perhaps there is some old previous data that would lead to a problem.
isGridView: true,
isListView: true,
methods: {
switchView: function() {
this.isGridView = !this.isGridView;
},
switchData: function () {
this.isListView = !this.isListView;
}
<div class="product-grid1">item1</div>
<div class="product-grid2">item2</div>
<div class="product-grid3">item3</div>
<div class="product-list1">item1</div>
<div class="product-list2">item2</div>
<div class="product-list3">item3</div>
<div id="app-gridview">
<div>
<button class="button" v-on:click="switchView()"></button>
<button class="button" v-on:click="switchData()"></button>
</div>
<div v-bind:class="[ isGridView ? 'grid-wrapper' : 'list-wrapper' ]">
<div class="grid-row" v-if="isGridView">
<div class="grid-header" v-for="name in gridData.columns">{{ name }}</div>
</div>
<!-- GridView structure -->
<div v-if="isGridView" class="grid-row" v-for="row in gridData.data">
<div class="list-row-item" v-for="name in gridData.columns">
<div>{{ row[name] }}</div>
</div>
</div>
<!-- ListView structure -->
<div v-if="!isGridView" class="list-row" v-for="row in gridData.data">
<img v-bind:src="row.ImagePath" class="list-image" />
<div class="list-property">
<div class="list-row-item" v-for="name in gridData.columns">
<div class="list-property-name">{{ name }}</div>
<div>{{ row[name] }}</div>
</div>
</div>
</div>
</div>
I tried to implement the list and the grid view, Where I need to toggle between each one. For that i have taken isGrid and isList set to true, And from vue side i am trying to place ternary operator, And switch between each other.
Can you please help me on toggle from the list and grid view.
When you create a component whose view can be changed, I suggest that you use the container-presentational component pattern. Really easy to keep track, and it's a breeze to add a new "view" of the data.
// this is the grid view
// this is a presentational component:
// only displays what is passed through props
Vue.component("GridView", {
props: ["users"],
computed: {
headers() {
if (!this.users.length) return []
return Object.keys(this.users[0])
},
},
template: `
<table>
<thead>
<tr>
<th
v-for="header in headers"
:key="header"
>
{{ header }}
</th>
</tr>
</thead>
<tbody>
<tr
v-for="user in users"
:key="user.id"
>
<td
v-for="(val, key) in user"
:key="user.id + '-' + key"
>
{{ val }}
</td>
</tr>
</tbody>
</table>
`
})
// this is the list view
// this is a presentational component:
// only displays what is passed through props
Vue.component("ListView", {
props: ["users"],
template: `
<ol>
<li
v-for="user in users"
:key="user.id"
>
<div
v-for="(val, key) in user"
:key="user.id + '-' + key"
>
{{ key }}: {{ val }}
</div>
</li>
</ol>
`
})
// this component handles the data:
// fetching, mapping, transforming, etc.
// this is a renderless component
Vue.component("DataContainer", {
data() {
return {
users: []
}
},
mounted() {
this.fetchUsers()
},
methods: {
async fetchUsers() {
try {
const response = await fetch('https://jsonplaceholder.typicode.com/users')
const json = await response.json()
this.users = json.map(({
id,
name,
username,
email
}) => ({
id,
name,
username,
email
}))
} catch (err) {
console.error(err)
}
}
},
render(h) {
// renders nothing, just provides the data
// by passing it through "users"
return this.$scopedSlots.default({
users: this.users,
})
},
})
// the Vue instance
new Vue({
el: "#app",
data() {
return {
layout: "list-view",
}
},
methods: {
switchView() {
this.layout = this.layout === "list-view" ? "grid-view" : "list-view"
}
},
template: `
<div>
<button
#click="switchView"
>
SWITCH VIEW
</button>
<data-container>
<template
#default="{ users }"
>
<component
:is="layout"
v-bind="{ users }"
/>
</template>
</data-container>
</div>
`,
})
table {
border-collapse: collapse;
}
table,
tr,
th,
td {
border: 1px solid black;
}
td,
th {
padding: 4px 8px;
}
th {
background-color: rgba(0, 0, 0, 0.3);
}
<script src="https://cdn.jsdelivr.net/npm/vue#2.6.12/dist/vue.js"></script>
<div id="app"></div>
I understand that this is a bit further away from correcting a v-if condition-handling - but this setup would help you create flexible, extensible & maintainable solution.
I this is the cleanest solution
<template>
<button #click="toggleList">switch render view</button>
<component
:is="currentComponent"
:columns="gridData.columns"
:items="gridData.data"
/>
</template>
<script>
import gridComponent from "./your-grid-component.vue";
import listComponent from "./your-list-component.vue";
export default {
components: {
gridComponent,
listComponent,
},
data() {
return {
listType: "grid", //grid/list
gridData: {
columns: [],
data: [],
},
};
},
methods: {
toggleList() {
this.listType = this.listType === "grid" ? "list" : "grid";
},
},
computed: {
currentComponent() {
return this.listType === "grid" ? "gridComponent" : "listComponent";
},
},
};
</script>
I'm working on a filtering feature in a Vue application. I'm new to Vue, and I have the filter semi-working. It successfully allows me to select an asset type from the dropdown, and will filter the results accordingly. But what's not working is the clearFilters method.
My goal is to reset the assetType to an empty string and the filterResults array to empty, and my thought was that since checking the length of filterResults, when I clear it it would return to displaying the entire un-filtered array.
What am I doing wrong? Any information would be greatly appreciated.
<template>
<div ref="content">
<div class="container pt-3 text-center">
<div class="filter-container">
<div class="btn-group">
<button
v-ripple="'rgba(255, 255, 255, .2)'"
#click="showAssetType = !showAssetType"
class="btn btn-secondary dropdown-toggle"
data-toggle="dropdown"
aria-haspopup="true"
aria-expanded="false"
style="max-height: 40px;">
{{assetType ? assetType : 'Asset Type'}}
</button>
<div class="dropdown-menu" :style="'display:' + (showAssetType ? 'block' : 'none') + ';'">
<a class="dropdown-item" #click.prevent="setAssetFilter('all')" href="#!">All Assets</a>
<a class="dropdown-item" #click.prevent="setAssetFilter('USD'); filterByAssetType()" href="#!">USD</a>
<a class="dropdown-item" #click.prevent="setAssetFilter('GBP'); filterByAssetType()" href="#!">GBP</a>
<a class="dropdown-item" #click.prevent="setAssetFilter('CAD'); filterByAssetType()" href="#!">CAD</a>
</div>
</div>
</div>
</div>
<div id="data-table" v-if="filterResults.length > 0">
<div v-for="(transaction, index) in filterResults" :key="index">
<global-history-item>
<template v-slot:header>
<h1 class="date">{{formatDate(transaction.date)}}</h1>
<div class="transaction-header-wrapper">
<p class="transaction-text">{{formatString(transaction.tx_type)}} Transaction</p>
<p class="transaction-text" style="text-align: right">{{transaction.coin_type}}</p>
</div>
</template>
<template v-slot:content>
<global-transaction :transaction='transaction' #updateClassType="updateClassType" />
</template>
</global-history-item>
</div>
</div>
<div id="data-table">
<div v-for="(item, index) in totalHistory" :key="index">
<div v-if="item.tx_type">
<global-history-item>
<template v-slot:header>
<h1 class="date">{{formatDate(item.date)}}</h1>
<div class="transaction-header-wrapper">
<p class="transaction-text">{{formatString(item.tx_type)}} Transaction</p>
</div>
</template>
<template v-slot:content>
<global-transaction :transaction='item' #updateClassType="updateClassType" />
</template>
</global-history-item>
</div>
<div v-else-if="item.invoice_id">
<global-history-item>
<template v-slot:header>
<h1 class="date">{{formatDate(item.date)}}</h1>
<div class="invoice-header-wrapper">
<p class="invoice-text">Invoice Created</p>
<p class="invoice-text">Invoice #{{item.invoice_id}}</p>
</div>
</template>
<template v-slot:content>
<global-invoice :invoice='item' />
</template>
</global-history-item>
</div>
<div v-else>
<global-history-item>
<template v-slot:header>
<h1 class="date">{{formatDate(item.date)}}</h1>
<div class="invoice-header-wrapper">
<p class="invoice-text">Login Event</p>
<br />
</div>
</template>
<template v-slot:content>
<global-account-activity :message='"A successful login to your account was made"' />
</template>
</global-history-item>
</div>
</div>
</div>
</div>
</template>
<script>
... imports removed for brevity
export default {
name: 'Global',
components: { },
props: ['totalHistory', 'printFormat'],
mixins: ['formatDate', 'formatMenuLabel'],
data () {
return {
showAssetType: false,
showClassType: false,
activityType: '',
assetType: '',
filterResults: [],
printMode: false
}
},
methods: {
setAssetFilter (value) {
this.showAssetType = false
this.assetType = value
},
formatString (str) {
const firstLetter = str.charAt(0)
const remainder = str.slice(1)
return firstLetter.toUpperCase() + remainder
},
updateClassType (transactionRecord) {
this.$store.dispatch('updateTransactionType', transactionRecord)
},
updateTransaction (transactionRecord) {
console.log('in updateTransaction', transactionRecord)
this.$store.dispatch('updateTransactionNote', transactionRecord)
},
filterByAssetType () {
const selectedCurrency = this.assetType
if (this.assetType === 'all') {
this.clearFilters()
} else {
this.filterResults = this.totalHistory.filter(function (trans) {
return trans.currency === selectedCurrency
})
}
},
clearFilters () {
return (this.assetType = '') && (this.filterResults = [])
}
}
}
</script>
So if I am not mistaking, you only want the method clearFilters, to work? If so, try:
clearFilters () {
this.assetType = ''
this.filterResults = []
}
The logical AND operator (&&) is not to chain expressions. It’s to do an expression if the first expression is truthy.
First expression && second expression, example
const questionAnswered = true
console.log(questionAnswered && "Hooray!")
// will log "Hooray!" (Expression 2)
If you set questionAnswered to false, it will log false (expression 1)
I agree with Jens rewrite of clearFilters(). The original looked odd to me.
I had started creating a sample component to demonstrate possibly simplifying the filtering process when there were no answers. Since I have finished it and it works, I am posting it.
ClearFilters.vue
<template>
<div class="clear-filters">
<div class="row">
<div class="col-md-6">
<table class="table table-bordered">
<thead>
<tr>
<th>NAME</th>
<th>CURRENCY</th>
</tr>
</thead>
<tbody>
<tr v-for="asset in filteredAssets" :key="asset.id">
<td>{{ asset.name }}</td>
<td>{{ asset.currency }}</td>
</tr>
</tbody>
</table>
</div>
<div class="col-md-6">
<div class="form-group">
<label class="criteria-label">FILTER CRITERIA:</label>
<select class="form-control" v-model="currentCriteria">
<option v-for="(criteria, index) in criteriaOptions" :key="index" :value="criteria">{{ criteria }}</option>
</select>
</div>
<button type="button" class="btn btn-secondary" #click="resetFilter">Reset</button>
</div>
</div>
</div>
</template>
<script>
import assets from './clear-filters-data.js';
export default {
data() {
return {
assets: assets,
criteriaOptions: [
'ALL', 'USD', 'GBP', 'CAD'
],
currentCriteria: 'ALL'
}
},
computed: {
filteredAssets() {
if (this.currentCriteria === 'ALL') {
return this.assets;
}
else {
return this.assets.filter( asset => asset.currency === this.currentCriteria);
}
}
},
methods: {
resetFilter() {
this.currentCriteria = 'ALL';
}
}
}
</script>
<style scoped>
.criteria-label {
font-weight: bold;
}
</style>
Test data:
const assets = [
{
id: 1,
name: 'asset1',
currency: 'USD'
},
{
id: 2,
name: 'asset2',
currency: 'USD'
},
{
id: 3,
name: 'asset3',
currency: 'USD'
},
{
id: 4,
name: 'asset4',
currency: 'GBP'
},
{
id: 5,
name: 'asset5',
currency: 'GBP'
},
{
id: 6,
name: 'asset6',
currency: 'GBP'
},
{
id: 7,
name: 'asset7',
currency: 'CAD'
},
{
id: 8,
name: 'asset8',
currency: 'CAD'
},
{
id: 9,
name: 'asset9',
currency: 'CAD'
},
]
export default assets;
I'm trying to paginate an array in my vue.js app component. When I try to build it I get an error from the vue ui output console:
warning Replace `·class="page-item"·v-for="(item,·index)·in·onlineCams"·:key="index"·v-if="index·>=·perpage·*·(n-1)·&&·index·<·perpage·*·n"` with `⏎··············class="page-item"⏎··············v-for="(item,·index)·in·onlineCams"⏎··············:key="index"⏎··············v-if="index·>=·perpage·*·(n·-·1)·&&·index·<·perpage·*·n"⏎············` prettier/prettier
193:84 error The 'onlineCams' variable inside 'v-for' directive should be replaced with a computed property that returns filtered array instead. You should not mix 'v-for' with 'v-if'
I'm using this code but it will not work. How I can fix it?
HTML for pagination
<div
class="col-1 text-center p-0 feed-col"
v-for="(cam, idx) in onlineCams"
:key="idx"
>
<img class="img-fluid w-100 h-100" :src="cam.image_url_360x270" />
<div class="card-img-overlay p-0">
<a
class="text-white text-decoration-none stretched-link"
target="_blank"
:href="cam.chat_room_url_revshare"
></a>
</div>
<nav aria-label="Page navigation example">
<ul class="pagination" v-for="n in pages" :key="n">
<li class="page-item" v-for="(item, index) in onlineCams" :key="index" v-if="index >= perpage * (n-1) && index < perpage * n">
<a class="page-link" href="#">{{ index }}</a>
</li>
</ul>
</nav>
</div>
JS code
<script>
export default Vue.extend({
name: "Index",
data() {
return {
onlineCams: [],
perPage: 50
};
},
mounted() {
// other code for update data here
},
computed: {
// cam pagination
length() {
return this.onlineCams.length
},
pages() {
return Math.ceil( this.length / this.perPage )
}
},
methods: { /* other code to fetch data here */ }
}); // end vue
</script>
Probbably, you've mixed Typescript with pure JS.
Some fixes:
<div
class="col-1 text-center p-0 feed-col"
v-for="(cam, idx) in onlineCams"
:key="idx"
>
<img class="img-fluid w-100 h-100" :src="cam.image_url_360x270" />
<div class="card-img-overlay p-0">
<a
class="text-white text-decoration-none stretched-link"
target="_blank"
:href="cam.chat_room_url_revshare"
></a>
</div>
<nav aria-label="Page navigation example">
<ul class="pagination" v-for="n in pages" :key="n">
<li class="page-item" v-for="(item, index) in onlineCams" :key="index" v-if="index >= perpage * (n-1) && index < perpage * n">
<a class="page-link" href="#">{{ index }}</a>
</li>
</ul>
</nav>
</div>
<script lang="ts">
import Vue from 'vue'; // Added this line
export default Vue.extend({
name: "Index",
data() {
return {
onlineCams: [],
perPage: 50
};
},
mounted() {
// other code for update data here
},
computed: {
// cam pagination
length() {
return this.onlineCams.length
},
pages() {
return Math.ceil( this.length / this.perPage )
}
},
methods: { /* other code to fetch data here */ }
}); // end vue
</script>
```
Here is an example of full client-side pagination, filtering and sorting which you can adapt for your needs:
//
new Vue({
el: '#app',
data() {
return {
items: [],
search: '',
sort: {
col: 'id',
desc: false
},
paging: {
page: 1,
pages: [],
perPage: 5,
totalItems: 0,
totalItemsFiltered: 0,
next: function() {
this.page++
},
back: function() {
--this.page
},
},
}
},
watch: {
'paging.perPage': function() {
this.computePaging()
},
items: function() {
this.computePaging()
},
},
computed: {
filtereditems() {
let items = this.items.filter(item => {
return (
item.title.toLowerCase().indexOf(this.search.toLowerCase()) > -1 ||
item.body.toLowerCase().indexOf(this.search.toLowerCase()) > -1
)
})
return (this.paging.page != -1 ?
this.paginate(items, this.paging.perPage, this.paging.page) :
items
).sort((a, b) => {
if (a[this.sort.col] < b[this.sort.col]) return this.sort.desc ? -1 : 1
if (a[this.sort.col] > b[this.sort.col]) return this.sort.desc ? 1 : -1
return 0
})
},
},
mounted() {
this.getItems()
},
methods: {
getItems() {
//https://jsonplaceholder.typicode.com/posts
fetch("https://jsonplaceholder.typicode.com/posts", {
"headers": {
"accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
"accept-language": "en-GB,en-US;q=0.9,en;q=0.8",
"cache-control": "max-age=0",
},
"method": "GET",
"mode": "cors",
"credentials": "include"
}).then(response => response.json())
.then(data => this.items = data);
},
computePaging() {
//
this.paging.pages = []
this.paging.totalItems = this.items.length
//
for (
let i = 1; i <=
Math.ceil(
(this.paging.totalItemsFiltered =
this.items.filter(item => {
return (
item.title.toLowerCase().indexOf(this.search.toLowerCase()) > -1 ||
item.body.toLowerCase().indexOf(this.search.toLowerCase()) > -1
)
}).length / this.paging.perPage)
); i++
) {
this.paging.pages.push(i)
}
},
paginate(array, page_size, page_number) {
--page_number
return array.slice(page_number * page_size, (page_number + 1) * page_size)
},
setSort(col) {
this.paging.page = 1
this.sort = {
col,
desc: this.sort.col === col ? !this.sort.desc : false
}
}
}
});
/*ignore - hide snippets console */
.as-console-wrapper {
max-height: 0px !important;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.14/vue.min.js"></script>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
<div id="app">
<input class="form-control" v-model="search" placeholder="Filter posts..." />
<table class="table table-sm">
<tr>
<th #click="setSort('id')">
<span v-if="this.sort.col === 'id'">{{ sort.desc ? '▲' : '▼'}}</span>ID
</th>
<th #click="setSort('userId')">
<span v-if="this.sort.col === 'userId'">{{ sort.desc ? '▲' : '▼'}}</span>User Id
</th>
<th #click="setSort('title')">
<span v-if="this.sort.col === 'title'">{{ sort.desc ? '▲' : '▼'}}</span>Title
</th>
<th #click="setSort('body')">
<span v-if="this.sort.col === 'body'">{{ sort.desc ? '▲' : '▼'}}</span>Body
</th>
</tr>
<tr v-for="(item, index) in filtereditems" :key="index">
<td>{{ item.id }}</td>
<td>{{ item.userId }}</td>
<td>{{ item.title }}</td>
<td>{{ item.body }}</td>
</tr>
</table>
<div class="row no-gutters" style="background-color: #fafafa;">
<div class="col p-2">
<div class="form-group">
Per Page:
<select class="custom-select ml-1" v-model="paging.perPage" style="width:100px">
<option>5</option>
<option>10</option>
<option>25</option>
<option>50</option>
<option>100</option>
</select>
</div>
</div>
<div class="col p-2">
<ul class="pagination float-right" v-if="items.length > 0">
<li class="page-item pagination-prev" :class="{'disabled': paging.page == 1 }">
<a class="page-link" href="javascript:void(0)" #click="() => { paging.back() }">
<span>«</span>
<span class="sr-only">Previous</span>
</a>
</li>
<template v-for="pageNumber in paging.pages">
<li
class="page-item"
v-if="Math.abs(pageNumber - paging.page) < 5 || pageNumber === paging.pages.length || pageNumber === 1"
:class="{
active: paging.page === pageNumber,
last: (pageNumber === paging.pages.length && Math.abs(pageNumber - paging.page) > 5),
first:(pageNumber === 1 && Math.abs(pageNumber - paging.page) > 5)
}"
#click="() => { paging.page = pageNumber }"
:key="'pn-'+pageNumber"
>
<a class="page-link" href="javascript:void(0)">{{ pageNumber }}</a>
</li>
</template>
<li class="page-item pagination-next" :class="{'disabled': paging.page === paging.pages.length}">
<a class="page-link" href="javascript:void(0)" #click="() => { paging.next() }">
<span>»</span>
<span class="sr-only">Next</span>
</a>
</li>
</ul>
</div>
</div>
</div>
Ideally, if you can implement paging, sorting and filtering server-side its much simpler then as the app can simply set params like ?page=1&sort=id&desc=1&search=abc then you only need run computePaging on items array which will calculate the pagination dom.
Using v-if and v-for together is not recommended, see this link: https://v2.vuejs.org/v2/guide/conditional.html#v-if-with-v-for
You must replace it for a computed property.
Or you can try somethig like this:
<li class="page-item" v-for="(item, index) in onlineCams" :key="index">
<template v-if="index >= perpage * (n-1) && index < perpage * n">
<a class="page-link" href="#">{{ index }}</a>
</template>
</li>
PS - Answer of your comment:
If you want to create a simple pagination, you can see this code as example:
<template>
<div>
<div>{{ itemsPaginate }}</div>
<div>
<button #click="currentPage++">next page</button>
<button #click="currentPage--">prior page</button>
</div>
</div>
</template>
<script>
export default {
name: 'App',
data: () => ({
items: [
{ id: 1, name: 'Test' },
{ id: 2, name: 'Test' },
{ id: 3, name: 'Test' },
{ id: 4, name: 'Test' },
{ id: 5, name: 'Test' },
{ id: 6, name: 'Test' },
{ id: 7, name: 'Test' },
{ id: 8, name: 'Test' },
{ id: 9, name: 'Test' },
{ id: 10, name: 'Test' },
],
currentPage: 1,
itemsByPage: 3,
}),
computed: {
itemsPaginate() {
let result = [];
let init = (this.currentPage - 1) * this.itemsByPage;
for (let i = init; i < this.items.length; i++) {
result.push(this.items[i]);
if (result.length >= this.itemsByPage) {
break;
}
}
return result;
},
},
};
</script>
If you need the others options you can add.
I am a little newbie using Vue JS, so i started with Vue 2.
I need to remove an array item but the button that trigger that method is inside a template and the v-for is inside a parent template.
This is my HTML:
MAIN
<div id="main">
<div class="panel-group" id="panelGrp">
<div class="row panelTopSpacing" v-for="panel in panels" is="panel-row" :panel.sync="panel" :general-fields="generalFields" :assistants="assistants" :companies="companies" :positions="positions"></div>
</div>
</div>
CHILD
//CHILD TEMPLATE
<template id="panelsTpl">
<div class="panel panel-success">
<div class="panel-heading">
{{panel.title}}
<a :class="panel.classObj.link" role="button" data-toggle="collapse" data-parent="#panelGrp" :href.sync="'#'+panel.id"></a>
<i :class="panel.infoIcon"></i>
</div>
<div :id.sync="panel.id" :class="panel.classObj.panel">
<div class="panel-body">
<div class="container-fluid" v-if="panel.id === 'genInfo'">
<div class="row">
<div v-for="genField in generalFields" is="general-field" :gen-field.sync="genField"></div>
</div>
</div>
<div class="container-fluid" v-else-if="panel.id === 'assistants'">
<div class="row">
<table class="table table-striped table-responsive table-hover">
<tr>
<th>Internal?</th>
<th>Can read?</th>
<th>Position</th>
<th>Name</th>
<th>Company</th>
<th width="50px"> </th>
</tr>
<tr>
<td><input type="checkbox"></td>
<td><input type="checkbox" ></td>
<td></td>
<td><input type="text" class="form-control"></td>
<td></td>
<td><span class="glyphicon glyphicon-ok" aria-hidden="true"></span></td>
</tr>
<tr v-for="(assistnt,index) in assistants" is="assistant-row" :assistant.sync="assistnt"></tr>
</table>
</div>
</div>
</div>
</div>
</div>
</template>
CHILD OF CHILD TEMPLATE
<template id="asstntsTpl">
<tr v-if="! editing">
<td>{{ assistant.internal }}</td>
<td>{{ assistant.allowRead }}</td>
<td>{{ assistant.positionId | position }}</td>
<td>{{ assistant.name }}</td>
<td>{{ assistant.cmpnyId | company }}</td>
<td>
<span class="glyphicon glyphicon-pencil" aria-hidden="true"></span>
<span class="glyphicon glyphicon-trash" aria-hidden="true"></span>
</td>
</tr>
<tr v-else>
<td><input type="checkbox" v-model="assistant.internal"></td>
<td><input type="checkbox" v-model="assistant.allowRead"></td>
<td><!--<select-position :position="positions" :id.sync="assistant.positionId"></select-position>--></td>
<td><input type="text" v-model="assistant.name" class="form-control"></td>
<td><!--<select-company :company="companies" :id.sync="assistant.cmpnyId"></select-company>--></td>
<td><span class="glyphicon glyphicon-ok" aria-hidden="true"></span></td>
</tr>
</template>
JS:
var main = new Vue({
el: "#main",
data: {
valid: false,
new_assistant: {
id: "",
name: "",
internal: true,
cmpnyId: "",
positionId: "",
allowRead: false
},
panels: [
{id: "genInfo", title: "General Info", classObj: {panel: "panel-collapse collapse in", link: ""}, infoIcon: "fas fa-file-alt infoIcon pull-right"},
{id: "assistants", title: "Assistants", classObj: {panel: "panel-collapse collapse", link: "collapsed"}, infoIcon: "fas fa-users infoIcon pull-right"},
{id: "agrmtns", title: "Agreements", classObj: {panel: "panel-collapse collapse", link: "collapsed"}, infoIcon: "fas fa-file-signature infoIcon pull-right"}
]
assistants: [
{id: "1",
name: "Bob",
internal: true,
cmpnyId: "1",
positionId: "1",
allowRead: false},
{id: "2",
name: "John",
internal: true,
cmpnyId: "1",
positionId: "1",
allowRead: false}
],
companies: [
{id: "1", name: "cmpny1"},
{id: "2", name: "cmpny2"},
{id: "3", name: "cmpny3"}
],
positions: [
{id: "1", name: "Pos1"},
{id: "2", name: "Pos2"},
{id: "3", name: "Pos3"}
]
},
methods: {
addAssistant: function () {
this.assistants.push(this.new_assistant);
this.new_assistant = {
id: "",
name: "",
internal: true,
cmpny_id: "",
position_id: "",
allowRead: false};
}
}
Everthing is working fine at this moment, but when I try to delete a row from the table that is populated with assistants array, it deletes the first row however i clicked the trash icon of second row.
VUE COMPONENT:
Vue.component("assistant-row", {
template: "#asstntsTpl",
props: ["nw-assistant", "assistant"],
data: function () {
return {
editing: false
};
},
methods: {
remove: function (index) {
this.$parent.assistants.splice(**index**, 1);
},
edit: function () {
this.editing = true;
},
update: function () {
this.editing = false;
}
}
});
Seems like splice is not working at all.
PD:::
I know how to use it in a simple scenario, like:
<li v-for="cat,index in categories">
<button #click="remove(index)">Remove</button>
</li>
I made this little jsfiddle simulating something like above code:
https://jsfiddle.net/empiricalDev/eywraw8t/399050/
Thanks in advance.
Regards!
You could try to use this.$emit() to emit an event to the parent component like this.$emit("delete",this.todo); which has event name as first parameter and this.todo as a second one, in the parent component add #delete="removechild" as follow :
<tod v-for="(todo, index) in todos" :todo="todo" #delete="removechild"></tod>
and implement your removechild in the parent component as follow :
removechild(todo){
this.todos.splice(this.todos.indexOf(todo),1);
}
Note :
if your prop is an object like {id:1,name:"todo 1"} you could filter your todos array like this.todos= this.todos.filter((item)=>{return item.id!=todo.id});
Vue.component("tod",{
template:"#tpl",
props:["todo"],
methods: {
remove: function() {
this.$emit("delete",this.todo);
}
}
});
new Vue({
el: '#app',
data: {
todos: ['Buy milk', 'Do exercises', 'Write a book', 'Plant a tree']
},
methods:{
removechild(todo){
this.todos= this.todos.filter((item)=>{return item!=todo});
}
}
})
body {
background: #20262E;
padding: 20px;
font-family: Helvetica;
}
#app {
background: #fff;
border-radius: 4px;
padding: 20px;
transition: all 0.2s;
}
li {
margin: 8px 0;
}
h2 {
font-weight: bold;
margin-bottom: 15px;
}
del {
color: rgba(0, 0, 0, 0.3);
}
<!DOCTYPE html>
<html>
<head>
<meta name="description" content="Vue.delete">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>JS Bin</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.2.1/vue.min.js"></script>
</head>
<body>
<div id="app">
<ol>
<tod v-for="(todo, index) in todos" :todo="todo" #delete="removechild"></tod>
</ol>
</div>
<template id="tpl">
<li>
{{todo}}
<button #click="remove">×</button>
</li>
</template>
</body>
</html>