Vuetify v-data-table, How to render line break (enter key) from v-textarea input - vuetify.js

I have input data from v-textarea with enter key and the result like
Hello\nWorld
Rendering this in v-data-table with default or rawHtml not working
<v-data-table
:headers="headers"
:items="dataTable"
hide-default-footer>
<template v-slot:item="props">
<tr>
<td><span v-html="props.item.s"></span></td> <!-- rawHtml -->
<td>{{ props.item.s }}</td> <!-- default-->
</tr>
</template>
</v-data-table>

Vuetify uses browser conventions to render tables,
To render a line break inside a <td>, the suggestion is to either use a pre tag or the CSS style td { white-space:pre }
See answers to this post.
I included the CSS inline using a template within the v-data-table
new Vue({
el: '#app',
vuetify: new Vuetify(),
data () {
return {
headers: [
{
text: 'ID',
align: 'start',
sortable: false,
value: 'name'
},
{
text: 'Description',
align: 'start',
sortable: false,
value: 'description'
}
],
items: [
{
name: "001",
description: `First line of content.
Second line of content.
Third line of content (with left tab).`
},
{
name: "002",
description: `First line of content.
Second line of content.
Third line of content (with left tab).`
},
]
}
},
})
<!-- begin snippet: js hide: false console: true babel: false -->
<link href="https://cdn.jsdelivr.net/npm/vuetify/dist/vuetify.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vuetify/dist/vuetify.js"></script>
<div id="app">
<v-app id="other">
<v-data-table :headers="headers" :items="items" item-key="name" class="elevation-1">
<template v-slot:item.description="{ item }">
<div style="white-space: pre-wrap;">{{ item.description }}</div>
</template>
</v-data-table>
</v-app>
</div>

Related

Router breaks vuetify tabs

