how to completely destroy a function on component closed? - javascript

I have a vue app form which has an image uploader in it. It has child component where carry the function to crop the image after upload the image from ant-design upload. it's working correctly for upload and crop process. The problem is where it's only work when user firstime upload the image. But in second time upload the image, the crop canvas still using the first image that need to crop. So i destroy the function on it's close or saved by using this.cropper.destroy() it's somehow work a little but still have the bug where second time upload will using first image, third time upload will using second image, fourth time upload will using third image and so on. it'll have no user friendly if this bug still there.
Here i'm show my child component code where i want it to be completely destroy on component destroy.
<template>
<div>
<a-row :gutter="16">
<a-col :span="12">
<img ref="image" :src="initialImage">
</a-col>
<a-col :span="12" align="center">
<p><strong>Preview</strong></p>
<img :src="updatedImage" class="preview-image">
</a-col>
</a-row>
<br />
<a-row :gutter="16">
<a-button type="primary" style="float: right;" #click="crop">Crop</a-button>
<a-button style="float: right; margin-right: 5px;" #click="cancel">Cancel</a-button>
</a-row>
</div>
</template>
<script>
import Cropper from 'cropperjs';
export default {
name: 'PostCropper',
props: {
uploadedImage: String,
},
data() {
return {
cropper: {},
updatedImage: {},
image: {},
initialImage: this.uploadedImage,
};
},
methods: {
crop() {
this.$emit('update-image', this.updatedImage);
this.cropper.destroy();
},
cancel() {
this.$emit('cancel-upload');
this.cropper.destroy();
},
cropImage() {
this.image = this.$refs.image;
this.cropper = new Cropper(this.image, {
zoomable: false,
scalable: false,
aspectRatio: 1,
crop: () => {
const canvas = this.cropper.getCroppedCanvas();
this.updatedImage = canvas.toDataURL('image/png');
},
});
},
},
watch: {
uploadedImage() {
this.initialImage = this.uploadedImage;
this.cropImage();
},
},
mounted() {
this.cropImage();
},
};
</script>
<style scoped>
.preview-image {
border-radius:100px;
width:150px;
height:150px;
}
</style>
What is actually i need to do for this this.cropper completely destroy on page close? Or anything that I can do to overcome this problem?

watch: {
uploadedImage() {
this.initialImage = this.uploadedImage;
this.cropper.destroy(); //==> use destroy function here
this.cropImage();
},
},

instead of destroy the cropper, i just destroyed whole child component with v-if so when click the button it will remounted the components

Related

How does MaterialUI do this?

If you look at their autocomplete component: https://mui.com/material-ui/react-autocomplete/
After you click a suggestion in the dropdown, the input box keeps focus... How do they do that? In every variation of this in my own vue app (not using material UI) I can't get the click event to stop an input from losing focus.
I have tried googling this for quite some time and there is no clear solution that I see. For example, people suggest mousedown/touchstart but that would break scrolling (via dragging the dropdown). MaterialUI obviously doesn't have this problem, and doesn't seem to be using mousedown.
I've tried analyzing the events using Chrome dev tools and I can only see a single click event, but with minified code it's difficult to tell what's going on.
Vuetify also does this: https://github.com/vuetifyjs/vuetify/blob/master/packages/vuetify/src/components/VAutocomplete/VAutocomplete.ts
I did find this too which is helpful, if anyone comes across this issue https://codepen.io/Pineapple/pen/MWBVqGW
Edit Here's what Im doing:
<app-input-autocomplete
#autocomplete-select="onSelect"
#autocomplete-close="onClose"
:open="open">
<template #default="{ result }">
<div class="input-autocomplete-address">
{{ result.address }}
</div>
</template>
</app-input-autocomplete>
and then in app-input-autocomplete:
<template>
<app-input
#focus="onFocus"
#blur="onBlur"
v-bind="$attrs">
<template #underInput>
<div ref="dropdown" v-show="open" class="input-autocomplete-dropdown">
<div class="input-autocomplete-results">
<div v-for="result in results" :key="result.id" #click="onClick(result)" class="input-autocomplete-result">
<slot :result="result" />
</div>
</div>
</div>
</template>
</app-input>
</template>
<script>
import { ref, toRef } from 'vue';
import AppInput from '#/components/AppInput.vue';
import { onClickOutside } from '#vueuse/core';
export default {
components: {
AppInput,
},
inheritAttrs: false,
props: {
open: {
type: Boolean,
default: false,
},
results: {
type: Array,
default: () => ([]),
},
},
emits: ['autocomplete-close', 'autocomplete-select'],
setup(props, { emit }) {
const dropdown = ref(null);
const open = toRef(props, 'open');
const focused = ref(false);
onClickOutside(dropdown, () => {
if (!focused.value && open.value) {
emit('autocomplete-close');
}
});
return {
dropdown,
focused,
};
},
methods: {
onFocus() {
this.focused = true;
},
onBlur() {
this.focused = false;
},
onClick(result) {
this.$emit('autocomplete-select', result);
},
},
};
</script>
I solved this by doing the following, thanks to #Claies for the idea to look, and also this link:
https://codepen.io/Pineapple/pen/MWBVqGW
event.preventDefault on mousedown
#click on result behaves like normal (close input)
#click/#focus on input set open = true
#blur sets open = false

