AdvancedMarkerView in Vue 3 - vuejs3

When using Google Maps AdvancedMarkerView isn't showing on the map. This problem only occurs when storing googleMap in the component. When storing googleMap in a const or window object its working fine.
I would like to store it in the component, so I can add or remove markers later. Am I missing something or is this because AdvancedMarkerView is beta?
mounted() {
const loader = new Loader({
apiKey: 'key',
version: 'beta',
libraries: ['marker'],
});
loader.load().then((google) => {
this.googleMap = new google.maps.Map(this.$refs.map, this.mapOptions);
// const googleMap = new google.maps.Map(this.$refs.map, this.mapOptions);
// window.googleMap = new google.maps.Map(this.$refs.map, this.mapOptions);
// Marker works fine
new google.maps.Marker({
map: this.googleMap,
position: this.home.position,
})
// Works only with window.googleMap or const googleMap
new google.maps.marker.AdvancedMarkerView({
map: this.googleMap,
position: this.home.position,
content: this.buildContent(this.home),
title: this.home.title
});
});
},

Vue3 with Advanced Markers works for me, here is my codesanbox: https://codesandbox.io/s/vue-3-google-map-base-circles-forked-1hbe2p?file=/src/components/GoogleMapLoader.vue
One thing to note is that, in Vue3, it seems using data() interferes with this.map, after changing data() to setup(), then it works fine.
Change to your API key and you should be able to see the new markers.

Related

Destroy and Redraw Chart.js Chart from Vue 3 watcher

