set default map centering with props in vue - vue.js

I'm using vue to make an arc gis driven application, because the map's center can changed based on user location, I am setting the default location to some props then passing them into my map component to be called in the map components mounted() hook for rendering.
I think i'm pretty close to doing this correctly but when I log my props in my map component in the console it says they're undefined.
I'm not sure if my app code is at fault or my child component code?
as you can see in my app.vue I am even trying to pass in plain integers to test the values and they are still undefined.
app.vue
<template>
<div id="app">
<web-map v-bind:centerX="-118" v-bind:centerY="34" />
<div class="center">
<b-button class="btn-block" #click="getLocation" 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: -118,
long: 34
}
},
};
</script>
webmap.vue
<template>
<div></div>
</template>
<script>
import { loadModules } from 'esri-loader';
export default {
name: 'web-map',
props:['centerX, centerY'],
data: function(){
return{
X: this.centerX,
Y: this.centerY
}
},
mounted() {
console.log(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: [this.X,this.Y], ///USE PROPS HERE FOR NEW CENTER
zoom: 8
});
});
},
beforeDestroy() {
if (this.view) {
// destroy the map view
this.view.container = null;
}
}
};
</script>

There is a typo. Change:
props:['centerX, centerY'],
to:
props:['centerX', 'centerY'],

Related

How could I change an image in a child page when pressing a button in its parent page?

