Nested components in app.vue not pulling content through - javascript

main.js file:
import Vue from 'vue'
import App from './App.vue'
import Parent from './assets/components/frame/Parent.vue';
import LeftSide from './assets/components/frame/LeftSide.vue';
import RightSide from './assets/components/frame/RightSide.vue';
import HeaderLeft from './assets/components/header/HeaderLeft.vue';
import HeaderRight from './assets/components/header/HeaderRight.vue';
Vue.component('Parent', Parent);
Vue.component('LeftSide', LeftSide);
Vue.component('RightSide', RightSide);
Vue.component('HeaderLeft', HeaderLeft);
Vue.component('HeaderRight', HeaderRight);
new Vue({
el: '#app',
render: h => h(App)
})
Non working app.vue file, no data appears on web page. When examining the page via dev tools, the parent div is there, but it's empty. I'm expecting the LeftSide and RightSide to be nested inside of it.
<template>
<div>
<Parent>
<LeftSide>
</LeftSide>
<RightSide>
</RightSide>
</Parent>
</div>
</template>
<script>
</script>
<style>
</style>
When modified to the below (exclude 'Parent'), to have no nested components, the data (left side and right side) outputs to the web page fine.
<template>
<div>
<LeftSide>
</LeftSide>
<RightSide>
</RightSide>
</div>
</template>
<script>
</script>
<style>
</style>
Parent component below
<template>
<div class="parent">
</div>
</template>
<script>
</script>
<style>
.parent
{
display: flex;
}
</style>

I fixed it by moving LeftSide and RightSide into the Parent.vue, instead of having the LeftSide and RightSide inside the Parent in the app.vue.

As you already figured out yourself you should move those components into the Parent component.
But there are situations where you want to have a reusable component as decorator (something like a panel) and place elements inside of it. In this case you are looking for slots. Using slot you can indeed write this:
<Parent>
<LeftSide />
<RightSide />
</Parent>
And in the Parent component you would need to define where those components have to be placed using <slot></slot>
<template>
<div class="parent">
<slot></slot>
</div>
</template>
<script>
</script>
<style>
.parent
{
display: flex;
}
</style>

Related

Vuejs Single-file-component not Rendered in Page