I have some vuetify tabs that work as expected before I add routes.
I want the route to change when the active tab changes.
When I add the :to directive to the the v-tab the routes work correctly (route changes when the tab changes) but the tab items do not display. The area where the v-tab-item was displayed is now blank.
<v-tabs v-model='activeTab'>
<v-tab
v-for='cc in cost_centres'
:key='cc.id'
:to="{ name: 'Element', params: {id: cc.id}}"
>
{{ cc.name }}
</v-tab>
<v-tabs-items v-model='activeTab'>
<v-tab-item
v-for='cc in cost_centres'
:key='cc.id'>
<!-- Content -->
</v-tab-item>
</v-tabs-items>
</v-tabs>
The vuetify docs reference the vue-router docs and I think I am wiring it up right but I just can't get it to work.
What am I missing?
the :to was setting the value of the v-tab and therefore the v-model of the v-tabs to the path instead of the id (or the index of the array).
So I removed the :to directive and watched the v-model.
When the v-model changed I extracted the value (index of the array) and used it to get the cost centre id and then set the route using router.push
On page load (mounted) I use the route param (cost centre id) to find the cost_centre index and set the activeTab v-model.
You might use <router-view> instead of the <v-tabs-items>:
const Foo = {
computed: {
id() {
return this.$route.params.id || ''
}
},
template: '<v-card flat>foo {{ id }}</v-card>'
}
const Bar = {
computed: {
id() {
return this.$route.params.id || ''
}
},
template: '<v-card flat>bar {{ id }}</v-card>'
}
const routes = [{
path: "/",
redirect: {
name: "foo"
}
},
{
path: "/foo",
name: "foo",
component: Foo
},
{
path: "/bar",
name: "bar",
component: Bar
}
]
const router = new VueRouter({
routes
})
new Vue({
el: '#app',
router,
vuetify: new Vuetify(),
data() {
return {
activeTab: "foo",
cost_centres: [{
id: 1,
name: "foo"
},
{
id: 2,
name: "bar"
},
]
}
},
watch: {
$route: function(val) {
console.log(val)
}
},
mounted() {
console.log(this.$route)
}
})
<link href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/#mdi/font#5.x/css/materialdesignicons.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/vuetify#2.x/dist/vuetify.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/vue#2.x/dist/vue.js"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vuetify#2.x/dist/vuetify.js"></script>
<div id="app">
<v-app>
<v-container>
<v-row>
<v-col>
<v-tabs v-model="activeTab">
<v-tab v-for="cc in cost_centres" :key="cc.id" :to="{ name: cc.name, params: { id: cc.id }}">
{{ cc.name }}
</v-tab>
</v-tabs>
<router-view></router-view>
</v-col>
</v-row>
</v-container>
</v-app>
</div>
This way the content is not synchronized by the v-model, but by the :to & the routes.
EDIT: NEW SOLUTION
If you just use the query part of the route, then you can control the tabs:
const routes = [{
path: "/",
redirect: {
path: "/0"
}
},
{
path: "/:idx",
name: "foo"
}
]
const router = new VueRouter({
routes
})
new Vue({
el: '#app',
router,
vuetify: new Vuetify(),
data() {
return {
activeTabRoute: 0,
activeTab: 0,
cost_centres: [{
id: 0,
name: "foo"
},
{
id: 1,
name: "bar"
},
]
}
},
watch: {
$route: function(val) {
this.activeTab = val.params.idx
}
}
})
<link href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/#mdi/font#5.x/css/materialdesignicons.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/vuetify#2.x/dist/vuetify.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/vue#2.x/dist/vue.js"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vuetify#2.x/dist/vuetify.js"></script>
<div id="app">
<v-app>
<v-container>
<v-row>
<v-col>
<v-tabs v-model="activeTabRoute">
<v-tab v-for="(cc, i) in cost_centres" :key="cc.id" :to="{ name: 'foo', params: { idx: i } }">
{{ cc.name }} {{ cc.id }} {{ activeTab }}
</v-tab>
</v-tabs>
<v-tabs-items v-model="activeTab">
<v-tab-item v-for="cc in cost_centres" :key="cc.id">
<v-card flat>
Name: {{ cc.name }}<br /> ID: {{ cc.id }}<br /> IDx: {{ $route.params.idx }}
</v-card>
</v-tab-item>
</v-tabs-items>
</v-col>
</v-row>
<v-row>
<v-col>$route: {{ $route }}</v-col>
</v-row>
</v-container>
</v-app>
</div>
I created the snippet above to use the index of the for...in, because the reflects the order of the tabs, and doesn't mess up the IDs of the items.
This way you can use this component with vue-router.

How to make Vutify VMenu right aligned?

I need to make right aligned v-menu with "attach" option.
Template:
<div id="app">
<v-app id="inspire">
<h1>VMenu bug with "right" option</h1>
<div class="place"></div>
<div class="text-center">
<v-btn
color="primary"
dark
#click="show = !show"
>
Dropdown
</v-btn>
</div>
<v-menu attach=".place" v-model="show" :right="true">
<v-list>
<v-list-item
v-for="(item, index) in items"
:key="index"
#click=""
>
<v-list-item-title>{{ item.title }}</v-list-item-title>
</v-list-item>
</v-list>
</v-menu>
</v-app>
</div>
JS:
new Vue({
el: '#app',
vuetify: new Vuetify(),
data: () => ({
show: false,
items: [
{ title: 'Click Me' },
{ title: 'Click Me' },
{ title: 'Click Me' },
{ title: 'Click Me 2' },
],
}),
})
I expect right aligned menu in ".place" element. But the menu is left aligned. Also top border of menu is under the ".place" element. It is strange. How can I fix it?
Demo
It is possible to align the content of v-menu to right border of the page
Here is the working codepen: https://codepen.io/chansv/pen/OJyjWmX
<div id="app">
<v-app id="inspire">
<h1>VMenu bug with "right" option</h1>
<div>
<div id="attachMenu" style="float: right;position: relative;width: 134px;left: 27px;"></div>
</div>
<div class="text-center">
<v-btn
color="primary"
dark
#click="show = !show"
>
Dropdown
</v-btn>
</div>
<v-menu attach="#attachMenu" v-model="show" :right="true">
<v-list>
<v-list-item
v-for="(item, index) in items"
:key="index"
#click=""
>
<v-list-item-title>{{ item.title }}</v-list-item-title>
</v-list-item>
</v-list>
</v-menu>
</v-app>
</div>
new Vue({
el: '#app',
vuetify: new Vuetify(),
data: () => ({
show: false,
items: [
{ title: 'Click Me' },
{ title: 'Click Me' },
{ title: 'Click Me' },
{ title: 'Click Me 2' },
],
}),
})
You almost got the right solution, the position with the right and left prop is inverted
So this is what you need to do:
<v-menu attach=".place" v-model="show" left>
I am using Vuetify 3 and I had the same problem. I solved it by using
<v-menu location="bottom end">
See :
https://next.vuetifyjs.com/en/components/menus/#location
https://next.vuetifyjs.com/en/components/overlays/#location-strategies

