I would like to use v-calendar to pick up dates in a textarea in my Laravel application.
If I follow the official documentation and use the code to use v-calendar from CDN, I can pick up the date from v-calendar.
However, if I use the same code to set the vue-component to pick up the dates from v-calendar, I cannot pick up the dates.
official document:
https://vcalendar.io/examples/datepickers.html
datepick.blade.php in case CDN
<script>
new Vue({
el: '#cal',
data: {
days: [],
date: "",
},
computed: {
dates() {
return this.days.map(day => day.date);
},
attributes() {
return this.dates.map(date => ({
highlight: true,
dates: date,
}));
},
},
methods: {
onDayClick(day) {
const idx = this.days.findIndex(d => d.id === day.id);
if (idx >= 0) {
this.days.splice(idx, 1);
} else {
const date = this.date;
if(date === "") {
this.date = (day.id.slice(5).replace("-", "/")) + " 19:00";
} else {
this.date = date + "\n" + (day.id.slice(5).replace("-", "/")) + " 19:00";
}
}
}
},
})
</script>
<div class="event-datepicker-wrapper" id="cal" ref="days">
<script src="https://cdn.jsdelivr.net/npm/vue#2.6.12"></script>
<link rel='stylesheet' href='https://unpkg.com/v-calendar/lib/v-calendar.min.css'>
<script src='https://unpkg.com/v-calendar'></script>
<div class="create-date">
<textarea rows="10" name="kouho" v-model="date" class="form-textarea event-choice-textarea" ></textarea>
</div>
<div class="calender-wrapper">
<div class="calender">
<v-calendar :attributes="attributes" #dayclick="onDayClick" />
</div>
</div>
<div class="calender-wrapper">
<div class="calender">
<v-calendar :attributes="attributes" #dayclick="onDayClick" />
</div>
app.js
require('./bootstrap');
window.Vue = require('vue');
Vue.component('calendar', require('./components/Calendar.vue').default);
const app = new Vue({
el: '#app',
});
Calendar.vue
<template>
<div class="event-datepicker-wrapper" ref="days">
<div class="create-date">
<textarea
rows="10"
name="kouho"
v-model="date"
class="form-textarea event-choice-textarea"
></textarea>
</div>
<div class="calender-wrapper">
<div class="calender">
<v-calendar :attributes="attributes" #dayclick="onDayClick" />
</div>
</div>
</div>
</template>
<script>
import VCalendar from "v-calendar";
Vue.use(VCalendar);
export default {
data() {
return {
days: [],
date: "",
};
},
computed: {
dates() {
return this.days.map(day => day.date);
},
attributes() {
return this.dates.map(date => ({
highlight: true,
dates: date,
}));
},
},
methods: {
onDayClick(day) {
console.log(day);
const idx = this.days.findIndex((d) => d.id === day.id);
// console.log(format(day.id));
console.log(idx);
if (idx >= 0) {
this.days.splice(idx, 1);
} else {
const date = this.date;
if (date === "") {
this.date = day.id.slice(5).replace("-", "/") + " 19:00";
} else {
this.date =
date + "\n" + day.id.slice(5).replace("-", "/") + " 19:00";
}
}
},
},
};
</script>
datepick.blade.php in case vue-components
<div id="app">
<div class="event-date-wrapper">
<calendar></calendar>
</div>
</div>
<script src="{{ mix('js/app.js') }}" defer></script>
Related
The way i am getting data is little bit complicated. I have "tweets" array where data is stored and each tweet is a card where i successfully change style when card is clicked(markTweet function), but there are also replies for each tweet which are shown same as tweets(each reply has its own card). The way i am getting data from server:
let replies = []
for(const tweet of tweets) {
let reply = await SQL('SELECT * FROM tweet_replies WHERE tweet_replies.conversation_id = ?', tweet.tweet_id)
replies.push(reply)
}
const data = {
tweets: tweets,
page: parseInt(currentPage),
numberOfPages: arr,
replies
}
Then i have component in vue. You can see replies are stored in tweets array in each tweet as tweetReplies.
In markReply func, am succesfully adding id to array.
<template>
<div class="container-full">
<div class="tweets-container">
<div
v-for="(tweet, i) in tweets"
:key="tweet.id"
>
<div
class="tweet-card"
:class="{ selected: tweet.isSelected }"
#click="markTweet(tweet.tweet_id, i)"
>
<div class="text">
<p
v-html="tweet.tweet_text"
>
{{ tweet.tweet_text }}
</p>
</div>
</div>
<div class="replies">
<div
v-for="(reply, index) in tweet.tweetReplies"
:key="reply.tweet_id"
#click="markReply(reply.tweet_id, index)"
>
<div class="tweet-card tweet-reply">
<div class="text">
<p>
{{ reply.tweet_text }}
</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import axios from 'axios'
import { getUserToken } from '#/auth/auth'
import moment from 'moment'
import { BFormTextarea, BButton, BFormSelect } from 'bootstrap-vue'
export default {
components: { BFormTextarea, BButton, BFormSelect },
data() {
return {
tweets: [],
tweetActionIds: [],
categories: [],
}
},
beforeMount() {
this.getTweets()
},
methods: {
getTweets() {
this.tweets = []
const API_URL = `${this.$server}/api/twitter/tweets`
const params = {
token: getUserToken(),
page: this.$route.query.page,
newCurrentPage: newCurrent,
}
axios.post(API_URL, null, { params }).then(res => {
this.currentPage = res.data.page
this.numberOfPages = res.data.numberOfPages
if (res.data) {
res.data.tweets.forEach(tweet => {
const tweetData = {
id: tweet.id,
tweet_id: tweet.tweet_id,
tweet_text: htmlText,
tweet_text_en: htmlTextEn,
twitter_name: tweet.twitter_name,
twitter_username: tweet.twitter_username,
added_at: moment(String(tweet.added_at)).format(
'MM/DD/YYYY hh:mm',
),
URL: tweet.URL,
isSelected: false,
tweetReplies: [],
}
this.tweets.push(tweetData)
})
res.data.replies.forEach(reply => {
reply.forEach(r => {
this.tweets.forEach(tweet => {
if (tweet.tweet_id === r.conversation_id) {
tweet.tweetReplies.push(r)
}
})
})
})
}
})
},
markTweet(tweetId, i) {
const idIndex = this.tweetActionIds.indexOf(tweetId)
this.tweets[i].isSelected = !this.tweets[i].isSelected
if (this.tweetActionIds.includes(tweetId)) {
this.tweetActionIds.splice(idIndex, 1)
} else {
this.tweetActionIds.push(tweetId)
}
},
markReply(replyId) {
const idIndex = this.tweetActionIds.indexOf(replyId)
if (this.tweetActionIds.includes(replyId)) {
this.tweetActionIds.splice(idIndex, 1)
} else {
this.tweetActionIds.push(replyId)
}
},
},
}
</script>
I have tried to add replySelected in data and then when click is triggered in markReply i changed replySelected to true, but every reply of a tweet was then selected, which is not what i want.
If I understood you correctly try like following snippet:
const app = Vue.createApp({
data() {
return {
tweets: [{id: 1, tweet_id: 1, isSelected: true, tweet_text: 'aaa', tweetReplies: [{tweet_id: 11, tweet_text: 'bbb'}, {tweet_id: 12, tweet_text: 'ccc'}]}, {id: 2, tweet_id: 2, isSelected: false, tweet_text: 'ddd', tweetReplies: [{tweet_id: 21, tweet_text: 'eee'}, {tweet_id: 22, tweet_text: 'fff'}]}],
tweetActionIds: [],
}
},
methods: {
markTweet(tweetId, i) {
const idIndex = this.tweetActionIds.indexOf(tweetId)
this.tweets[i].isSelected = !this.tweets[i].isSelected
if (this.tweetActionIds.includes(tweetId)) {
this.tweetActionIds.splice(idIndex, 1)
} else {
this.tweetActionIds.push(tweetId)
}
},
markReply(replyId) {
const idIndex = this.tweetActionIds.indexOf(replyId)
if (this.tweetActionIds.includes(replyId)) {
this.tweetActionIds.splice(idIndex, 1)
} else {
this.tweetActionIds.push(replyId)
}
},
checkReply(r) {
return this.tweetActionIds.includes(r) ? true : false
}
},
})
app.mount('#demo')
.selected {color: red;}
<script src="https://cdn.jsdelivr.net/npm/vue#3/dist/vue.global.prod.js"></script>
<div id="demo">
<div class="container-full">
<div class="tweets-container">
<div v-for="(tweet, i) in tweets" :key="tweet.id">
<div
class="tweet-card"
:class="{ selected: tweet.isSelected }"
#click="markTweet(tweet.tweet_id, i)"
>
<div class="text">
<p v-html="tweet.tweet_text">
{{ tweet.tweet_text }}
</p>
</div>
</div>
<div class="replies">
<div
v-for="(reply, index) in tweet.tweetReplies"
:key="reply.tweet_id"
#click="markReply(reply.tweet_id, index)"
>
<div class="tweet-card tweet-reply">
<div class="text" :class="{selected: checkReply(reply.tweet_id)}">
<p>{{ reply.tweet_text }}</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{{tweetActionIds}}
</div>
You can build on Nikola's answer by bypassing the extra step of adding isSelected to each individual Tweet by just checking if it's in the tweetActionIds array, and then do the same with the replies to keep it clean
<div id="demo">
<div class="container-full">
<div class="tweets-container">
<div
v-for="(tweet, i) in tweets"
:key="tweet.id"
>
<div
class="tweet-card"
:class="{ selected: isActive(tweet) }"
#click="markTweet(tweet.tweet_id, i)"
>
<div class="text">
<p v-html="tweet.tweet_text">
{{ tweet.tweet_text }}
</p>
</div>
</div>
<div class="replies">
<div
v-for="(reply, index) in tweet.tweetReplies"
:key="reply.tweet_id"
#click="markReply(reply.tweet_id, index)"
>
<div
:class="{ selected: isActive(reply) }"
class="tweet-card tweet-reply"
>
<div class="text">
<p>{{ reply.tweet_text }}</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{{tweetActionIds}}
</div>
const app = Vue.createApp({
data() {
return {
tweets: []
tweetActionIds: [],
categories: [],
}
},
methods: {
markTweet(tweetId, i) {
const idIndex = this.tweetActionIds.indexOf(tweetId)
if (this.tweetActionIds.includes(tweetId)) {
this.tweetActionIds.splice(idIndex, 1)
} else {
this.tweetActionIds.push(tweetId)
}
},
markReply(replyId) {
const idIndex = this.tweetActionIds.indexOf(replyId)
if (this.tweetActionIds.includes(replyId)) {
this.tweetActionIds.splice(idIndex, 1)
} else {
this.tweetActionIds.push(replyId)
}
},
isSelected(tweet) {
return this.tweetActionIds.includes(tweet.tweet_id);
}
},
})
I'm searching a solution for my problem : I want to load my component AddArticle.vue on Home.vue with httpVueLoader, I have tried many times with components: {"AddArticle" : window.httpVueLoader('./components/AddArticle.vue')}, or components: {"AddArticle" : httpVueLoader('./components/AddArticle.vue')}, but my problem is always here.... My component "AddArticle.vue" has not be displayed on my component "Home.vue", why ?
Thanks you in advance everyone
All my component and sources:
Home.vue
<template>
<div>
<AddArticle/>
<article v-for="article in articles" :key="article.id">
<div class="article-img">
<div :style="{ backgroundImage: 'url(' + article.image + ')' }">
</div>
</div>
<div class="article-content" v-if="editingArticle.id !== article.id">
<div class="article-title">
<h2>{{ article.name }} - {{ article.price }}€</h2>
<div>
<button #click="deleteArticle(article.id)">Supprimer</button>
<button #click="editArticle(article)">Modifier</button>
</div>
</div>
<p>{{ article.description }}</p>
</div>
<div class="article-content" v-else>
<div class="article-title">
<h2><input type="text" v-model="editingArticle.name"> - <input type="number" v-model="editingArticle.price"></h2>
<div>
<button #click="sendEditArticle()">Valider</button>
<button #click="abortEditArticle()">Annuler</button>
</div>
</div>
<p><textarea v-model="editingArticle.description"></textarea></p>
<input type="text" v-model="editingArticle.image" placeholder="Lien vers l'image">
</div>
</article>
</div>
</template>
<script>
module.exports = {
props: {
articles: { type: Array, default: [] },
panier: { type: Object }
},
components: {"AddArticle" : httpVueLoader('./components/AddArticle.vue')},
data () {
return {
edit: 'test',
editingArticle: {
id: -1,
name: '',
description: '',
image: '',
price: 0
}
}
},
methods: {
addArticle () {
alert("Piti")
},
deleteArticle (articleId) {
this.$emit('delete-article', articleId)
},
editArticle (article) {
this.editingArticle.id = article.id
this.editingArticle.name = article.name
this.editingArticle.description = article.description
this.editingArticle.image = article.image
this.editingArticle.price = article.price
},
sendEditArticle () {
this.$emit('update-article', this.editingArticle)
this.abortEditArticle()
},
abortEditArticle () {
this.editingArticle = {
id: -1,
name: '',
description: '',
image: '',
price: 0
}
}
}
}
</script>
<style scoped>
article {
display: flex;
}
.article-img {
flex: 1;
}
.article-img div {
width: 100px;
height: 100px;
background-size: cover;
}
.article-content {
flex: 3;
}
.article-title {
display: flex;
justify-content: space-between;
}
textarea {
width: 100%;
}
</style>
AddArticle.vue
<template>
<form #submit.prevent="addArticle">
<h2>Nouveau produit à ajouter</h2>
<input type="text" v-model="newArticle.name" placeholder="Nom du produit" required/>
<input type="number" v-model="newArticle.price" placeholder="Prix" required>
<textarea v-model="newArticle.description" required></textarea>
<input type="text" v-model="newArticle.image" placeholder="Lien vers l'image">
<button type="submit">Ajouter</button>
</form>
</template>
<script>
module.exports = {
props: {
articles: { type: Array, default: [] },
panier: { type: Object }
},
data () {
return {
newArticle: {
name: '',
description: '',
image: '',
price: 0
},
}
},
methods: {
addArticle() {
this.$emit('add-article')
}
},
async mounted () {
},}
</script>
vue-application.js
const Home = window.httpVueLoader('./components/Home.vue')
const Panier = window.httpVueLoader('./components/Panier.vue')
const AddArticle = window.httpVueLoader('./components/AddArticle.vue')
const routes = [
{ path: '/', component: Home },
{ path: '/panier', component: Panier },
{ path: "/article", component: AddArticle},
]
const router = new VueRouter({
routes
})
var app = new Vue({
router,
el: '#app',
data: {
articles: [],
panier: {
createdAt: null,
updatedAt: null,
articles: []
}
},
async mounted () {
const res = await axios.get('/api/articles')
this.articles = res.data
// const res2 = await axios.get('/api/panier')
// this.panier = res2.data
},
methods: {
async addArticle (article) {
const res = await axios.post('/api/article', article)
this.articles.push(res.data)
},
async updateArticle (newArticle) {
await axios.put('/api/article/' + newArticle.id, newArticle)
const article = this.articles.find(a => a.id === newArticle.id)
article.name = newArticle.name
article.description = newArticle.description
article.image = newArticle.image
article.price = newArticle.price
},
async deleteArticle (articleId) {
await axios.delete('/api/article/' + articleId)
const index = this.articles.findIndex(a => a.id === articleId)
this.articles.splice(index, 1)
}
}
})
index.html
<!doctype>
<html lang="fr">
<head>
<meta charset="utf-8">
<title>Mon magasin en ligne 100% bons plans</title>
<!-- Load Vue followed by Vue Router -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/http-vue-loader/src/httpVueLoader.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue-router/dist/vue-router.js"></script>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
</head>
<body>
<div id="app">
<header>
<h1>À la bonne franquette</h1>
<nav>
<router-link to='/'>Accueil</router-link>
<router-link to='/panier'>Panier</router-link>
<router-link to="/article"> AjoutArticle </router-link>
</nav>
</header>
<main>
<router-view
:articles="articles"
:panier="panier"
#add-article="addArticle"
#delete-article="deleteArticle"
#update-article="updateArticle"
></router-view>
</main>
</div>
<script src="/vue-application.js"></script>
</body>
</html>
How to share #focus="focus('')" and #blur="blur('')"?
If the email input format is correct, but the name input format is wrong, the name will not display an error message. I am thinking about how to use the "fieldName" so that he can judge which I clicked.
If there are many regular expressions that need to be judged, is there a way to share function?
const app = new Vue({
el: "#app",
data: {
carrierEmailError: false,
carrierNameError: false,
carrierEmailErrMsg: '',
carrierNameErrMsg: '',
},
methods: {
// emailRule
emailRule() {
var isEmail = /^\w+((-\w+)|(\.\w+))*\#[A-Za-z0-9]+((\.|-)[A-Za-z0-9]+)*\.[A-Za-z]+$/
if (!isEmail.test(this.carrierEmail)) {
return false
}
return true
},
// nameRule
nameRule() {
var isText = /^[A-Za-z0-9]{1,10}$/
if (!isText.test(this.carrierName)) {
return false
}
return true
},
focus(fieldName) {
this.carrierEmailError = false;
this.carrierNameError = false;
},
blur(fieldName) {
if (this.emailRule() === true) {
this.carrierEmailError = false;
this.carrierEmailErrMsg = '';
return
} else {
this.carrierEmailError = true;
this.carrierEmailErrMsg = 'Incorrect email format';
}
if (this.nameRule() === true) {
this.carrierNameError = false;
this.carrierNameErrMsg = '';
return
} else {
this.carrierNameError = true;
this.carrierNameErrMsg = 'Incorrect email format';
}
},
}
})
<html>
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
</head>
<body>
<div id="app">
<div class="form-control invoice-form" :class="{ 'is-invalid': carrierEmailError }">
<input v-model="carrierEmail" #blur="blur('carrierEmail')" #focus="focus('carrierEmail')" type="text" placeholder="E-MAIL">
</div>
<div class="invalid-feedback" v-show="carrierEmailError">
{{ carrierEmailErrMsg }}
</div>
<div class="form-control invoice-form" :class="{ 'is-invalid': carrierNameError }">
<input v-model="carrierName" #blur="blur('carrierName')" #focus="focus('carrierName')" type="text" placeholder="name">
<div class="ic-clear" #click="clearField('carrierName')"></div>
</div>
<div class="invalid-feedback" v-show="carrierNameError">{{ carrierNameErrMsg }}</div>
</div>
</body>
</html>
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>