Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Vue2-leaflet map not showing properly in BoostrapVue modal

Here's my problem - a Vue2 leaflet map does not render correctly in BootstrapVue modal.

Here's what it looks like visually (it should show just the ocean)

enter image description here

<template>
  <div>
    <b-modal size="lg" :visible="visible" @hidden="$emit('clear')" title="Event details">
      <div class="foobar1">
        <l-map :center="center" :zoom="13" ref="mymap">
          <l-tile-layer :url="url" :attribution="attribution"></l-tile-layer>
          <l-marker :lat-lng="center"></l-marker>
        </l-map>
      </div>

      <template slot="modal-footer">
        <b-btn variant="danger" @click="deleteEventLocal(event.id)">Delete</b-btn>
      </template>
    </b-modal>
  </div>
</template>

<script>
import * as moment from "moment";
import { LMap, LMarker, LTileLayer } from "vue2-leaflet";
import { deleteEvent } from "./api";
import "vue-weather-widget/dist/css/vue-weather-widget.css";
import VueWeatherWidget from "vue-weather-widget";

export default {
  data() {
    return {
      center: L.latLng(event.latitude, event.longitude),
      url: "http://{s}.tile.osm.org/{z}/{x}/{y}.png",
      attribution:
        '&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
    };
  },
  props: {
    visible: {
      type: Boolean
    },
    event: {
      required: true,
      type: Object
    }
  },
  methods: {
    async deleteEventLocal(id) {
      await deleteEvent(id);
      this.$emit("refresh");
      this.$emit("clear");
    }
  },
  components: {
    weather: VueWeatherWidget,
    LMap,
    LMarker,
    LTileLayer
  }
};
</script>

As you can see there aren't any CSS rules that could make the map spill outside the modal as it does. Which is weird.

I'm kinda asking this question to answer it myself as I couldn't find a solution before.

like image 551
walnut_salami Avatar asked May 29 '19 15:05

walnut_salami


2 Answers

There were 3 issues because of which this was happening.

1. First - I forgot to load the leaflet css into main.js - this is why the leaflet map was somehow outside the modal.

//src/main.js
import '@babel/polyfill';
import Vue from 'vue';
import './plugins/bootstrap-vue';
import App from './App.vue';
import router from './router';
import store from './store';
//above imports not important to this answer

import 'leaflet/dist/leaflet.css'; //<--------------add this line

new Vue({
  router,
  store,
  render: h => h(App),
}).$mount('#app');

2. Now the map may disappear. Set a width and height on the l-map component's container. I used a class but you can use style="" etc.

<div class="foobar1"> <!-- <--- Add a class on l-map's container -->
  <l-map :center="center" :zoom="13">
    <l-tile-layer :url="url" :attribution="attribution"></l-tile-layer>
    <l-marker :lat-lng="center"></l-marker>
  </l-map>
</div>
<style lang="scss">
  .foobar1 { /* <--- class we added above */
    width: 100%;
    height: 400px;
  }
</style>

3. Now your map will render within the modal but if you move the map's view, you'll see that leaflet does not download the map's squares in time. You will see something like this:

modal after fixing css but without invalidateSize() fix

To fix this:

  • create an event handler on b-modal for the @shown event.

     <b-modal
       @shown="modalShown"
    
    
       @hidden="$emit('clear')"
       size="lg"
       :visible="visible"
       title="Event details"
     >
    

    I called mine modalShown.

  • Then, add a ref attribute to your l-map. I called mine mymap.

    <l-map :center="center" :zoom="13" ref="mymap"> <!-- ref attribute added to l-map -->
      <l-tile-layer :url="url" :attribution="attribution"></l-tile-layer>
      <l-marker :lat-lng="center"></l-marker>
    </l-map>
    
  • Then, create a modalShown method in the Vue methods for your view/component and call invalidateSize() inside.

    export default {
      data() {
       //some data here
      }
    
      methods: {
         modalShown() {
          setTimeout(() => {
            //mapObject is a property that is part of leaflet
            this.$refs.mymap.mapObject.invalidateSize(); 
          }, 100);
        }
      }
    }
    

Now everything should be fine:

  • map should not spill outside the modal
  • map should be visible (duh)
  • map squares should be downloaded when within map body

Here's my full code, it contains some stuff specific to my app but overall it contains all of the code snippets above.

like image 180
walnut_salami Avatar answered Sep 28 '22 07:09

walnut_salami


Addtional to Artur Tagisow answer

You can also use this approach to your parent component if your map is in child component.

export default {
  data() {
   //some data here
  }

  methods: {
     modalShown() {
      setTimeout(() => {
        window.dispatchEvent(new Event("resize"));
      }, 100);
    }
  }
}
like image 34
JMilanes Avatar answered Sep 28 '22 06:09

JMilanes