dynamically call an object property in a v-for loop - vue.js

so i ran into a problem again,
i want to make a table component where you can send a array to the component, and it will render a table for you
we set it up like this
<template>
<section class="container">
<Apptable :search="true" :loader="true" title="User data" :data="users"/>
</section>
</template>
<script>
import Apptable from "~/components/table.vue";
export default {
components: {
Apptable
},
data() {
return {
users: [
{
id: 1,
name: "Lars",
Adres: "hondenstraat 21",
phone: "06555965"
},
{
id: 1,
name: "John",
Adres: "verwelstraat 35",
phone: "06555965"
}
]
};
}
};
</script>
i send data to the component and loop it from there like this
<template>
<section class="container">
<h2 v-if="title">{{title}}</h2>
<input v-if="search" class="search" placeholder="Search">
<button v-if="loader" class="update" #click="dialog = true">Update</button>
<table class="table">
<thead>
<tr class="tableheader">
<th v-for="(item, index) in Object.keys(data[0])" :key="index">{{item}}</th>
</tr>
</thead>
<tbody>
<tr class="userdata" v-for="(item, index) in data" :key="index">
<td v-for="(name, index) in Object.keys(data[index])" :key="index">{{//TODO: I WANT TO SELECT THE ITEM.DYNAMIC PROPERTY}}</td>
</tr>
</tbody>
</table>
<loader v-if="loader" :trigger="dialog"/>
</section>
</template>
<script>
import loader from "~/components/loader.vue";
export default {
components: {
loader
},
data() {
return {
dialog: false
};
},
watch: {
dialog(val) {
if (!val) return;
setTimeout(() => (this.dialog = false), 1500);
}
},
props: {
data: {
type: Array,
required: true
},
title: {
type: String,
required: false,
default: false
},
loader: {
type: Boolean,
required: false,
default: false
},
search: {
required: false,
type: Boolean,
default: true
}
}
};
</script>
so if you look at the table. were i left the todo, if i put in the {{item}} in the todo place. i will get this in my column
enter image description here
but i want to select the key of the object dynamically. but if i put {{item.name}} in the todo place it will not select the key dynamically.
so the point is that i want to dynamically call a property from the object in the v-for so the columns will get the data in the cells.

You should use item[name] instead of item.name
<tbody>
<tr class="userdata" v-for="(item, index) in data" :key="index">
<td v-for="(name, nIndex) in Object.keys(data[index])" :key="nIndex">
{{ item[name] }}
</td>
</tr>
</tbody>

Related

how to display 2 lists filtered by another field from one model

Model: Article.
id.
name.
type: ['code', 'design']
API gets all articles
How can I display two lists:
all articles with type ='Code',
all articles with type = 'Design'
In other words, is it possible to filter the API query
Or is it better to do it on the API side?
Extra: same as above but in a nested environment (ie Articles belong to Category. How to do it on the category detail page.
You can use computed properties. I built a sample component:
EDIT: Took some time to DRY it up.
Parent.vue
<template>
<div class="parent">
<div class="row">
<div class="col-md-6">
<article-list title="Code Articles" :articles="codeArticles" />
</div>
<div class="col-md-6">
<article-list title="Design Articles" :articles="designArticles" />
</div>
</div>
</div>
</template>
<script>
import ArticleList from './ArticleList.vue'
export default {
components: {
ArticleList
},
data() {
return {
articles: [
{
id: 1,
name: 'Article1',
type: 'Code'
},
{
id: 2,
name: 'Article2',
type: 'Design'
},
{
id: 3,
name: 'Article3',
type: 'Code'
},
{
id: 4,
name: 'Article4',
type: 'Design'
},
]
}
},
computed: {
codeArticles() {
return this.articles.filter(article => article.type === 'Code');
},
designArticles() {
return this.articles.filter(article => article.type === 'Design');
}
}
}
</script>
ArticleList.vue
<template>
<div class="two-filtered-lists">
<h5>{{ title }}</h5>
<table class="table table-bordered">
<thead>
<tr>
<th>ID</th>
<th>NAME</th>
<th>TYPE</th>
</tr>
</thead>
<tbody>
<tr v-for="article in articles" :key="article.id">
<td>{{ article.id }}</td>
<td>{{ article.name }}</td>
<td>{{ article.type }}</td>
</tr>
</tbody>
</table>
</div>
</template>
<script>
export default {
props: {
title: {
type: String,
required: true
},
articles: {
type: Array,
required: true
}
}
}
</script>

Why child component not mounted?

Why child component not mounted?
Base template:
<TableComponent :data="data">
<template slot="thead">
<tr>
<TableHeader></TableHeader>
</tr>
</template>
</TableComponent>
TableComponent:
<template>
<div>
<table>
<thead>
{{ renderThead() }}
</thead>
<tbody>
</tbody>
<tfoot>
</tfoot>
</table>
</div>
</template>
<script>
export default {
name: 'TableComponent',
props: {
data: {
required: true,
type: [Array, Function]
},
itemKey: {
default: 'id',
},
},
methods: {
renderThead: function () {
let method = this.$scopedSlots.thead
? this.$scopedSlots.thead
: () => this.$slots.thead
console.info(method())
// return method();
},
},
}
</script>
It is TableHeader component :
<template>
<th>
{{ name }}
</th>
</template>
<script>
export default {
name: 'TableHeader',
props: {
name: {
type: String
}
},
mounted() {
console.info("MOUNTED")
}
}
</script>
Console info on TableHeader not working.
Do you have any ideas?

Looping a component in another component Vue.js

The question here is how can I get this Task.vue file to loop in my tasklist.vue file knowing I'm willing to pass a json file so I can get the list of all the task to do.
Task.vue
<template>
<table :id="id" class="task_box">
<tr>
<td class="task_user">{{name}}</td>
<td class="task_date">{{date}}</td>
<td class="task_time">{{time}}</td>
</tr>
<tr>
<td colspan="3" class="task_description">
<div class="toto">{{description}}</div>
</td>
</tr>
</table>
</template>
<script>
export default {
name: "task",
data() {
return {
id: 1,
name: "Test",
date: new Date(),
time: "9:30 ",
description: "whatever"
};
}
};
</script>
So this task.vue is meant to be a container that I can use in the tasklist.vue.
tasklist.vue
<template>
<div>
<task v-for="task in tasks" :key="task.id"></task>
</div>
</template>
<script>
import Task from "./Task.vue";
export default {
name: "tasklist",
data() {
return {
tasks: []
};
},
components: {
Task
}
};
</script>
You need to use Properties to pass your task from your task list to your task. Code is untested.
#Task
<template>
<table :id="task.id" class="task_box">
<tr>
<td class="task_user">{{task.name}}</td>
<td class="task_date">{{task.date}}</td>
<td class="task_time">{{task.time}}</td>
</tr>
<tr>
<td colspan="3" class="task_description">
<div class="toto">{{description}}</div>
</td>
</tr>
</table>
</template>
<script>
export default {
name: "task",
props: ["task"],
};
</script>
#TaskList
<template>
<div>
<task v-for="task in tasks" :task="task" :key="task.id"></task>
</div>
</template>
<script>
import Task from "./Task.vue";
export default {
name: "tasklist",
data() {
return {
tasks: [{
id: 1,
name: "Test",
date: new Date(),
time: "9:30 ",
description: "whatever"
}]
};
},
components: {
Task
}
};
</script>
If task-component is reapeating, you should insert it's tag inside table tag.
Use props to pass data to task-component from tasklist-component
When tasklist-component is creating, you can load tasks via Ajax from json.
Full working example of code you can find here
TaskList.vue
<script>
import Task from "./Task.vue";
export default {
components: { Task },
data() {
return {
tasks: [],
isLoading: false,
doShowNewTaskAddingDialog: false,
};
},
created(){
// this.isLoading = true;
// $.ajax({
// url: '/some/tasks/url',
// method: 'GET',
// dataType: 'json',
// success: (tasks) => {
// this.isLoading = false;
// this.tasks = tasks;
// }
// });
this.tasks = [
{id: 1, name: "task 1", date: new Date(), time: "9:31", description: "descr 1" },
{id: 1, name: "task 2", date: new Date(), time: "9:32", description: "descr 2" },
{id: 1, name: "task 3", date: new Date(), time: "9:33", description: "descr 3" },
{id: 1, name: "task 4", date: new Date(), time: "9:34", description: "descr 4" },
]
},
methods:{
addButtonHandler(){
this.doShowNewTaskAddingDialog = true;
}
}
};
</script>
<template>
<div>
<div v-if="isLoading">Please wait, loading tasks...</div>
<table v-if="!isLoading">
<task
v-for="task in tasks"
:key="task.id"
:task="task"
:isNew="false"
/>
<task
v-if="doShowNewTaskAddingDialog"
:isNew="true"
/>
</table>
Add new?
</div>
</template>
<style>
table, td{
border-collapse: collapse;
border: 1px solid black;
}
</style>
Task.vue
<template>
<!--
I'd prefer use bootstrap row and col- divs here instead
of table and tbody-hack. See discussion here: https://github.com/vuejs/Discussion/issues/295
-->
<tbody>
<!-- display only -->
<tr v-if="!isNew">
<td class="task_user">{{name}}</td>
<td class="task_date">{{date}}</td>
<td class="task_time">{{time}}</td>
</tr>
<tr v-if="!isNew">
<td colspan="3" class="task_description">
<div class="toto">{{description}}</div>
</td>
</tr>
<!-- edit -->
<tr v-if="isNew">
<td class="task_user"><input type="text" v-model="name"></td>
<td class="task_date"><input type="text" v-model="date"></td>
<td class="task_time"><input type="text" v-model="time"></td>
</tr>
<tr v-if="isNew">
<td colspan="3" class="task_description">
<div class="toto"><input type="text" v-model="description"></div>
</td>
</tr>
</tbody>
</template>
<script>
export default {
props:{
task: { type: Object, required: false },
isNew: { type: Boolean, required: true },
},
created(){
if(!this.isNew){
this.id = this.task.id;
this.name = this.task.name;
this.date = this.task.date;
this.time = this.task.time;
this.description = this.task.description;
}
},
data() {
return {
id: 1,
name: "",
date: new Date(),
time: "0:00 ",
description: ""
};
}
};
</script>

Computed property on child component props

I'm having this setup where I have child component props that have datetime format inside it and I want to change it to more human readable format in my table, so i use moment js to change the format and to do those kinds of task it will be made more sense if I use computed property. Like this in my index.vue
<div class="page-container">
<div class="page-content">
<div class="content-wrapper">
<data-viewer :source="source" :thead="thead">
<template scope="props">
<tr>
<td>{{props.item.name}}</td>
<td>{{props.item.creator}}</td>
<td>
<i class="icon-checkmark5" v-if="props.item.publish === '0'"></i>
<i class="icon-cancel-circle2" v-else></i>
{{props.item.publish}} //for testing purpose to see returned value
</td>
<td>{{publishDate}}</td> //this is where i put my computed to change created_at format
</tr>
</template>
</data-viewer>
</div>
</div>
</div>
<script type="text/javascript">
import DataViewer from '../../components/dataviewer.vue'
import moment from 'moment'
export default{
components:{
DataViewer
},
data(){
return{
source: '/api/article',
thead: [
{title: 'Name', key: 'name', sort: true},
{title: 'Creator', key: 'creator_id', sort: true},
{title: 'Publish', key: 'publish', sort: true},
{title: 'Created', key: 'created_at', sort: true}
],
}
},
computed: {
publishDate: function(){
return moment(props.item.created_at).format('YYYY-MM-DD')
}
}
}
</script>
and here is what inside my dataviewer file
<template>
<table class="table">
<thead class="bg-primary">
<tr>
<th v-for="item in thead">
<span>{{item.title}}</span>
</th>
</tr>
</thead>
<tbody>
<slot v-for="item in model.data" :item="item"></slot>
</tbody>
</table>
</template>
<script>
import Vue from 'vue'
import axios from 'axios'
export default {
props: ['source', 'thead'],
data() {
return {
model: {
data: []
},
}
},
beforeMount() {
this.fetchData()
},
methods: {
fetchData() {
var vm = this
axios.get(this.source)
.then(function(response) {
Vue.set(vm.$data, 'model', response.data.model)
})
.catch(function(error) {
console.log(error)
})
}
}
}
</script>
but it just won't work, it can't find props.item.created_at, so how I can change created_at or any other property item to change from my index.vue?
Seems like using a filter will work here:
Instead of using computed props:
filters: {
publishDate: function(value){
return moment(value).format('YYYY-MM-DD')
}
}
In place of {{publishDate}}
<td>{{props.item.created_at | publishedDate }}</td>

Vue 2 - change data value of all components

I have following setup:
Vue code:
Vue.component('ordering-filters', {
template: `
<a href="#"
:class="iconClass + faClass"
aria-hidden="true"
#click="orderCountries({orderBy, order})">
</a>`,
props: {
orderBy: {
type: String,
required: true
},
order: {
type: String,
required: true
},
iconClass: {
type: String,
default: "fa fa-lg text-muted"
},
faClass: {
type: String,
required: true
}
},
methods: {
orderCountries(params){
Event.$emit('ordering-filters', params);
}
},
data() {
return {
isActive: false
}
}
});
HTML code:
<tr>
<td class="col-md-6">Country Name
<div class="arrow-group">
<ordering-filters
order-by="name"
order="asc"
fa-class=" fa-sort-asc"
></ordering-filters>
<ordering-filters
order-by="name"
order="desc"
fa-class=" fa-sort-desc"
></ordering-filters>
</div>
</td>
<td class="countries-visible-filter col-md-3">
<visible-filters></visible-filters>
</td>
<td>Order
<div class="arrow-group">
<ordering-filters
order-by="order"
order="asc"
fa-class=" fa-sort-asc"
></ordering-filters>
<ordering-filters
order-by="order"
order="desc"
fa-class=" fa-sort-desc"
></ordering-filters>
</div>
</td>
<td>Actions</td>
</tr>
I want to change isActive to all the ordering-filters components when click event is fired, set it to false for all and then set it to true on the clicked element. Is this possible?
You must delegate the control of the active element to the parent somehow. Here is one of the many ways to do that, using v-model (which is just a shortcut for :value and #input), and a simple computed prop.
Vue.component('ordering-filters', {
template: `<a href="#" #click="orderCountries()">
{{ filterId }}
<template v-if="isActive"> I'm on. </template>
</a>`,
props: ['value', 'filterId'],
computed: {
isActive() {
return this.value === this.filterId;
}
},
methods: {
orderCountries() {
// Do some ordering stuff
this.$emit('input', this.filterId); // The active element is now this one
}
}
});
new Vue({
el: '#app',
data() {
return {
activeFilterId: null
};
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.1/vue.min.js"></script>
<div id="app">
<ordering-filters filter-id="one" v-model="activeFilterId"></ordering-filters> -
<ordering-filters filter-id="two" v-model="activeFilterId"></ordering-filters> -
<ordering-filters filter-id="three" v-model="activeFilterId"></ordering-filters>
</div>