Can`t reduce this code via v-for:
<v-menu v-model="menu" :close-on-content-click="false" offset-y>
<v-container fluid class="pt-0">
<v-row>
<v-col class="pt-0">
<v-checkbox v-for="item in types.slice(0,6)" :key="item.type" hide-details :label="item.name"></v-checkbox>
</v-col>
<v-col class="pt-0">
<v-checkbox v-for="item in types.slice(6,12)" :key="item.type" hide-details :label="item.name"></v-checkbox>
</v-col>
<v-col class="pt-0">
<v-checkbox v-for="item in types.slice(12,18)" :key="item.type" hide-details :label="item.name"></v-checkbox>
</v-col>
</v-row>
</v-container>
</v-menu>
types: [
{ type: 0, name: 'Item_1' },
{ type: 1, name: 'Item_2' },
{ type: 2, name: 'Item_3' },
{ type: 3, name: 'Item_4' },
{ type: 4, name: 'Item_5' },
{ type: 5, name: 'Item_6' },
{ type: 6, name: 'Item_7' },
{ type: 7, name: 'Item_8' },
{ type: 8, name: 'Item_9' },
{ type: 9, name: 'Item_10' },
{ type: 10, name: 'Item_11' },
{ type: 11, name: 'Item_12' },
{ type: 12, name: 'Item_13' },
{ type: 13, name: 'Item_14' },
{ type: 14, name: 'Item_15' },
{ type: 15, name: 'Item_16' },
{ type: 16, name: 'Item_17' },
{ type: 17, name: 'Item_18' },
{ type: 18, name: 'Item_19' },
]
Codepen example: https://codepen.io/aurorame/pen/WNdjxYM
Try to make it via use v-for in v-col, but how to slice it correctly?
You could use v-for in a range (starts at index 1), and calculate the splice indexes for the three rows:
<v-col class="pt-0" v-for="i in 3">
<v-checkbox v-for="item in Object.keys(items).slice((i-1)*3, (i-1)*3+6)" :key="item" hide-details :label="items[item].label"></v-checkbox>
</v-col>
demo
You can achieve that goal via "computed properties" of Vue. Here is the code that works for me:
<template>
<v-app>
<v-menu v-model="menu" :close-on-content-click="false" offset-y>
<v-container fluid class="pt-0">
<v-row>
<v-col class="pt-0" v-for="(newItem, index) in eachPart" :key="index">
<v-checkbox v-for="item in newItem" :key="item.type" hide-details :label="item.name"></v-checkbox>
</v-col>
</v-row>
</v-container>
</v-menu>
</v-app>
</template>
<script>
export default {
name: "LoopView",
data() {
return {
menu: true,
/* here you define the number of column. you can change it to 3 */
part: 4,
types: [
{ type: 0, name: 'Item_1' },
{ type: 1, name: 'Item_2' },
{ type: 2, name: 'Item_3' },
{ type: 3, name: 'Item_4' },
{ type: 4, name: 'Item_5' },
{ type: 5, name: 'Item_6' },
{ type: 6, name: 'Item_7' },
{ type: 7, name: 'Item_8' },
{ type: 8, name: 'Item_9' },
{ type: 9, name: 'Item_10' },
{ type: 10, name: 'Item_11' },
{ type: 11, name: 'Item_12' },
{ type: 12, name: 'Item_13' },
{ type: 13, name: 'Item_14' },
{ type: 14, name: 'Item_15' },
{ type: 15, name: 'Item_16' },
{ type: 16, name: 'Item_17' },
{ type: 17, name: 'Item_18' },
{ type: 18, name: 'Item_19' },
],
}
},
computed: {
typesLength: function () {
return this.types.length;
},
eachPart: function () {
/* you can also use "Math.floor" if that is more suitable for your design */
let chunkSize = Math.ceil( this.typesLength / this.part );
let partArr = []
for (let i = 0; i < this.typesLength; i += chunkSize) {
const chunk = this.types.slice(i, i + chunkSize);
partArr.splice(i, 0, chunk);
}
return partArr
}
}
}
</script>
<style scoped>
</style>
Related
Im get the products state in my computed well , but when i add the products into the table i get ann error.
the products came from api call, What can I do to make the information appear?
the error is : [Vue warn]: Invalid prop: type check failed for prop "items". Expected Array, got Object
<template>
<v-card>
<v-card-title>
<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
:headers="headers"
:items="products"
:search="search"
></v-data-table>
</v-card>
</template>
<script>
export default {
name: "products",
data() {
return {
search: "",
headers: [
{
text: "Year",
align: "start",
filterable: true,
value: "year",
},
{ text: "Item", value: "item" },
{ text: "Element", value: "element" },
{ text: "Value", value: "value" },
{ text: "Unit", value: "unit" },
{ text: "Area Code", value: "areacode" },
{ text: "Area", value: "area" },
],
};
},
created() {
this.$store.dispatch("loadProducts");
},
computed: {
products() {
return this.$store.state.products
},
},
};
</script>
I have a Vuetify data table, and I have a feature that toggles whether editing is turned on or off.
For one of my columns (Roles), it will toggle between showing just text (editing disabled) or a selection dropdown (editing enabled). The toggle is set using a boolean, and I toggle the components' visibility using a v-if and v-else attribute.
Editing disabled
Editing enabled (user can then select the new value in each dropdown)
However, this seems to have a conflict with whether it can be sorted or searched. The other columns are unaffected and can be searched/sorted normally. Please help me to see if there are any errors, thank you.
Table
<v-data-table :headers="headers" :items="users" :items-per-page="10" :search="search">
<template v-slot:item.role="{ item }">
<v-select
:items="roles"
item-text="roleName"
item-value="roleId"
v-model="item"
v-if="!editIsDisabled"
></v-select>
<div v-else v-for="role in roles" :key="role.roleId">
<span v-if="role.roleId === item.roleId">{{ role.roleName }}</span>
</div>
</template>
...
</v-data-table>
Script tag
export default {
data() {
return {
editIsDisabled: true,
search: "",
roles: [
{
roleId: 1,
roleName: "Procurement"
},
{
roleId: 2,
roleName: "Fulfilment"
},
{
roleId: 3,
roleName: "Human Resources"
}
],
headers: [
{
text: "User ID",
value: "userId"
},
{
text: "User",
value: "userName"
},
{
text: "Role",
value: "role",
sortable: false
}
],
users: [
{
userId: 122,
userName: "John Lim",
roleId: 1
},
{
userId: 125,
userName: "Amy Lee",
roleId: 1
},
{
userId: 102,
userName: "Ben Tan",
roleId: 2
},
{
userId: 156,
userName: "Cindy Ng",
roleId: 2
},
{
userId: 89,
userName: "Chris Lee",
roleId: 3
}
]
};
}
};
The search items should be a part of users object, In you case, the roles are mapped based on roleId in users array and roles array
I've refactored the above code to include role as a property on created hook , so that roles will be a part of users array and it is eligible for search
Working codepen here: https://codepen.io/chansv/pen/zYYPrqL?editors=1010
<div id="app">
<v-app id="inspire">
<v-card>
<v-card-title>
Users
<v-spacer></v-spacer>
<v-text-field
v-model="search"
append-icon="search"
label="Search"
single-line
hide-details
></v-text-field>
<v-btn color="primary" #click="editIsDisabled = !editIsDisabled"> Edit</v-btn>
</v-card-title>
<v-data-table :headers="headers" :items="users" :items-per-page="10" :search="search">
<template v-slot:item.role="{ item }">
<v-select
:items="roles"
item-text="roleName"
item-value="roleId"
v-model="item"
v-if="!editIsDisabled"
></v-select>
<span v-else>{{item.role}}</span>
</template>
</v-data-table>
</v-card>
</v-app>
</div>
new Vue({
el: '#app',
vuetify: new Vuetify(),
data () {
return {
editIsDisabled: true,
search: "",
roles: [
{
roleId: 1,
roleName: "Procurement"
},
{
roleId: 2,
roleName: "Fulfilment"
},
{
roleId: 3,
roleName: "Human Resources"
}
],
headers: [
{
text: "User ID",
value: "userId"
},
{
text: "User",
value: "userName"
},
{
text: "Role",
value: "role",
sortable: false,
}
],
users: [
{
userId: 122,
userName: "John Lim",
roleId: 1
},
{
userId: 125,
userName: "Amy Lee",
roleId: 1
},
{
userId: 102,
userName: "Ben Tan",
roleId: 2
},
{
userId: 156,
userName: "Cindy Ng",
roleId: 2
},
{
userId: 89,
userName: "Chris Lee",
roleId: 3
}
]
}
},
created() {
var rolesObj = {};
this.roles.map(x => rolesObj[x.roleId] = x.roleName);
this.users.forEach(x => {
x.role = rolesObj[x.roleId];
})
},
})
Vuetify v-treeview how to get the get last selected element?
<v-treeview
v-model="treeModel"
:items="items"
selectable="selectable"
>
by treeModel I have all selected, but how can I get only the last item selected (clicked)?
by only providing the last item's id in the v-model
modified example from https://vuetifyjs.com/en/components/treeview#checkbox-color
option 1 - use v-on/#on update:active
** DOES NOT CURRENTLY WORK FOR VUETIFY v2 **
<div id="app">
<v-app id="inspire">
<v-treeview
v-model="selection"
selectable
selected-color="red"
:items="items"
#update:active="onUpdate"
></v-treeview>
</v-app>
</div>
new Vue({
el: '#app',
vuetify: new Vuetify(),
data: () => ({
selection: [],
items: [
{
id: 1,
name: 'Applications :',
children: [
{ id: 2, name: 'Calendar : app' },
{ id: 3, name: 'Chrome : app' },
{ id: 4, name: 'Webstorm : app' },
],
},
{
id: 5,
name: 'Documents :',
children: [
{ id: 6, name: 'Calendar.doc' },
{ id: 7, name: 'Chrome.doc' },
{ id: 8, name: 'Webstorm.doc' },
],
},
],
}),
methods: {
onUpdate(selection) {
console.log(selection)
}
}
})
the problem is that if you're using vuetify v2.0.0 - v2.0.5 the action does not actually work for a selection but for activatable
## option 2 - use watch
this option, at the moment, is preferred. It uses a watch to trigger the action when the `v-model` changes
```html
<div id="app">
<v-app id="inspire">
<v-treeview
v-model="selection"
:items="items"
selectable
></v-treeview>
</v-app>
</div>
new Vue({
el: '#app',
vuetify: new Vuetify(),
watch:{
selection(newValue) {
this.onUpdate(newValue)
}
},
data: () => ({
selection: [],
items: [
{
id: 1,
name: 'Applications :',
children: [
{ id: 2, name: 'Calendar : app' },
{ id: 3, name: 'Chrome : app' },
{ id: 4, name: 'Webstorm : app' },
],
},
{
id: 5,
name: 'Documents :',
children: [
{ id: 6, name: 'Calendar.doc' },
{ id: 7, name: 'Chrome.doc' },
{ id: 8, name: 'Webstorm.doc' },
],
},
],
}),
methods: {
onUpdate(selection) {
console.log(selection)
}
}
})
If you want to find the last item that was selected, you can use an array diff here
watch:{
selection(newValue, oldVal) {
var arrDiff = myArrDiffDependency.added(newValue, oldValue)
this.onUpdate(arrDiff)
}
},
watch: {
selection() {
if (this.items.length > 1) {
this.items.shift();
}
},
},
Isto resolve
I am attempting to make a custom component in Vue 2.0 that extends the existing functionality of the Bootstrap Vue library <b-table>. It mostly works how I would like it to except that the removeRow and resetData functions defined in the index.jsp don't work how I'd like them to.
removeRow does visually remove the row, and removes it from the data prop but the row reappears after the next interaction (sort, filter, etc.). So it's not actually updating the right thing. I'm trying to use a v-model as a shallow copy of items so that I can make deletions to it without affecting the original set of data but I'm just missing something.
resetData does set the data in the table back correctly, but doesn't re-render the table so you can't see the re-added rows, until you do a separate interaction (sort, filter, etc.), in which case they reappear.
So I know I'm somewhat close but would really appreciate any insight on how to get this working correctly and ways I could improve any part of this component.
OreillyTable.vue.js
const OreillyTable = {
inheritAttrs: false,
data: function () {
return {
filter: null,
sortDesc: false,
hideEmpty: false,
isBusy: false,
currentPage: 1,
data: null
}
},
mounted: function () {
let filtered = this.slots.filter(function(value, index, arr){
return value.customRender;
});
this.slots = filtered;
},
methods: {
oreillyTableSort (a, b, key) {
if (a[key] === null || b[key] === null) {
return a[key] === null && b[key] !== null ? -1 : (a[key] !== null && b[key] === null ? 1 : 0);
} else if (typeof a[key] === 'number' && typeof b[key] === 'number') {
// If both compared fields are native numbers
return a[key] < b[key] ? -1 : (a[key] > b[key] ? 1 : 0)
} else {
// Stringify the field data and use String.localeCompare
return this.toString(a[key]).localeCompare(this.toString(b[key]), undefined, {
numeric: true
});
}
},
toString (val) {
return typeof val !== "undefined" && val != null ? val.toString() : '';
},
oreillyFilter (filteredItems) {
this.totalRows = filteredItems.length;
this.currentPage = 1;
}
},
props: {
fields: {
type: Array
},
items: {
type: Array,
required: true
},
hideEmpty: {
type: Boolean
},
filterPlaceholder: {
type: String,
default: "Filter"
},
sortFunction: {
type: Function,
default: null
},
filterFunction: {
type: Function,
default: null
},
slots: {
type: Array
},
sortBy: {
type: String,
default: null
},
perPage: {
type: Number,
default: 10
},
value: {
}
},
template: `<div :class="{ 'no-events' : isBusy }">
<b-row>
<b-col md="12">
<b-form-group class="mb-2 col-md-3 float-right pr-0">
<b-input-group>
<b-form-input v-model="filter" :placeholder="filterPlaceholder" class="form-control" />
</b-input-group>
</b-form-group>
</b-col>
</b-row>
<div class="position-relative">
<div v-if="isBusy" class="loader"></div>
<b-table stacked="md" outlined responsive striped hover
v-bind="$attrs"
v-model="data"
:show-empty="!hideEmpty"
:items="items"
:fields="fields"
:no-provider-sorting="true"
:no-sort-reset="true"
:filter="filter"
:sort-by.sync="sortBy"
:sort-desc.sync="sortDesc"
:sort-compare="sortFunction === null ? this.oreillyTableSort : sortFunction"
:busy.sync="isBusy"
:current-page="currentPage"
:per-page="perPage"
#filtered="filterFunction === null ? this.oreillyFilter : filterFunction">
<template :slot="slot.key" slot-scope="row" v-for="slot in slots">
<slot :name="slot.key" :data="row"></slot>
</template>
</b-table>
<b-row v-if="items.length > perPage">
<b-col sm="12">
<b-pagination size="md" :total-rows="items.length" v-model="currentPage" :per-page="perPage"></b-pagination>
</b-col>
</b-row>
</div>
</div>`
};
index.jsp
<script>
Vue.use(window.vuelidate.default);
Vue.component('oreilly-table', OreillyTable);
const dashboardItems = [
{ id: 12, firstName: "John", lastName: "Adams", tmNumber: "588999", team: "Corporate", flapjackCount: 4, enrollDate: "2018-11-05" },
{ id: 13, firstName: "George", lastName: "Washington", tmNumber: "422111", team: "America", flapjackCount: 28, enrollDate: "2018-10-01" },
{ id: 14, firstName: "Abraham", lastName: "Lincoln", tmNumber: "358789", team: "America", flapjackCount: 16, enrollDate: "2017-09-02" },
{ id: 15, firstName: "Jimmy", lastName: "Carter", tmNumber: "225763", team: "Core", flapjackCount: 9, enrollDate: "2018-03-02" },
{ id: 16, firstName: "Thomas", lastName: "Jefferson", tmNumber: "169796", team: "Core", flapjackCount: 14, enrollDate: "2018-05-01" }
];
const Dashboard = {
template: `<jsp:include page="dashboard.jsp"/>`,
data: function(){
return {
notification: {
text: "The Great Flapjack Contest will be held on December 25, 2018.",
variant: "primary",
timer: true
},
fields: [
{ key: "name", label: "Name", sortable: true, customRender: true },
{ key: "team", label: "Team", sortable: true },
{ key: "enrollDate", label: "Enrollment Date", sortable: true, formatter: (value) => {return new Date(value).toLocaleDateString();} },
{ key: "flapjackCount", sortable: true },
{ key: "id", label: "", 'class': 'text-center', customRender: true }
]
}
},
methods: {
removeRow: function(id) {
this.$refs.table.isBusy = true;
setTimeout(() => { console.log("Ajax Request Here"); this.$refs.table.isBusy = false; }, 1000);
const index = this.$refs.table.data.findIndex(item => item.id === id)
if (~index)
this.$refs.table.data.splice(index, 1)
},
resetData: function() {
this.$refs.table.data = dashboardItems;
}
}
};
const router = new VueRouter({
mode: 'history',
base: "/ProjectTemplate/flapjack",
routes: [
{ path: '/enroll', component: Enroll },
{ path: '/', component: Dashboard },
{ path: '/404', component: NotFound },
{ path: '*', redirect: '/404' }
]
});
new Vue({router}).$mount('#app');
dashboard.jsp
<compress:html>
<div>
<oreilly-table ref="table" :items="dashboardItems" :slots="fields" :fields="fields">
<template slot="name" slot-scope="row">
{{ row.data.item.firstName }} {{ row.data.item.lastName }} ({{ row.data.item.tmNumber }})
</template>
<template slot="id" slot-scope="row">
Remove
</template>
</oreilly-table>
<footer class="footer position-sticky fixed-bottom bg-light">
<div class="container text-center">
<router-link tag="button" class="btn btn-outline-secondary" id="button" to="/enroll">Enroll</router-link>
<b-button #click.prevent="resetData" size="md" variant="outline-danger">Reset</b-button>
</div>
</footer>
</div>
I tried to reproduce your problem with a simple example (see: https://codesandbox.io/s/m30wqm0xk8?module=%2Fsrc%2Fcomponents%2FGridTest.vue) and I came across the same problem you have. Just like the others already said, I agree that the easiest way to reset original data is to make a copy. I wrote two methods to remove and reset data.
methods: {
removeRow(id) {
const index = this.records.findIndex(item => item.index === id);
this.records.splice(index, 1);
},
resetData() {
this.records = this.copyOfOrigin.slice(0);
}
}
On mount I execute a function that makes a copy of the data. This is done with slice because otherwise it makes only a reference to the original array (note that normally JS is pass-by-value, but as stated in the vue documentation with objects, and thus internally in vue it is pass by reference (see: Vue docs scroll to the red marked text)).
mounted: function() {
this.copyOfOrigin = this.records.slice(0);
}
Now you can simple remove a record but also reset all the data.
SEE FULL DEMO
I hope this fixes your issue and if you have any questions, feel free to ask.
I have an issue with all my checkboxes always being true.
I've tried using the "false-value" attribute, but to no help.
I also have a default input checkbox, which is functioning properly.
export default {
data() {
return {
straps: [],
checkedColors: [],
checkedSkins: [],
checkedTypes: [],
filterings: [{
title: "Farver",
filters: [{
title: "Grøn",
value: "grøn",
model: "checkedColors"
},
{
title: "Rød",
value: "rød",
model: "checkedColors"
},
{
title: "Gul",
value: "yellow",
model: "checkedColors"
},
{
title: "Lilla",
value: "lilla",
model: "checkedColors"
},
{
title: "Blå",
value: "blå",
model: "checkedColors"
},
{
title: "Grå",
value: "grå",
model: "checkedColors"
},
{
title: "Sort",
value: "sort",
model: "checkedColors"
},
{
title: "Hvid",
value: "hvid",
model: "checkedColors"
},
{
title: "Brun",
value: "brun",
model: "checkedColors"
}
]
},
{
title: "Materialer",
filters: [{
title: "Alligator",
value: "alligator",
model: "checkedSkins"
},
{
title: "Struds",
value: "ostridge",
model: "checkedSkins"
},
{
title: "Teju firben",
value: "teju",
model: "checkedSkins"
},
{
title: "Haj",
value: "shark",
model: "checkedSkins"
}
]
},
{
title: "Remme til",
filters: [{
title: "Universal",
value: "universal",
model: "checkedTypes"
},
{
title: "Audemars Piguet",
value: "ap",
model: "checkedTypes"
},
{
title: "Jaeger LeCoultre",
value: "jlc",
model: "checkedTypes"
},
{
title: "Rolex",
value: "rolex",
model: "checkedTypes"
}
]
}
]
};
},
computed: {
filteredStraps() {
var straps = this.straps;
if (this.search !== null) {
var straps = this.searchItems.filter(strap => {
if (!this.search) return this.searchItems;
return (
strap.title.toLowerCase().includes(this.search.toLowerCase()) ||
strap.skin.toLowerCase().includes(this.search.toLowerCase()) ||
strap.type.toLowerCase().includes(this.search.toLowerCase())
);
});
}
if (this.checkedSkins.length > 0) {
straps = straps.filter(strap => {
return this.checkedSkins.includes(strap.skin.toLowerCase());
});
}
if (this.checkedTypes.length > 0) {
straps = straps.filter(strap => {
return this.checkedTypes.includes(strap.type.toLowerCase());
});
}
if (this.sort == "newest") {
return straps.sort((a, b) => new Date(a.date) - new Date(b.date));
}
if (this.sort == "priceasc") {
return straps.sort((a, b) => a.price > b.price);
}
if (this.sort == "pricedesc") {
return straps.sort((a, b) => a.price < b.price);
} else {
return straps;
}
},
getStraps() {
db.collection("straps")
.get()
.then(querySnapshot => {
const straps = [];
querySnapshot.forEach(doc => {
const data = {
id: doc.id,
title:
doc
.data()
.type.charAt(0)
.toUpperCase() +
doc.data().type.slice(1) +
" RIOS1931 " +
doc
.data()
.title.charAt(0)
.toUpperCase() +
doc.data().title.slice(1) +
" Urrem i " +
doc
.data()
.skin.charAt(0)
.toUpperCase() +
doc.data().skin.slice(1),
price: doc.data().price,
skin: doc.data().skin,
type: doc.data().type,
imgs: doc.data().imgs[0].url,
colors: doc.data().colors,
date: doc
.data()
.date.toString()
.slice(0, 15)
};
straps.push(data);
});
this.straps = straps;
});
},
}
<v-layout>
<v-flex sm3 md2 class="hidden-xs-only text-xs-left">
<p class="pl-4"><strong>Sortering</strong></p>
<v-expansion-panel class="elevation-0">
<v-expansion-panel-content v-for="filtering in filterings" :key="filtering.title">
<div slot="header">{{filtering.title | capitalize}}</div>
<v-card>
<v-card-text>
<v-list>
<input type="checkbox" value="alligator" v-model="checkedSkins">
<label for="checker"></label>
<v-list-tile v-for="filter in filtering.filters" :key="filter.value">
<v-list-tile-content>
<v-checkbox :input-value="filter.value" :label="filter.title" v-model="filter.model" color="primary"></v-checkbox>
</v-list-tile-content>
</v-list-tile>
</v-list>
</v-card-text>
</v-card>
</v-expansion-panel-content>
<v-expansion-panel-content>
<div slot="header">Pris</div>
<v-card>
<v-card-text>
<v-layout>
<v-flex px-2>
<v-range-slider :min="slider[0]" :max="slider[1]" v-model="slider" thumb-label="always"></v-range-slider>
</v-flex>
</v-layout>
<v-layout>
<v-flex xs6 pr-2>
<v-text-field label="Fra pris" v-model="slider[0]" class="mt-0" hide-details single-line type="number"></v-text-field>
</v-flex>
<v-flex xs6 pl-2>
<v-text-field label="Til pris" v-model="slider[1]" class="mt-0" hide-details single-line type="number"></v-text-field>
</v-flex>
</v-layout>
</v-card-text>
</v-card>
</v-expansion-panel-content>
</v-expansion-panel>
</v-flex>
</v-layout>
As mentioned the default input works as intended, but the vuetify checkboxes are all returning true for some reason, and they won't work, even though they have the same attribute values in the front-end.
If you want to store checked objects as strings from filter.value property so you have 2 issues in your code(second one is related to your question):
You have incorrect value in your v-model directive. You bind filter.model variable to v-model not its stored array name, to fix this you should pass to v-model something like this $data[filter.model] to bind array from data as model dynamically.
You use input-value binding incorrectly. input-value is related to v-model value(see v-checkbox source code, it's overriding of default model), you don't need to change this value. So you need to pass filter.value to value attribute instead.
Result:
<v-checkbox :value="filter.value" :label="filter.title" v-model="$data[filter.model]" color="primary"></v-checkbox>