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>
Related
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;
dynamically created fields
Hi, the image above shows dynamically created fields I'm using. You can add or delete the fields with the button at the right.
My goal is to
store data that are input to these dynamically created fields into a list
bind the data / display the data at a confirmation page before submitting
The expected result is that when I key in data to these fields, it will display on the confirmation page before submitting the form
I've tried using v-model, but this only works for normal fields. Here's there code for what I have now
<template>
<div>
<div v-for="(line, index) in lines" :key="index" class="row">
<div class="col-lg-6">
<div class="row">
<div class="col-2">
<q-select
v-model="line.countryCode"
label="Country Code"
:options="countryPhoneCodes"
/>
</div>
<div class="col-10">
<q-input
v-model="line.number"
label="Phone Number"
placeholder="5551234567"
type="tel"
value=""
/>
</div>
</div>
</div>
<div class="col-lg-4">
<q-select
v-model="line.phoneUsageType"
label="Type of Usage"
:options="phoneUsageTypes"
/>
</div>
<div class="col-lg-2">
<div class="block float-right">
<q-btn round #click="removeLine(index)" icon="delete" />
<q-btn round v-if="index + 1 === lines.length" #click="addLine" icon="playlist-plus" />
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'PhoneNumberLine',
data () {
return {
lines: [],
blockRemoval: true,
phoneUsageTypes: [
{
label: 'Home', value: 'home'
}, {
label: 'Work', value: 'work'
}, {
label: 'Mobile', value: 'mobile'
}, {
label: 'Fax', value: 'fax'
}
],
countryPhoneCodes: [
{
label: '+90',
value: '+90'
}, {
label: '+1',
value: '+1'
}
]
}
},
watch: {
lines () {
this.blockRemoval = this.lines.length <= 1
}
},
methods: {
addLine () {
let checkEmptyLines = this.lines.filter(line => line.number === null)
if (checkEmptyLines.length >= 1 && this.lines.length > 0) {
return
}
this.lines.push({
countryCode: null,
number: null,
phoneUsageType: null
})
},
removeLine (lineId) {
if (!this.blockRemoval) {
this.lines.splice(lineId, 1)
}
}
},
mounted () {
this.addLine()
}
}
</script>
This should be similar to what you are trying to achieve. tweak it to fit your needs. good luck.
const makeLine = (n = 0) => ({
number: n
});
new Vue({
name: "App",
template: `
<div>
<div class="line" v-for="(line, idx) in lines" :key="idx">
<input v-model="line.number" />
<button #click="addLine" v-if="lines.length - 1 === idx">+</button>
<button #click="removeLine(idx)" v-if="lines.length > 1">-</button>
</div>
</div>
`,
data() {
return {
lines: [
makeLine(),
],
}
},
methods: {
addLine() {
this.lines.push(makeLine(this.lines.length));
},
removeLine(idx) {
this.lines = this.lines.filter((_, id) => id !== idx);
}
}
}).$mount("#app");
#app {
display: flex;
flex-direction: column;
}
.line {
display: flex;
}
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<div id="app"></div>
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'm making a todo app to better understand Vue and ran into a snag.
I've gone through several StackOverflow questions and the Vuejs forum, but I'm not understanding what I'm doing incorrectly.
The problem stems from the to-do-item Component Template:
<button
#click="$emit('remove-item', {{item.id}} )">
Remove
</button>
If I replace $emit with a component method that doesn't call $emit it works fine, but when I use $emit (even in a local component function) it refuses to render.
I'm not sure why this is. Here's the rest of my code:
Vue.component("todo-item", {
props:["item"],
template: `
<li>{{ item.text }}
<button
#click="$emit('remove-item', {{item.id}} )">
Remove
</button>
</li>`
})
let vm = new Vue({
el:"#app",
data: {
text: "",
todos: []
},
methods: {
getID: (function() {
let id = 0;
return function() {
return id++;
}
}()),
addTodo: function() {
this.todos.push({id: this.getID(), text: this.text});
this.text = "";
},
remove: function(remove_id) {
this.todos = this.todos.filter(({id}) => id != remove_id);
}
}
})
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.17/dist/vue.min.js"></script>
<div id="app">
<div id="container">
<main>
<ul>
<todo-item
v-for="todo in todos"
:item="todo"
#remove-item="remove"
>
</todo-item>
</ul>
</main>
<div id="add-field">
<input v-model="text" /> <button id="add" #click="addTodo">Add Todo</button>
</div>
</div>
</div>
The problem is that you are trying to use template syntax inside a javascript-executed attribute:
<button
#click="$emit('remove-item', {{item.id}} )">
Fix that, and it should work:
Vue.component("todo-item", {
props:["item"],
template: `
<li>{{ item.text }}
<button
#click="$emit('remove-item', item.id )">
Remove
</button>
</li>`
})
let vm = new Vue({
el:"#app",
data: {
text: "",
todos: []
},
methods: {
getID: (function() {
let id = 0;
return function() {
return id++;
}
}()),
addTodo: function() {
this.todos.push({id: this.getID(), text: this.text});
this.text = "";
},
remove: function(remove_id) {
this.todos = this.todos.filter(({id}) => id != remove_id);
}
}
})
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.17/dist/vue.min.js"></script>
<div id="app">
<div id="container">
<main>
<ul>
<todo-item
v-for="todo in todos"
:item="todo"
#remove-item="remove"
>
</todo-item>
</ul>
</main>
<div id="add-field">
<input v-model="text" />
<button id="add" #click="addTodo">Add Todo</button>
</div>
</div>
Hope this helps!