Vue Js: Issue with scoped slots and IE11 - javascript

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.

Related

Filtering a stack trace array in vuejs

I am looking for some good ideas on how to filter an array that contains a stack trace. I have a database table that has four columns, one with the stack trace error messages, one that shows the priority of the error, one that shows the date the error was registered and finally a column that displays an custom made error message, which I have placed on multiple try-blocks around my system.
On the frontend I am fetching the data with axios and placing it inside an object called errors. Then in my computed properties I create an array of fields that contain the individual columns from the database and their data. I use the Bootstrap table to output it.
<template>
<b-container>
<b-card class="mt-4">
<h5>{{ $t('events') }}</h5>
<b-table
:items="errors"
:fields="fields"
:per-page="[5, 10]"
sort-desc
primary-key="id"
/>
</b-card>
</b-container>
</template>
<script>
import {errorService} from '#/services/error';
import moment from 'moment';
export default {
components: {
CommonTable,
flapper
},
data() {
return {
errors: null,
};
},
computed: {
fields() {
return [
{
key: 'priority',
label: this.$t('errorLogs.priority'),
sortable: true
},
{
key: 'creationDateTime',
label: this.$t('creationDateTime'),
formatter: date => moment(date).locale(this.$i18n.locale).format('L'),
sortable: true
},
{
key: 'stackTrace',
label: this.$t('errorLogs.stackTrace'),
sortable: true
},
{
key: 'errorMessage',
label: this.$t('message'),
sortable: true
},
]
},
},
methods: {
load(){
errorService.getErrorLogs().then(result => {
this.errors = result.data
})
}
},
created() {
this.load()
}
};
</script>
It works as it should, but the output for the stack trace takes up way too much space in the table column.
Ideally it should only show
org.springframework.web.method.annotation.MethodArgumentTypeMismatchException
and then if the user wants more detail they can click on the stack trace and get the full version in a pop up or something.
I am guessing the easiest solution would be to filter the stack trace, so that it does not show any text beyong the : sign.
But how would I implement this in the setup that I currently have?
I am guessing in computed properties I need add a method to the stackTrace field.
So:
{
key: 'stackTrace',
label: this.$t('errorLogs.stackTrace'),
sortable: true
function: this.filteredStackTrace()
},
And then create a new method.
filteredStackTrace(){
this.errors.stackTrace.filter(some filter...)
}
Maybe something like following snippet:
const app = Vue.createApp({
data() {
return {
st: `Exception in thread "main" java.lang.NullPointerException
at com.example.myproject.Book.getTitle(Book.java:16)
at com.example.myproject.Author.getBookTitles(Author.java:25)
at com.example.myproject.Bootstrap.main(Bootstrap.java:14)`,
expanded: false
};
},
computed: {
firstLine() {
return this.st.split('\n')[0]
},
allLines() {
return this.st.split('\n').filter((item, idx) => idx !== 0).toString()
}
},
})
app.mount('#demo')
<script src="https://unpkg.com/vue#3/dist/vue.global.prod.js"></script>
<div id="demo">
{{ firstLine }}
<button #click="expanded = !expanded">all</button>
<div v-if="expanded">{{ allLines }}</div>
</div>

$refs is undefined when it's still working

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

v-on click, add handler only if condition has been met

