Vue.js binds inline style expressions when rendering an Array - vue.js

I have the following components, I think if the width of the column when the value of a circle in the rendering of a style =" width: 200px ", if the column width no value is not rendering, how do I do?
<template>
<div class="table-scrollable">
<table class="table table-bordered table-hover">
<thead>
<tr v-if="tableOption.columns.length">
<th v-for="(column, index) in tableOption.columns">
{{ column.name }}
</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</template>
<script>
export default {
data() {
return {
columns: [{
name: app.localize('Actions'),
width: 200
}, {
name: app.localize('TenancyCodeName'),
field: 'tenancyName'
}, {
name: app.localize('Name'),
field: 'name'
}, {
name: app.localize('Edition'),
field: 'editionDisplayName'
}, {
name: app.localize('Active'),
field: 'isActive',
width: 100
}, {
name: app.localize('CreationTime'),
field: 'creationTime',
}]
}
}
}
</script>
Seeking a solution,thanks!

If you're sure to always have the width, yes, your solution is good enough.
Otherwsise, you can use a method:
methods: {
// a more explicit name would be better
style (width) {
return {
width: width || 200 + 'px'
}
}
}
Then, in the HTML, you could do
<th :style="style(column.width)">...</th>
Or use a computed property that makes sure the style attribute is present

Imo the easiest way would be if you would define styles in object, like this:
{
name: app.localize('Actions'),
style: {
width: '200px', // px suffix required
}
}
so then you can simply do:
<th v-for="(column, index) in columns" :style="column.style">
Is there a better solution?

Related

Jest: Quasar table is not rendering rows

I'm using Jest for testing a quasar table in VueJs 3. The table I have is like this:
<template>
<q-table id="myTable"
:rows="rows"
:columns="columns"
virtual-scroll
:rows-per-page-options="[0]"
hide-bottom
/>
</template>
<script lang="ts">
import {defineComponent, PropType} from 'vue'
export default defineComponent({
name: 'TableDemo',
props: {
rows: {
type: [] as PropType<[{ name: string, surname: string }]>,
default: []
},
},
setup() {
const columns = [
{
name: 'name',
required: true,
align: 'left',
label: 'name',
field: 'name',
sortable: true
},
{
name: 'surname',
label: 'surname',
align: 'left',
field: 'surname',
sortable: true
},
]
return {columns}
}
})
</script>
I'm trying to write a very simple test to understand how this works:
it('Should contain the word "John"', async () => {
)
const wrapper = mount(TableDemo, {
props: {rows: [{name: 'John', surname: 'Marston'}]},
}
)
expect(wrapper.find('#myTable').text()).toContain('John')
})
My problem is that it doesn't render the rows, but only the columns.
Here what Jest renders:
<div class="q-table__container q-table--horizontal-separator column no-wrap q-table__card q-table--no-wrap"
id="myTable"><!---->
<div class="q-table__middle q-virtual-scroll q-virtual-scroll--vertical scroll">
<table class="q-table">
<thead>
<tr>
<th class="text-left sortable">name<i aria-hidden="true"
class="notranslate material-icons q-icon q-table__sort-icon q-table__sort-icon--left"
role="presentation">arrow_upward</i></th>
<th class="text-left sortable">surname<i aria-hidden="true"
class="notranslate material-icons q-icon q-table__sort-icon q-table__sort-icon--left"
role="presentation">arrow_upward</i></th>
</tr>
</thead>
<tbody class="q-virtual-scroll__padding">
<tr>
<td colspan="2" style="height: 0px; --q-virtual-scroll-item-height: 48px;"/>
</tr>
</tbody>
<tbody class="q-virtual-scroll__content" id="qvs_1" tabindex="-1"/>
<tbody class="q-virtual-scroll__padding">
<tr>
<td colspan="2" style="height: 48px; --q-virtual-scroll-item-height: 48px;"/>
</tr>
</tbody>
</table>
</div><!----></div>
Where is my mistake? Why doesn't the table render the row?
UPDATE
I actually realized that it works without "virtual-scroll" option, so the problem is bound to that. Does anyone have experience with it?
It seems they have used some timer function inside virtual scroll implementation, this is my fix:
call jest.useFakeTimers(); method before describe block, then inside test call jest.runAllTimers(); and await wrapper.vm.$nextTick(); before assertion
it('renders correctly', async () => {
const wrapper: any = wrapperFactory();
jest.runAllTimers();
await wrapper.vm.$nextTick();
expect(wrapper.html()).toMatchSnapshot();
});

Vue Sorted Table (Sorted from highest to lowest)

