Vuetify breadcrumbs text color - vuetify.js

I'm trying to have different text colors for my breadcrumbs based on a property but I can't figure out how to apply those colors anywhere. Can't add a color or class in the items either.
breadcrumbItems() {
return [
{
text: this.$t("receiving.breadcrumbs.step1"),
disabled: this.item.Status !== "STEP1"
},
{
text: this.$t("receiving.breadcrumbs.step2"),
disabled: this.item.Status !== "STEP2"
},
{
text: this.$t("receiving.breadcrumbs.step3"),
disabled: this.item.Status !== "STEP3"
}
];
}
<v-breadcrumbs :items="breadcrumbItems" class="breadStyle">
<template v-slot:divider>
<v-icon size="25">mdi-forward</v-icon>
</template>
</v-breadcrumbs>

Looking at the API for v-breadcrumbs: https://vuetifyjs.com/en/api/v-breadcrumbs-item/ it doesn't provide a property "color" or something similar, but there is a slot, so you can pass any kind of components in it.
You can create a <span> and customize its color and its style depending on the items:
<template>
<v-breadcrumbs :items="items">
<template v-slot:divider>
<v-icon size="25">mdi-forward</v-icon>
</template>
<template v-slot:item="{ item }">
<v-breadcrumbs-item :disabled="item.disabled">
<span :style="`color: ${item.color}`">
{{ item.text.toUpperCase() }}
</span>
</v-breadcrumbs-item>
</template>
</v-breadcrumbs>
</template>
<script>
export default {
data: () => ({
items: [
{
text: "Dashboard",
disabled: false,
color: "green",
},
{
text: "Link 1",
disabled: false,
color: "blue",
},
{
text: "Link 2",
disabled: true,
color: "red",
},
],
}),
};
</script>

I've found that the deep selector (https://vue-loader.vuejs.org/guide/scoped-css.html#deep-selectors) often helps with styling Vuetify components. I added this to my components scoped CSS and the colours work just fine for links:
.v-breadcrumbs >>> a {
color: purple;
}
I found the relevant tag by looking through the Elements-tab under Inspect (in Chrome).
I don't know if this is the best solution for your specific situation, but figured I'd add this for anyone with a simpler use case.

Related

Use Chips in Vue Text Field

How can I use chips in a Vue Text Field? The functionality I'm trying to replicate is kinda similar to the StackOverflow's Tag field. However I do not want auto-complete nor a dropdown list. Just a simple text field that displays chips for the items.
I have this
however im trying to get this
component.vue
<v-combobox
v-model="item.tokens"
chips
clearable
multiple
filled
rounded
>
<template v-slot:selection="{ attrs, item, select, selected }">
<v-chip
small
v-bind="attrs"
:input-value="selected"
close
#click="select"
#click:close="remove(item)"
>
{{ item }}
</v-chip>
</template>
</v-combobox>
<script>
export default {
data() {
return {
items: [
{
name: "Apples",
tokens: [
"_apple",
"_a",
],
backgroundColor: "#000000",
backgroundEnabled: false
},
{
name: "Oranges",
tokens: ["_orange", "_o"],
backgroundColor: "#000000",
backgroundEnabled: false
},
{
name: "Grapes",
tokens: ["_grape", "_g", "_grapes"],
backgroundColor: "#000000",
backgroundEnabled: false
}
]
};
},
};
</script>
Add the props append-icon="" in your v-combobox.
You will have something like this :
<v-combobox
v-model="item.tokens"
chips
clearable
multiple
filled
rounded
append-icon=""
>
<template v-slot:selection="{ attrs, item, select, selected }">
<v-chip
small
v-bind="attrs"
:input-value="selected"
close
#click="select"
#click:close="remove(item)"
>
{{ item }}
</v-chip>
</template>
</v-combobox>

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

Vuetify treeview - select parent without selecting children