Problem with expandable child component in parent v-data-table using Vuetify 2

I upgraded to Vuetify 2 from 1.5. Everything went pretty smoothly except for one thing. I have a parent component with a v-data-table and I want to pass data and expand each row with a child component.
ScanGrid(parent component):
<template>
<v-container>
<v-card>
<v-card-text>
<v-layout row align-center>
<v-data-table
:headers="headers"
:items="items"
:hide-default-footer="true"
item-key="id"
>
<template slot="items" slot-scope="props">
<tr #click="props.expanded = !props.expanded">
<td>{{ props.item.name }}</td>
<td class="text-xs-left large-column">
{{ props.item.scanned }}
</td>
<td class="text-xs-left large-column">
{{ props.item.incoming }}
</td>
<td class="text-xs-left large-column">
{{ props.item.outgoing }}
</td>
<td class="text-xs-left large-column">
{{ props.item.unknown }}
</td>
</tr>
</template>
<template slot="expand" slot-scope="props">
<ScanGridChild :value="props.item"></ScanGridChild>
</template>
</v-data-table>
</v-layout>
</v-card-text>
</v-card>
</v-container>
</template>
ScanGridChild(child component):
<template>
<v-card>
<v-card-text>{{ value }}</v-card-text>
</v-card>
</template>
<script>
export default {
name: "ScanGridChildComponent",
props: {
value: {
Type: Object,
Required: true
}
},
computed: {},
watch: {
props: function(newVal, oldVal) {
console.log("Prop changed: ", newVal, " | was: ", oldVal);
this.render();
}
}
};
</script>
<style></style>
It worked fine in Vuetify 1.5.19. I'm on Vuetify 2.1.6 and using single file components. Thanks.
Vuetify 2.x has major changes to many components, slot-scopes are replaced with v-slot, and many new properties and slots added to vuetify data table
Here is the working codepen reproduced the same feature with above code
https://codepen.io/chansv/pen/BaaWbKR?editors=1010
You need to make sure that you have vue js 2.x and vuetify 2.x
Parent component code:
<template>
<v-container>
<v-card>
<v-card-text>
<v-layout row align-center>
<v-data-table
:headers="headers"
:items="items"
item-key="name"
single-expand
:expanded="expanded"
hide-default-footer
#click:row="clickedRow"
>
<template v-slot:expanded-item="{ item }">
<td :colspan="headers.length">
<ScanGridChild :value="item"></ScanGridChild>
</td>
</template>
</v-data-table>
</v-layout>
</v-card-text>
</v-card>
</v-container>
</template>
<script>
export default {
...
data () {
return {
expanded: [],
headers: [
{
text: "Localisation",
sortable: true,
value: "name"
},
{
text: "Paquets scannés",
sortable: true,
value: "scanned"
},
{
text: "Paquets entrants",
sortable: true,
value: "incoming"
},
{
text: "Paquets sortants",
sortable: true,
value: "outgoing"
},
{
text: "Paquets inconnus",
sortable: true,
value: "unknown"
}
],
items: [
{
id: 1,
name: "Location 1",
scanned: 159,
incoming: 6,
outgoing: 24,
unknown: 4,
test: "Test 1"
},
{
id: 2,
name: "Location 2",
scanned: 45,
incoming: 6,
outgoing: 24,
unknown: 4,
test: "Test 2"
}
],
}
},
methods: {
clickedRow(value) {
if (this.expanded.length && this.expanded[0].id == value.id) {
this.expanded = [];
} else {
this.expanded = [];
this.expanded.push(value);
}
}
}
...
}
</script>
In child component
Replace
props: {
value: {
Type: Object,
Required: true
}
},
with( Type and Required change to lower case type and required)
props: {
value: {
type: Object,
required: true
}
},

