Computed array not available in template - vue.js

I want my array holding records between two given dates to always reflect the chosen from-to dates, but simply returning the response.data from Axios in the computed method doesn't seem to do the trick. How can I get the computed method to serve the results to the app?
<template>
<div>
<p v-for="event in events">{{event.title}}</p>
</div>
</template>
<script>
export default {
name: "App",
data () {
return {}
},
computed: {
fromDate: function() { ..code.. },
toDate: function() { ..code.. },
events: function() {
axios.get('json/' + this.fromDate + '/' + this.toDate).then(response => {
return response.data;
});
}
}
}
</script>

events: function() {
axios.get('json/' + this.fromDate + '/' + this.toDate)
.then(response => {
return response.data;
});
}
First of all, you weren't really returning anything inside that function. And in order to achieve that, you can use the plugin called vue-async-computed instead
// ...
import AsyncComputed from 'vue-async-computed'
// ...
Vue.use(AsyncComputed)
// ...
asyncComputed: {
events: function() {
return axios.get('json/' + this.fromDate + '/' + this.toDate)
.then(response => {
return response.data;
});
}
}
Using vue-async-computed along with ES7 also works:
asyncComputed: {
async events() {
const response = await axios.get('json/' + this.fromDate + '/' + this.toDate)
return response.data
}
}

Related

Update image src on the fly with VueJS

I'm a new VueJS user, currently struggling with updating image src on the fly. This is what I've got:
Template:
<div v-for="place in places">
<img
v-bind:src="isPlacePrivate(place.data.place_is_private)"
v-on:click="setPlaceAsPrivate(place.data.place_is_private, place.data.place_id)"
>
</div>
<script>
export default {
data: function () {
return {
places: null,
}
},
mounted () {
this.username = this.$route.params.username;
axios.get('/api/' + this.username + '/places')
.then(response => {
this.places = response.data.data;
})
.catch(error => {
// show error
});
},
methods: {
isPlacePrivate: function (value) {
// If Place is private
if (value == 1) {
var src = '/icons/padlock-color.png'
} else {
var src = '/icons/padlock.png'
}
return src;
},
setPlaceAsPrivate: function (value, placeId) {
let data = {
isPrivate: value
};
axios.put('/api/' + this.username + '/places/' + placeId + '/edit', data)
.then(response => {
let newValue = response.data.data.private;
this.isPlacePrivate(newValue);
})
.catch(error => {
// show error
});
},
},
}
</script>
On a page load -> if a particular place is private it will show colored padlock icon or uncolored padlock if a place is public!
A user will be able to press on the padlock icon and change the value from public->private or private->public.
Everything is working fine but the padlock image is not updating on the fly when a user is clicking on it, I need to refresh a page to see changes! How to make it work?
I would suggest using a computed property so that it is reactive
Also according to your updates you are looping through an array of places so when you get your response from your axios call instead of just updating the icon I would try replacing the object in the array so I created the method called updatePlace() and I pass in the response object.
And change your places in the v-for to a computed property as well so that it is also reactive
Template:
<div v-for="place in placesArray" :key="index" v-if="places">
<img
v-bind:src="imgSrc"
v-on:click="setPlaceAsPrivate(place.data.place_is_private, place.data.place_id)"
v-if="imgSrc"
>
</div>
Script:
<script>
export default {
data() {
return {
src: '',
places: null
}
},
computed: {
imgSrc() {
return this.src
},
placesArray() {
return this.places
}
},
Methods: {
isPlacePrivate: function (value) {
// If Place is private
if (value == 1) {
this.src = '/icons/padlock-color.png'
} else {
this.src = '/icons/padlock.png'
}
},
setPlaceAsPrivate: function (value, placeId) {
let data = {
isPrivate: value
};
axios.put('/api/' + this.username + '/places/' + placeId + '/edit', data)
.then(response => {
console.log(response);
let newValue = response.data.data;
this.updatePlace(newValue);
})
.catch(error => {
console.log(error);
});
},
},
updatePlace(newPlace) {
const index = this.places.findIndex(place => place.id === newPlace.id)
this.places.splice(index, 1, place)
},
created() {
this.username = this.$route.params.username;
axios.get('/api/' + this.username + '/places')
.then(response => {
this.places = response.data.data;
})
.catch(error => {
// show error
});
}
}
</script>
Also make sure to move your mounted method to a created() method so that it is called before anything else is trying to render.
Apparently the problem is that you are calling the function and printing its return on the <img v-bind:src>, the isPlacePrivate function returns a value, so when you use this function within the setPlaceAsPrivate it returns the value only in scope of setPlaceAsPrivate.
The isPlacePrivate function does not modify any data value of the component, so the image always remains the same. You just need to set a data and manipulate its value in the isPlacePrivate function.
Template
<img
v-bind:src="bindSrc"
v-on:click="setPlaceAsPrivate(place.data.place_is_private, place.data.place_id)"
>
Script
<script>
export default {
data() {
return {
bindSrc: '/icons/padlock-color.png', // default img src value
... // your other values
}
},
Methods: {
isPlacePrivate: function (value) {
// If Place is private
if (value == 1) {
this.bindSrc = '/icons/padlock-color.png'
} else {
this.bindSrc = '/icons/padlock.png'
}
},
setPlaceAsPrivate: function (value, placeId) {
let data = {
isPrivate: value
};
axios.put('/api/' + this.username + '/places/' + placeId + '/edit', data)
.then(response => {
console.log(response);
let newValue = response.data.data.private;
this.isPlacePrivate(newValue);
})
.catch(error => {
console.log(error);
});
},
}
}
</script>

