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.
Related
I'm wondering how to add and remove specific elements from dynamically added sections. I'm fighting with it for a week and can't find the solution for my case.
Problem is clearly described in the images.
To sum up the biggest problem is that the removing buttons don't know to which element they belong.
<template>
<div>
<div class="section" v-for="(el, index) in sections" :key="index" >
<td>
SECTION:{{index}}
<div class="element" v-for="(sel, index) in sections" :key="index">
<span>PICK UP YOUR ANIMAL</span>
<select >
<option v-for="(item, index) in zoo" :key="index" v-bind:value="item">
<option>{{item.name}}</option>
</option>
</select>
<button class="rem el" #click='removeElement(index, el)'>-</button>
</div>
</td>
<button class="addEleme" #click='addElement'>add element</button>
<button class="rem sec" #click='removeSection'>-</button>
</div>
<button class="addSect" #click='addSection'>add section</button>
<p>{{test}}</p>
<span>{{sections}}</span>
<span>{{elements}}</span>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
data() {
return {
zoo: [
{name: "Crocodyle Sara"},
{name: "Elepfhant Mike"},
{name: "Lion Monika"},
{name: "Shark Robert"},
{name: "Zebra Antony"}
],
sections:[],
elements:[],
test:"",
i:1
}
},
methods:{
// Sections meth
//----------------------------------------//
addSection(){
this.sections.push(this.zoo)
this.i+=1
this.test=`gucci: check console`
console.log('this.sections: ', this.sections);
},
removeSection(index){
this.sections.splice(index, 1)
this.test="remove section"
},
// Elemnts meth
//----------------------------------------//
addElement(){
this.test=`not gucci: should add element`
},
removeElement(index){
this.elements.splice(index, 1)
this.test="not gucci should remove element"
}
}
}
</script>
Take a look in following simple solutions with another component:
Vue.component('mySelect', {
template: `
<div>
<select v-model="selected" #change="selectAnimal">
<option disabled value="">Please select one</option>
<option v-for="(item, index) in zoo" :key="index" v-bind:value="item">
{{item.name}}
</option>
</select>
<button class="rem el" #click='remove'>remove element</button>
</div>
`,
props: ['idx', 'el'],
data() {
return {
zoo: [{name: "Crocodyle Sara"}, {name: "Elepfhant Mike"}, {name: "Lion Monika"}, {name: "Shark Robert"}, {name: "Zebra Antony"}],
selected: ''
}
},
methods: {
selectAnimal() {
const obj = {id: this.el, ...this.selected}
this.$emit('sel', obj)
},
remove() {
const obj = {idx: this.idx, el: this.el}
this.$emit('rem', obj)
}
}
})
new Vue({
el: "#demo",
data() {
return {
sections: [],
selected: []
}
},
methods:{
newId() {
return Math.floor(Date.now() / 1000)
},
addSection(){
this.sections.push({id: this.newId(), elements: [this.newId()]})
},
removeSection(index){
this.sections.splice(index, 1)
},
addElement(idx){
const nr = this.sections[idx].elements.length
this.sections[idx].elements.push(this.newId())
},
removeElement(obj){
const elems = this.sections[obj.idx].elements
const element = elems.find(e => e === obj.el)
this.selected = this.selected.filter(f => f.id !== element)
this.sections[obj.idx].elements = elems.filter(e => e !== element)
},
handleSelect(sel) {
const found = this.selected.find(s => s.id === sel.id)
this.selected = this.selected.map(x => (x.id === sel.id) ? sel : x)
if (!found) this.selected.push(sel)
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="demo">
<ul class="section" v-for="(section, index) in sections" :key="index" >
SECTION:{{section.id}}
<li class="element" v-for="(element, idx) in section.elements" :key="idx">
<span>PICK UP YOUR ANIMAL {{element}}</span>
<my-select :el="element" :idx="index" #sel="handleSelect" #rem="removeElement"></my-select>
</li>
<button class="addEleme" #click='addElement(index)'>add element</button>
<button class="rem sec" #click='removeSection(index)'>remove section</button>
</ul>
<button class="addSect" #click='addSection'>add section</button>
<p>Sections: {{ sections }}</p>
<p>Selected: {{ selected }}</p>
</div>
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 have offers cards list rendering thru the loop. Every 3rd col (bootstrap) elements i add row div. Now i need to add another col element (banner block) for every 6th element. For render some thing like that:
How i can implement that?
My code now
<div class="row" v-for="i in Math.ceil(offers.length / 3)">
<div class="col-xl-4 col-lg-4 col-md-6 col-sm-12 col-12" v-for="offer in offers.slice((i-1)*3, i*3)">
<h2>{{offer.name}}</h2>
<h2>{{offer.desc}}</h2>
</div>
</div>
for loop:
<div class="mycol" v-for="(offer,ind) in offers">
<template v-if="ind % 5 == 0">
<h2>banner</banner>
</template>
<template v-else>
<h2>{{offer.name}}</h2>
<h2>{{offer.desc}}</h2>
</template>
</div>
for new line for every third col you can use css
.mycol:nth-child(3n+1){
clear:left;
}
I would recommend you do less programming in the view and more in the view model. Create a computed that splits up your data into series of offers and banners, and also into rows, then use that computed in a straightforward way.
const chunk = (arr, size) =>
arr
.reduce((acc, _, i) =>
(i % size) ?
acc :
[...acc, arr.slice(i, i + size)], []);
new Vue({
el: '#app',
data: {
offers: []
},
computed: {
rows() {
const withBanners = chunk(this.offers, 5).map((arr) => [...arr, {name: 'banner', type: 'Banner'}]).reduce((a, b) => a.concat(b), []);
return chunk(withBanners, 3);
}
},
mounted() {
setTimeout(() => {
this.offers = [{
name: 'offer'
},
{
name: 'offer'
},
{
name: 'offer'
},
{
name: 'offer'
},
{
name: 'offer'
},
{
name: 'offer'
},
{
name: 'offer'
},
{
name: 'offer'
},
{
name: 'offer'
},
{
name: 'offer'
},
{
name: 'offer'
}
];
}, 500);
}
});
#app {
display: grid;
}
.row {
display: grid;
grid-gap: 2rem;
grid-template-columns: repeat(3, auto);
justify-content: left;
}
.box {
width: 8rem;
height: 8rem;
}
.banner {
background-color: #f9c;
}
.offer {
background-color: #99f;
}
<script src="https://unpkg.com/vue#latest/dist/vue.js"></script>
<div id="app">
<div class="row" v-for="row in rows">
<div class="col-xl-4 col-lg-4 col-md-6 col-sm-12 col-12" v-for="item in row">
<div v-if="item.type === 'Banner'" class="banner box">
<h2>{{item.name}}</h2>
</div>
<div v-else class="offer box">
<h2>{{item.name}}</h2>
</div>
</div>
</div>
</div>
This should do exactly what you want.. I had to manipulate the data some because Vue's templating language is not designed to handle the logic for this kind of use case
HTML
<div id="app">
<div v-for="items in rows" class="row">
<div v-for="item in items" class="col-xl-4 col-lg-4 col-md-6 col-sm-12 col-12">{{item}}</div>
</div>
</div>
SCRIPT
created () {
while (this.items.length > 0) {
const howMany = (this.rows.length % 3 === 0) ? 3 : 2
const row = this.items.splice(0, howMany)
if (howMany === 2) row.push('banner')
this.rows.push(row)
}
},
https://jsfiddle.net/jamesharrington/k6c0rgL3/17/
I assume that you want to add a banner every 6 elemtns, but you want to show the 6th. I would handle this on my data object, inserting the banner inside it. It is easier. You could split your array on this way.
let firstPart = myData.slice(0,5)
let lastPart = myData.slice(5,)
let newData = [...firstPart, banner, ...lastPart]
Now, you just need to do this every 6 elements.
I recommend to use flex if it is possible.
So the code will look like: http://jsfiddle.net/n89dbo37/
new Vue({
el: '#app',
data() {
return {
items: _.times(20, i => ({type: 'offer'})),
};
},
computed: {
itemsWithBanners() {
let result = [];
this.items.forEach((item, idx) => {
if (idx && idx % 5 === 0) {
result.push({type: 'banner'});
}
result.push(item);
});
return result;
},
},
});
Thanks for everyone, i took Roy J solution, rebuild for my case and get result. My code:
<template>
<div class="section-space80 results-col" >
<div class="container" >
<div class="row">
<div class="col-md-12">
<div class="wrapper-content bg-white pinside40">
<div class="row" v-for="row in rows">
<div v-for="offer in row" class="col-xl-4 col-lg-4 col-md-6 col-sm-12 col-12">
<div class="lender-listing" v-if="offer.type && offer.type === 'Banner'">
<div class="lender-head">
Banner
</div>
</div>
<div class="lender-listing" v-if="offer.mfoName">
<div class="lender-head">
<div class="lender-logo">Offer</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
const chunk = (arr, size) =>
arr
.reduce((acc, _, i) =>
(i % size) ?
acc :
[...acc, arr.slice(i, i + size)], []);
import axios from 'axios'
export default {
data() {
return {
showOffers: true,
loanOffers: [],
isVisible: false,
loadMore: true,
offset: 0,
rows: ''
}
},
methods: {
getOffersList: function () {
let dataElements = this
dataElements.loading = true
axios.get('/api/v1/getUserOffers')
.then(function (response) {
dataElements.loanOffers = response.data
const withBanners = chunk(dataElements.loanOffers, 5).map((arr) => [...arr, {name: 'banner', type: 'Banner'}]).reduce((a, b) => a.concat(b));
dataElements.rows = chunk(withBanners, 3);
})
},
},
beforeMount(){
this.getOffersList()
}
}
</script>
I offer to use template and loop over it.
Then inside you check v-if="i%6" --> your article v-else --> yuor ad.
I want that, clicking on a menu item, the active class is triggered only for that specific item and removed for the others, until now I wrote this:
<template>
<nav class="navbar">
<div class="navbar__brand">
<router-link to="/">Stock Trader</router-link>
</div>
<div class="navbar__menu">
<ul class="navbar__menu--list">
<li #click="isActive=!isActive" class="navbar__menu--item" :class="{active:isActive}">
<router-link to="/portfolio">Portfolio</router-link>
</li>
<li #click="isActive=!isActive" class="navbar__menu--item" :class="{active:isActive}">
<router-link to="/stocks">Stocks</router-link>
</li>
</ul>
</div>
<div class="navbar__menu--second">
<ul class="navbar__menu--list">
<li #click="isActive=!isActive" class="navbar__menu--item" :class="{active:isActive}">
End Day
</li>
<li #click="isActive=!isActive" class="navbar__menu--item" :class="{active:isActive}">
Save / Load
</li>
</ul>
</div>
</nav>
</template>
<script>
export default {
data(){
return{
isActive: false
}
}
}
</script>
now of course, when I click on one item the active class is inserted/removed for all the items, what is the best solution for making that only a specific item, on clicking on it, receives the active class?
You'll want some sort of identifier for each clickable item and set that to your data property. For example
data() {
return { active: null }
}
and in your list items (for example)
<li #click="active = 'portfolio'"
class="navbar__menu--item"
:class="{active:active === 'portfolio'}">
In this example, the identifier is "portfolio" but this could be anything, as long as you use a unique value per item.
You could keep an object of links you have and handle a click on each of items. E.g.
data() {
return {
links: [
{
title : 'Portfolio',
to : '/portfolio',
isActive : false,
location : 'first',
},
{
title : 'Stocks',
to : '/stocks',
isActive : false,
location : 'first',
},
{
title : 'End Day',
to : '#',
isActive : false,
location : 'second',
},
{
title : 'Save / Load',
to : '#',
isActive : false,
location : 'second',
},
]
};
},
methods: {
handleNavClick(item) {
this.links.forEach(el, () => {
el.isActive = false;
});
item.isActive = true;
}
},
I do this in vue3.
the template is:
<li
v-for="(title, index) in titles"
class="title"
:key="index"
:class="{ active: active === index }"
#click="updateActive(index)"
>
{{ title }}
</li>
and the script is
<script lang="ts" setup>
import { ref } from "vue"
const titles = ["title1","title2","title3"]
const active = ref(-1)
function updateActive(val: number) {
active.value = val
}
</script>
If you have several ul => use title instead of index. For example :
<ul>
<div>
Applicants
</div>
<li
v-for="(title, index) in applicantMenuTitles"
:key="index"
:class="{ active: active === title }"
#click="updateActive(title)"
>
{{ title }}
<div
v-if=" active === title "
class="cursor"
/>
</li>
</ul>
<ul>
<div>
Offices
</div>
<li
v-for="(title, index) in officeMenuTitles"
:key="index"
:class="{ active: active === title }"
#click="updateActive(title)"
>
{{ title }}
<div
v-if=" active === title "
class="cursor"
/>
</li>
</ul>
And on script :
...
const active = ref('')
function updateActive(title: string) {
active.value = title
}