Inject component tag dynamically - vue.js

I have multiple drop divs and so every time i drop the image of a component into those div i import the component corresponding to this image and i need to put it into the dom in the target div
drops[i].addEventListener('drop', function () {
if (this.draggedElement === null) {
return false
}
// get the component conf of this image
let conf = BlocksStore.conf[this.draggedElement.dataset.category].blocks[this.draggedElement.dataset.type]
// get the component itself (./blocks/SimpleTitle.vue)
let component = require('./blocks/' + conf.component)
drops[i].classList.remove('drag-enter')
// here is where i don't know what to do ...
drops[i].innerHTML = component
this.draggedElement = null
}.bind(this))
Here the code of ./blocks/SimpleTitle.vue
<template>
<tr>
<td align="center">
Lorem Ipsum
</td>
</tr>
</template>
<script>
export default {
name: 'simple-title'
}
</script>
<style>
</style>
I already tried to append the tag instead of the component but the dom don't comprehend it as a component

Related

Vue JS 3 Parent Calling Multiple Children methods

I have a parent page for FAQ and child component that collapses the Q&A. But on the FAQ page we have a button to expand all the FAQs. So I am trying to call a method on the child components which we will have a dozen or so of these. But when I click on view all it only opens the last child component. Why is it only hitting the last component and not all of them?
import CollapsiblePanel from '#/components/CollapsiblePanel';
// Imports
import { ref } from 'vue';
const collapsiblePanelRef = ref();
function expand() {
collapsiblePanelRef.value.expandAll()
}
Then the mark up with the child ref...
<a #click="expand">View All</a>
<CollapsiblePanel ref="collapsiblePanelRef">
<template v-slot:title>
Sample title 1
</template>
<template v-slot:content>
Lorem ipsum 1
</template>
</CollapsiblePanel>
<CollapsiblePanel ref="collapsiblePanelRef">
<template v-slot:title>
Sample title 2
</template>
<template v-slot:content>
Lorem ipsum 2
</template>
</CollapsiblePanel>
Then the CollapsiblePanel looks like...
function expandAll() {
isActive.value = true;
}
defineExpose({
expandAll
})
I do not know the exact answer to why only the last opens, but I would rather do this with a prop. In the parent template:
<a #click="expandAll">View All</a>
<CollapsiblePanel :open="openAll">
...
</CollapsiblePanel>
<CollapsiblePanel :open="openAll">
...
</CollapsiblePanel>
Function for the button in the parent:
const openAll = ref(false);
function expandAll() {
openAll.value = !openAll.value;
}
And than in the CollapsiblePanel script setup:
const props = defineProps({openAll: Boolean});
const currentOpen = ref(false);
/// Call with click on child:
const toggleIndividual = () => {
currentOpen.value = !currentOpen.value;
}
/// If prop changes (expand all button is clicked) open child
watch(props.openAll) {
currentOpen.value = true;
}
And in the CollapsiblePanel template a v-if="currentOpen" on the div that needs to open and close. You can put #click="toggleIndividual" on the element that you want to click to expand it.
Now if you toggle expand all, all childs will expand. If you click an individual child it will collaps.
Hope this helps.

What is a suspensible component in Vue 3?