I have a chart displayed in a canvas that I want to destroy and redraw from a Vue 3 watcher. The watcher works and the function that fires on the change works up to the redraw step. When it reaches the redraw step, I get:
TypeError: Argument 1 ('element') to Window.getComputedStyle must be an instance of Element
The chart object is loaded as a component and is rendered from a method when initially mounted. I am using vanilla chart.js (not vue-chartjs).
Mount:
mounted() {
this.renderChart()
},
Watch:
watch: {
'$store.state.weather': {
handler(newValue, oldValue) {
this.forecastChart.destroy()
this.animation = 0
this.renderChart() // works fine until this line
},
deep: true
}
}
Method:
methods: {
renderChart() {
let ctx = document.getElementById(this.id).getContext('2d');
this.forecastChart = new Chart(ctx, {
// source: https://stackoverflow.com/a/69047139/2827397
type: 'doughnut',
plugins: [{
beforeDraw: (chart) => {
const {ctx} = chart;
// etc.
Similar questions seem to have solutions that are outdated and have not worked for me.
Ideally, I'd like to make the chart reactive to the change in the Vuex store state value, but destroying and redrawing the chart would be an acceptable outcome and that is what my question here is regarding.
chart.js 3.9.1, vue 3.0.0, vuex 4.0.2
Edit 1:
Trying to .update() rather than .destroy() the chart object didn't yield results, either.
updateChart() {
this.forecastChart.data.datasets[0].needleValue = this.weather.airQuality - 0.5
this.forecastChart.update()
},
Results in:
Unhandled Promise Rejection: TypeError: undefined is not an object (evaluating 'item.fullSize = options.fullSize')
I'm not smart enough to be able to explain why, but the following revision solves the issue I was facing. Above, I was referencing the chart object with the following destroy() command:
this.forecastChart.destroy()
While this command didn't cause any error to be displayed in the console, it was clearly not working properly in this context (again--very important to note here that this is being done in Vue 3).
I replaced the above line with:
let chartStatus = Chart.getChart(this.forecastChart)
chartStatus.destroy()
And now the original chart is properly destroyed and the new chart is drawn in its place. Here is the relevant code all together:
watch: {
'$store.state.weather.airQuality': {
handler() {
this.updateChart()
},
deep: true
}
},
methods: {
updateChart() {
let chartStatus = Chart.getChart(this.forecastChart) // key change
chartStatus.destroy() // key change
this.animation = 0
this.renderChart()
},
renderChart() {
let ctx = document.getElementById(this.id).getContext('2d');
this.forecastChart = new Chart(ctx, {
type: 'doughnut',
plugins: [{
beforeDraw: (chart) => {
const {ctx} = chart;
// etc.

mmenu wordpress plugin - bind open / close events

I am using the licenced wordpress plugin version 3.1.0.
I have the menu working, but I cannot access the mmenu API to trigger the button open / close effect I would like to use. Previously I have used the mmenu core version [not WP plugin] and triggered the class changes using this:
var $menu = $("#menu").mmenu({...})
var API = $menu.data("mmenu");
API.bind("open:finish", function () {
$("#menu-btn").addClass("is-active");
});
API.bind("close:finish", function () {
$("#menu-btn").removeClass("is-active");
});
Modifying the var API to use the plugin generated id fails with undefined, probably because the menu creation is managed in a different script.
var API = $('#mm-1').data("mmenu"); //'mm-1' - the plugin generated mmenu id
I have also tried to use jQuery direct on #menu-btn but it is not triggered unless I remove the #menu-btn from the mmenu settings. For example [not copied, just a rough example so please ignore syntax errors]:
$("#menu-btn").click(function(){console.log('click')});
all I need is to add / remove an 'is-active' class to the open menu link [id=menu-btn].
The mmenu adds a body class when opened, so added a mutation observer to check if the has the .mm-wrapper--opened class or not. If it does, add the 'is-active' class to the menu icon (which uses the .hamburger class) and if not, remove it.
if ($('body').hasClass('mm-wrapper--opened')){
$('.hamburger').addClass("is-active");
}
const targetNode = document.body;
const config = { childList : true, attributes: true };
const callback = function(mutationsList, observer) {
for(let mutation of mutationsList) {
if (mutation.type === 'attributes') {
if ($('body').hasClass('mm-wrapper--opened')){
setTimeout(() => { $('.hamburger').addClass("is-active"); }, 500);
}
else {
$('.hamburger').removeClass("is-active");
}
}
}
};
const observer = new MutationObserver(callback);
observer.observe(targetNode, config);

Meteor: Reactive markers with mapbox

I'm currently trying to reactively show markers on a Mapbox map. My approach was to observe a collection and while doing so, create a GeoJSON object. Changes in that particular object do not reflect on the map however.
var featureArray = [];
CollectionName.find({}).observe({
added: function(item) {
featureArray.push({
type: "Feature",
geometry: {
type: "Point",
coordinates: [+item.location.coordinates[0], +item.location.coordinates[1]]
},
properties: {
_id: item._id,
}
});
},
changedAt: function(newDocument, oldDocument, atIndex) {
//..
},
removed: function(oldDocument, atIndex) {
//..
}
});
var CollectionGeoJSON = {
'type': 'FeatureCollection',
'features': testmarkers
};
var markers = L.mapbox.featureLayer().setGeoJSON(CollectionGeoJSON);
// add cluster markers and add them to map
My idea was to manually add/remove/change the markers on the client (as changes are synced to the server anyway), however also no success here as I'm not sure how to do that using the Mapbox API.
Any hints are greatly appreciated.
I've created a meteorpad example, showing this.
The reactivity is created by calling template.autorun in the onRendered callback. The template.autorun callback is triggered by any changes to the results of Cities.find(), and updates the map with .setGeoJSON
this.autorun(function () {
if (Mapbox.loaded()) {
geojson = Cities.find().fetch()
view.featureLayer.setGeoJSON(geojson);
}
});
In this example the contents of the Cities collection are already in the correct format to be passed to .setGeoJSON, but if you prefer you could have a different Colleciton schema, and create the list in this format within the template.autorun callback.

data binding in react + signalR

I have a strategy question.
I want to change data in my website using signalR and display changed data using react. My question would be: How to perform data binding between signalR and react?
My first clue is the following:
signalR:
chat.client.addMessage = function (name, message) {
chatHistory.push({ Author: name, Text: message }); //here I change global variable chatHistory
};
react:
var CommentList = React.createClass({some class here});
var CommentBox = React.createClass({
componentRefresh: function () {
this.setState({ data: chatHistory });
},
getInitialState: function () {
return { data: chatHistory };
},
componentDidMount: function () {
this.componentRefresh();
setInterval(this.componentRefresh, this.props.interval);
},
render: function () {
return (
React.DOM.div(null,
CommentList({ data: this.state.data })
)
);
}
});
React.renderComponent(
CommentBox({ interval: 2000 }),
document.getElementById('content')
);
in react commentBox component I feed global chatHistory and ask for a new value every 2 seconds.
Is there more elegant way of doing it?
and how to avoid redrawing of CommentBox if chatHistory variable wasn't changed?
Your approach of maintaining state in CommentBox is fine. As your component base grows, it might become complicated to maintain self-updating components though. I recommend investigating the Flux architecture the React team designed and their Todo MVC Flux example in particular.
You could implement shouldComponentUpdate to prevent React from re-rendering the CommentBox if you know state hasn't changed. Also, you should keep a reference to the interval so you can clear it when the CommentBox is unmounted otherwise it will go on polling after the component is removed.
var CommentBox = React.createClass({
...
componentDidMount: function() {
this.componentRefresh();
this._interval = setInterval(this.componentRefresh, this.props.interval);
},
componentWillUnmount: function() {
clearInterval(this._interval);
this._interval = null;
},
shouldComponentUpdate: function(nextProps, nextState) {
// Do a deep comparison of `chatHistory`. For example, use
// Underscore's `isEqual` function.
return !_.isEqual(this.state.chatHistory, nextState.chatHistory);
},
...
});

Dragging Markers in Leaflet in Meteors realtime-environment

I want draggable markers on a Leaflet map and distribute their new locations in realtime to the clients. I use meteor. To achieve this I observe the marker-collection.
This is what I've tried so far but it crashes when I drag one marker. The selected marker disappears as it should but it won't rerender the markersGroup on the map.
var newMarker;
var markers = [];
Happening.find({}).observe({
added: function(marker) {
var myIcon;
myIcon = L.icon({
iconUrl: "icon_33997.svg"
});
newMarker = L.marker([marker.location.coordinates[1],marker.location.coordinates[0]], {
icon: myIcon,
_id: marker._id,
draggable: true
});
newMarker.on('dragend', function (e){
var newCoords = this.getLatLng();
var happeningOld = Happening.find({_id: e.target.options._id}).fetch();
return Happening.update({_id: e.target.options._id}, {
item: happeningOld[0].item,
location: {
type: 'Point',
coordinates: [newCoords.lng, newCoords.lat]
},
time: Date.now(),
owner: happeningOld[0].owner
});
});
markers[newMarker.options._id] = newMarker;
markersGroup.addLayer(newMarker);
return map.addLayer(markersGroup);
},
changed: function(marker){
map.removeLayer(markersGroup);
markersGroup.removeLayer(markers[marker._id]);
markersGroup.addLayer(markers[marker._id]);
return map.addLayer(markersGroup);
}
});
This is the crash-report:
Exception in queued task: L.DistanceGrid.prototype._sqDist#http://localhost:3000/packages/leaflet-markercluster.js?d3d9bebcb9f8a1b1711174aea16a51003ba02d10:36
L.DistanceGrid.prototype.getNearObject#http://localhost:3000/packages/leaflet-markercluster.js?d3d9bebcb9f8a1b1711174aea16a51003ba02d10:36
L.MarkerClusterGroup<._addLayer#http://localhost:3000/packages/leaflet-markercluster.js?d3d9bebcb9f8a1b1711174aea16a51003ba02d10:36
L.MarkerClusterGroup<.addLayers/l<#http://localhost:3000/packages/leaflet-markercluster.js?d3d9bebcb9f8a1b1711174aea16a51003ba02d10:36
o.Util.bind/<#http://localhost:3000/packages/leaflet.js?ad7b569067d1f68c7403ea1c89a172b4cfd68d85:37
L.MarkerClusterGroup<.addLayers#http://localhost:3000/packages/leaflet-markercluster.js?d3d9bebcb9f8a1b1711174aea16a51003ba02d10:36
L.MarkerClusterGroup<.onAdd#http://localhost:3000/packages/leaflet-markercluster.js?d3d9bebcb9f8a1b1711174aea16a51003ba02d10:36
o.Map<._layerAdd#http://localhost:3000/packages/leaflet.js?ad7b569067d1f68c7403ea1c89a172b4cfd68d85:37
o.Map<.addLayer#http://localhost:3000/packages/leaflet.js?ad7b569067d1f68c7403ea1c89a172b4cfd68d85:37
Template.map.rendered/<.changed#http://localhost:3000/where-to-go.js?1b666a1c77f7d81e0212a2c65aa72a9d570b4dac:287
LocalCollection._observeFromObserveChanges/observeChangesCallbacks.changed#http://localhost:3000/packages/minimongo.js?4ee0ab879b747ffce53b84d2eb80d456d2dcca6d:3845
LocalCollection._CachingChangeObserver/self.applyChange.changed#http://localhost:3000/packages/minimongo.js?4ee0ab879b747ffce53b84d2eb80d456d2dcca6d:3750
.observeChanges/wrapCallback/</<#http://localhost:3000/packages/minimongo.js?4ee0ab879b747ffce53b84d2eb80d456d2dcca6d:374
.runTask#http://localhost:3000/packages/meteor.js?148e9381d225ecad703f4b858769b636ff7a2537:576
.flush#http://localhost:3000/packages/meteor.js?148e9381d225ecad703f4b858769b636ff7a2537:604
.drain#http://localhost:3000/packages/meteor.js?148e9381d225ecad703f4b858769b636ff7a2537:612
LocalCollection.prototype.update#http://localhost:3000/packages/minimongo.js?4ee0ab879b747ffce53b84d2eb80d456d2dcca6d:732
#http://localhost:3000/packages/mongo-livedata.js?cf17a2975aa7445f0db2377c2af07e5efc240958:730
.apply/ret<#http://localhost:3000/packages/livedata.js?7f11e3eaafcbe13d80ab0fb510d25d9595e78de2:3818
.withValue#http://localhost:3000/packages/meteor.js?148e9381d225ecad703f4b858769b636ff7a2537:794
.apply#http://localhost:3000/packages/livedata.js?7f11e3eaafcbe13d80ab0fb510d25d9595e78de2:3810
Meteor.Collection.prototype[name]#http://localhost:3000/packages/mongo-livedata.js?cf17a2975aa7445f0db2377c2af07e5efc240958:531
Template.map.rendered/<.added/<#http://localhost:3000/where-to-go.js?1b666a1c77f7d81e0212a2c65aa72a9d570b4dac:272
o.Mixin.Events.fireEvent#http://localhost:3000/packages/leaflet.js?ad7b569067d1f68c7403ea1c89a172b4cfd68d85:37
o.Handler.MarkerDrag<._onDragEnd#http://localhost:3000/packages/leaflet.js?ad7b569067d1f68c7403ea1c89a172b4cfd68d85:40
o.Mixin.Events.fireEvent#http://localhost:3000/packages/leaflet.js?ad7b569067d1f68c7403ea1c89a172b4cfd68d85:37
o.Draggable<._onUp#http://localhost:3000/packages/leaflet.js?ad7b569067d1f68c7403ea1c89a172b4cfd68d85:39
o.DomEvent.addListener/s#http://localhost:3000/packages/leaflet.js?ad7b569067d1f68c7403ea1c89a172b4cfd68d85:39
Ok, nearly got it. The thing is that the error is created by the MarkerCluster plugin. When I exclude it, it works with the following approach:
var newMarker;
Happening.find({}).observe({
added: function(marker) {
markerInit(marker);
markers[newMarker.options._id] = newMarker;
return map.addLayer(newMarker)
},
changed: function(marker){
map.removeLayer(markers[marker._id]);
markerInit(marker);
markers[newMarker.options._id] = newMarker;
map.addLayer(markers[marker._id]);
}
});
markerInit() sets up the markers like my code before does. I'm still not sure how to get it with MarkerCluster working.

Resources