Why is Babel throwing an error in my vue.js component? - vue.js

<template>
<div emptyDiv>
<h3> Stages </h3>
<table :style="tableStyle">
<tbody>
<template v-for="item in jobs">
<tr>
<td v-for="stage in item.stage_exec" :style="getStyle(stage.build_id)" :title="stage.build_id" >
[[ stage.cleanDuration ]]
</td>
</tr>
</template>
</tbody>
</table>
</div>
</template>
<script>
import Vue from 'vue';
import moment from 'moment';
export default {
delimiters: [ '[[', ']]' ],
props: ['jobs'],
computed: {
tableStyle() {
return {
'background-color': '#f9f9f9',
'border-color': '#C0C0C0',
'padding': '8px',
'width': '100%',
'display': 'table',
'table-layout': 'fixed',
};
},
emptyDiv() {
return {
'display': 'contents',
};
},
},
methods: {
getStyle (name) {
switch (name) {
case 'SUCCESS':
return {
'background-color': '#dff0d8',
'padding': '10px',
'line-height': '1.42857143',
'border': '1px solid #C0C0C0',
}
case 'FAILURE':
return {
'background-color': '#f45942',
'padding': '10px',
'line-height': '1.42857143',
'border': '1px solid #C0C0C0',
}
}
},
},
filters: {
durationReadable: function(duration) {
milliseconds = parseInt((duration%1000)/100)
seconds = parseInt((duration/1000)%60)
minutes = parseInt((duration/(1000*60))%60)
if (minutes < 10) {
minutes = '0' + minutes
}
if (seconds < 10){
seconds = '0' + seconds
}
return minutes + " m " + seconds + " s " + milliseconds + ' ms'
}
},
created() {
testEndpoint = 'http://testurl'
fetch(testEndpoint)
.then(body => {
cleanStartTime = moment(body[0].time_start)
cleanEndTime = moment(body[0].time_end)
cleanDuration = this.calculateDuration(cleanStartTime, cleanEndTime)
this.job_execs.push({
'name': body[0].job.name,
'build_id': body[0].build_id,
'env': body[0].job.env,
'time_start': cleanStartTime.format('LLL'),
'time_end': cleanEndTime.format
})
})
.catch( err => {
this.isEmpty = (this.binaries.length == 0) ? true : false;
console.log('Error Fetching:', testEndpoint, err);
return { 'failure': testEndpoint, 'reason': err };
},
},
</script>
When I try to serve my component, it is failing to compile and I keep getting a babel error that I cannot figure out.
Unexpected token (117:2)
115 | return { 'failure': testEndpoint, 'reason': err };
116 | },
> 117 |
Unexpected token (117:2)
115 | return { 'failure': testEndpoint, 'reason': err };
116 | },
> 117 | },
| ^
118 | };
119 |
So what is the unexpected token?

Related

Sorting multiple columns based on headers in Vue.js