I have a DefaultLayout component with a dark mode toggle button which is its own component. One if its children (DefaultLayout's) is About.vue where I want a specific image to change its src depending on a localStorage value that can be set to either 'dark' or 'light'.
I've managed to read the localStorage value but the image does not change unless I refresh the page.
I'm new to Vue so I'm lost on how I can create a method to do this in DefaultLayout and change a variable in its child. I've tried to use an emit with no luck.
Could anyone point me in the right direction?
Yes, the local storage is for keeping data not propagate events.
The simplest way for you is to make a prop in child component and pass the value by this prop. But if you want to implement it as global variable the suggested way is by Pinia.
Below is a simple example
Vue.component('About', {
name: 'About',
template: `<div>
<div v-if="mode==='dark'">Dark</div>
<div v-else>Light</div>
</div>
`,
data() {
return {
mode: 'light',
};
},
mounted() {
this.setMode('white'); // In realtime use `this.getMode()` instead of 'white'
},
methods: {
setMode(val) {
this.mode = val;
},
getMode() {
return JSON.parse(localStorage.getItem('mode'));
}
}
});
var app = new Vue({
el: "#app",
template: `<div>
<input type="checkbox" v-model="toggler" #input="setVal" />
<About ref="about" />
</div>`,
data() {
return {
toggler: false,
};
},
methods: {
setVal() {
const mode = this.toggler === false ? 'dark' : 'light';
// localStorage.setItem('mode', mode); // In realtime uncomment this line
this.$refs.about.setMode(mode);
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
</div>

Vue 3: Wait until parent is done with data fetching to fetch child data and show loader

I'm looking for a reusable way to display a full page loader (Sidebar always visible but the loader should cover the content part of the page) till all necessary api fetches has been done.
I've got a parent component LaunchDetails wrapped in a PageLoader component
LaunchDetails.vue
<template>
<PageLoader>
<router-link :to="{ name: 'launches' }"> Back to launches </router-link>
<h1>{{ name }}</h1>
<section>
<TabMenu :links="menuLinks" />
</section>
<section>
<router-view />
</section>
</PageLoader>
</template>
<script>
import TabMenu from "#/components/general/TabMenu";
export default {
data() {
return {
menuLinks: [
{ to: { name: "launchOverview" }, display_name: "Overview" },
{ to: { name: "launchRocket" }, display_name: "Rocket" },
],
};
},
components: {
TabMenu,
},
created() {
this.$store.dispatch("launches/fetchLaunch", this.$route.params.launch_id);
},
computed: {
name() {
return this.$store.getters["launches/name"];
},
},
};
</script>
PageLoader.vue
<template>
<Spinner v-if="isLoading" full size="medium" />
<slot v-else></slot>
</template>
<script>
import Spinner from "#/components/general/Spinner.vue";
export default {
components: {
Spinner,
},
computed: {
isLoading() {
return this.$store.getters["loader/isLoading"];
},
},
};
</script>
The LaunchDetails template has another router-view. In these child pages new fetch requests are made based on data from the LaunchDetails requests.
RocketDetails.vue
<template>
<PageLoader>
<h2>Launch rocket details</h2>
<RocketCard v-if="rocket" :rocket="rocket" />
</PageLoader>
</template>
<script>
import LaunchService from "#/services/LaunchService";
import RocketCard from "#/components/rocket/RocketCard.vue";
export default {
components: {
RocketCard,
},
mounted() {
this.loadRocket();
},
data() {
return {
rocket: null,
};
},
methods: {
async loadRocket() {
const rocket_id = this.$store.getters["launches/getRocketId"];
if (rocket_id) {
const response = await LaunchService.getRocket(rocket_id);
this.rocket = response.data;
}
},
},
};
</script>
What I need is a way to fetch data in the parent component (LaunchDetails). If this data is stored in the vuex store, the child component (LaunchRocket) is getting the necessary store data and executes the fetch requests. While this is done I would like to have a full page loader or a full page loader while the parent component is loading and a loader containing the nested canvas.
At this point the vuex store is keeping track of an isLoading property, handled with axios interceptors.
All code is visible in this sandbox
(Note: In this example I could get the rocket_id from the url but this will not be the case in my project so I'm really looking for a way to get this data from the vuex store)
Im introduce your savior Suspense, this feature has been added in vue v3 but still is an experimental feature. Basically how its work you create one suspense in parent component and you can show a loading when all component in any depth of your application is resolved. Note that your components should be an async component means that it should either lazily loaded or made your setup function (composition api) an async function so it will return an async component, with this way you can fetch you data in child component and in parent show a fallback if necessary.
More info: https://vuejs.org/guide/built-ins/suspense.html#suspense
You could use Events:
var Child = Vue.component('child', {
data() {
return {
isLoading: true
}
},
template: `<div>
<span v-if="isLoading">Loading …</span>
<span v-else>Child</span>
</div>`,
created() {
this.$parent.$on('loaded', this.setLoaded);
},
methods: {
setLoaded() {
this.isLoading = false
}
}
});
var Parent = Vue.component('parent', {
components: { Child },
data() {
return {
isLoading: true
}
},
template: `<div>
Parent
<Child />
</div>`,
mounted() {
let request1 = new Promise((resolve, reject) => {
setTimeout(resolve, 1000);
});
let request2 = new Promise((resolve, reject) => {
setTimeout(resolve, 2000);
});
Promise.all([ request1, request2 ]).then(() => this.$emit('loaded'))
}
});
new Vue({
components: { Parent },
el: '#app',
template: `<Parent />`
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app"></div>
This may be considered an anti-pattern since it couples the parent with the child and events are considered to be sent the other way round. If you don't want to use events for that, a watched property works just fine, too. The non-parent-child event emitting was removed in Vue 3 but can be implemented using external libraries.

How to transfer data between components in Vue.js?

I'm trying to transfer data form my main vue js to a vue component. My project is built with laravel. I've app.js that mainly work for vue. And also have other components. I would like to transfer data between them.
Please check my code.
This is my app.js
require('./bootstrap');
import Vue from "vue";
import VueResource from 'vue-resource'
Vue.use(VueResource);
Vue.http.headers.common['X-CSRF-TOKEN'] = $('input[name="csrf-token"]').attr('value');
Vue.component('google-map', require('./components/GoogleMap.vue'));
const app = new Vue({
el: '#app',
data: {
lat: 0,
lng: 0,
center: { lat: 45.508, lng: -73.587 },
},
created(){
this.lat = 34.20;
this.lng = 100.36;
this.setCenter();
},
methods: {
setCenter(){
this.center.lat = parseFloat(this.lat);
this.center.lng = parseFloat(this.lng);
}
}
});
This is my GoogleMap.vue
<template>
<div>
</div>
</template>
<script>
export default {
name: "GoogleMap",
data() {
return {
center: { lat: 45.508, lng: -73.587 },
};
},
mounted() {
this.centerFun();
},
methods: {
centerFun(){
console.log(this.center);
}
}
};
</script>
This is my map.php
<html>
<head>
<title>Map</title>
</head>
<body>
<div id="app">
<label>Lat</label><input type="text" name="" v-model="lat" #keyup="testFun">
<label>Lng</label><input type="text" name="" v-model="lng" #keyup="testFun">
<google-map></google-map>
</div>
<script type="text/javascript" src="js/app.js"></script>
</body>
</html>
You can see there are two "center" var.
Can I bind those to a single one? Or what can I do?
If I've multiple components, how can I transfer data form app.js to those components or those components to app.js?
Update GoogleMap.vue to this:
<template>
<div>
</div>
</template>
<script>
export default {
name: "GoogleMap",
props: ['center'],
data() {
return {
// center: { lat: 45.508, lng: -73.587 },
};
},
mounted() {
// this.centerFun();
},
methods: {
//centerFun(){
// console.log(this.center);
//}
}
};
</script>
...so that it can accept the center prop from the parent component. And then send center from your parent component to googlemap component like this:
<google-map :center="center"></google-map>
As #AlxTheRed mentioned, more info about how props work can be found at:
https://v2.vuejs.org/v2/guide/components-props.html
You can pass values from parent component to child component via props.
Declare the variables you want from the parent component as props in child component.
In parent component, where you are including your child component:
<child-comp var1="abc" :var2="xyz"></child-comp>
In child component's instance, declare these as props:
props: ['var1','var2'],
Now, in child component, you can directly access them as:
this.var1
Read more here:
https://v2.vuejs.org/v2/guide/components.html
If your structure contains many dependencies, I suggest you to use vuex.

OpenLayer setCenter() doesn't work a second time

I am trying to reproduce a map app using openLayer API with VUE and this setCenter() function doesn't work on my second call of it. In the following snippet of code, the map would be initialized with a random centre of [60,60] and then when i input an number to update its Latitude it works ok, but when i try to change the centre again by repeating the step with a different input the map simply doesn't update the view.
In browser console, however,
window.map.getView().getCenter()
would give the latest coordinates that i want.
The reason why map is initialized in a setTimeout() fashion is how i imported openLayer using <script> tags in my index.html file. Therefore why must the map instance be bounded to window.
Here is the code:
<template>
<div id="map" class="map">
<input type="text" #keyup.enter="changeCenter" v-model="center">
</div>
</template>
<script>
export default {
name: "Olmap",
props: ["InitCenter"], //[60,60]
data(){
return{
center: ""
}
},
components:{
topInfoBar
},
methods:{
changeCenter: function(){
window.changeCenter([parseInt(this.center), 40])
}
},
created(){
setTimeout(()=>{
var map = new window.ol.Map({
target: 'map',
layers: [
new window.ol.layer.Tile({
source: new window.ol.source.OSM()
})
],
view: new window.ol.View({
center: window.ol.proj.fromLonLat(this.InitCenter),
zoom: 4
})
});
window.map = map;
},50);
var changeCenter = function(center){
window.map.getView().setCenter(center);
}
window.changeCenter = changeCenter;
},
}
</script>
Anyone can help me with this?

Vue: remove a component when other is completely loaded

<template>
<div id="app">
<Loading></Loading>
<Content></Content>
</div>
</template>
<script>
import Loading from './Loading.vue'
import Content from './Content.vue'
export default {
name: 'App',
components: {
Loading,
Content
}
}
</script>
What is the best and elegant way to handle a loading component and remove it (or vue component or change styles) when all page is loaded?
I tried with v-cloack, but I think its not working beyond data stuff.
I tried with mounted, but doesn't seems to work.
v-cloak is to hide un-compiled mustache bindings until the Vue instance is ready. So you can use v-if to show/hide loading component.
var child1 = Vue.extend({
template: "<div>Loading...</div>"
});
var child2 = Vue.extend({
template: "<div>After Component loaded</div>",
});
var app = new Vue({
el: "#vue-instance",
data: {
loading: true
},
mounted() {
var vm = this;
setTimeout(function() {
vm.loading = false;
}, 1000);
},
components: {
child1,
child2
},
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.1/vue.js"></script>
<div id="vue-instance">
<child1 :name="name" v-if="loading"></child1>
<child2 :name="name" v-if="!loading"></child2>
</div>