Markers are not shown on leaflet map - vue.js

I am working on a task in which I have to show coordinates from an API.
Currently i am successfully able to fetch the data. But when I show it on the Map, Markers are not visible.
When i look in console the following errors appears:-
Uncaught (in promise) TypeError: Cannot read property 'data' of undefined at eval
I have individually tried to pin point the source of error by putting console.log("done") at regular intervals and the exception occurs after the following line in my code:
this.map.addLayer(this.markers);
I have marked this line using comment in my code.
here's what i put in my html
<div id="mapApp">
<div class="container-fluid" style="padding: 0px; position:
relative; min-height: 90vh">
<div id="map" class="map"></div>
</div>
</div>
here's my Full JavaScript File
import Vue from 'vue'
import axios from 'axios'
import L from 'leaflet'
import googleMutant from 'leaflet.gridlayer.googlemutant'
import markercluster from 'leaflet.markercluster'
const baseUrl = 'http://127.0.0.1:8000/';
const vm = new Vue({
el: '#mapApp',
data: {
map: null,
tileLayer: null,
layers: [],
properties: [],
panState: 'closed',
foodIconUnselected: null,
foodIconSelected:null,
realEstateIconUnselected:null,
realEstateIconSelected:null,
zoomlevel:8,
firstLatitude:null,
firstLongitude:null,
roadMutant:null,
markers:null,
allPropertyLayer:null,
mcdPropertyLayer:null,
subwayPropertyLayer:null,
burgerkingPropertyLayer:null,
chickeriaPropertyLayer:null,
starbucksPropertyLayer:null,
kfcPropertyLayer:null,
spettacoloPropertyLayer:null,
holycowPropertyLayer:null,
query:'',
iconLabel:null,
labelName:null,
},
created(){
// execute the method to fetch propertis
let searchParams = new URLSearchParams(window.location.search);
let search_term_value = searchParams.get("search_term")
this.fetchPropertyData(search_term_value);
},
methods: {
initMap() {
this.$data.map = L.map('map').setView([this.$data.firstLatitude, this.$data.firstLongitude], this.$data.zoomlevel);
this.$data.roadMutant = L.gridLayer.googleMutant({
maxZoom: 24,
type: 'roadmap',
styles: /*removed on purpose to reduce size */,
}).addTo(this.$data.map);
L.control.zoom({
position:'topright'
}).addTo(this.$data.map);
this.$data.markers = L.markerClusterGroup({
iconCreateFunction: function (cluster) {
var markers = cluster.getAllChildMarkers();
var html = '<div class="marker-cluster">' + markers.length + '</div>';
return L.divIcon({html: html, className: 'mycluster', iconSize: L.point(40, 40)});
},
spiderfyOnMaxZoom: false, showCoverageOnHover: false, zoomToBoundsOnClick: true,
});
this.$data.allPropertyLayer = new L.FeatureGroup();
this.$data.mcdPropertyLayer = new L.FeatureGroup();
this.$data.subwayPropertyLayer = new L.FeatureGroup();
this.$data.burgerkingPropertyLayer = new L.FeatureGroup();
this.$data.chickeriaPropertyLayer = new L.FeatureGroup();
this.$data.starbucksPropertyLayer = new L.FeatureGroup();
this.$data.kfcPropertyLayer = new L.FeatureGroup();
this.$data.spettacoloPropertyLayer = new L.FeatureGroup();
this.$data.holycowPropertyLayer = new L.FeatureGroup();
console.log("Done Till Here");
this.fillDataInMap();
},
fillFirstData(){
this.$data.firstLatitude = this.$data.properties[0].latitude;
this.$data.firstLongitude = this.$data.properties[0].longitude;
},
fetchPropertyData(search_term_value){
const self = this;
axios.get(baseUrl + 'api/search/property', {
params:{
'search_term': search_term_value
}
}).then(function (response) {
self.$data.properties = response.data.search_results.data;
self.fillFirstData();
self.initMap();
}).catch(function (error) {
alert(error.response.data);
console.log(error);
})
},
fillDataInMap(){
for(let i =0;i<this.$data.properties.length;i++){
this.$data.iconLabel = this.getPropertyIconLabelUnSelected(this.$data.properties[i]);
this.$data.labelName = this.getPropertyLabelName(this.$data.properties[i]);
let marker = L.marker(new L.LatLng(this.$data.properties[i].latitude, this.$data.properties[i].longitude), {icon: this.$data.iconlabel, id: i+1});
marker.bindTooltip(this.$data.labelName, {permanent: true, direction: 'auto', className: 'map-icon-label',});
marker.bindPopup(this.$data.properties[i].address);
marker.title = this.$data.properties[i].id;
marker.on('click', function (e) {
//showDetails(e, e.target.title);
//changeMarkerIconOnClick(e);
});
marker.on('mouseover',function (e) {
e.target.openPopup();
});
marker.on('mouseout',function (e) {
e.target.closePopup();
});
this.$data.allPropertyLayer.addLayer(marker);
this.addMarkerToRespectiveLayer(this.$data.properties[i].property_sub_type,marker);
}
this.$data.markers.addLayer(this.$data.allPropertyLayer);
Next line throws the exception
this.map.addLayer(this.markers);
},
searchWithQuery(){
this.fetchPropertyData(this.$data.query)
},
showPropertyDetails(id) {
alert('property id is: ' + id)
},
}
});

