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>
Related
I have 2 buttons. One adds a movie to local storage, the other removes it from there. I made a function that basically switches the button. If the movie is added it shows "remove", if the movie's not been added it shows the button "add".
The function works but it doesn't know when the boolean changes so the button doesn't change. Someone explained that i should use watch property, but how am I supposed to watch an output of a function?
here is the code
<template>
<div>
<div class="card" v-for="movie in movies"
:key="movie.id">
{{movie.title}}
{{movie.release_date}}
<button v-show="!showButton(movie.id)" type="submit" #click="storeMovie(movie.id)" >
Aggiungi
</button>
<button v-show="showButton(movie.id)" type="submit" #click="removeMovie(movie.id)">
Rimuovi
</button>
</div>
<div class="card" v-for="favourite in watchlist"
:key="favourite.id">
{{favourite.title}}
</div>
</div>
</template>
<script>
import axios from 'axios'
export default {
name: 'HomeComp',
data () {
return {
movies: [],
watchlist: [],
movie: null,
}
},
mounted () {
axios
.get('https://api.themoviedb.org/3/movie/popular?api_key=###&language=it-IT&page=1&include_adult=false®ion=IT')
.then(response => {
this.movies = response.data.results
// console.log(response.data.results)
})
.catch(error => {
console.log(error)
this.errored = true
})
.finally(() => this.loading = false)
},
watch: {
switchButton(oldValue, newValue) {
if (oldValue != newValue) {
this.showButton(id) = true;
} //made an attempt here
}
},
methods: {
storeMovie(id) {
const favouriteMovie = this.movies.find(movie => movie.id === id )
this.watchlist.push(favouriteMovie);
localStorage.setItem("watchlist", JSON.stringify(this.watchlist));
},
removeMovie(id) {
const removedMovie = this.watchlist.find(movie => movie.id === id )
const indexMovie = this.watchlist.indexOf(removedMovie);
if (indexMovie > -1) {
this.watchlist.splice(indexMovie, 1);
}
localStorage.setItem("watchlist", JSON.stringify(this.watchlist));
},
showButton(id) {
const favouriteMovie = this.watchlist.find(movie => movie.id === id )
if (favouriteMovie && favouriteMovie.length > 0) {
return true
} else{
return false
}
}
},
}
</script>
<style scoped lang="scss">
</style>
A better approach would be to store the state of a movie being stored or not in the watchlist directly on the movie object.
Then use a computed to get the watchlist from the movie list instead of using two different arrays.
<template>
<div>
<div class="card" v-for="movie in movies" :key="movie.id">
{{movie.title}}
{{movie.release_date}}
<button v-show="!movie.toWatch" type="submit" #click="storeMovie(movie.id)">
{{ movie.toWatch ? 'Rimuovi' : 'Aggiungi' }}
</button>
</div>
<div class="card" v-for="favourite in watchList" :key="favourite.id">
{{favourite.title}}
</div>
</div>
</template>
<script>
export default {
name: 'HomeComp',
data() {
return {
movies: [],
}
},
computed: {
// Get the watchList from the movies list
watchList() {
return this.movies.filter(movie => movie.toWatch)
}
},
watch: {
watchList(newWatchList) {
// Update the localStorage whenever the list changes
localStorage.setItem("watchlist", JSON.stringify(newWatchList));
}
},
mounted() {
// your axios call
},
methods: {
storeMovie(id) {
const favouriteMovie = this.movies.find(movie => movie.id === id)
if (favouriteMovie) {
// just reverse the boolean
favouriteMovie.toWatch = !favouriteMovie.toWatch
}
},
},
}
</script>
I'm trying to get a clicked button id from MovieTable.vue to WatchedForm.vue component. WatchedForm.vue component updates the data in the database based on the given id. So this Movie_id is the id obtained from the database. I've already tried using props, but I didn't get it to work. Please help! I'm losing my mind..
App.vue:
<template>
<div class="container p-5">
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#movieModal">
Add Movie
</button>
<movie-form #add:movie="addMovie" />
<movie-table
:movies="movies"
#delete:movie="deleteMovie"
#edit:movie="WatchedMovie"
#edit2:movie="unWatchedMovie"
/>
</div>
<watched-form
#edit:movie="watchedMovie"
#edit2:movie="unWatchedMovie"
/>
</template>
<script>
//importing bootstrap 5
import "bootstrap/dist/css/bootstrap.min.css";
import "bootstrap/dist/js/bootstrap.min.js";
import MovieTable from '#/components/MovieTable.vue';
import MovieForm from '#/components/MovieForm.vue';
import WatchedForm from '#/components/WatchedForm.vue';
export default {
components: {
MovieTable,
MovieForm,
WatchedForm
},
data() {
return {
movies: {},
}
},
mounted() {
this.getMovies()
},
methods: {
async getMovies() {
try {
const response = await fetch('http://localhost:8081/api/movies')
const data = await response.json()
this.movies = data
} catch (error) {
console.error(error)
}
},
async addMovie(movie) {
try {
const response = await fetch('http://localhost:8081/api/addMovie', {
method: 'POST',
body: JSON.stringify(movie),
headers: {"Content-type": "application/json; charset=UTF-8"}
})
const data = await response.json()
this.movies = [...this.movies, data]
} catch (error) {
console.error(error)
}
},
async deleteMovie(Movie_id) {
try {
await fetch(`http://localhost:8081/api/delete/${Movie_id}`, {
method: 'DELETE'
})
this.movies = this.movies.filter(movie => movie.Movie_id !== Movie_id)
} catch (error) {
console.error(error)
}
},
async watchedMovie(Movie_id, updatedMovie) {
try {
const response = await fetch(`http://localhost:8081/api/movies/watched/${Movie_id}`, {
method: 'PUT',
body: JSON.stringify(updatedMovie),
headers: { "Content-type": "application/json" }
})
const data = await response.json()
this.movies = this.movies.map(movie => movie.Movie_id === Movie_id ? data : movie)
} catch (error) {
console.error(error)
}
},
async unWatchedMovie(Movie_id, updatedMovie) {
try {
const response = await fetch(`http://localhost:8081/api/movies/unwatched?id=${Movie_id}`, {
method: 'PUT',
body: JSON.stringify(updatedMovie),
headers: { "Content-type": "application/json" }
})
const data = await response.json()
this.movies = this.movies.map(movie => movie.Movie_id === Movie_id ? data : movie)
this.movies = this.getMovies();
} catch (error) {
console.error(error)
}
}
}
}
</script>
MovieTable.vue:
<template>
<div id="movie-table">
<p v-if="movies.length < 1" class="empty-table">No movies</p>
<table v-else>
<thead>
<tr>
<th>Name</th>
<th>Genre</th>
<th>Duration</th>
<th>Rating</th>
<th>Watched?</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<tr :key="movie.Movie_id" v-for="movie in movies " :id="'form' + movie.Movie_id">
<td>{{movie.Name}}</td>
<td>{{movie.Genre}}</td>
<td>{{movie.Duration}}</td>
<td>{{movie.Rating}}</td>
<td>
<span v-if="movie.is_watched">Yes</span>
<span v-else>No</span>
</td>
<td>
<button type="button" class="btn btn-primary" #click="unWatchedMovie(movie)" v-if="movie.is_watched === 1">
Unwatched
</button>
<!-- THIS BUTTON ID I WANT TO GET -->
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#watchedModal" #click="watchedMovie( movie)" v-else>
Watched
</button>
<button class="btn btn-primary" #click="$emit('delete:movie', movie.Movie_id)">Delete</button>
</td>
</tr>
</tbody>
</table>
</div>
</template>
<script>
export default {
name: 'movie-table',
props: {
movies: Array
},
data() {
return {
editing: null,
}
},
methods: {
watchedMovie(movie) {
if (movie.Name === '' || movie.Genre === '' || movie.Duration === '' || movie.is_watched === '') return
this.$emit('edit:movie', movie.Movie_id, movie)
this.editing = null
},
unWatchedMovie(movie) {
if (movie.Name === '' || movie.Genre === '' || movie.Duration === '' || movie.is_watched === '') return
this.$emit('edit2:movie', movie.Movie_id, movie)
this.editing = null
},
handler(id) {
console.log(id);
}
}
}
And WatchedForm.vue:
<template>
<div id="watched-form">
<div class="modal fade" id="watchedModal" tabindex="-1" aria-labelledby="watchedModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title text-danger" id="watchedModalLabel">Add new view</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<form #submit.prevent="handleSubmit">
<!-- TRYING THERE TO SHOW ID FROM MOVIETABLE -->
<div id="watched-form1">{{movie.movie_id}}</div>
<div class="mb-3">
<label class="form-label">Place</label>
<input ref="first"
type="text"
v-model="movie.Place"
#focus="clearStatus"
#keypress="clearStatus"
class="form-control"
/>
</div>
<div class="mb-3">
<label class="form-label">Date</label>
<input
type="Date"
v-model="movie.Date"
#focus="clearStatus"
class="form-control"
/>
</div>
<div class="mb-3">
<label class="form-label">Rating 1-5</label>
<input
type="number"
min="1"
max="5"
:class="{ 'has-error': submitting && invalidRating}" class="form-control"
v-model="movie.Rating"
#focus="clearStatus"
/>
</div>
<div class="mb-3">
<label class="form-label">Comments</label>
<textarea class="form-control" v-model="movie.Comments" #focus="clearStatus"></textarea>
</div>
<div class="mb-3">
<label class="form-label">Watched?</label>
<input
type="checkbox"
v-model="movie.is_watched.checked"
#focus="clearStatus"
checked
disabled
/>
</div>
<p v-if="error && submitting" class="error-message">❗Please fill out rating required field</p>
<p v-if="success" class="success-message">✅ Movie successfully added</p>
<button type="submit" class="btn btn-primary">Save</button>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-warning" data-bs-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'watched-form',
data() {
return {
submitting: false,
error: false,
success: false,
movie: {
Name: '',
Genre: '',
Duration: '',
Rating: '',
is_watched: '',
}
}
},
methods: {
handleSubmit() {
this.clearStatus()
this.submitting = true
if (this.invalidRating ) {
this.error = true
return
}
this.$emit('edit:movie', this.movie)
this.$refs.first.focus()
this.movie = {
Name: '',
Genre: '',
Duration: '',
Rating: '',
is_watched: ''
}
this.error = false
this.success = true
this.submitting = false
},
clearStatus() {
this.success = false
this.error = false
},
},
computed: {
invalidRating() {
return this.movie.Rating === ''
}
},
}
</script>
You just need to do the below changes in App.vue, when you emit the movie id from MovieTable.vue, you got to send it as props to WatchedForm.vue not as a variable instead as a computed property. Check the below approach
<movie-table
:movies="movies"
#delete:movie="deleteMovie"
#edit:movie="WatchedMovie"
#edit2:movie="unWatchedMovie"
/>
</div>
<watched-form
:movieId="getMovieId"
/>
and add a variable called movieId inside data() like
data() {
return {
movieId: '',
....other variables
};
}
and in computed
computed: {
getMovieId() {
return this.movieId;
}
}
and in your methods where you set the movieId that is emitted from MovieTable.vue such that the computed property runs dynamically
methods: {
async watchedMovie(Movie_id, updatedMovie) {
this.movieId = Movie_id; // setting the movie_id in localvariable so that the computed property sends the updated value to WatchedForm.vue
// ...Other lines of code
},
async unWatchedMovie(Movie_id, updatedMovie) {
this.movieId = Movie_id; // setting the movie_id in localvariable so that the computed property sends the updated value to WatchedForm.vue
// ...Other lines of code
}
}
Beginner to Vue and JavaScript - but learning ))
I have a Vue Mains App, and a sub-component handling a form (Calculate the difference between 2 values).
I wish the values of the form to be reset to the initial value when the mains App "Reset" button is clicked.
I have 2 problems:
the page reloads when I click on the form button "Calculate Difference"
the "Reset" is not resetting the initial value - it seems the event is not distributed to the sub-component.
On loading the page I have the following warning that I can't interpret:
[Vue warn]: Property "reset" was accessed during render but is not defined on the instance. at
Here is the all in one-page sample code:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Difference</title>
<script src="https://unpkg.com/vue#next"></script>
</head>
<body>
<div id="app">
<br> Change Values, Click "Calculate Difference", then "Reset" to set Back to 0
<br>
<calculator v-on:reset="reset" :start_val="0" :end_val="0"> </calculator>
<br>
<button class="button" v-on:click="onResetButton()"> Reset </button>
</div>
</body>
<script>
const vm = Vue.createApp({
emits: {
'reset': null
},
methods: {
onResetButton() {
console.log("onResetButton $emit")
this.$emit('reset');
},
},
})
vm.component('calculator', {
props: ['start_val', 'end_val'],
data() {
return {
calculator: {
start_val: this.start_val,
end_val: this.end_val,
difference: this.end_val - this.start_val,
},
}
},
methods: {
onDifference() {
console.log("onDifference")
this.calculator.difference = this.calculator.end_val - this.calculator.start_val
console.log(this.calculator.difference)
},
reset() {
calculator.start_val = this.start_val
calculator.end_val = this.end_val
calculator.difference = this.end_val - this.start_val
console.log("reset calculator")
}
},
template: `
<hr>
<form>
<br> Start Value : <input class="input" type="number" v-model.number="calculator.start_val">
<br> End Value : <input class="input" type="number" v-model.number="calculator.end_val">
<br> <button class="button" v-on:click="onDifference()">Calculate Difference </button>
</form>
<p>Difference : {{ calculator.difference }} </p>
<hr>
`
})
vm.mount('#app');
</script>
</html>
Here's a small snippet that probably gives you ideas:
const {
reactive,
ref,
toRefs,
computed
} = Vue
const Calculator = {
props: {
startVal: {
type: Number,
default: 0,
},
endVal: {
type: Number,
default: 0,
},
},
emits: ['update:startVal', 'update:endVal'],
setup(props, {
emit
}) {
const start_val = computed({
get() {
return props.startVal
},
set(val) {
emit('update:startVal', val || 0)
},
})
const end_val = computed({
get() {
return props.endVal
},
set(val) {
emit('update:endVal', val || 0)
},
})
return {
start_val,
end_val
}
},
template: `
<div>
{{ start_val }} / {{ end_val }}<br />
StartVal: <input v-model.number="start_val" /><br />
EndVal: <input v-model.number="end_val" />
</div>
`
}
const initState = () => ({
startVal: 0,
endVal: 0
})
const App = {
setup() {
const state = reactive(initState())
const result = computed(() => {
return state.startVal + state.endVal
})
const resetState = () => {
const defaultState = initState()
for (let key in state) {
state[key] = defaultState[key]
}
}
return {
result,
...toRefs(state),
resetState,
}
},
template: `
<div>
Result: {{ result }}<br />
<button
#click="resetState"
>
RESET
</button>
<Calculator
v-model:startVal="startVal"
v-model:endVal="endVal"
/>
</div>
`
}
const vm = Vue
.createApp(App)
vm.component('Calculator', Calculator)
vm.mount('#app')
<script src="https://unpkg.com/vue#next"></script>
<div id="app"></div>
I'm making a quiz app. I have question selection menu and I need to save each checkbox state for each question. For example, if you were to select an answer for 1st question, I need to save your choices even if you skip to 3rd question so when you come back to 1st your old choices are still there.
Now the problem that I have is - my checkboxes aren't unique, they are same for each question. Please take a look at gif on what happens.
https://i.imgur.com/hua2NI8.gif
This is my code
<template>
<div v-if="loading"><loader></loader></div>
<div v-else class="main">
<h5 class="test-title">Test</h5>
<h5 class="choosequestion">
<font-awesome-icon icon="list-ul" style="margin-right: 8px" />Choose question
</h5>
<div v-dragscroll="true" class="questionsbox">
<!-- answered, current -->
<input v-for="(question, index) in questions" :key="index" :value="index + 1" #click="chooseQuestion(index)" class="questionbox" type="button" />
</div>
<div class="buttons">
<a class="ctlBtn">Restart</a>
<a class="ctlBtn">End</a>
</div>
<div class="questionmain">
<h5 class="questiontext">
<font-awesome-icon icon="question-circle" style="margin-right: 8px" />
{{ currentQuestion.question }}
</h5>
<ul>
<li
v-for="( answer, key ) in currentQuestion.answers"
:index="currentQuestion.key"
:key="answer.title"
>
<input v-model="checkedAnswers[key]" #change="answerClick" :id="answer.title" type="checkbox" />
<label :for="answer.title" >{{ answer.title }}</label>
</li>
{{ checkedAnswers }}
</ul>
<img
class="questionimg"
:src="currentQuestion.image_url"
/>
<a class="nextBtn" #click="nextQuestion"><font-awesome-icon icon="arrow-right" style="color: black;"/></a>
</div>
</div>
</template>
<script>
import { dragscroll } from "vue-dragscroll";
import Loader from "./Loader.vue";
export default {
components: { Loader },
name: "TestCard",
data() {
return {
questions: [],
loading: true,
index: 0,
checkedAnswers: []
};
},
computed: {
currentQuestion() {
if (this.questions !== []) {
return this.questions[this.index];
}
return null;
},
},
methods: {
async getQuestions() {
this.loading = true;
let response = await fetch("http://localhost:4000/test");
let jsonResponse = await response.json();
// put data on questions property
this.questions = jsonResponse.questions;
this.loading = false;
},
answerClick() {
console.log("answer clicked");
},
chooseQuestion(index) {
this.index = index;
},
nextQuestion() {
this.index += 1;
console.log("next question")
}
},
mounted() {
this.getQuestions();
},
directives: {
dragscroll,
},
};
</script>
After that, I would like to check if answers are correct when user presses on "End". How do I connect each checkbox to it's answer and question, then check it if its correct? I have "correct: true" in JSON for each correct answer :)
Thank you
Without changing too much of your existing code you could go about mapping your answers to questions like this. No need for Vuex or more complexity.
<template>
...
<ul>
<li
v-for="( answer, key ) in currentQuestion.answers"
:index="currentQuestion.key"
:key="answer.title"
>
// we pass the key to answerClick to be able to map the answer to the current question
<input v-model="checkedAnswers[key]" #change="answerClick(key)" :id="answer.title" type="checkbox" />
<label :for="answer.title" >{{ answer.title }}</label>
</li>
{{ checkedAnswers }}
...
</template>
</template>
<script>
export default {
name: "TestCard",
data() {
return {
questions: [],
loading: true,
index: 0,
checkedAnswers: [],
answers: []
};
},
computed: {
currentQuestion() {
if (this.questions !== []) {
return this.questions[this.index];
}
return null;
}
},
methods: {
async getQuestions() {
this.loading = true;
let response = await fetch("http://localhost:4000/test");
let jsonResponse = await response.json();
// put data on questions property
this.questions = jsonResponse.questions;
this.loading = false;
},
answerClick(key) {
console.log("answer clicked");
if (!this.answers[this.index]) this.answers[this.index] = [] // if the current question has no answers mapped yet set an empty array
this.answers[this.index][key] = this.checkedAnswers[key]; // set the answer to whatever value it has currently (true, false)
},
chooseQuestion(index) {
this.index = index;
this.checkedAnswers = this.answers[this.index] ? this.answers[this.index] : []; // set the checkedAnswers to what we can find in our mapping, if there is nothing to find use an empty array
},
nextQuestion() {
this.index += 1;
this.checkedAnswers = []; // unset the checked answers for the next question
console.log("next question")
}
},
mounted() {
this.getQuestions();
},
};
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