After some research the following suggestion by Mr. Evan You was found:
https://github.com/vuejs/vue/issues/7349#issuecomment-354937350
So without any hesitation I gave it a try:
Component template
<template>
<div v-on='{ click: dataType === `section` ? toggleSectionElements : null }'>
... magic
</div>
<template>
JS Logic
<script>
export default {
name: `product-section`,
props: [`section`, `sectionName`, `depth`],
methods: {
toggleSectionElements() {
... magic
}
},
computed: {
dataType() {
if (this.$props.section.sections || this.$props.depth === 0) {
return `section`
} else {
return `element`
}
}
}
}
</script>
But for described case it results in error during rendering:
[Vue warn]: Invalid handler for event "click": got null
Can someone please suggest what has been done wrong? :thinking:
Update
The way Data Model looks like:
DataModel: {
mainSectionA: {
sections: {
sectionA: {
sections: {
elementA: { values: { ... } },
elementB: { values: { ... } }
}
values: { ... }
}
sectionB: {
elementA: { values: { ... } },
elementB: { values: { ... } }
}
},
values: { ... }
},
mainSectionB: {
sections: {
elementA: { values: { ... } },
elementB: { values: { ... } },
elementC: { values: { ... } },
... elements
},
values: { ... }
}
}
Just change it to the below and it will work
v-on="condition ? { mouseover: handler } : {}"
or, if your handler is called mouseover
v-on="condition ? { mouseover } : {}"
Instead of polluting your template with ternary logic, you should actually perform the check inside the click handler instead. It not only makes your template more readable, but also makes maintaining the code easier since all logic has been abstracted and delegated to the event handler's callback instead.
Quick solution
Therefore the quick solution is to actually ensure that the toggleSectionElements() will only work when a correct dataType is present. This can be achieved by using a guard clause:
toggleSectionElements() {
// Guard clause to prevent further code execution
if (this.dataType() !== 'section')
return;
// Magic here
}
Even better, is that if separate handlers should be assigned to each dataType: you can then create a factory function for that purpose:
methods: {
// This is just a factory function
toggleElements() {
switch (this.dataType()) {
case 'section':
return this.toggleSectionElements;
case 'element':
// Something else...
}
},
toggleSectionElements() {
// Magic for section element
}
}
Suggestion: using atomic components
Since it might be costly to bind click event handlers to elements that end up doing nothing, you can also break down your component to be more atomic. The collection element will be responsible of receiving an array of "section" or "element", and each "section"/"element" will have its own component, something like this:
You have a collection component, say <my-collection>, that holds all "section" and "element" components
"section" component will use the <my-section> component
"element" component will use the <my-element> component
This is when VueJS becomes really powerful: you can use dynamic component inside <my-collection> to determine which component to use depending on the dataType encountered.
This is done by running a v-for through the collection, and then using v-bind:is="..." to determine whether a specific collection item should be using "section" or "element". I understand that this is probably going to go out of scope of your original question, but it's a worthwhile design to consider:
const collectionComponent = Vue.component('my-collection', {
template: '#my-collection-component',
data: function() {
return {
collection: [{
dataType: 'section',
description: 'Hello I am section 1'
}, {
dataType: 'element',
description: 'Hello I am element 1'
}, {
dataType: 'section',
description: 'Hello I am section 2'
}, {
dataType: 'element',
description: 'Hello I am element 2'
}]
}
},
methods: {
componentToUse(dataType) {
return 'my-' + dataType;
}
}
});
const sectionComponent = Vue.component('my-section', {
template: '#my-section-component',
props: ['itemData'],
methods: {
toggle() {
console.log('Doing some magic.');
}
}
});
const elementComponent = Vue.component('my-element', {
template: '#my-element-component',
props: ['itemData']
});
new Vue({
el: '#app'
});
.box {
border: 1px solid #999;
cursor: pointer;
margin: 10px;
padding: 10px;
}
.box:hover {
background-color: #eee;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<my-collection />
</div>
<script type="text/x-template" id="my-collection-component">
<div>
<component
v-for="(item, i) in collection"
v-bind:key="i"
v-bind:is="componentToUse(item.dataType)"
v-bind:itemData="item" />
</div>
</script>
<script type="text/x-template" id="my-section-component">
<div #click="toggle" class="box">
<h1>{{ itemData.dataType }}</h1>
<p>{{ itemData.description }}</p>
<p>Clicking on me will invoke a section-specific logic</p>
</div>
</script>
<script type="text/x-template" id="my-element-component">
<div class="box">
<h1>{{ itemData.dataType }}</h1>
<p>{{ itemData.description }}</p>
<p>Clicking on me will do nothing</p>
</div>
</script>
here:
click: dataType === `section` ? toggleSectionElements : null
in the not-equal case you pass null, but the value on click expects a function. you can try an emptry function:
click: dataType === `section` ? toggleSectionElements : ()=>{}
In Vue 3 you can pass null to the listener. Combining it with optional chaining you can do this:
#click="handler?.() || null"
Same for old browsers:
#click="handler ? handler() : null"

vue pagination total didn't work

<template>
...
<div class="pagination">
<el-pagination
#current-change="handleCurrentChange"
layout="prev, pager, next"
:total="totalCount">
</el-pagination>
</div>
</template>
<script>
import Vue from 'vue';
Vue.use(ElementUI);
export default {
data() {
return {
tableData: [],
orderTableUrl: setting.orderTableUrl,
width: 110,
page_size: 10,
page_num: 1,
messages: [],
totalCount: 100,
}
},
created() {
this.getTableData()
},
methods: {
getTableData: function () {
let self = this;
axios.Get({
url: self.orderTableUrl,
params: {
'page_size': self.page_size,
'page_num': self.page_num
},
callback: function (res) {
self.tableData = res.data.orders;
self.totalCount = res.data.orders_total_pages;
console.log(self.totalCount)
}
});
},
}
}
the pagination part use element.ui .
Here is my problem: in method callback, console.log can echo real num of total page, but it cannot display on template, and only can see the num 1 of page on window.
I'm so puzzled for that.
Is it said that vue can immediately show data on change on view
finish the question. the ':total' does work, just surprised that it means the count of objects instead of pages ...

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...

Categories