I found the reason why it was causing exception. I declared variable for custom markers but somehow forgot to initialise them. After removing the Custom markers, markers show on the map but then i am unable to interact with map. It remains static like an image.

Related

VueJS Leaflet 'moveend' fires multiple times

Ask for help from the community. For two weeks I can not overcome the problem with repeated firing of 'mooveend' in the project. I have tried all the advice given here. Here's what I've read and researched already, but it didn't work for me.
This is one of the tips:
moveend event fired many times when page is load with Leaflet
<template>
<div id="map"></div>
</template>
<script>
export default {
name: "ObjectMapView",
props: ['coordinate'],
data: function () {
return {
map: null,
addressPoints: null,
markers: null,
}
},
mounted: function() {
this.initializedMap();
},
watch: {
coordinate: function (val) {
this.run();
}
},
methods: {
initializedMap: function () {
this.map = L.map('map').setView([52.5073390000,5.4742833000], 13);
L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', {
attribution: '© OpenStreetMap contributors'
}).addTo(this.map);
this.markers = L.markerClusterGroup();
},
run: function () {
var map = this.map;
var markers = this.markers;
var getAllObjects = this.coordinate;
var getBoundsMarkers;
//Clearing Layers When Switching a Filter
markers.clearLayers();
this.addressPoints = getAllObjects.map(function (latlng){
return [latlng.latitude, latlng.longitude, latlng.zip, latlng.object_id, latlng.archived];
});
map.addLayer(markers);
//We give to the map only those coordinates that are in the zone of visibility of the map during the first
getBoundsMarkers = getAllObjects.filter((coord) => {
if(!coord.latitude && !coord.longitude){
return false;
}
return map.getBounds().contains(L.latLng(coord.latitude, coord.longitude));
});
/*
Responds to changing the boundaries of the map visibility zone and
transmits a list of coordinates that are in the visibility zone
*/
console.log('getAllObjects_1', getAllObjects);
map.on('moveend', function() {
console.log('moveend');
console.log('getAllObjects_2', getAllObjects);
getBoundsMarkers = getAllObjects.filter((coord) => {
if(!coord.latitude && !coord.longitude){
return false;
}
return map.getBounds().contains(L.latLng(coord.latitude, coord.longitude));
});
eventHub.$emit('sendMarkers', getBoundsMarkers);
});
// In the loop, we iterate over the coordinates and give them to the map
for (var i = 0; i < this.addressPoints.length; i++) {
var a = this.addressPoints[i];
var title = '' + a[2] + ''; //bubble
var marker = L.marker(new L.LatLng(a[0], a[1]), {
title: title
});
marker.bindPopup(title);
markers.addLayer(marker);
}
eventHub.$emit('sendMarkers', getBoundsMarkers);
}
}
}
</script>
<style scoped>
#map {
width: 97%;
height: 100%;
}
</style>
I figured it out myself.
The 'zoomend' and 'dragend' option didn't work for me. I searched a lot for a suitable option and realized that the "moveend" event fires several times because this event is created every time you move the map. Therefore it is necessary to stop this event. I got out of the situation in this way. Immediately after the map was initialized, I wrote:
map.off('moveend');
and for me it worked. Now it works fine. I will be very happy if this is useful to someone.

