I need to manipulate some DOM properties manually (change the style of a file element dynamically). I search for the desired element by document.getElementById ("filepond - item-" + id).
But I run into a problem if the Filepond DOM elements with initial files have not been rendered yet (getElementById() does not find any element).
I am currently solving this problem with this.$NextTick():
<template>
<file-pond
ref="filepond"
:server="server"
:files="files"
#initfile.once="markMainImage"
/>
</template>
<script>
[...]
methods: {
async markMainImage () {
await this. $ nextTick ();
document.getElementById ("filepond - item-" + id).classList.add ("main-image");
},
},
</script>
But this is not convenient and not very reliable.
Can the FilePond:loaded event be used for this purpose (https://pqina.nl/filepond/docs/api/instance/events/#filepond-loaded-event)?
If so, how do you use it in Vue?
#loaded="markMainImage" doesn't fire
Or is there another way to find out if filepond is rendered with initial files?
Thanks to Rik for comment
#addfile.once="markMainImage"
It works.
Related
I use a custom directive to render LaTeX-code with KaTeX' renderMathInElement function. This, obviously, changes the component's innerHTML. I would like to re-run KaTeX once the content changes, but: The content never does!
A simple reproduction of the problem does not need KaTeX or directives and still shows, that reactivity works, but stops to work for the parts of a component with changed innerHTML:
<template>
<div>
{{content}}
<span ref="elem">{{content}}</span>
</div>
</template>
<script lang="ts">
import { Component, Ref, Vue } from "vue-property-decorator";
#Component({})
export default class Test extends Vue {
content = "Hello World!";
#Ref()
elem!: HTMLSpanElement;
mounted(): void {
// Without the following statement, Vue correctly re-renders the whole component after a second with the new content
// With this line, the update does not happen for the span element.
this.elem.innerHTML = "<b>Hello World!</b>";
setTimeout(() => {
this.content = "Greetings!";
}, 1000);
}
}
</script>
I suppose this is intended behavior - but that doesn't solve my problem. Is there some way to force Vue to replace all the component's DOM as soon as a re-render takes place?
You can use a key on your span, but if you don't want to tie it in with content, you can instead set it to a number, and increment it every time you want to make a change. Like so (I am not using TS here):
Set a key on your span:
<span :key="content_key">{{ content }}</span>
Then you can watch content and update the key accordingly:
watch: {
content() {
this.content_key ++;
}
}
In this way you can avoid setting the key to content directly.
Does this work for you?
I have a custom map component, which wraps a Openlayers 4 instance. This component which I am forced to use, is used multiple places across my SPA. The initialization process is quite long, so I would like to keep one instance of the map available, and move it between views when I need to. Problem is that the state doesen´t update within the component when it has moved.
I´ve boiled the problem down to this fiddle: https://jsfiddle.net/j16d4yto/
When moved on the same router-view the state updates fine (click the ‘Change text’ button). But when the router-view changes, and the component is moved with appendChild to the new div, the state freezes, and you can´t update the text variable anymore.
This is how I move the component from one element to another:
this.$root.$on('showMoveableComponent', function(element) {
element.appendChild(thisElement);
this.text = 'Changed text2';
});
I bet I am doing something wrong here, and probably also approaching this problem in the wrong way?
Thanks!
It's not working because of when router-view changed your MoveableComponent has been destroyed only its DOM element still referenced by you. You can test by print something in destroyed lifecycle callback function.
So this mean you can solve this by using built-in keep-alive component:
<keep-alive>
<router-view></router-view>
</keep-alive>
Example
The keep-alive component will cache everything which may not good in some other cases.
In my opinion the better way to solve this is create another Vue instance and move it.
const MoveableComponent = new Vue({
el: '#some-id',
template: `...`,
data: { ... },
methods: {
changeText() {
...
},
moveTo(element) {
element.appendChild(this.$el)
}
}
})
Example
I am using a jquery date picker tool. But it is inside a v-if. When I toggle it on and off if I write this code it does not work.
showdatepicker:function(){
this.datepicker= true;
$(".jquery-date-picker").datepicker();
}
Because when $(".jquery-date-picker").datepicker(); runs vue still did not rendered html and added my datepicker input. But if change it to:
showdatepicker:function(){
this.datepicker= true;
setTimeout(function(){$(".jquery-date-picker").datepicker();},0);
}
It works. Because runs it after html render.
My question: is setTimeout a guaranteed way to do it after html render. Is there a proposed way in vuejs to do this right. I couldn't find any answer from my google and stackoverflow searches.
You can use nextTick which will call a function after the next DOM update cycle.
this.datepicker = true;
Vue.nextTick(function () {
$(".jquery-date-picker").datepicker();
})
And here is it's page in the documentation VueJS Docs
Vue features triggers, in your case the mounted() function could be used. This articles describes how to use it and the whole Vue hook timeline : https://alligator.io/vuejs/component-lifecycle/
A stackoverflow explanation : Vue JS mounted()
I have been doing a lot of VueJS tutorials including the router, event bus, and trying to use fetchival and axios to no avail.
The setup, I want there to be two sections. One where I have buttons and the second section would be updated with html data from html files that varies depending on the button pressed.
I have used event bus to be able to just update the second div with basic, static html
(i.e. <p>got it</p>) but I cannot, for the life of me, use any request to get html from another website or file and load it into the div.
I don't necessarily need anyone to build it for me, but even some guidance and direction would be infinitely appreciated.
Based on your comments above, I think you want to change your thinking from "loading html files" to "showing different parts of the Vue component."
Here's a basic example. I'm going to use Vue single-file component syntax, but it's not hard to refactor for class-based components:
<template>
<div>
<button #click="clickedShowFirst">Show First</button>
<button #click="clickedShowSecond">Show Second</button>
<div v-if="showingFirst">
This is the first section!
</div>
<div v-else>
This is the second section!
</div>
</div>
</template>
<script>
export default {
data: function () {
return {
// We default to showing the first block
showingFirst: true
}
}
methods: {
clickedShowFirst: function () {
this.showingFirst = true
},
clickedShowSecond: function () {
this.showingFirst = false
}
}
}
</script>
You could of course make each of the v-if blocks components of their own that you import (which makes sense if they are complex themselves).
Or as suggested by Phillipe, you can use vue-router and make each of those views a different page with a different URL.
One last recommendation to leave you with, I found Jeffrey Way's Laracasts series on Vue.js amazingly helpful when I was learning. His episode titled "Exercise #3: Tabs" is very similar to what you're asking here.
You could use vue-router (https://router.vuejs.org/en/). In first section put the router-link (https://router.vuejs.org/en/api/router-link.html), your buttons, in second section put the router-view (https://router.vuejs.org/en/api/router-view.html).
I'm currently trying to create a component which manages several linked dropdowns and elements on a page. In addition, this element supplies a rather fancy navigation element, containing anchor links which automatically scroll to the desired element in this component.
The problem is that the actual contents of the component are completely dynamic and partially determined by the content manager in the CMS. There are several sub components that are always present, but apart from that the content manager can add any number of sections, (using various named and an unnamed ) and each of these sections should be added to the navigation of the component.
I see 3 options:
For every component added, it's title and unique id is added to a property array on the parent component. This, however, is a bit prone to errors. (Unfortunately I will have no control over the eventual backend implementation, so I'm trying to create a foolproof system to avoid to much wasted time.)
Due to other components, I'm already using Vuex to manage most of the data in the app. I figured I use a simple directive, to be added on each item in the parent component. This directive is responsible for adding that element to the Vuex store. The parent component simply reads the contents of the store and generates the navigation based on this.
The problem here is that, as far as I can tell, I have to use the vNode argument in the bind hook of my directive to access the Vuex store. This seems... hacky. Is there anything wrong with this approach?
In the mounted hook of my parent component, I traverse the DOM, searching for elements with a particular data-property, and add a link to each of these elements to my navigation. Seems prone to failure and fragile.
What is the preferred approach for this situation?
As a follow up question - What is the correct use case for the vNode argument in a vue directive? The documentation seems rather sparse on this subject.
I would steer away from using a directive in this case. In Vue 2, the primary use case for directives is for low level DOM manipulation.
Note that in Vue 2.0, the primary form of code reuse and abstraction
is components - however there may be cases where you just need some
low-level DOM access on plain elements, and this is where custom
directives would still be useful.
Instead I would suggest a mixin approach, where your mixin essentially registers your components that should be included in navigation with Vuex.
Consider the following code.
const NavMixin = {
computed:{
navElement(){
return this.$el
},
title(){
return this.$vnode.tag.split("-").pop()
}
},
mounted(){
this.$store.commit('addLink', {
element: this.navElement,
title: this.title
})
}
}
This mixin defines a couple of computed values that determine the element that should be used for navigation and the title of the component. Obviously the title is a placeholder and you should modify it to suit your needs. The mounted hook registers the component with Vue. Should a component need a custom title or navElement, mixed in computed properties are overridden by the component's definition.
Next I define my components and use the mixin.
Vue.component("child1",{
mixins:[NavMixin],
template:`<h1>I am child1</h1>`
})
Vue.component("child2",{
mixins:[NavMixin],
template:`<h1>I am child2</h1>`
})
Vue.component("child3",{
template:`<h1>I am child3</h1>`
})
Note that here I am not adding the mixin to the third component, because I could conceive of a situation where you may not want all components included in navigation.
Here is a quick example of usage.
console.clear()
const store = new Vuex.Store({
state: {
links: []
},
mutations: {
addLink (state, link) {
state.links.push(link)
}
}
})
const NavMixin = {
computed:{
navElement(){
return this.$el
},
title(){
return this.$vnode.tag.split("-").pop()
}
},
mounted(){
this.$store.commit('addLink', {
element: this.navElement,
title: this.title
})
}
}
Vue.component("child1",{
mixins:[NavMixin],
template:`<h1>I am child1</h1>`,
})
Vue.component("child2",{
mixins:[NavMixin],
template:`<h1>I am child2</h1>`
})
Vue.component("child3",{
template:`<h1>I am child3</h1>`
})
Vue.component("container",{
template:`
<div>
<button v-for="link in $store.state.links" #click="scroll(link)">{{link.title}}</button>
<slot></slot>
</div>
`,
methods:{
scroll(link){
document.querySelector("body").scrollTop = link.element.offsetTop
}
},
})
new Vue({
el:"#app",
store
})
h1{
height:300px
}
<script src="https://unpkg.com/vue#2.2.6/dist/vue.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vuex/2.3.1/vuex.js"></script>
<div id="app">
<container>
<child1></child1>
<child3></child3>
<child2></child2>
</container>
</div>
This solution is pretty robust. You do not need to parse anything. You have control over which components are added to the navigation. You handle potentially nested components. You said you don't know which types of components will be added, but you should have control over the definition of the components that will be used, which means its relatively simple to include the mixin.