Passing data to one of many components - javascript

I am trying to create a DownloadButton component in VueJS that animates when clicked, and stops animating upon completion of the download. The DownloadButton component will be used in a table where its repeated many times. I want the download method to be contained in the parent. The problem is that changing the loading variable causes all the components to be affected rather than just the one being clicked.
Parent:
<DownloadButton #click.native="download" :loading="loading"></DownloadButton>
<DownloadButton #click.native="download" :loading="loading"></DownloadButton>
<DownloadButton #click.native="download" :loading="loading"></DownloadButton>
methods: {
download() {
this.loading = true
// wait for the download procedure to finish...
this.loading = false
}
}

You should monitor loading state of each button not just global loading.
Here is quick and simple example of what you want I think:
Vue.component("download-button", {
template: "#dbTemplate",
props: ['loading'],
computed: {
stateText() {
return this.loading ? 'Loading...' : 'Load';
}
}
});
new Vue({
el: "#app",
data: {
resources: [
{ date: new Date(), url: "some-url1" },
{ date: new Date(), url: "some-url2" },
{ date: new Date(), url: "some-url3" },
{ date: new Date(), url: "some-url4" }
],
resourceStates: {}
},
methods: {
downloadResource(resource) {
this.$set(this.resourceStates, resource.url, true);
new Promise((resolve, reject) => {
setTimeout(() => resolve(new Date()), 1000);
}).then((date) => {
resource.date = date;
this.$set(this.resourceStates, resource.url, false);
})
},
isLoading(resource) {
return !!this.resourceStates[resource.url];
}
}
});
<script src="https://unpkg.com/vue#2.5.16/dist/vue.js"></script>
<div id="app">
<div v-for="res in resources" :key="res.url" style="padding: 10px 0">
{{ res.date.toLocaleString() }}
<download-button #click.native="downloadResource(res)" :loading="isLoading(res)">
</download-button>
</div>
</div>
<script type="text/template-x" id="dbTemplate">
<button :disabled="loading">
{{ stateText }}
</button>
</script>

Related

Vue screen that refreshes periodically, done safely

I have a page in Vue/Nuxt that needs to refresh a list of items every few seconds. This is an SPA that does an Axios fetch to a server to get updated information. At the moment, I have something like this:
methods: {
doRefresh() {
setTimeout(function() {
// trigger server fetch here
doRefresh();
}, 5000);
}
}
It works, unless the other code in doRefresh throws an error, in which case the refreshing stops, or somehow the code gets called twice, and I get two timers going at the same time.
An alternative is call setInterval() only once. The trouble with that is that it keeps going even after I leave the page. I could store the reference returned by the setInterval(), and then stop it in a destroyed() hook. But again, an error might prevent that from happening.
Is there a safe and reliable way to run a timer on a Vue page, and destroy it when the user leaves the page?
This approach together with try-catch is a way to go, have a look at this snippet:
https://codepen.io/alexbrohshtut/pen/YzXjNeB
<div id="app">
<wrapper/>
</div>
Vue.component("interval-component", {
template: `
<div> {{lastRefreshed}}
<button #click="init">Start</button></div>`,
data() {
return {
timeoutId: undefined,
lastRefreshed: undefined
};
},
methods: {
doJob() {
if (Math.random() > 0.9) throw new Error();
this.lastRefreshed = new Date();
console.log("Job done");
},
init() {
if (this.timeoutId) return;
this.run();
},
run() {
console.log("cycle started");
const vm = this;
this.timeoutId = setTimeout(function() {
try {
vm.doJob();
} catch (e) {
console.log(e);
} finally {
vm.run();
}
}, 2000);
}
},
destroyed() {
clearTimeout(this.timeoutId);
console.log("Destroyed");
}
});
Vue.component("wrapper", {
template: `<div> <button #click="create" v-if="destroyed"> Create</button>
<button v-else #click="destroy">Destroy</button>
<interval-component v-if="!destroyed" /></div>`,
data() {
return {
destroyed: true
};
},
methods: {
destroy() {
this.destroyed = true;
},
create() {
this.destroyed = false;
}
}
});
new Vue({
el: "#app"
});

Vuejs 2: debounce not working on a watch option