Vue.js - trying to write a computed to return a substring of my data

I am just learning Vue.js. I followed a Youtube tutorial on creating a iTunes album search. I got it all working great. Now I was just trying to add a single property to each album result to try and learn it better. Each album data that comes in has a copyright variable in the json.
So here is my code that is working without errors:
<template>
<div>
<h1>Results for {{$route.params.id}}</h1>
<div v-if="albumExists">
<div v-for="(album, index) in albumData" :key="album.id">
<Card
:title="album.collectionCensoredName"
:image="album.artworkUrl100"
:artistName="album.artistName"
:year="copyrightYear(album.copyright)"
:url="album.artistViewUrl"
:color="picker(index)"
/>
</div>
</div>
<div v-else>
<h1>Could Not Find Artist</h1>
</div>
</div>
</template>
<script>
import axios from 'axios';
import Card from '~/components/Card.vue';
export default {
asyncData({params}) {
return axios.get(`https://itunes.apple.com/search?term=${params.id}&entity=album`)
.then((response) => {
return {albumData: response.data.results}
});
},
components: {
Card
},
middleware: 'search',
methods: {
picker(index) {
return index % 2 == 0 ? 'red' : 'blue';
},
copyrightYear(copyright) {
return '(' + copyright.slice(2, 6) + ')';
}
},
computed: {
albumExists() {
return this.albumData.length > 0;
},
// copyrightYear() {
// return '(' + this.albumData.copyright.slice(2, 6) + ')';
// }
}
}
</script>
Now I would like to get the copyright from a computed as that's how I think it should be. Maybe I'm wrong? But I would like to just do :year="copyrightYear" and then use the commented code in the computed section. But when I try that, I get this.albumData.copyright is undefined. Any ideas how I can do this with a computed? Or do I have to use a method for this example?
UPDATE
Here is my updated < script > section:
<script>
import axios from 'axios';
import Card from '~/components/Card.vue';
export default {
asyncData({params}) {
return axios.get(`https://itunes.apple.com/search?term=${params.id}&entity=album`)
.then((response) => {
return {albumData: response.data.results}
});
},
components: {
Card
},
data() {
return {
albumData:null
}
},
middleware: 'search',
methods: {
picker(index) {
return index % 2 == 0 ? 'red' : 'blue';
},
// copyrightYear(copyright) {
// return '(' + copyright.slice(2, 6) + ')';
// }
},
computed: {
albumExists() {
return (this.albumData && this.albumData.length > 0);
},
copyrightYear() {
return '(' + this.albumData.copyright.slice(2, 6) + ')';
}
}
}
</script>
You are expecting an array so best practice is
data() {
return {
albumData: []
}
},
For one way binding (only for display) just customize object property and then bind.
asyncData({params}) {
return axios.get(`https://itunes.apple.com/search?term=${params.id}&entity=album`)
.then((response) => {
if(response.data.results && response.data.results.length >0){
return {
albumData: response.data.results.map(x => {
return {
artworkUrl100: x.artworkUrl100,
artistName: x.artistName,
copyrightYear: x.copyright.slice(2, 6),
artistViewUrl: x.artistViewUrl
}
})
}
}
});
},
albumData needs to be defined in your component's data, props, or another computed to be reactive.
So when you async resolves, assign the result to this.albumData, but define this.albumData in your data from the start, even if undefined or null.
data() {
return {albumData:null}
},
Also, albumExists() should return false if this.albumData is null or undefined. That will be called before the async returns.
Instead of using a computed function, call your method that you commented out. It should work after your fix albumExists() to return false if albumData is null

Vue-Router: Cannot read property '$route' of undefined - VueJS

