I build a pagination component that works fine. But I don't know how to limit the shown items in the v-for loop and show the next items on the next page.
this is the pagination component:
<template>
<div>
<ul class="pagination">
<li class="pagination-item">
<button
type="button"
#click="onClickFirstPage"
:disabled="isInFirstPage"
>
First
</button>
</li>
<li class="pagination-item">
<button
type="button"
#click="onClickPreviousPage"
:disabled="isInFirstPage"
>
Previous
</button>
</li>
<li v-for="page in pages" :key="page.name" class="pagination-item">
<button #click="onClickPage(page.name)" type="button" :disabled="page.isDisabled" :class="{ active: isPageActive(page.name )}">
{{ page.name }}
</button>
</li>
<!-- Range of pages -->
<li>
<button
type="button"
#click="onClickNextPage"
:disabled="isInLastPage"
>
Next
</button>
</li>
<li>
<button
type="button"
#click="onClickLastPage"
:disabled="isInLastPage"
>
Last
</button>
</li>
</ul>
</div>
<script>
export default {
props: {
maxVisibleButtons: {
type: Number,
required: false,
default: 3
},
totalPages: {
type: Number,
required: true
},
total: {
type: Number,
required: true
},
currentPage: {
type: Number,
required: true
}
},
computed: {
startPage() {
// When on the first page
if(this.currentPage === 1) {
return 1;
}
// When on the last page
if(this.currentPage === this.totalPages) {
return this.totalPages - this.maxVisibleButtons;
}
// When in between
return this.currentPage - 1;
},
pages() {
const range = [];
for(let i = this.startPage; i <= Math.min(this.startPage + this.maxVisibleButtons - 1, this.totalPages); i+=1) {
range.push({
name: i,
isDisabled: i === this.currentPage
});
}
return range;
},
isInFirstPage() {
return this.currentPage === 1;
},
isInLastPage() {
return this.currentPage === this.totalPages;
},
},
methods: {
onClickFirstPage() {
this.$emit("pagechanged", 1);
},
onClickPreviousPage() {
this.$emit("pagechanged", this.currentPage - 1);
},
onClickPage(page) {
this.$emit('pagechanged', page);
},
onClickNextPage() {
this.$emit('pagechanged', this.currentPage + 1);
},
onClickLastPage() {
this.$emit('pagechanged', this.totalPages);
},
isPageActive(page) {
return this.currentPage === page;
}
}
}
</script>
<style scoped lang="scss">
.pagination {
list-style-type: none;
}
.pagination-item {
display: inline-block;
}
.active {
background-color: #4AAE9B;
color: #ffffff;
}
</style>
my v-for with the items
<div
v-for="casino in orderBy(filteredCasinos, 'Rating', -1)"
:key="casino.id + '-filterd'"
class="casino-card"
v-show="filteredCasinos.length > 1"
>
At the moment the pagination works when I click to the next page but as I mentioned all items get shown and I want to limit them.
Use a computed property to keep track what should be visible on the current page.
With a computed property you can easily introduce a filter on the items. Then just create the functions that sets the current page, and BAM - a paginated view of items.
The snippet below does all this, and also gives the ability to choose how many items should be visible on a given page.
Advice:
new Vue({
el: "#app",
data() {
return {
posts: [],
currentPage: 1,
postsPerPage: 10,
}
},
computed: {
// computed property to set the items visible on current page
currentPagePosts() {
return this.posts.slice((this.currentPage - 1) * this.postsPerPage, this.currentPage * this.postsPerPage)
}
},
methods: {
// pagination function
setCurrentPage(direction) {
if (direction === -1 && this.currentPage > 1) {
this.currentPage -= 1
} else if (direction === 1 && this.currentPage < this.posts.length / this.postsPerPage) {
this.currentPage += 1
}
}
},
// fetching example data (200 post-like items)
async mounted() {
const response = await fetch('https://jsonplaceholder.typicode.com/todos')
this.posts = await response.json()
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<button #click="setCurrentPage(-1)">PREV</button>
<button #click="setCurrentPage(1)">NEXT</button><br /> Current page / max page: {{ currentPage }} / {{ Math.ceil(posts.length / postsPerPage) }}<br />
<label>Set posts per page: <input v-model="postsPerPage" type="text" /></label>
<hr />
<!-- the v-for only lists items from the computed property -->
<div v-for="post in currentPagePosts" :key="post.id">
ID: {{ post.id }} Title: {{ post.title }}
</div>
</div>
Related
In home page i have container where i am showing some elements, and below there is a pagination with all the pages(1, 2, 3, 4...), pagination is working. But, i want to add a query in my url so if type something like http://localhost:8080/twity/?page=10 i want to go to that page. I am adding a query like this, but it is only showing a number of the current page i am at, if i change number in url it is just returning me back to the first(default) page...
this.$router.push({ query: { page: this.currentPage } })
I can share my whole Home component code if needed. Thanks.
Home component
<template>
<div class="main">
<div class="tweets-container">
<div
v-for="tweet in tweets"
:key="tweet.id"
>
<div class="tweet-card">
<div class="username-time">
<div class="user-info">
<p class="name">
{{ tweet.twitter_name }}
</p>
<p class="user-info-p">
#
</p>
<p class="username">
<a
:href="'https://twitter.com/' + tweet.twitter_username"
class="twitter_link"
target="_blank"
>
{{ tweet.twitter_username }}
</a>
</p>
</div>
<div class="time">
<p>{{ tweet.added_at }}</p>
</div>
</div>
<div class="text">
<p>{{ tweet.tweet_text }}</p>
</div>
</div>
</div>
</div>
<div class="reply-container">
<h1>Replies</h1>
</div>
<ul class="pagination">
<li class="page-item">
<p
class="hover-underline-animation"
#click="previousPage"
>
Previous
</p>
</li>
<li class="page-item all-pages">
<div
v-for="page in numberOfPages"
:key="page"
:class="page == currentPage ? 'active' : 'hover-underline-animation'"
#click="getTweets(page)"
>
<p>{{ page }}</p>
</div>
</li>
<li class="page-item">
<p
class="hover-underline-animation"
#click="nextPage"
>
Next
</p>
</li>
</ul>
</div>
</template>
<script>
import axios from 'axios'
import { getUserToken } from '#/auth/auth'
import moment from 'moment'
export default {
components: {},
data() {
return {
tweets: [],
currentPage: 1,
numberOfPages: 0,
}
},
beforeMount() {
this.getTweets()
},
methods: {
nextPage() {
this.currentPage += 1
this.tweets = []
this.getTweets()
},
previousPage() {
this.currentPage -= 1
this.tweets = []
this.getTweets()
},
getTweets(newCurrent) {
this.tweets = []
const API_URL = `${this.$server}/api/twitter/tweets`
const params = {
token: getUserToken(),
page: this.currentPage,
newCurrentPage: newCurrent,
}
axios.post(API_URL, null, { params }).then(res => {
this.currentPage = res.data.page
this.numberOfPages = res.data.numberOfPages
// Set query string
this.$router.push({ query: { page: this.currentPage } })
res.data.tweets.forEach(tweet => {
const tweetData = {
id: tweet.id,
tweet_text: tweet.tweet_text,
twitter_name: tweet.twitter_name,
twitter_username: tweet.twitter_username,
added_at: moment(String(tweet.added_at)).format('MM/DD/YYYY hh:mm'),
}
this.tweets.push(tweetData)
})
})
},
},
}
</script>
Nodejs function(this is for getting data from db and controlling pagination)
getAllTweets: async (req) => {
// get number of all rows in table
let tweetsNum = await SQL('SELECT COUNT(*) as total FROM twitter_tweets')
tweetsNum = tweetsNum[0].total
// number of tweets
const pageSize = 25
let skip = 0
const numberOfPages = Math.ceil(tweetsNum / pageSize)
// page number from client
const newCurrentPage = req.query.newCurrentPage
let currentPage = req.query.page
// check if there is newCurrentPage
if(newCurrentPage != undefined) {
skip = (newCurrentPage - 1) * pageSize
currentPage = newCurrentPage
} else {
// check if page is valid)
if(currentPage < 1) {
currentPage = 1
} else if(currentPage > numberOfPages) {
currentPage = numberOfPages
}
// offset
skip = (currentPage - 1) * pageSize
}
// paginate tweets from db
const tweetsFromDb = await SQL('SELECT * FROM twitter_tweets ORDER BY added_at DESC LIMIT ?,?', [skip, pageSize])
let tweets = Object.values(JSON.parse(JSON.stringify(tweetsFromDb)))
const data = {
tweets: tweets,
page: parseInt(currentPage),
numberOfPages: parseInt(numberOfPages),
}
return data
}
beforeRouteUpdate(to,from,next) {
yourFunctionXXXXX(to.query);
next();
},
beforeRouteEnter(to, from, next) {
next((vm) => {
vm.yourFunctionXXXXX(to.query);
});
},
You should first use the route pre-guard to intercept your route parameters, and then manually decide what content you should display based on the parameters
I'm looking at upgrading to v3 and was disappointed to see inline-template has been removed. Therefore I'm trying to convert to use a scoped slot instead.
I have defined the following list component:
<template>
<slot :items="filteredItems" :page="page" :totalPages="totalPages" :onPageChanged="onPageChanged"></slot>
</template>
<script>
export default {
props: {
items: {
type: Array
},
initialPage: {
type: Number,
default: 1,
},
pageSize: {
type: Number,
default: 10
}
},
beforeCreate() {
this.page = this.initialPage;
},
computed: {
filteredItems() {
return this.items.slice((this.page - 1) * this.pageSize, this.page * this.pageSize);
},
totalPages() {
return Math.ceil(this.items.length / this.pageSize);
}
},
methods: {
onPageChanged(page) {
console.log('Page changed!!!');
this.page = page;
}
}
};
</script>
Which is called like so:
<list :items="[ { foo: 'A' }, { foo: 'B' }, { foo: 'C' } ]" :page-size="2" #="{ items, page, totalPages, onPageChanged }">
<ul class="list-group">
<li class="list-group-item" v-for="item in items">{{ item.foo }}</li>
</ul>
<pager :page="page" :total-pages="totalPages" #pageChanged="onPageChanged"></pager>
</list>
Here's the pager component:
<template>
<ul class="pagination">
<li class="page-item" v-if="hasPreviousPage">«</li>
<li class="page-item" v-if="hasPreviousPage">‹</li>
<li v-for="page in pages" :class="['page-item', { active: page.isActive }]">{{ page.name }}</li>
<li class="page-item disabled" v-if="page < totalPages - 2"><span class="page-link"> ... </span></li>
<li class="page-item" v-if="page < totalPages - 2">{{ totalPages }}</li>
<li class="page-item" v-if="hasNextPage">›</li>
<li class="page-item" v-if="hasNextPage">»</li>
</ul>
</template>
<script>
export default {
props: {
page: {
type: Number,
required: true
},
totalPages: {
type: Number,
required: true
}
},
computed: {
hasPreviousPage() {
return this.page > 1;
},
hasNextPage() {
return this.page < this.totalPages;
},
pages() {
const range = [];
for (let i = this.page <= 2 ? 1 : this.page - 2; i <= (this.page >= this.totalPages - 2 ? this.totalPages : this.page + 2); i++) {
range.push({
name: i,
isActive: this.page == i
});
}
return range;
}
},
methods: {
changePage(page, e = event) {
e.preventDefault();
// Trigger the page changed event.
this.$emit('pageChanged', page);
}
}
};
</script>
However whenever I try to change the page, the changePage method is invoked which emits the pageChanged event, but it doesn't invoke the onPageChanged method within the list component.
I'd appreciate if someone could show me what I'm doing wrong. Thanks
The event name should be written in kebab-case format as follows :
this.$emit('page-changed', page);
and use it like #page-changed="onPageChanged
I am currently working on an application that involves scrolling through members from two different lists. Once you click one you see their image and bio. We added up and down arrows to scroll through their biography, but when i click the down arrow it seems that the active class on the carousel slide now jumps to the last member in my array, but still displays the member i was already on. To add to that I cannot swipe back and forth once i am on a member (this is something that I could do previously.) I know this probably isn't the best description and there is only so much code I can show, as I cannot display the entire application.
I wanted to also note that if I refresh my page, everything works as expected. Does this mean something isn't initializing correctly?
this is the code specific to the page i am talking about:
<template>
<div class="member-info-carousel">
<div class="header">
<h2 v-if="founderChairman === true">Member List One</h2>
<h2 v-else>Member List Two</h2>
<img src="../assets/logo.png" alt="Logo" />
</div>
<carousel :minSwipeDistance="384" :perPage="1" :paginationEnabled="false" :navigationEnabled="true" navigationNextLabel="<i>NEXT</i>"
navigationPrevLabel="<i>BACK</i>" :navigateTo="selectedListIndex" #pagechange="OnPageChange">
<slide v-for="(member, index) in selectedList" :key="index">
<div class="member-bio-page" :member="member" v-on:showContent="showContent">
<div class="bio">
<div class="portrait-image">
<img :src="member.imgSrc" />
</div>
<div class="bio-container">
<div class="inner-scroll" v-bind:style="{top: scrollVar + 'px'}">
<div class="english"></div>
<div class="pin-name">
<img :src="member.pin" />
<h1>{{ member.name }}</h1>
</div>
<div class="description-container">
<div class="para">
<p class="quote" v-html="member.quote"></p>
<p v-html="member.bio"></p>
<div class="spanish"></div>
<p class="quote" v-html="member.spanishQuote"></p>
<p v-html="member.spanishBio"></p>
</div>
</div>
</div>
</div>
<div class="scroll-buttons">
<div>
<!-- set the class of active is the scroll variable is less than 0-->
<img class="btn-scroll" v-bind:class="{ 'active': scrollVar < 0 }" #click="scrollUp" src="#/assets/arrow-up.png">
</div>
<div>
<!-- set the class of active is the scroll variable is greater than the height of the scrollable inner container-->
<img class="btn-scroll" v-bind:class="{ 'active': scrollVar > pageChangeHeight }" #click="scrollDown" src="#/assets/arrow-down.png">
</div>
</div>
<div class="eng-span">
English
Español
</div>
</div>
<div class="play-button">
<!-- if the array members has a property of video, then the play button will show on the slide. If not it will not show the image -->
<img v-if="member.hasOwnProperty('video')" #click="showContent" src="#/assets/play-button.png">
</div>
</div>
<!-- <MemberBioPage :member="member" v-on:showContent="showContent"/> -->
</slide>
</carousel>
<modal name="video-modal"
:width="1706"
:height="960">
<video width="1706" height="960" :src="(selectedList && selectedList[this.currentPage]) ? selectedList[this.currentPage].video : ''" autoplay />
</modal>
<div class="footer-controls">
<div class="footer-bar">
<p>Tap Back or Next to view additional profiles.</p>
<p>Tap the arrows to scroll text up or down.</p>
</div>
<div class="nav-container">
<img class="nav-bubble" src="#/assets/navigation-bubble-bio-page.png" alt="An image where the back, next and close button sit" />
</div>
<button class="close-button" #click="closeInfo">
<img src="#/assets/x-close-button.png" />
CLOSE
</button>
</div>
</div>
</template>
<script>
import { Carousel, Slide } from 'vue-carousel'
export default {
data () {
return {
currentPage: 0,
pageChangeHeight: -10,
scrollVar: 0
}
},
components: {
// MemberBioPage,
Carousel,
Slide
},
mounted () {
this.enableArrows()
},
updated () {
this.enableArrows()
},
computed: {
selectedList () {
return this.$store.state.selectedList
},
selectedListIndex () {
return this.$store.state.selectedListIndex
},
founderChairman () {
return this.$store.state.founderChairman
}
},
methods: {
enableArrows () {
var outerHeight
var innerHeight
if (document.querySelectorAll('.VueCarousel-slide-active').length > 0) {
outerHeight = document.querySelectorAll('.VueCarousel-slide-active .bio-container')[0].clientHeight
innerHeight = document.querySelectorAll('.VueCarousel-slide-active .inner-scroll')[0].clientHeight
} else {
outerHeight = document.querySelectorAll('.VueCarousel-slide .bio-container')[0].clientHeight
innerHeight = document.querySelectorAll('.VueCarousel-slide .inner-scroll')[0].clientHeight
}
this.pageChangeHeight = outerHeight - innerHeight
return this.pageChangeHeight
},
scrollUp () {
this.scrollVar += 40
console.log(this.scrollVar += 40)
},
scrollDown () {
this.scrollVar -= 40
console.log(this.scrollVar)
},
OnPageChange (newPageIndex) {
this.scrollVar = 0
this.currentPage = newPageIndex
this.pageChangeHeight = -10
},
closeInfo () {
if (this.$store.state.selectedList === this.$store.state.foundersList) {
this.$store.commit('setSelectedState', this.$store.state.foundersList)
this.$router.push({ name: 'Carousel' })
} else if (this.$store.state.selectedList === this.$store.state.chairmanList) {
this.$store.commit('setSelectedState', this.$store.state.chairmanList)
this.$router.push({ name: 'Carousel' })
}
},
showContent () {
this.$modal.show('video-modal')
},
toEnglish () {
this.scrollVar = 0
},
toSpanish () {
var spanishPos
if (document.querySelectorAll('.VueCarousel-slide-active').length > 0) {
spanishPos = document.querySelectorAll('.VueCarousel-slide-active .spanish')[0].offsetTop
} else {
spanishPos = document.querySelectorAll('.VueCarousel-slide .spanish')[0].offsetTop
}
this.scrollVar = -spanishPos
}
}
}
</script>
This is my store index file:
import Vue from 'vue'
import Vuex from 'vuex'
import chairmans from '#/data/chairmans-club'
import founders from '#/data/founders-circle'
import memoriam from '#/data/in-memoriam'
Vue.use(Vuex)
const store = new Vuex.Store({
state: {
finishedLoading: false,
transitioning: false,
foundersList: founders,
chairmanList: chairmans,
selectedList: founders,
selectedListIndex: -1,
founderChairman: true,
inMemoriam: memoriam,
idleTimer: {
id: null,
duration: 1
},
idleTimeoutModal: {
show: false,
duration: 1
}
},
mutations: {
setSelectedState (state, list) {
state.selectedList = list
},
setSelectedListIndex (state, idx) {
state.selectedListIndex = idx
},
showIdleTimeoutModal (state, value) {
state.idleTimeoutModal.show = value
},
founderChairmanClicked (state, data) {
state.founderChairman = data
},
setInMemoriam (state, content) {
state.inMemoriam = content
}
},
actions: {
restartIdleTimer ({ state, commit }) {
clearTimeout(state.idleTimer.id)
state.idleTimer.id = setTimeout(() => {
commit('showIdleTimeoutModal', true)
}, state.idleTimer.duration * 1000)
},
stopIdleTimer ({ state }) {
clearTimeout(state.idleTimer.id)
}
}
})
export default store
and an example of the data i am pulling:
const event = [
{
index: 0,
name: 'Member Name',
carouselImage: require('#/assets/carousel-images/image.jpg'),
drawerImage: require('#/assets/drawer-images/drawerimage.jpg'),
imgSrc: require('#/assets/bio-images/bioimage.jpg'),
quote: '“quote.”',
spanishQuote: `“spanish quote.”`,
bio: '<p>bio copy here</p>',
spanishBio: '<p>spanish bio copy</p>',
pin: require('#/assets/pin.png')
}
]
export default event
I'm a beginner programmer and I create a spa like trello. Creates boards. In the board creates lists, they are displayed different with different id, but the list items are displayed with the same id and they are duplicated in each list. Sorry for my english:) Help me, please and tell in detail what the problem is.. Thank you very much
vuex file router.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
boards: JSON.parse(localStorage.getItem('boards') || '[]'),
lists: [],
items: []
// items: JSON.parse(localStorage.getItem('items') || '[]')
// lists: JSON.parse(localStorage.getItem('lists') || '[]')
},
mutations: {
addBoard(state, board) {
state.boards.push(board)
localStorage.setItem('boards', JSON.stringify(state.boards))
},
addList(state, list) {
state.lists.push(list)
// localStorage.setItem('lists', JSON.stringify(state.lists))
},
createItemListt(state, item) {
state.items.push(item)
// localStorage.setItem('items', JSON.stringify(state.items))
}
},
actions: {
addBoard({commit}, board) {
commit('addBoard', board)
},
addList({commit}, list) {
commit('addList', list)
},
createItemListt({commit}, item) {
commit('createItemListt', item)
}
},
getters: {
boards: s => s.boards,
taskById: s => id => s.boards.find(t => t.id === id),
lists: d => d.lists,
items: a => a.items
},
modules: {
}
})
the page on which the lists are created MyBoard.vue
<template>
<div>
<div class="wrapper">
<div class="row">
<h1>{{board.title}}</h1>
<div class="list " v-for="list in lists" :key="list.idList">
<div class="list__title">
<h3>{{list.titleList}}</h3>
</div>
<div class="list__card" v-for="item in items" :key="item.idItemList">
<span class="list__item">{{item.itemList}}</span>
<a class="btn-floating btn-tiny btn-check" tag="button">
<i class="material-icons">check</i>
</a>
</div>
<createItemList />
</div>
<createList />
</div>
</div>
</div>
</template>
<script>
export default {
computed: {
board() {
return this.$store.getters.taskById(+this.$route.params.id);
},
lists() {
return this.$store.getters.lists;
},
items() {
return this.$store.getters.items;
}
},
components: {
createList: () => import("../components/createList"),
createItemList: () => import("../components/createItemList")
}
};
</script>
CreateList.Vue
<template>
<div>
<div class="row">
<div class="new-list" v-show="isCreating">
<div class="list__title input-field">
<input
type="text"
required
id="list-title"
class="none validate"
tag="button"
autofocus
v-model="titleList"
v-on:keyup.enter="createList"
/>
<label for="list-title">Enter Title List</label>
</div>
<a class="btn-floating transparent btn-close" tag="button" #click="closeList">
<i class="material-icons">close</i>
</a>
</div>
<div class="create-list z-depth-2" v-show="!isCreating">
<p>Create list</p>
<a
class="btn-floating btn-large waves-effect waves-light deep-purple lighten-2 pulse"
tag="button"
#click="addList"
v-on:keyup.enter="addList"
>
<i class="material-icons">add</i>
</a>
</div>
</div>
</div>
</template>
<script>
export default {
data: () => ({
isCreating: false,
titleList: "",
idList: ""
}),
methods: {
addList() {
this.isCreating = true;
},
closeList() {
this.isCreating = false;
},
createList() {
if (this.titleList == "") {
return false;
}
const list = {
idList: Date.now(),
titleList: this.titleList
};
this.$store.dispatch("addList", list);
this.titleList = "";
this.isCreating = false;
console.log(list.titleList);
}
}
};
</script>
CreateItemList.vue
<template>
<div>
<div class="add-item">
<div class="textarea-item input-field" v-show="isAdding">
<input
type="text"
class="validate"
id="list-item"
v-model="itemList"
v-on:keyup.enter="createItemList"
autofocus
/>
<label for="list-item">Enter Item List</label>
</div>
<a class="waves-effect waves-light btn" v-show="!isAdding" #click="addCard">
<i class="material-icons right">add</i>Add Card
</a>
</div>
</div>
</template>
<script>
export default {
data: () => ({
isAdding: false,
itemList: "",
}),
methods: {
addCard() {
this.isAdding = true;
},
createItemList() {
if (this.itemList == "") {
return false;
}
const item = {
idItemList: Date.now(),
itemList: this.itemList
};
this.$store.dispatch("createItemListt", item);
this.itemList = "";
this.isAdding = false;
}
}
};
</script>
attach photo
Tried to go with the basic idea of the structure you laid out. I added:
id to all items, so they can be identifed
children to appropriate items, so you can keep track of what's in them
const store = new Vuex.Store({
state: {
tables: [
{ id: 1, children: ['1.1', '1.2'] },
{ id: 2, children: ['2.1'] }
],
lists: [
{ id: '1.1', children: ['1.1.1'] },
{ id: '1.2', children: ['1.2.1'] },
{ id: '2.1', children: ['2.1.1', '2.1.2'] },
],
cards: [
{ id: '1.1.1' },
{ id: '1.2.1' },
{ id: '2.1.1' },
{ id: '2.1.2' },
]
},
mutations: {
ADD_CARD(state, listId) {
const list = state.lists.find(e => e.id === listId)
const cards = state.cards
const card = { id: Date.now() }
cards.push( card )
list.children.push( card.id )
},
ADD_LIST(state, tableId) {
const table = state.tables.find(e => e.id === tableId)
const lists = state.lists
const list = { id: Date.now(), children: [] }
lists.push( list )
table.children.push( list.id )
},
ADD_TABLE(state) {
const tables = state.tables
const table = { id: Date.now(), children: [] }
tables.push( table )
},
TRY_MOVING_LIST(state) {
const table1 = state.tables.find(e => e.id === 1)
const table2 = state.tables.find(e => e.id === 2)
const item = table1.children.pop() // remove the last item
table2.children.push(item)
}
},
actions: {
addCard({ commit }, listId) {
commit('ADD_CARD', listId)
},
addList({ commit }, tableId) {
commit('ADD_LIST', tableId)
},
addTable({ commit }) {
commit('ADD_TABLE')
},
tryMovingList({ commit }) {
commit('TRY_MOVING_LIST')
}
},
getters: {
getTables: s => s.tables,
getListById: s => id => s.lists.find(e => e.id === id),
getCardById: s => id => s.cards.find(e => e.id === id),
}
})
Vue.component('CustomCard', {
props: ['card'],
template: `<div>
card ID: {{ card.id }}<br />
</div>`
})
Vue.component('CustomList', {
props: ['list'],
template: `<div>
list ID: {{ list.id }}<br />
<custom-card
v-for="item in list.children"
:key="item"
:card="getCard(item)"
/>
<button #click="addCard">ADD CARD +</button>
<hr />
</div>`,
methods: {
getCard(id) {
return this.$store.getters.getCardById(id)
},
addCard() {
this.$store.dispatch('addCard', this.list.id)
}
}
})
Vue.component('CustomTable', {
props: ['cTable'],
template: `<div>
table ID: {{ cTable.id }}<br />
<custom-list
v-for="item in cTable.children"
:key="item"
:list="getList(item)"
/>
<button #click="addList">ADD LIST +</button>
<hr />
</div>`,
methods: {
getList(id) {
return this.$store.getters.getListById(id)
},
addList(id) {
this.$store.dispatch('addList', this.cTable.id)
}
}
})
new Vue({
el: "#app",
store,
computed: {
tables() {
return this.$store.state.tables
}
},
methods: {
addTable() {
this.$store.dispatch('addTable')
},
tryMovingList() {
// this function will move the last list in table ID 1
// to the end of table ID 2's lists
// NOT FOOLPROOF - you should add error handling logic!
this.$store.dispatch('tryMovingList')
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script src="https://unpkg.com/vuex"></script>
<div id="app">
<button #click="tryMovingList()">MOVE LIST</button><br />
<button #click="addTable()">ADD TABLE +</button>
<hr />
<custom-table v-for="item in tables" :key="'table-' + item.id" :c-table="item" />
</div>
With this setup you can change the hierarchy quite easily: just delete an ID from one Array of children and add it to another (e.g. remove '1.1' from table ID 1's children array and add it to table ID 2's children array - everything moved to table ID 2. tryMovingList() does exactly this - this method/action is not foolproof, just for you to try out moving a whole list)
There could be other patterns to solve this problem (like a real linked list data structure or the mediator pattern), but for smaller apps this is OK, I think (I would use it... :) ).
ONE PIECE OF ADVICE
If you want to store state in localStorage on mutations, don't do it by yourself - use Vuex's integrated subscribe mechanism: https://vuex.vuejs.org/api/#subscribe
I am working in a quiz app using Cordova and vue.js in which question and option will be fetched from API. In this, a timer is used which will indicate how much time is remaining. For this, I have set the timer in mounted(), in which there is a callback function which will be called every 1-second later by using setInterval. But the problem is when the timer is on, if I select one radio button, if the value is false then it just move towards the last radio button among the 4 buttons. If the value is true then it doesnot move. This problem doesnot occur when the timer is off, then it works fine. Please help me out
<template>
<v-ons-page>
<div class="test_body">
<v-ons-card class="test_card">
<div class="timer" v-if="!timeStop">
<div class="minutes" v-text="this.data.minutes"></div>
<div class="seconds" id="seconds" v-text="secondsShown"></div>
</div>
<div class="timer" v-else>
<div class="minutes" v-text="this.data.minutes"></div>
<div class="seconds" v-text="secondsShown"></div>
</div>
<div v-for="(ques,index) in questions">
<div v-show="index===ques_index">
<p class="test_text">{{ ques.question_text }} </p>
<ol align="left">
<li class="test_list" v-for="choice in ques.choices">
<input type="radio" v-bind:value="choice.is_right" v-bind:name="index"
v-model=" userResponses[index] "> {{ choice.choice_text }}
</li>
</ol>
<p class="test_number" align="right">{{index+1}} /{{questions.length}} </p>
<v-ons-button class="prev_next_button" modifier="cta" style="margin: 6px 0"
v-if="ques_index > 0" #click="prev">
Prev
</v-ons-button>
<v-ons-button class="prev_next_button" modifier="cta" style="margin: 6px 0" #click="next">
Next
</v-ons-button>
</div>
</div>
<div v-show="ques_index === questions.length">
<h2>
Quiz finished
</h2>
<p>
Total score: {{ score() }} / {{ questions.length }}
</p>
<v-ons-button class="prev_next_button" modifier="cta" style="margin: 6px 0" #click="congratz">
Congratulation
</v-ons-button>
</div>
</v-ons-card>
</div>
</v-ons-page>
</template>
<script>
import swal from 'sweetalert';
import congratz from './congratulation';
export default {
data() {
return {
minute: 0,
seconds: 0,
interval: null,
started: true,
questions: {},
ques_index: 0,
userResponses: [],
}
},
beforeMount() {
this.question();
},
mounted() {
this.loaded()
},
methods: {
congratz() {
swal({
text: "Congratulation on Your First Exam!",
});
this.pageStack.push(congratz)
},
question() {
this.$http({
method: 'post',
url: this.base_url + '/exam/api/',
auth: {
username: 'l360_mobile_app',
password: 'itsd321#'
},
data: {
"id": this.$store.state.user.id,
"duration": this.data.minutes
}
}).then((response) => {
//alert(response.data.questions[0].question_text);
//var questions = [];
this.questions = response.data.questions;
})
.catch((error) => {
// alert(error);
});
},
next: function () {
this.ques_index++;
},
prev: function () {
this.ques_index--;
},
score() {
var c = 0;
for (var i = 0; i < this.userResponses.length; i++)
if (this.userResponses[i])
c++;
return c;
},
loaded: function () {
this.interval = setInterval(this.intervalCallback, 1000)
},
intervalCallback: function () {
if (!this.started) return false
if (this.seconds == 0) {
if (this.data.minutes == 0) {
return null
}
this.seconds = 59
this.data.minutes--
} else {
this.seconds--
}
},
},
computed: {
secondsShown: function () {
if (this.seconds < 10) {
return "0" + parseInt(this.seconds, 10)
}
return this.seconds
},
timeStop: function () {
if (this.ques_index === this.questions.length) {
this.started = false;
return this.seconds;
}
}
},
props: ['pageStack']
}
</script>
<style>
</style>
To keep the values of input tags unique, add choice_id after the choice.is_right. Use choice.is_right + '_' + choice.choice_id instead of choice.is_right. When you get the values just remove the _ and id or check if 'true' is a sub-string of the value.
<ol align="left">
<li class="test_list" v-for="choice in ques.choices">
<input type="radio" v-bind:value="choice.is_right + '_' + choice.choice_id" v-bind:name="index" v-model="userResponses[index]">{{ choice.choice_text }}
</li>
</ol>