I have a list that displays all the songs in all of the artists.
I am able to do this by having v-for in a v-for.
However, i need the list to show a-z, I am able to do this with my computed property on one v-for, however it doesn't seem to work when I add v-for's in v-for's?
See my try <!--This is my Test-->?
How can I get this to work?
See my data structure also below!
This is my code try;
<template>
<div class="SongView">
<!-- Dynamic Headers -->
<h1>{{header}}</h1>
<br>
<h2>{{header2}}</h2>
<!-- Search Componenet -->
<div class="mx-5 mb-3 form-group">
<br>
<input class="mb-5 form-control" placeholder="Search Albums Here..." v-model="searchQuery"/>
</div>
<!-- This is my working list - Dynamic Song Route Button -->
<div class="row button-container-all mx-5 pb-5">
<div v-for="album in datanew" :key="album.id">
<div class="" v-for="(item, i) in album.albums" :key="i">
<div class="" v-for="(itemtwo, i) in item.songsinalbum" :key="i">
<router-link class="routerlink" :key="item.songsinalbum" :to="'/songselected/' + item.song + '/artist/' + album.artist">
<div class="routerlink button">{{itemtwo.song}}</div>
</router-link>
</div>
</div>
</div>
</div>
<!--This is my test-->
<div class="test button-container-all">
<div v-for="itemss in sortedSongs" :key="itemss.id">
{{ itemss.album }}
</div>
</div>
</div>
</template>
<script>
import {datatwo} from '#/data2'
export default {
data() {
return {
datanew: datatwo,
header:"Browse by Song" ,
header2: "Select a Song:",
}
},
computed: {
sortedSongs() {
const res =[]
this.datanew.albums.item.songsinalbum.forEach(a => a.album.forEach(s => res.push(s)))
return res.sort(function(a, b) {
if(a.album < b.album) { return -1; }
if(a.album > b.album) { return 1; }
return 0;
})
}
}
}
</script>
Here is my data structure;
export const datatwo = [
{id: "1", artist: "aaxx", dateadded: "07/04/2022", artistroute: "/axxx",
albums: [{album:'Shomati', songsinalbum: [
{ song : 'MaMaMa', keys: [
{key: "Am"},
{key: "Em"}
]},
{ song : 'Al Naharos Bavel', keys: [
{key: "Am"},
{key: "Em"}
]},
{ song : 'Levinyomin', keys: [
{key: "Am"},
{key: "Em"}
]}, ]},
{album : 'Simcha', songsinalbum: [
{ song : 'Kolot', keys: [
{key: "Am"},
{key: "Em"}
]},
{ song : 'Kalu Kol Hakotzim', keys: [
{key: "Am"},
{key: "Em"}
]},
{ song : 'Vehi Sheamdah', keys: [
{key: "Am"},
{key: "Em"}
]}, ]}, ]
},
Thanks!
The problem here came from your computed value where you tried to push. The following line wont works because this.datanew.albums.item is undefined (because this.datanew.albums is an array)
this.datanew.albums.item.songsinalbum.forEach(a => a.album.forEach(s => res.push(s)))
return res.sort(function(a, b) {
To sort data what I would do is loop through the albums and then push into the res array, the song list sorted.
sortedAlbums() {
const res = []
this.datatwo.forEach(data => {
data.albums.forEach(album => {
album.songsinalbum.sort((a,b) => {
if(a.song > b.song) return 1
else return -1
})
console.log(album)
res.push(album)
})
})
return res
}
Edit
If you want only the sorted song, what i recommand doing is pushing all the sond in an array and then sort the entire array.
This is not the most efficient way because you could have inserted directly at the good position but this is the easiest way.
sortedSong() {
const res = []
this.datatwo.forEach(data => {
data.albums.forEach(album => {
album.songsinalbum.forEach((song) => {
res.push(song)
})
})
})
return res.sort((a, b) => {
if(a.song > b.song) return 1
else return -1
})
}
Working snippet
new Vue({
el: '#app',
computed: {
sortedSong() {
const res = []
this.datatwo.forEach(data => {
data.albums.forEach(album => {
album.songsinalbum.forEach((song) => {
res.push(song)
})
})
})
return res.sort((a, b) => {
if(a.song > b.song) return 1
else return -1
})
}
},
data: () => {
return {
datatwo: [{
id: "1",
artist: "aaxx",
dateadded: "07/04/2022",
artistroute: "/axxx",
albums: [{
album: 'Shomati',
songsinalbum: [{
song: 'MaMaMa',
keys: [{
key: "Am"
},
{
key: "Em"
}
]
},
{
song: 'Al Naharos Bavel',
keys: [{
key: "Am"
},
{
key: "Em"
}
]
},
{
song: 'Levinyomin',
keys: [{
key: "Am"
},
{
key: "Em"
}
]
},
]
},
{
album: 'Simcha',
songsinalbum: [{
song: 'Kolot',
keys: [{
key: "Am"
},
{
key: "Em"
}
]
},
{
song: 'Kalu Kol Hakotzim',
keys: [{
key: "Am"
},
{
key: "Em"
}
]
},
{
song: 'Vehi Sheamdah',
keys: [{
key: "Am"
},
{
key: "Em"
}
]
},
]
},
]
}]
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div v-for="song of sortedSong">
Song : {{ song.song }}
</div>
</div>
Related
I have a table:
<b-table striped hover :items="coursesArray" :fields="fields"></b-table>
The data comes from
coursesArray: [
{
id: "1",
name: "foo",
teacherEmails: [{ 0: "sample1" }, { 1: "sample2" }, { 2: "sample3" }],
teacherIds: ["A1", "A2", "A3"],
},
],
And fields are:
fields: [
{ key: "id", label: "Course ID" },
{ key: "name", label: "Course Name" },
{
key: "teacherEmails",
label: "Teacher Email",
formatter: "teacherEmailTCellFormat",
},
{ key: "teacherIds", label: "Teacher ID" },
],
And this is how it's rendered without the formatter.
So, I added a custom formatter in fields to remove the brackets and curly braces.
The first problem I'm having is that looping over the value to return all the items in teacherEmails does not work.
teacherEmailTCellFormat(value) {
console.log(value)
value.forEach(item => {
return each[0]
}
}
The second problem, that I don't understand why it's happening, is that if I log the value passed to the formatter, it's clear the formatter function is called twice.
Any help or clearance will be appreciated.
You need to return an array, you can do this using Array#map:
new Vue({
el:"#app",
data: () => ({
fields: [
{ key: "id", label: "Course ID" },
{ key: "name", label: "Course Name" },
{ key: "teacherEmails", label: "Teacher Email", formatter: "teacherEmailTCellFormat" },
{ key: "teacherIds", label: "Teacher ID" },
],
coursesArray: [
{
id: "1",
name: "foo",
teacherEmails: [{ 0: "sample1" }, { 1: "sample2" }, { 2: "sample3" }],
teacherIds: ["A1", "A2", "A3"],
}
]
}),
methods: {
teacherEmailTCellFormat(value) {
return value.map((item, i) => item[i]);
}
}
});
<link type="text/css" rel="stylesheet" href="https://unpkg.com/bootstrap/dist/css/bootstrap.min.css"/>
<link type="text/css" rel="stylesheet" href="https://unpkg.com/bootstrap-vue#latest/dist/bootstrap-vue.css"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.min.js"></script>
<script src="https://unpkg.com/babel-polyfill#latest/dist/polyfill.min.js"></script>
<script src="https://unpkg.com/bootstrap-vue#latest/dist/bootstrap-vue.js"></script>
<div id="app">
<b-table striped hover :items="coursesArray" :fields="fields"></b-table>
</div>
Note: I assumed the key represents the index of the item in the array, but you can change the mapping as you want. The main issue was returning the array.
I'm using antd in my app and I'm trying to do a customRender to show an image in a cell.
My columns array looks like this:
columns: [
{ title: 'Design',
dataIndex: 'designImage.fileUrl',
customRender () { // h will be injected
return '<div id="foo">bar</div>'
}
},
]
So, as you might imagine, it turns out like this:
I also tried this way:
{ title: 'Design',
dataIndex: 'designImage.fileUrl',
customRender: (text, record, index) => {
return {
children: '<img src="https://via.placeholder.com/300.png/09f/fff">'
}
}
},
Unfortunately, that ended up like this:
Can someone please help me figure out what I'm doing wrong?
You can take advantage of the scopedSlots property within columns, and use it to define a scoped slot for the customRender property.
Here is an example:
const columns = [
{
title: "Image",
dataIndex: "image",
key: "image",
scopedSlots: { customRender: "image-column" },
},
];
Now, in your template, you can use the image-column named slot, like this:
<a-table :columns="columns" :data-source="tableData">
<template slot="image-column" slot-scope="image">
<img :src="image" /> <!-- Add your custom elements here -->
</template>
</a-table>
And here is a component example:
<template>
<a-table :columns="columns" :data-source="tableData">
<template slot="image-column" slot-scope="image">
<img :src="image" />
</template>
</a-table>
</template>
<script>
const columns = [
{
title: "Image",
dataIndex: "image",
key: "image",
scopedSlots: { customRender: "image-column" },
},
];
const tableData = [
{
image: "https://picsum.photos/200",
},
{
image: "https://picsum.photos/200",
},
];
export default {
data() {
return {
columns,
tableData,
};
},
};
</script>
When you use antd's Table, customRender should return a vnode but not a string, so you can try it like this
columns: [
{ title: 'Design',
dataIndex: 'designImage.fileUrl',
customRender () {
return <div id="foo">bar</div>
}
}
]
I'm trying to make nested table, in documentation into the nested table they push one same data, how can i change it and push different data for every parent table row?
<script>
const columns = [
{ title: 'Status', dataIndex: 'Status', key: 'Status' },
{ title: 'Date', dataIndex: 'DateCreated', key: 'DateCreated' }
];
const innerColumns = [
{ title: 'Price', dataIndex: 'Price', key: 'Price' },
{ title: 'Status', dataIndex: 'Status', key: 'Status' },
];
const data = [
{
key: 0,
"Status": "u",
"DateCreated": "2019-10-11"
},
{
key: 1,
"Status": "u",
"DateCreated": "2019-09-20"
},
];
const innerData = [
[
{
key: 0,
"Price": 10623,
"Status": "u",
}
],
[
{
key: 0,
"Price": 15084,
"Status": "u",
},
{
key: 1,
"Price": 15084,
"Status": "u",
}
],
];
export default {
data() {
return {
data,
columns,
innerColumns,
innerData,
};
},
};
</script>
<template>
<div class="hello">
<a-table
:columns="columns"
:dataSource="data"
class="components-table-demo-nested">
<a-table
slot="expandedRowRender"
:columns="innerColumns"
:dataSource="innerData[1]"
:pagination="false"
>
</a-table>
</a-table>
</div>
</template>
Here my code
This worked for me
Template:
<a-table :data-source="data" :defaultExpandAllRows="true" v-on:expandedRowsChange="console.log">
<a-table-column title="First Col" data-index="firstCol" />
<template v-slot:expandedRowRender="record, index, indent, expanded">
<a-table :data-source="record.nestedData"
:pagination="false">
<a-table-column title="File Name" data-index="filename" />
</a-table>
</template>
</a-table>
Script:
Vue.component('demo', {
data: function() {
return {
data: [
{
firstCol: '1',
nestedData: [{filename: 'nested 11'}, {filename: 'nested 12'}]
},
{
firstCol: '2',
nestedData: [{filename: 'nested 22'}, {filename: 'nested 22'}]
}
]
}
}
});
I have a component with a v-for div. each item has a click function access their respective children object. I need to have a back button that would refresh the v-for div but using the ParentId of the current item I'm in.
Scan view:
<template>
<div p-0 m-0>
<div v-show="!currentItem" class="scanBreadcrumbs">
<h2>Show location</h2>
</div>
<div v-for="item in items" :key="item.id" :item="item">
<SubScan
v-show="currentItem && currentItem.id === item.id"
:item="item"
></SubScan>
<p
class="locationBox"
#click="swapComponent(item)"
v-show="path.length === 0"
>
{{ item.title }}
</p>
</div>
</div>
</template>
<script>
import { mapGetters } from "vuex";
import { SubScan } from "#/components/scan";
export default {
name: "Scan",
components: {
SubScan
},
computed: {
...mapGetters(["getResourceHierarchy", "getIsDarkMode", "getFontSize"])
},
methods: {
swapComponent(item) {
this.path.push(item.title);
this.currentItem = item;
}
},
data: () => ({
currentItem: null,
path: [],
items: [
{
parentId: null,
id: 11,
title: "Location 1",
items: [
{
parentId: 11,
id: 4324,
title: "Row 1",
items: [
{
parentId: 4324,
id: 4355,
title: "Row 1.1",
items: [
{
parentId: 4355,
id: 64645,
title: "Row 1.2",
items: [
{
parentId: 64645,
id: 7576657,
title: "Row 1.3",
items: [
{
parentId: 7576657
id: 8686,
title: "Row 1.4",
items: [
{
parentId: 8686,
id: 234324,
title: "QR Code"
}
]
}
]
}
]
}
]
}
]
}
]
}
]
})
};
</script>
SubScan component where the back button is:
<template>
<div>
<div class="scanBreadcrumbs">
<h2 v-show="path">{{ path.join(" / ") }}</h2>
</div>
<div>
<div class="showList" v-for="item in itemChildren" :key="item.id">
<p class="locationBox" #click="swapComponent(item)">
{{ item.title }}
</p>
<div class="backButton">
<v-icon #click="swapPrevious(item)" class="arrow"
>fa-arrow-left</v-icon
>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: "SubScan",
props: {
item: {
type: Object,
required: true
}
},
data: () => ({
currentItem: null,
secondaryPath: [],
parentPath: []
}),
methods: {
swapComponent(item) {
console.log(item.parentId);
this.path.push(item.title);
this.parentPath.push(this.currentItem);
this.currentItem = item;
},
swapPrevious(item) {
console.log(item);
this.path.pop(this.currentItem.title);
this.currentItem = item.id;
}
},
computed: {
items(currentItem) {
return this.currentItem ? this.item.items : this.item.items;
},
itemChildren(currentItem) {
return this.currentItem ? this.currentItem.items : this.item.items;
},
path() {
return this.secondaryPath.concat(this.item.title);
}
}
};
</script>
I can only go back to the children of the object I clicked on in Scan view.
Thank you for your time.
I managed to fix my problem by assigning parent objects to each children. Then I
moved everything to Scan.vue for simplicity. This is my first project using Vue
so things might not be optimal. Scan.vue
<template>
<div p-0 m-0>
<div class="scanBreadcrumbs">
<h2 v-show="path">{{ path.join("/") }}</h2>
<h2 v-if="path.length === 0">Show location</h2>
</div>
<div>
<div v-for="item in items">
<p class="locationBox" #click="swapComponent(item)">
{{ item.title }}
</p>
</div>
<div v-if="path.length > 0">
<div class="backButton">
<v-icon #click="swapPrevious()" class="arrow">fa-arrow-left</v-icon>
</div>
</div>
</div>
</div>
</template>
<script>
import { mapGetters } from "vuex";
export default {
name: "Scan",
computed: {
...mapGetters(["getResourceHierarchy", "getIsDarkMode", "getFontSize"])
},
methods: {
swapComponent(item) {
this.path.push(item.title);
this.currentItem = item;
this.items = this.currentItem.items;
},
assignParent(children, parent) {
children.forEach(item => {
item.Parent = parent;
var parentTitle = "";
if (parent) parentTitle = parent.title;
if (item.items) {
this.assignParent(item.items, item);
}
});
},
swapPrevious() {
if (this.currentItem.parentId === null) {
this.items = this.initialItems;
this.path.pop(this.currentItem.title);
} else {
this.currentItem = this.currentItem.Parent;
this.items = this.currentItem.items;
this.path.pop(this.currentItem.title);
}
}
},
mounted: function() {
this.assignParent(this.items, null);
this.initialItems = this.items;
},
data: () => ({
currentItem: null,
path: [],
initialItems: [],
items: [
{
parentId: null,
id: 11,
title: "Location 1",
items: [
{
parentId: 11,
id: 4324,
title: "Row 1",
items: [
{
parentId: 4324,
id: 4355,
title: "Row 1.1",
items: [
{
parentId: 4355,
id: 64646,
title: "Row 1.2",
items: [
{
parentId: 64646,
id: 7576657,
title: "Row 1.3",
items: [
{
parentId: 7576657,
id: 8686,
title: "Row 1.4",
items: [
{
parentId: 8686,
id: 12313,
title: "Row 1.5",
items: [
{
parentId: 12313,
id: 234324,
title: "QR Code"
}
]
}
]
}
]
}
]
}
]
}
]
}
]
}
]
})
};
</script>
<style lang="scss" scoped></style>
Suppose I'm trying to make a simple questionnaire, where the user answers a list of questions.
new Vue(
{
el: "#app",
data:
{
questions:
[
{
id: 1,
name: "What is your favorite color?",
selectedId: 2,
choices:
[
{ id: 1, name: "red" },
{ id: 2, name: "green" },
{ id: 3, name: "blue" },
]
},
...
]
}
});
How do I go about making a question component with two-way binding. That is, if the user swaps their favorite color from green to red, by clicking on the respective input, the selectedId will automatically update. I'm not very clear on how v-model works within a component. Does it only have access to the components data? Also, I don't understand the difference between props/data.
There are lots of ways you can approach this, here's my attempt:
let id = 0;
Vue.component('question', {
template: '#question',
props: ['question'],
data() {
return {
radioGroup: `question-${id++}`,
};
},
methods: {
onChange(choice) {
this.question.selectedId = choice.id;
},
isChoiceSelected(choice) {
return this.question.selectedId === choice.id;
},
},
});
new Vue({
el: '#app',
data: {
questions: [
{
title: 'What is your favorite color?',
selectedId: null,
choices: [
{ id: 1, text: 'Red' },
{ id: 2, text: 'Green' },
{ id: 3, text: 'Blue' },
],
},
{
title: 'What is your favorite food?',
selectedId: null,
choices: [
{ id: 1, text: 'Beans' },
{ id: 2, text: 'Pizza' },
{ id: 3, text: 'Something else' },
],
},
],
},
});
.question {
margin: 20px 0;
}
<script src="https://rawgit.com/yyx990803/vue/master/dist/vue.js"></script>
<div id="app">
<question v-for="question of questions" :question="question"></question>
</div>
<template id="question">
<div class="question">
<div>{{ question.title }}</div>
<div v-for="choice of question.choices">
<input type="radio" :name="radioGroup" :checked="isChoiceSelected(choice)" #change="onChange(choice)"> {{ choice.text }}
</div>
<div>selectedId: {{ question.selectedId }}</div>
</div>
</template>