I want to apply sorting on firstname, lastname and email but i did not undestand How We sort table in ascending and descnding order by clicking on column name in vuejs. I am tried lot of concepts on stackoverflow and other resources but they didn't seem to work . This is very silly question but I am stopped on this from many days.I'm new to Vue.js and any help would be appreciated. Image
<template>
<div class="h-full flex flex-col">
<ListFilter
ref="filter"
:class="{ 'mb-8': hasFilters || search }"
:filter="filter"
:has-filters="hasFilters"
:has-quick-filters="hasQuickFilters"
:query="query"
:search="search"
:search-placeholder="searchPlaceholder"
:filter-classes="filterClasses"
:initial-values="initialFilter"
:apply-filter="values => applyFilter(filterMapper(values))"
:apply-search="applySearch"
:clear-search="clearSearch"
>
<template #filters="props">
<slot
name="filters"
:reset="props.reset"
:filter="filter"
/>
</template>
<template #quickfilter>
<slot
name="quickfilter"
:apply-filter="applyFilter"
/>
</template>
<template #loader>
<loader
:loading="loading"
:backdrop="true"
/>
</template>
</ListFilter>
<!-- <div>
</div> -->
<vuetable
ref="vuetable"
:row-class="rowClass"
:api-mode="false"
:fields="columns"
:data-manager="dataManager"
:css="css.table"
:track-by="trackBy"
:sort-order="innerSortOrder"
:detail-row-component="detailRow"
:detail-row-options="detailOptions"
:no-data-template="noDataTemplate"
pagination-path="pagination"
#vuetable:row-clicked="handleRowClicked"
#vuetable:cell-clicked="props => $emit('cell-clicked', props)"
#vuetable:pagination-data="onPaginationData"
>
<template #empty-result>
<slot name="empty-result" />
</template>
<template #actions="props">
<div v-if="hasActions">
<ListActions :record="props.rowData">
<slot
name="actions"
:record="props.rowData"
/>
</ListActions>
</div>
</template>
<template #inline-actions="props">
<div v-if="hasInlineActions">
<InlineActions :record="props.rowData">
<slot
name="inline-actions"
:record="props.rowData"
/>
</InlineActions>
</div>
</template>
<template #detail-toggler="props">
<div v-if="hasDetailRow">
<DetailToggler
:row-data="props.rowData"
:row-index="props.rowIndex"
>
<template #default="detailProps">
<slot
name="detail-toggler"
:record="props.rowData"
:is-open="detailProps.isOpen"
:toggle-row="detailProps.toggleRow"
/>
</template>
</DetailToggler>
</div>
</template>
</vuetable>
<div
v-if="!infinityScroll"
class="pt-2"
>
<vuetable-pagination
ref="pagination"
:css="css.pagination"
#vuetable-pagination:change-page="onChangePage"
/>
</div>
</div>
</template>
<script>
import { Vuetable, VuetablePagination } from 'vue3-vuetable';
import AuthMixin from '#/components/auth/AuthMixin';
import ModalNavigation from '#/mixins/ModalNavigation';
// import Loader from '#/components/ui/Loader';
import { throttle } from 'lodash-es';
import ListFilter from '#/components/auth/list/ListFilter';
import NotifyMixin from '#/mixins/NotifyMixin';
const css = {
table: {
tableClass: 'table-auto w-full table',
tableBodyClass: '',
tableHeaderClass: 'px-4 py-2',
tableWrapper: 'overflow-x-auto flex-1',
loadingClass: 'loading',
ascendingIcon: 'blue chevron up icon',
descendingIcon: 'blue chevron down icon',
ascendingClass: 'sorted-asc',
descendingClass: 'sorted-desc',
sortableIcon: 'grey sort icon',
handleIcon: 'grey sidebar icon',
detailRowClass: 'bg-blue-100',
},
pagination: {
wrapperClass: 'flex justify-center py-4',
activeClass: 'active',
disabledClass: '',
pageClass: 'btn-paging',
linkClass: 'btn-paging',
paginationClass: '',
paginationInfoClass: 'pagination-info',
dropdownClass: '',
icons: {
first: '',
prev: '',
next: '',
last: '',
},
},
};
export default {
components: {
// Loader,
Vuetable,
VuetablePagination,
ListFilter,
},
mixins: [NotifyMixin, AuthMixin, ModalNavigation],
props: {
css: {
type: Object,
default() {
return css;
},
},
},
data() {
return {
// loading: false,
query: '',
// filter: this.filterMapper(this.initialFilter),
sort: undefined,
showFilters: false,
loading: false,
scrollContainer: null,
innerSortOrder: this.sortOrder || [],
data: [],
columns: [
{
name: 'first_name',
title: 'First Name',
class: 'w-1/4',
},
{
name: 'last_name',
title: 'Last Name',
class: 'w-1/4',
},
{
name: 'email',
title: 'Email',
class: 'w-1/4',
},
{
name: 'phone_number',
title: 'Phone Number',
class: 'w-1/4',
},
{
name: 'communities',
title: 'Communities',
class: 'w-1/4',
},
{
label: 'communities',
field: 'Communities',
class: 'w-1/4',
formatter(value) {
// console.log('ROW', value);
let communities = [];
value.community_has_leasing_agents.forEach(function (row) {
communities.push('<span class="tag">' + row.community_id + '</span>');
});
// console.log('communities=>', communities);
return communities.join('');
},
},
],
};
},
computed: {
search() {
return true;
},
searchPlaceholder() {
return 'Search by name, community, email';
},
hasFilters() {
// return !!this.$slots.filters;
return true;
},
hasQuickFilters() {
// return !!this.$slots['quickfilter'];
return true;
},
hasActions() {
// return !!this.$slots.actions;
return true;
},
hasInlineActions() {
// return !!this.$slots['inline-actions'];
return true;
},
hasDetailRow() {
// return this.detailRow !== "";
return true;
},
hasClickRowListener() {
// return this.$attrs['row-clicked'];
return true;
},
nonClickableRow() {
return (
this.onRowClick === 'none' &&
!this.hasClickRowListener &&
!(this.$route.params?.passFlowTo && this.$route.params?.passFlowTo !== this.$route.name)
);
},
detailOptions() {
return {
...this.detailRowOptions,
vuetable: this.$refs.vuetable,
};
},
},
async mounted() {
console.log('== Leasing Agents Index ==');
console.log(this.profile);
await this.getLeasingAgentsFromAPI();
},
created() {
if (!this.community) {
this.notifyError('please select a community to continue, then refresh the browser');
}
},
methods: {
async getLeasingAgentsFromAPI() {
console.log('LEASING::getLeasingAgentsFromAPI()');
this.loading = true;
return await this.$calendarDataProvider
.get('calendarGetLeasingAgents', {
customer_id: this.profile.customerId,
})
.then(res => {
console.log('LEASING::getLeasingAgentsFromAPI() result:');
console.log(res);
this.data = res;
return res;
// return this.getEvents(res);
})
.catch(err => console.log(err.message))
.finally(() => {
this.loading = false;
});
},
rowClass(dataItem) {
const classes = ['table-row', 'row'];
if (this.nonClickableRow) {
classes.push('table-row-nonclickable');
}
if (this.hasDetailRow && this.$refs.vuetable?.isVisibleDetailRow(dataItem[this.trackBy])) {
classes.push('table-row-detail-open');
}
return classes.join(' ');
},
toggleFilters() {
this.showFilters = !this.showFilters;
},
applySearch(value) {
this.query = value;
},
clearSearch() {
this.query = '';
},
applyFilter(values) {
this.filter = values;
this.$refs.vuetable?.changePage(1);
this.$refs.vuetable?.resetData();
},
doSearch: throttle(
function () {
if (this.query.length > 2 || this.query.length === 0) {
this.reload();
}
},
500,
{ trailing: true }
),
onPaginationData(paginationData) {
if (!this.infinityScroll) {
this.$refs.pagination.setPaginationData(paginationData);
}
},
onChangePage(page) {
this.$refs.vuetable.changePage(page);
},
reload() {
this.$refs.vuetable.resetData();
this.$refs.vuetable.refresh();
},
getSort({ sortField, direction }) {
// sortField: 'type&bundle.name', - sort by two fields
const fields = sortField.split('&');
if (fields.length > 1) {
return fields.map(item => {
const [fieldName, dir = direction] = item.split('|');
return `${fieldName},${dir}`;
});
}
return `${sortField},${direction}`;
},
async dataManager(/*sortOrder = [], pagination = { current_page: 1 }*/) {
// allow static data
// if (this.data) {
// return {
// data: this.data,
// };
// }
const data = await this.getLeasingAgentsFromAPI();
return {
data: data,
};
// const prevData = this.$refs.vuetable?.tableData ?? [];
// const { pageSize: size, query, filter, sort: oldSort } = this;
// const sort = sortOrder[0] ? this.getSort(sortOrder[0]) : undefined;
// this.loading = true;
// return this[this.dataProvider]
// .getList(this.resource, {
// page: pagination.current_page - 1,
// size,
// sort,
// query,
// ...this.requestParams,
// ...filter,
// })
// .then(this.responseMapper)
// .then(({ content: nextData, totalElements }) => {
// const newPagination = this.$refs.vuetable?.makePagination(totalElements, this.pageSize, pagination.current_page);
// this.sort = sort;
// this.innerSortOrder = sortOrder;
// return {
// pagination: newPagination,
// data: this.infinityScroll && oldSort === sort ? [...prevData, ...nextData] : nextData,
// };
// })
// .catch(error => {
// this.notifyError(error.message);
// return {
// pagination: this.$refs.vuetable?.tablePagination,
// data: this.$refs.vuetable?.tableData,
// };
// })
// .finally(() => (this.loading = false));
},
handleRowClicked({ data, ...rest }) {
if (this.$route.params?.passFlowTo && this.$route.params?.passFlowTo !== this.$route.name) {
this.$router.push({
name: this.$route.params?.passFlowTo,
params: {
...this.$route.params,
[this.routeIdParam]: data[this.trackBy],
},
});
} else {
switch (this.onRowClick) {
case 'edit':
this.$router.replace({ path: `${this.basePath}/${data[this.trackBy]}` });
break;
case 'details':
this.$router.replace({ path: `${this.basePath}/${data[this.trackBy]}/details` });
break;
default:
this.$emit('row-clicked', { data, ...rest });
}
}
},
handleScrollContainer(e) {
if (this.$refs.vuetable.tablePagination && e.target.scrollHeight - e.target.scrollTop - e.target.clientHeight <= 2) {
this.onChangePage('next');
}
},
},
};
</script>
<style scoped>
.tag {
display: inline-flex;
align-items: center;
overflow: hidden;
margin-right: 0.25rem;
padding-left: 0.5rem;
padding-right: 0.5rem;
padding-top: 0.25rem;
padding-bottom: 0.25rem;
border-radius: 0.0625rem;
--bg-opacity: 1;
background-color: #eaf6ff;
background-color: rgba(234, 246, 255, var(--bg-opacity));
--text-opacity: 1;
color: #105d91;
color: rgba(16, 93, 145, var(--text-opacity));
font-family: 'FrankNew', sans-serif;
font-weight: 500;
}
</style>
I'm attaching my index.vue file and image.