When using the treeview component from Vuetify, I am attempting to be able to select a parent without having it also select all of the descendants (children). I've tried various combinations of selectable, activatable, etc... but can't seem to find the appropriate combination.
Anyone have any pointers for achieving this desired result?
I had the same problem, just add the selection-type="independent" attribute to your treeview component
UPD: Example on CodePen https://codepen.io/anon/pen/LwOJRE
Documentation https://vuetifyjs.com/en/components/treeview#selection-type
I've put together a jsFiddle to achieve this result: https://jsfiddle.net/g50odsmy/
Vue.use(Vuetify);
var vm = new Vue({
el: "#app",
computed: {
treeParents: function() {
let tree = [...this.tree];
// Filter tree with only parents of selections
tree = tree.filter(elem => {
for (let i = 0; i < tree.length; i++) {
// Skip current element
if (tree[i].id === elem.id) continue;
// Check only elements with childrens
if (tree[i].children) {
let item = this.findTreeItem([tree[i]], elem.id);
// If current element is a children of another element, exclude from result
if (item) {
return false;
}
}
}
return true;
});
return tree;
}
},
methods: {
findTreeItem(items, id) {
if (!items) {
return;
}
for (const item of items) {
// Test current object
if (item.id === id) {
return item;
}
// Test children recursively
const child = this.findTreeItem(item.children, id);
if (child) {
return child;
}
}
}
},
data: () => ({
open: ["public"],
files: {
html: "mdi-language-html5",
js: "mdi-nodejs",
json: "mdi-json",
md: "mdi-markdown",
pdf: "mdi-file-pdf",
png: "mdi-file-image",
txt: "mdi-file-document-outline",
xls: "mdi-file-excel"
},
tree: [],
items: [
{
id: ".git",
name: ".git"
},
{
id: "node_modules",
name: "node_modules"
},
{
id: "public",
name: "public",
children: [
{
id: "static",
name: "static",
children: [
{
id: "logo.png",
name: "logo.png",
file: "png"
}
]
},
{
id: "favicon.ico",
name: "favicon.ico",
file: "png"
},
{
id: "index.html",
name: "index.html",
file: "html"
}
]
},
{
id: ".gitignore",
name: ".gitignore",
file: "txt"
},
{
id: "babel.config.js",
name: "babel.config.js",
file: "js"
},
{
id: "package.json",
name: "package.json",
file: "json"
},
{
id: "README.md",
name: "README.md",
file: "md"
},
{
id: "vue.config.js",
name: "vue.config.js",
file: "js"
},
{
id: "yarn.lock",
name: "yarn.lock",
file: "txt"
}
]
}),
});
<link href="https://unpkg.com/vuetify#1.5.6/dist/vuetify.min.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vuetify/1.5.6/vuetify.min.js"></script>
<link href='https://fonts.googleapis.com/css?family=Roboto:300,400,500,700|Material+Icons' rel="stylesheet" type="text/css">
<div id="app">
<v-app>
<v-layout row wrap>
<v-flex>
<v-treeview
v-model="tree"
:items="items"
activatable
active-class="grey lighten-4 indigo--text"
selected-color="indigo"
open-on-click
selectable
transition
return-object
hoverable
></v-treeview>
</v-flex>
</v-layout>
<v-layout row wrap>
<v-flex xs6 style="border: 2px solid red"><v-chip>Current mode:</v-chip> {{tree}}</v-flex>
<v-flex xs6 style="border: 2px solid green"><v-chip>Only parents:</v-chip> {{treeParents}}</v-flex>
<v-flex xs6 style="border: 2px solid red"># selected: {{tree.length}}</v-flex>
<v-flex xs6 style="border: 2px solid green"># selected: {{ treeParents.length}}</v-flex>
</v-layout>
</v-app>
</div>
In treeParents variable i filter out all the children if a parent is selected.
This solution forces you to save also the original tree to reload the same data later, but i've opened a Feature Request on Vuetify GitHub project page https://github.com/vuetifyjs/vuetify/issues/6759 and hope to have time to explore the component better to see if i can make a pull request.

Binding a v-data-table to a props property in a template