Reveal.js not initialising in vue except on refresh

I'm creating a Vue app to read comic books via Reveal.js. The component takes the data from the parent. there is an Axios call in the parent to provide the data from a rest API. I'm also using Vue router with the createWebHashHistory setup as I'm using a Django backend to provide the API.
If I refresh the page it will load the presentation correctly but when I navigate to the page it doesn't seem to initialise Reveal. there are no errors in the console.
I've tried to watch the route changing and other events to run Reveals sync or initialise but I've not had any success.
component
<template>
<div class="reveal" id="comic_box" ref="comic_box">
<div id="slides_div" class="slides">
<section v-for="(page, index) in comic_data.pages" :key="page.index" :data-menu-title="page.page_file_name">
<img :data-src="'/image/' + comic_data.selector + '/' + page.index " class="w-100" :alt="page.page_file_name">
</section>
</div>
</div>
</template>
<script>
import Reveal from "reveal.js";
export default {
name: "TheComicReader",
data () {
return {
}
},
props: {
comic_data: Object
},
methods: {
},
watch: {
'$route' (to, from) {
Reveal.initialize()
}
},
mounted () {
Reveal.initialize()
},
}
</script>
<style scoped>
</style>
comic_data
{
"selector": "e1b76b93-814c-4ee8-9104-8c8187977836",
"title": "Batman 125 (2022) (digital-SD).cbr",
"last_read_page": 0,
"pages": [
{
"index": 0,
"page_file_name": "Batman 125-000.jpg",
"content_type": "image/jpeg"
},
{
"index": 1,
"page_file_name": "Batman 125-001.jpg",
"content_type": "image/jpeg"
}
]
}
After further investigation I noticed that the DOM elements in Reveal were not updating after moving away from the page. I solved this by forcing Reveal to bind to the new comic_box by ref. This is now consistently loading the presentation correctly.
mounted () {
Reveal(this.$refs.comic_box).initialize()
}

Vue 2 Component Mounts on One Page, Throws TypeError on another but works if loaded on first page before