threeJS Nuxt, import syntax in head to resolve error (cannot use import statement outside a module, missing stack frame)

I am trying to use ThreeJS in my NuxtJS app. I am trying to resolve an error while deploying to local host.
The issue is with the following two lines:
import * as THREE from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
which throws a syntax error "cannot use import statement outside a module". "node:vm - Missing stack frames".
The full code in the script tag is attached below.
The problem will likely resolve if I adapt the code into the head section of export default, but I cannot find the appropriate syntax for this.
<script>
import * as THREE from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
export default {
data() {
return {
camera: new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000),
scene: new THREE.Scene(),
renderer: new THREE.WebGLRenderer({
canvas: document.querySelector('#canvas')
}),
mesh: null
}
},
head() {
return {
script: [
{
async: true,
type: 'module',
src: 'https://unpkg.com/es-module-shims#1.3.6/dist/es-module-shims.js'
},
{
type: 'module',
imports: {
type: 'module',
three: 'https://unpkg.com/three#<version>/build/three.module.js'
}
},
]
}
},
mounted() {
this.init;
},
methods: {
init() {
this.renderer.setPixelRatio(window.devicePixelRatio);
this.renderer.setSize(window.innerWidth, window.innerHeight);
this.camera.position.setZ(30);
this.renderer.render(this.scene, this.camera);
// add shapes//geometry //materials //mesh
const geometry = new THREE.PlaneGeometry(10, 3, 16, 100)
const material = new THREE.MeshBasicMaterial({ color: 0xFF6347, wireframe: true });
const plane = new THREE.Mesh(geometry, material);
const controls = new OrbitControls(this.camera, this.renderer.domElement);
// renderer.render( scene, camera);
// render
this.scene.add(plane)
// lighting
const pointLight = new THREE.PointLight(0xFFFFFF)
pointLight.position.set(5, 5, 5)
// pointLight.position.set(20,20,20)
const ambientLight = new THREE.AmbientLight(0xFFFFFF);
this.scene.add(pointLight, ambientLight)
const lightHelper = new THREE.PointLightHelper(pointLight)
const gridHelper = new THREE.GridHelper(200, 50);
this.scene.add(lightHelper, gridHelper)
// animate
function animate() {
requestAnimationFrame(animate);
plane.rotation.x += 0.01;
plane.rotation.y += 0.005;
plane.rotation.z += 0.01;
controls.update();
this.renderer.render(this.scene, this.camera);
}
animate()
}
},
}
</script>

ThreeJS component working in VueJS 2 but not 3

