So I'm bringing in data from a CMS which is dynamically creating a title, a size and an input field which the user will be able to change.
When I increase the input value (1, 2, 3, 4 etc) the value changes in all inputs.
I need to take the number of individual inputs and do something with it.
I can kind of get this to work if I have my input type set to number but I want to keep it set to text so I can use custom increment and decrement buttons.
As I said the buttons and the counter works but values clone to every input. I'm sure it's something to do with the increaseQty and decreaseQty functions..
I'm very new to Vue so be gentle.
Thanks a million - Code below
<template>
<div>
<div id="step2items0" class="inventory">
<div class="inner-wrap">
<label>Choose Your {{ selected }} Items</label>
</div>
<div id="tabBtns" class="hr">
<div class="inner-wrap mobile-center">
<div v-for="(tab, index) in tabs" :key="index" #click="selectTab(tab)" class="tab" :class="selected_tab == tab ? 'selected' : ''" >
{{ tab }}
</div>
</div>
</div>
<div class="items-wrap">
<div class="item" v-for="(row, index) in items" :key="index">
<span class="lbl">{{ row.item_title }}</span>
<span class="input-wrap" style="display: flex; flex-direction: column">
<input type="text" name="item_m3" class="number" :value="row.item_m3"/>
<small>Item M3</small>
</span>
<span class="input-wrap">
<i id="decreaseQty" aria-hidden="true" class="fa fa-caret-left prev-btn" #click="decreaseQty"></i>
<input id="quantity" type="text" class="number" :value="quantity"/>
<i id="increaseQty" aria-hidden="true" class="fa fa-caret-right nxt-btn" #click="increaseQty"></i>
</span>
<div class="clear"></div>
</div>
</div>
</div>
</div>
</template>
<script>
module.exports = {
props: ["selected", "content", "value"],
data: function () {
return {
tabs: [],
selected_tab: "",
items: [],
quantity: 0,
};
},
computed: {
},
methods: {
//
increaseQty() {
this.quantity++;
console.log("AMOUNT -", this.quantity);
},
decreaseQty() {
if (this.quantity === 0) {
alert("Negative quantity not allowed");
} else {
this.quantity--;
console.log("AMOUNT -", this.quantity);
}
},
//
selectTab: function (value) {
console.log("TAB: ", value);
this.selected_tab = value;
const { content } = this;
let { items } = this;
content.forEach(function (row) {
if (row.room_name == value) {
items = row.item;
items.forEach(function (row) {
item_title = row.item_title;
item_m3 = row.item_m3;
console.log(" - ", item_title, " - ", item_m3);
});
}
});
this.items = items;
},
},
mounted: function () {
const { selected, content, tabs } = this;
let { selected_tab, items } = this;
console.log("SELECTED: ", selected);
if (selected === "Household") {
content.forEach(function (row) {
if (row.is_household == true) {
tabs.push(row.room_name);
if (selected_tab == "") {
selected_tab = row.room_name;
items = row.item;
items.forEach(function (row) {
item_title = row.item_title;
item_m3 = row.item_m3;
console.log(" - ", item_title, " - ", item_m3);
});
}
}
});
} else if (selected === "Business") {
content.forEach(function (row) {
if (row.is_business == true) {
tabs.push(row.room_name);
if (selected_tab == "") {
selected_tab = row.room_name;
items = row.item;
items.forEach(function (row) {
item_title = row.item_title;
item_m3 = row.item_m3;
console.log(row.room_name, " - ", item_title, " - ", item_m3);
});
}
}
});
}
this.selected_tab = selected_tab;
this.items = items;
},
};
</script>
<style scoped>
</style>
Input increment is affecting all inputs
You can get individual values inside the v-for by storing the values in an array.
data: {
items: [],
itemValues: [],
},
And use v-model to bind the input (you are using v-bind:value, not v-model). You can check this answer to learn the difference.
<div class="item" v-for="(row, index) in items" :key="index">
<input type="text" v-model="itemValues[index]">
</div>
Here's a fiddle:
https://jsfiddle.net/jariway/jxv0k4y3/62/
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've searched and couldn't see any answer that fits what I need. I have a v-for loop with a button on each item and used VueClipboard2 to copy text. Anytime the button is clicked, I do some css changes to indicated the item that was copied. What happens is that, if there's more than 1 item, clicking on any button affects affect every other item and does the same effect.
I want to limit the clicking to the "own" item being clicked.
Here's my code:
<template>
<div class="form" id="shorten">
<form class="" #submit.prevent="shortener($event, value)">
<div>
<div class="form__shortener">
<input
class="form-input"
type="url"
name="link"
id="link"
placeholder="shorten a url here"
aria-label="input a url"
v-model="value"
/>
<button class="form-btn btn">
{{ buttonText }}
<p v-if="loading" class="loading"></p>
</button>
</div>
<SlideXLeftTransition :delay="100">
<p v-if="error" class="error">Please enter a valid link</p>
</SlideXLeftTransition>
</div>
</form>
<SlideYUpTransition group>
<div v-for="(link, index) in links" :key="index" class="form__links">
<p class="form__links-main">
{{ link.mainUrl }}
</p>
<div class="center form__links-copy">
<p>
<a :href="link.shortenedUrl" class="form__links-copy-link no-decoration">{{ link.shortenedUrl }}</a>
</p>
<button
class="form__links-copyBtn btn"
:class="[copied === true ? 'copied' : '']"
v-clipboard:copy="link.shortenedUrl"
v-clipboard:success="onCopy"
v-clipboard:error="onError"
>
<span v-if="!loading && !copied">Copy</span>
<span v-if="copied">Copied!</span>
</button>
</div>
</div>
</SlideYUpTransition>
</div>
</template>
<script>
import { required, minLength } from 'vuelidate/lib/validators';
import { SlideYUpTransition, SlideXLeftTransition } from 'vue2-transitions';
import axios from 'axios';
export default {
data() {
return {
value: '',
links: [],
message: '',
error: false,
loading: false,
buttonText: 'Shorten it!',
shortenedUrl: '',
copied: false,
};
},
validations: {
value: {
required,
minLength: minLength(1),
},
},
methods: {
async shortener(event, value) {
this.$v.$touch();
if (this.$v.$invalid) {
this.showError();
} else {
try {
this.loading = true;
this.buttonText = 'Loading';
const request = await axios.post('https://rel.ink/api/links/', { url: value });
this.loading = false;
this.buttonText = 'Shortened!';
setTimeout(() => {
this.buttonText = 'Shorten it!';
}, 1200);
this.shortenedUrl = `https://rel.ink/${request.data.hashid}`;
const mainUrl = request.data.url.length <= 20 ? request.data.url : `${request.data.url.slice(0, 30)}...`;
this.links.push({
shortenedUrl: `https://rel.ink/${request.data.hashid}`,
mainUrl,
});
localStorage.setItem('links', JSON.stringify(this.links));
} catch (error) {
this.showError();
console.log(error);
}
}
},
onCopy() {
this.copied = true;
setTimeout(() => {
this.copied = false;
}, 2500);
},
showError() {
this.error = true;
setTimeout(() => {
this.error = false;
}, 2000);
},
onError() {
alert('Sorry, there was an error copying that link. please reload!');
},
getLinks() {
if (localStorage.getItem('links')) this.links = JSON.parse(localStorage.getItem('links'));
},
},
components: {
SlideYUpTransition,
SlideXLeftTransition,
},
mounted() {
this.getLinks();
},
};
</script>
I would appreciate if anyone who help out.
Here's the live link: https://url-shortener-vue.netlify.app
To replicate, shorten two lines and click on the copy button on 1. It triggers all other items button.
Thank you.
Reason for your problem is
:class="[copied === true ? 'copied' : '']". SInce when you click any copy button, you change copied, and same class is used in all the iterations.
So, got the problem.
Solution is, you should have this copied corresponding to each link. So make your link as object.
link = [{ link: 'url...', copied: false}, {}, ...].
and, check for each link's copied value.
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 have been googling and playing with every combination I know but I cannot get my checkboxes to be initialised as checked.
my controller
public function changeStatus(Request $request, about $about)
{
$this->validate($request, [
'status' => 'in:true,false'
]);
$about->update(['status' => !$about->status]);
return response()->json($about);
}
my migration
$table->boolean('status')->default(false)->nullable();
my template vuejs
<template v-for="item in abouts" :key="item.id"> <a href="javascript:void(0)"
class="togglebutton btn btn-link btn-sm btn-sm"
#click="changeStatusItem(item)">
<label>
<input type="checkbox" name="status" v-model="item.status"
v-bind:id="item.id" :checked="item.status"/>
<span class="toggle"></span>
</label>
</a></template>
my script
export default{props: {
checked: Boolean
},
data() {
return {
abouts: {},
}
},
methods: {
changeStatusItem(item) {
//Start Progress bar
this.$Progress.start();
axios.get(`/change_status_abouts/${item.id}`).then((res) => {
/** Alert notify bootstrapp **/
console.log(res);
})
},}}
Here is how I would do it. Remember that it could be something as simple as the casting from Laravel model i.e. returns a string "1" and not a boolean so to fix that you can use a method that checks for boolean or string, etc. Also need to set the response back to the collection. Also don't use v-model - you don't need it in your example
<template>
<div v-for="item in abouts"
:key="item.id">
<a href="javascript:void(0)"
class="togglebutton btn btn-link btn-sm btn-sm"
#click="changeStatusItem(item)">
<label>
<input type="checkbox"
name="status"
:id="item.id"
:checked="isChecked(item)"/>
<span class="toggle"></span>
</label>
</a>
</div>
</template>
<script>
export default {
name: "Example",
data: function() {
return {
abouts: {}
}
},
methods: {
isChecked(obj) {
return (typeof obj.status === "boolean" )
? obj.status
: typeof obj.status === "string" && obj.status === '1';
},
changeStatusItem: function (item) {
let vm = this;
axios.get(`/change_status_abouts/${item.id}`)
.then((response) => {
for (key in vm.abouts) {
if (vm.abouts.hasOwnProperty(key)) {
vm.abouts[key].status = (vm.abouts[key].id === item.id)
}
}
});
},
getAbouts: function () {
let vm = this;
axios.get(`/get/abouts`)
.then((response) => {
vm.abouts = response.data;
});
},
init() {
this.getAbouts();
}
},
mounted: function () {
this.$nextTick(this.init());
}
}
</script>
When you calling this below method, you can use Array.find() once get response laravel and update into your about array in VueJs
changeStatusItem(item) {
axios.get(`/change_status_abouts/${item.id}`).then((res) => {
let rec = this._data.about.find(o=>o.id == item.id);
if(rec){
rec.status = res.status;
}
})
}
And also you can directly use click event on <input type="checkbox" #click="checkChange($event,item)"> and that event you can call the your ajax call. So in that case you don't need to update the status in about array
Please check below code snippet.
var demo = new Vue({
el: '#demo',
data: {
about:[]
},
methods: {
changeStatusItem(item) {
let rec = this._data.about.find(o=>o.id == item.id);
if(rec){
rec.status = !rec.status;
}
},
getValue(item) {
return `${item.id}. - ${item.title.toLocaleUpperCase()}`;
}
},
created: function() {
let self = this;
fetch('https://jsonplaceholder.typicode.com/posts')
.then(response => response.json())
.then(json =>{self.$data.about = json.map(o=>Object.assign(o,{status:false}))
document.querySelector('.main-div').style.display='none'
})
}
})
.main-div{background-color:#40e0d0;color:#fff;font-size:24px;padding:26px 0;text-align:center;height:100%;width:100%;display:inline-block}.lds-roller{display:inline-block;position:relative;width:64px;height:64px}.lds-roller div{animation:lds-roller 1.2s cubic-bezier(.5,0,.5,1) infinite;transform-origin:32px 32px}.lds-roller div:after{content:" ";display:block;position:absolute;width:6px;height:6px;border-radius:50%;background:#fff;margin:-3px 0 0 -3px}.lds-roller div:nth-child(1){animation-delay:-36ms}.lds-roller div:nth-child(1):after{top:50px;left:50px}.lds-roller div:nth-child(2){animation-delay:-72ms}.lds-roller div:nth-child(2):after{top:54px;left:45px}.lds-roller div:nth-child(3){animation-delay:-108ms}.lds-roller div:nth-child(3):after{top:57px;left:39px}.lds-roller div:nth-child(4){animation-delay:-144ms}.lds-roller div:nth-child(4):after{top:58px;left:32px}.lds-roller div:nth-child(5){animation-delay:-.18s}.lds-roller div:nth-child(5):after{top:57px;left:25px}.lds-roller div:nth-child(6){animation-delay:-216ms}.lds-roller div:nth-child(6):after{top:54px;left:19px}.lds-roller div:nth-child(7){animation-delay:-252ms}.lds-roller div:nth-child(7):after{top:50px;left:14px}.lds-roller div:nth-child(8){animation-delay:-288ms}.lds-roller div:nth-child(8):after{top:45px;left:10px}#keyframes lds-roller{0%{transform:rotate(0)}100%{transform:rotate(360deg)}}.list{padding: 10px;border-bottom: 1px solid #ccc;}
<link rel="stylesheet" type="text/css" href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900|Material+Icons">
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/vuetify#1.4.0/dist/vuetify.min.css">
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vuetify#1.4.0/dist/vuetify.min.js"></script>
<div id="demo" >
<div class="main-div"><div class="lds-roller"><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div></div><p>Pleaes wait...!</p>
</div>
<div class="list" v-for="item in about">
<a href="javascript:void(0)"
class="togglebutton btn btn-link btn-sm btn-sm"
#click="changeStatusItem(item)">
<label>
<input type="checkbox" name="status" v-model="item.status"
v-bind:id="item.id"/>
<span class="toggle"></span>
</label> {{getValue(item)}}
</a>
</div>
</div>
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>