Reveal.js not initialising in vue except on refresh - javascript

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()
}

Related

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.

When data changes that is passed to a Vue3 control, it doesn't update. It works when hard coded

This is the pared down code for the main component. I have reactive data elements setup here.
<template>
<div class="scheduler">
<recursive-render :items="data.probeGroupedData"/>
</div>
</template>
<script>
import { ref } from "vue";
import RecursiveRender from "./RecursiveRender.vue";
export default {
name: "Scheduler",
components: {
RecursiveRender
},
setup() {
const data = ref({
probeGroupedData: [],
probeScriptInfo: [],
probeScriptInfoHashById: {},
});
return {
data,
};
},
methods: {
probeSort: function() {
var result = []
//I do various things to sort the data and fill up result and then:
this.data.probeGroupedData = result;
console.log("\nresult:" + JSON.stringify(result, null, ' '));
}
}
},
mounted() {
//I do various things to request the data here, and call this.probeSort
},
};
</script>
The component writes data just fine if I paste the data directly into it on setup.
probeGroupedData: [{
"id": "_1",
"label": "Renton, WA",
"keyName": "id",
"cssClass": "top",
"children": [
{
"label": "Motorola",
"id": "_0",
"cssClass": "manufacturer",
"children": [
{
"label": "Atrix",
"id": "_1",
"cssClass": "family",
"children": [
{
"label": "HD",
"id": "_2",
"cssClass": "model",
"children": [
{
"isLeaf": true,
"id": 1,
"cssClass": "device"
}
]
}
]
}
]
}]
But it isn't updating it when it is being written by probeSort. Do I need to have a watcher or something know that it has changed?
This is the entire code for RecursiveRender
<template>
<div>
<div v-for="item in data.items" :class="cssClass" :key="item.id">
<div v-if="item.isLeaf" :id="item.id">
{{item.label}}
</div>
<div v-if="item.children">
{{item.label}}
<recursive-render :items="item.children" />
</div>
</div>
</div>
</template>
<script>
import { ref } from 'vue';
import RecursiveRender from "./RecursiveRender.vue";
export default {
name: 'RecursiveRender',
components: {
RecursiveRender
},
setup() {
const data = ref({
items: [],
});
return {
data
};
},
props: {
items: {
type: Array,
default: () => []
}
},
mounted() {
//this is where the data is received
this.data.items = this.items;
}
};
</script>
I think the issue may be due to use of ref instead of reactive
ref is for values like boolean, string, and number. It will not update on deep changes. If you have nested data like array or object, reactive might be a better option since it can look for deep data changes.
I also noticed you're not using data.value. this.data.probeGroupedData = result; is likely storing the data in the wrong place. You could try this.data.value.probeGroupedData = result; which may still have issues with reactivity. you could also do this.data.value = {...this.data.value, probeGroupedData : result} which will trigger the reactivity without worrying about the depth issue.
Thanks for the clues that Daniel gave me. The error is passing the value on mount. Just having the prop is enough, it doesn't have to be passed back to data.
<template>
<div>
<div v-for="item in items" :class="cssClass" :key="item.id">
<div v-if="item.isLeaf" :id="item.id">
{{item.label}}
</div>
<div v-if="item.children">
{{item.label}}
<recursive-render :items="item.children" />
</div>
</div>
</div>
</template>
<script>
import RecursiveRender from "./RecursiveRender.vue";
export default {
name: 'RecursiveRender',
components: {
RecursiveRender
},
props: {
items: {
type: Array,
default: () => []
}
},
};
</script>
With more experience, I've learned you need to use provide / inject.
In the parent component, the data you want to have reactive needs to be provided:
//add provide to the import:
import { provide, ref } from "vue";
//in setup() you need to pass this to provide:
setup() {
const boardData = ref ({ <your data here> });
provide('boardData', boardData);
return {
boardData,
<and anything else>
}
}
In the child component you need to add inject to the import:
import { ref, inject } from "vue";
In setup you inject it using the same name as you used to provide it:
setup() {
const boardData = inject('boardData');
return {
boardData,
//and whatever else you need for the component
}
}
This is being used now for a backgammon game that has child pips, and it works well. For layout reasons the pips need to be in four groups of six, so I used a child component for them so I didn't have to have complex repeated loops in the main code. The pips now behave as they are all on rendered in the parent, and changes that the pips do to the reactive data are rendered properly in other sets of pips.

NuxtJS passing props

I have an issue so I have in NuxtJS (vuejs) with a default layout like that
<template>
<div id="app">
<Loader #animation:complete = 'loader'/>
<Header/>
<nuxt :title = 'title'/>
<Footer/>
<BgDecor/>
</div>
</template>
<script>
import { gsap } from "gsap/dist/gsap";
import Loader from "#/layouts/parts/Loader";
import Header from "#/layouts/parts/Header";
import Footer from "#/layouts/parts/Footer";
import BgDecor from "#/layouts/parts/BgDecor";
export default {
data() {
return {
loaderDone: null,
};
},
components: {
Loader,
Header,
Footer,
BgDecor,
},
head() {
return {
titleTemplate: "%s - Product Designer UI/UX Designer Strategist",
meta: [
{
hid: "description",
name: "description",
content:
"Artistic Director, Web / Motion Designer, FrontEnd Developer for over 10 years, specializing in visual communication and web design",
},
{
hid: "keywords",
name: "keywords",
content:
"Product Design, UI, UX, Designer, UI Designer, UX Designer, FrontEnd Developer",
},
],
};
},
methods: {
loader(e) {
console.log("yesssssss");
this.loaderDone = "yup";
},
},
computed: {
title() {
return this.loaderDone;
},
},
};
</script>
<style>
</style>
my issue is I have yup in Nuxt but not inside the page inside the component nuxt
yes I know it's complicated but I would like to when my loader is done get an $emit to my index page or other pages to know my loader is done
If I put the loader inside the index directly every time I come back to the page index I get again the loader I would like just to have it when I start my website that all
so in the dev tools I have the yup inside the nuxt parent of the index but if I go again with props it seems a lot of work for just getting info my loader is done I can start my other animation
If anyone has an idea thanks :)
have a great evening
You can do this with a vuex store.
Your Store could look like this
const store = new Vuex.Store({
state: {
loaderDone: false
},
mutations: {
setLoadingDone (state) {
state.loadingDone = true
}
}
})
In your loader you could map the mutation and in your component you can map the state.
To set the done flag you can do this in your loader to have the method setLoadingDone()
// loader.vue
import { mapMutations } from 'vuex}
export default {
// ...
methods: {
...mapMutations(['setLoadingDone '])
}
}
And your other componens can get this value like this
// other components
import { mapState } from 'vuex}
export default {
// ...
computed: {
...mapState(['loadingDone '])
}
}

how to completely destroy a function on component closed?

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

Nuxtjs handler.call is not a function

been banging my head once more.
I'm surprised I haven't seen a similar question here on SOF, but it's really weird.
This code WAS working, but I moved into a standalone component to be used on multiple pages.
This is the error I'm getting when I go to the page:
handler.call is not a function
I know for a fact it is this component because if I remove the component from the page, there is no error and runs fine. The component calls no functions and has no functions in the script. I have no idea what is going on.
and in the console log, there isn't much help either:
TypeError: "handler.call is not a function"
NuxtJS 21
invokeWithErrorHandling
callHook
insert
invokeInsertHook
patch
_update
updateComponent
get
Watcher
mountComponent
$mount
mount
_callee5$
tryCatch
invoke
method
asyncGeneratorStep
_next
run
notify
flush
This is the very simple component source code:
<template>
<div>
<button v-if="can_edit" class='btn-blue'> Edit </button>
<div v-for="card in cards" class='my-credit-card' v-bind:key="card.id">
<h5>{{card.name}}</h5>
<h5 class='mt-0'>•••• •••• •••• {{card.last4}}</h5>
<p class='small'>Exp. {{card.expiration}}</p>
</div>
<div v-if="cards.length == 0">
<p class='subtle'>
Your saved payment methods will display here once you send your first card!
</p>
</div>
<a v-if="(can_add && cards.length > 0)" href="/add-card" class='action'> + Add New Card </a>
</div>
</template>
<script>
export default {
data : function(){
return {
cards : [
{
id: 1,
name: "Lisa Smith",
last4: "4231",
expiration: "12/2022"
},
{
id: 2,
name: "John Smith",
last4: "1234",
expiration: "11/2023"
},
],
};
},
props : {
can_add : {
default : true,
type: Boolean,
},
can_edit : {
default : true,
type: Boolean,
},
},
mounted : {
// fetch cards
},
}
</script>
and this is how I'm importing the component:
<template>
<section class='container'>
<h1>My Credit Cards</h1>
<mycards :can_add="true" :can_edit="true"></mycards>
</section>
</template>
<script>
import mycards from '~/components/my_cards.vue';
export default {
data : function(){
return {
test : 1,
};
},
components : {
mycards,
},
}
</script>
This:
mounted : {
// fetch cards
},
Should be:
mounted () {
// fetch cards
},
You're setting your mounted hook to an object, which won't have a call method on it.

Categories