Related
I am a beginner in Vue.js and Vuetify:
I have a Vuetify data table and I am trying to append different icons (customized icons from flaticon) to just the column for example "fat (g)". Means every new line has it's own icon.
Moreover I like to have a title instead of the checkmark in the v-table-header.
How can I do this in the following code?
Example code:
https://github.com/vuetifyjs/vuetify/blob/master/packages/docs/src/examples/v-data-table/prop-row-selection.vue:
<template>
<div id="app">
<v-app id="inspire">
<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>
</template>
<script>
new Vue({
el: '#app',
vuetify: new Vuetify(),
data () {
return {
singleSelect: false,
selected: [],
headers: [
{
text: 'Dessert (100g serving)',
align: 'start',
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' },
],
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%',
},
{
name: 'Cupcake',
calories: 305,
fat: 3.7,
carbs: 67,
protein: 4.3,
iron: '8%',
},
{
name: 'Gingerbread',
calories: 356,
fat: 16.0,
carbs: 49,
protein: 3.9,
iron: '16%',
},
{
name: 'Jelly bean',
calories: 375,
fat: 0.0,
carbs: 94,
protein: 0.0,
iron: '0%',
},
{
name: 'Lollipop',
calories: 392,
fat: 0.2,
carbs: 98,
protein: 0,
iron: '2%',
},
{
name: 'Honeycomb',
calories: 408,
fat: 3.2,
carbs: 87,
protein: 6.5,
iron: '45%',
},
{
name: 'Donut',
calories: 452,
fat: 25.0,
carbs: 51,
protein: 4.9,
iron: '22%',
},
{
name: 'KitKat',
calories: 518,
fat: 26.0,
carbs: 65,
protein: 7,
iron: '6%',
},
],
}
},
})
</script>
Check this codesanbox I made: https://codesandbox.io/s/stack-70958304-brw8z?file=/src/components/AppendIcons.vue
Well it depends, how do you want to decide what icon use for each row. The normal aproach would be to have the desired icon name within your data array.
desserts: [
{
name: 'Frozen Yogurt',
calories: 159,
fat: 6.0,
carbs: 24,
protein: 4.0,
iron: '1%',
icon: 'home'
},
...
]
Now to append the icon to the fat(g) column can be done using the item slot of your v-data-table. In my example I used the #item.fat="{item}" which is a shortand for v-slot:item.fat="{item}" to modify the content of the fat column. For more info about slots check the documentation page.
If you're using es-lint you might encounter a warning mention something about the valid-v-slot rule, this is a known false positive that you can read more about in this GitHub issue thread, I personally just turn that es-lint rule off.
<v-data-table
v-model="selected"
:headers="headers"
:items="desserts"
item-key="name"
show-select
class="elevation-1"
>
<template v-slot:top>
<div class="my-2 mx-4 text-h5 font-weight-medium">
My custom title
</div>
</template>
<template #item.fat="{item}">
<div class="d-flex">
<div>
{{ item.fat }}
</div>
<div class="ml-2">
<i :class="`fi fi-rr-${item.icon}`"></i>
</div>
</div>
</template>
</v-data-table>
UPDATE 1: Import flaticon through CDN.
<style>
/* <style> blocks that doesn't have the 'scoped' attribute
make the CSS available in all your components, I usually put
my global CSS in the App.vue file or in a external css file.
In this case I'm just importing the regular rounded style
of flaticons, if you want to use bold, solid, straight icons
you'll need to import those as well */
#import url("https://cdn-uicons.flaticon.com/uicons-regular-rounded/css/uicons-regular-rounded.css");
i {
font-size: 1.2em;
}
</style>
Vuetify components use material icons by default inside of them, in case you want to go one step further and modify those icons to use flaticons. Instead of creating a wrapper component or manually defining the specific icon each time a component appears, you can configure it at a global level, see Icons Font docs for more info.
Let's say you want to modify the sort arrow icon used in vuetify components to use the arrow-small-up flaticon. Then you would do something like this:
// src/plugins/vuetify.js
import Vue from "vue";
import Vuetify from "vuetify";
import "vuetify/dist/vuetify.min.css";
Vue.use(Vuetify)
export default new Vuetify({
icons: {
values: {
sort: "fi fi-rr-arrow-small-up"
},
},
})
UPDATE 2: How to use custom SVG files in vuetify.
Local SVG files can be displayed in your applicaction by simply using the normal <img> tag. Vuetify's <v-img> component can only display external SVG files that comes from an URL. Like this one right here: https://dev.w3.org/SVG/tools/svgweb/samples/svg-files/410.svg
That's because the internal fileloader that use <v-img> doesn't support local SVG files. So the simplest solution is just use the normal <img> tag.
<div>
{{ item.fat }}
</div>
<div class="ml-2">
<img v-if="item.name == 'Donut'" src="../assets/donut.svg" width="20" alt=""/>
<i v-else :class="`fi fi-rr-${item.icon}`"></i>
</div>
I'm new in vue.js, please help. I need filters in my table. Each column must have multiple select filter by values. When we click on column header - dropdown multi select opens, and we can select filtered values. For example like this Vuetify Data Table Inline Filter but this example doesn't work with vuetify 2.
My html:
<template>
<v-card class="elevation-3">
<v-card-title>
{{ other_title }}
<v-btn style="background-color: white; box-shadow: none;" #click="csvExport(other_title, otherIncidentsData)">
CSV<i class="fas fa-file-csv"></i>
</v-btn>
<v-btn text #click="exportToPdf()">
Pdf<v-icon>mdi-file-pdf-box-outline</v-icon>
</v-btn>
<v-btn text #click="showExportModal">
<v-icon>mdi-email-receive-outline</v-icon>
</v-btn>
<v-spacer></v-spacer>
<v-text-field
v-model="search"
append-icon="mdi-magnify"
label="Search"
single-line
hide-details
></v-text-field>
</v-card-title>
<v-data-table
:search="search"
:headers="headers"
:items="otherIncidents"
:items-per-page="10"
>
<template v-slot:item.priority.name="{ item }">
<v-chip :color="getPriorityColor(item.priority.name)" dark>{{ item.priority.name }}</v-chip>
</template>
</v-data-table>
</v-card>
</template>
And JS:
export default {
mixins: [
mixin
],
data() {
return {
search: '',
title: 'MediaMyne reports',
project_title: 'PROJECTS (open at the end of the reporting period)',
new_title: 'NEW REQUESTS (created during the reporting period)',
other_title: 'OTHER REQUESTS (remaining open or changed during the reporting period)',
tabs: [
{ name: 'Projects' },
{ name: 'New Requests' },
{ name: 'Other Requests' },
],
headers: [
{
text: 'Company', align: 'start', sortable: true, value: 'customer.name', width: '14%',
},
{
text: 'Name (Costumer Contact)', align: 'start', sortable: true, value: 'reported_By_Customer_Contact.name', width: '16%',
},
{
text: 'Title', align: 'start', sortable: true, value: 'name', width: '17%',
},
{
text: 'Days open', align: 'center', sortable: true, value: 'daysOpen', width: '9%',
},
{
text: 'Days waiting', align: 'center', sortable: true, value: 'daysWaiting', width: '10%',
},
{
text: 'Workflow step', align: 'start', sortable: true, value: 'workflow_Step.name', width: '12%',
},
{
text: 'Support type', align: 'start', sortable: true, value: 'custom_Fields.customFields.custom_266', width: '12%',
},
{
text: 'Priority', align: 'start', sortable: true, value: 'priority.name', width: '10%',
},
],
otherIncidents: [],
newIncidents: [],
projectIncidents: [],
activeTab: 0,
pdfReportTitle: ''
};
},
components: {
ExportModal
},
computed: {
...mapGetters([
'isLoggedIn'
]),
otherIncidentsData() {
return this.otherIncidents.map(item => ({
Company: item.customer.name,
Costumer_contact_name: item.reported_By_Customer_Contact.name,
Title: item.name,
Days_open: item.daysOpen,
Days_waiting: item.daysWaiting,
Workflow_step: item.workflow_Step.name,
Support_type: item.custom_Fields.customFields.custom_266,
Priority: item.priority.name
}));
},
methods: {
...mapActions([
'setLoginState'
]),
getPriorityColor(priority) {
switch (priority.toLowerCase()) {
case 'critical':
return '#fc0000';
case 'high':
return '#c20202';
case 'normal':
return '#dd7417';
case 'low':
return '#318d14';
default:
return 'rgb(0,0,0,0)';
}
}
};
I'm making this on codepen these days, since I haven't found any nice example:
https://codepen.io/manuel-84/pen/NWxLLmN (work in progress)
new Vue({
el: '#app',
vuetify: new Vuetify(),
data: () => ({
dialog: false,
filters: { 'name': [], 'calories': [], 'status': [] },
activeFilters: {},
desserts: [],
editedIndex: -1,
editedItem: {
name: '',
calories: 0,
fat: 0,
carbs: 0,
protein: 0,
},
defaultItem: {
name: '',
calories: 0,
fat: 0,
carbs: 0,
protein: 0,
},
}),
computed: {
headers () {
return [
{
text: 'Dessert (100g serving)',
align: 'start',
sortable: true,
value: 'name',
filter: value => {
return this.activeFilters.name.includes(value);
}
},
{ text: 'Calories', value: 'calories',
filter: value => {
return this.activeFilters.calories.includes(value);
}
},
{ text: 'Status', value: 'status',
filter: value => {
return this.activeFilters.status.includes(value);
}
},
{ text: 'Fat (g)', value: 'fat' },
{ text: 'Carbs (g)', value: 'carbs' },
{ text: 'Protein (g)', value: 'protein' },
{ text: 'Actions', value: 'actions', sortable: false },
]
},
formTitle () {
return this.editedIndex === -1 ? 'New Item' : 'Edit Item'
},
},
watch: {
dialog (val) {
val || this.close()
},
/*filters: {
deep: true,
handler(val) {
console.log(val)
}
}*/
},
created () {
this.initialize()
},
methods: {
initialize () {
this.desserts = [
{
name: 'Frozen Yogurt',
calories: 159,
fat: 6.0,
carbs: 24,
protein: 4.0,
status: 'DIET'
},
{
name: 'Ice cream sandwich',
calories: 237,
fat: 9.0,
carbs: 37,
protein: 4.3,
status: 'NO DIET'
},
{
name: 'Eclair',
calories: 262,
fat: 16.0,
carbs: 23,
protein: 6.0,
status: 'DIET'
},
{
name: 'Cupcake',
calories: 305,
fat: 3.7,
carbs: 67,
protein: 4.3,
status: 'NO DIET'
},
{
name: 'Gingerbread',
calories: 356,
fat: 16.0,
carbs: 49,
protein: 3.9,
status: 'DIET'
},
{
name: 'Jelly bean',
calories: 375,
fat: 0.0,
carbs: 94,
protein: 0.0,
status: 'NO DIET'
},
{
name: 'Lollipop',
calories: 392,
fat: 0.2,
carbs: 98,
protein: 0,
status: 'NO DIET'
},
{
name: 'Honeycomb',
calories: 408,
fat: 3.2,
carbs: 87,
protein: 6.5,
status: 'NO DIET'
},
{
name: 'Donut',
calories: 452,
fat: 25.0,
carbs: 51,
protein: 4.9,
status: 'DIET'
},
{
name: 'KitKat',
calories: 518,
fat: 26.0,
carbs: 65,
protein: 7,
status: 'NO DIET'
},
];
for (col in this.filters) {
this.filters[col] = this.desserts.map((d) => { return d[col] }).filter(
(value, index, self) => { return self.indexOf(value) === index }
);
}
this.activeFilters = Object.assign({}, this.filters)
},
toggleAll (col) {
this.activeFilters[col] = this.desserts.map((d) => { return d[col] }).filter(
(value, index, self) => { return self.indexOf(value) === index }
)
},
clearAll (col) {
this.activeFilters[col] = []
},
editItem (item) {
this.editedIndex = this.desserts.indexOf(item)
this.editedItem = Object.assign({}, item)
this.dialog = true
},
deleteItem (item) {
const index = this.desserts.indexOf(item)
confirm('Are you sure you want to delete this item?') && this.desserts.splice(index, 1)
},
close () {
this.dialog = false
this.$nextTick(() => {
this.editedItem = Object.assign({}, this.defaultItem)
this.editedIndex = -1
})
},
save () {
if (this.editedIndex > -1) {
Object.assign(this.desserts[this.editedIndex], this.editedItem)
} else {
this.desserts.push(this.editedItem)
}
this.close()
},
},
})
.v-list--dense .v-list-item, .v-list-item--dense {
min-height: 20px !important;
height: 2rem;
}
.v-application--is-ltr .v-list-item__action:first-child, .v-application--is-ltr .v-list-item__icon:first-child {
margin-right: .5rem !important;
}
<script src="https://cdn.jsdelivr.net/npm/babel-polyfill/dist/polyfill.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue#2.x/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vuetify#2.3.4/dist/vuetify.min.js"></script>
<link href="https://cdn.jsdelivr.net/npm/vuetify#2.3.4/dist/vuetify.min.css" rel="stylesheet"/>
<link href="https://cdn.jsdelivr.net/npm/#mdi/font#5.x/css/materialdesignicons.min.css" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css?family=Material+Icons" rel="stylesheet"/>
<span id="app">
<v-app id="inspire">
<v-data-table :headers="headers" :items="desserts" sort-by="calories" item-key="name" show-select>
<template v-for="(col, i) in filters" v-slot:[`header.${i}`]="{ header }">
<div style="display: inline-block; padding: 16px 0;">{{ header.text }}</div>
<div style="float: right; margin-top: 8px">
<v-menu :close-on-content-click="false" :nudge-width="200" offset-y transition="slide-y-transition" left fixed style="position: absolute; right: 0">
<template v-slot:activator="{ on, attrs }">
<v-btn color="indigo" icon v-bind="attrs" v-on="on">
<v-icon small
:color="activeFilters[header.value].length < filters[header.value].length ? 'red' : 'default'">
mdi-filter-variant
</v-icon>
</v-btn>
</template>
<v-list flat dense class="pa-0">
<v-list-item-group multiple v-model="activeFilters[header.value]" class="py-2">
<template v-for="(item, i) in filters[header.value]">
<v-list-item :key="`item-${i}`" :value="item" :ripple="false">
<template v-slot:default="{ active, toggle }">
<v-list-item-action>
<v-checkbox :input-value="active" :true-value="item"
#click="toggle" color="primary" dense></v-checkbox>
</v-list-item-action>
<v-list-item-content>
<v-list-item-title v-text="item"></v-list-item-title>
</v-list-item-content>
</template>
</v-list-item>
</template>
</v-list-item-group>
<v-divider></v-divider>
<v-btn text block #click="toggleAll(header.value)">Toggle all</v-btn>
<v-btn text block #click="clearAll(header.value)">Clear all</v-btn>
</v-list>
</v-menu>
</div>
</template>
<template v-slot:header="{ props: { headers } }">
<thead>
<tr>
<th :colspan="headers.length">
This is a header
</th>
</tr>
</thead>
</template>
<template v-slot:top>
<v-toolbar flat color="white">
<v-toolbar-title>My CRUD</v-toolbar-title>
<v-divider class="mx-4" inset vertical></v-divider>
<v-spacer></v-spacer>
<v-dialog v-model="dialog" max-width="500px">
<template v-slot:activator="{ on, attrs }">
<v-btn color="primary" dark class="mb-2" v-bind="attrs" v-on="on">New Item</v-btn>
</template>
<v-card>
<v-card-title>
<span class="headline">{{ formTitle }}</span>
</v-card-title>
<v-card-text>
<v-container>
<v-row>
<v-col cols="12" sm="6" md="4">
<v-text-field v-model="editedItem.name" label="Dessert name"></v-text-field>
</v-col>
<v-col cols="12" sm="6" md="4">
<v-text-field v-model="editedItem.calories" label="Calories"></v-text-field>
</v-col>
<v-col cols="12" sm="6" md="4">
<v-text-field v-model="editedItem.fat" label="Fat (g)"></v-text-field>
</v-col>
<v-col cols="12" sm="6" md="4">
<v-text-field v-model="editedItem.carbs" label="Carbs (g)"></v-text-field>
</v-col>
<v-col cols="12" sm="6" md="4">
<v-text-field v-model="editedItem.protein" label="Protein (g)"></v-text-field>
</v-col>
</v-row>
</v-container>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="blue darken-1" text #click="close">Cancel</v-btn>
<v-btn color="blue darken-1" text #click="save">Save</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</v-toolbar>
</template>
<template v-slot:item.actions="{ item }">
<v-icon small class="mr-2" #click="editItem(item)">
mdi-pencil
</v-icon>
<v-icon small #click="deleteItem(item)">
mdi-delete
</v-icon>
</template>
<template v-slot:no-data>
<v-btn color="primary" #click="initialize">Reset</v-btn>
</template>
</v-data-table>
</v-app>
</div>
I hope the code is clear, anyway feel free to ask
I am currently trying to pull in the datatable row info into a dialog for the rows with qty input value greater than 0. I know with checkboxes if selected/true, it pulls all the same row information into a selected array to easily use. I'm trying to get this same functionality for input greater than 0.
Got checked boxes row info to get into dialog by using the selected array. Cant seem to figure out how to do the same (get that rows data) with inputs greater than 0
https://codepen.io/anon/pen/xeVZKv?editors=1010
HTML
<div id="app">
<v-app id="inspire">
<v-data-table
:headers="headers"
:items="desserts"
class="elevation-1"
v-model="selected"
item-key="name"
>
<template v-slot:items="props">
<td>{{ props.item.name }}</td>
<td class="text-xs-right">{{ props.item.calories }}</td>
<td class="text-xs-right">{{ props.item.fat }}</td>
<td class="text-xs-right">{{ props.item.carbs }}</td>
<td class="text-xs-right">{{ props.item.protein }}</td>
<td v-if="props.item.qty < 2" style="width: 10%">
<v-checkbox v-model="props.selected" primary hide-details></v-checkbox>
</td>
<td v-else style="display:inline-flex;">
<v-text-field
value="0"
onclick="this.select()"
type= "number"
:max="props.item.Qty"
:totalCount="props.item.Qty"
min="0"
:id="'inputCount' + props.index"
style="width:40px; margin-left: 5px;"
#change="counter()"
></v-text-field>
<div style="padding-top: 15px; margin-left: 10px;">of {{ props.item.qty }}</div>
</td>
</template>
<template v-slot:pageText="props">
Lignes {{ props.pageStart }} - {{ props.pageStop }} de {{ props.itemsLength }}
</template>
</v-data-table>
<v-layout row wrap class="">
<v-flex xs4 class="text-xs-left">
</v-flex>
<v-flex xs4 class="text-xs-center">
</v-flex>
<v-flex xs4 class="text-xs-right">
<v-btn v-if="buyCounter < 1" color="primary" class="receiveButton" :disabled="buttonActivate" #click="buyModel()">Receive</v-btn>
<v-btn v-else-if="buyCounter > 0 && buyCounter < 2" color="primary" class="receiveButton" :disabled="buttonActivate" #click="buyModel()">Receive {{ buyCounter }} Part</v-btn>
<v-btn v-else-if="buyCounter > 1" color="primary" class="receiveButton" :disabled="buttonActivate" #click="buyModel()">Receive {{ buyCounter }} Parts</v-btn>
<v-btn v-else color="primary" class="receiveButton" :disabled="buttonActivate">Receive</v-btn>
</v-flex>
</v-layout>
</v-app>
</div>
JS
new Vue({
el: '#app',
data () {
return {
inputCount: 0,
selected: [],
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: 'Qty Available', value: 'qty' }
],
desserts: [
{
name: 'Frozen Yogurt',
calories: 159,
fat: 6.0,
carbs: 24,
protein: 4.0,
qty: 1
},
{
name: 'Ice cream sandwich',
calories: 237,
fat: 9.0,
carbs: 37,
protein: 4.3,
qty: 2
},
{
name: 'Eclair',
calories: 262,
fat: 16.0,
carbs: 23,
protein: 6.0,
qty: 1
},
{
name: 'Cupcake',
calories: 305,
fat: 3.7,
carbs: 67,
protein: 4.3,
qty: 1
},
{
name: 'Gingerbread',
calories: 356,
fat: 16.0,
carbs: 49,
protein: 3.9,
qty: 3
},
{
name: 'Jelly bean',
calories: 375,
fat: 0.0,
carbs: 94,
protein: 0.0,
qty: 1
},
{
name: 'Lollipop',
calories: 392,
fat: 0.2,
carbs: 98,
protein: 0,
qty: 1
},
{
name: 'Honeycomb',
calories: 408,
fat: 3.2,
carbs: 87,
protein: 6.5,
qty: 1
},
{
name: 'Donut',
calories: 452,
fat: 25.0,
carbs: 51,
protein: 4.9,
qty: 1
},
{
name: 'KitKat',
calories: 518,
fat: 26.0,
carbs: 65,
protein: 7,
qty: 1
}
]
};
},
computed: {
buttonActivate: function () {
let count = 0;
$('input[id^="inputCount"]').each(function (i, e) {
let element = $(e).val();
if (element == "") {
element = 0;
}
let eachCountInt = parseInt(element);
count = eachCountInt + count;
})
this.inputCount = count;
if (this.selected.length > 0 || this.inputCount > 0) {
return false;
} else {
return true;
}
},
buyCounter: function () {
let count = this.selected.length + this.inputCount;
return count;
}
},
methods: {
counter() {
let count = 0;
$('input[id^="inputCount"]').each(function (i, e) {
let element = $(e).val();
if (element == "") {
element = 0;
}
let eachCountInt = parseInt(element);
count = eachCountInt + count;;
})
this.inputCount = count;
},
buyModel() {
}
}
})
Overall looking to pull in all selected/greater than 0 info into the dialog to confirm before processing. Any help would be greatly appreciated.
I have a data table containing clickable rows. All is working well, I encountered one problem. If I want to highlight text on the row, the click event is triggered. The only thing I found that could help is the .exact modifier.
Hoping it will ignore the click handler if text is highlighted. But the click event is still triggered.
My Question: Is there a way I can highlight text on an item without triggering the click event.
Expected Result: Using #click.exact wont fire click event when highlighting text
Actual Result: Click event is fired when highlighting text, event using #click.exact
Side Note: It manages to hightlight the text, but as soon as you let the mouse button go, it triggers the click event.
<v-data-table
v-show="model.id && !editMode"
:headers="bagHeaders"
:items="bags"
class="elevation-1"
item-key="id"
:loading="bagsStatus.loading"
:pagination.sync="pagination"
>
<template slot="items" slot-scope="props">
<tr #click.exact="onBagClick(props.item.id)">
<td class="text-xs-left" v-for="header in bagHeaders" :key="header.id">{{ formatColumn(header, props.item) }}</td>
</tr>
</template>
</v-data-table>
Edit:
Other Attempts: #click.prevent also not working
Best work around so far: https://codepen.io/anon/pen/YBNLLy
OK, try #click.stop on the TDs which stops the event from propagating to the parent TR.
Since you want to preserve the normal row clicking behavior on certain condition, you could add a method for inspecting if any text-selection is being made while clicking and proceed with stopping the event propagation, otherwise invoke the onBagClick() method of the parent TR:
new Vue({
el: '#app',
methods: {
onBagClick(id) {
alert('Bag Click');
},
checkMouseAction(e) {
const isTextHighlighting = window.getSelection().toString().trim() !== '';
if (!isTextHighlighting) {
e.target.parentElement.click();
// Or call the this.onBagClick() instead
}
}
}
})
table {
border-collapse: collapse;
}
table td {
border: 1px solid;
padding: 10px;
}
table td:first-child {
background-color: lavender;
}
table td:last-child {
background-color: pink;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<table>
<tr #click="onBagClick">
<td #click.stop="checkMouseAction">Selectable text. Left-click and drag these lines with your mouse.</td>
<td>Hardly selectable text. An alert dialog will get in the way by popping up.</td>
</tr>
</table>
</div>
Edits
The above will work too, but actually I figured out another obvious workaround: Do the text-selection checking on the table row level:
Working demo
new Vue({
el: '#app',
data() {
return {
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'
}
],
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%'
},
{
name: 'Cupcake',
calories: 305,
fat: 3.7,
carbs: 67,
protein: 4.3,
iron: '8%'
},
{
name: 'Gingerbread',
calories: 356,
fat: 16.0,
carbs: 49,
protein: 3.9,
iron: '16%'
},
{
name: 'Jelly bean',
calories: 375,
fat: 0.0,
carbs: 94,
protein: 0.0,
iron: '0%'
},
{
name: 'Lollipop',
calories: 392,
fat: 0.2,
carbs: 98,
protein: 0,
iron: '2%'
},
{
name: 'Honeycomb',
calories: 408,
fat: 3.2,
carbs: 87,
protein: 6.5,
iron: '45%'
},
{
name: 'Donut',
calories: 452,
fat: 25.0,
carbs: 51,
protein: 4.9,
iron: '22%'
},
{
name: 'KitKat',
calories: 518,
fat: 26.0,
carbs: 65,
protein: 7,
iron: '6%'
}
]
}
},
methods: {
onBagClick(id) {
const isTextHighlighting = window.getSelection().toString().trim() !== '';
if (!isTextHighlighting) {
alert("Bag Click");
}
}
}
})
#import url('https://fonts.googleapis.com/css?family=Roboto|Material+Icons');
<link href="https://cdn.jsdelivr.net/npm/vuetify#1.4.4/dist/vuetify.min.css" rel="stylesheet" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vuetify#1.4.4/dist/vuetify.min.js"></script>
<div id="app">
<v-app id="inspire">
<v-data-table :headers="headers" :items="desserts" class="elevation-1">
<template slot="items" slot-scope="props">
<tr #click="onBagClick(props.item.id)">
<td class="text-xs-left"
v-for="header in props.item"
:key="header.id">{{header}}</td>
</tr>
</template>
</v-data-table>
</v-app>
</div>
I'm using window.getSelection() for reading selected texts. If you care about supporting IE 8 (and below), have a look at this post for a fallback text-selection-acquiring approach.
In a data table, I want to display only items, where the property 'display' is 'true'. There is the property 'filter' in the component v-data-table. But there is no example showing, how to use it.
I have tried several approaches, but without success. The following code snippet is also available at codepen.
new Vue({
el: '#app',
data () {
return {
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' }
],
desserts: [
{
value: false,
name: 'Frozen Yogurt',
calories: 159,
fat: 6.0,
carbs: 24,
protein: 4.0,
iron: '1%',
display: false
},
{
value: false,
name: 'Ice cream sandwich',
calories: 237,
fat: 9.0,
carbs: 37,
protein: 4.3,
iron: '1%',
display: true
},
{
value: false,
name: 'Eclair',
calories: 262,
fat: 16.0,
carbs: 23,
protein: 6.0,
iron: '7%',
display: false
},
{
value: false,
name: 'Cupcake',
calories: 305,
fat: 3.7,
carbs: 67,
protein: 4.3,
iron: '8%',
display: true
},
{
value: false,
name: 'Gingerbread',
calories: 356,
fat: 16.0,
carbs: 49,
protein: 3.9,
iron: '16%',
display: false
},
{
value: false,
name: 'Jelly bean',
calories: 375,
fat: 0.0,
carbs: 94,
protein: 0.0,
iron: '0%',
display: true
},
{
value: false,
name: 'Lollipop',
calories: 392,
fat: 0.2,
carbs: 98,
protein: 0,
iron: '2%',
display: false
},
{
value: false,
name: 'Honeycomb',
calories: 408,
fat: 3.2,
carbs: 87,
protein: 6.5,
iron: '45%',
display: false
},
{
value: false,
name: 'Donut',
calories: 452,
fat: 25.0,
carbs: 51,
protein: 4.9,
iron: '22%',
display: false
},
{
value: false,
name: 'KitKat',
calories: 518,
fat: 26.0,
carbs: 65,
protein: 7,
iron: '6%',
display: false
}
]
}
},
methods: {
filterItems(val, search) {
return val.display;
}
}
})
<!DOCTYPE html>
<html>
<head>
<link href='https://fonts.googleapis.com/css?family=Roboto:300,400,500,700|Material+Icons' rel="stylesheet">
<link href="https://unpkg.com/vuetify/dist/vuetify.min.css" rel="stylesheet">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, minimal-ui">
</head>
<body>
<div id="app">
<v-app id="inspire">
<v-data-table
:headers="headers"
:items="desserts"
hide-actions
item-key="name"
:filter="filterItems"
>
<template slot="items" slot-scope="props">
<tr #click="props.expanded = !props.expanded" :class="[props.expanded && 'expanded']">
<td>{{ props.item.name }}</td>
<td class="text-xs-right">{{ props.item.calories }}</td>
<td class="text-xs-right">{{ props.item.fat }}</td>
<td class="text-xs-right">{{ props.item.carbs }}</td>
<td class="text-xs-right">{{ props.item.protein }}</td>
<td class="text-xs-right">{{ props.item.iron }}</td>
</tr>
</template>
<template slot="expand" slot-scope="props">
<v-card flat>
<v-card-text>Peek-a-boo!</v-card-text>
</v-card>
</template>
</v-data-table>
</v-app>
</div>
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="https://unpkg.com/vuetify/dist/vuetify.js"></script>
</body>
</html>
The problematic part of the code is:
methods: {
filterItems(val, search) {
return ???;
}
}
I was struggling for a long time with this issue because I also thought :filter did not work. Later, after a lot of pain, I realised that I needed a :search prop to use :filter, and the functionality that I wanted was not the one that is provided by Vuetify.
Like you, I wanted to filter my data-table with a function that returned a boolean, i.e: true, if row should be displayed.
If someone is looking for a quick solution to do the same, a simple "v-if" should do the trick. Based on the example from above:
<template slot="items" slot-scope="props" v-if="props.item.display">
There are other solutions of course, depending on your needs, like using .filter method on the desserts array itself.
In my case, because I wanted to always display 5 rows at a time in my data-table (not using hide-actions), I ended up using the created() lifecycle hook, instead of a v-if:
created: function() {
this.shownDesserts = this.desserts.filter(dessert => {
return dessert.display
})
},
Looking at the source code, I think you really want custom-filter as opposed to filter:
(items: object[], search: string, filter: Filter): object[]
So you would define a function and pass it as an argument to the custom-filter property on the table. When searching, you are provided with all the items in the table represented by an array of objects (object[]), the search string that was typed into the search box, and the filter function to apply against all the objects in the array, so:
:custom-filter="filterItems"
filterItems(items, search, filter) {
items.filter(r => filter(r.calories > search))
}
The above of course is just a very rudimentary example.
Here is the solution I came up with. I was having a similar problem where I wanted to implement a custom filter that still worked with the search. I ended up using a computed property that applies a filter to the data and that computed property is what gets sent to the table.
computed: {
dataListDisplay: function() {
return this.applyFilters(this.dataList);
},
}
dataListDisplay is what you send to the v-data-table items prop. And you will need to implement the applFilters function how you'd like.
My table is read only. I imagine you'd have to be careful if you are changing the data. You may need to provide a setter for the dataListDisplay computed function.
For props, filter, this should help:
methods: {
filterItems(val, search) {
return val != null && typeof val !== 'boolean' && val.toString().toLowerCase().indexOf(search) !== -1;
}
}