V-data-table controlling expanded items via v-slot:body

The documentation on vuetify 2.0 v-data-tables doesn't specify how to control the expanded items via the v-slot:body. I have a table i need to specify with the body v-slot and would like to use the expand feature.
Expected behavior is by clicking the button in one column in the table, the row expands below.
I'm using the v-slot:body as i will need to fully customize the column content. I'm migrating the code from vuetify 1.5 where props.expanded enabled this functionality.
Codepen: https://codepen.io/thokkane/pen/PooemJP
<template>
<v-data-table
:headers="headers"
:items="deserts"
:expanded.sync="expanded"
:single-expand="singleExpand"
item-key="name"
hide-default-footer
>
<template v-slot:body="{ items }">
<tbody>
<tr v-for="item in items" :key="item.name">
<td>
<v-btn #click="expanded.includes(item.name) ? expanded = [] : expanded.push(item.name)">Expand</v-btn>
</td>
</tr>
</tbody>
</template>
<template v-slot:expanded-item="{ headers, item }">
<span>item.name</span>
</template>
</v-data-table>
</template>
<script>
export default {
data () {
return {
expanded: [],
singleExpand: false,
headers: [
{ text: 'Dessert (100g serving)', align: 'left', sortable: false, value: 'name' },
{ text: 'Calories', value: 'calories' },
{ text: 'Fat (g)', value: 'fat' },
{ text: 'Carbs (g)', value: 'carbs' },
{ text: 'Protein (g)', value: 'protein' },
{ text: 'Iron (%)', value: 'iron' },
{ text: '', value: 'data-table-expand' },
],
desserts: [
{
name: 'Frozen Yogurt',
calories: 159,
fat: 6.0,
carbs: 24,
protein: 4.0,
iron: '1%',
},
{
name: 'Ice cream sandwich',
calories: 237,
fat: 9.0,
carbs: 37,
protein: 4.3,
iron: '1%',
},
{
name: 'Eclair',
calories: 262,
fat: 16.0,
carbs: 23,
protein: 6.0,
iron: '7%',
},
],
}
},
}
</script>
When you use v-slot:body together with v-slot:expanded-item, you are overriding your changes inside v-slot:expanded-item. This is because expanded-item slot is inside the body slot. If you are going to use body for customization, you unfortunately have to customize everything inside.
Imagine a structure like this:
<div slot="body">
<div slot="item">...</div>
<div slot="expanded-item">...</div>
etc...
<div>
So in this case <template v-slot:body> will replace <div slot="body"> and anything inside. Therefore usage of <template v-slot:expanded-item> will not work with <template v-slot:body>. Besides v-slot:body props does not include item-specific properties and events like select(), isSelected, expand(), isExpanded etc.
I would recommend you to use v-slot:item instead. You can accomplish same thing without needing to customize everything with less code.
Something like this should work:
<template v-slot:item="{ item, expand, isExpanded }">
<tr>
<td>
<v-btn #click="expand(!isExpanded)">Expand</v-btn>
</td>
<td class="d-block d-sm-table-cell" v-for="field in Object.keys(item)">
{{item[field]}}
</td>
</tr>
</template>
<template v-slot:expanded-item="{ headers, item }">
<td :colspan="headers.length">Expanded Content</td>
</template>
If you want to have access to expanded items in JavaScript, don't forget to add :expanded.sync="expanded" to <v-data-table> . To open unique item on click you need to set item-key="" property on <v-data-table>.
With newer versions you can achieve this using body slot as well, here's my code
https://codepen.io/saurabhtalreja/pen/JjyWxEr
new Vue({
el: '#app',
vuetify: new Vuetify(),
data: () => ({
headers: [{
name: 'id',
text: 'id',
value: 'id'
},
{
name: 'text',
text: 'text',
value: 'text'
}
],
items: [{
id: 1,
text: "Item 1"
},
{
id: 2,
text: "Item 2"
},
{
id: 3,
text: "Item 3"
},
{
id: 4,
text: "Item 4"
},
{
id: 5,
text: "Item 5"
}
]
})
})
<link href="https://cdn.jsdelivr.net/npm/#mdi/font#3.x/css/materialdesignicons.min.css" rel="stylesheet"/>
<link href="https://cdn.jsdelivr.net/npm/vuetify#2.x/dist/vuetify.min.css" rel="stylesheet"/>
<script src="https://cdn.jsdelivr.net/npm/vue#2.x/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vuetify#2.x/dist/vuetify.js"></script>
<div id="app">
<v-app>
<v-content>
<v-container grid-list-xl>
<v-data-table class="elevation-1" :headers="headers" :items="items" show-expand hide-default-footer>
<template v-slot:body="{ items, headers, expand, isExpanded }">
<tbody>
<tr v-for="item in items" :key="item.name">
<td>
<v-btn #click="expand(item,!isExpanded(item))">Expand</v-btn>
<div v-if="isExpanded(item)" :style="{width: '250px'}">
Expanded content for {{ item.text }}
</div>
</td>
<td>{{item.id}}</td>
<td>{{item.text}}</td>
</tr>
</tbody>
</template>
</v-data-table>
</v-container>
</v-content>
</v-app>
</div>
<div id="app">
<v-app>
<v-content>
<v-container grid-list-xl>
<v-data-table class="elevation-1" :headers="headers" :items="items" show-expand hide-default-footer>
<template v-slot:expanded-item="{item, headers}">
<td :colspan="headers.length">
expanded content for {{ item.text }}
</td>
</template>
</v-data-table>
</v-container>
</v-content>
</v-app>
</div>

