Vue.js: Target other div than the bound one within vue object - javascript

I'm using Vue and Leaflet for displaying polygons (zones) on a map and display appropriate information (messages) about the specific polygons after clicking on them on the map. The div, where I render the messages in, has the id "#messagearea" and is bound to the "el" object. To display the appropriate messages, I am dependant on the "Zone-id".
Now I also want to display information into another div with a different id. I am also dependant on the "Zone-id" here, so I would like to do this in the same Vue. If I would create another Vue, I would have to render the Leaflet map again to write another polygon.on('click',...) function, which displays appropriate information for the polygons. What is the most elegant and/or easiest way to realize this?
Here my vue object:
var mapVue = new Vue({
el: '#messagearea',
data: {
function() {
return {
map: false
};
},
zones: [],
messages: [],
},
ready: function () {
this.map = L.map('map').setView([51.959, 7.623], 14);
L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', {
attribution: '© OpenStreetMap contributors'
}).addTo(this.map);
this.$http.get('/api/zones', function (data) {
this.$set('zones', data);
for (var i = 0; i < this.zones['Zones'].length; i++) {
polygon = L.polygon(
this.zones['Zones'][i]['Geometry']['Coordinates']).addTo(this.map);
polygon.bindPopup(this.zones['Zones'][i]['Name']);
polygon.on('click', messageCallback(i))
// HERE I WOULD LIKE TO ADD THE FUNCTION FOR THE OTHER DIV
}
function messageCallback(i) {
return function () {
mapVue.getMessages(mapVue.zones['Zones'][i]['Zone-id']);
}
}
});
},
methods:
{
getMessages: function (id) {
this.$http.get('/api/messages?zone=' + id, function (data) {
console.log("messages called");
this.$set('messages', data['Messages']);
});
}
}
})

I solved this issue by making use of Vue.component(), the vue.$dispatch() and vue.$broadcast() functions. I just dispatched the zone id to a parent component and then delivered it with the broadcast function to all child components, which are in need of the zone id. Displaying the appropriate messages was no problem then anymore.

Related

Map layers do not render again past initial load

