How to make a 'Container' component in Vue.js - javascript

I'm new to Vue and cannot find a way to implement a React-like 'Wrapper' component with Vue.js, for example, a reusable datagrid component using a 3rd-party some-table component and a pagination component. Intuitively, datagrid will provide the data/props/events that both components need and control the communication between them. In React, this could be done as simply as something like
// inside <Datagrid />
<Table {...someProps} />
<Pagination {...otherProps} />
With Vue, It seems like something like below can only pass props down to children components
// inside Datagrid.vue
<some-table v-bind="$props"></some-table>
I'm not sure if slots could be of help. This wrapper component that I've been struggling for takes all the props/events/slots its children need and pass them down to them so that I could utilize all the functionality that it's children(which probably some 3rd-party components) provide. Moreover, it may also take responsibility for something like data exchange between its children. While datagrid could be a slots wrapper, but what if both table and pagination require a same data prop which I think should reside in datagrid. How to pass this data down to the slots?
// Datagrid.vue
<template>
<div>
<slot name="table"></slot>
<slot name="pagination"></slot>
</div>
</template>
<script>
export default {
name: 'Datagrid',
data() {
return {
data: 'How to share it with table and pagination',
}
},
}
</script>
Some solutions that I could figure out:
Render Functions, but I don't think that complicity is needed in this case
Instead of creating a 'Container' component, simply turn to mixins, but does this mean I have to input <pagination :total="total" :other-props="are-same-for-all-datagrids"></pagination> each time I want a datagrid?
Any examples dealing with such situations in Vue? Thanks in advance!

You want to use slots:
Vue.component('wrapper', { template: '#wrapper' })
new Vue({ el: '#app' })
<!-- wrapper template -->
<script type="text/x-template" id="wrapper">
<div class="wrapper" style="background: beige; padding: 5px;">
This is being wrapped:
<slot></slot>
</div>
</script>
<div id="app">
<wrapper>
<div style="background: aliceblue;">I'm being wrapped!</div>
</wrapper>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.3.3/vue.js"></script>

Related

Nuxtjs: How to call component inside another component?

I understand paradigm "Page-component" but what if I have a page that renders component, how do I call another component inside this component? Currently nuxtjs does not allow me do it. I can not stick to standart "page-component" scheme as I am bulding cart which calls cart-items.
Say If a cart component which is called by page looks like this, how would it call cart-item component inside it?
<!---- cart component called from index.vue --->
<template>
<div>
<Cart-item></Cart-item> < ---------- This doesn't work.
</div>
</template>
<script>
export default {
props: ['items']
}
</script>
I managed it the standard way:
<template>
<div>
<CartItem></CartItem>
</div>
</template>
<script>
import CartItem from '../components/Cart-item'
export default {
props: ['items']
}
</script>
Since nuxtjs auto-registers all components wonder if there is more graceful way.
EDIT: as promised, here is an example on how to pass some content to a component from another one thanks to slots. This is totally working in any Nuxt page ofc.
NestedContent.vue
<template>
<div>
<p>Here is the NestedContent component and below is a slot passed to ParentWithSlots' component</p>
<hr />
<parent-with-slots>
<!-- <template #default> // this one can be omit since we do use the default slot here -->
<p>This content is inserted into the component ParentWithSlots</p>
<!-- </template> -->
</parent-with-slots>
</div>
</template>
ParentWithSlots.vue
<template>
<div>
<p>xxxxxxxxxxx ParentWithSlots' content before slot xxxxxxxxxxx</p>
<slot>Default content in case none is provided</slot>
<p>xxxxxxxxxxx ParentWithSlots' content after slot xxxxxxxxxxx</p>
</div>
</template>
Here is how it looks
PS: you may also give a try to layouts, it can be useful for overall positioning of some of your components visually.
If your components are in the components directory, you can set components: true in your nuxt.config.js and have access to it pretty much anywhere without any additional step with the <cart-item></cart-item> syntax.
More details here: https://nuxtjs.org/blog/improve-your-developer-experience-with-nuxt-components/

Render external element inside Vue component