Auto select rows in v-data-table

I would like to programmatically checkmark a row in a v-data-table when an external listener notifies me of a particular value.
As an example, here is a Vuetify selectable table: https://vuetifyjs.com/en/components/data-tables#selectable-rows
In the example, If I were passed the value of 'Gingerbread' after the table and its data have already been instantiated, how would I programmatically select that corresponding row?
You can do this by pushing your values in your model like this:
HTML:
<div id="app">
<v-app id="inspire">
<v-btn #click="select">button</v-btn>
<v-data-table
v-model="selected"
:headers="headers"
:items="desserts"
:single-select="singleSelect"
item-key="name"
show-select
class="elevation-1"
>
<template v-slot:top>
<v-switch v-model="singleSelect" label="Single select" class="pa-3"></v-switch>
</template>
</v-data-table>
</v-app>
</div>
VueJS:
new Vue({
el: '#app',
vuetify: new Vuetify(),
methods: {
select: function() {
let result = this.desserts.find((dessert) => {
return dessert.name == 'Gingerbread'
})
this.selected.push(result)
}
},
data () {
return {
singleSelect: false,
selected: [],
headers: [
{ text: 'Dessert (100g serving)', value: 'name' },
{ text: 'Calories', value: 'calories' },
],
desserts: [
{ name: 'Gingerbread', calories: 356 },
{ name: 'Jelly bean', calories: 375 }
],
}
},
})
The v-data-table component has a property called filteredItems which you can use to add items to the selected array
<v-data-table v-model="selected":items="itemsArray" ref="table"></v-data-table>
function selectAll() {
this.$refs.table.filteredItems.map(item => {
if(...some condition...) {
this.selected.push(item)
}
})
}

Resources