I have a vue component which calls a load method returning a multi-part json object. The template of this vue is made up of several sub-vue components where I assign :data="some_object".
This works in all templates except for the one with a v-data-table in that the v-for process (or the building/rendering of the v-data-table) seems to kick-in before the "data" property is loaded.
With an npm dev server if I make a subtle change to the project which triggers a refresh the data-table then loads the data as I expect.
Tried various events to try and assign a local property to the one passed in via "props[]". Interestingly if I do a dummy v-for to iterate through or simply access the data[...] property the subsequent v-data-table loads. But I need to bind in other rules based on columns in the same row and that doesn't work.
Parent/main vue component:
...
<v-flex xs6 class="my-2">
<ShipViaForm :data="freight"></ShipViaForm>
</v-flex>
<OrderHeaderForm :data="orderheader"></OrderHeaderForm>
<v-flex xs12>
<DetailsForm :data="orderdet" :onSubmit="submit"></DetailsForm>
</v-flex>
...
So in the above the :data property is assigned from the result below for each sub component.
...
methods: {
load(id) {
API.getPickingDetails(id).then((result) => {
this.picking = result.picking;
this.freight = this.picking.freight;
this.orderheader = this.picking.orderheader;
this.orderdet = this.picking.orderdet;
});
},
...
DetailsForm.vue
<template lang="html">
<v-card>
<v-card-title>
<!-- the next div is a dummy one to force the 'data' property to load before v-data-table -->
<div v-show="false">
<div class="hide" v-for='header in headers' v-bind:key='header.product_code'>
{{ data[0][header.value] }}
</div>
</div>
<v-data-table
:headers='headers'
:items='data'
disable-initial-sort
hide-actions
>
<template slot='items' slot-scope='props'>
<td v-for='header in headers' v-bind:key='header.product_code'>
<v-text-field v-if="header.input"
label=""
v-bind:type="header.type"
v-bind:max="props.item[header.max]"
v-model="props.item[header.value]">
</v-text-field>
<span v-else>{{ props.item[header.value] }}</span>
</td>
</template>
</v-data-table>
</v-card-title>
</v-card>
</template>
<script>
import API from '#/lib/API';
export default {
props: ['data'],
data() {
return {
valid: false,
order_id: '',
headers: [
{ text: 'Order Qty', value: 'ord_qty', input: false },
{ text: 'B/O Qty', value: 'bo_qty', input: false },
{ text: 'EDP Code', value: 'product_code', input: false },
{ text: 'Description', value: 'product_desc', input: false },
{ text: 'Location', value: 'location', input: false },
{ text: 'Pick Qty', value: 'pick_qty', input: true, type: 'number', max: ['ord_qty'] },
{ text: 'UM', value: 'unit_measure', input: false },
{ text: 'Net Price', value: 'net_price', input: false },
],
};
},
mounted() {
const { id } = this.$route.params;
this.order_id = id;
},
methods: {
submit() {
if (this.valid) {
API.updateOrder(this.order_id, this.data).then((result) => {
console.log(result);
this.$router.push({
name: 'Orders',
});
});
}
},
clear() {
this.$refs.form.reset();
},
},
};
</script>
Hopefully this will help someone else who can't see the forest for the trees...
When I declared the data() { ... } properties in the parent form I initialised orderdet as {} instead of [].

Vuetify Snackbar leave event

I manage to implement a global Vuetify Snackbar.
My problem is to detect when the snackbar close. I read that this component support Vue transition event since 1.2. But it work only on the enter event not the leave ones.
here a fiddle for comprehension.
<transition #before-enter="beforeEnter" #before-leave="beforeLeave" #after-enter="afterEnter" #after-leave="afterLeave" #leave="leave">
<v-snackbar v-model="snackbar" top right>
Hello
<v-btn #click="snackbar = false" dark>Close</v-btn>
</v-snackbar>
</transition>
I faced the same problem and solved this way:
export default {
data: () => ({
errorMessage: '',
snackTimeout: 6000,
}),
watch: {
errorMessage() {
setTimeout(() => {
this.clearErrorMessage();
}, this.snackTimeout);
},
},
methods: {
setErrorMessage(message) {
this.snackMessage = message;
},
clearErrorMessage() {
this.snackMessage = '';
},
},
};
<template>
<v-snackbar
:value="errorMessage"
:timeout="snackTimeout"
top
>
{{ errorMessage }}
<v-btn
color="error"
flat
#click.stop="clearErrorMessage"
>
{{ 'close' }}
</v-btn>
</v-snackbar>
</template>
Define an attribute with the timeout and another with the message to show by the snackBar.
Define a function to set the message and another to clear it.
Define a watch for the message text and set a timer with the same timeout of the snackBar to clear it.
The snackBar appears only when the message is not empty.
You can use get and set methods to handle reading and updating the bound model separately.
I made a generic snackbar component that can be triggered from any other component. I'm using Vuex, vue-property-decorator and typescript here, so adjust accordingly.
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.11/vue.js"></script>
<template>
<v-snackbar v-model="snackbar" max-width="100%">
<template v-slot:action="{ attrs }">
{{ text }}
<v-btn color="primary" text fab v-bind="attrs">
<v-icon dark #click="close()"> mdi-close-circle-outline </v-icon>
</v-btn>
</template>
</v-snackbar>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator'
#Component({})
export default class Snackbar extends Vue {
get snackbar() {
return this.$store.state.snackbar.show
}
set snackbar(show: boolean) {
this.$store.dispatch('updateSnackbar', { show, text: '' })
}
get text() {
return this.$store.state.snackbar.text
}
public close() {
this.$store.dispatch('updateSnackbar', { show: false, text: '' })
}
}
</script>

Resources