I have an external div that I need to render inside my Vue app. I'm trying to use a slot, like but that's a no go as nothing renders.
Any ideas?
Goal is to have HTML like this (Vue mounts on #app):
<div id="app" data-slot-header="#header"></div>
<div id="header">
<h1>Title here</h1>
</div>
Then the Vue component
<template>
<div>
<slot name="header"></slot>
</div>
</template>
You can use a dynamic <component> and refer to your #header element as a template reference.
For example
new Vue({
data: () => ({
headerComponent: {
template: '#header' // refer to template element by selector
}
}),
}).$mount('#app')
#app:before,#header:before{position:absolute;top:0;right:0;color:rgba(1,1,1,.5);font-size:.8rem}#app{border:1px solid #666;position:relative}#app:before{content:'Vue app'}#header{position:relative;opacity:.5}#header:before{content:'Original header'}
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.min.js"></script>
<div id="app">
<p>Dynamic component rendered here 👇</p>
<component :is="headerComponent"></component>
</div>
<div id="header">
<h1>Title here</h1>
</div>
Slots are mainly used with reusable Vue components so that the parent component can render custom stuff inside designated sections of the child. The root component does not have a parent, so it doesn't make sense to use slots for this.
Why can't you just hard-code the div in the template? Or do you need it to be dynamic; will you be swapping out the header contents in some situations? Please provide more information about what your use-case is, otherwise my answer is "just hard-code it".
Take a look at portal-vue. It allows child components to render templates anywhere in the DOM. This might work for your situation.

Conditional Root Tag on Vue Component

My Question
I have a Vue component that renders content like so:
<template>
<div class="item">
<h1>{{ title }}</h1>
<p>{{ contents }}</p>
<!-- Lot's of other stuff... -->
</div>
</template>
<script>
// export default...
</script>
<style lang="scss">
// style...
</style>
Note the contents within the div...
In some circumstances, I need to change <div class="item"> to <a class="item">. With that in mind, is there a way to conditionally change the tag (e.g. a, div) for the root element of a Vue component?
Research
I have searched around online and was able to find something about using the render function like so:
render (createElement) {
return createElement(this.tag, {}, this.$slots.default);
}
The issue I have with the above is that it implies that I need two separate components, for example; Item.vue and ItemTag.vue. Surely there is a way to do this with one component?
I believe you could use is:
<div :is="useA ? 'a' : 'div'">
...
</div>
This isn't quite what the docs suggests it's for but it does seem to have the desired effect.
https://v2.vuejs.org/v2/api/#is
Using a render function instead wouldn't necessarily require you to have two components but it would need you to rewrite your entire template as a render function.

In VueJS, how do I use load in a view based on what is populated in my JS?

I am new to Vue.JS and require a little help as I am stuck with some logic.
I have been given a project to building and as I am under NDA I can only show comparative code examples.
In one View (Bars), which is brought into the viewport of the application using the router, I have a simple for loop.
<ul class="bar-listings">
<li v-for="bar in bars">
<router-link to="{bar.barPage}">
<div class="item">
<div class="item-bd">
<h2>{{ bar.barName }}</h2>
</div>
</div>
</router-link>
</li>
</ul>
Then in my JS file conatining all my data, I have this (Only one object for now)
export default [
{
barName: "The Tropicana Bar",
barPage: "views/bars/Tropicana",
}
];
The title displays correctly on Bars so the loop is pulling the data correctly.
However, I will have a .vue file for each bar, which also uses the data from my JS file. See below:
<template>
<div class="content-wrapper">
<h1>{{ getBarByIndex({ bars, index }).barName }}</h1>
</div>
</template>
<script>
import bars from "./../../data/bars";
export default {
data() {
return {
bars: bars
};
},
methods: {
getBarByIndex({ bars = [], index = 0 }) {
return bars[index] || {}
}
}
};
</script>
So what I need to solve is how do I make the <a v-bind:href=""> load the view for this bar?
What I think you can do, is to have a child component inside your component, you can leverage this by using nested routes in your router-view, additionally, you can pass props as an object to your router components, parent or child, whatever you need.
Basically, you will have a main router view, but inside your component, you will have another router-view which renders child components.
See: Nested Routes
See: Passing Props to Route components

Communicating variable in vue.js between components

I am building app using laravel and vue. I have navbar, currently it looks like:
<template>
<nav class="navbar">
<p>{{msg}}</p>
</nav>
</template>
And I use it like here:
<body class="">
<div id="app">
<div class="">
<navbar></navbar>
#yield('content')
</div>
</div>
</body>
In yield I am loading another components, so I have navbar and another component together. Now I want to override that {{msg}} variable from navbar in another components. In every component that variable will be diferent.
I do not know how to override it in components and from {{msg}} do some text. Can you help me? (That code above is all what I have)
If you want to use msg in other components, then you need to use prop
Use like:
props: ['msg'],
Then, you need to bind it like:
<component-name :msg="msg"></component-name>
In your component, you can take it like:
<template>{{ msg }}</template>
Hope you understand!
Components can be communicate with props. You can transfer the data to another components and you can use if statement.
https://v2.vuejs.org/v2/guide/components.html#Props

Categories