I am using VueMapbox (0.4.1) to utilize Mapbox GL in a Vue project.
<template>
<MglMap
:accessToken="accessToken"
:mapStyle.sync="mapStyle"
:repaint="true"
#load="onMapLoaded">
<MglMarker
:coordinates="someCoordinates"
class="map-marker-wrapper">
<div
slot="marker"
class="map-marker">
</div>
</MglMarker>
</MglMap>
</template>
<script>
import Mapbox from 'mapbox-gl'
import { MglMap, MglMarker } from 'vue-mapbox'
import * as MAP from '#/constants/map'
// Vue-Mapbox documentation: https://soal.github.io/vue-mapbox/guide/basemap.html#adding-map-component
export default {
name: 'Map',
components: {
MglMap,
MglMarker
},
props: {
someCoordinates: {
type: Array,
required: true
},
darkMode: {
type: Boolean,
required: true
}
},
data () {
return {
accessToken: MAP.ACCESS_TOKEN,
mapbox: null,
map: null,
actionsDispatcher: null
}
},
computed: {
mapStyle () {
return this.darkMode ? MAP.COLOR_PROFILES.DARK : MAP.COLOR_PROFILES.LIGHT
}
},
created () {
this.mapbox = Mapbox
},
methods: {
async onMapLoaded (event) {
this.map = event.map
this.actionsDispatcher = event.component.actions
await this.actionsDispatcher.flyTo({
center: this.someCoordinates
})
}
}
}
</script>
On the first load, everything works as expected:
But if I move to a different pseudo-route (say from /#/Map to /#/Profile and back), some of the map layers specified by my mapStyle (roads, city names,..) are not rendered anymore (default layers instead). The map also stops honoring any change of the mapStyle url, even when I specify mapStyle.sync in my template.
If I hit the browser's reload button it loads the layers as expected as the entire app is reloaded from scratch, but I unfortunately cannot afford to do this by default.
Any ideas are greatly appreciated.
I found a solution, in the example of vue-mapbox, the variable map (this.map) is set by "event.map" which causes an error because the card is not refreshed afterwards.
In my case i just remove that (this.map = event.map) in my onMapLoaded function and this is great.
Have a good day.
Despite I don’t know the syntax of Vue.js, the problem you are facing is that you are creating your layers in map.on('load', ... which is an event that happens only once, so when the style change happens, all the layers of the map style (including the ones created by custom code) are removed.
If you want to recreate your layers on style change, you have to do it in the event map.on('style.load', ..., but as said, I don’t see in your vue.js code where that is being done. If you share the part of the code where vue.js is invoking the methods it’ll be easier to help you

view.goTo map renders blank arcgis

I am working on an arcgis map, I'm trying to update the map center by calling goTo() on my mapview but for some reason the map just changes to be blank and never updates, I am logging the new coordinates and they are correct.
I am using the reference docs here: https://developers.arcgis.com/javascript/latest/api-reference/esri-views-MapView.html
Can someone with some arcgis experience help me out. I know this isn't an issue with my code specifically but it might be an issue with vue and component rendering as it relates to arcgis
so far I have tried
- getting rid of props and updating everything within the component locally
- using keys to force re-render the component
as an interesting note, if I just enter in some magic numbers for my new location the map updates correctly, however when i use some function to get the location and then pass it in, it does not work and just shows as a blank map
my app.vue
<template>
<div id="app">
<web-map v-bind:centerX="lat" v-bind:centerY="long" ref="map"/>
<div class="center">
<b-button class="btn-block" #click="updateCenter()" variant="primary">My Location</b-button>
</div>
</div>
</template>
<script>
import WebMap from './components/webmap.vue';
export default {
name: 'App',
components: { WebMap },
data(){
return{
lat: null,
long: null,
}
},
methods:{
updateCenter(){
this.$refs.map.getLocation()
}
},
};
</script>
my map component
<template>
<div></div>
</template>
<script>
import { loadModules } from 'esri-loader';
export default {
name: 'web-map',
data: function(){
return{
X: -118,
Y: 34,
}
},
mounted() {
console.log('new data',this.X,this.Y)
// lazy load the required ArcGIS API for JavaScript modules and CSS
loadModules(['esri/Map', 'esri/views/MapView'], { css: true })
.then(([ArcGISMap, MapView]) => {
const map = new ArcGISMap({
basemap: 'topo-vector'
});
this.view = new MapView({
container: this.$el,
map: map,
center: [-118,34], ///USE PROPS HERE FOR NEW CENTER
zoom: 8
});
});
},
beforeDestroy() {
if (this.view) {
// destroy the map view
this.view.container = null;
}
},
methods:{
showPos(pos){
console.log('new location',pos.coords.latitude,pos.coords.longitude)
this.view.goTo({center:[pos.coords.latitude,pos.coords.longitude]})
},
getLocation(){
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(this.showPos);
} else {
console.log("Geolocation is not supported by this browser.");
}
},
}
};
</script>
Switch:
this.view = new MapView({
container: this.$el,
map: map,
center: [-118,34], ///USE PROPS HERE FOR NEW CENTER
zoom: 8
});
to
this.view = new MapView({
container: this.$el,
map: map });
this.view.center.longitude = -118;
this.view.center.latitude = 34;
this.view.zoom = 8;
The other answer by Tao has the long/latitude backwards in the .goTo({center: []}) method call, which is why it goes to the ocean: https://developers.arcgis.com/javascript/latest/api-reference/esri-views-MapView.html#goTo
Here's something that works:
https://codesandbox.io/s/frosty-glitter-39wpe?file=/src/App.vue
I made it from scratch, only taking small bits from yours and combining them with some examples from ArcGIS (which I'm not familiar with, at all).
One thing to note is that the .goTo({center: [lat, long]}) didn't work as expected: it kept centering in the middle of some ocean.
I then imported Point from esri and passed the center as new Point(long, lat), which seems to produce the expected result. Since it works, I haven't looked further, but I guess it should be doable without the conversion. You probably need to pass in the coordinates system or something along these lines.
As far as I can tell, what's wrong in your example is the way you try to pass data down from parent to child. You expect this.$refs.map to be a Vue instance, but it's not. It's a DOM element. It's basically the Vue instance's $el. Accessing child methods from parent instance is not so straight forward.
Another thing to notice is that, even though you bind centerX and centerY on child in your example, you never seem to use them (but I guess that's just a left over from when you tried with props !?).
Anyways, in my example, I chose to simply update the coords prop of the children while having a watch fn to handle re-centering.

How to obtain this ref to declaring class from Dojo gridx detailProvider (Dod module)

Using the Dojo framework.
I have a 2 nested grid. Grid 1 uses a Dod with a detailprovider to load details on demand when one clicks a expand icon. When pressed this opens a nested grid. I need to track changes made in both grids. In the declaring class I've got an array that keeps track on changes made. The problem is that I can't access the array from a detailprovider. Since I've to conform to the protocol which gridx later invokes. What can I do to obtain a ref to the declaring class
var myDeclaringClass = declare([_WidgetBase,_TemplatedMixin,_WidgetsInTemplateMixin], {
array: [],
initGrid: function(){
var grid = new Grid({
store: store,
structure: columns,
modules: [ {
moduleClass: Dod,
showExpando: true,
detailProvider: this.myDetailProvider
}]
});
// .... grid.placeAt() .. grid.startup()
},
myDetailProvider: function(parentGrid, rowId, detailNode, rendered) {
// construct Nested Grid ...
// How to obtain this reference here?
// to access this.array?
rendered.callback();
return rendered;
}
retrun myDeclaring;
}
EDIT:
I have also tried with a static var like:
statics: { array: [] }
But here I will stille need a instance ref to access it.
Try something like this
var myDeclaringClass = declare([_WidgetBase,_TemplatedMixin,_WidgetsInTemplateMixin], {
array: [],
var globalref : this,//here i'm assigning 'this' i.e. class level ref to the variable
initGrid: function(){
var grid = new Grid({
store: store,
structure: columns,
modules: [ {
moduleClass: Dod,
showExpando: true,
detailProvider: this.myDetailProvider
}]
});
// .... grid.placeAt() .. grid.startup()
},
myDetailProvider: function(parentGrid, rowId, detailNode, rendered) {
// construct Nested Grid ...
// How to obtain this reference here?
// to access this.array?
globalref.array//should give you access to the array
rendered.callback();
return rendered;
}
retrun myDeclaring;
}

rendering vue.js components and passing in data

I'm having trouble figuring out how to render a parent component, display a list of contracts in a list on part of the page, and when a user clicks on one of them, display the details of that specific contract on the other part of the page.
Here is my slim file:
#contracts_area
.filter-section
ul
li.filter-item v-for="contract in contractsAry" :key="contract.id" #click="showContract(contract)"
| {{ contract.name }}
.display-section
component :is="currentView" transition="fade" transition-mode="out-in"
script type="text/x-template" id="manage-contracts-template"
div
h1 Blank when page is newly loaded for now
script type="text/x-template" id="view-contract-template"
div :apply_contract="showContract"
h1#display-item__name v-name="name"
javascript:
Vue.component('manage-template', {
template: '#manage-contracts-template'
});
Vue.component('view-contract', {
template: '#view-contract-template',
props: ['show_contract'],
data: function() {
return {
name: ''
}
},
methods: {
showContract: function(contract) {
return this.name = contract.name
}
}
});
Vue.http.headers.common['X-CSRF-Token'] = $('meta[name="csrf-token"]').attr('content');
var contractsResource = Vue.resource('/all_contracts{/id}.json');
var contracts = new Vue({
el: '#contracts_area',
data: {
currentView: 'manage-template',
contractsAry: [],
errors: {}
},
mounted: function() {
var that = this;
contractsResource.get().then(
function(res) {
that.contractsAry = res.data;
}
)
},
methods: {
showContract: function(contract) {
this.currentView = 'view-contract'
}
}
});
Basically I'd like it so that when a user clicks on any contract item in the .filter-section, it shows the data for that contract in the .display-section. How can I achieve this?
In short you can bind a value to a prop.
.display-section
component :is="currentView" :contract="currentContract"
view-contract
props: ['contract']
contracts-area
data: {
currentContract: null,
},
methods: {
showContract: function(contract) {
this.currentView = "view-contract";
this.currentContract = contract;
}
}
There are multiple ways to pass data in Vue.
Binding values to props.
Using ref to directly call a method from a child component.
Custom Events. Note that to pass events globally, you will need a global event bus.
A single central source of truth (i.e. vuex)
I have illustrated methods 1, 2, 3 in Codepen
Note that 2nd and 3rd methods will only work after your component has been rendered. In your case, since your components for currentView are dynamic and when user clicked, display-section component does not yet exists; it will not receive any events yet. So their content will be empty at first.
To workaround this you can directly access $parent in mounted() from child component, however this would create coupling between them. Another solution is creating the components but conditionally displaying them. And one another solution would be waiting until child component has been mounted and then emitting events.
If your needs are simple I suggest binding values to props (1), else you may consider using something like vuex.

ember-leaflet: how to refresh component data?

I'm using ember-leaflet to display Leaflet maps in my Ember application. In my case, the map is used to display an appointment location. The user selects an appointment from a list and the corresponding appointment details (including the map) are then displayed.
When an appointment is selected, the details are passed to a component, which contains the map. Unfortunately, after the user selects an appointment and then another is selected, the coordinates on the map do not change. However, outputting the coordinates using Handlebars, I can in fact see that the different coordinates are being passed. That leads me to believe that the map needs to be "refreshed" somehow in order for the new coordinates to be displayed on the map.
I use the component like so: {{ember-leaflet geoJSON=geoJSON}} where geoJSON is a string containing the location data.
My component is as follows:
// components/leaflet-map.js
import Ember from 'ember';
import ENV from '../config/environment';
import EmberLeafletComponent from 'ember-leaflet/components/leaflet-map';
import MarkerCollectionLayer from 'ember-leaflet/layers/marker-collection';
import TileLayer from 'ember-leaflet/layers/tile';
L.Icon.Default.imagePath = '/images/leaflet';
export default EmberLeafletComponent.extend({
center: Ember.computed(function() {
return this.get('coordinates');
}),
/////////////////////////////////////
// PROPERTIES
/////////////////////////////////////
geoJSON: null,
/////////////////////////////////////
// COMPUTED PROPERTIES
/////////////////////////////////////
childLayers: Ember.computed('coordinates', function() {
return [
TileLayer.extend({
tileUrl: 'https://api.tiles.mapbox.com/v4/{id}/{z}/{x}/{y}.png?access_token={accessToken}',
options: {
id: 'XXXXXXX',
accessToken: ENV.APP.MAPBOX_KEY
}
}),
MarkerCollectionLayer.extend({
content: [{ location: this.get('coordinates') }]
})
];
}),
coordinates: Ember.computed('geoJSON', function() {
if (this.get('geoJSON')) {
const coordinates = JSON.parse(this.get('geoJSON')).coordinates;
if (coordinates) {
return L.latLng(coordinates[1], coordinates[0]);
}
}
return null;
}),
});
When setting a breakpoint, it appears that childLayers and coordinates are only being called once regardless of which appointment is selected by the user. I've considered setting up an observer to observe the geoJSON property, but it seems overkill.
Any help would be greatly appreciated!

Categories

Resources