When I debounce this function in VueJs it works fine if I provide the number of milliseconds as a primitive. However, if I provide it as a reference to a prop, it ignores it.
Here's the abbreviated version of the props:
props : {
debounce : {
type : Number,
default : 500
}
}
Here is the watch option that does NOT work:
watch : {
term : _.debounce(function () {
console.log('Debounced term: ' + this.term);
}, this.debounce)
}
Here is a watch option that DOES work:
watch : {
term : _.debounce(function () {
console.log('Debounced term: ' + this.term);
}, 500)
}
It suspect that it is a scope issue but I don't know how to fix it. If I replace the watch method as follows...:
watch : {
term : function () {
console.log(this.debounce);
}
}
... I get the correct debounce value (500) appearing in the console.
Another variation to #Bert's answer is to build the watcher's function in created(),
// SO: Vuejs 2: debounce not working on a watch option
console.clear()
Vue.component("debounce",{
props : {
debounce : {
type : Number,
default : 500
}
},
template:`
<div>
<input type="text" v-model="term">
</div>
`,
data(){
return {
term: "",
debounceFn: null
}
},
created() {
this.debounceFn = _.debounce( () => {
console.log('Debounced term: ' + this.term);
}, this.debounce)
},
watch : {
term : function () {
this.debounceFn();
}
},
})
new Vue({
el: "#app"
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.3/vue.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>
<div id="app">
<debounce :debounce="2000"></debounce>
</div>
Example on CodePen
The primary issue here is using this.debounce as the interval when defining your debounced function. At the time _.debounce(...) is run (when the component is being compiled) the function is not yet attached to the Vue, so this is not the Vue and this.debounce will be undefined. That being the case, you will need to define the watch after the component instance has been created. Vue gives you the ability to do that using $watch.
I would recommend you add it in the created lifecycle handler.
created(){
this.unwatch = this.$watch('term', _.debounce((newVal) => {
console.log('Debounced term: ' + this.term);
}, this.debounce))
},
beforeDestroy(){
this.unwatch()
}
Note above that the code also calls unwatch which before the component is destroyed. This is typically handled for you by Vue, but because the code is adding the watch manually, the code also needs to manage removing the watch. Of course, you will need to add unwatch as a data property.
Here is a working example.
console.clear()
Vue.component("debounce",{
props : {
debounce : {
type : Number,
default : 500
}
},
template:`
<input type="text" v-model="term">
`,
data(){
return {
unwatch: null,
term: ""
}
},
created(){
this.unwatch = this.$watch('term', _.debounce((newVal) => {
console.log('Debounced term: ' + this.term);
}, this.debounce))
},
beforeDestroy(){
this.unwatch()
}
})
new Vue({
el: "#app"
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>
<script src="https://unpkg.com/vue#2.4.2"></script>
<div id="app">
<debounce :debounce="250"></debounce>
</div>
The debounced method needs to be abstracted since we need to call the same function everytime the watch is triggered. If we place the debounced method inside a Vue computed or watch property, it will be renewed everytime.
const debouncedGetData = _.debounce(getData, 1000);
function getData(val){
this.newFoo = val;
}
new Vue({
el: "#app",
template: `
<div>
<input v-model="foo" placeholder="Type something..." />
<pre>{{ newFoo }}</pre>
</div>
`,
data(){
return {
foo: '',
newFoo: ''
}
},
watch:{
foo(val, prevVal){
debouncedGetData.call(this, val);
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.9.1/underscore-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="app"></div>
Good Luck...
new Vue({
el: '#term',
data: function() {
return {
term: 'Term',
debounce: 1000
}
},
watch: {
term : _.debounce(function () {
console.log('Debounced term: ' + this.term);
}, this.debounce)
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.3/vue.js"></script>
<div id="term">
<input v-model="term">
</div>

Vue.js uncaught reference error

Learning Vue and stuck. I have a brand new Laravel 5.4 project that I am using to learn Vue concepts, using Pusher/Echo. All is working in terms of message broadcasting, and the messages are fetched from the server and displayed on page load as expected. I want to programatically (from somewhere else in the project) send a message into the queue.
I am using this example as guide to accessing the Vue method outside the instance.
Why can I not access the instance method from my main JS file? The project is compiled with webpack FYI.
My Vue.js file:
$(document).ready(function()
{
Vue.component('chat-system', require('../components/chat-system.vue'));
var chatSystem = new Vue({
el: '#system-chat',
data: {
sysmessages: []
},
created() {
this.fetchMessages();
Echo.private(sys_channel)
.listen('SystemMessageSent', (e) => {
this.sysmessages.unshift({
sysmessage: e.message.message,
player: e.player
});
});
},
methods: {
fetchMessages() {
axios.get(sys_get_route)
.then(response => {
this.sysmessages = response.data;
});
},
addMessage(sysmessage) {
this.sysmessages.unshift(sysmessage);
this.$nextTick(() => {
this.$refs.sysmessages.scrollToTop();
});
axios.post(sys_send_route, sysmessage)
.then(response => {
console.log(response.data);
});
},
sendMessage(sysmessage) {
if (sysmessage !== '') {
this.$emit('systemmessagesent', {
player: this.player,
message: sysmessage
});
}
}
}
});
});
My Vue.js component:
<template>
<div id="round-status-message" class="round-status-message">
<div class="row">
<div class="col-xs-12" v-for="sysmessage in sysmessages">
{{ sysmessage.message }}
</div>
</div>
</div>
</template>
<script>
export default {
props: ['player', 'sysmessages'],
data() {
return {
newSysMessage: ''
}
},
methods: {
scrollToTop () {
this.$el.scrollTop = 0
},
sendMessage() {
this.$emit('systemmessagesent', {
player: this.player,
message: this.newSysMessage
});
this.newSysMessage = ''
}
}
};
</script>
I want to send a message into the queue programatically, so in my app.js, to test, I do:
// TESTING SYSTEM MESSAGES - DELETE
window.setInterval(function(){
var resp = {};
resp.data = {
id: 1,
message: "She hastily put down yet, before the end of half.",
progress_id: 1,
created_at: "2017-08-17 14:01:11",
updated_at: "2017-08-17 14:01:11"
};
chatSystem.$refs.sysmessages.sendMessage(resp);
console.log(resp);
}, 3000);
// TESTING SYSTEM MESSAGES - DELETE
But I get Uncaught ReferenceError: chatSystem is not defined
All I needed was to make the method name available to the global scope?
global.chatSystem = chatSystem; // App variable globally
This seems to work now...

Vue.js with laravel 5.3

I'm using Laravel 5.3 with Vue.js(very new to this).
Here's my current code
app.js
var vm = new Vue({
el: '#app',
data: {
messages: []
},
ready: function(){
this.getMessages();
},
methods: {
getMessages: function(){
this.$http.get('api/messages').then((response) => {
this.$set('messages', data);
}, (response) => {
});
}
}
});
api.php route is very simple
Route::get('/messages', function() {
return Message::latest()->get();
});
Note: here when i try access the route directly as localhost:8000/api/messages i get the array with the full data
On my view i have
<div class="content" id="app">
<tr v-for="message in messages">
<td> #{{ message}} </td>
</tr>
</div>
I have included online libraries for all jquery, vue.js, and vue.resource.
When i use vue.js debugger it shows that it returns messages[] but it's empty.
I have followed a lot of examples but couldn't get it to work.
Any help is greatly appreciated
if you are using vue.js 2.0 , ready is deprecated now, you may use mounted instead
mounted: function () {
this.$nextTick(function () {
this.getMessages();
})
}
Vue.js Docs
Since you are using the arrow syntax, then I switched to full ES2015 Code
getMessages() {
this.$http.get('api/messages')
.then( result => {
this.messages = result.json()
})
}
Try this:
var vm = new Vue({
el: '#app',
data: {
messages: []
},
ready: function(){
this.getMessages();
},
methods: {
getMessages: function(){
let ctrl = this;
this.$http.get('api/messages').then((response) => {
this.messages = response.data;
});
}
}
});

VueJS Resource reload content

Resource file helper/json.json
{
"content": {
"content_body": "<a href='#' v-on:click.prevent='getLink'>{{ button }}</a>",
"content_nav": "",
}
}
Vue main.js file
new Vue({
el: 'body',
data: {
text: 'Lorem sss',
},
methods: {
getLink: function(){
this.$http.get('http://localhost/vuejs/helper/json.json').then((resp) => {
this.$set('text', resp.data.content.content_body);
}, (resp) => {
console.log('error');
})
}
}
})
Output: Not Renderer
{{ button }}
Event does not work when the button is clicked. Data can not be loaded.
Vue.resourse have no relation to this problem
becouse html string from json isn't compiled.
Here a little test based on your example:
<body>
<div v-el:sample></div>
</body>
var test = new Vue({
el: 'body',
data: {
button: 'Lorem sss',
},
methods: {
getLink: function(){
var r = Math.floor(Math.random() * (4 - 1)) + 1;
this.$set('button', ['','btn1','btn2','btn3'][r] );
},
getCompiled: function() {
$(this.$els.sample).empty()
var element = $(this.$els.sample).append("<a href='#' v-on:click.prevent='getLink'>{{ button }}</a>");
this.$compile(element.get(0));
$(this.$els.sample).prepend('<p>Compiled button:</p>')
}
},
ready: function() {
this.getCompiled();
}
})
jsfidle

Categories