vue2 list return mixed string or component - vue.js

In loop like this, I mostly just iterate over item values as strings, but sometimes need to return rendered component, for example build link element, or dropdown menu, for that table cell - need to find a way to return other component output instead of raw html string
<tr class="listing-item listing-item-category">
<td v-for="td in headeritems">{{val(td.k)}}</td>
</tr>
Is that even possible? I've found no mention of this, how should the method code go to return other component output? I know I would have to use v-html, but how to get it?

Assume we have a list like this:
headerItems: [
{
type: 'text',
value: 'Some text'
},
{
type: 'img',
props: {
src: 'http://some-where....'
}
},
{
type: 'my-component',
value: 'v-model value',
props: {
prop1: 10,
prop2: 'Blah bla',
},
events: {
myEvt: () => console.log('myEvt has fired')
}
},
],
So, We can render it:
<tr>
<td
v-for="(item, i) in headerItems" :key="i"
>
<div v-if="item.type === 'text'"> {{ item.value }}</div>
<component
v-else
:is="item.type"
v-model="item.value"
v-bind="item.props"
v-on="item.events"
/>
</td>
</tr>

Related

changing a single value using v-model / full table is redrawn

I was building an editable table, which began to crawl to a halt when the number of rows started to run in the 100's. This led me to investigate what was going on.
In the example below, when changing the value in the input, the whole table is redrawn, and the ifFunction() function is trigged 4 times.
Why is this happening? Shouldn't Vue be capable of just redrawing the respective cell? Have I done something wrong with the key-binding?
<template>
<div id="app">
<table border="1" cellpadding="10">
<tr v-for="(row, rowKey) in locations" :key="`row_+${rowKey}`">
<td v-for="(column, columnKey) in row" :key="`row_+${rowKey}+column_+${columnKey}`">
<span v-if="ifFunction()">{{ column }}</span>
</td>
</tr>
</table>
<input v-model="locations[0][1]">
</div>
</template>
<script>
export default {
data() {
return {
locations: [
["1","John"],
["2","Jake"]
], // TODO : locations is not generic enough.
}
},
methods: {
ifFunction() {
console.log('ifFunction');
return true;
},
}
}
</script>
The data property defines reactive elements - if you change one part of it, everything that's depending on that piece of data will be recalculated.
You can use computed properties to "cache" values, and only update those that really need updating.
I rebuilt your component so computed properties can be used throughout: created a cRow and a cCell component ("custom row" and "custom cell") and built back the table from these components. The row and the cell components each have a computed property that "proxies" the prop to the template - thus also caching it.
On first render you see the ifFunction() four times (this is the number of cells you have based on the data property in Vue instance), but if you change the value with the input field, you only see it once (for every update; you may have to click "Full page" to be able to update the value).
Vue.component('cCell', {
props: {
celldata: {
type: String,
required: true
},
isInput: {
type: Boolean,
required: true
},
coords: {
type: Array,
required: true
}
},
data() {
return {
normalCellData: ''
}
},
watch: {
normalCellData: {
handler: function(value) {
this.$emit('cellinput', {
coords: this.coords,
value
})
},
immediate: false
}
},
template: `<td v-if="ifFunction()"><span v-if="!isInput">{{normalCellData}}</span> <input v-else type="text" v-model="normalCellData" /></td>`,
methods: {
ifFunction() {
console.log('ifFunction');
return true;
},
},
mounted() {
this.normalCellData = this.celldata
}
})
Vue.component('cRow', {
props: {
rowdata: {
type: Array,
required: true
},
rownum: {
type: Number,
required: true
}
},
template: `
<tr>
<td
is="c-cell"
v-for="(item, i) in rowdata"
:celldata="item"
:is-input="!!(i % 2)"
:coords="[i, rownum]"
#cellinput="reemit"
></td>
</tr>`,
methods: {
reemit(data) {
this.$emit('cellinput', data)
}
}
})
new Vue({
el: "#app",
data: {
locations: [
["1", "John"],
["2", "Jake"]
], // TODO : locations is not generic enough.
},
methods: {
updateLocations({
coords,
value
}) {
// creating a copy of the locations data attribute
const loc = JSON.parse(JSON.stringify(this.locations))
loc[coords[1]][coords[0]] = value
// changing the whole locations data attribute to preserve
// reactivity
this.locations = loc
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<table border="1" cellpadding="10">
<tbody>
<tr v-for="(row, i) in locations" is="c-row" :rowdata="row" :rownum="i" #cellinput="updateLocations"></tr>
</tbody>
</table>
<!-- <input v-model="locations[0][1]">
<input v-model="locations[1][1]">-->
{{locations}}
</div>

Vue,js, Computing a property with v-model?

I have a vue page that loads a bunch of actions from a database. It then creates a table with some data got from the action, using v-for.
Here is my problem: In one of the table rows I need a checkbox that is v-modeled to an attribute, action.weekly. Ideally, it will be a boolean. But there are a bunch of action entries in my database that don't have a weekly attribute. In those cases, I need the checkbox checked, as if it were true. Normally, I would use a computed property here, but you can't pass arguments to computed properties, so I don't know how to tell vue which action to look at at (I can't pass $event, ndx in to a computed property like I am doing below with handleEnableChanged() ).
Here is my code for the table:
<tbody>
<tr v-for="(action, ndx) in actions" >
<td class="pointer" #click='openModalCard(action, ndx)'>
{{action.name}}
</td>
<input type="checkbox" v-model="action.weekly??" #change="handleEnableChanged($event, ndx)"/>
</td>
<input type="checkbox" v-model="action.enabled" #change="handleEnableChanged($event, ndx)">
</td>
</tr>
</tbody>
In the cases where action does not have a weekly attribute, I want the checkbox checked as if it were true. How can I accomplish this?
If there is a better way to approach this, please let me know. I'm still a novice with vue.js.
I think it would be easiest to use v-if and v-else for this.. With that being said, I have also provided an example of how to handle this without v-if and v-else..
Using v-if and v-else:
new Vue({
el: "#root",
data: {
actions: [
{
name: "first",
weekly: true,
enabled: false
},
{
name: "second",
enabled: false
},
{
name: "third",
weekly: true,
enabled: true
},
{
name: "fourth",
enabled: true
}
]
},
template: `
<tbody>
<tr v-for="(action, ndx) in actions" >
<td class="pointer" #click='console.log(action, ndx)'>{{action.name}}</td>
<input v-if="'weekly' in action" type="checkbox" v-model="action.weekly" #change="handleEnableChanged($event, ndx)"/>
<input v-else type="checkbox" checked="true" #change="handleEnableChanged($event, ndx)"/>
</td>
<input type="checkbox" v-model="action.enabled" #change="handleEnableChanged($event, ndx)">
</td>
</tr>
</tbody>
`,
methods: {
handleEnableChanged(evt, ndx) {
console.log(evt, ndx);
},
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.min.js"></script>
<div id="root"></div>
Without v-if and v-else:
new Vue({
el: "#root",
data: {
actions: ""
},
template: `
<tbody>
<tr v-for="(action, ndx) in actions" >
<td class="pointer" #click='console.log(action, ndx)'>{{action.name}}</td>
<input type="checkbox" v-model="action.weekly" #change="handleEnableChanged($event, ndx)"/>
</td>
<input type="checkbox" v-model="action.enabled" #change="handleEnableChanged($event, ndx)">
</td>
</tr>
</tbody>
`,
methods: {
handleEnableChanged(evt, ndx) {
console.log(evt, ndx);
},
getActions() {
let originalActions = [
{
name: "first",
weekly: true,
enabled: false
},
{
name: "second",
enabled: false
},
{
name: "third",
weekly: true,
enabled: true
},
{
name: "fourth",
enabled: true
}
];
this.actions = originalActions.map(a => {
return {
name: a.name,
weekly: 'weekly' in a ? a.weekly : true,
enabled: a.enabled
}
})
}
},
created() {
this.getActions();
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.min.js"></script>
<div id="root"></div>
You can just do this:
<input type="checkbox" :checked="!!action.weekly">
The !! operator will return true if the value is not undefined or empty
If you want all items in the array to have an action.weekly property, then update your data when it is initially retrieved. You can then just use v-model="action.weekly" for all items in the array.
this.newArray = this.oldArray.map(item => {
if(!item.hasOwnProperty('weekly')){
item.weekly = true;
}
return item
})

Why aren't images being displayed in my v-data-table

So currently I am using v-data-table to display the data being passed into the vue component via a prop. The prop is an array of objects, each object has several fields but the only fields I want to display are name, img, and store.price. Name and price display perfectly fine, but when I try to display an image, only the image link appears in the data table. Can someone take a look at my code and steer me in the right direction?
<template>
<v-data-table
v-model="selected"
:headers="headers"
:items="displayedTools"
:items-per-page="10"
:single-select="singleSelect"
show-select
class="elevation-1 tool-table"
>
<template slot="items" slot-scope="props">
<td><img :src="props.item.img" style="width: 10px; height: 10px"></td>
<td class="text-xs-right">{{ props.item.name }}</td>
<td class="text-xs-right">{{ props.item.stores.price }}</td>
</template>
</v-data-table>
</template>
<script>
export default {
name: 'ToolResults',
props: ['found_tools'],
data() {
return {
singleSelect: false,
selected: [],
headers: [
{
text: 'Image',
value: 'img'
},
{
text: 'Tool Name',
align: 'left',
sortable: true,
value: 'name',
},
{
text: 'Price',
value: 'stores.price'
}
],
displayedTools: [{}]
}
},
created() {
this.populateTable()
this.addImages()
},
methods: {
populateTable(){
this.found_tools.forEach(element => {
this.displayedTools.push(element);
});
},
//Method might need to be added to display Prices properly because it is an array.
displayPrices(){
},
//Add Image to rows
addImages(){
this.displayedTools.forEach(function(part, index, theArray) {
//theArray[index].img = "<v-img src=" + theArray[index].img + "><v-img>"
theArray[index].img = 'https:' + theArray[index].img
})
},
toToolboxPage(toolbox) {
console.log(toolbox)
// The Toolbox.vue compoent is already created. The user just needs to be redirected there
}
}
}
</script>
<style>
.tool-table {
margin: 2em;
}
</style>
The result of the above code is:
It seems that you haven't closed your img tag at the template.
Also in your addImages() method you have commented the line that assign the images and left the one that assigns a string and the v-img tag isn't closed.
So it should be something like:
addImages(){
this.displayedTools.forEach(function(part, index, theArray) {
theArray[index].img = "<v-img src=" + theArray[index].img + "></v-img>"
})
},

dynamically call an object property in a v-for loop

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>

vue.js how to v-model values as separate arrays

from the backend I'm getting an array like this.
then I render this array to a table like this
My code
<tr v-for="item in items">
<td>
{{item[1]}}
</td>
<td>
{{item[2]}}
</td>
<td>
<input type="text" v-model="grnItems[items[1]]"/>
</td>
</tr>
This is a purchase return component
what I want is v-model this each an every input element as a separate array along with the item name.
like this
[
["chicken","12"]
["chille","19"]
]
How do I achieve this using vue.js?
Use an auxiliar array with the data populated the way you want, some example using computed properties
new Vue({
el: '#app',
data: {
items: [['1', 'some text', '66'], ['2', 'another text', '12'], ['5', 'random text', '89']],
result: []
},
computed: {
procesedItems() {
return this.items.map(i => ({
id: i[0],
name: i[1],
amount: i[2]
}))
}
},
methods: {
doSomething() {
this.result = this.procesedItems.map(i => {
let aux = [];
aux.push(i.name, i.amount)
return aux
})
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>
<div id="app">
<ul>
<li v-for="item in procesedItems"> {{item.id }} {{item.name }} <input v-model="item.amount"/></li>
</ul>
<button #click="doSomething">Calculate</button>
{{ result }}
</div>