No errors in browser, Webpack compiles successfully, but the "hello from dashboard" doesn't show up in the page.
I'm using Vue v2.6
main.js
import Vue from 'vue'
Vue.component('dashboard', require('#comp/dashboard.vue').default);
const app = require('#/App.vue').default; // this app component works fine
import "./css/app.css"
new Vue({
render: h => h(app) // this app component works fine
}).$mount('#app')
dashboard.vue
<template>
<div>
Hello from dashboard
</div>
</template>
<script>
export default {
name: "dashboard"
}
</script>
index.html
<body>
<div id="app">
<dashboard></dashboard>
</div>
</body>
This is the rendered HTML from the browser, However, "hello from dashboard" is not there :(
<body>
<div id="app">
<dashboard></dashboard>
</div>
</body>
You have the root file "App.vue" mounted in the div id = "app", so the "dashboard" needs to be added to App.vue to see its contents.
// App.vue
<template>
<dashboard />
</template>
<script>
export default {};
</script>
<style scoped></style>
In this code, you have connected a component globally, which will be available in App.vue or another child component
Vue.component('dashboard', require('#comp/dashboard.vue').default);
I assume its the path you are missing so i added a / before comp. Because you stated the App.vue works.
Vue.component('dashboard', require('#/comp/dashboard.vue').default);

Vue doesn't render one of the components. No errors, even shows in DOM, looks evrything right for me

I have two components and one of them doesnt want to render. It even appears in DOM. I would think I messed up some paths and so on, but if it shows in DOM then evrything is alright. I dont event know what part of code to attach over here for you to view.. so will attach
picture of a DOM
the beggining of my App.vue
<template>
<div>
<field-modal></field-modal>
<form> // All below just renders fine
<div class="progress">
<div class="progress-bar" :style="progressBar"></div>
</div>
<fileds-registration v-for="(field, i) in info">
</fileds-registration>
<button class="btn btn-primary" :disabled="!isButton">
Send Data
</button>
</form>
</div>
</template>
<script>
import FiledsRegistration from './components/Action.vue';
import FieldModal from './components/Modal.vue';
export default {
components: {
FiledsRegistration,
FieldModal
},
data(){ .......
And my whole component Modal.vue file which doesnt want to render
<template>
<div class="modal">
<p>Here we go</p>
</div>
</template>
<script>
export default
{
components: {
},
data(){
return {
}
}
}
</script>
<style scoped>
.modal
{
width: 500px;
height: 500px;
background: blue;
}
<style>

Vue: Create a parent component that can render a child component

How can I create a parent component that can receive and render a child component?
For example:
<Form>
<Input/>
<Input/>
<Select/>
<Button/>
<Form/>
You can use slots.
Parent.vue
<template>
<form>
<slot/> <!-- The child component will be rendered here -->
</form>
</template>
Child.vue
<template>
<div>
<input/>
<input/>
</div>
</template>
Grandparent.vue
<template>
<div>
<Parent> <!-- Parent component -->
<Child/> <!-- Pass child component to it as slot -->
</Parent>
</div>
</template>
<script>
import Parent from './Parent.vue'
import Child from './Child.vue'
components: {
Parent,
Child
}
</script>
You should import the components as follows:
import Select from "../components/Select";
import Button from "../components/Button";
import Input from "../components/Input";
And then register them in the parent as follows:
components: {
Select,
Button,
Input
},
You can send your child components to another component, say parent component, using slots.
You need to assign a name to the child components like
<Form>
<Input name="input1"/>
<Input name="input2"/>
<Select name="select"/>
<Button name="button"/>
<Form/>
Then inside the parent component, you can receive it like
<template>
<form>
<slot name="input1"/>
<slot name="input2"/>
<slot name="select"/>
<slot name="button"/>
</form>
</template>
The slots with corresponding names will be replaced with the child components.

Template content is not being injected into slot

I'm trying to use slots to inject content from a parent component to its child, but Vue keeps rendering the default content, not parsing the content sent from its parent.
This is the code of the parent component, which in turn is a child of a global component:
let parentComponent = {
template: `
<div>
<child-component>
<template v-slot:action>Close</template>
<template v-slot:element>Modal</template>
</child-component>
</div>
`,
components: {
'child-component': childComponent
}
};
And here is its child component, where I want to pass content:
let childComponent = {
template: `
<button>
<slot name="action">Open</slot>
<slot name="element">Window</slot>
</button>
`,
};
The button is still displaying the default content: "Open Window"
What am I doing wrong?
EDIT:
This is the rest of the content, just in case it helps:
index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>VueJS</title>
</head>
<body>
<div id="app">
<vue-directives></vue-directives>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.16/dist/vue.js"></script>
<script src="components/slotDirective/slotDirectiveSubcomponent.js"></script>
<script src="components/slotDirective/slotDirective.js"></script>
<script src="components/VueDirectives.js"></script>
<script>
let vue = new Vue({
el: '#app'
});
</script>
</body>
</html>
And VueDirectives.js:
Vue.component('vue-directives', {
template: `
<div>
<h3>{{ title }}</h3>
<parentComponent/>
</div>
`,
data() {
return {
title: "VueJS directives",
}
},
components: {
parentComponent
}
});
I fixed the error. I don't know the reason, but it wasn't working either if I loaded Vue.js from a CDN, or if I manually downloaded it locally, as a single file.
Then I finally tried installing it from npm, and loading Vue.js in node_modules/vue/dist/vue.js, and this way it works. I assume otherwise Vue.js does not comply with all its functionality.
I'm not sure that slots are designed for this. If you are just changing the text of a button passing props into the parentComponent is how i would go about it. E.g.
<parentComponent buttonText="some text or bind with a data value or computed prop"/>
see: https://v2.vuejs.org/v2/guide/components-props.html
Using slots... If you are experimenting try this, a reusable dialog box that you can pop in anywhere and control the content. E.g.
// myDialog
<v-dialog>
<slot>Here you can put what you want</slot>
</v-dialog>
And to use:
<myDialog>
<template>
<myContent /> Or just put content here without another component
</template>
</myDialog>

How to update a slot in Vue.JS

I have a Vue component simplified below.
Here is the template
<template>
<slot></slot>
</template>
The slot may contain HTML, which is why I decided to use a slot rather than a prop which I would simply bind to. I'd like to keep it that way.
I have a method that gets new HTML from the server. I'd like to use this new HTML to update the slot. I'm not sure if slots are reactive and how I can accomplish this.
I can view the default slot using this.$slots.default[0], but I don't know how to update it with a string of HTML content. Simply assigning the string to the element is obviously incorrect, to .innerHtml does not work because it isn't an available function, and to .text doesn't work. I assume that even though the text element exists on the slot object, the element properties take precedence.
Per suggestion in comments, I've tried this along with a computer property.
<span v-html="messageContent"><slot></slot></span>
But now the problem is that it overwrites the slot passed to me.
How can I reactively update a slot with new HTML in Vue.JS?
I think your issue comes from a misunderstanding of how <slot> inherently works in VueJS. Slots are used to interweave content from a consuming parent component into a child component. See it as a HTML equivalent of v-bind:prop. When you use v-bind:prop on a component, you are effectively passing data into a child component. This is the same as slots.
Without any concrete example or code from your end, this answer is at best just guess-work. I assume that your parent component is a VueJS app itself, and the child component is the one that holds the <slot> element.
<!-- Parent template -->
<div id="app">
<custom-component>
<!-- content here -->
</custom-component>
</div>
<!-- Custom component template -->
<template>
<slot></slot>
</template>
In this case, the app has a default ground state where it passes static HTML to the child component:
<!-- Parent template -->
<div id="app">
<custom-component>
<!-- Markup to be interweaved into custom component -->
<p>Lorem ipsum dolor sit amet.</p>
</custom-component>
</div>
<!-- Custom component template -->
<template>
<slot></slot>
</template>
Then, when an event is fired, you want to replace that ground-state markup with new incoming markup. This can be done by storing the incoming HTML in the data attribute, and simply using v-html to conditionally render it. Let's say we want to store the incoming markup in app's vm.$data.customHTML:
data: {
customHTML: null
}
Then your template will look like this:
<!-- Parent template -->
<div id="app">
<custom-component>
<div v-if="customHTML" v-html="customHTML"></div>
<div v-else>
<p>Lorem ipsum dolor sit amet.</p>
</div>
</custom-component>
</div>
<!-- Custom component template -->
<template>
<slot></slot>
</template>
Note that in contrast to the code you have tried, the differences are that:
It is the parent component (i.e. the consuming component) that is responsible for dictating what kind of markup to pass to the child
The child component is as dumb as it gets: it simply receives markup and renders it in the <slot> element
See proof-of-concept below:
var customComponent = Vue.component('custom-component', {
template: '#custom-component-template'
});
new Vue({
el: '#app',
data: {
customHTML: null
},
components: {
customComponent: customComponent
},
methods: {
updateSlot: function() {
this.customHTML = '<p>Foo bar baz</p>';
}
}
});
.custom-component {
background-color: yellow;
border: 1px solid #000;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.min.js"></script>
<div id="app">
<h1>I am the app</h1>
<button type="button" #click="updateSlot">Click me to update slot content</button>
<custom-component>
<div v-if="customHTML" v-html="customHTML">
</div>
<div v-else>
<p>Lorem ipsum dolor sit amet.</p>
</div>
</custom-component>
</div>
<!-- custom-component template -->
<script type="text/template" id="custom-component-template">
<div class="custom-component">
<h2>I am a custom component</h2>
<!-- slot receives markup set in <custom-component> -->
<slot></slot>
</div>
</script>
Below is my solution though I don't like this opinion (load html into slot directly in current component level) because it breaks the rules for the slot. And I think you should do like this way (<component><template v-html="yourHtml"></template></component>), it will be better because Slot will focus on its job as Vue designed.
The key is this.$slots.default must be one VNode, so I used extend() and $mount() to get the _vnode.
Vue.config.productionTip = false
Vue.component('child', {
template: '<div><slot></slot><a style="color:green">Child</a></div>',
mounted: function(){
setTimeout(()=>{
let slotBuilder = Vue.extend({
// use your html instead
template: '<div><a style="color:red">slot in child</a></div>',
})
let slotInstance = new slotBuilder()
this.$slots.default = slotInstance.$mount()._vnode
this.$forceUpdate()
}, 2000)
}
})
new Vue({
el: '#app',
data() {
return {
test: ''
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>
<div id="app">
<child><h1>Test</h1></child>
</div>

Categories