How to display markerClusters by axios props with Vue 3 and vue-Leaflet?

I have a problem to display the markerClusters after receive by props in my component Map :
<template>
<div>
<LMap
v-show="showLoading"
class="map"
ref="map"
:center="center"
:zoom="zoom"
:maxZoom="max"
:minZoom="min"
:options="{ attributionControl: false }"
#ready="onLeafletReady"
:zoomAnimation="true"
:markerZoomAnimation="true"
:useGlobalLeaflet="true"
>
<template v-if="leafletReady">
<LTileLayer
v-for="tileProvider in tileProviders"
:attribution="tileProvider.attribution"
:key="tileProvider.name"
layer-type="base"
:name="tileProvider.name"
:url="tileProvider.url"
:visible="tileProvider.visible"
>
</LTileLayer>
<LControlScale position="bottomleft" :imperial="false" :metric="true" />
<LControlAttribution position="bottomright" prefix="FTM" />
<LControlLayers />
<div class="loader" v-if="loading" />
</template>
</LMap>
</div>
</template>
<script>
import { defineComponent, ref } from "vue"
import * as L from "leaflet"
import "leaflet.markercluster/dist/leaflet.markercluster.js"
import "leaflet/dist/leaflet.css"
import "leaflet.markercluster/dist/MarkerCluster.css"
import "leaflet.markercluster/dist/MarkerCluster.Default.css"
import {
LControlAttribution,
LControlLayers,
LControlScale,
LMap,
LTileLayer
} from "#vue-leaflet/vue-leaflet"
export default defineComponent({
components: {
LControlAttribution,
LControlLayers,
LControlScale,
LMap,
LTileLayer
},
props: {
pois: {
type: Array,
default: null
}
},
setup() {
return {
showLoading: ref(true),
zoom: ref(7),
center: ref({ lat: 48.85664, lng: 2.35347 })
}
},
data() {
return {
leafletReady: false,
poiMarkersGroup: null,
attribution:
'© <a target="_blank" href="http://osm.org/copyright">OpenStreetMap</a> contributors',
max: 19,
min: 6,
tileProviders: [
{
name: this.$t("map.mapTiles.standard"),
visible: true,
maxZoom: 19,
attribution:
'© <a target="_blank" href="http://osm.org/copyright">OpenStreetMap</a> contributors',
url: "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
},
{
name: this.$t("map.mapTiles.satellite"),
visible: false,
maxZoom: 17,
url: "https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}",
attribution:
"Tiles © Esri — Source: Esri, i-cubed, USDA, USGS, AEX, GeoEye, Getmapping, Aerogrid, IGN, IGP, UPR-EGP, and the GIS User Community"
},
{
name: this.$t("map.mapTiles.urbanTransport"),
visible: false,
maxZoom: 20,
attribution:
'© OpenStreetMap France | © OpenStreetMap contributors',
url: "https://{s}.tile.openstreetmap.fr/osmfr/{z}/{x}/{y}.png"
},
{
name: this.$t("map.mapTiles.cyclePaths"),
visible: false,
maxZoom: 20,
attribution:
'CyclOSM | Map data: © OpenStreetMap contributors',
url: "https://{s}.tile-cyclosm.openstreetmap.fr/cyclosm/{z}/{x}/{y}.png"
}
],
url: "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
}
},
methods: {
async onLeafletReady(_map) {
await this.$nextTick()
this.leafletReady = true
},
compilClusters(pois, nbPois) {
L.Map.addInitHook(function () {
const markerCluster = L.markerClusterGroup({
removeOutsideVisibleBounds: true,
chunkedLoading: true,
showCoverageOnHover: false
}).addTo(this)
let markers = []
for (let i = 0; i < nbPois; i++) {
const marker = L.marker(L.latLng(pois[i].coords))
marker.bindPopup(
"<ul style='padding: 0; list-style-type: none;'>" +
"<li style='font-weight: bold;'>" +
pois[i].name +
"<li>" +
pois[i].address +
"</li>" +
"</li>" +
"<li>" +
pois[i].postalCode +
" " +
pois[i].city +
"</li>" +
"</ul>"
)
markers.push(marker)
markerCluster.addLayers(markers)
}
})
}
},
watch: {
pois(val) {
if (val !== null) {
this.compilClusters(val, val.length)
}
}
}
})
</script>
<style lang="scss" scoped>
.map {
width: 100%;
height: 100%;
height: 100vh;
}
</style>
I'm using Quasar+leaflet+#vue-leaflet/vue-leaflet and leaflet.markercluster.
I guess based on code https://codesandbox.io/s/vue-3-leaflet-marker-cluster-poc-forked-ns238?file=/components/TestMap.vue:2715-3306
When I send the latlng by props, the coordinates are passed to the compilClusters(latLng, nbOfLatLng) function through the watch() function.
But they are not displayed, if I reload my page, they are displayed!
How to display it when I receive them by props?