I'm reading this article: https://v3.vuejs.org/guide/component-dynamic-async.html#using-with-suspense
It's referring a concept called "suspensible" component.
I have researched, but I can't find any information about what is a so called "suspensible" component.
Can anyone explain what it is? Thanks!
"Suspensible" means replaceable by fallback content while parent <Suspense> resolves async child components found in its <template #default>.
The concept is borrowed from React's Suspense API.
In more detail, <Suspense> is a built-in Vue 3 component which renders a <template #fallback> instead of the <template #default>, until all async child components in default template are resolved.
In order to be suspensible, a component's rendering needs to depend on a promise:
be loaded using () => import('some/path')
or use an async/await (or any other form of Promise syntax) in its setup function
A suspensible component is suspensed when included in a <Suspense>'s default template, while its parent <Suspense> has not resolved all its suspensible components, even if the suspensed component itself has already resolved.
Obviously, <Suspense> components themselves are suspensible and suspensing can be nested.
Here's a more detailed explanation on <Suspense> in Vue 3.
Among other usages, <Suspence> provides an elegant and intuitive way to resolve the common problem of having to wrap child components and templates in v-if guarding against non-existent properties on data which has not yet been loaded.
A typical Vue 2 example:
Vue.config.devtools = false;
Vue.config.productionTip = false;
Vue.component('render-items', {
props: ['items'],
template: `<table>
<tr>
<th>Id</th>
<th>User Id</th>
<th>Title</th>
</tr>
<tr v-for="(item, key) in items" :key="key">
<td v-text="item.id"></td>
<td v-text="item.userId"></td>
<td v-text="item.title"></td>
</tr>
</table>`
});
new Vue({
el: '#app',
data: () => ({
items: []
}),
computed: {
hasData() {
return this.items.length;
}
},
async created() {
const items = await fetch('https://jsonplaceholder.typicode.com/posts')
.then(r => r.json());
this.items = items;
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.12/vue.js"></script>
<div id="app">
<render-items :items="items" v-if="hasData"></render-items>
<template v-else>loading...</template>
</div>
Same example (more or less) in Vue 3, using <Suspense> and async setup:
const RenderItems = Vue.defineComponent({
async setup() {
const items = await fetch('https://jsonplaceholder.typicode.com/posts')
.then(r => r.json());
return Vue.reactive({ items });
},
template: `<table>
<tr>
<th>Id</th>
<th>User Id</th>
<th>Title</th>
</tr>
<tr v-for="(item, key) in items" :key="key">
<td v-text="item.id"></td>
<td v-text="item.userId"></td>
<td v-text="item.title"></td>
</tr>
</table>`
});
const App = { components: { RenderItems }};
Vue.createApp(App).mount('#app');
<script src="https://unpkg.com/vue#next/dist/vue.global.prod.js"></script>
<div id="app">
<Suspense>
<template #default>
<render-items></render-items>
</template>
<template #fallback>
loading...
</template>
</Suspense>
</div>
One major advantage is in the Vue 3 example we can contain the data fetcher (and the data) in the child component. This is not possible in Vue 2, because:
the sub-component is only created after data has loaded
the parent needs to know when the condition changed (so it needs access to the actual condition) in order to switch between rendering fallback content or the child component.
The simplest way to do it in Vue 2 is actually to load the data in parent and pass the result to the child component, via props. If you have a lot of sub-components, this pattern can get messy.
In Vue 3, the responsibility for loading the data and checking the condition can rest entirely with the child-component. Parent doesn't need access to the actual condition.
Just like <template>, <Suspense> does not create a DOM element.
In the above Vue 3 example, <RenderItems /> is suspensible.
A suspensible component would be one that is capable of using the new item in Vue 3. A suspense item it something that loads and may take a longer time to load, like an API call. Generally you would be using async/await inside of items that are inside of the suspense item.
A lot of good info here: https://v3.vuejs.org/guide/component-dynamic-async.html#async-components
You would use a suspense item to say while items inside of the suspense item are being awaited show something else (like a skeleton loader).

Are Vue component slots not considered children?

Lets say that I have a parent component, Tabs, which takes in child Tab Components as slots.
So the setup is like this:
Tabs (Parent)
<div>
<slot name="tabChild">
</div>
Tab (Child)
<div>
Tab {{name}}
</div>
MyApp
<Tabs>
<Tab slot="tabChild" name="1" ></Tab>
<Tab slot="tabChild" name="1" ></Tab>
</Tabs>
However, in the Tabs (Parent) component, when I try to programmatically access its children, like this:
Tabs Component
mounted(){
let childTabs = this.$children //this is empty??
childTabs = this.$slots //this is correctly the child Tab Components
}
Moreover, in the Tab (Child) component, when I try to access its parent, which I thought was the Tabs component (since they are slotted within it), it is not:
Tab Component
mounted(){
let parentTab = this.$parent //this is MyApp (grandfather), NOT Tabs
}
Why are the tab child components slotted within the greater Tabs component not its children?
Well, $parent will always refer to the "direct" parent/wrapper a particular component is nested inside, so it's not quite reliable when the need to refer to the "root" parent arises.
Here are some good excerpts from the official docs:
In most cases, reaching into the parent makes your application more difficult to debug and understand, especially if you mutate data in the parent. When looking at that component later, it will be very difficult to figure out where that mutation came from. [1]
Use $parent and $children sparingly as they mostly serve as an escape-hatch. Prefer using props and events for parent-child communication. [2]
Implicit parent-child communication: Props and events should be preferred for parent-child component communication, instead of this.$parent or mutating props.
An ideal Vue application is props down, events up. Sticking to this convention makes your components much easier to understand. [3]
Unfortunately, using the $parent property didn’t scale well to more deeply nested components. That’s where dependency injection can be useful, using two new instance options: provide and inject. [4]
Here's a quick example that sort of demonstrates the above recommendations:
const Parent = Vue.extend({
template: `
<ul :style="{ columnCount: colCount }">
<slot></slot>
</ul>
`,
provide() {
return {
biologicalParent: this
}
},
props: ['value', 'colCount', 'message'],
methods: {
sayIt() {
const
num = this.$children.indexOf(this.value) + 1,
message = [
`I am child no. ${num}`,
`among ${this.$children.length} of my siblings`,
`in ${this.colCount} different lineages.`
]
.join(' ');
this.$emit('update:message', message);
}
},
watch: {
value: 'sayIt'
}
});
const Child = Vue.extend({
template: `<li v-text="name" #click="setChild"></li>`,
props: ['name'],
inject: ['biologicalParent'],
methods: {
setChild() {
this.biologicalParent.$emit('input', this);
}
}
});
new Vue({
el: '#demo',
data: () => ({
lineage: 3,
childCount: 10,
message: 'Click on a child for the associated message.',
lastChild: undefined
}),
components: {
Parent,
Child
}
});
input {
width: 4em;
}
li {
cursor: pointer;
}
.message {
background-color: beige;
border: 1px solid orange;
padding: 3px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="demo">
<table>
<tr>
<td>Child count:</td>
<td><input type="number" v-model="childCount" /></td>
</tr>
<tr>
<td>Lineage:</td>
<td><input type="number" v-model="lineage" /></td>
</tr>
</table>
<parent
v-model="lastChild"
:col-count="lineage"
:message.sync="message">
<child
v-for="(n, i) of Array.apply(null, {length: childCount})"
:key="i"
:name="`child #${i+1}`">
</child>
</parent>
<p class="message">{{ message }}</p>
</div>
The provide options allows us to specify the data or methods we want to provide to descendant components. In the above example, that's the Parent instance.
Notice how the Child index/number is evaluated at runtime (when clicked) instead of compile time (rendering phase).

How to set $ref to child component elements from parent component in vuejs?

This is the parent component:
<template>
<upload-block
:imSrc="LargeIcon"
inputName="LargeIcon"
:inputHandler="uploadAppIcon"
inputRef="LargeIcon"
:uploadClickHandler="handleUploadIcon"></upload-block>
</template>
<script>
export default class ParentCom extends Vue {
//all props for <upload-block></upload-block> component defined here
handleUploadIcon(event) {
const icon_type = event.currentTarget.getAttribute("data-type");
let appImgElem = this.$refs[icon_type];
appImgElem.click();
}
async uploadAppIcon(event) {
//code
}
}
</script>
And this is the child component:
<template>
<div class="upload-div" #click="uploadClickHandler" :data-type="inputName">
<img v-if="imSrc" :src="imSrc">
<div v-else class="upload-icon-block">
<span>
<font-awesome-icon class="upload-icon" icon="arrow-circle-up" size="lg"></font-awesome-icon>
<br>Click to upload
</span>
</div>
<!-- <spinner variant="primary" :show="true"></spinner> -->
<input style="display:none" type="file" :ref="inputRef" :name="inputName" #input="inputHandler">
</div>
</template>
<script>
#Component({
props: {
imSrc: String,
inputRef: String,
inputName: String,
inputHandler: Function,
uploadClickHandler: Function
}
})
export default class ChicdCom extends Vue {
}
</script>
The problem I am facing in the handleUploadIcon method in which I am not able to get the input element via ref.
It is showing Cannot read property 'click' of undefined in this line appImgElem.click();
But when I move the file input to the parent component, it's works fine. So can you plz help me how to set the ref to child component elements from parent as currently is it not setting.
Thanks
Well you could add a ref to upload-block in the parent component:
<upload-block ref="upload" ... >
Then in the handleUploadIcon you can acces your input: this.$refs.upload.$refs[icon_type]
But I would try to move handleUploadIcon to the child component if I were you.

autohide an element in VueJS after 1 second

OK, so I'm new to Vue ( basically, new to JS in general, but I'm playing with Vue right now ) and what I want to do, is to auto hide an element ( not on click ) inside the template tag of a component. In jQuery this would look like:
$(function() {
setTimeout(function() {
$(".hideElement").hide()
}, 1000);
});
but how this works in Vue? my component looks like this:
<template>
<div>
<h1 class="hideElement"> HELLO </h1>
</div>
</template>
<script> // nothing here
</script>
<style> // nothing here
</style>
I know how to toggle the element on click of a button, but I just want to auto hide it after 1 second without any click events everytime the users enter this component ( which is a new "page" )
You could just add a property in the data object and use v-show directive to determine whether the element should be visible or not. If the boolean is false the element is hidden, if true the element is visible.
Method Created called synchronously after the instance is created.
<template>
<div>
<h1 v-show="elementVisible" class="hideElement"> HELLO </h1>
</div>
</template>
<script>
export default {
data() {
return {
elementVisible: true
}
},
created() {
setTimeout(() => this.elementVisible = false, 1000)
}
}
</script>