Hi i am a newbie in vuejs. I wanted to sort a table by Highest to lowest. However i install the library of vue-sorted-table. But when im trying run the code the data always return lowest to highest. Can anyone help me? Thank you..
Here is the code:
<template>
<div id="app">
<sorted-table :values="values" #sort-table="onSort">
<thead>
<tr>
<th scope="col" style="text-align: left; width: 10rem;">
<sort-link name="id">ID</sort-link>
</th>
</tr>
</thead>
<template #body="sort">
<tbody>
<tr v-for="value in sort.values" :key="value.id">
<td>{{ value.id }}</td>
</tr>
</tbody>
</template>
</sorted-table>
</div>
</template>
<script>
import { ChevronUpIcon } from "vue-feather-icons";
export default {
name: "App",
data: function() {
return {
values: [{ id: 2 }, { id: 1 }, { id: 3 }],
sortBy: "",
sortDir: ""
};
},
components: {
ChevronUpIcon
},
methods: {
onSort(sortBy, sortDir) {
this.sortBy = sortBy;
this.sortDir = sortDir;
}
}
};
</script>
<style>
.feather {
transition: 0.3s transform ease-in-out;
}
.feather.asc {
transform: rotate(-180deg);
}
</style>
code can access here:
https://codesandbox.io/s/pedantic-kirch-bx9zv
Add dir property to sorted-table, and make its value equals to desc
<template>
<div id="app">
<sorted-table :values="values" #sort-table="onSort" dir="desc">
<thead>
<tr>
<th scope="col" style="text-align: left; width: 10rem;">
<sort-link name="id">ID</sort-link>
</th>
</tr>
</thead>
<template #body="sort">
<tbody>
<tr v-for="value in sort.values" :key="value.id">
<td>{{ value.id }}</td>
</tr>
</tbody>
</template>
</sorted-table>
</div>
</template>
<script>
import { ChevronUpIcon } from "vue-feather-icons";
export default {
name: "App",
data: function() {
return {
values: [{ id: 2 }, { id: 1 }, { id: 3 }],
sortBy: "",
sortDir: ""
};
},
components: {
ChevronUpIcon
},
methods: {
onSort(sortBy, sortDir) {
this.sortBy = sortBy;
this.sortDir = sortDir;
}
}
};
</script>
<style>
.feather {
transition: 0.3s transform ease-in-out;
}
.feather.asc {
transform: rotate(-180deg);
}
</style>
check https://github.com/BernhardtD/vue-sorted-table and pay attention to dir property of the table, you'll find the answer

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>"
})
},

:class not updating when clicked in vuejs v-for