How to update a table's data in VueJS?

I have a table with date filters (it's a panel with 4 options: day/week/month/year). My task is create pagination. After selection of the filter with specific date range, I fetch data from API and save its response to vuex store. I logged everything - fetching and vuex work fine. But when I start to change filters, my table data isn't updating properly - seems like it uses old stored data when new is added. I tried lots of ways to solve it - using a computed property, special counter, etc. How my problem can be solved? Thanks.
<template>
<div class='page'>
<h1 class='title'>Bookings</h1>
<div class='schedule-page classes bookings' :class='{ isActive: isOpenPopup }'>
<div class='filter-bar-wrapper'>
<div class='row'>
<FilterBar #change='handleFilters' :categories='allCategories' :statuses='bookingClassesStatus' />
<button class='button button--primary button--create' #click='handleCreateBooking'
v-if='caller !== callerOptions.ADMIN'>
Add a new bookings
</button>
</div>
</div>
<div class='table-wrapper'>
<p class='title'>{{ today }}</p>
<table class='bookings-table'>
<thead>
<tr>
<th>ID booking</th>
<th>Date</th>
<th>Time</th>
<th>Studio name</th>
<th>Client name</th>
<th>Class name</th>
<th>Categories</th>
<th>Points</th>
<th>Event creator</th>
<th>Status</th>
<th>Validate code</th>
</tr>
</thead>
<tbody>
<template>
<tr :key='`booking-body-title`' class='empty-row'
v-if='this.newBookingsAmount > 0'>
<td :style='"padding: 12px 16px 12px 28px; color:" + Styles.BLUE'>New bookings</td>
</tr>
<template v-for='(booking, index) in this.bookings'>
// lots of tr/td
</template>
</template>
</tbody>
</table>
<Pagination :pagesNum='this.pages' #change='handlePaging' />
</div>
</div>
</template>
<script>
import { mapGetters, mapActions } from 'vuex'
import moment from 'moment'
import FilterBar from '#/components/schedule/FilterBar'
import STYLES from '#/store/styles'
import bookingStatus from '#/store/modules/bookingStatus'
import Pagination from '#/components/Pagination'
import classStatus from '#/store/modules/classStatus'
import { TAKE_ITEMS_PER_PAGE } from '#/store/modules/admin/config'
import callerOptions from '#/store/modules/admin/bookings/callerOptions'
export default {
props: {
currentStudio: {
type: Object,
},
caller: {
type: String,
required: true,
validator: (value) => {
return Object.values(callerOptions).includes(value)
},
},
},
data() {
return {
bookings: [],
newBookingsAmount: 0,
pages: 0,
callerOptions: callerOptions,
mode: '',
filters: {
startDate: moment()
.startOf('day')
.utcOffset(0, true)
.toISOString(),
endDate: moment()
.endOf('day')
.utcOffset(0, true)
.toISOString(),
},
bookingClassesStatus: bookingStatus,
bookingItem: {
date: null,
class: '',
client: '',
status: '',
numberOfPoints: null,
comment: '',
},
}
},
computed: {
...mapGetters([
'bookingsList',
]),
today() {
switch (this.mode) {
case 'day':
return moment(this.filters.startDate).format('MMMM D')
case 'week':
return `${moment(this.filters.startDate).utc().format('D.MM')} - ${moment(this.filters.endDate).utc().format('D.MM')}`
case 'month':
return moment(this.filters.startDate).format('MMMM, YYYY')
case 'year':
return moment(this.filters.startDate).format('YYYY')
default:
return 'Invalid date'
}
},
},
methods: {
moment,
...mapActions([
'getBookingsList',
'addBooking',
]),
handleFilters(filters, mode) {
this.filters = filters
this.mode = mode
this.refresh()
},
handlePaging(page) {
const rootElement = document.querySelector('.main-content')
if (rootElement) {
rootElement.scrollTo(0, 0)
}
switch (this.caller) {
case callerOptions.ADMIN:
this.getBookingsList({
...this.filters,
page: page,
take: TAKE_ITEMS_PER_PAGE,
})
break
case callerOptions.CLIENT:
this.getBookingsList({
clientId: this.clientInfo.id,
...this.filters,
page: page,
take: TAKE_ITEMS_PER_PAGE,
})
break
case callerOptions.CLASS:
this.getBookingsList({
classId: this.studioClassesInfo.id,
...this.filters,
page: page,
take: TAKE_ITEMS_PER_PAGE,
})
break
case callerOptions.STUDIO:
this.getBookingsList({
studioId: this.studiosItemInfo.id,
...this.filters,
page: page,
take: TAKE_ITEMS_PER_PAGE,
})
break
default:
break
}
this.$router.push({
name: this.$route.name,
query: { page: page, mode: this.mode },
}).catch(() => {
})
this.bookings = [...this.bookingsList.filter(item => this.isNew.includes(item.status)), ...this.bookingsList.filter(item => !this.isNew.includes(item.status))]
this.newBookingsAmount = this.bookingsList.filter(item => this.isNew.includes(item.status)).length > 0 && this.bookingsList !== undefined ? this.bookingsList.filter(item => this.isNew.includes(item.status)).length : 0
this.pages = this.bookingsPages === undefined ? 1 : this.bookingsPages
},
async refresh() {
switch (this.caller) {
case callerOptions.ADMIN:
await this.getBookingsList({
...this.filters,
page: this.$router.currentRoute.query.page,
take: TAKE_ITEMS_PER_PAGE,
})
break
case callerOptions.CLIENT:
await this.getBookingsList({
clientId: this.clientInfo.id,
...this.filters,
page: this.$router.currentRoute.query.page,
take: TAKE_ITEMS_PER_PAGE,
})
break
case callerOptions.CLASS:
await this.getBookingsList({
classId: this.studioClassesInfo.id,
...this.filters,
page: this.$router.currentRoute.query.page,
take: TAKE_ITEMS_PER_PAGE,
})
break
case callerOptions.STUDIO:
await this.getBookingsList({
studioId: this.studiosItemInfo.id,
...this.filters,
page: this.$router.currentRoute.query.page,
take: TAKE_ITEMS_PER_PAGE,
})
break
default:
break
}
await this.$router.push({
name: this.$route.name,
query: { page: 1, mode: this.mode },
}).catch(() => {
})
this.newBookingsAmount = this.bookingsList.filter(item => this.isNew.includes(item.status)).length > 0 && this.bookingsList !== undefined ? this.bookingsList.filter(item => this.isNew.includes(item.status)).length : 0
this.pages = this.bookingsPages === undefined ? 1 : this.bookingsPages
this.bookings = this.$store.state.bookings.bookingsList
},
},
async mounted() {
await this.getClientsList()
await this.getClassesList()
await this.getStudiosList()
this.mode = this.$route.query.mode === undefined ? 'day' : this.$route.query.mode
},
watch: {
filters: {
handler() {
},
deep: true,
},
bookings: {
handler() {
},
deep: true,
}
},
components: {
FilterBar,
Pagination,
},
}
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
I fixed it. My solution was to add undefined validation inside rows

Check if the value change in loop Vuejs

I'm making chat app by Vuejs and want to handle if messages in loop belong to new user for styling color/background-color of user's message.
<template v-for="msg in allMsgs">
<li :key=msg.id> //I want to add some class to handle if next message belong to new user.
<span class="chatname">{{msg.user.name}}</span>
{{msg.content}}
</li>
</template>
https://prnt.sc/114ynuq
Thank you so much
You can use a computed property to determine the position of each message according to the sequence, and then, use class-binding as follows:
new Vue({
el:"#app",
data: () => ({
allMsgs: [
{ id:1, user: { name:'B' }, content:'contentB' },
{ id:2, user: { name:'A' }, content:'contentA' },
{ id:3, user: { name:'A' }, content:'contentA' },
{ id:4, user: { name:'B' }, content:'contentB' },
{ id:5, user: { name:'B' }, content:'contentB' },
{ id:6, user: { name:'A' }, content:'contentA' },
{ id:7, user: { name:'A' }, content:'contentA' }
]
}),
computed: {
messages: function() {
let pos = 1, prev = null;
return this.allMsgs.map((msg, index) => {
// if msg is not the first, and it belongs to a new user, opposite pos
if(index !== 0 && msg.user.name !== prev.user.name) pos *= -1;
msg.position = pos;
prev = msg;
return msg;
});
}
}
});
.chatname { font-weight:bold; }
.left { text-align:left; }
.right { text-align:right; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<template v-for="(msg, index) in messages">
<li
:key=msg.id
:class="{ 'right': msg.position === 1, 'left': msg.position === -1 }"
>
<span class="chatname">
{{msg.user.name}}
</span>
{{msg.content}}
</li>
</template>
</div>

vuejs start a timer for every record

I have this component rendered, I need to tell the users how much time remaining for each record displayed
I couldn't get anything worked out, I don't know any legible ways to work it out, so just trying calling out methods. I tried child components too but no avail.
<template>
<div>
<ul v-for="question in questions">
{{init_timer(secs)}}
{{start_timer()}}
<li>
{{question.name}}
<span class="question_clock">Validity : -- counter : {{time_remaining}}
</span> </small>
</li>
</ul>
</div>
</template>
<script>
export default {
data: function(){
return {
questions: [],
time_remaining : 1
};
},
methods: {
init_timer: function(secs) {
this.time_remaining = secs
},
start_timer: function() {
console.log('startiem')
setTimeout(function () { this.time_remaining -= 1 }.bind(this), 1000)
},
}
,
created: function(){
$.getJSON('/questions/json', function(response){
this.questions = response
}.bind(this ));
},
}
</script>
any help is much appreciated
I wrote a quick timer component that might accomplish what you want.
Vue.component("timer", {
props: {
interval: {
type: Number,
default: 1
},
initial: {
type: Number
}
},
template: `
<span>{{current}}</span>
`,
data() {
return {
current: this.initial,
timerInterval: null
}
},
methods: {
onInterval() {
this.current = this.current -= this.interval
if (this.current <= 0) {
clearInterval(this.timerInterval)
this.current = 0
}
}
},
mounted() {
setInterval(this.onInterval, this.interval * 1000)
}
})
Basically, you set the initial property which is the time allowed in seconds and the timer will update the DOM every interval seconds.
Here is an example.
console.clear()
const questions = [{
name: "What is your name?",
time: 30
},
{
name: "What is your quest?",
time: 50
},
{
name: "What is your favorite color?",
time: 20
},
{
name: "What is the airspeed velocity of an unladen swallow?",
time: 10
},
]
Vue.component("timer", {
props: {
interval: {
type: Number,
default: 1
},
initial: {
type: Number
}
},
template: `
<span>{{current}}</span>
`,
data() {
return {
current: this.initial,
timerInterval: null
}
},
methods: {
onInterval() {
this.current = this.current -= this.interval
if (this.current <= 0) {
clearInterval(this.timerInterval)
this.current = 0
}
}
},
mounted() {
setInterval(this.onInterval, this.interval * 1000)
}
})
new Vue({
el: "#app",
data: function() {
return {
questions: [],
time_remaining: 1
};
},
created: function() {
setTimeout(() => this.questions = questions, 100)
// $.getJSON('/questions/json', function(response){
// this.questions = response
// }.bind(this ));
},
})
<script src="https://unpkg.com/vue#2.2.6/dist/vue.js"></script>
<div id="app">
<ul>
<li v-for="question in questions">
{{question.name}}
<span class="question_clock">
<timer :initial="question.time"></timer>
</span>
</li>
</ul>
</div>