Vue.js use component in another - javascript

I have a component that I would like to use in another in order to make a navigation menu. I want separate the menu from links. How can I display data in a child component in another parent component? I use Vue.js CDN.
I am a newbie in using vue.js. Thank you for your help.
index.html
<div id="navbar"><navbar-div></navbar-div></div>
navbar.js:
Vue.component('navbar-content', {
props: ['name', 'link'],
template: '<a class="navbar-item" v-bind:href="link">{{ name }}</a>'
})
Vue.component('navbar-div', {
props: ['links'],
template: `
<nav class="navbar is-dark">
<div class="navbar-brand">[...]</div>
<div class="navbar-menu">
<nav class="navbar-start">
<navbar-content v-for="item in links"
v-bind:key="item.id"
v-bind:name="item.name"
v-bind:link="item.link">
</navbar-content>
</nav>
</div>
</nav>
`})
new Vue({
el: "#navbar",
data: {
links: [
{id: 1, name: "Item 1", link: "link1"},
{id: 2, name: "Item 2", link: "link2"}
],
}
})

In your HTML you have to bind the links to navbar-div; otherwise it's not going to receive the data as props
Vue.component('navbar-content', {
props: ['name', 'link'],
template: `
<a
class="navbar-item"
:href="link"
>
{{ name }}
</a>
`
})
Vue.component('navbar-div', {
props: ['links'],
template: `
<nav class="navbar is-dark">
<div class="navbar-brand">[...]</div>
<div class="navbar-menu">
<nav class="navbar-start">
<navbar-content
v-for="item in links"
:key="item.id"
:name="item.name"
:link="item.link"
/>
</nav>
</div>
</nav>
`
})
new Vue({
el: "#navbar",
data: {
links: [{
id: 1,
name: "Item 1",
link: "link1"
},
{
id: 2,
name: "Item 2",
link: "link2"
}
],
},
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="navbar">
<navbar-div :links="links"></navbar-div>
</div>

Related

How to specify bootstrap accordion for data from API in vue js

I'm trying to create an order feed using accordion elements from bootstrap-vue and I am struggling with making only one element opened when I press it.
I've tried changing ids which are from api, but have no result.
HTML:
<div v-for="el in APIData" :key="el.id" >
<div class="accordion" role="tablist" :id="el.id">
<b-card no-body class="mb-1" :id="el.id">
<b-card-header header-tag="header" class="p-1" role="tab" :id="el.id">
<b-button block v-b-toggle.accordion-1 variant="dark">{{ el.name }}. Deadline: {{ el.deadline }} Price: <strong>{{ el.price }}</strong></b-button>
</b-card-header>
<b-collapse id="accordion-1" accordion="my-accordion" role="tabpanel" >
<b-card-body>
<b-card-text>
<div> <p> <strong>Posted:</strong> {{ el.date_posted }}. <br />{{ el.description }}</p> </div>
<a class= "button btn btn-dark">More</a>
</b-card-text>
</b-card-body>
</b-collapse>
</b-card>
</div>
</div>
Script:
<script>
import axios from 'axios'
export default {
name: 'Orders',
data () {
return {
APIData: [],
}
},
created () {
axios
.get('/api/v1/orders/')
.then(response => {
this.APIData = response.data
console.log(response.data)
})
.catch(err => {
console.log(err)
})
},
}
</script>
Example of data:
[
{
price: "179",
id: "1",
date_posted: "04_04_2022",
description: "some desc bla bla bla",
name: "some name",
deadline: "04_06_2022"
},
{
price: "189",
id: "2",
date_posted: "05_04_2022",
description: "another desc bla bla bla",
name: "some name",
deadline: "05_06_2022"
},
{
price: "199",
id: "3",
date_posted: "06_04_2022",
description: "another desc bla bla bla",
name: "some name",
deadline: "06_06_2022"
},
]
Again, I need to get opened only one accordion but get three instead e.g. because it seems to bootstrap that it is all one element.
You can simply achieve that by concatenating the el.id in the v-b-toggle attribute and the accordion id as well.
Working Demo :
new Vue({
el: '#app',
data: {
APIData: [{
id: 1,
name: 'Accordion 1'
}, {
id: 2,
name: 'Accordion 2'
}]
}
})
#app {
padding: 20px;
height: 350px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script src="https://unpkg.com/bootstrap-vue#2.22.0/dist/bootstrap-vue.js"></script>
<link rel="stylesheet" href="https://unpkg.com/bootstrap-vue#2.22.0/dist/bootstrap-vue.css"/>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap#4.4.1/dist/css/bootstrap.min.css"/>
<div id="app">
<div v-for="el in APIData" :key="el.id" >
<p>
<b-btn v-b-toggle="`collapse-${el.id}`" variant="primary">Toggle {{ el.name }}</b-btn>
</p>
<b-collapse :id="`collapse-${el.id}`">
<b-card>
Collapse {{ el.name }} contents Here
</b-card>
</b-collapse>
</div>
</div>

Creating an accordion menu: The submenus are not displayed

I am creating an accordion menu. The headings are displayed but I don't know how to display the submenus?
Something missing on this part? Because, I don't see where is the problem? If you have a solution, I'm really interested.
<ul class="submenu" #submenu>
<li *ngFor="let submenu of menu.submenu">
<a routerLink="{{ submenu.url }}"> {{ submenu.name }} </a>
</li>
</ul>
HTML file
<div class="sidebar">
<ul id="accordion" class="accordion">
<li *ngFor="let menu of menus; let i = index" [class.active]="menu.active">
<ng-container>
<div class="menu" (click)="selectMenu(menu)">
<i [class]="menu.iconClass"></i> {{ menu.name }}
<i class="fa fa-chevron-down"></i>
</div>
<ul class="submenu" #submenu>
<li *ngFor="let submenu of menu.submenu">
<a routerLink="{{ submenu.url }}"> {{ submenu.name }} </a>
</li>
</ul>
</ng-container>
</li>
</ul>
</div>
Then
export class AppComponent {
menus: Menu[] = [
{
name: 'Markets',
iconClass: 'bx bx-lock-alt',
active: false,
submenu: [
{ name: 'Indice', url: 'https://www.google.be'},
{ name: 'Value', url: '#'}
]
},
{
name: 'Stock',
iconClass: 'bx bx-lock-alt',
active: false,
submenu: [
{ name: 'Currency', url: 'https://www.google.be'},
{ name: 'Devise', url: '#'}
]
},
{
name: 'Traiding',
iconClass: 'bx bx-lock-alt',
active: false,
submenu: [
{ name: 'Day traiding', url: 'https://www.google.be'},
{ name: 'Devise', url: '#'}
]
}
]
selectMenu(parentMenu: { name: string }) : void {
this.menus.forEach(menu => {
if(menu.name !== parentMenu.name){
menu.active = false;
} else {
menu.active = true;
}
})
}
}
There is a reproduction here.
Thank you in advance for your help.

How can I work around the limitation of multiple root elements in Vue.js? [duplicate]

This question already has answers here:
A way to render multiple root elements on VueJS with v-for directive
(6 answers)
Closed 2 years ago.
hopefully someone here will be able to help me with this problem.
I have the following data:
[
{
title: 'Header',
children: [
{
title: 'Paragraph',
children: [],
},
],
},
{
title: 'Container',
children: [
{
title: 'Paragraph',
children: [],
},
],
},
]
I want to render this in a list of <div> like this:
<div class="sortable-item" data-depth="1" data-index="0">Header</div> <!-- Parent -->
<div class="sortable-item" data-depth="2" data-index="0">Paragraph</div> <!-- Child-->
<div class="sortable-item" data-depth="1" data-index="1">Container</div> <!-- Parent -->
<div class="sortable-item" data-depth="2" data-index="0">Paragraph</div> <!-- Child-->
I have built a component that would be recursive, this is what I have so far:
<template>
<template v-for="(item, index) in tree">
<div
class="sortable-item"
:data-depth="getDepth()"
:data-index="index"
:key="getKey(index)"
>
{{ item.title }}
</div>
<Multi-Level-Sortable
:tree="item.children"
:parent-depth="getDepth()"
:parent-index="index"
:key="getKey(index + 0.5)"
></Multi-Level-Sortable>
</template>
</template>
<script>
export default {
name: 'MultiLevelSortable',
props: {
tree: {
type: Array,
default() {
return [];
},
},
parentDepth: {
type: Number,
},
parentIndex: {
type: Number,
},
},
methods: {
getDepth() {
return typeof this.parentDepth !== 'undefined' ? this.parentDepth + 1 : 1;
},
getKey(index) {
return typeof this.parentIndex !== 'undefined' ? `${this.parentIndex}.${index}` : `${index}`;
},
},
};
</script>
As you can see not only I have a <template> as the root element I also have a v-for, two "no no" for Vue.js. How can I solve this to render the list of elements like I pointed out above?
Note: I have tried vue-fragment and I was able to achieve the structure I wanted, but then when I tried using Sortable.js it didn't work, as if it wouldn't recognise any of the .sortable-item elements.
Any help will be greatly appreciated! Thank you!
Thanks to #AlexMA I was able to solve my problem by using a functional component. Here is what it looks like:
import SortableItemContent from './SortableItemContent.vue';
export default {
functional: true,
props: {
tree: {
type: Array,
default() {
return [];
},
},
},
render(createElement, { props }) {
const flat = [];
function flatten(data, depth) {
const depthRef = typeof depth !== 'undefined' ? depth + 1 : 0;
data.forEach((item, index) => {
const itemCopy = item;
itemCopy.index = index;
itemCopy.depth = depthRef;
itemCopy.indentation = new Array(depthRef);
flat.push(itemCopy);
if (item.children.length) {
flatten(item.children, depthRef);
}
});
}
flatten(props.tree);
return flat.map((element) => createElement('div', {
attrs: {
'data-index': element.index,
'data-depth': element.depth,
class: 'sortable-item',
},
},
[
createElement(SortableItemContent, {
props: {
title: element.title,
indentation: element.indentation,
},
}),
]));
},
};
The SortableItemContent component looks like this:
<template>
<div class="item-content">
<div
v-for="(item, index) in indentation"
:key="index"
class="item-indentation"
></div>
<div class="item-wrapper">
<div class="item-icon"></div>
<div class="item-title">{{ title }}</div>
</div>
</div>
</template>
<script>
export default {
name: 'SortableItemContent',
props: {
title: String,
indentation: Array,
},
};
</script>
Given the data I have posted on my question, it now renders the HTML elements like I wanted:
<div data-index="0" data-depth="0" class="sortable-item">
<div class="item-content">
<div class="item-wrapper">
<div class="item-icon"></div>
<div class="item-title">Header</div>
</div>
</div>
</div>
<div data-index="0" data-depth="1" class="sortable-item">
<div class="item-content">
<div class="item-indentation"></div>
<div class="item-wrapper">
<div class="item-icon"></div>
<div class="item-title">Paragraph</div>
</div>
</div>
</div>
<div data-index="1" data-depth="0" class="sortable-item">
<div class="item-content">
<div class="item-wrapper">
<div class="item-icon"></div>
<div class="item-title">Container</div>
</div>
</div>
</div>
<div data-index="0" data-depth="1" class="sortable-item">
<div class="item-content">
<div class="item-indentation"></div>
<div class="item-wrapper">
<div class="item-icon"></div>
<div class="item-title">Paragraph</div>
</div>
</div>
</div>
Thank you again #AlexMA for the tip on Functional Components.

How do I fetch JSON data with Vue and Axios

I'm trying to fetch product data from a JSON file, but can't get it to work.
I've tried several things and searched the internet for a solution but none of the examples on the internet equals my situation.
I'm new to both vue and axios, so please excuse my ignorance.
This is what I have so far:
Vue.component('products',{
data: {
results: []
},
mounted() {
axios.get("js/prods.json")
.then(response => {this.results = response.data.results})
},
template:`
<div id="products">
<div class="productsItemContainer" v-for="product in products">
<div class="productsItem">
<div class="">
<div class="mkcenter" style="position:relative">
<a class="item">
<img class="productImg" width="120px" height="120px" v-bind:src="'assets/products/' + product.image">
<div class="floating ui red label" v-if="product.new">NEW</div>
</a>
</div>
</div>
<div class="productItemName" >
<a>{{ product.name }}</a>
</div>
<div class="mkdivider mkcenter"></div>
<div class="productItemPrice" >
<a>€ {{ product.unit_price }}</a>
</div>
<div v-on:click="addToCart" class="mkcenter">
<div class="ui vertical animated basic button" tabindex="0">
<div class="hidden content">Koop</div>
<div class="visible content">
<i class="shop icon"></i>
</div>
</div>
</div>
</div>
</div>
</div>
`
})
new Vue({
el:"#app",
});
The json file is as follows
{
"products":[
{
"name": "Danser Skydancer",
"inventory": 5,
"unit_price": 45.99,
"image":"a.jpg",
"new":true
},
{
"name": "Avocado Zwem Ring",
"inventory": 10,
"unit_price": 123.75,
"image":"b.jpg",
"new":false
}
]
}
The problem is only with the fetching of the data from a JSON file, because the following worked:
Vue.component('products',{
data:function(){
return{
reactive:true,
products: [
{
name: "Danser Skydancer",
inventory: 5,
unit_price: 45.99,
image:"a.jpg",
new:true
},
{
name: "Avocado Zwem Ring",
inventory: 10,
unit_price: 123.75,
image:"b.jpg",
new:false
}
],
cart:0
}
},
template: etc.........
As the warnings suggest, please do the following:
Rename the data array from results to products since you are referencing it by the latter one as a name during render.
Make your data option a function returning an object since data option must be a function, so that each instance can maintain an independent copy of the returned data object. Have a look at the docs on this.
Vue.component('products', {
data() {
return {
products: []
}
},
mounted() {
axios
.get("js/prods.json")
.then(response => {
this.products = response.data.products;
});
},
template: `
//...
`
}
<div id="products">
<div class="productsItemContainer" v-for="product in products">
<div class="productsItem">
...
Also, since you're not using CDN (I think), I would suggest making the template a component with a separate Vue file rather than doing it inside template literals, something like that:
Products.vue
<template>
<div id="products">
<div class="productsItemContainer" v-for="product in products">
<div class="productsItem">
<!-- The rest of the elements -->
</div>
</div>
</div>
</template>
<script>
export default {
name: 'Products',
data() {
return {
products: []
}
},
mounted() {
axios
.get("js/prods.json")
.then(response => {
this.products = response.data.products;
});
}
}
</script>
And then in your main JS file or anywhere else requiring this component:
import Products from './components/Products.vue';
new Vue({
el: '#app',
data() {
return {
//...
}
},
components: {
Products
}
})
<div id="app">
<Products />
</div>

Bootstrap Vue Dynamic table templating

I am working with Bootstrap Vue JS table component to create a datatable:
https://bootstrap-vue.js.org/docs/components/table
I am new to VueJS and am uncertain on how to approach this problem which makes searching for a solution even more complicated.
I use an API endpoint to return JSON data:
{
"options":{
"filter":false
},
"fields":[
{
"key":"id",
"label":"Id",
"editLink":false,
"display":true,
"sortable":true,
"class":"shrink"
},
{
"key":"name",
"label":"Name",
"editLink":true,
"display":true,
"sortable":true
}
],
"data":[ ]
}
Here is my table template:
<b-table striped hover bordered foot-clone class="table-sm"
:items="users" :fields="displayedFields" :per-page="perPage" :current-page="currentPage" :filter="filter"
#filtered="onFiltered"
>
<template v-for="(field, index) in fields">
<template slot="{{field.key}}" slot-scope="row" v-if="field.editLink">
<router-link :to="'/user/' + row.item.id" v-bind:key="index"></router-link>
</template>
</template>
<template slot="status" slot-scope="row">
<toggle-button :width="36" :height="18" v-model="row.status" :labels="false" :colors="{checked: '#00FF00', unchecked: '#FF0000', disabled: '#CCCCCC'}"/>
</template>
</b-table>
The first template tag is an attempt from a wild guess. I want to be able to conditionally select a table for a column from the fields config. You can see in my attempt that I want to put a RouterLink when the field's config editLink is true.
How can I get this done?
If you're using version 2.0.0 or newer of bootstrap-vue you need to change the slot names as
they've changed, and the old vue slot has also been deprecated in favor for v-slot.
I changed the accepted answers fiddle to work with the new naming and v-slot
new Vue({
el: "#app",
data: {
fields: [{
key: "id",
label: "Id",
colType: "span"
}, {
key: "name",
label: "Name",
colType: "button"
}, {
key: "uhh",
label: "uhh..",
colType: "idk"
}],
items: [{
id: 0,
name: "Test 0"
}, {
id: 1,
name: "Test 1"
}, {
id: 2,
name: "Test 2"
}]
}
});
<link href="https://unpkg.com/bootstrap#4.3.1/dist/css/bootstrap.min.css" rel="stylesheet" />
<link href="https://unpkg.com/bootstrap-vue#2.0.0/dist/bootstrap-vue.css" rel="stylesheet" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.11/vue.js"></script>
<script src="https://unpkg.com/bootstrap-vue#2.0.0/dist/bootstrap-vue.js"></script>
<div id="app">
<b-table :items="items" :fields="fields">
<template v-for="(field, index) in fields" v-slot:[`cell(${field.key})`]="data">
<div v-if="field.colType === 'button'">
<h5>{{data.item.name}}</h5>
<b-button>Am Button</b-button>
</div>
<div v-else-if="field.colType === 'span'">
<h5>{{data.item.name}}</h5>
<span>Am Span</span>
</div>
<div v-else>
<h5>{{data.item.name}}</h5>
Am Confused
</div>
</template>
</b-table>
</div>
Here's a jsfiddle showing dynamic columns with a b-table:
https://jsfiddle.net/nardnob/wvk6fxgt/
new Vue({
el: "#app",
data: {
fields: [{
key: "id",
label: "Id",
colType: "span"
}, {
key: "name",
label: "Name",
colType: "button"
}, {
key: "uhh",
label: "uhh..",
colType: "idk"
}],
items: [{
id: 0,
name: "Test 0"
}, {
id: 1,
name: "Test 1"
}, {
id: 2,
name: "Test 2"
}]
}
});
<div id="app">
<b-table :items="items" :fields="fields">
<template v-for="(field, index) in fields" :slot="field.key" slot-scope="data">
<div v-if="field.colType === 'button'">
<h5>{{data.item.name}}</h5>
<b-button>Am Button</b-button>
</div>
<div v-else-if="field.colType === 'span'">
<h5>{{data.item.name}}</h5>
<span>Am Span</span>
</div>
<div v-else>
<h5>{{data.item.name}}</h5>
Am Confused
</div>
</template>
</b-table>
</div>

Categories