I'm upgrading my app to VueJS 3. I read that you could keep the same components. But I have an error in the console now, although I didn't change anything. Here is my component:
<template>
<v-container>
<div
#click="onClick"
#mousemove="onMouseMove"
id="menu3D"
style="background-color: transparent; position: fixed; left: 20px; width:15%; height:100%;">
</div>
<v-row class="text-center">
<v-col
class="mb-5"
cols="12"
>
<h2 class="headline font-weight-bold mb-3">
Accueil
</h2>
<v-row justify="center">
<p>
Client: {{ JSON.stringify(client)}}
</p>
<p>
Mouse: {{ JSON.stringify(mouse)}}
</p>
<p>
Container: {{ JSON.stringify(container)}}
</p>
</v-row>
</v-col>
</v-row>
</v-container>
</template>
<script>
import * as Three from 'three';
export default {
name: 'Accueil',
mounted() {
this.init();
},
methods: {
init() {
this.createScene();
this.createCamera();
this.userData.formes.forEach((x) => this.createShape(x));
this.addSpotlight(16777215);
this.addAmbientLight();
this.animate();
window.addEventListener('resize', this.onResize);
},
onResize() {
const container = document.getElementById('menu3D');
this.renderer.setSize(container.clientWidth, container.clientHeight);
this.camera.aspect = container.clientWidth / container.clientHeight;
this.camera.updateProjectionMatrix();
},
createScene() {
this.renderer = new Three.WebGLRenderer({
antialias: true,
alpha: true,
});
const container = document.getElementById('menu3D');
this.renderer.setSize(container.clientWidth, container.clientHeight);
this.renderer.setPixelRatio(window.devicePixelRatio);
this.renderer.setClearColor(0xffffff, 0);
container.appendChild(this.renderer.domElement);
},
createCamera() {
const container = document.getElementById('menu3D');
this.camera = new Three.PerspectiveCamera(50,
container.clientWidth / container.clientHeight, 0.01, 1000);
this.camera.position.set(0, 5, 20);
this.camera.zoom = 1;
},
createShape(shape) {
const material = new Three.MeshStandardMaterial({
color: '#0000ff',
roughness: 1,
metalness: 0.5,
emissive: 0,
depthFunc: 3,
depthTest: true,
depthWrite: true,
stencilWrite: false,
stencilWriteMask: 255,
stencilFunc: 519,
stencilRef: 0,
stencilFuncMask: 255,
stencilFail: 7680,
stencilZFail: 7680,
stencilZPass: 7680,
});
switch (shape.nom) {
case 'Box': {
this.geometry = new Three.BoxBufferGeometry(1.8, 1.8, 1.8);
break;
}
case 'Sphere': {
this.geometry = new Three.SphereBufferGeometry(1, 8, 6, 0, 6.283185, 0, 3.141593);
break;
}
case 'Dodecahedron': {
this.geometry = new Three.DodecahedronBufferGeometry(1.2, 0);
break;
}
case 'Icosahedron': {
this.geometry = new Three.IcosahedronBufferGeometry(1.5, 0);
break;
}
default: {
return false;
}
}
this.mesh = new Three.Mesh(this.geometry, material);
this.mesh.name = shape.nom;
this.mesh.userData = shape.userData;
this.mesh.receiveShadow = true;
this.mesh.castShadow = true;
this.mesh.position.set(0, shape.userData.position.y, 0);
this.scene.add(this.mesh);
return true;
},
addSpotlight(color) {
const light = new Three.SpotLight(color, 2, 1000);
light.position.set(0, 0, 30);
this.scene.add(light);
},
addAmbientLight() {
const light = new Three.AmbientLight('#fff', 0.5);
this.scene.add(light);
},
verifForme(e) {
const t = this;
const elt = t.scene.getObjectByName(e);
t.intersects = t.raycaster.intersectObject(elt);
if (t.intersects.length !== 0) {
// s'il ne figure pas dans le tableau, on le met en premier
if (t.userData.souris.indexOf(e) < 0) {
t.userData.souris.unshift(e);
console.log(`${t.userData.souris[0]} survolé!`);
}
if (t.userData.souris[0] === e) {
const obj = t.intersects[0].object;
obj.material.color.set(`#${elt.userData.couleurs[1]}`);
obj.scale.set(obj.scale.x < 1.4
? obj.scale.x + t.VITESSE_ZOOM
: obj.scale.x, obj.scale.y < 1.4
? obj.scale.y + t.VITESSE_ZOOM
: obj.scale.y, obj.scale.z < 1.4
? obj.scale.z + t.VITESSE_ZOOM
: obj.scale.z);
obj.rotation.y += t.VITESSE_ROTATION / t.RALENTISSEMENT;
t.replacer(obj, obj.userData.position.y + obj.userData.decalage);
} else {
t.retrecir(e, elt);
}
} else {
if (t.userData.souris.indexOf(e) >= 0) {
t.userData.souris = t.userData.souris.filter((forme) => forme !== e);
}
t.retrecir(e, elt);
}
},
onClick(event) {
event.preventDefault();
if (this.userData.souris.length > 0) {
console.log(`${this.userData.souris[0]} cliqué!`);
} else {
console.log('clic dans le vide!');
}
},
onMouseMove(event) {
const container = document.getElementById('menu3D');
this.mouse.x = (event.offsetX / container.clientWidth) * 2 - 1;
this.mouse.y = -(event.offsetY / container.clientHeight) * 2 + 1;
this.client.clientX = event.clientX;
this.client.clientY = event.clientY;
this.container.width = container.clientWidth;
this.container.height = container.clientHeight;
// console.log(JSON.stringify(this.mouse))
},
replacer(e, py) {
// la ligne suivante est pour éviter les tremblements
if (Math.abs(e.position.y - py) < 0.05) { return true; }
let rhesus = 10 * this.VITESSE_DEPLACEMENT;
if (this.userData.souris[0] !== e.name) { rhesus *= 3; }
// console.log(e.name+': '+this.userData.souris[0]+' - '+rhesus)
if (e.position.y > py) { rhesus = -1; }
e.position.set(0, Math.trunc(10 * e.position.y + rhesus) / 10, 0);
return true;
},
retrecir(n, e) {
// on vérifie si le truc cliqué est dessus
let dec = 0;
const elt = this;
if ((elt.userData.souris.length > 0)
&& (elt.userData.formes.map((x) => x.nom).indexOf(n)
< elt.userData.formes.map((x) => x.nom).indexOf(elt.userData.souris[0]))) {
dec = Math.trunc(10
* e.parent.getObjectByName(elt.userData.souris[0]).userData.decalage
* 2.1) / 10;
}
e.material.color.set(`#${e.userData.couleurs[0]}`);
e.rotation.y += elt.VITESSE_ROTATION;
e.scale.set(e.scale.x > 1
? e.scale.x - elt.VITESSE_ZOOM : e.scale.x,
e.scale.y > 1
? e.scale.y - elt.VITESSE_ZOOM : e.scale.y,
e.scale.z > 1
? e.scale.z - elt.VITESSE_ZOOM : e.scale.z);
const newY = e.userData.position.y + dec;
if (e.position.y !== newY) {
elt.replacer(e, newY);
}
},
animate() {
const elt = this;
requestAnimationFrame(this.animate);
this.raycaster.setFromCamera(this.mouse, this.camera);
this.userData.formes.map((x) => x.nom).forEach((x) => elt.verifForme(x));
if (this.userData.souris.length > 0) {
document.body.style.cursor = 'pointer';
} else { document.body.style.cursor = 'default'; }
this.camera.updateProjectionMatrix();
this.renderer.render(this.scene, this.camera);
},
},
data: () => ({
container: { height: 0, width: 0 },
client: { clientX: 0, clientY: 0 },
scene: new Three.Scene(),
camera: null,
renderer: Three.WebGLRenderer,
mesh: new Three.Mesh(),
factor: 0,
mouse: new Three.Vector2(1, 1),
raycaster: new Three.Raycaster(),
intersects: [],
VITESSE_ROTATION: 0.05,
VITESSE_DEPLACEMENT: 0.1,
VITESSE_ZOOM: 0.05,
RALENTISSEMENT: 3,
userData: {
souris: [],
formes: [
{
nom: 'Box',
userData: {
position: {
x: 0,
y: 7.8,
z: 0,
},
couleurs: [
'aaaaaa',
'095256',
],
decalage: 0.5,
},
},
{
nom: 'Icosahedron',
userData: {
position: {
x: 0,
y: 5.5,
z: 0,
},
couleurs: [
'aaaaaa',
'087F8C',
],
decalage: 0.5,
},
},
{
nom: 'Dodecahedron',
userData: {
position: {
x: 0,
y: 3.1,
z: 0,
},
couleurs: [
'aaaaaa',
'5AAA95',
],
decalage: 0.4,
},
},
{
nom: 'Sphere',
userData: {
position: {
x: 0,
y: 1,
z: 0,
},
couleurs: [
'aaaaaa',
'86A873',
],
decalage: 0.2,
},
},
],
},
}),
};
</script>
And here is the error I have in the console with VueJS 3:
three.module.js?5a89:24471 Uncaught TypeError:
'get' on proxy: property 'modelViewMatrix' is a read-only and
non-configurable data property on the proxy target but the proxy did not
return its actual value (expected '#<Matrix4>' but got '[object Object]')
at renderObject (three.module.js?5a89:24471)
at renderObjects (three.module.js?5a89:24458)
at Proxy.WebGLRenderer.render (three.module.js?5a89:24258)
at animate (HelloWorld.vue?fdab:192)
If anyone has got a clue, thanks in advance...
It worked with Vue 2
Reason it worked fine with Vue 2 lies in the fact Vue 2 is using different reactivity system based on Object.defineProperty API.
The same API is used by THREE.js a lot to add some non-writable and non-configurable properties to it's data structures
When object with such property was passed to Vue (by declaring it inside data for example), Vue just skipped such property resulting in stored value/object being non-reactive (as Vue could not detect property access while rendering the component template)
Vue 3 proxies
Vue 3 is using new reactivity system base on ES6 proxies.
This is pretty new and even that a lot of effort has been put into developing and testing it, issues like this will arise as people start migrating (And I completely agree with #Serg - Vue 3 is still new and unless you have skill and time to "live on the edge" you should wait a bit before migrating from Vue 2)
This new reactivity system doesn't play well with non-writable non-configurable properties on objects - you can find minimal reproducible example in this sandbox
Imho it is a bug and is reported to Vue#next repo
sandbox uses composition API but that doesn't matter as using reactive() is the same as declaring your variables inside data() function (Vue just do it automatically for you)
Workarounds
As said before, problem is in reactivity system. I'm not an expert on THREE.js but from what I know it doesn't make much sense to put the THREE data structures into Vue reactivity system - all point of reactivity is to detect data changes and re-render template when needed. THREE has its own rendering system and is usually using single <canvas> HTML element so it makes no sense to trigger Vue re-render on THREE data structures change...
There are multiple ways to opt-out from Vue reactivity:
Use Object.freeze() on your objects. Not very useful in this case but good to know
Do not declare your variables in data() and assign the values in created()/mounted() hook (example bellow). You can assign them into component itself (this) if you need to access them in multiple methods or as a local variables (const/let) whenf you don't need it
When using Composition API, do not use reactive() on THREE data structures
NOTE: Even if they admit it is a bug, only way of fixing it is to leave such property and object it holds non-reactive (not putting Proxy around that object) so result will be same as opting-out of reactivity completely. But using this workaround also gives you faster and less memory hungry app as all the reactivity is not really that cheap
Example - creating non-reactive component properties
export default {
data() {
return {
};
},
mounted() {
this.init();
},
methods: {
init() {
this.scene = new THREE.Scene();
this.camera = new THREE.OrthographicCamera(...);
this.renderer = new THREE.WebGLRenderer({ ... })
this.geometry = new THREE.PlaneBufferGeometry( );
const material = new THREE.MeshBasicMaterial({ color: 0xff0000 });
this.plane = new THREE.Mesh(this.geometry, material);
this.scene.add(this.plane);
this.renderer.render(this.scene, this.camera);
},
}
toRaw(vue3) - At this time, you can feel his strength !
You can use this method, to solve a series of these problems
If mesh/xxx is a ref variable
scene.add(toRaw(mesh.value))
renderer.value.render(toRaw(scene.value), camera.value);
I am using threejs + vue3 + pinia. Pinia was wrapping objects in Proxy too, but I need to pass 3d object to it sometimes (inside if other model). So I had a model like:
class SomeModel {
otherProp: 'some value',
graphicObject: new THREE.Object3D(),
}
The way I fixed this issue is by changing graphicObject prop to a function, that return 3d object, that was saved in other variable. It looks like this:
class SomeModel {
otherProp: 'some value',
constructor(graphicObject) {
this.graphicObject = () => graphicObject,
}
}
new SomeModel(new THREE.Object3D());
This way 3d object is hidden from Vue at all time, if you dont pass this object directly to any reactive variable. And the way you access it in other methods is by just calling this function like in example:
<script setup>
import { ref } from 'vue';
// You may just call constructor inside new SomeModel() if you want.
const graphicObject = new THREE.Object3D();
const someModel = ref(new SomeModel(graphicObject));
function changePosition(x, y, z) {
// should not emit errors, because 3d object is not reactive -
// it's just a value, returned from function.
someModel.value.graphicObject().position.set(x, y, z);
}
</script>

Vue / Vuetify - this.$refs.form.reset() error in file input

I was using this.$refs.form.reset() for my modal and all the fields are cleared not until I used the v-file-input field. The error produces Failed to execute 'readAsDataURL' on 'FileReader': parameter 1 is not of type 'Blob'.
If I manually set the the value of the v-file-input into null, everything is working fine. How do I retain using this.$refs.form.reset() without this error?
<v-file-input
v-model="tableItem.photo"
accept="image/png, image/jpeg, image/bmp"
placeholder="Pick an avatar"
prepend-icon="mdi-camera"
label="Avatar"
#change="getImagePreviews()"
></v-file-input>
getImagePreviews() {
let reader = new FileReader();
reader.addEventListener("load", () => {
this.imageURL = reader.result
})
reader.readAsDataURL(this.tableItem.photo)
},
closeModal() {
this.tableItem.photo = null // this is working but I need to remove this.$refs.form.reset()
this.$refs.form.reset()
this.$refs.form.resetValidation()
}
Since the readAsDataURL() function accepts only Blob data, you can check if the parameter is of Blob type:
getImagePreviews() {
if (this.tableItem.photo instanceof Blob){
this.reader.readAsDataURL(this.tableItem.photo)
}else{
this.imageURL = null
}
},
Also, you may want to initialize the reader in the mounted hook:
data: ()=>({
reader: null
}),
mounted(){
this.reader = new FileReader();
this.reader.addEventListener("load", () => {
this.imageURL = this.reader.result
})
}

Getting reactivity from watch in Vue.js

Trying to make a component in Vue.js, which first shows image via thumbnail, loading full image in background, and when loaded, show full image.
The thing which does not work, component does not react on change of showThumb flag in watch section. What is wrong?
Vue.component('page-image',
{
props: ['data'],
template:
'<img v-if="showThumb == true" v-bind:src="thumbSrc"></img>'+
'<img v-else v-bind:src="fullSrc"></img>',
data: function()
{
return { thumbSrc: '', fullSrc: '', showThumb: true };
},
watch:
{
data: function()
{
this.thumbSrc = data.thumbImg.url;
this.fullSrc = data.fullImg.url;
this.showThumb = true;
var imgElement = new Image();
imgElement.src = this.fullSrc;
imgElement.onload = (function()
{
this.showThumb = false; // <<-- this part is broken
} );
}
}
} );
Note: there is a reason why I do it via 2 img tags - this example is simplified.
Your onload callback will have a different scope than the surrounding watch function, so you cannot set your data property like this. Change it to an arrow function to keep scope:
imgElement.onload = () =>
{
this.showThumb = false;
};
See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions