Skip to content
This repository has been archived by the owner on Mar 17, 2021. It is now read-only.

Commit

Permalink
Working on table component
Browse files Browse the repository at this point in the history
  • Loading branch information
romanslonov committed Apr 17, 2019
1 parent 0f0f4f4 commit cd0f6bd
Show file tree
Hide file tree
Showing 8 changed files with 375 additions and 0 deletions.
1 change: 1 addition & 0 deletions docs/.vuepress/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ module.exports = {
'components/select',
'components/spinner',
'components/stepper',
'components/table',
'components/tabs',
'components/textbox',
'components/toggle',
Expand Down
65 changes: 65 additions & 0 deletions docs/components/table.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# Table

## Default
<div class="p-3 pb-5 border rounded-2 my-3 flex">
<v-table :fields="normal.fields" :items="normal.items" />
</div>

```html
<v-table :fields="normal.fields" :items="normal.items" />
```

## Title & Search
<div class="p-3 pb-5 border rounded-2 my-3 flex">
<v-table :fields="normal.fields" :items="normal.items">
<template v-slot:toolbar="{ handleSearchInput }">
<v-table-header title="Custom title" :handleSearchInput="handleSearchInput"></v-table-header>
</template>
</v-table>
</div>

```html
<v-table :fields="normal.fields" :items="normal.items">

<!--custom header-->
<template v-slot:toolbar="{ handleSearchInput }">
<v-table-header
title="Custom title"
:handleSearchInput="handleSearchInput"
></v-table-header>
</template>

</v-table>
```

<script>
export default {
data() {
return {
normal: {
fields: {
name: { label: 'Name', sortable: true },
createdAt: { label: 'Created', sortable: false },
},
items: [
{ id: 1, name: 'Item 1', createdAt: '1 days ago', },
{ id: 2, name: 'Item 2', createdAt: '2 days ago', },
{ id: 3, name: 'Item 3', createdAt: '3 days ago', },
],
},
multiple: {
selected: [],
fields: {
name: { label: 'Name', sortable: true },
createdAt: { label: 'Created', sortable: false },
},
items: [
{ id: 1, name: 'Item 1', createdAt: '1 days ago', },
{ id: 2, name: 'Item 2', createdAt: '2 days ago', },
{ id: 3, name: 'Item 3', createdAt: '3 days ago', },
],
},
};
},
};
</script>
8 changes: 8 additions & 0 deletions src/components/Table/TableHeader/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import TableHeader from './main.vue';

// eslint-disable-next-line func-names
TableHeader.install = function (Vue) {
Vue.component('VTableHeader', TableHeader);
};

export default TableHeader;
50 changes: 50 additions & 0 deletions src/components/Table/TableHeader/main.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<template>
<div class="table-header">
<h2 class="table-header__title">{{ title }}</h2>

<v-textbox
floated
label="Search"
v-if="searchable"
v-on="handleSearchInput"
class="table-header__search"
type="text"
placeholder="Search"
:disabled="disabledSearch"
/>
</div>
</template>

<script>
export default {
name: 'VTableHeader',
props: {
title: {
type: String,
default: 'Table title',
},
searchable: {
type: Boolean,
default: true,
},
handleSearchInput: {
type: Object,
default: () => {},
},
disabledSearch: {
type: Boolean,
},
},
};
</script>

<style>
.table-header {
display: flex;
align-items: center;
justify-content: space-between;
}
.table-header__title {
margin: 0;
}
</style>
8 changes: 8 additions & 0 deletions src/components/Table/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import Table from './main.vue';

// eslint-disable-next-line func-names
Table.install = function (Vue) {
Vue.component('VTable', Table);
};

export default Table;
228 changes: 228 additions & 0 deletions src/components/Table/main.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
<template>
<div class="table-wrapper">

<slot
name="toolbar"
:items="items"
:handleSearchInput="{ input: (e) => query = e.target.value }"
:disabledSearch="items.length === 0"
></slot>

<table>

<thead>
<tr v-if="!(items.length === 0 || items.length > 0 && mutableItems.length === 0)">
<th class="table-select-td" v-if="multiSelect">
<input
ref="checkbox"
type="checkbox"
@change="handleSelectAll"
:checked="mutableItems.length === mutableSelected.length"
>
</th>

<th
@click="onHeadClick(field, key)"
v-for="(field, key) in fields"
:key="key"
:class="[
field.sortable
? 'table-sorting'
: null,
sortBy===key
? 'sorting--' + (sortDesc?'desc':'asc')
: '',
field.class ? field.class : null
]"
>
<span
v-html="field.label"
v-if="field.label"
class="table-sorting__icon table-sorting__label"
></span>
</th>
</tr>
</thead>

