I'm quite new in Vue.js. I do some simple exercises about communication between Vue components. But I still have a problem who is a child and who is a parent. For example I have this code:
HTML:
<body>
<div id="root" class="component">
<coupon #applied="onCouponApplied"></coupon>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.5/css/bulma.css"
/>
<!-- <script src="app.js"></script> -->
<script src="main.js"></script>
</body>
VUE.JS:
Vue.component("coupon", {
template: `
<div>
<input palceholder="Put your name" #blur="onCouponApplied"/>
</div>
`,
data() {
return {
message: ""
};
},
methods: {
onCouponApplied() {
this.$emit("applied");
}
}
});
new Vue({
el: "#root",
methods: {
onCouponApplied() {
alert("that's work!");
}
}
});
So.. here I have component coupon and new Vue. I guess that the new Vue is a parent. But... I try to understand, how it is work. Can anyone explain to me very simply how it works? I read the vue.js documentation, but still have a problem :/
All components refer to the Vue instance you mentioned. So your coupon component is a child of your root div. The parent component is the component that embeds another component.
A "UserListView" would have a list component which embeds user components. The view is the parent of the list and the list could be the parent of the user components.
Your whole app consists of components. Every Vue app has at least one component that is the parent component (Vue instance).
Every other component that you make becomes either direct or indirect child of the Vue instance.
From official docs:
A Vue application consists of a root Vue instance created with new
Vue, optionally organized into a tree of nested, reusable components.
The root div is the parent. You are creating a new Vue instance ( new Vue({ ) and associating it with the div element tagged with an id of 'root' ( el: "#root", ). The coupon component is contained within this div element, making it a child.
Related
I'm a bit new to the component's world and trying to figure out a thing, how the parent child relationship works in components. I've seen some examples of some component libraries where they have some parent child components to be defined and those are used as the child components. For example, table and tr:
<my-table> <!-- Parent -->
<my-tr> </my-tr> <!-- Child -->
</my-table>
Now, I assume, that child works for parent via slots. So the parent should be defined something like this:
<template>
<div>
<slot></slot>
</div>
</template>
Now the parent element can have multiple <my-tr> as well. And slot should be rendering all of those. However, I am trying to a similar thing but a little more complex than that.
I am trying to create a slider with this approach. Where there is a my-slider component and my-slider-item components used to define inside my-slider component. And then I want to control the visibility of the child components defined in the parent component slot by modifying it's properties.
It should be looking like this:
<my-slider>
<my-slider-item>Item 1</my-slider-item>
<my-slider-item>Item 2</my-slider-item>
<my-slider-item>Item 3</my-slider-item>
</my-slider>
my-slider component
<template>
<div class="my-slider">
<slot></slot>
</div>
</template>
my-slider-item component
<template>
<div class="my-slider__item">
<slot></slot>
</div>
</template>
Now how can I know in the parent that how many <my-slider-item> are defined in the parent slot and based on that I want to control the visibility of the child 1 at a time as it is going to work as the slider.
I'm not sure but I missing some basic concept here which I was not getting after looking at tons of example since yesterday. If anyone can please help here? Thanks a lot!
The parent-child relationship is actually established by importing the child component into the parent component, and including the child in the parent's 'components' option.
I created an example scenario with simple Parent and Child component definitions in order to show a standard relationship implementation. Built with Vue 2 and the Vue CLI.
MySlider.vue (parent)
<template>
<div class="my-slider">
<h4>My Slider</h4>
<my-slider-item v-for="(item, index) in sliderItems" :key="index" :sliderItem="item" />
</div>
</template>
<script>
import MySliderItem from './MySliderItem.vue'
export default {
components: {
MySliderItem
},
data() {
return {
sliderItems: [
{
name: 'Slider Item 1'
},
{
name: 'Slider Item 2'
},
{
name: 'Slider Item 3'
}
]
}
}
}
</script>
MySliderItem.vue (child)
<template>
<div class="my-slider-item">
<h5>{{ sliderItem.name }}</h5>
</div>
</template>
<script>
export default {
props: {
sliderItem: {
type: Object,
required: true
}
}
}
</script>
In my JSFiddle example, I'm trying to define a custom Vue component: https://jsfiddle.net/50wL7mdz/402951/ .
Unfortunately, nothing is rendered. Why?
The code:
HTML:
<script src="https://unpkg.com/vue"></script>
<blog-post title="hi again!"></blog-post>
JS:
Vue.component('blog-post', {
props: ['title'],
template: '<h3>{{ title }}</h3>'
})
You forgot to create a Vue. Wrap the <blogpost> component into a div and create a Vue using that div as the template.
Like so
HTML:
<script src="https://unpkg.com/vue"></script>
<div id="app">
<blog-post title="hi again!"></blog-post>
</div>
JS
Vue.component('blog-post', {
props: ['title'],
template: '<h3>{{ title }}</h3>'
})
// create a new Vue instance and mount it to our div element above with the id of app
var vm = new Vue({
el: '#app'
});
Look at the documentation documentation
Here is the working fiddle
My friend I recommend you another option, that I preferred. Please use, if you want, sandbox where you can add or modify components much much easier.
Here is the sandbox link.
How can I write my component to wrap another vue component, while my wrapper component get some extra props? My wrapper template component should be:
<wrapper-component>
<v-table></v-table> <!-- pass to v-table all the props beside prop1 and prop2 -->
</wrapper-component>
and the wrapper props:
props: {
prop1: String,
prop2: String
}
Here I want to wrap a table component, and pass to the table component all the props and events that were passed to the wrapper, beside two extra props prop1 and prop2. What is the correct way of doing this in vue?
And is there a solution for events too?
Place the component you wish to wrap into the template of the wrapper component, add v-bind="$attrs" v-on="$listeners" to that component tag, then add the inner component (and, optionally, inheritAttrs: false) to the wrapper component's config object.
Vue's documentation doesn't seem to cover this in a guide or anything, but docs for $attrs, $listeners, and inheritAttrs can be found in Vue's API documentation. Also, a term that may help you when searching for this topic in the future is "Higher-Order Component" (HOC) - which is basically the same as your use of "wrapper component". (This term is how I originally found $attrs)
For example...
<!-- WrapperComponent.vue -->
<template>
<div class="wrapper-component">
<v-table v-bind="$attrs" v-on="$listeners"></v-table>
</div>
</template>
<script>
import Table from './BaseTable'
export default {
components: { 'v-table': Table },
inheritAttrs: false // optional
}
</script>
Edit: Alternatively, you may want to use dynamic components via the is attribute so you can pass in the component to be wrapped as a prop (closer to the higher-order component idea) instead of it always being the same inner component. For example:
<!-- WrapperComponent.vue -->
<template>
<div class="wrapper-component">
<component :is="wraps" v-bind="$attrs" v-on="$listeners"></component>
</div>
</template>
<script>
export default {
inheritAttrs: false, // optional
props: ['wraps']
}
</script>
Edit 2: The part of OP's original question that I missed was passing all props EXCEPT one or two. This is handled by explicitly defining the prop on the wrapper. To quote the documentation for $attrs:
Contains parent-scope attribute bindings (except for class and style) that are not recognized (and extracted) as props
For example, example1 is recognized and extracted as a prop in the snippet below, so it doesn't get included as part of the $attrs being passed down.
Vue.component('wrapper-component', {
template: `
<div class="wrapper-component">
<component :is="wraps" v-bind="$attrs" v-on="$listeners"></component>
</div>
`,
// NOTE: "example1" is explicitly defined on wrapper, not passed down to nested component via $attrs
props: ['wraps', 'example1']
})
Vue.component('posts', {
template: `
<div>
<div>Posts component</div>
<div v-text="example1"></div>
<div v-text="example2"></div>
<div v-text="example3"></div>
</div>
`,
props: ['example1', 'example2', 'example3'],
})
new Vue({
el: '#app',
template: `
<wrapper-component wraps="posts"
example1="example1"
example2="example2"
example3="example3"
></wrapper-component>
`,
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app"></div>
Is it possible to use v-model on an element in the DOM that is outside the root element of the Vue instance?
I.e, Could you have the following:
$(function(){
Vue.component('custom-element', {
template: '#custom-template'
})
var vm = new Vue({
el: "#app"
data: {
selected: ""
}
})
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.13/dist/vue.js"></script>
<select id="SportSelector" v-model="selected">
<option value="football"> Football </option>
<option value="tennis"> Tennis </option>
</select>
<div id="app">
<custom-element>
</custom-element>
</div>
<script type="text/x-template" id="custom-template">
<div>
<p> {{selected}} </p>
</div>
</script>
Where the select is outside the root element ("app") of the Vue. This is a simplified version of my problem, but I believe highlights the difficulties I'm having.
I saw this answer talking about centralised state management for sharing data between Vues but that seems a bit heavy to have to wrap the select in an entirely different Vue, I feel like i'm missing something major! (still very new to Vue). Does everything the Vue interacts with have to be under the root element of the instance of the Vue?
No, you cannot use v-model on elements outside the context of the Vue. The reason is Vue compiles down to a render function that renders the contents of the element it controls; it doesn't render anything outside that element, unless you involve specific code or a library like vue-portal.
That said, you can update Vue when the select changes using standard javascript set the Vue data property. I also cleaned up a few things like passing the selected data to your component (which is necessary; components cannot access their parents data directly).
$(function(){
Vue.component('custom-element', {
props: ["selected"],
template: '#custom-template'
})
var vm = new Vue({
el: "#app",
data: {
selected: ""
}
})
//get a reference to the select
const sports = document.querySelector("#SportSelector")
//add a listener that updates the Vue data when the select changes
sports.addEventListener("change", evt => vm.selected = evt.target.value)
//initialize Vue with the current selected value
vm.selected = sports.value
})
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.13/dist/vue.js"></script>
<select id="SportSelector">
<option value="football"> Football </option>
<option value="tennis"> Tennis </option>
</select>
<div id="app">
<custom-element :selected="selected">
</custom-element>
</div>
<script type="text/x-template" id="custom-template">
<div>
<p> {{selected}} </p>
</div>
</script>
You can use portal-vue to accomplish this. It allows you to place the code from your component anywhere in the DOM.
Add it your view instance:
import PortalVue from 'portal-vue';
Vue.use(PortalVue);
In your component you define the content you want to render outside the component:
<portal to="destination">
<p>This slot content will be rendered wherever the <portal-target> with name 'destination'
is located.</p>
</portal>
You can then place this anywhere in the DOM:
<portal-target name="destination">
<!--
This component can be located anywhere in your App.
The slot content of the above portal component will be rendered here.
-->
</portal-target>
Example code from https://github.com/LinusBorg/portal-vue
Unless you have some funky interaction going on with the select, add it to your Vue instance. The Vue instance should encapsulate all your templates and logic for the form.
If you're trying to mix jQuery and Vue on the same element you're probably not going to have a good time.
I'm currently rendering all the html server-side and I'm trying to get vue to use this html as the $el for the following two components. As far as I understand from the lifecycle diagram, this should work.
There is a parent Vue instance (which binds to #main) and contains a child component (via the <mod-sidebar> element in the html).
Now I get the following error message:
[Vue warn]: v-on:click="gotoSlide" expects a function value, got undefined
And the v-text directive isn't working either, even though the data is clearly defined.
Thus, the issue seems to be, that the parent instance already compiles all of the directives in the child component as its own (e.g. v-text in the child component is already compiled by the parent, before the child is initialized it seems).
Is there any way to prevent that from happening, so that the directives within the custom child tag <mod-sidebar> are only compiled by the child element?
Vue.component(`mod-sidebar`, {
ready: function() {
// initialize slider
},
data: function() {
return {
name: 'slider',
};
},
methods: {
gotoSlide: function(event) {
return false;
},
},
replace: false
});
new Vue({
el: '#main'
});
<main class="skel-main" id="main" role="main"> <!-- parent -->
<mod-sidebar class="sidebar"> <!-- child -->
<div class="sidebar--slider">
<div class="sidebar--slider-item">
<a class="sidebar--slider-link" href="#" #click="gotoSlide">
<span class="sidebar--slider-text" v-text="name"></span>
</a>
</div>
</div>
</mod-sidebar>
</main>
http://jsfiddle.net/gtmmeak9/27/
You are missing inline-template
<mod-sidebar class="sidebar" inline-template>