I have a popup component which renders a photo ID that works currently on one page and I am trying to also have the component visible on another page. When I add my component to another page it throws an error at me. Unsure what is causing the error. Stack suggests my cleanup function doesn't exist, it is a function written in the component.
If however I load the photoID popup from the page which already has the working component and then navigate to the second page and try to open the new component it will load fine until I refresh the page.
My Popup component
//popup.vue
<template>
<!--
Rendering of this component should be handled by the parent using a v-if.
Listen for event 'closeButtonPressed' to know when to stop rendering it.
-->
<div class="modal is-active">
<div class="modal-background" #click="$emit('closeButtonPressed')"></div>
<div class="modal-content">
<p class="image is-4by3">
<img class="photo-id-img" :src="photoIdObjectUrl">
</p>
</div>
<button class="modal-close is-large" aria-label="close" #click="$emit('closeButtonPressed')"></button>
</div>
</template>
<script>
import { mapState, mapActions } from 'vuex';
import utils from "../Utils/utils.js";
export default {
name: 'photoIdPopup',
props: {
clientId: {
type: Number,
required: true,
}
},
data: () => ({
/**
* This is a url we generate that points to a blob containing our photo
* id image. An img element's src attribute can be set to it to
* display the image.
*/
photoIdObjectUrl: null,
}),
destroyed() {
this.cleanUp();
},
methods: {
...mapActions({
getFile: "filestorage/getFile",
}),
async getPhotoIdObjectUrl() {
// Get photo id file from api.
const file = await this.getFile({
usercode: this.photoIdFile.usercode,
id: this.photoIdFile.id,
clientId: this.clientId,
});
// Convert the file to a blob.
const blob = new Blob([utils.str2bytes(file)]);
// Create a objectUrl from the blob for img element's src attribute.
return URL.createObjectURL(blob);
},
/** We have to cleanup the object urls we previously allocated. */
cleanUp() {
if (this.photoIdObjectUrl) {
URL.revokeObjectURL(this.photoIdObjectUrl);
this.photoIdObjectUrl = null;
}
},
async initializePhotoIdImage() {
this.photoIdObjectUrl = await this.getPhotoIdObjectUrl();
}
},
computed: {
...mapState({
photoIdFile(state) {
return state.filestorage['drLicense'];
}
}),
},
watch: {
photoIdFile: {
immediate: true,
handler(newPhotoIdFile) {
if (newPhotoIdFile) {
this.initializePhotoIdImage();
} else {
this.cleanup();
}
}
}
}
}
</script>
<style scoped>
.photo-id-img {
object-fit: contain;
}
</style>
How I'm calling the object on the new page, there is no difference in how I do it between this page and the page which has my working component.
//interview-summary.vue
<photoIdPopup
v-if="isPhotoIdShowing"
#closeButtonPressed="viewPhotoIdPressed"
:clientId="clientId"
/>
viewPhotoIdPressed() {
this.isPhotoIdShowing = !this.isPhotoIdShowing
Error Stack
Any help would be appreciated as I'm still learning Vue. My first assumption is that I'm missing an object in my state that I need to render the component properly but I'm not sure.

"Video Player is Loading" "This is a modal window" - How to remove unwanted text in react videojs .m3u8 stream

I am embedding an .m3u8 stream in a react JS page. I am using video.js and have some unwanted text showing which I just cannot remove! The text is on two lines and says "Video Player is loading" then "This is a modal window". See the image below for an example (the blue box contains the video).
Once the video has loaded, the text does not go away and I can't work out why it is there in the first place.
If anyone has any idea how to remove this text it would be fantastic. Thank you.
Please find my code below. It has been pretty much copy and pasted from the documentation: https://docs.videojs.com/tutorial-react.html
import React, { Component } from 'react';
import { connect } from 'react-redux'
import videojs from 'video.js';
import 'video.js/dist/video-js.css';
//import 'videojs-contrib-hls/dist/videojs-contrib-hls.js';
// Workaround for webworkify not working with webpack
window.videojs = videojs;
require('videojs-contrib-hls/dist/videojs-contrib-hls.js');
class VideoPlayer extends Component {
componentDidMount() {
// instantiate Video.js
this.player = videojs(this.videoNode, this.props, function onPlayerReady() {
console.log('onPlayerReady', this)
});
}
// destroy player on unmount
componentWillUnmount() {
if (this.player) {
this.player.dispose()
}
}
// wrap the player in a div with a `data-vjs-player` attribute
// so videojs won't create additional wrapper in the DOM
// see https://github.com/videojs/video.js/pull/3856
render() {
return (
<div>
<div data-vjs-player>
<video ref={ node => this.videoNode = node } className="video-js"></video>
</div>
</div>
)
}
}
class videoWidget extends Component {
render() {
const videoOptions = {
autoplay: true,
textTrackSettings: false,
bigPlayButton: false,
controlBar: false,
sources: [{
src: 'http://my-source-url.m3u8',
type: "application/x-mpegURL"
}],
}
return (
<div>
<VideoPlayer {...videoOptions} />
</div>
);
}
}
I have just discovered that the two lines of text "Video player is loading" and "This is a modal window" can respectively be removed by adding the lines below into the videoOptions definition:
loadingSpinner: false,
errorDisplay: false,

VueJS: Obtaining dynamic component information in method run via v-bind

I believe this is a relatively unique problem, and as such I'm having difficulty trying to solve it.
I'm creating a file manager-like solution in Vue, and I'm looking for certain folders/files to display a unique thumbnail (in my example, showing the Creative Cloud logo if the 'Creative Cloud' folder is found). In my app I am using a component to represent each file.
The file-grid Vue file reads as such (sorry for the mess, I've been trying to integrate multiple different solutions to see what sticks):
<template>
<div id="localMain">
<div id="filesGrid">
<File :fileName="file"
:imageAddress="findImage($event)"
id="file"
v-for="file in files"
:key="file.id"></File>
</div>
</div>
</template>
<script>
import File from './LocalMain/File';
export default {
data() {
return {
creativeCloud: 'static/logos/creative-cloud.svg',
blankThumb: 'static/code.svg',
files: [
'Creative Cloud',
'Documents',
...
],
};
},
components: {
File,
},
methods: {
findImage: function findImage(e) {
/* Get the name of the file/folder, and choose a thumbnail accordingly */
const name = e.target.dataset.fileName;
let image = this.blankThumb;
if (name === 'Creative Cloud') {
image = this.creativeCloud;
} else {
image = this.blankThumb;
}
return image;
},
},
};
</script>
<style scoped>
/* styling */
</style>
The file component itself looks like this:
<template>
<div id="file">
<img :src="imageAddress" alt="Logo" id="fileImg" />
<h3 v-if="display">{{ fileName }}</h3>
</div>
</template>
<script>
export default {
data() {
return {
display: false,
};
},
props: {
fileName: String,
imageAddress: String,
},
};
</script>
<style scoped>
/* styling */
</style>
I apologise for the ambiguity in this question, but I'm quite confused.
I might be missing something, but why not just v-bind the method with the file name as the argument?
eg.
Parent template
<File :fileName="file"
:imageAddress="findImage(file)"
id="file"
v-for="file in files"
:key="file.id"></File>
Parent Javascript
findImage: function findImage(name) {
var image = this.blankThumb;
if (name === 'Creative Cloud') {
image = this.creativeCloud;
}
return image;
},

Categories