I am trying to get the json to display in cards or list after search. The search part seems to be working since I can see the call to the API with the text written in the search box in the console. but nothing is displaying and I get this error TypeError: Cannot read property 'data' of undefined. I don't think I am getting the json path correct.
app.vue
<template>
<div id="app">
<Header/>
<SearchForm v-on:search="search"/>
<SearchResults
v-if="results.length > 0"
v-bind:results="results"
v-bind:reformattedSearchString="reformattedSearchString"
/>
<Pagination
v-if="results.length > 0"
v-bind:prevPageToken="api.prevPageToken"
v-bind:nextPageToken="api.nextPageToken"
v-on:prev-page="prevPage"
v-on:next-page="nextPage"
/>
</div>
</template>
<script>
import Header from './components/layout/Header';
import SearchForm from './components/SearchForm';
import SearchResults from './components/SearchResults';
import Pagination from './components/Pagination';
import axios from 'axios';
export default {
name: 'app',
components: {
Header,
SearchForm,
SearchResults,
Pagination
},
data() {
return {
results: [],
reformattedSearchString: '',
api: {
baseUrl: 'https://geodeepdive.org/api/v1/articles?',
max: 10,
q: '',
prevPageToken: '',
nextPageToken: ''
}
};
},
methods: {
search(searchParams) {
this.reformattedSearchString = searchParams.join(' ');
this.api.q = searchParams.join('+');
const { baseUrl, q, max} = this.api;
const apiUrl = `${baseUrl}&term=${q}&title=${q}&max=${max}`;
this.getData(apiUrl);
},
prevPage() {
const { baseUrl, q, max, prevPageToken } = this.api;
const apiUrl = `${baseUrl}&term=${q}&title=${q}&max=${max}&pageToken=${prevPageToken}`;
this.getData(apiUrl);
},
nextPage() {
const { baseUrl, q, max,nextPageToken } = this.api;
const apiUrl = `${baseUrl}&term=${q}&title=${q}&max=${max}&pageToken=${nextPageToken}`;
this.getData(apiUrl);
},
getData(apiUrl) {
axios
.get(apiUrl)
.then(res => {
this.results = res.success.data;
this.api.prevPageToken = res.success.data.prevPageToken;
this.api.nextPageToken = res.success.data.nextPageToken;
})
.catch(error => console.log(error))
}
}
};
</script>
searchresults
<template>
<div class="container mb-3">
<div class="d-flex mb-3">
<div class="mr-auto">
<h3>Search Results for "{{ reformattedSearchString }}"</h3>
</div>
<div class="btn-group ml-auto" role="group">
<button
#click="changeDisplayMode('grid')"
type="button"
class="btn btn-outline-secondary"
v-bind:class="{ active: displayMode === 'grid' }"
>
<i class="fas fa-th"></i>
</button>
<button
#click="changeDisplayMode('list')"
type="button"
class="btn btn-outline-secondary"
v-bind:class="{ active: displayMode === 'list' }"
>
<i class="fas fa-list"></i>
</button>
</div>
</div>
<div class="card-columns" v-if="displayMode === 'grid'">
<div class="card" v-bind:key="result.link.url" v-for="result in results">
<ArticleGridItem v-bind:result="result"/>
</div>
</div>
<div v-else>
<div class="card mb-2" v-bind:key="result.link.url" v-for="result in results">
<ArticleListItem v-bind:result="result"/>
</div>
</div>
</div>
</template>
<script>
import ArticleListItem from './ArticleListItem';
import ArticleGridItem from './ArticleGridItem';
export default {
name: 'SearchResults',
components: {
ArticleListItem,
ArticleGridItem
},
data() {
return {
title: 'Search Results',
displayMode: 'grid'
};
},
methods: {
changeDisplayMode(displayMode) {
this.displayMode = displayMode;
}
},
props: ['results', 'reformattedSearchString']
};
</script>
json
{
success: {
v: 1,
data: [
{
type: "article",
_gddid: "5ea0b2b3998e17af826b7f42",
title: "The current COVID-19 wave will likely be mitigated in the second-line European countries",
volume: "",
journal: "Cold Spring Harbor Laboratory Press",
link: [
{
url: "https://www.medrxiv.org/content/10.1101/2020.04.17.20069179v1",
type: "publisher"
}
],
publisher: "bioRxiv",
author: [
{
name: "Samuel Soubeyrand"
}
],
pages: "",
number: "",
identifier: [
{
type: "doi",
id: "10.1101/2020.04.17.20069179"
}
],
year: "2020"
}
]
}
}
you are missing res.data object
this.results = res.data.success.data;
this.api.prevPageToken = res.data.success.data.prevPageToken;
this.api.nextPageToken = res.data.success.data.nextPageToken;
Related
My website looks like this:
Jobs.vue:
<div
id="jobs"
class="job-item"
v-for="(item, index) in showJobs"
:key="index"
>
<router-link
tag="a"
:to="{ name: 'Detail', params: { id: item.id } }"
>
<h3 class="mleft-27">{{ item.position }}</h3>
</router-link>
<div class="job-info flex-wrap">
<div>
<b>{{ item.exprerience }}</b>
</div>
<div>
<b>{{ item.salary }}</b>
</div>
<div>
<b>{{ item.headequarters }}</b>
</div>
</div>
<div class="info-job flex-wrap">
<div class="list-info-job" v-html="item.content"></div>
<router-link
:to="{ name: 'Detail', params: { id: item.id } }"
>
<button class="btn-detail">See Detail</button>
</router-link>
</div>
</div>
<div class="job-page">
<b-pagination
v-model="currentPage"
:total-rows="rows"
:per-page="perPage"
aria-controls="jobs"
></b-pagination>
Here is the script that I followed according to the docs from Vue-Bootstrap. And another thing is that my page uses filter and search box so I have to put 2 arrays of data, is this the problem? I have updated all the script code you can check it out and give me the solution
<script>
import "../assets/job.css";
import axios from "axios";
export default {
name: "jobs",
data() {
return {
currentPage: 1,
perPage: 2,
search: "",
noData: [],
display: {
group_filter: "",
btn_show_filter: "",
btn_close_filter: "",
},
checks: ["All", "Developer", "Tester", "Designer", "Support"],
jobinfos: [],
showJobs: [],
selected: "All",
};
},
computed: {
jobs() {
return this.showJobs.slice((this.currentPage - 1) * this.perPage, (this.currentPage * this.perPage));
},
rows() {
return this.showJobs.length;
},
},
mounted() {
this.getJobs();
var self = this;
window.addEventListener("keyup", function (event) {
if (event.keyCode === 13) {
self.searchJob();
}
});
},
methods: {
async getJobs() {
await axios
.get(`http://localhost:1337/jobinfos`)
.then((response) => {
this.jobinfos = response.data;
this.showJobs = response.data;
})
.catch((e) => {});
},
searchJob() {
if (this.selected == "All") {
this.showJobs = this.jobinfos;
}
if (this.selected != "All") {
this.showJobs = this.jobinfos.filter((i) => i.genres === this.selected);
}
if (this.search.length > 1) {
let searchedJobs = this.showJobs.filter((job) =>
job.position.toLowerCase().includes(this.search.toLowerCase())
);
this.showJobs = searchedJobs;
}
},
selectFilter(item) {
this.selected = item;
if (this.selected == "All") {
this.showJobs = this.jobinfos;
} else {
this.showJobs = this.jobinfos.filter((i) => i.genres === this.selected);
}
},
},
};
</script>
To be honest i have never done pagination work in VueJS so hope to get some help from everyone. Thank you very much
The behaviour of the b-pagination component is very simple - it takes a number of records (rows) and the number of records that should be displayed (perPage) and creates a component with an appropriate number of pages. The currentPage variable tells you on which page you should be and which records should be displayed, but it is up to you to tell the upper component what should it render. I think that in your case the .slice() should work. This method returns a portion of an array, based on provided values that can be determined with the use of the currentPage and perPage variables. I prepared code snipped to demonstrate that - I do not have your CSS so it might look a little bit different but it should be clearly visible how it works.
new Vue({
el: '#jobs',
data() {
return {
currentPage: 1,
perPage: 2,
showJobs: [{
position: 'position1',
exprerience: 'exprerience1',
salary: 'salary1',
headequarters: 'headequarters1'
},
{
position: 'position2',
exprerience: 'exprerience2',
salary: 'salary2',
headequarters: 'headequarters2'
},
{
position: 'position3',
exprerience: 'exprerience3',
salary: 'salary3',
headequarters: 'headequarters3'
},
{
position: 'position4',
exprerience: 'exprerience4',
salary: 'salary',
headequarters: 'headequarters4'
}
],
};
},
computed: {
jobs() {
return this.showJobs.slice((this.currentPage - 1) * this.perPage, (this.currentPage * this.perPage));
},
rows() {
return this.showJobs.length;
}
}
});
<link type="text/css" rel="stylesheet" href="https://unpkg.com/bootstrap/dist/css/bootstrap.min.css" />
<link type="text/css" rel="stylesheet" href="https://unpkg.com/bootstrap-vue#latest/dist/bootstrap-vue.css" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script src="https://unpkg.com/bootstrap-vue#latest/dist/bootstrap-vue.js"></script>
<div id="jobs">
<div class="job-item" v-for="(item, index) in jobs" :key="index">
<a tag="a" :to="{ name: 'Detail', params: { id: item.id } }">
<h3 class="mleft-27">{{ item.position }}</h3>
</a>
<div class="job-info flex-wrap">
<div>
<b>{{ item.exprerience }}</b>
</div>
<div>
<b>{{ item.salary }}</b>
</div>
<div>
<b>{{ item.headequarters }}</b>
</div>
</div>
<div class="info-job flex-wrap">
<div class="list-info-job" v-html="item.content"></div>
<a :to="{ name: 'Detail', params: { id: item.id } }">
<button class="btn-detail">See Detail</button>
</a>
</div>
</div>
<div class="job-page">
<b-pagination v-model="currentPage" :total-rows="rows" :per-page="perPage" aria-controls="jobs" />
</div>
</div>
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 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
}
}
Creating a filter to sort through quotes data, but having problems with creating the correct functions.
HTML:
<template>
<div class="home">
<h1>{{ message }}</h1>
<h3> Theme filter: </h3>
<div>
<button v-on:click="userFilterKey = 'movies'"> Search movies</button>
<button v-on:click="userFilterKey= 'all'"> Search all</button>
<button v-on:click="userFilterKey= 'games'"> Search games </button>
<div v-for="quote in quotes" v-bind:key="quote.id">
<p>Souce: {{ quote.source }} </p>
<p>Quote: {{ quote.quote }} </p>
<p>Theme: {{ quote.theme }} </p>
</div>
</div>
</div>
</template>
Script:
import axios from "axios";
export default {
data: function () {
return {
message: "Welcome to Vue.js!",
quotes: [],
userFilterKey: "all",
};
},
methods: {
userFilter: function () {
return this[this.userFilterKey];
},
all: function () {
return this.quotes;
},
movies: function () {
return this.quotes.filter((theme) => (quotes.theme = "movies"));
},
books: function () {
return this.quotes.filter((theme) => (quotes.theme = "books"));
},
quotesIndex: function () {
axios
.get("linktodata")
.then((response) => {
this.quotes = response.data;
});
},
How do I create filters to sort through the theme key of the quotes array within my link?
Use a computed prop.
new Vue({
el: '#app',
data: () => ({
quotes: [{
id: 1,
source: 'a',
quote: '',
theme: ''
},
{
id: 2,
source: 'b',
quote: '',
theme: 'movies'
},
{
id: 3,
source: 'c',
quote: '',
theme: 'games'
}
],
userFilterKey: "all"
}),
computed: {
filteredQuotes() {
if (this.userFilterKey === 'all') {
return this.quotes
}
return this.quotes.filter(v => v.theme === this.userFilterKey)
}
}
})
<div id="app">
<button #click="userFilterKey = 'all'"> Search all </button>
<button #click="userFilterKey = 'movies'"> Search movies</button>
<button #click="userFilterKey = 'games'"> Search games </button>
<div v-for="quote in filteredQuotes" :key="quote.id">
<p>Souce: {{ quote.source }} </p>
<p>Quote: {{ quote.quote }} </p>
<p>Theme: {{ quote.theme }} </p>
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.14/vue.min.js"></script>
Try using Pipes. Its something that does the filter in client side and is faster. may be that solves your problem
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