Dynamic nesting of v-for loops in Vue.js - javascript

Not sure if this is possible, but worth a shot.
I'm trying to build a system where a set of components could be dynamically rendered, in the same component, in a dynamiclly nested set of v-for loops.
To give an example.
let's say I have a series of <div/>s and a json in this structure
list : [
['Test','Test],
['Test2', 'Test2],
['Test3','Test3],
]
This would render:
<div>
<div>
<div>
Test
</div>
<div>
Test
</div>
</div>
<div>
<div>
Test2
</div>
<div>
Test2
</div>
</div>
<div>
<div>
Test3
</div>
<div>
Test3
</div>
</div>
</div>
However, if I added another set of nesting,
e.g.
list : [
['Test','Test],
['Test2', 'Test2],
['Test3',['Test4', 'Test4']],
]
It would render like this,
<div>
<div>
<div>
Test
</div>
<div>
Test
</div>
</div>
<div>
<div>
Test2
</div>
<div>
Test2
</div>
</div>
<div>
<div>
Test3
</div>
<div>
<div>
Test4
</div>
<div>
Test4
</div>
</div>
</div>
</div>
I know how this could be acomplished with a fixed level of nesting
e.g.
<div>
<div v-for="(item,index) in list" :key="index">
<div v-for="(subItem, subIndex) in item :key="subIndex">
{{subItem}}
</div>
</div>
</div>
However I am unsure on how I could make it react to a dynamic level of nesting if it had to be interpreted at runtime.
If anyone has any ideas on this I would appreciate the help.
Edit: to clarify, the solution I would be looking for would work for a unkown amoint of nesting.

Create two components <list> and <list-item> and in <list-item> call <list> recursively if item props is an array else render a normal <div> tag.
Like this:
Vue.component('list', {
props:["items"],
functional: true,
render: function(createElement,{ props, children }){
return props.items.map((item)=>createElement('list-item',{ props: {item} }))
}
});
Vue.component('list-item', {
props:["item"],
template: '<div v-if="isArray(item)"><list v-bind:items="item"> </list> </div>'+
'<div v-else> {{item}} </div>',
methods:{
isArray:function(item){
return Array.isArray(item);
}
}
});
var vm = new Vue({
el: '#app',
data:{
items:[
['Test','Test'],
['Test2', 'Test2'],
['Test3',['Test4', 'Test4']],
],
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div>
<list v-bind:items="items"></list>
</div>
</div>

In order to achieve this..you may try out the use of components and callback with own components for nested looping.
Is just an idea I not sure its work or not.

Related

React JS render list data in separate row

I am facing an issue in React Js. I want to show names data in each separate row.
const names = ['James', 'John', 'Paul', 'Ringo'[![\]][1]][1];
My Code:
return (
<div class="col-lg-8 bar-name">
<div>
{names.map(filteredName => (
<li>
{filteredName}
</li>
))}
</div>
</div>)
How can i use in div element ? What should i do?
can anyone help me?
You need to add elements to want to be displayed alongside in the List tag.
Example:
return (
<div class="col-lg-8 bar-name">
<div>
{names.map(filteredName => (
<li>
{filteredName}
<div>
Ryan
</div>
<div>
Eric
</div>
</li>
))}
</div>
)

How to dynamically render the user data in a component on button click in React

I'm new in React and got stuck on how can I achieve this.
After the user fills the field CEP from the first card and click on the button 'Gravar Dados', it shows a second card with all the information from the first card.
Template Picture:
New Result Picture:
What I need is dynamically create another card, under the second card with new info from the first card.
This is what I've done so far.
I have this component DadosPessoais.js which is the second card in the Template Picture:
render() {
return (
<div className="card shadow mt-5" >
<div className="card-header">
<span>Dados Pessoais </span>
</div>
<div className="card-body">
<div className="row">
<div className="col-4">
<div>
<span>Cep: <strong>{this.props.cep}</strong></span>
</div>
<div>
<span>Bairro: <strong>{this.props.bairro}</strong></span>
</div>
</div>
<div className="col-5">
<div>
<span>Rua: <strong>{this.props.rua}</strong></span>
</div>
<div>
<span>Cidade: <strong>{this.props.cidade}</strong></span>
</div>
</div>
<div className="col-3">
<div>
<span>UF: <strong>{this.props.uf}</strong></span>
</div>
</div>
</div>
</div>
</div>
);
}
In my Home.js I have a form with onSubmit that calls mostrarDadosPessoais function:
Function:
mostrarDadosPessoais(e) {
e.preventDefault();
this.setState({
listaDados: this.state.listaDados.concat({
cep: e.target.value,
rua: e.target.value,
bairro: e.target.value,
cidade: e.target.value,
uf: e.target.value,
}),
});
}
And my input components UserInput, Should it be like this?:
<div className="col-3">
<UserInput name="Rua" Label="Rua" Id="Rua" value={this.state.rua} Disabled />
</div>
<div className="col-3">
<UserInput name="Bairro" Label="Bairro" Id="Bairro" value={this.state.bairro} Disabled />
</div>
<div className="col-3">
<UserInput name="Cidade" Label="Cidade" Id="Cidade" value={this.state.cidade} Disabled />
</div>
<div className="col-1">
<UserInput name="UF" Label="UF" Id="UF" value={this.state.uf} Disabled />
</div>
And to show the result card I've done this:
<div className="col-md-4">
{this.state.listaDados.length === 0
? null
: this.state.listaDados.map((lista) => (<DadosPessoais {...lista} />))}
</div>
This is my state:
this.state = {
listaCidade: [],
listaDados: [],
}
Any ideas on how can I create another component and keep the values from the first one?
Thank you.
If I understand well, you need a list of elements, displayed on the right, that will show user-entered data. You need the user to be able to enter many addresses through the form on the left, and you want each address to display on the right.
I would solve this with a component that contains state for all the elements you need to display in an array that you update on a form submit event, because that's an easy event to work with when you have forms with lots of fields. You can grab the values of the components through the event object e.
class StateContainer extends Component {
constructor() {
super(props);
this.state = {
addresses: [],
}
}
mostrarDadosPessoais(e) {
e.preventDefault();
this.setState({
listaDados: this.state.listaDados.concat({
cep: e.target.cep.value,
rua: e.target.rua.value,
// etc, etc
}),
})
}
render() {
return (
<div>
<form handleSubmit={this.mostrarDadosPessoais}>
{/* left-side form */}
<input name="msg" />
<button type="submit"></button>
</form>
<div>
{/* right-side list of elements */}
{this.state.addresses.length === 0
? null
: this.state.addresses.map(
(address) => (<DadosPessoais {...address} />)
)
}
</div>
</div>
);
}
}

How to render component children at parent

I'm familiar with ReactJS, but not with VueJS.
Where can I place the component children at the parent component.
I have this example in ReactJS, how can I create the same using VueJs:
function FancyBorder(props) {
return (
<div className={'FancyBorder FancyBorder-' + props.color}>
{props.children}
</div>
);
}
function WelcomeDialog() {
return (
<FancyBorder color="blue">
<h1 className="Dialog-title">
Welcome
</h1>
<p className="Dialog-message">
Thank you for visiting our spacecraft!
</p>
</FancyBorder>
);
}
What is the {props.children} in VueJS ??
The Vue analogy to the React "children" concept is the slots.
https://v2.vuejs.org/v2/guide/components.html#Content-Distribution-with-Slots
https://v2.vuejs.org/v2/guide/components-slots.html
Slots can be used like:
// FancyBorder.vue
<div class="FancyBorder">
<slot/>
</div>
// App.vue
<FancyBorder>
Contents!
</FancyBorder>
Vue slots also have an advantage over the React children, as you can have multiple "groups" of elements, using named slots, this can make the code less reliant on "magic" classnames:
// FancyBorder.vue
<div class="FancyBorder">
<h1 className="Dialog-title">
<slot name="header"></slot>
</h1>
<slot/>
</div>
// App.vue
<FancyBorder>
<template slot="header">
Header
</template>
Contents!
</FancyBorder>

Pass component as a property and respect its named slots in Vue.js

I want my container component to render contents based on child's slot names. Here's an example markup:
<div id="app">
<Container>
<component :is="childComponent"></component>
</Container>
</div>
<script>
import Container from './Container'
import someComponent from './someComponent'
export default {
components: {
Container,
someComponent
},
data() {
childComponent: 'someComponent'
}
}
</script>
// Container.vue
<template>
<div>
<header>
<slot name="head"></slot>
</header>
<div class="ContainerBody">
<slot name="body"></slot>
</div>
<footer>
<slot name="footer"></slot>
</footer>
</div>
</template>
// Some child component
<template>
<div>
<h1 slot="head">Child Title</h1>
<div slot="body" class="body"><div>Child Body</div></div>
<footer slot="footer">Child Footer</footer>
</div>
</template>
How do I make it that Vue respects slot names and renders child contents in accordingly named slots, so the result would look like this:
<div>
<header>
Child Title
</header>
<div class="ContainerBody">
<div>Child Body</div>
</div>
<footer>
Child Footer
</footer>
</div>
Right now it will only render my child component in an unnamed slot:
<slot></slot>
The idea is to render child component differently when it's loaded as a child of Container. I would like it to work with .Vue files and allow child components to still behave as usual when they're not a child of a container.
I don't think you can do exactly what you want because each component has to have a single root element, which precludes it being plugged in as three separate slots.
What I was able to do was to turn the problem inside-out, making the top-level component the childComponent and having it take a container prop which it uses to set the :is of its root element.
// "importing" async component definitions
const vueContainerComponent = () => new Promise((resolve) => resolve({
template: '#container-template'
}));
const vueChildComponent = () => new Promise((resolve) => resolve({
template: '#child-template',
props: ['container']
}));
new Vue({
el: '#app',
components: {
someComponent:() => vueChildComponent().then((spec) => ({
extends: spec,
components: {
Container: vueContainerComponent
}
}))
},
data: {
container: 'Container',
childComponent: 'someComponent'
}
});
header {
background-color: lightgray;
}
footer {
background-color: darkslategray;
color: white;
}
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.min.js"></script>
<div id="app">
<component :is="childComponent" :container="container"></component>
</div>
<template id="container-template">
<div>
<header>
<slot name="head"></slot>
</header>
<div class="ContainerBody">
<slot name="body"></slot>
</div>
<footer>
<slot name="footer"></slot>
</footer>
</div>
</template>
<template id="child-template">
<div :is="container">
<h1 slot="head">Child Title</h1>
<div slot="body" class="body"><div>Child Body</div></div>
<footer slot="footer">Child Footer</footer>
</div>
</template>

Truly Nested Components in Vue.js?

Is it possible to create semantically nested elements with Vue.js?
Example: let's say I'm building an 'accordion' element. Accordions are made up of a 'heading' and a 'content' section. The content can be toggled in and out of view by clicking the header. The final html for an element like this would be something like this:
<div class="accordion">
<div class="heading">
My Accordion
</div>
<div class="content">
Accordions are fun! Loren ipsum dolor sit amet.
This can be extensive text, include pictures, etc.
</div>
</div>
I would like to be able to create such elements in my html using syntax like this:
<accordion>
<heading>My Accordion</heading>
<content>
Accordions are fun! Loren ipsum dolor sit amet.
This can be extensive text, include pictures, etc.
</content>
</accordion>
The 'heading' and 'content' elements are not generic, and should only exist within the context of the parent 'accordion' element, so I believe they should be declared within the parent component's definition.
I know that in order to capture the innerHTML content of an element, we must use a <slot> element, so I tried using the following templates:
<template id="heading">
<div class="heading">
<slot></slot>
</div>
</template>
<template id="content">
<div class="content">
<slot></slot>
</div>
</template>
<template id="accordion">
<div class="accordion">
<heading></heading>
<content></content>
</div>
</template>
<div id="app">
<accordion>
<heading>My Accordion</heading>
<content>
Accordions are fun. Lorem ipsum dolor sit amet.
I could add a lot more text here, or other elements.
</content>
</accordion>
</div>
And the Vue javascript...:
Vue.component('accordion', {
template: '#accordion',
components: {
heading: {
template: '#heading'
},
content: [
template: '#content'
}
}
});
Vue({
el: '#app'
});
Unfortunately, it doesn't work. I've read the official documentation several times, and within the 'Components' section, when it talks about <slot> elements, it seems to indicate we should be able to do it - but I can't for the life of me work out how... The docs actually mention an element with a structure like this:
<app>
<app-header></app-header>
<app-footer></app-footer>
</app>
...but it doesn't give simple, concrete examples of how to do it.
The way the information is passed from parent to child element is confusing, and I have been unable to find any tutorials online that show how to setup a nested element like this. Any guidance would be greatly appreciated.
so <heading> and <content> should also be Vue components?
then it should look like this:
<div id="app">
<h5>Accordion test</h5>
<accordion>
<heading slot="heading">Heading Text</heading>
<content slot="content">Content Text</content>
</accordion>
<template id="accordion">
<div class="header">
<slot name="heading"></slot>
</div>
<div class="content">
<slot name="content"></slot>
</div>
</template>
<template id="heading">
<div class="template-heading">
<slot></slot>
</div>
</template>
<template id="content">
<div class="template-content">
<slot></slot>
</div>
</template>
</div>
and the JS:
var Heading = Vue.extend({
template: '#heading'
})
var Content = Vue.extend({
template: '#content'
})
var Accordion = Vue.extend({
template: '#accordion',
components: {
heading: Heading,
content: Content
}
})
Vue.component('heading', Heading)
Vue.component('content', Content)
Vue.component('accordion', Accordion)
var App = new Vue({
el: '#app',
data() {
return {
test: 'Test'
}
}
})
So:
The content of the <heading> element goes into the slot inside of
<heading>'s template.
And the whole template of <heading> goes into the <slot name="heading">inside the template of <accordion>
Working fiddle: https://jsfiddle.net/Linusborg/ud9a614o/

Categories

Resources