I want to add a css class to a item in v-for when the td in clicked
<template>
<div>
<h1>Forces ()</h1>
<section v-if="errored">
<p>We're sorry, we're not able to retrieve this information at the moment, please try back later</p>
</section>
<section v-if="loading">
<p>loading...</p>
</section>
<table class="table table-bordered table-hover">
<thead>
<tr>
<th>#</th>
<th>ID</th>
<th
#click="orderByName = !orderByName">Forces</th>
</tr>
<th>Delete</th>
</thead>
<tbody>
<tr v-for="(force, index) in forces" #click="completeTask(force)" :class="{completed: force.done}" v-bind:key="index">
<td>
<router-link :to="{ name: 'ListForce', params: { name: force.id } }">Link</router-link>
</td>
<th>{{ force.done }}</th>
<th>{{ force.name }}</th>
<th><button v-on:click="removeElement(index)">remove</button></th>
</tr>
</tbody>
</table>
<div>
</div>
</div>
</template>
<script>
import {APIService} from '../APIService';
const apiService = new APIService();
const _ = require('lodash');
export default {
name: 'ListForces',
components: {
},
data() {
return {
question: '',
forces: [{
done: null,
id: null,
name: null
}],
errored: false,
loading: true,
orderByName: false,
};
},
methods: {
getForces(){
apiService.getForces().then((data, error) => {
this.forces = data;
this.forces.map(function(e){
e.done = false;
});
this.loading= false;
console.log(this.forces)
});
},
completeTask(force) {
force.done = ! force.done;
console.log(force.done);
},
removeElement: function (index) {
this.forces.splice(index, 1);
}
},
computed: {
/* forcesOrdered() {
return this.orderByName ? _.orderBy(this.forces, 'name', 'desc') : this.forces;
}*/
},
mounted() {
this.getForces();
},
}
</script>
<style>
.completed {
text-decoration: line-through;
}
</style>
I want a line to go through the items when clicked, but the dom doesn't update. If I go into the vue tab in chrome I can see the force.done changes for each item but only if I click out of the object and click back into it. I'm not to sure why the state isn't updating as it's done so when I have used an click and a css bind before.
I've tried to make minimal changes to get this working:
// import {APIService} from '../APIService';
// const apiService = new APIService();
// const _ = require('lodash');
const apiService = {
getForces () {
return Promise.resolve([
{ id: 1, name: 'Red' },
{ id: 2, name: 'Green' },
{ id: 3, name: 'Blue' }
])
}
}
new Vue({
el: '#app',
name: 'ListForces',
components: {
},
data() {
return {
question: '',
forces: [{
done: null,
id: null,
name: null
}],
errored: false,
loading: true,
orderByName: false,
};
},
methods: {
getForces(){
apiService.getForces().then((data, error) => {
for (const force of data) {
force.done = false;
}
this.forces = data;
this.loading= false;
console.log(this.forces)
});
},
completeTask(force) {
force.done = ! force.done;
console.log(force.done);
},
removeElement: function (index) {
this.forces.splice(index, 1);
}
},
mounted() {
this.getForces();
}
})
.completed {
text-decoration: line-through;
}
<script src="https://unpkg.com/vue#2.6.10/dist/vue.js"></script>
<div id="app">
<div>
<h1>Forces ()</h1>
<section v-if="errored">
<p>We're sorry, we're not able to retrieve this information at the moment, please try back later</p>
</section>
<section v-if="loading">
<p>loading...</p>
</section>
<table class="table table-bordered table-hover">
<thead>
<tr>
<th>#</th>
<th>ID</th>
<th
#click="orderByName = !orderByName">Forces</th>
<th>Delete</th>
</tr>
</thead>
<tbody>
<tr v-for="(force, index) in forces" #click="completeTask(force)" :class="{completed: force.done}" v-bind:key="index">
<th>{{ force.done }}</th>
<th>{{ force.name }}</th>
<th><button v-on:click="removeElement(index)">remove</button></th>
</tr>
</tbody>
</table>
<div>
</div>
</div>
</div>
The key problem was here:
this.forces = data;
this.forces.map(function(e){
e.done = false;
});
The problem here is that the property done is being added to the data after it has been made reactive. The reactivity observers get added as soon as the line this.forces = data runs. Adding done after that counts as adding a new property, which won't work.
It's also a misuse of map so I've switched it to a for/of loop instead.
for (const force of data) {
force.done = false;
}
this.forces = data; // <- data becomes reactive here, including 'done'
On an unrelated note I've tweaked the template to move the Delete header inside the top row.
Make sure force.done is set to false in data before initialization so it is reactive.
data() {
return {
force:{
done: false;
}
}
}
If force exists but has no done member set, you can use Vue.set instead of = to create a reactive value after initialization.
Vue.set(this.force, 'done', true);

Remove datatables search field without disabling searchable

I made an datatable in Angular5 using angular-datatables. I then created the table footer and implemented input fields to search each column individually, and added style="display: table-header-group;" to the footer so it goes above the header.
Now I want to remove the standard datatables search input field.
If i just disable it in datatable configuration, then the custom search fields won't work.
I also tried to change value of class dataTables_filter to display: none, but it won't work, since it get's it's styling from node_modules folder.
I could change my node_modules/datatables/style.css but the problem is, that's the only folder that is on the gitignore list, and doesn't go to our repository.
Here is my code:
HTML:
<tfoot style="display: table-header-group;">
<tr>
<th style="width: 25%">
</th>
<th style="width: 25%">
</th>
<th style="width: 25%">
<input type="text" placeholder="Search actions" name="search-actions" class="form-control" />
</th>
<th style="width: 25%">
<input type="text" placeholder="Search messages" name="search-messages" class="form-control" />
</th>
</tr>
</tfoot>
TypeScript:
ngOnInit(): void {
this.dtOptions = {
ajax: 'http://www.mocky.io/v2/5b0eb2083200006300c19bd8',
columns: [{
title: 'Date',
data: 'date'
}, {
title: 'Time',
data: 'time'
}, {
title: 'Action',
data: 'action'
}, {
title: 'Message',
data: 'message'
}]
};
}
ngAfterViewInit(): void {
this.datatableElement.dtInstance.then((dtInstance: DataTables.Api) => {
dtInstance.columns().every(function () {
const that = this;
$('input', this.footer()).on('keyup change', function () {
if (that.search() !== this['value']) {
that
.search(this['value'])
.draw();
}
});
});
});
}
Use dom option and exclude f from default value (lfrtip for default style) to hide filtering control.
For example:
this.dtOptions = {
// ... skipped ...
dom: 'lrtip'
}