I have a list of ID's from the vuex store.
idList = [23,54,66]
I want to use a v-for to iterate that list and then create a new data model that looks like this below but unsure how to set up my v-model code:
newData = {
[
{
id: 23,
url: // based off v-text-field,
key: //based off v-text-field
},
{
id: 54,
url: // based off v-text-field,
key: //based off v-text-field
},
{
id: 66,
url: // based off v-text-field,
key: //based off v-text-field
},
]
}
my code so far:
<v-checkbox
class="stream-targets"
v-for="(target,index) in idList"
v-model="locationTarget"
:value="target"
>
<template v-slot:label>
<span class="check-text" v-model='???'>{{ target}} [{{ target}}]</span>
<v-text-field placeholder="URL" v-model='??'></v-text-field>
<v-text-field placeholder="Key" v-model='??'></v-text-field>
</template>
</v-checkbox>
U nead something like this:
in vuex:
state: {
idList: [23,54,66]
},
in component and in created() method:
var idList = this.$store.state.idList;
idList.forEach(el => {
this.newData.push({
id: el,
url: '',
key: ''
});
});
and in component:
<v-checkbox
class="stream-targets"
v-for="(target,index) in newData"
:key="index"
v-model="locationTarget"
:value="target"
>
<template v-slot:label>
<!-- <span class="check-text" >{{target.id}}</span> -->
<v-text-field placeholder="URL" v-model='target.url'></v-text-field>
<v-text-field placeholder="Key" v-model='target.key'></v-text-field>
</template>
</v-checkbox>
Related
Intro: I am exploring Vue Js and got stuck while trying to make a dynamic data table component the problem I am facing is that I cannot pass a component via props and render it inside a table.
Problem: So basically what I am trying to do is to pass some custom component from headers prop in v-data-table such as:
headers = [
{ text: 'Name', value: 'name' },
{
text: 'Phone Number',
value: 'phone_number',
render: () => (
<div>
<p>Custom Render</p>
</div>
)
},
{ text: 'Actions', value: 'actions' }
]
So from the code above we can see that I want to render that paragraph from the render function inside Phone Number header, I did this thing in React Js before, but I cannot find a way to do it in Vue Js if someone can point me in the right direction would be fantastic. Thank you in advance.
You have 2 options - slots and dynamic components.
Let's first explore slots:
<template>
<v-data-table :items="dataItems" :headers="headerItems">
<template slot="item.phone_number" slot-scope="{item}">
<v-chip>{{ item.phone_number }}</v-chip>
</template>
<template slot="item.company_name" slot-scope="{item}">
<v-chip color="pink darken-4" text-color="white">{{ item.company_name }}</v-chip>
</template>
</v-data-table>
</template>
The data table provides you slots where you can customize the content. If you want to make your component more reusable and want to populate these slots from your parent component - then you need to re-expose these slots to the parent component:
<template>
<v-data-table :items="dataItems" :headers="headerItems">
<template slot="item.phone_number" slot-scope="props">
<slot name="phone" :props="props" />
</template>
<template slot="item.company_name" slot-scope="props">
<slot name="company" :props="props" />
</template>
</v-data-table>
</template>
If you don't know which slots will be customized - you can re-expose all of the data-table slots:
<template>
<v-data-table
:headers="headers"
:items="items"
:search="search"
hide-default-footer
:options.sync="pagination"
:expanded="expanded"
class="tbl_manage_students"
height="100%"
fixed-header
v-bind="$attrs"
#update:expanded="$emit('update:expanded', $event)"
>
<!-- https://devinduct.com/blogpost/59/vue-tricks-passing-slots-to-child-components -->
<template v-for="(index, name) in $slots" v-slot:[name]>
<slot :name="name" />
</template>
<template v-for="(index, name) in $scopedSlots" v-slot:[name]="data">
<slot :name="name" v-bind="data" />
</template>
<v-alert slot="no-results" color="error" icon="warning">
{{ $t("no_results", {term: search}) }}"
</v-alert>
<template #footer="data">
<!-- you can safely skip the "footer" slot override here - so it will be passed through to the parent component -->
<table-footer :info="data" #size="pagination.itemsPerPage = $event" #page="pagination.page = $event" />
</template>
</v-data-table>
</template>
<script>
import tableFooter from '#/components/ui/TableFooter'; // you can safely ignore this component in your own implementation
export default
{
name: 'TeacherTable',
components:
{
tableFooter,
},
props:
{
search:
{
type: String,
default: ''
},
items:
{
type: Array,
default: () => []
},
sort:
{
type: String,
default: ''
},
headers:
{
type: Array,
required: true
},
expanded:
{
type: Array,
default: () => []
}
},
data()
{
return {
pagination:
{
sortDesc: [false],
sortBy: [this.sort],
itemsPerPageOptions: [25, 50, 100],
itemsPerPage: 25,
page: 1,
},
};
},
watch:
{
items()
{
this.pagination.page = 1;
},
sort()
{
this.pagination.sortBy = [this.sort];
this.pagination.sortDesc = [false];
},
}
};
</script>
Dynamic components can be provided by props:
<template>
<v-data-table :items="dataItems" :headers="headerItems">
<template slot="item.phone_number" slot-scope="{item}">
<component :is="compPhone" :phone="item.phone_number" />
</template>
<template slot="item.company_name" slot-scope="{item}">
<component :is="compCompany" :company="item.company_name" />
</template>
</v-data-table>
</template>
<script>
export default
{
name: 'MyTable',
props:
{
compPhone:
{
type: [Object, String], // keep in mind that String type allows you to specify only the HTML tag - but not its contents
default: 'span'
},
compCompany:
{
type: [Object, String],
default: 'span'
},
}
}
</script>
Slots are more powerful than dynamic components as they (slots) use the Dependency Inversion principle. You can read more in the Markus Oberlehner's blog
Okay, I don't believe this is the best way possible but it works for me and maybe it will work for someone else.
What I did was I modified the headers array like this:
headers = [
{ text: 'Name', align: 'start', sortable: false, value: 'name' },
{
text: 'Phone Number',
key: 'phone_number',
value: 'custom_render',
render: Vue.component('phone_number', {
props: ['item'],
template: '<v-chip>{{item}}</v-chip>'
})
},
{ text: 'Bookings', value: 'bookings_count' },
{
text: 'Company',
key: 'company.name',
value: 'custom_render',
render: Vue.component('company_name', {
props: ['item'],
template:
'<v-chip color="pink darken-4" text-color="white">{{item}}</v-chip>'
})
},
{ text: 'Actions', value: 'actions', sortable: false }
]
And inside v-data-table I reference the slot of custom_render and render that component there like this:
<template v-slot:[`item.custom_render`]="{ item, header }">
<component
:is="header.render"
:item="getValue(item, header.key)"
></component>
</template>
To go inside the nested object like company.name I made a function which I called getValue that accepts 2 parametes, the object and the path to that value we need which is stored in headers array as key (ex. company.name) and used loadash to return the value.
getValue function:
getValue (item: any, path: string): any {
return loadash.get(item, path)
}
Note: This is just the initial idea, which worked for me. If someone has better ideas please engage with this post. Take a look at the props that I am passing to those dynamic components, note that you can pass more variables in that way.
I want to test my custom component via vue-test-utils. I use vuetify autocomplete component in this case. I just wrapped with div and extract as a custom component. Nothing to do special so far.
This component is really works on development. I have no problem with it.
I used this component as shown below.
<my-component
id="brand-dropdown"
ref="brand-dropdown"
:items="brands"
labelKey="product.brand"
item-text="name"
item-value="name"
v-model="product.brandName"/>
My custom component looks like:
<template>
<div class="comboContainer">
<v-autocomplete
class="product-combobox"
ref="complete"
:items="localItems"
append-icon="$dropdownArrow"
outlined
dense
hide-details
color="#999999"
:item-text="itemText"
:item-value="itemValue"
:filter="autoCompleteFilter"
v-model="selected"
#change="onListItemSelected">
<template slot="label">
<span>{{$t(labelKey)}}</span>
</template>
<template slot="prepend-inner" v-if="showSearchIcon">
<div class="d-flex align-center justify-center"
style="height: 25px;">
<img src="~/assets/icons/search/search.png"/>
</div>
</template>
<template v-slot:no-data>
<v-list-item id="noMatchText">
<v-list-item-title>
<div :no-data-item="dataTestId">
{{ $t('selection.noMatchFound') }}
</div>
</v-list-item-title>
</v-list-item>
</template>
<template v-slot:item="{ item }">
<v-tooltip top color="transparent">
<template v-slot:activator="{ on, attrs }">
<div
v-on="item[itemText].length >36?on:null"
class="combobox-item comboboxOverFlow"
:data-testid="itemDataTestIdPrefix+item.id"
:parent-list="dataTestId">
<span>{{ item[itemText] }}</span>
</div>
</template>
<div class="popup-rectangle">
<span>{{ item[itemText] }}</span>
</div>
</v-tooltip>
</template>
</v-autocomplete>
</div>
</template>
<script>
export default {
props: {
items: {
type: Array,
default: [],
},
labelKey: {
type: String,
default: '',
},
itemText: {
type: String,
default: '',
},
itemValue: {
type: String,
default: '',
},
value: {
type: [Number, String, Array],
},
disabled: {
type: Boolean,
default: false,
}
shouldTriggerWatch: {
type: Boolean,
default: false,
}
},
data() {
return {
selected: this.value,
showSearchIcon: false,
};
},
watch: {
value: {
immediate: true,
handler(val) {
if (this.shouldTriggerWatch) {
this.selected = val;
this.$emit('input', this.selected);
}
},
},
},
computed: {
localItems() {
if (this.items && this.items.length) {
return this.items.map(x => ({
...x,
displayName: x.lang_key ? this.$t(x.lang_key) : x.name,
}));
}
return [];
},
},
methods: {
onListItemSelected() {
this.$emit('input', this.selected);
},
autoCompleteFilter(item, queryText, itemText) {
const re = new RegExp(`(\\s+|^)(${queryText.toLocaleLowerCase()})(.*|$)`);
return re.test(itemText.toLocaleLowerCase());
},
},
};
I want to test three test case.
When I pass empty list should render as empty dropdown and render no-data slot.
<template v-slot:no-data>
<v-list-item id="noMatchText">
<v-list-item-title>
<div :no-data-item="dataTestId">
{{ $t('selection.noMatchFound') }}
</div>
</v-list-item-title>
</v-list-item>
</template>
When I pass filled list with test data I want to test all items rendered correctly
<template v-slot:item="{ item }">
<v-tooltip top color="transparent">
<template v-slot:activator="{ on, attrs }">
<div
v-on="item[itemText].length >36?on:null"
class="combobox-item comboboxOverFlow"
:data-testid="itemDataTestIdPrefix+item.id"
:parent-list="dataTestId">
<span>{{ item[itemText] }}</span>
</div>
</template>
<div class="popup-rectangle">
<span>{{ item[itemText] }}</span>
</div>
</v-tooltip>
</template>
When I search I want to provide my filter function works as expected. And autocomplete renders items according to filtered value.
This is what my test file.
function mountComponent(options) {
return mount(ComboBox, {
vuetify: new Vuetify(),
sync: false,
mocks: {
$t: key => key,
$i18n: { locale: 'en' },
},
...options,
});
}
describe('Combobox unit tests', () => {
beforeEach(() => {
document.body.setAttribute('data-app', 'true');
});
test('should create component successfully', () => {
const wrapper = mountComponent({ items: [] });
expect(wrapper.exists()).toBeTruthy();
});
test('should list zero items if the item list is empty', async () => {
const wrapper = mountComponent({
propsData: {
items: [],
labelKey: 'labelKey',
dataTestId: 'test-dropdown',
itemText: 'name',
itemValue: 'id',
},
});
const autocomplete = wrapper.find('.product-combobox');
const autocompleteControls = autocomplete.find('.v-input__slot');
autocompleteControls.trigger('click');
await wrapper.vm.$nextTick();
await flushPromises();
**// v menu cant opened !!!!**
});
test('should list third items correctly', async () => {
const testItems = [{ name: 'item1', id: 1 }, { name: 'item2', id: 2 }, { name: 'item3', id: 3 }];
const wrapper = mountComponent({
attachToDocument: true,
propsData: {
eagerProp: true,
items: testItems,
dataTestId: 'test-dropdown',
itemDataTestIdPrefix: 'test-dropdown-item-',
itemText: 'name',
itemValue: 'id',
value: null,
},
});
const slot = wrapper.find('.v-input__slot');
const input = wrapper.find('input');
// Focus input should only focus
input.trigger('focus');
expect(wrapper.vm.$children[0].isFocused).toBe(true);
expect(wrapper.vm.$children[0].menuCanShow).toBe(true);
expect(wrapper.vm.$children[0].isMenuActive).toBe(false);
slot.trigger('click');
expect(wrapper.vm.$children[0].isMenuActive).toBe(true);
expect(wrapper.vm.$children[0].menuCanShow).toBe(true);
wrapper.setProps({ searchInput: 'foo' });
expect(wrapper.vm.$children[0].isMenuActive).toBe(true);
expect(wrapper.vm.$children[0].menuCanShow).toBe(true);
// v menu cant opened !!!!
// you think these expects is unnecesary. you are right I just explore this component
// if I success I'll delete all of them
});
test('should filter autocomplete search results', async () => {
});
});
I can't open v-menu in test enviroment.
I tried emit events and trigger('click')
When I console log wrapper.html() I cant see v menu is opened and all items rendered in html. Output is shown below.
<div class="comboContainer">
<div class="v-input product-combobox v-input--hide-details v-input--dense theme--light v-text-field v-text-field--enclosed v-text-field--outlined v-select v-autocomplete">
<div class="v-input__control">
<div role="combobox" aria-haspopup="listbox" aria-expanded="false" aria-owns="list-2" class="v-input__slot" style="height: 44px;">
<fieldset aria-hidden="true">
<legend style="width: 0px;"><span></span></legend>
</fieldset>
<div class="v-select__slot">
<label for="input-2" class="v-label theme--light" style="left: 0px; position: absolute;"><span></span></label><input data-testid="test-dropdown" id="input-2" type="text" autocomplete="off">
<div class="v-input__append-inner">
<div class="v-input__icon v-input__icon--append"><i aria-hidden="true" class="v-icon notranslate material-icons theme--light">$dropdownArrow</i></div>
</div>
<input type="hidden">
</div>
<div class="v-menu"></div>
</div>
</div>
</div>
</div>
My problem is where is items? <div class="v-menu"></div> Why I cant see in wrapper.html. I don't use shallowMount, I use mount. Because of this I cannot write my covered test cases.
How can I simulate opened menu and rendered all items and provide some assertions?
What am I missing?
Versions
"#nuxtjs/vuetify": "1.12.1",
"#vue/test-utils": "1.0.0-beta.29",
"vue-jest": "3.0.7"
Well, probably the better way would be mocking and substituting v-autocomplete with a test component (just control values that go to it and emit fake events from it). You are not developing Vuetify, so no need to test what happens inside a component.
I am new to vuejs. I'd wanted to create input fields dynamically by the click of a button using vuetify. My attempt to implement is shown below and commented line wise to show what I was trying to achieve.
I there a way this can be done? Any suggestion is welcome.
<template>
<v-container>
<v-row>
<v-col cols="12" sm="12">
<v-btn color="success">Add Input</v-btn>
</v-col>
<v-col cols="12" sm="12" ref="mount">
<!-- inputs fields are appended here -->
</v-col>
</v-row>
</v-container>
</template>
<script>
export default {
data() {
return {
items: {},
};
},
methods: {
addField() {
// create element
let textField = document.createElement("v-text-field");
// add it to the data property
this.$data.items["firstName"] = "";
// add the vmodel attribute to the element
textField.setAttribute("v-model", "firstName");
// finally mount it in the templates
this.$refs.mount.appendChild(textField);
},
},
};
</script>
You should use an array of objects like so basic: [{ name: "", time: "" }]. Then you can use a loop to bind inputfields to those properties and add items to that array. You can see a working example here
<div v-for="item in basic" :key="item.id">
<button #click="addRow">Add row</button>
<input type="text" v-model="item.name" />
<input type="text" v-model="item.time" />
{{ item.name }} - {{ item.time }}
</div>
data () {
return {
id: 1,
basic: [{ name: "", time: "" }]
};
}
addRow () {
console.log("added");
this.id += 1;
this.basic.push({
name: "",
time: ""
});
}
i'm kinda stuck. i get this error every time i try to update my store. what i'm doing is, i fetch my product and store it. here on upload image section (component) at created fill the image with state to show the already uploaded images, then on each upload change my state via dispatch and mutation. i know it has something to do with created() when i fill my data with state, but don't know how to solve that.
here is my template:
<v-card flat class="pb-4" max-width="450px">
<v-img class="rounded-lg theme__main__color" :src="url"></v-img>
<v-list container inline class="transparent text-center pa-0">
<v-list-item class="px-0">
<v-list-item class="px-1">
<v-btn
width="100%"
class="theme__btn__w text_main_color"
:loading="isSelecting"
#click="onChooseClick"
>{{lang.chooseimage}}</v-btn>
</v-list-item>
<v-list-item class="px-1">
<v-btn
width="100%"
class="theme__btn__s text_main_color"
#click.prevent="uploadImage"
>{{lang.upload}}</v-btn>
<input
ref="uploader"
class="d-none"
type="file"
accept="image/*"
#change="onFileChanged"
>
</v-list-item>
</v-list-item>
</v-list>
</v-card>
and this is my script:
export default {
data(){
return{
isSelecting: false,
selectedFile: null,
url: null,
placeholder: '/images/placeholder/place-800.png',
section: 'img',
pimages: [],
pId: null
}
},
methods:{
onChooseClick(){
this.isSelecting = true
window.addEventListener('focus', () => {
this.isSelecting = false
}, { once: true })
this.$refs.uploader.click()
},
onFileChanged(e) {
this.selectedFile = e.target.files[0]
this.url = URL.createObjectURL(this.selectedFile)
// do something
},
async uploadImage(){
if(this.notEmpty(this.selectedFile) && this.notEmpty(this.pId)){
const data = new FormData()
data.append('image', this.selectedFile)
data.append('pId', this.pId)
// let response = await this.axiosPost('product/createproimg', data)
this.pimages.push({"url": this.url})
this.url = this.placeholder
this.setEditProductImg(this.pimages)
}
}
},
created(){
this.url = this.placeholder
this.pId = this.editProduct.pId
this.pimages = this.editProduct.images
this.$nuxt.$on('insert',(section)=>{
if(section === 'desc' && !this.pId){
this.pId = this.editProduct.pId
}
})
}
}
and of course my store:
state:
editProduct: {
pId: null,
images: []
}
getter:
editProduct(state){
return state.editProduct
}
mutation:
SET_EDITPRODUCT_IMG(state, img){
state.editProduct.images = img
},
action:
setEditProductImg({commit}, img){
commit('SET_EDITPRODUCT_IMG', img)
},
UPDATE
thanks to #skirtle the above problem has been solved! but got a new same error on something else. as advise by #skirtle used const to mutate my state but get error. to be more clear, my state is an empty array, it will be filled the first time, but i get the error if i even try to change my select!!! let alone send another mutation!! here is the codes:
<template>
<div class="pt-6">
<v-row class="ma-0">
<v-col cols="12" md="12" class="pa-0">
<v-row class="ma-0">
<!-- form 1 -->
<template v-for="(select, index) in selects">
<component
:is="select"
:key="select.name"
v-model="catId"
#changed="addComponent(index)"
:catid="catId"
:selectindex="index"
:pcat="productCat[index]"
:subcat="subCat"
></component>
</template>
<!-- btn -->
<v-col cols="12" sm="6" md="3" class="px-1">
<v-btn width="100%" class="theme__little__color2 text_main_color px-2" #click.prevent="addCatBtn()">{{lang.addcat}}</v-btn>
</v-col>
{{selectedCatArr}}
<!-- btn -->
<addproductbtn :section="section" />
</v-row>
</v-col>
</v-row>
</div>
</template>
<script>
import addproductbtn from '~/components/global/cms/addproductbtn'
import selectcategory from '~/components/global/cms/selectcategory'
export default {
components:{
'addproductbtn': addproductbtn
},
data(){
return{
section: 'cat',
selects: [selectcategory],
catId: 0,
subCat: true,
selectedCatArr: []
}
},
methods:{
addComponent(index){
this.selects.length = index + 1
setTimeout(() => {
this.selects.push(selectcategory)
}, 1);
},
addCatBtn(){
this.goToRedirect('/cms/category/insert', this.$route.path, this.ProductId)
},
async insertCategory(){
const data = {
pId: this.editProduct.pId,
catId: this.catId
}
// let response = await this.axiosPost('product/catupdate', data)
const productCategory = this.selectedCatArr
this.setEditProductCat(productCategory)
}
},
computed:{
productCat(){
return this.editProduct.categories
},
ProductId(){
return this.editProduct.pId
}
},
created(){
this.$nuxt.$on('insert', ()=>{
this.insertCategory()
})
this.$nuxt.$on('nextcat', (subCat)=>{
this.subCat = subCat
})
this.$nuxt.$on('nextpanel', ()=>{
this.insertCategory()
})
this.$nuxt.$on('selectedcat', (selected, index)=>{
delete selected.subCategory
this.selectedCatArr.length = index
this.selectedCatArr.push(selected)
})
}
}
</script>
and my select component:
<template>
<v-col cols="12" sm="6" md="3" class="px-1 text_details_color3" v-if="showCat">
<v-select
return-object
:items="items"
:label="lang.category"
v-model="selected"
#change="emitEvent"
item-text="title"
item-value="id"
outlined></v-select>
{{selected}}
</v-col>
</template>
<script>
export default {
props:['selectindex','catid','pcat','subcat'],
data(){
return{
selected:{},
items:[],
showCat: true
}
},
async fetch(){
// this.items = await this.axiosGet(`categories/${this.catid}/1`)
this.items = [
{id: this.catid + 1, title: this.catid+'title1', subCategory: true},
{id: this.catid + 2, title: this.catid+'title2', subCategory: true},
{id: this.catid + 3, title: this.catid+'title3', subCategory: false},
{id: this.catid + 4, title: this.catid+'title4', subCategory: true}
]
},
methods:{
emitEvent(){
this.$emit('input', this.selected.id)
this.$emit('changed')
$nuxt.$emit('nextcat', this.selected.subCategory)
$nuxt.$emit('selectedcat', this.selected, this.selectindex)
}
},
computed:{
//
},
created(){
},
mounted(){
this.selected = this.pcat
this.showCat = this.subcat
}
}
</script>
I believe the problem is this line:
this.pimages.push({"url": this.url})
The array this.pimages is the same array that's inside the store state and by calling push you're modifying it outside the store.
There are a couple of ways you could fix this.
One way would be to perform the push inside a mutation, e.g. by having an ADD_EDITPRODUCT_IMG mutation:
mutation:
ADD_EDITPRODUCT_IMG(state, img){
state.editProduct.push(img)
},
You'd then call that in much the same way as with your current mutation, except that you'd just pass it the new image to add rather than passing the full array.
An alternative approach that is a bit closer to what you currently have would be to take a copy of the array rather than modifying the original:
const newImages = [...this.pimages, {"url": this.url}]
this.setEditProductImg(newImages)
Using this approach you wouldn't need any changes to your existing store.
Update:
I also suggest making pimages a computed property. There doesn't seem to be any good reason to 'copy' the data out of the store state in a created hook.
Basing on this How to render custom data in Bootstrap-Vue Table component? I created this code:
assignDocuments(id) {
const documents = this.$store.state.documentList.filter(e => e.user.idUser === id);
console.log(documents);
let i;
for(i=0; i < documents.length;i++) {
return documents[i] ? `${documents[i].filename}` : 'loading...';
}
}
but it doesn't work like I need... I need to display the names(filename) of all objects(in this case I have 2 objects in documents array) in array but now only name of first object is displayed in b-table.
EDIT:
B-table code:
<b-table ref="table" small striped hover :items="$store.state.userList" :fields="fields" head-variant="dark">
<template v-slot:cell(indexNumber)="row">
{{ row.item.indexNumber}}
</template>
<template v-slot:cell(name)="row">
{{ row.item.name}}
</template>
<template v-slot:cell(surname)="row">
{{ row.item.surname}}
</template>
<template v-slot:cell(specialization.specName)="row">
{{ row.item.specialization.specName}}
</template>
<template v-slot:cell(yearbook)="row">
{{ row.item.yearBook.startYear }}<b>/</b>{{ row.item.yearBook.endYear }}
</template>
<template v-slot:cell(details)="row">
<b-button size="sm" #click="row.toggleDetails" class="mr-2">
{{row.detailsShowing ? 'Ukryj' : 'Pokaż'}} Szczegóły
</b-button>
</template>
</b-table>
Fields:
fields:[
{
key: 'indexNumber',
label: 'Numer indeksu'
},
{
key: 'name',
label: 'Imię'
},
{
key: 'surname',
label: 'Nazwisko',
},
{
key: 'specialization.specName',
label: 'Kierunek',
},
{
key: 'yearBook',
label: 'Rocznik',
},
{
key: 'idUser',
label: 'Documents',
formatter: 'assignDocuments'
},
{
key: 'details',
label: 'Szczegóły',
},
],
It's because in your function you are returning after the first iteration of your loop. You instead need to get all the file names, and then return:
In the example that you shared, they are only assigning one value; the first/ last name as a string. In your case, it appears that you have multiple documents that could be related to a single row in the table.
Try changing your code to return all the docs:
assignDocuments(id) {
const documents = this.$store.state.documentList.filter(e => e.user.idUser === id);
console.log(documents);
let fileNames = [];
// Get the file name for each doc and put into our array
documents.forEach(elem => fileNames.push(elem.filename));
// Return the file names as a concatenated string
return filesNames.length > 0 ? fileNames.join() : 'loading...';
}
Edit:
Based on your comment, I have updated so that you can place this in buttons later on! What you could do is utilize the same function, and then dynamically create your buttons based on the docs that you have for that row.
<template>
<div>
<b-table :items="someTableItems" :fields="fields">
<button v-for="(doc, index) in assignDocuments(someIdHere)"
:key="`docBtn-${index}`"
:class="doc.cssClassObject">
{{ doc.filename }}
</button>
</b-table>
</div>
</template>
assignDocuments(id) {
const documents = this.$store.state.documentList.filter(e => e.user.idUser === id);
console.log(documents);
let files= [];
// Get the file name for each doc and put into our array
documents.forEach(elem => {
const isAccepted = elem.status === "accepted";
files.push({
status: elem.status,
filename: elem.filename,
cssClassObject : { accepted: isAccepted, rejected: !isAccepted }
});
});
// Return the files as an array
return files.length > 0 ? files : 'loading...';
}