Related
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.
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?
In parent component, send a variable as prop to child(DirectionsRenderer)
In child there is function(preparePoints function in DirectionsRenderer.js) set this;
but couldnt access 'this' reference inside that function
Parent:
<template>
<div>
<div>
<h2>Start</h2>
<label>
<gmap-autocomplete #place_changed="setStartPlace"></gmap-autocomplete>
<button #click="addStartMarker">Add</button>
</label>
<br />
</div>
<div>
<h2>End</h2>
<label>
<gmap-autocomplete #place_changed="setEndPlace"></gmap-autocomplete>
<button #click="addEndMarker">Add</button>
</label>
<br />
</div>
<br />
<gmap-map ref="xyz" :center="center" :zoom="7" style="width:100%; height: 400px;">
<gmap-marker
:key="index"
v-for="(m, index) in markers"
:position="m.position"
#click="center=m.position"
></gmap-marker>
<DirectionsRenderer
:v-model="pointList" //this is what I want filled by child component
travelMode="DRIVING"
:origin="origin"
:destination="destionation"
/>
{{pointList}}
</gmap-map>
</div>
</template>
<script>
import DirectionsRenderer from "./DirectionsRenderer.js";
export default {
components: {
DirectionsRenderer,
},
name: "GoogleMap",
data() {
return {
center: { lat: 41.85, lng: -87.65 },
pointList: [],
markers: [],
places: [],
path: [],
currentPlace: null,
startPoint: {},
endPoint: {},
};
},
computed: {
origin() {
if (!this.startPoint) return null;
return this.startPoint;
},
destionation() {
if (!this.endPoint) return null;
return this.endPoint;
},
},
mounted() {
this.geolocate();
},
methods: {
getPoints() {
return this.pointList;
},
setStartPlace(place) {
this.currentPlace = place;
},
setEndPlace(place) {
this.currentPlace = place;
},
addStartMarker() {
if (this.currentPlace) {
const marker = {
lat: this.currentPlace.geometry.location.lat(),
lng: this.currentPlace.geometry.location.lng(),
};
this.startPoint = marker;
this.markers[0] = { position: marker };
this.places.push(this.currentPlace);
this.center = marker;
this.currentPlace = null;
}
},
addEndMarker() {
if (this.currentPlace) {
const marker = {
lat: this.currentPlace.geometry.location.lat(),
lng: this.currentPlace.geometry.location.lng(),
};
this.endPoint = marker;
this.markers[1] = { position: marker };
this.places.push(this.currentPlace);
this.center = marker;
this.currentPlace = null;
}
},
geolocate: function () {
navigator.geolocation.getCurrentPosition((position) => {
this.center = {
lat: position.coords.latitude,
lng: position.coords.longitude,
};
});
},
},
};
</script>
Child(DirectionsRenderer.js):
export default MapElementFactory({
name: "directionsRenderer",
ctr() {
return window.google.maps.DirectionsRenderer;
},
events: [],
mappedProps: {},
props: {
pointList: { type: Array },
origin: { type: Object },
destination: { type: Object },
travelMode: { type: String }
},
methods: {
preparePoints: (array) => {
var result = []
array.forEach(element => {
result.push({ lat: element.lat(), lng: element.lng() })
});
debugger;
this.pointList = result;//**Throws exception cant read 'pointList' of undefined
}
},
afterCreate(directionsRenderer) {
let directionsService = new window.google.maps.DirectionsService();
this.$watch(
() => [this.origin, this.destination, this.travelMode],
() => {
let { origin, destination, travelMode } = this;
if (!origin || !destination || !travelMode) return;
var self = this;
directionsService.route(
{
origin,
destination,
travelMode
},
(response, status) => {
self.preparePoints(response.routes[0].overview_path);
}
);
}
);
},
});
How to retrieve data from child properly?
There is array in child all I need to do set 'pointList' in child and use it in parent.
You cannot use events.
The problem is, that you do not have a child / parent relationship, but a deeper hierarchy. You can use provide/inject for this.
In your parent, use
export default {
... all your old code ...
provide() {
return {
// Notice that this is a lambda => this gets bound to the this in provide, which is the vue instance
pointsCallback: (points) => this.pointList = points
}
}
}
In your child, use this:
export default {
inject: ['pointsCallback'],
// Your other code ...
// { {
// Somewhere after self.preparePoints(response.routes[0].overview_path);
this.pointsCallback(theNewPointsListYouComputed);
// This will call the pointsCallback you defined in your parent.
// } } and so on
}
I am using Vue-Chartjs to create a simple Line chart, I'm filling the chart with data via a get request to an API
however I want to generate new values randomly when I click on a button, & pass the values as a prop to chart-line component.
I've tried using reactiveProp & I also tried using a watcher for chartData prop, but I'm always getting this error
client.js?06a0:83 TypeError: Cannot read property 'map' of undefined
Dashboard Component
<template>
<div class="container">
<h1>Dashboard Page</h1>
<v-alert v-if="errorDetected"
class="mt-4"
dense
outlined
type="error"
>
There was an error while getting the chart data
</v-alert>
<v-btn #click="generateNewData()">Generate new data</v-btn>
<div class="loader-container">
<img v-if="!loaded" class="chart-loader mt-3" src="../static/loader-dotted.gif" alt="">
</div>
<ChartLine v-if="loaded" :chartData="values" :bind="true" />
</div>
</template>
<script>
import ChartLine from '../components/chart-line'
export default {
middleware: 'session',
components: {
ChartLine
},
data() {
return {
values: [],
customValues: [],
loaded: false,
errorDetected: false
}
},
head() {
return {
title: 'Dashboard page',
meta: [
{
hid: 'description',
name: 'description',
content: 'simple dashboard SPA'
}
]
}
},
mounted() {
this.requestData()
},
methods: {
requestData() {
this.loaded = false
this.$axios.get('http://www.mocky.io/v2/5eda474f330000fefc79eab4?mocky-delay=2000ms').then(response => {
console.log("requestData -> response", response)
this.values = response.data.data.value
this.loaded = true
}).catch(error => {
this.loaded = true
this.errorDetected = true
})
},
generateNewData() {
this.values = [];
for(let i=0; i<7; i++)
this.values.push(Math.floor((Math.random() * 10) + 1))
}
}
}
</script>
<style>
.loader-container {
display: flex;
justify-content: center;
}
.chart-loader {
width: 150px;
}
</style>
ChartLine Component
<script>
//Importing Line class from the vue-chartjs wrapper
import { Line, mixins } from 'vue-chartjs'
const { reactiveProp } = mixins
//Exporting this so it can be used in other components
export default {
extends: Line,
mixins: [reactiveProp],
props: ['chartData'],
data () {
return {
datacollection: {
//Data to be represented on x-axis
labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'],
datasets: [
{
label: "Data 1",
backgroundColor: "transparent",
borderColor: "rgba(1, 116, 188, 0.50)",
pointBackgroundColor: "rgba(171, 71, 188, 1)",
//Data to be represented on y-axis
data: this.chartData
}
],
},
options: {
responsive: true,
maintainAspectRatio: false
}
//Chart.js options that controls the appearance of the chart
}
},
watch: {
chartData() {
this.renderChart(this.datacollection, this.options)
}
},
mounted () {
//renderChart function renders the chart with the datacollection and options object.
this.renderChart(this.datacollection, this.options)
}
}
</script>
UPDATE
I've managed to solve the issue of updating the chart
I removed the dataCollection from the chart-line component & added it in the dashboard component, I've also used the requestData() method in the created() hook to make a get request to the API, then on a button click I generate a new values and pass it as a prop
Update Code
Dashboard Component
<template>
<div class="container">
<h1>Dashboard Page</h1>
<v-alert v-if="errorDetected"
class="mt-4"
dense
outlined
type="error"
>
There was an error while getting the chart data
</v-alert>
<v-btn class="primary" #click="generateNewData()">Generate New Data</v-btn>
<div class="loader-container">
<img v-if="!loaded" class="chart-loader mt-3" src="../static/loader-dotted.gif" alt="">
</div>
<ChartLine v-if="loaded" :chart-data="dataCollection" />
</div>
</template>
<script>
import ChartLine from '../components/chart-line'
export default {
middleware: 'session',
components: {
ChartLine
},
data() {
return {
dataCollection: null,
values: [],
customValues: [],
loaded: false,
errorDetected: false
}
},
head() {
return {
title: 'Dashboard page',
meta: [
{
hid: 'description',
name: 'description',
content: 'simple dashboard SPA'
}
]
}
},
created() {
// this.loaded = false
// this.fillData()
// this.loaded = true
this.requestData()
},
methods: {
requestData() {
this.loaded = false
this.$axios.get('http://www.mocky.io/v2/5eda474f330000fefc79eab4?mocky-delay=2000ms').then(response => {
this.values = response.data.data.value
this.loaded = true
this.fillData()
}).catch(error => {
this.loaded = true
this.errorDetected = true
})
},
fillData () {
this.dataCollection = {
labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'],
datasets: [
{
label: "Data 1",
backgroundColor: "transparent",
borderColor: "rgba(1, 116, 188, 0.50)",
pointBackgroundColor: "rgba(171, 71, 188, 1)",
data: this.values
}
]
}
},
generateNewData() {
this.values = []
this.loaded = false
setTimeout(function(){},2000)
for(let i=0; i<7; i++) {
this.values.push(Math.floor(Math.random() * (50 - 5 + 1)) + 5)
}
this.fillData()
this.loaded = true
}
}
}
</script>
<style>
.loader-container {
display: flex;
justify-content: center;
}
.chart-loader {
width: 150px;
}
</style>
Chart-Line Component
<script>
//Importing Line class from the vue-chartjs wrapper
import { Line, mixins } from 'vue-chartjs'
//Exporting this so it can be used in other components
export default {
extends: Line,
mixins: [mixins.reactiveProp],
// props:['chartData'],
data () {
return {
options: {
responsive: true,
maintainAspectRatio: false
}
//Chart.js options that controls the appearance of the chart
}
},
mounted () {
//renderChart function renders the chart with the datacollection and options object.
this.renderChart(this.chartData, this.options)
}
}
</script>
however there's still one thing I can't figure out, which is the loading state, when clicking on the button to generate new data
when I first open the dashboard page, the loading state works, but when I click on the button, loading state doesn't work
any Idea why??????
Trying to add info bubble to map in my heremap vue component (ive taken bits from https://developer.here.com/blog/showing-a-here-map-with-the-vue.js-javascript-framework and also https://developer.here.com/blog/develop-a-cross-platform-desktop-maps-application-with-electron-vue.js-and-here)
I have a couple of methods on my component(mostly copied over from the here docs)
methods:{
AddMarkerToGroup(group, location, icon) {
var marker = new H.map.Marker({ lat: location.Latitude, lng: location.Longitude }, { icon: icon });
marker.setData(location.Data);
group.addObject(marker);
},
addMarkersToMap(locations,defaultIconUrl) {
var scale = window.devicePixelRatio;
var icon = new H.map.Icon(defaultIconUrl, { size: { w: 45 * scale, h: 50 * scale } });
var group = new H.map.Group();
this.map.addObject(group);
group.addEventListener('tap', function (evt) {
// event target is the marker itself, group is a parent event target
// for all objects that it contains
var bubble = new H.ui.InfoBubble(evt.target.getPosition(), {
// read custom data
content: evt.target.getData()
});
// show info bubble
this.ui.addBubble(bubble);
}, false);
var addmarker = this.AddMarkerToGroup;
locations.forEach(function (location) {
addmarker(group, location, icon);
});
}`
However i cant get the info bubble to display when the map marker is clicked. this.ui is undefined in the context of this event listener. Outside the event listener it isn't undefined. ui is defined in the mounted component event:
mounted: function() {
// Initialize the platform object:
var pixelRatio = window.devicePixelRatio || 1;
let defaultLayers = this.platform.createDefaultLayers({
tileSize: pixelRatio === 1 ? 256 : 512,
ppi: pixelRatio === 1 ? undefined : 320
});
this.map = new H.Map(
this.$refs.map,
defaultLayers.normal.map,
{pixelRatio: pixelRatio, zoom: 5, center: { lat: 54.00366, lng: -2.547855} });
let behavior = new H.mapevents.Behavior(new H.mapevents.MapEvents(this.map));
this.ui = H.ui.UI.createDefault(this.map, defaultLayers);
this.LoadMapLocations();
},
Does anybody know how to get info bubble to display?
These blogs were really useful:
https://developer.here.com/blog/showing-a-here-map-with-the-vue.js-javascript-framework
https://developer.here.com/blog/develop-a-cross-platform-desktop-maps-application-with-electron-vue.js-and-here
https://developer.here.com/blog/searching-for-points-of-interest-with-the-here-places-api-in-a-vue.js-application
My problem was I forgot to add the reference to the stylesheet.
<link rel="stylesheet" type="text/css" href="https://js.api.here.com/v3/3.0/mapsjs-ui.css?dp-version=1533195059" />
dont forget to add the script files:
<script src="https://js.api.here.com/v3/3.0/mapsjs-core.js" type="text/javascript" charset="utf-8"></script>
<script src="https://js.api.here.com/v3/3.0/mapsjs-service.js" type="text/javascript" charset="utf-8"></script>
<script src="https://js.api.here.com/v3/3.0/mapsjs-places.js" type="text/javascript" charset="utf-8"></script>
<script src="https://js.api.here.com/v3/3.0/mapsjs-mapevents.js" type="text/javascript" charset="utf-8"></script>
<script src="https://js.api.here.com/v3/3.0/mapsjs-ui.js" type="text/javascript" charset="utf-8"></script>
My HereMap.vue component in full:
`<template>
<div class="here-map">
<div ref="map" v-bind:style="{ width: width, height: height }"></div>
</div>
</template>`
<script>
export default {
name: "HereMap",
data() {
return {
map: {},
platform: {},
router:{},
geocoder:{},
directions:[],
ui: null
}
},
props: {
appId: String,
appCode: String,
lat: String,
lng: String,
width: String,
height: String
},
created: function() {
this.platform = new H.service.Platform({
"app_id": this.appId,
"app_code": this.appCode,
'useHTTPS': true,
'useCIT': true
});
this.router = this.platform.getRoutingService();
this.geocoder = this.platform.getGeocodingService();
},
mounted: function() {
// Initialize the platform object:
var pixelRatio = window.devicePixelRatio || 1;
let defaultLayers = this.platform.createDefaultLayers({
tileSize: pixelRatio === 1 ? 256 : 512,
ppi: pixelRatio === 1 ? undefined : 320
});
this.map = new H.Map(
this.$refs.map,
defaultLayers.normal.map,
{pixelRatio: pixelRatio, zoom: 5, center: { lat: 54.00366, lng: -2.547855} });
let behavior = new H.mapevents.Behavior(new H.mapevents.MapEvents(this.map));
this.ui = H.ui.UI.createDefault(this.map, defaultLayers);
this.LoadMapLocations();
},
methods:{
AddMarkerToGroup(group, location, icon) {
console.log(location);
var marker = new H.map.Marker({ lat: location.Latitude, lng: location.Longitude }, { icon: icon });
marker.setData(location.Data);
group.addObject(marker);
},
addMarkersToMap(locations,defaultIconUrl) {
var scale = window.devicePixelRatio;
var icon = new H.map.Icon(defaultIconUrl, { size: { w: 45 * scale, h: 50 * scale } });
var group = new H.map.Group();
this.map.addObject(group);
var self = this;
var position;
group.addEventListener('tap', function (evt) {
position = evt.target.getPosition();
// event target is the marker itself, group is a parent event target
// for all objects that it contains
var bubble = new H.ui.InfoBubble(evt.target.getPosition(), {
// read custom data
content: evt.target.getData()
});
// show info bubble
self.ui.addBubble(bubble);
}, false);
var addmarker = this.AddMarkerToGroup;
locations.forEach(function (location) {
addmarker(group, location, icon);
});
},
LoadMapLocations(){
let locations = [
{ Name: "Wolverhampton" , Latitude:52.5914143, Longitude: -2.1496674, Data: "wolverhampton meeting" },
{ Name: "London" , Latitude:51.5048147, Longitude: -0.121162, Data: "london meeting" },
{ Name: "Manchester" , Latitude:53.4757539, Longitude: -2.2791187, Data: "manchester meeting" }
];
this.addMarkersToMap(locations,"https://image.flaticon.com/icons/png/512/33/33622.png");
},
ZoomToLocation(lat,long,zoom){
console.log("zoom to location ");
this.map.setCenter({ lat: lat, lng: long });
this.map.setZoom(zoom);
}
}
};