Please review my code.
<template>
<div class="row flex">
<div class="col-md-6 home_feed">
<post-detail :posts="posts"></post-detail>
</div>
</div>
</template>
<script>
import axios from 'axios'
export default {
async asyncData (params) {
let { data } = await axios.get('http://localhost:8000/api/v1/users/' + this.$route.params.id + '/')
return {
posts: data
}
},
components: {
'post-detail': Item
}
}
</script>
I got this error: Cannot read property '$route' of undefined when I asyncdata from params.id, but when I type: console.log(this.$route.params.id), it goes right. How can I fix this
if you want to load data from server (from browser) in mounted lifecycle try this:
export default {
data() {
return {
data: {}
}
},
mounted() {
this.asyncData();
},
methods: {
async asyncData ({ route }) {
let { data} = await axios.get('http://localhost:8000/api/v1/users/' + this.$route.params.id + '/')
this.data = data
}
}
}
Response from server will be available in response variable.
For SSR you can do :
async asyncData ({ store, route }) {
let { data} = await axios.get('localhost:8000/api/v1/users/'; + route.params.id + '/')
return {
posts: data
}
}
asyncData will be called before the components are instantiated, and it doesn't have access to this. (see https://ssr.vuejs.org/en/data.html Logic Collocation with Components for details)
For SSR you can change
<script>
async asyncData ({ store, route }) {
let { data} = await axios.get('localhost:8000/api/v1/users/' + this.$route.params.id + '/')
return {
posts: data
}
}
</script>
to
<script>
async asyncData ({ route }) {
let { data} = await axios.get('localhost:8000/api/v1/users/' + route.params.id + '/')
return {
posts: data
}
}
</script>
According to the nuxt tutorial you can not have access to this inside asyncData because it is called before initiating the component. Nuxt Documentation
#Mikhail
This code is success:
export default {
data() {
return {
data: {}
}
},
mounted() {
this.asyncData();
},
methods: {
async asyncData ({ route }) {
let { data} = await axios.get('http://localhost:8000/api/v1/users/' + route.params.id + '/')
this.data = data
}
}
}
But when get API parent-children data like this: {{data.user.username}}, data.user goes undefined. So API data goes error.
I use Nuxt and your code for SSR not work:
Error: Cannot read property $route of undefined
<script>
async asyncData ({ store, route }) {
let { data} = await axios.get('localhost:8000/api/v1/users/'; + this.$route.params.id + '/')
return {
posts: data
}
}
</script>

this.$http.get is not working inside methods vue js

I am working with Laravel + spark + vue js.
Blade file
<draggable class="col-md-12" :list="emergencies" :element="draggableOuterContainer" #end="onEnd">
Js file
import draggable from 'vuedraggable'
module.exports = {
data() {
return {
emergencies:[]
};
},
components: {
draggable,
},
created() {
this.getEmergencies();
},
methods: {
getEmergencies() {
this.$http.get('/ajax-call-url')
.then(response => {
this.emergencies = response.data;
});
},
onEnd: function(evt){
var counter = 1;
this.emergencies.forEach(function(user, index) {
this.$http.get('/ajax-call-url/')
.then(response => {
});
counter++;
});
}
}
};
Here I have drag and Drop, On Drop, I call "onEnd" function and getting following error.
TypeError: this is undefined
Here this.emergencies.forEach is working but it is giving error on this.$http.get
Any suggestions, what can be the solutions?
Instead of using function syntax, use arrow functions, as scope of this changes inside function:
onEnd: function(evt){
var counter = 1;
this.emergencies.forEach((user, index) => {
this.$http.get('/ajax-call-url/')
.then(response => {
});
counter++;
});
}
Check this for explanation.

VueJS: Setting data initially based on http response

So I have a template .vue file:
<template>
<div id="app">
<textarea v-model="input" :value="input" #input="update"></textarea>
<div v-html="compiledMarkdown"></div>
</div>
</template>
<script>
var markdown = require('markdown').markdown;
export default {
name: 'app',
data() {
return {
input: '# Some default data'
}
},
mounted: function () {
this.$nextTick(function () {
this.$http.get(window.location.pathname + '/data').then((response) => {
this.input = response.body.markdown;
}) })
},
computed: {
compiledMarkdown: function() {
this.$http.post(window.location.pathname, {
"html": markdown.toHTML(this.input)}).then(function() {
},function() {
});
return markdown.toHTML(this.input);
}
},
methods: {
update: function(e) {
this.input = e.target.value
}
}
}
</script>
In the mounted function I am trying to set input equal to the response of an HTTP request, but when you view this file this.input is still the same as it was initially declared. How can I change this.input inside the compiledMarkdown function to be this.input in the mounted function. What other approaches might I take?
You can not call a async method from a computed property, you can use method or watcher to run asynchronous code, from docs
This is most useful when you want to perform asynchronous or expensive operations in response to changing data.
You have to ran that relevant code when input changes, like following:
var app = new Vue({
el: '#app',
data: {
input: '# Some default data',
markdown : ''
},
methods: {
fetchSchoolData: function (schoolId) {
var url = this.buildApiUrl('/api/school-detail?schoolId=' + schoolId);
this.$http.get(url).then(response => {
this.schoolsListData = response.data;
}).catch(function (error) {
console.log(error);
});
},
},
mounted: function () {
this.$nextTick(function () {
this.$http.get(window.location.pathname + '/data').then((response) => {
this.input = response.body.markdown;
})
})
},
watch: {
// whenever input changes, this function will run
input: function (newInput) {
this.$http.post(window.location.pathname, {
"html": markdown.toHTML(this.input)}).then(function() {
},function() {
this.markdown = markdown.toHTML(this.input);
});
}
},
Have a look at my similar answer here.