Im working on a website with vue where i have to get images with axios from a server and put them as background images of divs. The case is that i have the urls but not all of them are correct. So im trying to make a function that makes an http request and returns me the url if that request is successful.
I thought of something like this:
Template:
<div class="element" :style="bgImg(url)"/>
Script:
methods: {
bgImg(url) {
axios.get(url)
.then(response => {
return `background-image: url(${response.config.url})`;
})
.cath(e) {
throw new Error(e);
}
}
}
I was expecting to get the url returned from that function, but nothing happens.
You could use a custom vue directive for that job:
Vue.directive('validate-bgimg', function (el, binding) {
el.style.backgroundImage = null // reset
var url = binding.value
if (!url) {
return
}
axios.get(`https://cors-anywhere.herokuapp.com/${url}`) // get around CORS limitations. Idealy you should call your own api to validate image urls
.then(response => {
if (response.status != 200) {
console.warn("Invalide image url", url, response)
} else {
el.style.backgroundImage = `url(${url})`
}
})
.catch(e => {
console.error("Could not validate image", url, e)
})
})
new Vue({
el: "#app",
data: {
imgUrl: undefined
}
})
#app {
background: lightgray;
border-radius: 4px;
padding: 20px;
margin: 20px;
transition: all 0.2s;
}
.element {
margin: 8px 0;
width: 200px;
height: 200px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.18.0/axios.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<b>test case:</b>
<button #click="imgUrl='https://dummyimage.com/200x200/000/fff.gif&text=test+1'">1</button>
<button #click="imgUrl='https://dummyimage.com/200x200/000/fff.gif&text=test+2'">2</button>
<button #click="imgUrl='https://dummyimage.com/200x200/000/fff.gif&text=test+3'">3</button>
<button #click="imgUrl='https://httpbin.org/status/404'">HTTP 404</button>
<button #click="imgUrl='https://httpbin.org/status/503'">HTTP 503</button>
<button #click="imgUrl='https://invalid.tld'">cannot resolve domain name</button>
<div class="element" v-validate-bgimg="imgUrl"/>
</div>
You should use setAttribute method.
//add id to the element
<div id="element" class="element" />
var url = url(${response.config.url});
// select the element using id and set image
document.getElementById("element").setAttribute("style", "background-image:"+ url);
Rather than returning from a then() callback (which will pass the result of the block to the next then()), you can store the data that you are seeking from the response object for use outside of the promise.
Use a computed or temp value, that will make it easier.
In this solution, you would set a placeholder image to show while loading (or leave empty).
Then use a mounted() hook to load data, and then assign to the style
Template:
<div class="element" :style="imgStyle"/>
Script:
data:{
return () => {
imgStyle: {
'background-image': 'url(my-placeholder.png)' // or not included at all
}
}
},
mounted() {
axios.get(url)
.then(response => {
this.$set(this.imgStyle, 'background-image', `url(${response.config.url})`);
})
.cath(e) {
throw new Error(e);
}
}
As a side note, try to stay away from functions in templates for displaying values that can be done using a computed.
Related
I'm wondering if I am able to get a components data, the count property in this instance and console.log it into normal javascript, is this possible? I'm wanting to do console.log(btn.data.count) in this case
<div id="app" v-cloak>
<h1>{{greeting}}</h1>
<button-counter></button-counter>
</div>
<script src="https://unpkg.com/vue#next"></script>
<script>
let app = Vue.createApp({
data: function(){
return {
greeting: "hi"
}
}
})
let btn = app.component('button-counter', {
data: function () {
return {
count: 0
}
},
template: '<button v-on:click="count++">You clicked me {{ count }} times.</button>'
})
console.log(btn.data.count) // doesn't work
app.mount("#app")
</script>
There might be multiple instances of the button-counter component, so you cannot ask for the count.
You can only access the data from within the component itself. For example from within a method that handles the click event:
let btn = app.component('button-counter', {
data: function () {
return {
count: 0
}
},
methods: {
onClick() {
console.log(this.count)
this.count++
}
},
template: '<button v-on:click="onClick">You clicked me {{ count }} times.</button>'
})
You can use console.log normally you just need to use it where it makes sense. It does not make sense using it before #app is mounted.
Try writing a method for your count++ and add console log there you will see that it gets executed every time.
I know this has a simple answer but I appear to be stuck. I have an upload image input in a form. Following several tutorials, I have successfully created the upload method. My issue is once the image is uploaded to Firestore storage I use this.$emit('imgurl', downloadURL)
My problem is I do not know how to get that value so when the user submits the form the url gets added to the database.
Parts of the code:
HTML:
<div class="field avatar">
<label for="avatar">Avatar</label>
<input type="file" name="imgurl" accept="image/*" #change="detectFiles($event.target.files)">
<div class="progress-bar green" :style="{ width: progressUpload + '%'}">{{ progressUpload }}%</div>
<img class="avatar" v-bind:src="this.downloadURL">
</div>
Methods:
detectFiles (fileList) {
Array.from(Array(fileList.length).keys()).map( x => {
this.upload(fileList[x])
})
},
upload (file) {
var storage = firebase.storage();
this.uploadTask = storage.ref('avatars/'+file.name).put(file);
}
Watch:
watch: {
uploadTask: function() {
this.uploadTask.on('state_changed', sp => {
this.progressUpload = Math.floor(sp.bytesTransferred / sp.totalBytes * 100)
},
null,
() => {
this.uploadTask.snapshot.ref.getDownloadURL().then(downloadURL => {
this.downloadURL = downloadURL
this.$emit('imgurl', downloadURL)
})
})
}
}
Add to the database:
db.collection('teams').add({
team_name: this.team_name,
team_id: this.team_id,
bio: this.bio,
url: this.imgurl,
}).then(() => {
this.$router.push({ name: 'Admin' })
}).catch(err => {
console.log(err)
})
You can pass a function as a prop to a child component, then call this function passing your downloadURL as argument.
Parent Component
HTML
<child passURL="getDownloadURL">
JS
data: {
return {
downloadURL: null
}
},
methods: {
getDownloadURL: function(url) {
this.downloadURL = url
}
}
Child Component
JS
props: ['passURL'],
Inside your watcher, you can call
this.passURL(downloadURL)
Instead of $emit.
I found the answer. I added a hidden input field
<input type="hidden" name="imgurl" v-model="imgurl">
and replaced the emit with this.imgurl = downloadURL
I have used List.JS before successfully, but this time I'm trying to use it with a Vue.JS rendering of a list from JSON data.
I have a button at the top that when clicked should show only the QB position player.
Unfortunately I just get nothing, all list items are removed and I don't get an error in the console so I'm not sure how to diagnose this.
Could it have something to do with the fact that the list elements aren't prerendered/static html but injected using vue.js?
https://jsfiddle.net/nolaandy/hw2mheem/
HTML/Vue Template
<div id='app'>
<div class="all-players-wrapper" id="all-player-listings">
<button id="filter-qb">QB</button>
<ul class="list">
<li v-for="player in playerJSON">
<div class="player-listing">
<div class="player-left">
<div class="player-name">{{player.firstName}} {{player.lastName}}</div>
<div class="playerPosition">{{ player.Position }}</div>
</div><!-- end player-left -->
<div class="player-right">
<div class="player-grade">GRADE <span>{{player.NFLGrade}}</span></div>
</div> <!--end player-right -->
</div>
</li>
</ul>
</div>
</div>
JS
var vm = new Vue({
el: '#app',
data: {
status: 'Combine Particpants',
playerJSON: []
},
created: function () {
this.loadData();
},
methods: {
loadData: function () {
var self = this;
axios.get('https://s3-us-west-2.amazonaws.com/s.cdpn.io/500458/tiny.json').then(function (response) {
self.playerJSON = response.data
console.log(response.data);
})
.catch(function (error) {
self.status = 'An error occurred - ' + error
});
}
}
});
var options = {
valueNames: [ 'playerPosition' ]
};
var featureList = new List('all-player-listings', options);
$('#filter-qb').click(function() {
featureList.filter(function(item) {
if (item.values().playerPosition == "QB") {
return true;
} else {
return false;
}
});
return false;
});
As you suspected, List.js isn't going to work properly if the DOM changes unpredictably. In this case, axios makes its call and populates the data after the (empty) List has been read into featureList.
Your example would work if you put the list-selecting-and-filtering code in the resolution of the axios call, but that's not going to be a solution that works in a truly dynamic environment.
A custom directive will be called every time the DOM updates, so you can apply your adjustments consistently. Here's a directive to apply a filter using List.js:
directives: {
filteredList(el, binding) {
if (binding.value) {
const options = {
valueNames: ['playerPosition']
};
const featureList = new List(el, options);
featureList.filter((item) => item.values().playerPosition === binding.value);
}
}
}
Apply it like so:
<div class="all-players-wrapper" v-filtered-list="filterValue">
Add the filterValue data item, and have the button set it:
<button id="filter-qb" #click="() => filterValue='QB'">QB</button>
and you're in business.
It's worth noting that you could get the same effect by using a computed to filter the data, and you wouldn't need an external library.
Updated fiddle
I am learning Vuejs and I am stuck. Why can I see the messages get added to the object (in Chrome Vue debugger) yet it is not added to the div that contains the list?
My Vue Component:
<template>
<div id="round-status-message" class="round-status-message">
<div class="row">
<div class="col-xs-12" v-for="sysmessage in sysmessages" v-html="sysmessage.message"></div>
</div>
</div>
</template>
<script>
export default {
props: ['sysmessages'],
methods: {
scrollToTop () {
this.$el.scrollTop = 0
}
}
};
</script>
My Vue instance:
$(document).ready(function()
{
Vue.component('chat-system', require('../components/chat-system.vue'));
var chatSystem = new Vue({
el: '#system-chat',
data: function () {
return {
sysmessages: []
};
},
created() {
this.fetchMessages();
Echo.private(sys_channel)
.listen('SystemMessageSent', (e) => {
this.sysmessages.unshift({
sysmessage: e.message.message,
});
this.processMessage(e);
});
},
methods: {
fetchMessages() {
axios.get(sys_get_route)
.then(response => {
this.sysmessages = response.data;
});
},
processMessage(message) {
this.$nextTick(() => {
this.$refs.sysmessages.scrollToTop();
});
// updateGame();
}
}
});
});
My template call in HTML:
<div id="system-chat">
<chat-system ref="sysmessages" v-on:systemmessagesent="processMessage" :sysmessages="sysmessages" :player="{{ Auth::user() }}"></chat-system>
</div>
There are no compile or run time errors and I can see records added to the props in the vue chrome tool. I can also see empty HTML elements added to the div.
What have I missed?
UPDATE: My record structures:
response.data is an array of objects, each like this:
{"data":[
{"id":100,
"progress_id":5,
"message":"start message",
"action":"welcome"
},
{"id"....
e.message.message contains the text message entry, so just a string.
I am trying to access the message variable in each object during the fetchMessages method.
You're adding objects with sysmessage as the property.
this.sysmessages.unshift({
sysmessage: e.message.message,
});
But you are trying to view
v-for="sysmessage in sysmessages" v-html="sysmessage.message"
Based on your update, the code should be:
this.sysmessages.unshift({
message: e.message.message,
});
And you can leave the template as
v-html="sysmessage.message"
Template html
<div class="item" v-for="n, index in teamRoster">
<span> {{ getFantasyScore(n.personId) }} </span>
</div>
Method
getFantasyScore(playerId) {
if(playerId) {
axios.get(config.NBLAPI + config.API.PLAYERFANTASYSCORE + playerId)
.then( (response) => {
if( response.status == 200 ) {
console.log(response.data.total)
return response.data.total;
}
});
}
}
I'm trying to display the returned data to DOM but it doesnt display anything. But when I try to console log the data is displays. How can I be able to display it. What am I missing?
Problem is, your getFantasyScore method doesn't return anything and even then, the data is asynchronous and not reactive.
I would create a component that loads the data on creation. Something like
Vue.component('fantasy-score', {
template: '<span>{{score}}</span>',
props: ['playerId'],
data () {
return { score: null }
},
created () {
axios.get(config.NBLAPI + config.API.PLAYERFANTASYSCORE + this.playerId)
.then(response => {
this.score = response.data.total
})
}
})
and then in your template
<div class="item" v-for="n, index in teamRoster">
<fantasy-score :player-id="n.personId"></fantasy-score>
</div>
You shouldn't use methods for AJAX results because they are async. You could retrieve the full teamRoster object and then add this to your div:
<div class="item" v-for="fantasyScore in teamRoster" v-if="teamRoster">
<span> {{ fantasyScore }} </span>
</div>