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...
Related
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"
});
I'm building a native hybrid app using Nuxt JS 2.9.1 and Vuetify JS. I have a notification component that is loaded into my default.vue layout just after the <nuxt /> tag, this component is loaded into every page and triggers notifications using a Cordova plugin.
On each page, I'm making a HTTP GET request to a server, but for development it's a local JSON file, I'm using Axios to do this, however, I need to access an individual object from an array of many objects via it's index on each page to send to the notification component.
Since I'm unable to access each individual object index within my methods, I'm creating a hidden <div> containing a reference to each object based on the URL that a user is on, and am using a ref to access this in my methods.
However, I get the following error, despite it working correctly:
Cannot read property '$refs' of undefined and Cannot read property 'innerText' of undefined
These errors seem to be inaccurate as it still appears to function.
<template>
<div>
<div v-for="(url, index) in dataUrls" :key="url.id">
<div ref="getId">{{ index }}</div>
<div ref="getUrl">{{ dataUrls[index].url }}</div>
<div ref="getName">{{ dataUrls[index].name }}</div>
<div ref="fetch">{{ dataUrls[index].fetchInterval }}</div>
<div ref="muteNotifications">{{ dataUrls[index].muteNotifications }}</div>
<div ref="notificationIcon">{{ dataUrls[index].notificationIcon }}</div>
<div ref="notificationID">{{ dataUrls[index].notificationID }}</div>
</div>
<div v-for="(interval, index) in intervalData" :key="interval.id">
<div ref="storedInterval">{{ intervalData[index].storedInterval }}</div>
<div ref="running">{{ intervalData[index].running }}</div>
<div ref="lastNotification">{{ intervalData[index].lastNotification }}</div>
<div ref="lastUpdated">{{ intervalData[index].lastUpdated }}</div>
</div>
</div>
</template>
<script>
export default {
data () {
return {
defaultNotification: []
}
},
mounted () {
document.addEventListener("deviceready", this.startFetchNotifications(), false)
},
methods: {
/**
* Fetch notifications
*/
fetchNotification(key) {
let self = this self.axios.get(self.$refs.getUrl[key].innerText).then(function(response) {
if (self.$refs.lastNotification[key].innerText === '') {
self.intervalData[parseInt(self.$refs.getId[key].innerText)].lastNotification = response.data.notification
}
if (self.$refs.lastNotification[key].innerText != response.data.notification) {
if (process.env.NODE_ENV === 'production') {
cordova.plugins.notification.local.schedule({
id: parseInt(self.$refs.notificationID[key].innerText),
title: response.data.notification,
text: self.$refs.getName[key].innerText,
vibrate: false,
priority: 1,
badge: 0,
foreground: true,
sticky: true,
sound: true,
icon: 'file://' + self.$refs.notificationIcon[key].innerText,
smallIcon: 'file://' + self.$refs.notificationIcon[key].innerText
});
} else {
console.info('Native notification: ' + response.data.notification + ', won\'t run in the browser')
}
self.intervalData[parseInt(self.$refs.getId[key].innerText)].lastNotification = response.data.notification ? response.data.notification : ''
self.intervalData[parseInt(self.$refs.getId[key].innerText)].lastUpdated = new Date()
}
})
},
/**
* Fetch new notifications
*/
autoFetchNotifications() {
let self = this
Object.keys(this.dataUrls).forEach(function(key, index) {
// Interval updated
if (parseInt(self.$refs.fetch[key].innerText) != parseInt(self.$refs.storedInterval[key].innerText)) {
self.intervalData[parseInt(self.$refs.getId[key].innerText)].storedInterval = parseInt(self.$refs.fetch[key].innerText)
// Stop running so we can start with new interval further down
if (self.$refs.running[key].innerText != 'false') {
clearInterval(parseInt(self.$refs.running[key].innerText))
self.intervalData[parseInt(self.$refs.getId[key].innerText)].running = 'false'
}
}
// If not running & not muted, then run
if (self.$refs.running[key].innerText === 'false' && self.$refs.muteNotifications[key].innerText === 'false' && parseInt(self.$refs.fetch[key].innerText) > 0) {
self.intervalData[parseInt(self.$refs.getId[key].innerText)].running = setInterval(() => {
self.fetchNotification(key)
}, parseInt(self.$refs.fetch[key].innerText))
}
// If running & muted, then stop
if (self.$refs.running[key].innerText != 'false' && self.$refs.muteNotifications[key].innerText === 'true') {
clearInterval(parseInt(self.$refs.running[key].innerText))
self.intervalData[parseInt(self.$refs.getId[key].innerText)].running = 'false'
}
})
}
},
computed: {
dataUrls () {
return this.$store.state.localStorage.dataUrls
},
intervalData () {
return this.$store.state.localStorage.intervalData
}
},
watch: {
dataUrls: {
handler: function (val, Oldval) {
setTimeout(function () {
console.log('fetch')
this.autoFetchNotifications()
}.bind(this), 10)
},
deep: true
}
}
}
</script>
Above is my notification component, loaded in every view.
How can I suppress this error, or what alternative can I implement here.
Try to replace
document.addEventListener("deviceready", this.startFetchNotifications(), false)
by
document.addEventListener("deviceready", this.startFetchNotifications, false)
to call the startFetchNotifications function when deviceready event listener is triggered, not when the event is created.
you should avoid using $ref in vuejs ...
$ref is populated after the first dom render, so $ref is empty in mouted()
cf: https://v2.vuejs.org/v2/api/?#ref
My component looks like this:
<template>
<div>
<div v-if="!loaded">
<p><i class="fas fa-spinner fa-spin"></i> Loading feed</p>
</div>
<div v-else>
<div data-slider ref="feedSlider" v-if="length > 0">
<div class="swiper-wrapper">
<div class="slide" v-for="record in records" :key="record.id">
<slot :record="record"></slot>
</div>
</div>
</div>
<div v-else>
<p>There are no records available.</p>
</div>
</div>
</div>
</template>
<script>
import Swiper from 'swiper';
import AjaxCaller from '../../mixins/AjaxCaller';
export default {
mixins: [AjaxCaller],
data() {
return {
loaded: false,
records: [],
length: 0,
}
},
mounted() {
this.makeCall(this.success, this.failure);
},
methods: {
success(response) {
this.loaded = true;
if (!response.data.records) {
return;
}
this.records = response.data.records;
this.length = this.records.length;
if (this.length < 2) {
return;
}
setTimeout(() => {
this.initiateSlider();
}, 1000);
},
initiateSlider() {
(new Swiper(this.$refs.feedSlider, {
effect: 'slide',
slideClass: 'slide',
slideActiveClass: 'slide-active',
slideVisibleClass: 'slide-visible',
slideDuplicateClass: 'slide-duplicate',
slidesPerView: 1,
spaceBetween: 0,
loop: true,
speed: 2000,
autoplay: {
delay: 5000,
},
autoplayDisableOnInteraction: false,
}));
},
failure(error) {
this.stopProcessing();
console.log(error);
}
}
}
</script>
The imported mixin AjaxCaller, which works fine with any other component:
<script>
export default {
props: {
url: {
type: String,
required: true
},
method: {
type: String,
default: 'post'
}
},
data() {
return {
processing: false
}
},
computed: {
getMethodParams() {
if (this.method === 'post') {
return {};
}
return this.requestData();
},
postMethodData() {
if (this.method === 'get') {
return {};
}
return this.requestData();
}
},
methods: {
requestData() {
return {};
},
startProcessing() {
this.processing = true;
this.startProcessingEvent();
},
stopProcessing() {
this.processing = false;
this.stopProcessingEvent();
},
startProcessingEvent() {},
stopProcessingEvent() {},
makeCall(success, failure) {
this.startProcessing();
window.axios.request({
url: this.url,
method: this.method,
params: this.getMethodParams,
data: this.postMethodData
})
.then(success)
.catch(failure);
}
}
}
</script>
And here's how I call it from within the view:
<feed-wrapper url="{{ route('front.news.feed') }}">
<div slot-scope="{ record }">
<p>
<a :href="record.uri" v-text="record.name"></a><br />
<span v-text="record.excerpt"></span>
</p>
</div>
</feed-wrapper>
Everything works fine in any browser other than IE 11 (and lower).
It even works in Edge - no issues what so ever.
In IE I get
[Vue warn]: Failed to generate render function:
Syntax Error: Expected identifier in ...
It doesn't even get to execute method call from within the mounted segment.
I use laravel-mix with Laravel so everything is compiled using webpack with babel so it's not ES6 related issue.
I've already spent whole night trying to un-puzzle this so any help would be much appreciated.
I know you've already said that you don't believe it's an ES6 issue but the evidence suggests it is.
IE11 doesn't support destructuring. If you type something like var {record} = {} into your IE11 console you'll see this same error message, 'Expected identifier'.
Try doing a search through the compiled code in your original error message and look for the word record. I suspect you'll find something like this:
fn:function({ record })
If you see that it means that the destructuring has made it to the browser without being compiled through Babel.
Exactly why this is happening depends on where you're using that scoped slot template. If you're using it inside a single-file component it should be going through Babel but if you aren't then it may be making it to the browser without transpiling. You said that you're calling it 'from within the view' but that doesn't clarify exactly how you're using it. There's a note about this in the docs, for what it's worth:
https://v2.vuejs.org/v2/guide/components-slots.html#Destructuring-slot-scope
Assuming you aren't able to fix the transpiling problem directly (e.g. by moving the template to somewhere it'll go through Babel) you can just remove the ES6 destructuring. So something like:
<div slot-scope="slotProps">
and then using slotProps.record instead of record in the code that follows.
I try to dynamic notify when I wrote some messages.
That's my vue.js code.
<script>
Vue.http.options.emulateJSON = true; // Send as
new Vue({
el: '#app',
data: {
name : "",
postResult : ""
},
methods: {
click: function() {
this.$http.post('/api/test', {name:this.name}).then(function(response){
var result = response.data;
//this.postResults.push(result.name);
if (result.name == "1234")
{
this.postResult = "<div> Success </div>";
}
else
{
this.postResult = "<div> Fail </div>";
}
}, function(response){
// Error Handling
});
}
}
});
</script>
When I use jQuery's Ajax, I used this method. But my vue.js script is not working. Should I study more about Vue JS? or I forget some syntax in this vue.js?
<template>
<div v-if='requestCompleted'>
<div v-if='!postResult'> Fail </div>
<div v-else-if='postResult'> Success </div>
</div>
</template>
<script>
Vue.http.options.emulateJSON = true; // Send as
new Vue({
el: '#app',
data: {
name : "",
postResult : null,
requestCompleted: false
},
methods: {
click: function() {
this.$http.post('/api/test', {name:this.name}).then((response)=>{
var result = response.data;
this.requestCompleted=true;
if (result.name == "1234")
{
this.postResult = true;
}
else
{
this.postResult = false;
}
}, function(response){
// Error Handling
});
}
}
});
</script>
Use arrow functions for getting access to 'this' inside your callback function.
For HTTP requests, it's better to use Axios. Also, you can use vuex store and manage your requests with actions
You don't have "this" inside your response callback. Do var me = this at the top level of your click function, then do me.postResult = ... in the callback.
In general terms, try and keep all your markup in the template element, no ?
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;
});
}
}
});