<tbody>
<tr v-if="items.length === 0" :colspan="Object.keys(fields).length">
<slot name="empty">
<div style="text-align:center;" v-html="emptyText"></div>
</slot>
</tr>

<tr v-for="(item, index) in mutableItems" :key="index">
<td class="table-select-td" v-if="multiSelect">
<input type="checkbox" @change="handleSelect" v-model="selected[index]">
</td>

<td
v-for="(field, key) in fields"
:key="key"
:class="`table-${field.label.toLowerCase()}`"
>
<slot
:name="key"
:value="item[key]"
:item="item"
:index="index"
>
{{ item[key] }}
</slot>
</td>
</tr>

<tr
v-if="items.length > 0 && mutableItems.length === 0"
:colspan="Object.keys(fields).length"
>
<slot name="emptyfiltered">
<div style="text-align:center;" v-html="emptyFilteredText"></div>
</slot>
</tr>
</tbody>

</table>

</div>
</template>

<script>
import fuzzysearch from 'fuzzysearch';
import { toString } from '../../helpers/util';
const defaultSortCompare = (a, b, sortBy) => (
toString(a[sortBy]).localeCompare(toString(b[sortBy]), undefined, { numeric: true })
);
export default {
name: 'VTable',
props: {
fields: {
type: Object,
default: () => {},
},
items: {
type: Array,
default: () => [],
},
multiSelect: {
type: Boolean,
default: false,
},
perPage: {
type: Number,
default: 10,
},
currentPage: {
type: Number,
default: 1,
},
sortCompare: {
type: Function,
default: null,
},
emptyText: {
type: String,
default: 'There is no records...',
},
emptyFilteredText: {
type: String,
default: 'There are no records matching your request',
},
},
data() {
return {
query: '',
selected: [],
sortBy: null,
sortDesc: true,
};
},
computed: {
mutableItems() {
let items = this.items.slice();
if (this.query) {
items = items.filter(item => fuzzysearch(this.query, toString(item)));
}
const sortCompare = this.sortCompare || defaultSortCompare;
if (this.sortBy) {
items = items.sort((a, b) => {
const r = sortCompare(a, b, this.sortBy);
return this.sortDesc ? r : r * -1;
});
}
if (this.perPage) {
items = items.slice((this.currentPage - 1) * this.perPage, this.currentPage * this.perPage);
}
return items;
},
mutableSelected() {
return this.mutableItems
/* eslint-disable-next-line */
.map((el, i) => {
if (this.selected[i]) return el;
})
.filter(el => el !== undefined);
},
},
methods: {
onHeadClick(field, key) {
if (!field.sortable) {
this.sortBy = null;
return;
}
if (key === this.sortBy) {
this.sortDesc = !this.sortDesc;
}
this.sortBy = key;
},
handleSelect() {
this.$emit('input', this.mutableSelected);
},
handleSelectAll(e) {
if (e.target.checked) {
this.selectAll();
} else {
this.clearSelection();
}
this.handleSelect();
},
clearSelection() {
this.selected = [];
},
selectAll() {
this.selected = [...this.mutableItems];
},
},
};
</script>

<style>
.table-wrapper {
width: 100%;
}
.table-wrapper table {
display: table;
width: 100%;
}
.table-select-td {
width: 24px;
}
table {
max-width: 100%;
width: 100%;
}
table thead th {
text-align: left;
}
</style>
2 changes: 2 additions & 0 deletions src/components/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ export { default as VSelectOption } from './SelectOption';
export { default as VSpinner } from './Spinner';
export { default as VStepper } from './Stepper';
export { default as VTab } from './Tab';
export { default as VTable } from './Table';
export { default as VTableHeader } from './Table/TableHeader';
export { default as VTabs } from './Tabs';
export { default as VTextbox } from './Textbox';
export { default as VToggle } from './Toggle';
13 changes: 13 additions & 0 deletions src/helpers/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,16 @@ export function toUpperCaseFirstLetter(string) {
export function isObjectEmpty(obj) {
return Object.keys(obj).length === 0;
}

/**
* Transform object to string
*/
export function toString(v) {
if (!v) {
return '';
}
if (v instanceof Object) {
return Object.keys(v).map(k => toString(v[k])).join(' ');
}
return String(v).toLowerCase();
}

0 comments on commit cd0f6bd

Please sign in to comment.