Repository: tochoromero/vuejs-smart-table
Branch: master
Commit: e8af4c4a97eb
Files: 38
Total size: 127.5 KB
Directory structure:
gitextract_kaf1xz4h/
├── .browserslistrc
├── .circleci/
│ └── config.yml
├── .editorconfig
├── .eslintignore
├── .eslintrc.js
├── .gitignore
├── LICENSE
├── README.md
├── babel.config.js
├── deploy.sh
├── docs/
│ ├── .vuepress/
│ │ ├── components/
│ │ │ ├── BasicFiltering.vue
│ │ │ ├── CustomFiltering.vue
│ │ │ ├── InputSpinner.vue
│ │ │ ├── Pagination.vue
│ │ │ ├── Selection.vue
│ │ │ ├── SelectionApi.vue
│ │ │ ├── Sorting.vue
│ │ │ ├── SortingFA.vue
│ │ │ ├── TheBasics.vue
│ │ │ └── data.json
│ │ └── config.js
│ ├── README.md
│ ├── filtering/
│ │ └── README.md
│ ├── pagination/
│ │ └── README.md
│ ├── selection/
│ │ └── README.md
│ ├── sorting/
│ │ └── README.md
│ └── the-basics/
│ └── README.md
├── package.json
├── postcss.config.js
├── src/
│ ├── SmartPagination.vue
│ ├── VTable.vue
│ ├── VTh.vue
│ ├── VTr.vue
│ ├── main.js
│ ├── store.js
│ └── table-utils.js
└── tests/
└── unit/
├── .eslintrc.js
└── table-utils.spec.js
================================================
FILE CONTENTS
================================================
================================================
FILE: .browserslistrc
================================================
> 1%
last 2 versions
not ie <= 8
================================================
FILE: .circleci/config.yml
================================================
version: 2.1
jobs:
build:
docker:
- image: circleci/node
steps:
- checkout
- run: yarn install
- run: yarn test:unit
- store_test_results:
path: tests/results
================================================
FILE: .editorconfig
================================================
[*.{js,jsx,ts,tsx,vue}]
indent_style = space
indent_size = 2
trim_trailing_whitespace = true
insert_final_newline = true
================================================
FILE: .eslintignore
================================================
/dist
================================================
FILE: .eslintrc.js
================================================
module.exports = {
root: true,
env: {
node: true
},
'extends': [
'plugin:vue/essential',
'@vue/standard'
],
rules: {
'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off'
},
parserOptions: {
parser: 'babel-eslint'
}
}
================================================
FILE: .gitignore
================================================
.DS_Store
node_modules
dist
tests/results
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw*
docs/.vuepress/dist
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2019 Hector Romero
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
:fire::fire::fire: Vue 3 support is comming :fire::fire::fire:
Vue 3 support is already in beta in the `next` branch, the next version also supports Vue 2, you can take a look in the [new documentation site](https://vue-smart-table.netlify.app/).
# VueJs Smart Table
Vue Smart Table was created out of the need for a simple highly customizable data table plugin
that could take advantage of Vue's slots. It has no dependencies but Vue and because it
renders as a standard HTML table it is compatible with CSS Frameworks such as Bootstrap and Foundation.
Out of the box you will get filtering, column sorting, client side pagination and row selection.
## Full Documentation
Please read the [documentation](https://tochoromero.github.io/vuejs-smart-table/) to learn how to use it.
## Installation
To install simply run
```
npm add vuejs-smart-table
```
or
```
yarn add vuejs-smart-table
```
Then in your `main.js`
```js
import SmartTable from 'vuejs-smart-table'
Vue.use(SmartTable)
```
This will globally register four Components: `v-table`, `v-th`, `v-tr` and `smart-pagination`
================================================
FILE: babel.config.js
================================================
module.exports = {
presets: [
'@vue/app'
]
}
================================================
FILE: deploy.sh
================================================
#!/usr/bin/env sh
set -e
npm run docs:build
cd docs/.vuepress/dist
git init
git add -A
git commit -m 'deploy'
git push -f git@github.com:tochoromero/vuejs-smart-table.git master:gh-pages
cd -
================================================
FILE: docs/.vuepress/components/BasicFiltering.vue
================================================
<template>
<div class="card mt-3">
<div class="card-body">
<label>Filter by Name:</label>
<input class="form-control" v-model="filters.name.value"/>
<v-table
:data="users"
:filters="filters"
>
<thead slot="head">
<th>Name</th>
<th>Age</th>
<th>Email</th>
<th>Address</th>
</thead>
<tbody slot="body" slot-scope="{displayData}">
<tr v-for="row in displayData" :key="row.guid">
<td>{{ row.name }}</td>
<td>{{ row.age }}</td>
<td>{{ row.email }}</td>
<td>{{ row.address.street }}, {{ row.address.city }} {{ row.address.state}}</td>
</tr>
</tbody>
</v-table>
</div>
</div>
</template>
<script>
import users from './data.json'
import VTable from '../../../src/VTable.vue'
export default {
name: 'BasicFiltering',
components: { VTable },
data: () => ({
users: users.slice(0, 10),
filters: {
name: { value: '', keys: ['name'] }
}
})
}
</script>
<style lang="scss" scoped>
@import '~bootstrap/dist/css/bootstrap.min.css';
</style>
================================================
FILE: docs/.vuepress/components/CustomFiltering.vue
================================================
<template>
<div class="card mt-3">
<div class="card-body">
<label>Min Age:</label>
<InputSpinner
v-model="filters.age.value.min"
:min="0"
:max="filters.age.value.max"
inputWidth="100px"
/>
<label>Max Age:</label>
<InputSpinner
v-model="filters.age.value.max"
:min="filters.age.value.min"
:max="99"
inputWidth="100px"
/>
<v-table
:data="users"
:filters="filters"
>
<thead slot="head">
<th>Name</th>
<th>Age</th>
<th>Email</th>
<th>Address</th>
</thead>
<tbody slot="body" slot-scope="{displayData}">
<tr v-for="row in displayData" :key="row.guid">
<td>{{ row.name }}</td>
<td>{{ row.age }}</td>
<td>{{ row.email }}</td>
<td>
{{ row.address.street }},
{{ row.address.city }}
{{ row.address.state}}
</td>
</tr>
</tbody>
</v-table>
</div>
</div>
</template>
<script>
import users from './data.json'
import VTable from '../../../src/VTable.vue'
import InputSpinner from './InputSpinner'
export default {
name: 'CustomFiltering',
components: { InputSpinner, VTable },
data () {
return {
users,
filters: {
age: { value: { min: 21, max: 22 }, custom: this.ageFilter }
}
}
},
methods: {
ageFilter (filterValue, row) {
return row.age >= filterValue.min && row.age <= filterValue.max
}
}
}
</script>
<style lang="scss" scoped>
@import '~bootstrap/dist/css/bootstrap.min.css';
@import '~@fortawesome/fontawesome-free/css/all.css';
</style>
================================================
FILE: docs/.vuepress/components/InputSpinner.vue
================================================
<template>
<div>
<div class="input-group input-spinner"
:style="{width: componentWidth, 'min-width': '9rem'}">
<div class="input-group-prepend">
<button class="btn btn-outline-secondary"
type="button"
:disabled="isSubtractDisabled || disabled"
@click="subtract">
<i class="fas fa-minus"/>
</button>
</div>
<input type="number"
class="form-control inputSpinner"
v-model.number.trim="numValue"
:style="inputWidthStyle"
:readonly="readonly || disabled"
@input="onInput"/>
<div class="input-group-append">
<button class="btn btn-outline-secondary"
type="button"
:disabled="isAddDisabled || disabled"
@click="add">
<i class="fas fa-plus"/>
</button>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'InputSpinner',
props: {
min: {
type: Number,
required: false
},
max: {
type: Number,
required: false
},
value: {
type: Number,
required: false,
default: 0
},
precision: {
type: Number,
required: false,
default: 0
},
step: {
type: Number,
required: false,
default: 1
},
inputWidth: {
type: String,
required: false,
default: null
},
componentWidth: {
type: String,
required: false,
default: '100%'
},
readonly: {
type: Boolean,
required: false,
default: true
},
disabled: {
type: Boolean,
required: false,
default: false
}
},
data () {
return {
numValue: this.value
}
},
computed: {
isAddDisabled () {
if (!Number.isFinite(this.max)) {
return false
}
return this.numValue + this.step > this.max
},
isSubtractDisabled () {
if (!Number.isFinite(this.min)) {
return false
}
return this.numValue - this.step < this.min
},
inputWidthStyle () {
if (this.inputWidth) {
return {
'max-width': this.inputWidth,
'width': this.inputWidth
}
}
}
},
watch: {
value (newValue, oldValue) {
if (newValue !== oldValue) {
this.numValue = newValue
}
}
},
methods: {
add () {
if (this.isAddDisabled) {
return
}
this.numValue = Number(this.numValue) + this.step
this.numValue = Number(this.numValue.toFixed(this.precision))
this.$emit('input', this.numValue)
},
subtract () {
if (this.isSubtractDisabled) {
return
}
this.numValue = Number(this.numValue) - this.step
this.numValue = Number(this.numValue.toFixed(this.precision))
this.$emit('input', this.numValue)
},
onInput (event) {
const inputValue = event.target.value
let parsed = Number(parseFloat(inputValue).toFixed(this.precision))
if (isNaN(parsed)) {
if (typeof this.min === 'number') {
parsed = this.min
} else {
parsed = 0
}
}
if (typeof this.min === 'number' && parsed < this.min) {
parsed = this.min
}
if (typeof this.max === 'number' && parsed > this.max) {
parsed = this.max
}
if (inputValue[0] === '0' && !inputValue.includes('.')) {
event.target.value = parsed
}
this.numValue = parsed
this.$emit('input', this.numValue)
}
}
}
</script>
<style scoped>
.inputSpinner {
background-color: white !important;
text-align: center;
}
</style>
================================================
FILE: docs/.vuepress/components/Pagination.vue
================================================
<template>
<div class="card mt-3">
<div class="card-body">
<v-table
:data="users"
:currentPage.sync="currentPage"
:pageSize="5"
@totalPagesChanged="totalPages = $event"
>
<thead slot="head">
<th>Name</th>
<th>Age</th>
<th>State</th>
<th>Registered</th>
</thead>
<tbody slot="body" slot-scope="{displayData}">
<tr v-for="row in displayData" :key="row.guid">
<td>{{ row.name }}</td>
<td>{{ row.age }}</td>
<td>{{ row.address.state }}</td>
<td>{{ row.registered }}</td>
</tr>
</tbody>
</v-table>
<smart-pagination
:currentPage.sync="currentPage"
:totalPages="totalPages"
/>
</div>
</div>
</template>
<script>
import users from './data.json'
import VTable from '../../../src/VTable.vue'
import SmartPagination from '../../../src/SmartPagination.vue'
export default {
name: 'Pagination',
components: { VTable, SmartPagination },
data: () => ({
users: users.slice(0, 30),
currentPage: 1,
totalPages: 0
})
}
</script>
<style>
@import "~bootstrap/dist/css/bootstrap.css";
</style>
================================================
FILE: docs/.vuepress/components/Selection.vue
================================================
<template>
<div class="card mt-3">
<div class="card-body">
<v-table
class="table-hover"
ref="usersTable"
:data="users"
selectionMode="multiple"
selectedClass="table-info"
@selectionChanged="selectedRows = $event"
>
<thead slot="head">
<th>Name</th>
<th>Age</th>
<th>State</th>
<th>Registered</th>
</thead>
<tbody slot="body" slot-scope="{displayData}">
<v-tr
v-for="row in displayData"
:key="row.guid"
:row="row"
>
<td>{{ row.name }}</td>
<td>{{ row.age }}</td>
<td>{{ row.address.state }}</td>
<td>{{ row.registered }}</td>
</v-tr>
</tbody>
</v-table>
<strong>Selected:</strong>
<div v-if="selectedRows.length === 0" class="text-muted">No Rows Selected</div>
<ul>
<li v-for="selected in selectedRows">
{{ selected.name }}
</li>
</ul>
</div>
</div>
</template>
<script>
import users from './data.json'
import VTable from '../../../src/VTable.vue'
import VTr from '../../../src/VTr.vue'
export default {
name: 'Selection',
components: { VTable, VTr },
data: () => ({
users: users.slice(0, 10),
selectedRows: []
})
}
</script>
<style>
@import "~bootstrap/dist/css/bootstrap.css";
</style>
================================================
FILE: docs/.vuepress/components/SelectionApi.vue
================================================
<template>
<div class="card mt-3">
<div class="card-body">
<button
class="btn btn-outline-secondary"
@click="selectAll"
>
Select All
</button>
<button
class="btn btn-outline-secondary"
@click="deselectAll"
>
Deselect All
</button>
<v-table
ref="usersTable"
:data="users"
selectionMode="multiple"
selectedClass="table-info"
@selectionChanged="selectedRows = $event"
>
<thead slot="head">
<th>Name</th>
<th>Age</th>
<th>State</th>
<th>Registered</th>
</thead>
<tbody slot="body" slot-scope="{displayData}">
<v-tr
v-for="row in displayData"
:key="row.guid"
:row="row"
>
<td>{{ row.name }}</td>
<td>{{ row.age }}</td>
<td>{{ row.address.state }}</td>
<td>{{ row.registered }}</td>
</v-tr>
</tbody>
</v-table>
<strong>Selected:</strong>
<div v-if="selectedRows.length === 0" class="text-muted">No Rows Selected</div>
<ul>
<li v-for="selected in selectedRows">
{{ selected.name }}
</li>
</ul>
</div>
</div>
</template>
<script>
import users from './data.json'
import VTable from '../../../src/VTable.vue'
import VTr from '../../../src/VTr.vue'
export default {
name: 'Sorting',
components: { VTable, VTr },
data: () => ({
users: users.slice(0, 10),
selectedRows: []
}),
mounted () {
this.$refs.usersTable.selectRows(this.users.slice(1, 4))
},
methods: {
selectAll () {
this.$refs.usersTable.selectAll()
},
deselectAll () {
this.$refs.usersTable.deselectAll()
}
}
}
</script>
<style>
@import "~bootstrap/dist/css/bootstrap.css";
</style>
================================================
FILE: docs/.vuepress/components/Sorting.vue
================================================
<template>
<div class="card mt-3">
<div class="card-body">
<v-table
:data="users"
>
<thead slot="head">
<v-th :sortKey="nameLength" defaultSort="desc">Name</v-th>
<v-th sortKey="age">Age</v-th>
<v-th sortKey="address.state">State</v-th>
<v-th :customSort="dateSort">Registered</v-th>
</thead>
<tbody slot="body" slot-scope="{displayData}">
<tr v-for="row in displayData" :key="row.guid">
<td>{{ row.name }}</td>
<td>{{ row.age }}</td>
<td>{{ row.address.state }}</td>
<td>{{ row.registered }}</td>
</tr>
</tbody>
</v-table>
</div>
</div>
</template>
<script>
import users from './data.json'
import VTable from '../../../src/VTable.vue'
import VTh from '../../../src/VTh.vue'
export default {
name: 'Sorting',
components: { VTable, VTh },
data: () => ({
users: users.slice(0, 10)
}),
methods: {
nameLength (row) {
return row.name.length
},
dateSort(a, b) {
let date1 = new Date(a.registered).getTime();
let date2 = new Date(b.registered).getTime();
return date1 - date2;
}
}
}
</script>
<style>
@import "~bootstrap/dist/css/bootstrap.css";
</style>
================================================
FILE: docs/.vuepress/components/SortingFA.vue
================================================
<template>
<div class="card mt-3">
<div class="card-body">
<v-table
:data="users"
:hideSortIcons="true"
>
<thead slot="head">
<v-th sortKey="name" defaultSort="asc">Name</v-th>
<v-th sortKey="age">Age</v-th>
<v-th sortKey="address.state">State</v-th>
</thead>
<tbody slot="body" slot-scope="{displayData}">
<tr v-for="row in displayData" :key="row.guid">
<td>{{ row.name }}</td>
<td>{{ row.age }}</td>
<td>{{ row.address.state }}</td>
</tr>
</tbody>
</v-table>
</div>
</div>
</template>
<script>
import users from './data.json'
import VTable from '../../../src/VTable.vue'
import VTh from '../../../src/VTh.vue'
export default {
name: 'SortingFA',
components: { VTable, VTh },
data: () => ({
users: users.slice(20, 30)
})
}
</script>
<style>
@import "~@fortawesome/fontawesome-free/css/all.css";
@import "~bootstrap/dist/css/bootstrap.css";
.vt-sort:before{
font-family: "Font Awesome 5 Free";
padding-right: 0.5em;
width: 1.28571429em;
display: inline-block;
text-align: center;
}
.vt-sortable:before{
content: "\f338";
}
.vt-asc:before{
content: "\f160";
}
.vt-desc:before{
content: "\f161";
}
</style>
================================================
FILE: docs/.vuepress/components/TheBasics.vue
================================================
<template>
<v-table
:data="data"
>
<thead slot="head">
<th>Name</th>
<th>Age</th>
<th>Email</th>
<th>Address</th>
</thead>
<tbody slot="body" slot-scope="{displayData}">
<tr v-for="row in displayData" :key="row.guid">
<td>{{ row.name }}</td>
<td>{{ row.age }}</td>
<td>{{ row.email }}</td>
<td>{{ row.address.street }}, {{ row.address.city }} {{ row.address.state}}</td>
</tr>
</tbody>
</v-table>
</template>
<script>
import data from './data.json'
import VTable from '../../../src/VTable.vue'
export default {
name: 'TheBasics',
components: { VTable },
data: () => ({
data: data.slice(0, 5)
})
}
</script>
================================================
FILE: docs/.vuepress/components/data.json
================================================
[
{
"_id": "57ef9cd8e22df324d77c4f07",
"index": 0,
"guid": "9bc8e89d-658c-47cf-acc3-b0e225ddb549",
"isActive": false,
"balance": "$1,029.59",
"picture": "http://placehold.it/32x32",
"age": 25,
"eyeColor": "green",
"name": "Deana Lindsay",
"gender": "female",
"company": "TERRAGO",
"email": "deanalindsay@terrago.com",
"phone": "+1 (858) 506-2166",
"address": {
"street": "268 Garnet Street",
"city": "Chicopee",
"state": "Pennsylvania"
},
"registered": "2015-10-30",
"tags": [
"reprehenderit",
"duis",
"mollit",
"eiusmod",
"incididunt",
"nisi",
"sunt"
]
},
{
"_id": "57ef9cd8392af802937a0974",
"index": 1,
"guid": "1436aae5-4540-474a-8bad-4ca6b9c903ac",
"isActive": true,
"balance": "$1,302.22",
"picture": "http://placehold.it/32x32",
"age": 39,
"eyeColor": "blue",
"name": "Wyatt Kline",
"gender": "male",
"company": "MEDMEX",
"email": "wyattkline@medmex.com",
"phone": "+1 (827) 579-2502",
"address": {
"street": "234 Irwin Street",
"city": "Irwin",
"state": "Maine"
},
"registered": "2014-06-17",
"tags": [
"aute",
"laboris",
"sit",
"voluptate",
"magna",
"voluptate",
"occaecat"
]
},
{
"_id": "57ef9cd865c7f5203dc5e126",
"index": 2,
"guid": "2bc5aad6-2172-4041-a949-b31729290f25",
"isActive": true,
"balance": "$3,066.01",
"picture": "http://placehold.it/32x32",
"age": 29,
"eyeColor": "green",
"name": "Harmon Huber",
"gender": "male",
"company": "SNIPS",
"email": "harmonhuber@snips.com",
"phone": "+1 (931) 482-3018",
"address": {
"street": "913 Gerritsen Avenue",
"city": "Nash",
"state": "American Samoa"
},
"registered": "2016-01-25",
"tags": [
"do",
"voluptate",
"id",
"do",
"occaecat",
"minim",
"tempor"
]
},
{
"_id": "57ef9cd8594cc4bc18a121f7",
"index": 3,
"guid": "5d48aae2-9e3c-434e-a835-3abdab56e240",
"isActive": false,
"balance": "$3,574.33",
"picture": "http://placehold.it/32x32",
"age": 29,
"eyeColor": "green",
"name": "Penny Maddox",
"gender": "female",
"company": "BALUBA",
"email": "pennymaddox@baluba.com",
"phone": "+1 (873) 552-2338",
"address": {
"street": "218 Agate Court",
"city": "Sandston",
"state": "Oregon"
},
"registered": "2016-01-08",
"tags": [
"anim",
"aliqua",
"consequat",
"tempor",
"excepteur",
"est",
"enim"
]
},
{
"_id": "57ef9cd846b7cd74053c10c9",
"index": 4,
"guid": "3c4a0399-68c9-4741-8c77-0e7e0dd9ed00",
"isActive": true,
"balance": "$1,363.39",
"picture": "http://placehold.it/32x32",
"age": 39,
"eyeColor": "brown",
"name": "Morgan Gomez",
"gender": "male",
"company": "AFFLUEX",
"email": "morgangomez@affluex.com",
"phone": "+1 (976) 466-3779",
"address": {
"street": "632 Highland Avenue",
"city": "Tuttle",
"state": "Connecticut"
},
"registered": "2014-04-10",
"tags": [
"duis",
"deserunt",
"id",
"nostrud",
"mollit",
"consequat",
"ea"
]
},
{
"_id": "57ef9cd8d429bf34a0c2dc36",
"index": 5,
"guid": "d321b26f-f8ce-461e-9673-5b9497aacea7",
"isActive": false,
"balance": "$1,469.54",
"picture": "http://placehold.it/32x32",
"age": 35,
"eyeColor": "green",
"name": "Beck Mckay",
"gender": "male",
"company": "GEOLOGIX",
"email": "beckmckay@geologix.com",
"phone": "+1 (879) 477-3341",
"address": {
"street": "936 Woodpoint Road",
"city": "Wakulla",
"state": "Mississippi"
},
"registered": "2016-05-06",
"tags": [
"non",
"cillum",
"culpa",
"irure",
"nulla",
"non",
"occaecat"
]
},
{
"_id": "57ef9cd86866468dc1c20447",
"index": 6,
"guid": "c097a185-98ee-4bdd-a6ec-a3e5e40801be",
"isActive": true,
"balance": "$3,125.22",
"picture": "http://placehold.it/32x32",
"age": 39,
"eyeColor": "blue",
"name": "Massey Carlson",
"gender": "male",
"company": "EARTHWAX",
"email": "masseycarlson@earthwax.com",
"phone": "+1 (871) 471-2647",
"address": {
"street": "278 Chapel Street",
"city": "Taycheedah",
"state": "Hawaii"
},
"registered": "2014-10-22",
"tags": [
"ullamco",
"fugiat",
"consequat",
"nostrud",
"aliqua",
"consequat",
"fugiat"
]
},
{
"_id": "57ef9cd83f55c1d078fc6cfd",
"index": 7,
"guid": "29e53cbb-6353-44a3-a2c8-e7b3176d00af",
"isActive": false,
"balance": "$3,663.46",
"picture": "http://placehold.it/32x32",
"age": 33,
"eyeColor": "blue",
"name": "Hill Hale",
"gender": "male",
"company": "CALCU",
"email": "hillhale@calcu.com",
"phone": "+1 (877) 598-2610",
"address": {
"street": "618 Newport Street",
"city": "Deercroft",
"state": "Colorado"
},
"registered": "2016-04-18",
"tags": [
"nostrud",
"duis",
"Lorem",
"ex",
"elit",
"labore",
"in"
]
},
{
"_id": "57ef9cd8f8863a277e1c2055",
"index": 8,
"guid": "955a6cd5-b73c-4a6c-9280-76cbc4e232b2",
"isActive": false,
"balance": "$2,451.20",
"picture": "http://placehold.it/32x32",
"age": 26,
"eyeColor": "blue",
"name": "Stokes Hurst",
"gender": "male",
"company": "DATAGEN",
"email": "stokeshurst@datagen.com",
"phone": "+1 (897) 537-2718",
"address": {
"street": "146 Conover Street",
"city": "Dahlen",
"state": "North Dakota"
},
"registered": "2016-01-30",
"tags": [
"est",
"eu",
"anim",
"eiusmod",
"exercitation",
"commodo",
"nulla"
]
},
{
"_id": "57ef9cd86b62971bb96f7603",
"index": 9,
"guid": "239a3301-1dae-4ef8-ae30-7e92a84c78a2",
"isActive": false,
"balance": "$1,753.67",
"picture": "http://placehold.it/32x32",
"age": 20,
"eyeColor": "green",
"name": "Cain Knapp",
"gender": "male",
"company": "NAMEBOX",
"email": "cainknapp@namebox.com",
"phone": "+1 (873) 435-3377",
"address": {
"street": "460 Bridgewater Street",
"city": "Manchester",
"state": "Michigan"
},
"registered": "2016-01-04",
"tags": [
"fugiat",
"non",
"adipisicing",
"id",
"incididunt",
"do",
"enim"
]
},
{
"_id": "57ef9cd8431bccda13eea218",
"index": 10,
"guid": "b5671cc8-2776-4180-9cab-2f26fd38c720",
"isActive": false,
"balance": "$1,619.16",
"picture": "http://placehold.it/32x32",
"age": 22,
"eyeColor": "brown",
"name": "Ramirez Valdez",
"gender": "male",
"company": "SOLAREN",
"email": "ramirezvaldez@solaren.com",
"phone": "+1 (820) 465-2360",
"address": {
"street": "932 Battery Avenue",
"city": "Iola",
"state": "Virginia"
},
"registered": "2014-01-27",
"tags": [
"minim",
"aliqua",
"culpa",
"dolore",
"excepteur",
"minim",
"sit"
]
},
{
"_id": "57ef9cd8e7379045d04a0540",
"index": 11,
"guid": "19b39b01-74e1-495c-b7a6-a88d66420fba",
"isActive": false,
"balance": "$1,638.78",
"picture": "http://placehold.it/32x32",
"age": 39,
"eyeColor": "brown",
"name": "Alisha Michael",
"gender": "female",
"company": "STUCCO",
"email": "alishamichael@stucco.com",
"phone": "+1 (800) 497-2778",
"address": {
"street": "184 Coffey Street",
"city": "Trucksville",
"state": "New York"
},
"registered": "2015-03-31",
"tags": [
"irure",
"dolore",
"minim",
"excepteur",
"aliquip",
"officia",
"fugiat"
]
},
{
"_id": "57ef9cd8059fd44b460b7c38",
"index": 12,
"guid": "2a3054d0-a2ea-4ee2-a2f5-f5d1d0573b11",
"isActive": true,
"balance": "$1,156.43",
"picture": "http://placehold.it/32x32",
"age": 21,
"eyeColor": "brown",
"name": "Shepard Russo",
"gender": "male",
"company": "MAXIMIND",
"email": "shepardrusso@maximind.com",
"phone": "+1 (810) 417-3060",
"address": {
"street": "450 Stryker Court",
"city": "Eagleville",
"state": "South Dakota"
},
"registered": "2014-12-17",
"tags": [
"qui",
"irure",
"pariatur",
"eiusmod",
"tempor",
"ullamco",
"aliquip"
]
},
{
"_id": "57ef9cd8afc877c47ac02b6a",
"index": 13,
"guid": "7ad0c0a8-59ca-457f-9b41-fd1ae83b4f60",
"isActive": false,
"balance": "$2,376.34",
"picture": "http://placehold.it/32x32",
"age": 39,
"eyeColor": "blue",
"name": "Joyner Cohen",
"gender": "male",
"company": "LYRIA",
"email": "joynercohen@lyria.com",
"phone": "+1 (962) 595-2903",
"address": {
"street": "304 Calder Place",
"city": "Choctaw",
"state": "Louisiana"
},
"registered": "2016-04-30",
"tags": [
"in",
"sunt",
"cupidatat",
"nostrud",
"laboris",
"culpa",
"consequat"
]
},
{
"_id": "57ef9cd8aa516857d091b670",
"index": 14,
"guid": "47be1205-5fde-4a1b-ba80-5ab56e659c32",
"isActive": false,
"balance": "$1,837.95",
"picture": "http://placehold.it/32x32",
"age": 32,
"eyeColor": "blue",
"name": "Doreen Vincent",
"gender": "female",
"company": "VANTAGE",
"email": "doreenvincent@vantage.com",
"phone": "+1 (972) 484-2153",
"address": {
"street": "520 Greenpoint Avenue",
"city": "Chapin",
"state": "Rhode Island"
},
"registered": "2015-12-11",
"tags": [
"cillum",
"aute",
"Lorem",
"occaecat",
"eu",
"voluptate",
"ea"
]
},
{
"_id": "57ef9cd8778023bd5ebc71df",
"index": 15,
"guid": "3efedc36-6bcd-4f78-b64e-11f6c3374ff0",
"isActive": true,
"balance": "$3,412.92",
"picture": "http://placehold.it/32x32",
"age": 34,
"eyeColor": "green",
"name": "Felicia Osborne",
"gender": "female",
"company": "XELEGYL",
"email": "feliciaosborne@xelegyl.com",
"phone": "+1 (884) 448-3923",
"address": {
"street": "895 Luquer Street",
"city": "Bluffview",
"state": "Alabama"
},
"registered": "2015-04-11",
"tags": [
"elit",
"veniam",
"consectetur",
"sunt",
"ipsum",
"incididunt",
"adipisicing"
]
},
{
"_id": "57ef9cd862a19b82a2b57c2a",
"index": 16,
"guid": "2a08992f-b1ca-4a75-9b4c-df4ba052f3b2",
"isActive": false,
"balance": "$2,053.01",
"picture": "http://placehold.it/32x32",
"age": 35,
"eyeColor": "green",
"name": "Dillon Schmidt",
"gender": "male",
"company": "KEEG",
"email": "dillonschmidt@keeg.com",
"phone": "+1 (869) 554-3796",
"address": {
"street": "344 Sullivan Place",
"city": "Bawcomville",
"state": "Palau"
},
"registered": "2016-07-04",
"tags": [
"mollit",
"duis",
"pariatur",
"velit",
"Lorem",
"Lorem",
"anim"
]
},
{
"_id": "57ef9cd8b2b36698294cfb64",
"index": 17,
"guid": "9644a804-0216-49c7-8625-d15a3bfb31c5",
"isActive": false,
"balance": "$2,507.00",
"picture": "http://placehold.it/32x32",
"age": 40,
"eyeColor": "blue",
"name": "Melody Miranda",
"gender": "female",
"company": "ENTHAZE",
"email": "melodymiranda@enthaze.com",
"phone": "+1 (849) 464-3618",
"address": {
"street": "418 Whitney Avenue",
"city": "Sanders",
"state": "Indiana"
},
"registered": "2014-04-22",
"tags": [
"consequat",
"commodo",
"magna",
"aute",
"occaecat",
"ea",
"minim"
]
},
{
"_id": "57ef9cd8410de3de4768210a",
"index": 18,
"guid": "3859f0d4-cc87-4d6b-b101-e867b8e5c0cc",
"isActive": false,
"balance": "$2,737.68",
"picture": "http://placehold.it/32x32",
"age": 36,
"eyeColor": "green",
"name": "Wilkerson Melendez",
"gender": "male",
"company": "TECHMANIA",
"email": "wilkersonmelendez@techmania.com",
"phone": "+1 (952) 481-3063",
"address": {
"street": "818 Ocean Parkway",
"city": "Kerby",
"state": "Iowa"
},
"registered": "2014-11-14",
"tags": [
"sit",
"cupidatat",
"aliqua",
"in",
"officia",
"pariatur",
"ex"
]
},
{
"_id": "57ef9cd8bda3e38af810f58a",
"index": 19,
"guid": "90af70fd-1241-4d07-b3f0-727f7987a60c",
"isActive": true,
"balance": "$1,028.57",
"picture": "http://placehold.it/32x32",
"age": 37,
"eyeColor": "blue",
"name": "Rivera Velazquez",
"gender": "male",
"company": "GLEAMINK",
"email": "riveravelazquez@gleamink.com",
"phone": "+1 (973) 597-3283",
"address": {
"street": "330 Monaco Place",
"city": "Grahamtown",
"state": "Northern Mariana Islands"
},
"registered": "2016-01-10",
"tags": [
"excepteur",
"est",
"occaecat",
"nulla",
"nostrud",
"eu",
"ipsum"
]
},
{
"_id": "57ef9cd8351d9509288c8469",
"index": 20,
"guid": "c9f0b7e4-f7ec-44b5-ba6b-d9991bb3404b",
"isActive": true,
"balance": "$2,456.41",
"picture": "http://placehold.it/32x32",
"age": 35,
"eyeColor": "blue",
"name": "Reese Velez",
"gender": "male",
"company": "POLARIA",
"email": "reesevelez@polaria.com",
"phone": "+1 (950) 549-3805",
"address": {
"street": "571 Wilson Avenue",
"city": "Hannasville",
"state": "Tennessee"
},
"registered": "2016-07-24",
"tags": [
"veniam",
"occaecat",
"irure",
"consequat",
"labore",
"laboris",
"Lorem"
]
},
{
"_id": "57ef9cd8014e14851bee0084",
"index": 21,
"guid": "6691378e-7cb9-4bff-ac93-a79fadfa49d1",
"isActive": false,
"balance": "$3,892.36",
"picture": "http://placehold.it/32x32",
"age": 33,
"eyeColor": "blue",
"name": "Kayla Morgan",
"gender": "female",
"company": "VIDTO",
"email": "kaylamorgan@vidto.com",
"phone": "+1 (976) 499-2436",
"address": {
"street": "188 Lancaster Avenue",
"city": "Bowden",
"state": "District Of Columbia"
},
"registered": "2015-12-16",
"tags": [
"minim",
"proident",
"sunt",
"nostrud",
"adipisicing",
"cupidatat",
"veniam"
]
},
{
"_id": "57ef9cd810b76a3d60fb0b9b",
"index": 22,
"guid": "26e927c7-63c5-4e82-bdc0-7832cb890156",
"isActive": false,
"balance": "$3,668.66",
"picture": "http://placehold.it/32x32",
"age": 20,
"eyeColor": "brown",
"name": "Benson Snyder",
"gender": "male",
"company": "ZOSIS",
"email": "bensonsnyder@zosis.com",
"phone": "+1 (923) 595-3264",
"address": {
"street": "294 Douglass Street",
"city": "Hiseville",
"state": "Marshall Islands"
},
"registered": "2016-07-02",
"tags": [
"sint",
"amet",
"cillum",
"exercitation",
"Lorem",
"amet",
"ad"
]
},
{
"_id": "57ef9cd8d6e3b40316003054",
"index": 23,
"guid": "248e3d33-6827-461b-a92e-408a4927d5dc",
"isActive": true,
"balance": "$2,253.39",
"picture": "http://placehold.it/32x32",
"age": 32,
"eyeColor": "green",
"name": "Strickland Andrews",
"gender": "male",
"company": "COMBOT",
"email": "stricklandandrews@combot.com",
"phone": "+1 (969) 560-2376",
"address": {
"street": "677 Albemarle Road",
"city": "Thornport",
"state": "Florida"
},
"registered": "2015-06-15",
"tags": [
"culpa",
"nulla",
"excepteur",
"incididunt",
"nulla",
"mollit",
"occaecat"
]
},
{
"_id": "57ef9cd8bbf66c3b7757b773",
"index": 24,
"guid": "303c36fd-14e0-49f7-b6de-01c6cf866313",
"isActive": false,
"balance": "$1,248.11",
"picture": "http://placehold.it/32x32",
"age": 21,
"eyeColor": "blue",
"name": "Castro Hanson",
"gender": "male",
"company": "FURNITECH",
"email": "castrohanson@furnitech.com",
"phone": "+1 (901) 512-2724",
"address": {
"street": "932 Herkimer Court",
"city": "Cavalero",
"state": "Texas"
},
"registered": "2014-12-07",
"tags": [
"est",
"aliqua",
"enim",
"ex",
"Lorem",
"nostrud",
"eiusmod"
]
},
{
"_id": "57ef9cd8aa22d92e5c82b2f6",
"index": 25,
"guid": "fd997458-836f-4d24-bbab-a0aa26e68c2d",
"isActive": false,
"balance": "$1,264.43",
"picture": "http://placehold.it/32x32",
"age": 21,
"eyeColor": "blue",
"name": "Iris Nielsen",
"gender": "female",
"company": "ROUGHIES",
"email": "irisnielsen@roughies.com",
"phone": "+1 (916) 468-3250",
"address": {
"street": "824 Doscher Street",
"city": "Goodville",
"state": "South Carolina"
},
"registered": "2015-08-01",
"tags": [
"sint",
"voluptate",
"adipisicing",
"id",
"eiusmod",
"veniam",
"excepteur"
]
},
{
"_id": "57ef9cd8e00712c4924d6413",
"index": 26,
"guid": "f6b3c048-e563-4fa6-a28f-d358c5b4f74c",
"isActive": true,
"balance": "$1,462.23",
"picture": "http://placehold.it/32x32",
"age": 23,
"eyeColor": "blue",
"name": "Dionne Boyer",
"gender": "female",
"company": "PETIGEMS",
"email": "dionneboyer@petigems.com",
"phone": "+1 (826) 510-3961",
"address": {
"street": "483 Prospect Avenue",
"city": "Forestburg",
"state": "Nevada"
},
"registered": "2016-09-09",
"tags": [
"ipsum",
"id",
"eiusmod",
"laboris",
"incididunt",
"deserunt",
"anim"
]
},
{
"_id": "57ef9cd8a25729d7ccb55e01",
"index": 27,
"guid": "376807be-1fdc-4d6a-961c-84b313b81f70",
"isActive": true,
"balance": "$1,384.08",
"picture": "http://placehold.it/32x32",
"age": 33,
"eyeColor": "green",
"name": "Cooke Alford",
"gender": "male",
"company": "PROXSOFT",
"email": "cookealford@proxsoft.com",
"phone": "+1 (850) 486-2468",
"address": {
"street": "870 High Street",
"city": "Reinerton",
"state": "Delaware"
},
"registered": "2016-03-25",
"tags": [
"id",
"amet",
"irure",
"eiusmod",
"excepteur",
"nostrud",
"qui"
]
},
{
"_id": "57ef9cd8998c6d6498836a72",
"index": 28,
"guid": "fe5ad1f3-c898-4635-bbd8-6c3185e9415f",
"isActive": false,
"balance": "$1,658.18",
"picture": "http://placehold.it/32x32",
"age": 24,
"eyeColor": "brown",
"name": "Goff Lamb",
"gender": "male",
"company": "GLOBOIL",
"email": "gofflamb@globoil.com",
"phone": "+1 (996) 400-2491",
"address": {
"street": "361 Interborough Parkway",
"city": "Dodge",
"state": "Kansas"
},
"registered": "2016-09-12",
"tags": [
"nisi",
"cupidatat",
"aliquip",
"non",
"et",
"sint",
"enim"
]
},
{
"_id": "57ef9cd82e0778dff1ee3579",
"index": 29,
"guid": "ea213edd-47dd-4fd5-be36-27e34fa7fba1",
"isActive": false,
"balance": "$2,355.96",
"picture": "http://placehold.it/32x32",
"age": 21,
"eyeColor": "brown",
"name": "Barry Ramsey",
"gender": "male",
"company": "ACCUPHARM",
"email": "barryramsey@accupharm.com",
"phone": "+1 (841) 406-3771",
"address": {
"street": "220 Henderson Walk",
"city": "Gerber",
"state": "California"
},
"registered": "2014-11-15",
"tags": [
"cillum",
"esse",
"aliqua",
"do",
"irure",
"eu",
"eu"
]
},
{
"_id": "57ef9cd8b7a3f7141cd6787a",
"index": 30,
"guid": "05148b5f-e9b2-4697-a25c-b9de7473f4bc",
"isActive": true,
"balance": "$1,262.77",
"picture": "http://placehold.it/32x32",
"age": 31,
"eyeColor": "brown",
"name": "Aguilar Koch",
"gender": "male",
"company": "EMERGENT",
"email": "aguilarkoch@emergent.com",
"phone": "+1 (829) 518-3177",
"address": {
"street": "929 Himrod Street",
"city": "Leeper",
"state": "New Jersey"
},
"registered": "2015-07-12",
"tags": [
"ipsum",
"eu",
"cillum",
"aute",
"labore",
"ea",
"eiusmod"
]
},
{
"_id": "57ef9cd83f6b95641c5e3c75",
"index": 31,
"guid": "27304437-1b44-4ad8-81e8-710b4b8345ff",
"isActive": false,
"balance": "$3,601.82",
"picture": "http://placehold.it/32x32",
"age": 24,
"eyeColor": "brown",
"name": "Oconnor Hopper",
"gender": "male",
"company": "FUELWORKS",
"email": "oconnorhopper@fuelworks.com",
"phone": "+1 (891) 493-3016",
"address": {
"street": "426 Montieth Street",
"city": "Hegins",
"state": "Wyoming"
},
"registered": "2015-07-24",
"tags": [
"minim",
"voluptate",
"elit",
"incididunt",
"ut",
"fugiat",
"occaecat"
]
},
{
"_id": "57ef9cd8369d6e4b835f3fcd",
"index": 32,
"guid": "721a77b7-b011-47f4-a810-e7f29e7006c9",
"isActive": true,
"balance": "$1,915.04",
"picture": "http://placehold.it/32x32",
"age": 20,
"eyeColor": "green",
"name": "Daugherty White",
"gender": "male",
"company": "BOLAX",
"email": "daughertywhite@bolax.com",
"phone": "+1 (826) 594-2129",
"address": {
"street": "449 Lester Court",
"city": "Joes",
"state": "Massachusetts"
},
"registered": "2014-10-13",
"tags": [
"Lorem",
"enim",
"nostrud",
"nisi",
"consequat",
"ullamco",
"laborum"
]
},
{
"_id": "57ef9cd86305c595658af48b",
"index": 33,
"guid": "1cfc8de2-d36c-4ef1-9e40-f088cf75eb82",
"isActive": true,
"balance": "$2,866.63",
"picture": "http://placehold.it/32x32",
"age": 40,
"eyeColor": "blue",
"name": "Kane Mclaughlin",
"gender": "male",
"company": "BUZZNESS",
"email": "kanemclaughlin@buzzness.com",
"phone": "+1 (832) 531-3366",
"address": {
"street": "661 Apollo Street",
"city": "Crayne",
"state": "Kentucky"
},
"registered": "2014-07-08",
"tags": [
"laboris",
"quis",
"enim",
"ea",
"cupidatat",
"nisi",
"ipsum"
]
},
{
"_id": "57ef9cd8306a49b3a4173669",
"index": 34,
"guid": "0bf330e0-c370-466d-9a5e-4506272fd132",
"isActive": false,
"balance": "$1,185.64",
"picture": "http://placehold.it/32x32",
"age": 40,
"eyeColor": "brown",
"name": "Berry Moore",
"gender": "male",
"company": "MOBILDATA",
"email": "berrymoore@mobildata.com",
"phone": "+1 (868) 455-2610",
"address": {
"street": "655 Eldert Street",
"city": "Camino",
"state": "Arizona"
},
"registered": "2015-10-08",
"tags": [
"labore",
"dolore",
"labore",
"proident",
"aute",
"amet",
"quis"
]
},
{
"_id": "57ef9cd80ae90356bd04b760",
"index": 35,
"guid": "2be39caa-9fcc-4616-a7b5-f72e3d3cbdaf",
"isActive": true,
"balance": "$1,185.60",
"picture": "http://placehold.it/32x32",
"age": 26,
"eyeColor": "blue",
"name": "Gwendolyn Hunt",
"gender": "female",
"company": "DIGIAL",
"email": "gwendolynhunt@digial.com",
"phone": "+1 (853) 562-3237",
"address": {
"street": "597 Hart Street",
"city": "Kenmar",
"state": "Maryland"
},
"registered": "2015-10-27",
"tags": [
"Lorem",
"officia",
"adipisicing",
"velit",
"tempor",
"eiusmod",
"labore"
]
},
{
"_id": "57ef9cd884359a9538967098",
"index": 36,
"guid": "63ab3d9c-8138-4f09-baf9-6d76c5ba573a",
"isActive": true,
"balance": "$2,469.48",
"picture": "http://placehold.it/32x32",
"age": 23,
"eyeColor": "green",
"name": "Willie Garrett",
"gender": "female",
"company": "BALOOBA",
"email": "williegarrett@balooba.com",
"phone": "+1 (843) 425-3145",
"address": {
"street": "456 Maple Avenue",
"city": "Hackneyville",
"state": "Illinois"
},
"registered": "2014-02-09",
"tags": [
"est",
"aliqua",
"in",
"excepteur",
"ut",
"eu",
"fugiat"
]
},
{
"_id": "57ef9cd8db282c5f4f1448d4",
"index": 37,
"guid": "bc8ce4f1-6cc4-4925-9046-3a5e91f858bc",
"isActive": true,
"balance": "$1,067.32",
"picture": "http://placehold.it/32x32",
"age": 29,
"eyeColor": "green",
"name": "Christina Good",
"gender": "female",
"company": "APPLIDECK",
"email": "christinagood@applideck.com",
"phone": "+1 (851) 462-3278",
"address": {
"street": "650 Arlington Place",
"city": "Wheaton",
"state": "West Virginia"
},
"registered": "2015-01-28",
"tags": [
"cupidatat",
"ad",
"consequat",
"proident",
"ex",
"officia",
"adipisicing"
]
},
{
"_id": "57ef9cd88452ab36aa70f054",
"index": 38,
"guid": "4db3de26-08ae-40a0-9dc6-cd9aad7bdc25",
"isActive": false,
"balance": "$3,016.01",
"picture": "http://placehold.it/32x32",
"age": 35,
"eyeColor": "brown",
"name": "Hooper Blair",
"gender": "male",
"company": "ANARCO",
"email": "hooperblair@anarco.com",
"phone": "+1 (877) 468-2384",
"address": {
"street": "535 Bancroft Place",
"city": "National",
"state": "Alaska"
},
"registered": "2015-02-24",
"tags": [
"non",
"laboris",
"aliqua",
"velit",
"officia",
"aliqua",
"in"
]
},
{
"_id": "57ef9cd8fd30953315b376ce",
"index": 39,
"guid": "2537139a-70e6-438b-980f-a43ee251db50",
"isActive": true,
"balance": "$1,259.80",
"picture": "http://placehold.it/32x32",
"age": 26,
"eyeColor": "brown",
"name": "Wilson Whitley",
"gender": "male",
"company": "BUNGA",
"email": "wilsonwhitley@bunga.com",
"phone": "+1 (881) 545-2408",
"address": {
"street": "536 Delevan Street",
"city": "Clara",
"state": "Oklahoma"
},
"registered": "2015-05-15",
"tags": [
"sunt",
"officia",
"officia",
"est",
"Lorem",
"elit",
"exercitation"
]
},
{
"_id": "57ef9cd88d9237233ce5a603",
"index": 40,
"guid": "407cc2cb-7eb0-4614-97d8-85a2b0ca2c55",
"isActive": false,
"balance": "$1,954.73",
"picture": "http://placehold.it/32x32",
"age": 28,
"eyeColor": "blue",
"name": "Hilda Osborn",
"gender": "female",
"company": "SLOGANAUT",
"email": "hildaosborn@sloganaut.com",
"phone": "+1 (975) 466-3813",
"address": {
"street": "412 Tudor Terrace",
"city": "Brecon",
"state": "Federated States Of Micronesia"
},
"registered": "2016-06-23",
"tags": [
"anim",
"culpa",
"fugiat",
"minim",
"occaecat",
"sit",
"excepteur"
]
},
{
"_id": "57ef9cd839131d7d2e092514",
"index": 41,
"guid": "0eb0fb97-2de9-4815-a533-7ac084b94863",
"isActive": false,
"balance": "$2,347.37",
"picture": "http://placehold.it/32x32",
"age": 29,
"eyeColor": "blue",
"name": "Denise Griffin",
"gender": "female",
"company": "ATOMICA",
"email": "denisegriffin@atomica.com",
"phone": "+1 (858) 538-2320",
"address": {
"street": "415 Catherine Street",
"city": "Onton",
"state": "Vermont"
},
"registered": "2014-06-17",
"tags": [
"qui",
"sunt",
"commodo",
"tempor",
"exercitation",
"nisi",
"exercitation"
]
},
{
"_id": "57ef9cd89974d5066113c5eb",
"index": 42,
"guid": "03ba8cb5-6ad5-47f9-a0f1-28939ade5983",
"isActive": true,
"balance": "$3,867.30",
"picture": "http://placehold.it/32x32",
"age": 26,
"eyeColor": "green",
"name": "Neal Castro",
"gender": "male",
"company": "OVATION",
"email": "nealcastro@ovation.com",
"phone": "+1 (967) 436-2454",
"address": {
"street": "119 Harrison Avenue",
"city": "Volta",
"state": "Idaho"
},
"registered": "2014-06-22",
"tags": [
"Lorem",
"cupidatat",
"et",
"quis",
"voluptate",
"incididunt",
"dolore"
]
},
{
"_id": "57ef9cd88d860c6070fafef1",
"index": 43,
"guid": "e14b2f74-0aaa-4218-940d-5f7fb2551151",
"isActive": true,
"balance": "$3,011.45",
"picture": "http://placehold.it/32x32",
"age": 22,
"eyeColor": "blue",
"name": "Rich Montoya",
"gender": "male",
"company": "ORBAXTER",
"email": "richmontoya@orbaxter.com",
"phone": "+1 (866) 530-3039",
"address": {
"street": "134 Blake Court",
"city": "Kaka",
"state": "Arkansas"
},
"registered": "2016-06-21",
"tags": [
"sunt",
"cillum",
"enim",
"occaecat",
"minim",
"reprehenderit",
"nulla"
]
},
{
"_id": "57ef9cd8e093c420b70f6787",
"index": 44,
"guid": "1cabe364-180d-4147-88c7-2154241c22f2",
"isActive": false,
"balance": "$2,413.74",
"picture": "http://placehold.it/32x32",
"age": 37,
"eyeColor": "brown",
"name": "Galloway Wilcox",
"gender": "male",
"company": "KENEGY",
"email": "gallowaywilcox@kenegy.com",
"phone": "+1 (806) 596-3339",
"address": {
"street": "131 Kay Court",
"city": "Bannock",
"state": "Ohio"
},
"registered": "2014-12-04",
"tags": [
"consequat",
"elit",
"in",
"mollit",
"nostrud",
"amet",
"irure"
]
},
{
"_id": "57ef9cd8a07f64e3585adaa4",
"index": 45,
"guid": "9a0ba82c-3a59-4b8c-a735-16f57c4e5b7b",
"isActive": true,
"balance": "$1,419.98",
"picture": "http://placehold.it/32x32",
"age": 25,
"eyeColor": "green",
"name": "Estela Johnson",
"gender": "female",
"company": "LUNCHPOD",
"email": "estelajohnson@lunchpod.com",
"phone": "+1 (857) 457-3968",
"address": {
"street": "909 Cheever Place",
"city": "Clarence",
"state": "Missouri"
},
"registered": "2015-06-19",
"tags": [
"sint",
"velit",
"ut",
"minim",
"ad",
"proident",
"labore"
]
},
{
"_id": "57ef9cd863d1b1a4acb74988",
"index": 46,
"guid": "8154d3fc-2b77-4107-ab34-42bb23f470e6",
"isActive": false,
"balance": "$2,049.10",
"picture": "http://placehold.it/32x32",
"age": 27,
"eyeColor": "green",
"name": "Lorrie Huffman",
"gender": "female",
"company": "SPHERIX",
"email": "lorriehuffman@spherix.com",
"phone": "+1 (855) 535-2317",
"address": {
"street": "221 Essex Street",
"city": "Tedrow",
"state": "Washington"
},
"registered": "2016-09-27",
"tags": [
"consequat",
"excepteur",
"ipsum",
"ipsum",
"esse",
"cillum",
"qui"
]
},
{
"_id": "57ef9cd8e0a6bad7cf9ec1dc",
"index": 47,
"guid": "d44f213d-7164-4aba-b5e1-5e8646853e41",
"isActive": true,
"balance": "$1,958.21",
"picture": "http://placehold.it/32x32",
"age": 37,
"eyeColor": "brown",
"name": "Celia Burnett",
"gender": "female",
"company": "ENORMO",
"email": "celiaburnett@enormo.com",
"phone": "+1 (834) 438-2214",
"address": {
"street": "315 Louisiana Avenue",
"city": "Wauhillau",
"state": "New Hampshire"
},
"registered": "2014-02-21",
"tags": [
"officia",
"cupidatat",
"ullamco",
"velit",
"ad",
"ea",
"nostrud"
]
},
{
"_id": "57ef9cd8e0617ebaf2a76998",
"index": 48,
"guid": "9d09a10a-6b16-44c5-bd72-20ee3188432c",
"isActive": false,
"balance": "$3,027.42",
"picture": "http://placehold.it/32x32",
"age": 31,
"eyeColor": "brown",
"name": "Delaney Hamilton",
"gender": "male",
"company": "ACCUSAGE",
"email": "delaneyhamilton@accusage.com",
"phone": "+1 (862) 425-3630",
"address": {
"street": "407 Oceanview Avenue",
"city": "Waikele",
"state": "New Mexico"
},
"registered": "2014-07-06",
"tags": [
"aliquip",
"fugiat",
"cillum",
"id",
"excepteur",
"ad",
"non"
]
},
{
"_id": "57ef9cd831ecdff3ea6aba9e",
"index": 49,
"guid": "f5212db5-dfe2-426e-a4e0-83331831bbe8",
"isActive": false,
"balance": "$2,970.07",
"picture": "http://placehold.it/32x32",
"age": 39,
"eyeColor": "green",
"name": "Elma Baldwin",
"gender": "female",
"company": "XANIDE",
"email": "elmabaldwin@xanide.com",
"phone": "+1 (991) 445-3729",
"address": {
"street": "817 Loring Avenue",
"city": "Jenkinsville",
"state": "Virgin Islands"
},
"registered": "2015-03-13",
"tags": [
"aute",
"nisi",
"laborum",
"incididunt",
"non",
"eiusmod",
"ad"
]
},
{
"_id": "57ef9cd86e789f3db59cc997",
"index": 50,
"guid": "c24777cb-e9c6-451a-b70b-9ffeefb35ee0",
"isActive": false,
"balance": "$1,072.96",
"picture": "http://placehold.it/32x32",
"age": 24,
"eyeColor": "green",
"name": "Roxie Butler",
"gender": "female",
"company": "ISOLOGICA",
"email": "roxiebutler@isologica.com",
"phone": "+1 (919) 442-2920",
"address": {
"street": "512 Dewey Place",
"city": "Lacomb",
"state": "Montana"
},
"registered": "2015-05-26",
"tags": [
"qui",
"labore",
"non",
"quis",
"id",
"quis",
"anim"
]
},
{
"_id": "57ef9cd882f83000536be05a",
"index": 51,
"guid": "2798139e-b8ec-409e-aa87-613149059143",
"isActive": false,
"balance": "$3,771.00",
"picture": "http://placehold.it/32x32",
"age": 38,
"eyeColor": "blue",
"name": "Margie Davenport",
"gender": "female",
"company": "MALATHION",
"email": "margiedavenport@malathion.com",
"phone": "+1 (976) 567-3933",
"address": {
"street": "241 Lloyd Court",
"city": "Stockwell",
"state": "Puerto Rico"
},
"registered": "2016-04-05",
"tags": [
"et",
"reprehenderit",
"dolor",
"consequat",
"sit",
"qui",
"mollit"
]
},
{
"_id": "57ef9cd8199466c0f80eaef0",
"index": 52,
"guid": "0d8ea36b-df98-4b07-952a-4cbac7c92c64",
"isActive": false,
"balance": "$2,047.61",
"picture": "http://placehold.it/32x32",
"age": 31,
"eyeColor": "brown",
"name": "Zelma Macias",
"gender": "female",
"company": "ECLIPSENT",
"email": "zelmamacias@eclipsent.com",
"phone": "+1 (893) 522-3991",
"address": {
"street": "812 Morgan Avenue",
"city": "Moquino",
"state": "Minnesota"
},
"registered": "2014-03-14",
"tags": [
"quis",
"aliquip",
"occaecat",
"ut",
"duis",
"est",
"Lorem"
]
},
{
"_id": "57ef9cd8e332fa4683ed0ef2",
"index": 53,
"guid": "992c8935-6934-4b46-be34-bb81f596d0cc",
"isActive": true,
"balance": "$3,363.81",
"picture": "http://placehold.it/32x32",
"age": 32,
"eyeColor": "blue",
"name": "Dominique Best",
"gender": "female",
"company": "SNOWPOKE",
"email": "dominiquebest@snowpoke.com",
"phone": "+1 (941) 596-2718",
"address": {
"street": "851 Turnbull Avenue",
"city": "Ruffin",
"state": "Wisconsin"
},
"registered": "2016-03-04",
"tags": [
"magna",
"velit",
"Lorem",
"culpa",
"mollit",
"adipisicing",
"eu"
]
},
{
"_id": "57ef9cd8b65cc853f277c21a",
"index": 54,
"guid": "8f690f7b-cdc8-4c84-b928-44fe3e7bc4b4",
"isActive": false,
"balance": "$3,463.40",
"picture": "http://placehold.it/32x32",
"age": 39,
"eyeColor": "blue",
"name": "Meyers Navarro",
"gender": "male",
"company": "VALREDA",
"email": "meyersnavarro@valreda.com",
"phone": "+1 (969) 596-2487",
"address": {
"street": "692 Lake Street",
"city": "Marienthal",
"state": "Guam"
},
"registered": "2015-06-22",
"tags": [
"mollit",
"laboris",
"deserunt",
"sit",
"excepteur",
"ut",
"ut"
]
},
{
"_id": "57ef9cd88d03578a7c15a598",
"index": 55,
"guid": "46cd7eac-1353-4bd2-99cc-2a9b5264a966",
"isActive": false,
"balance": "$1,911.65",
"picture": "http://placehold.it/32x32",
"age": 24,
"eyeColor": "green",
"name": "Holland Hess",
"gender": "male",
"company": "IDEALIS",
"email": "hollandhess@idealis.com",
"phone": "+1 (867) 599-2608",
"address": {
"street": "934 Stoddard Place",
"city": "Frizzleburg",
"state": "Nebraska"
},
"registered": "2015-02-19",
"tags": [
"dolor",
"sint",
"ipsum",
"ex",
"et",
"aute",
"elit"
]
},
{
"_id": "57ef9cd8a1f6180aec4821e5",
"index": 56,
"guid": "eb23120a-a47d-499d-a3a5-9a5cc0d40233",
"isActive": true,
"balance": "$3,547.64",
"picture": "http://placehold.it/32x32",
"age": 25,
"eyeColor": "brown",
"name": "Mckay Fields",
"gender": "male",
"company": "SLAX",
"email": "mckayfields@slax.com",
"phone": "+1 (802) 483-2694",
"address": {
"street": "595 Jerome Street",
"city": "Oneida",
"state": "Utah"
},
"registered": "2014-11-18",
"tags": [
"reprehenderit",
"pariatur",
"aliquip",
"ullamco",
"veniam",
"labore",
"velit"
]
},
{
"_id": "57ef9cd8c473ecd605a3b6ca",
"index": 57,
"guid": "84c4fe9c-6917-49ce-a7a4-71ba9a7cba69",
"isActive": true,
"balance": "$1,731.31",
"picture": "http://placehold.it/32x32",
"age": 39,
"eyeColor": "blue",
"name": "Melanie Austin",
"gender": "female",
"company": "ANDRYX",
"email": "melanieaustin@andryx.com",
"phone": "+1 (994) 412-2994",
"address": {
"street": "533 Saratoga Avenue",
"city": "Bangor",
"state": "Georgia"
},
"registered": "2014-03-09",
"tags": [
"mollit",
"minim",
"in",
"in",
"Lorem",
"magna",
"non"
]
},
{
"_id": "57ef9cd891a61ccfd176aeef",
"index": 58,
"guid": "80295d78-5ce6-4dfe-b945-50e2ce602b44",
"isActive": true,
"balance": "$2,992.55",
"picture": "http://placehold.it/32x32",
"age": 25,
"eyeColor": "blue",
"name": "Peck Schneider",
"gender": "male",
"company": "COFINE",
"email": "peckschneider@cofine.com",
"phone": "+1 (913) 428-3201",
"address": {
"street": "441 Harbor Lane",
"city": "Tolu",
"state": "Pennsylvania"
},
"registered": "2016-05-29",
"tags": [
"Lorem",
"voluptate",
"ipsum",
"dolore",
"aliquip",
"ad",
"consequat"
]
},
{
"_id": "57ef9cd8e58b17c583b460a7",
"index": 59,
"guid": "0b18fc0c-f18f-46e3-845a-cd7b15c55865",
"isActive": false,
"balance": "$1,502.40",
"picture": "http://placehold.it/32x32",
"age": 23,
"eyeColor": "green",
"name": "Mcclain Rhodes",
"gender": "male",
"company": "RODEOCEAN",
"email": "mcclainrhodes@rodeocean.com",
"phone": "+1 (842) 446-2464",
"address": {
"street": "374 Amersfort Place",
"city": "Romeville",
"state": "Maine"
},
"registered": "2014-03-19",
"tags": [
"dolor",
"eiusmod",
"ad",
"voluptate",
"ea",
"deserunt",
"et"
]
},
{
"_id": "57ef9cd8724c03c5ad8e4a8e",
"index": 60,
"guid": "326dce0c-4b47-499b-9489-36acd6dadff8",
"isActive": true,
"balance": "$1,747.38",
"picture": "http://placehold.it/32x32",
"age": 25,
"eyeColor": "blue",
"name": "Cervantes Bright",
"gender": "male",
"company": "VALPREAL",
"email": "cervantesbright@valpreal.com",
"phone": "+1 (873) 587-2025",
"address": {
"street": "132 Bliss Terrace",
"city": "Winesburg",
"state": "American Samoa"
},
"registered": "2016-03-18",
"tags": [
"magna",
"dolore",
"veniam",
"veniam",
"sint",
"quis",
"id"
]
},
{
"_id": "57ef9cd8b398ed2934e4a722",
"index": 61,
"guid": "4b744dd7-3c86-4bce-a3c8-9eb4359ceed5",
"isActive": false,
"balance": "$3,697.92",
"picture": "http://placehold.it/32x32",
"age": 34,
"eyeColor": "blue",
"name": "Hardin Pittman",
"gender": "male",
"company": "SKINSERVE",
"email": "hardinpittman@skinserve.com",
"phone": "+1 (887) 454-3837",
"address": {
"street": "617 Robert Street",
"city": "Stevens",
"state": "Oregon"
},
"registered": "2015-04-25",
"tags": [
"sit",
"anim",
"proident",
"minim",
"adipisicing",
"duis",
"cillum"
]
},
{
"_id": "57ef9cd89a9aa77a634b1a47",
"index": 62,
"guid": "971cfb58-9d8e-4a06-ab1c-c6bc120c1865",
"isActive": false,
"balance": "$1,121.41",
"picture": "http://placehold.it/32x32",
"age": 35,
"eyeColor": "blue",
"name": "Anna Ortega",
"gender": "female",
"company": "PHUEL",
"email": "annaortega@phuel.com",
"phone": "+1 (887) 422-3308",
"address": {
"street": "809 Vandervoort Place",
"city": "Gibsonia",
"state": "Connecticut"
},
"registered": "2016-06-11",
"tags": [
"veniam",
"commodo",
"incididunt",
"commodo",
"ut",
"irure",
"aliqua"
]
},
{
"_id": "57ef9cd8d4ea52c7b0fce734",
"index": 63,
"guid": "3e62c184-2c7d-4620-be08-4a10ac9f1bce",
"isActive": true,
"balance": "$3,080.98",
"picture": "http://placehold.it/32x32",
"age": 37,
"eyeColor": "brown",
"name": "Juliana Rios",
"gender": "female",
"company": "FIREWAX",
"email": "julianarios@firewax.com",
"phone": "+1 (978) 409-2654",
"address": {
"street": "893 Heyward Street",
"city": "Fedora",
"state": "Mississippi"
},
"registered": "2015-07-21",
"tags": [
"sit",
"sint",
"eu",
"quis",
"do",
"mollit",
"magna"
]
},
{
"_id": "57ef9cd8faa17dc8818830e9",
"index": 64,
"guid": "d3de53e8-6012-43ea-8c6b-1546739f9455",
"isActive": true,
"balance": "$3,162.09",
"picture": "http://placehold.it/32x32",
"age": 24,
"eyeColor": "green",
"name": "Allison Collier",
"gender": "male",
"company": "ISOLOGICS",
"email": "allisoncollier@isologics.com",
"phone": "+1 (961) 458-2654",
"address": {
"street": "987 Story Street",
"city": "Fairview",
"state": "Hawaii"
},
"registered": "2014-04-11",
"tags": [
"nulla",
"labore",
"anim",
"tempor",
"enim",
"aliqua",
"culpa"
]
},
{
"_id": "57ef9cd8414f04a3dafce7ef",
"index": 65,
"guid": "d45fc8d9-2f09-42cf-918c-5d9c9b9049fb",
"isActive": false,
"balance": "$2,228.33",
"picture": "http://placehold.it/32x32",
"age": 33,
"eyeColor": "brown",
"name": "Laurel Raymond",
"gender": "female",
"company": "FREAKIN",
"email": "laurelraymond@freakin.com",
"phone": "+1 (845) 411-2269",
"address": {
"street": "270 Alice Court",
"city": "Bynum",
"state": "Colorado"
},
"registered": "2014-09-18",
"tags": [
"sit",
"dolore",
"eiusmod",
"dolor",
"aliquip",
"est",
"reprehenderit"
]
},
{
"_id": "57ef9cd8e744f8d49ef6d406",
"index": 66,
"guid": "8fd91938-f5a5-409d-8a97-bf4e683d76a1",
"isActive": true,
"balance": "$3,892.91",
"picture": "http://placehold.it/32x32",
"age": 38,
"eyeColor": "brown",
"name": "Althea Horn",
"gender": "female",
"company": "FILODYNE",
"email": "altheahorn@filodyne.com",
"phone": "+1 (950) 526-2536",
"address": {
"street": "666 Taylor Street",
"city": "Driftwood",
"state": "North Dakota"
},
"registered": "2016-01-13",
"tags": [
"cillum",
"fugiat",
"incididunt",
"sunt",
"proident",
"et",
"amet"
]
},
{
"_id": "57ef9cd80d3bd026a5b14c3d",
"index": 67,
"guid": "4a617fa7-dc5a-440a-ab0b-13f6e6ad34fe",
"isActive": false,
"balance": "$1,371.59",
"picture": "http://placehold.it/32x32",
"age": 22,
"eyeColor": "blue",
"name": "Potter Cannon",
"gender": "male",
"company": "MONDICIL",
"email": "pottercannon@mondicil.com",
"phone": "+1 (913) 431-3765",
"address": {
"street": "762 Drew Street",
"city": "Riceville",
"state": "Michigan"
},
"registered": "2015-08-31",
"tags": [
"velit",
"velit",
"ex",
"aliqua",
"consequat",
"esse",
"irure"
]
},
{
"_id": "57ef9cd8218b984cbcc2fcee",
"index": 68,
"guid": "ebe26092-53b1-4fd9-b48f-37f68a7008b9",
"isActive": true,
"balance": "$2,799.55",
"picture": "http://placehold.it/32x32",
"age": 23,
"eyeColor": "brown",
"name": "Alyson Calhoun",
"gender": "female",
"company": "ZEPITOPE",
"email": "alysoncalhoun@zepitope.com",
"phone": "+1 (820) 510-3098",
"address": {
"street": "804 Rodney Street",
"city": "Hatteras",
"state": "Virginia"
},
"registered": "2014-09-25",
"tags": [
"occaecat",
"officia",
"enim",
"eu",
"ullamco",
"eu",
"pariatur"
]
},
{
"_id": "57ef9cd88e11ec366ca17611",
"index": 69,
"guid": "2bb46e78-c42a-449c-93b2-204091ec8296",
"isActive": false,
"balance": "$3,754.62",
"picture": "http://placehold.it/32x32",
"age": 27,
"eyeColor": "brown",
"name": "Zimmerman Hudson",
"gender": "male",
"company": "OVERPLEX",
"email": "zimmermanhudson@overplex.com",
"phone": "+1 (810) 589-3217",
"address": {
"street": "710 Lombardy Street",
"city": "Tampico",
"state": "New York"
},
"registered": "2015-12-05",
"tags": [
"aute",
"Lorem",
"magna",
"ut",
"esse",
"tempor",
"voluptate"
]
},
{
"_id": "57ef9cd8c663c704beb3ba0f",
"index": 70,
"guid": "3fe808f0-0757-4862-93e1-033724b2d61e",
"isActive": false,
"balance": "$3,403.56",
"picture": "http://placehold.it/32x32",
"age": 38,
"eyeColor": "green",
"name": "Sharon Vargas",
"gender": "female",
"company": "SYNKGEN",
"email": "sharonvargas@synkgen.com",
"phone": "+1 (837) 452-3719",
"address": {
"street": "451 Eckford Street",
"city": "Wyano",
"state": "South Dakota"
},
"registered": "2015-11-19",
"tags": [
"consectetur",
"sint",
"ad",
"sunt",
"ea",
"velit",
"officia"
]
},
{
"_id": "57ef9cd8c23f68e4099957d1",
"index": 71,
"guid": "79f80e61-d92e-4b97-bea7-0016fb3fbcdc",
"isActive": false,
"balance": "$1,738.35",
"picture": "http://placehold.it/32x32",
"age": 22,
"eyeColor": "blue",
"name": "Herman House",
"gender": "male",
"company": "FRENEX",
"email": "hermanhouse@frenex.com",
"phone": "+1 (928) 579-3978",
"address": {
"street": "770 Wyckoff Avenue",
"city": "Trail",
"state": "Louisiana"
},
"registered": "2015-03-20",
"tags": [
"elit",
"voluptate",
"sint",
"laboris",
"et",
"nisi",
"ad"
]
},
{
"_id": "57ef9cd8f684a162c8263ed1",
"index": 72,
"guid": "3a82a0ae-ffaa-4833-a4c1-1947eaf2a22a",
"isActive": false,
"balance": "$3,451.10",
"picture": "http://placehold.it/32x32",
"age": 30,
"eyeColor": "green",
"name": "Lenore Hahn",
"gender": "female",
"company": "EXOSIS",
"email": "lenorehahn@exosis.com",
"phone": "+1 (917) 524-3275",
"address": {
"street": "838 Gold Street",
"city": "Watchtower",
"state": "Rhode Island"
},
"registered": "2015-05-28",
"tags": [
"id",
"proident",
"velit",
"consequat",
"non",
"duis",
"consectetur"
]
},
{
"_id": "57ef9cd8a220db71c82724b6",
"index": 73,
"guid": "3715858a-2b15-40b2-bd25-4c5f27f8a242",
"isActive": false,
"balance": "$2,007.02",
"picture": "http://placehold.it/32x32",
"age": 40,
"eyeColor": "brown",
"name": "Mcfadden Stark",
"gender": "male",
"company": "GEEKOLA",
"email": "mcfaddenstark@geekola.com",
"phone": "+1 (996) 480-2189",
"address": {
"street": "422 Livonia Avenue",
"city": "Allendale",
"state": "Alabama"
},
"registered": "2016-06-04",
"tags": [
"qui",
"consequat",
"mollit",
"magna",
"Lorem",
"officia",
"reprehenderit"
]
},
{
"_id": "57ef9cd8ce39d98040920aca",
"index": 74,
"guid": "015bb1b1-90c7-47a7-8439-1382355e2198",
"isActive": false,
"balance": "$2,762.45",
"picture": "http://placehold.it/32x32",
"age": 39,
"eyeColor": "brown",
"name": "Haney Schwartz",
"gender": "male",
"company": "GEOSTELE",
"email": "haneyschwartz@geostele.com",
"phone": "+1 (845) 531-2525",
"address": {
"street": "308 Poplar Avenue",
"city": "Wolcott",
"state": "Palau"
},
"registered": "2016-05-11",
"tags": [
"proident",
"enim",
"sunt",
"laborum",
"exercitation",
"dolore",
"cillum"
]
},
{
"_id": "57ef9cd890c774293d1b0fb4",
"index": 75,
"guid": "d073457d-da93-48aa-8c60-02d9d72ef8fb",
"isActive": false,
"balance": "$1,206.03",
"picture": "http://placehold.it/32x32",
"age": 34,
"eyeColor": "brown",
"name": "Bobbie Livingston",
"gender": "female",
"company": "CORPULSE",
"email": "bobbielivingston@corpulse.com",
"phone": "+1 (836) 495-2861",
"address": {
"street": "655 Laurel Avenue",
"city": "Eastmont",
"state": "Indiana"
},
"registered": "2015-09-02",
"tags": [
"proident",
"laboris",
"ut",
"exercitation",
"labore",
"proident",
"adipisicing"
]
},
{
"_id": "57ef9cd8e27b869852bce881",
"index": 76,
"guid": "8f740d74-174c-4383-bf25-d97b57bae01d",
"isActive": false,
"balance": "$2,399.50",
"picture": "http://placehold.it/32x32",
"age": 35,
"eyeColor": "brown",
"name": "Ball Talley",
"gender": "male",
"company": "GOKO",
"email": "balltalley@goko.com",
"phone": "+1 (994) 551-2840",
"address": {
"street": "271 Hanover Place",
"city": "Kipp",
"state": "Iowa"
},
"registered": "2016-04-07",
"tags": [
"nisi",
"consequat",
"labore",
"occaecat",
"magna",
"duis",
"ullamco"
]
},
{
"_id": "57ef9cd8971d5bf7c2ba6a2e",
"index": 77,
"guid": "df18fc2c-8480-4396-93ea-4db26c228f7e",
"isActive": true,
"balance": "$3,772.45",
"picture": "http://placehold.it/32x32",
"age": 20,
"eyeColor": "brown",
"name": "Tara Mcknight",
"gender": "female",
"company": "COMTENT",
"email": "taramcknight@comtent.com",
"phone": "+1 (932) 574-3393",
"address": {
"street": "619 Argyle Road",
"city": "Roulette",
"state": "Northern Mariana Islands"
},
"registered": "2015-11-21",
"tags": [
"reprehenderit",
"occaecat",
"culpa",
"anim",
"eiusmod",
"labore",
"fugiat"
]
},
{
"_id": "57ef9cd890abec4d44010eea",
"index": 78,
"guid": "74ee47e9-0a58-4a57-80d9-98571e613ae0",
"isActive": true,
"balance": "$1,971.96",
"picture": "http://placehold.it/32x32",
"age": 21,
"eyeColor": "brown",
"name": "Ebony Miller",
"gender": "female",
"company": "TETRATREX",
"email": "ebonymiller@tetratrex.com",
"phone": "+1 (953) 596-3683",
"address": {
"street": "451 Landis Court",
"city": "Elrama",
"state": "Tennessee"
},
"registered": "2015-05-06",
"tags": [
"reprehenderit",
"voluptate",
"ipsum",
"Lorem",
"ea",
"deserunt",
"duis"
]
},
{
"_id": "57ef9cd8e8a49916e0eef4a3",
"index": 79,
"guid": "1a5eb28e-ba61-4abf-95d1-cf69373ac673",
"isActive": true,
"balance": "$3,136.92",
"picture": "http://placehold.it/32x32",
"age": 35,
"eyeColor": "green",
"name": "Sexton James",
"gender": "male",
"company": "XLEEN",
"email": "sextonjames@xleen.com",
"phone": "+1 (886) 443-3743",
"address": {
"street": "565 Dobbin Street",
"city": "Hessville",
"state": "District Of Columbia"
},
"registered": "2016-03-23",
"tags": [
"ea",
"veniam",
"ut",
"aute",
"aliquip",
"ea",
"consectetur"
]
},
{
"_id": "57ef9cd89c8e57302ca806f7",
"index": 80,
"guid": "ad19546d-5b93-4dd0-a19f-53e4eb4d79aa",
"isActive": true,
"balance": "$2,174.53",
"picture": "http://placehold.it/32x32",
"age": 40,
"eyeColor": "brown",
"name": "Barr Espinoza",
"gender": "male",
"company": "PETICULAR",
"email": "barrespinoza@peticular.com",
"phone": "+1 (863) 570-3563",
"address": {
"street": "492 Underhill Avenue",
"city": "Gratton",
"state": "Marshall Islands"
},
"registered": "2016-08-18",
"tags": [
"ullamco",
"cillum",
"ullamco",
"exercitation",
"irure",
"esse",
"aliquip"
]
},
{
"_id": "57ef9cd84211c7823bf419cf",
"index": 81,
"guid": "9c75ad58-fbf9-49c3-a3cc-ec0e747cbc2c",
"isActive": true,
"balance": "$2,401.76",
"picture": "http://placehold.it/32x32",
"age": 29,
"eyeColor": "green",
"name": "Christa Lawson",
"gender": "female",
"company": "ENTALITY",
"email": "christalawson@entality.com",
"phone": "+1 (887) 449-2183",
"address": {
"street": "625 Grafton Street",
"city": "Cresaptown",
"state": "Florida"
},
"registered": "2014-03-09",
"tags": [
"in",
"ea",
"elit",
"sit",
"elit",
"laboris",
"labore"
]
},
{
"_id": "57ef9cd8deb2a837eee99361",
"index": 82,
"guid": "439349b3-b4e5-4e22-b074-d3d8d053986c",
"isActive": true,
"balance": "$3,718.44",
"picture": "http://placehold.it/32x32",
"age": 38,
"eyeColor": "blue",
"name": "Carla Gray",
"gender": "female",
"company": "NETPLODE",
"email": "carlagray@netplode.com",
"phone": "+1 (831) 547-3290",
"address": {
"street": "129 Mill Street",
"city": "Austinburg",
"state": "Texas"
},
"registered": "2014-03-23",
"tags": [
"non",
"tempor",
"eu",
"ipsum",
"voluptate",
"exercitation",
"officia"
]
},
{
"_id": "57ef9cd846d6320f8ee38cd6",
"index": 83,
"guid": "ff535f76-d5ed-4d07-9e5d-1426a6a649f1",
"isActive": true,
"balance": "$2,427.75",
"picture": "http://placehold.it/32x32",
"age": 23,
"eyeColor": "blue",
"name": "Aurora Banks",
"gender": "female",
"company": "BEDDER",
"email": "aurorabanks@bedder.com",
"phone": "+1 (936) 433-3077",
"address": {
"street": "498 Louis Place",
"city": "Canby",
"state": "South Carolina"
},
"registered": "2015-08-29",
"tags": [
"eu",
"mollit",
"et",
"mollit",
"laborum",
"incididunt",
"aute"
]
},
{
"_id": "57ef9cd893f2a82424d8f6ee",
"index": 84,
"guid": "8b007ae2-3474-4eff-bdad-17cd7c378867",
"isActive": true,
"balance": "$2,206.37",
"picture": "http://placehold.it/32x32",
"age": 28,
"eyeColor": "green",
"name": "Ruth Maxwell",
"gender": "female",
"company": "ZEAM",
"email": "ruthmaxwell@zeam.com",
"phone": "+1 (831) 492-3070",
"address": {
"street": "106 Utica Avenue",
"city": "Norfolk",
"state": "Nevada"
},
"registered": "2015-11-12",
"tags": [
"consectetur",
"aliqua",
"sit",
"magna",
"voluptate",
"esse",
"non"
]
},
{
"_id": "57ef9cd8f8c3e6032c08eab0",
"index": 85,
"guid": "b9872ffe-a6dc-4525-ac91-75b356a43ac2",
"isActive": false,
"balance": "$1,234.70",
"picture": "http://placehold.it/32x32",
"age": 20,
"eyeColor": "brown",
"name": "Mayer Edwards",
"gender": "male",
"company": "POWERNET",
"email": "mayeredwards@powernet.com",
"phone": "+1 (823) 411-3607",
"address": {
"street": "539 Jay Street",
"city": "Sena",
"state": "Delaware"
},
"registered": "2015-06-07",
"tags": [
"officia",
"occaecat",
"proident",
"Lorem",
"aute",
"et",
"sit"
]
},
{
"_id": "57ef9cd886542a15d66dc2a6",
"index": 86,
"guid": "a959a4e6-7e0c-41a8-912c-c1280609f486",
"isActive": true,
"balance": "$1,686.52",
"picture": "http://placehold.it/32x32",
"age": 23,
"eyeColor": "blue",
"name": "Caroline Bass",
"gender": "female",
"company": "ARTWORLDS",
"email": "carolinebass@artworlds.com",
"phone": "+1 (932) 419-2973",
"address": {
"street": "858 Pershing Loop",
"city": "Sidman",
"state": "Kansas"
},
"registered": "2015-04-24",
"tags": [
"ea",
"veniam",
"nulla",
"sint",
"proident",
"esse",
"nisi"
]
},
{
"_id": "57ef9cd8f1adf135eb8b5d4f",
"index": 87,
"guid": "1ae041da-fddc-4df5-8485-aff3eacbdcf4",
"isActive": false,
"balance": "$2,991.87",
"picture": "http://placehold.it/32x32",
"age": 27,
"eyeColor": "green",
"name": "Walsh Mccarthy",
"gender": "male",
"company": "FARMAGE",
"email": "walshmccarthy@farmage.com",
"phone": "+1 (921) 500-3805",
"address": {
"street": "930 Banker Street",
"city": "Nicholson",
"state": "California"
},
"registered": "2014-10-18",
"tags": [
"laboris",
"ad",
"adipisicing",
"esse",
"sit",
"consequat",
"magna"
]
},
{
"_id": "57ef9cd8e292916e52e972ec",
"index": 88,
"guid": "9207f1d2-870c-466e-a1ad-ed2b9af4ad65",
"isActive": true,
"balance": "$1,310.82",
"picture": "http://placehold.it/32x32",
"age": 37,
"eyeColor": "brown",
"name": "Natalie Bond",
"gender": "female",
"company": "GYNKO",
"email": "nataliebond@gynko.com",
"phone": "+1 (908) 587-2856",
"address": {
"street": "698 Madoc Avenue",
"city": "Outlook",
"state": "New Jersey"
},
"registered": "2016-03-30",
"tags": [
"aute",
"magna",
"eiusmod",
"est",
"officia",
"adipisicing",
"amet"
]
},
{
"_id": "57ef9cd8ff15e5092584605b",
"index": 89,
"guid": "1cf9890d-2d89-4a98-ac4c-488b47f33ec7",
"isActive": false,
"balance": "$1,664.41",
"picture": "http://placehold.it/32x32",
"age": 32,
"eyeColor": "green",
"name": "Watts Kramer",
"gender": "male",
"company": "MARKETOID",
"email": "wattskramer@marketoid.com",
"phone": "+1 (981) 572-3764",
"address": {
"street": "460 Caton Place",
"city": "Southmont",
"state": "Wyoming"
},
"registered": "2015-02-16",
"tags": [
"occaecat",
"sit",
"amet",
"non",
"sit",
"enim",
"ex"
]
},
{
"_id": "57ef9cd85d843de02021c7a4",
"index": 90,
"guid": "095bf361-65f5-42ae-aa51-1973d6ad6a2c",
"isActive": true,
"balance": "$1,981.37",
"picture": "http://placehold.it/32x32",
"age": 20,
"eyeColor": "green",
"name": "Iva Brady",
"gender": "female",
"company": "COMDOM",
"email": "ivabrady@comdom.com",
"phone": "+1 (942) 514-2261",
"address": {
"street": "525 Hoyts Lane",
"city": "Waterloo",
"state": "Massachusetts"
},
"registered": "2015-12-22",
"tags": [
"nisi",
"quis",
"adipisicing",
"et",
"quis",
"esse",
"consequat"
]
},
{
"_id": "57ef9cd8bf5b4c18438c890c",
"index": 91,
"guid": "bfb1f645-a63e-4fc0-9a94-a208396e32f7",
"isActive": true,
"balance": "$2,694.33",
"picture": "http://placehold.it/32x32",
"age": 39,
"eyeColor": "blue",
"name": "Estella Moses",
"gender": "female",
"company": "UNDERTAP",
"email": "estellamoses@undertap.com",
"phone": "+1 (999) 418-3031",
"address": {
"street": "127 Liberty Avenue",
"city": "Roy",
"state": "Kentucky"
},
"registered": "2014-08-10",
"tags": [
"ipsum",
"ea",
"eu",
"duis",
"enim",
"consequat",
"veniam"
]
},
{
"_id": "57ef9cd895135c80288cf57e",
"index": 92,
"guid": "6470c22d-0086-4acf-9fdf-7d1762a17e08",
"isActive": true,
"balance": "$3,995.47",
"picture": "http://placehold.it/32x32",
"age": 40,
"eyeColor": "brown",
"name": "Anthony Knight",
"gender": "male",
"company": "EQUICOM",
"email": "anthonyknight@equicom.com",
"phone": "+1 (873) 569-3224",
"address": {
"street": "654 Greene Avenue",
"city": "Chamizal",
"state": "Arizona"
},
"registered": "2014-02-08",
"tags": [
"Lorem",
"duis",
"incididunt",
"nulla",
"Lorem",
"dolor",
"adipisicing"
]
},
{
"_id": "57ef9cd8ca4056877bc2cb23",
"index": 93,
"guid": "fc8ea442-c629-42a1-87dd-c58609c0ebdb",
"isActive": false,
"balance": "$3,017.70",
"picture": "http://placehold.it/32x32",
"age": 40,
"eyeColor": "blue",
"name": "Russell Gilliam",
"gender": "male",
"company": "BLUEGRAIN",
"email": "russellgilliam@bluegrain.com",
"phone": "+1 (996) 465-2053",
"address": {
"street": "400 Columbia Place",
"city": "Summerset",
"state": "Maryland"
},
"registered": "2015-01-19",
"tags": [
"sit",
"eu",
"qui",
"Lorem",
"enim",
"aliquip",
"enim"
]
},
{
"_id": "57ef9cd8723a1463115cbf16",
"index": 94,
"guid": "282ddfb3-7c52-4732-80a2-be8c436ee0c9",
"isActive": true,
"balance": "$1,448.23",
"picture": "http://placehold.it/32x32",
"age": 35,
"eyeColor": "brown",
"name": "Mcmahon Reynolds",
"gender": "male",
"company": "PANZENT",
"email": "mcmahonreynolds@panzent.com",
"phone": "+1 (877) 512-2213",
"address": {
"street": "452 Kathleen Court",
"city": "Barronett",
"state": "Illinois"
},
"registered": "2015-04-03",
"tags": [
"qui",
"in",
"qui",
"laboris",
"et",
"nulla",
"id"
]
},
{
"_id": "57ef9cd8d99535859969e271",
"index": 95,
"guid": "ac088fe2-019a-4fd7-bd8c-5d21eeb5e731",
"isActive": false,
"balance": "$2,204.60",
"picture": "http://placehold.it/32x32",
"age": 20,
"eyeColor": "green",
"name": "Rosa Myers",
"gender": "female",
"company": "LUNCHPAD",
"email": "rosamyers@lunchpad.com",
"phone": "+1 (847) 423-3501",
"address": {
"street": "913 Trucklemans Lane",
"city": "Lynn",
"state": "West Virginia"
},
"registered": "2016-06-10",
"tags": [
"veniam",
"consectetur",
"dolor",
"quis",
"anim",
"do",
"sit"
]
},
{
"_id": "57ef9cd802b0ae83a4ed2495",
"index": 96,
"guid": "0507ff39-d0be-4f4d-afc0-01458a66d7a9",
"isActive": true,
"balance": "$3,655.93",
"picture": "http://placehold.it/32x32",
"age": 28,
"eyeColor": "brown",
"name": "Angeline Schroeder",
"gender": "female",
"company": "ZORROMOP",
"email": "angelineschroeder@zorromop.com",
"phone": "+1 (852) 421-2895",
"address": {
"street": "195 Sumpter Street",
"city": "Washington",
"state": "Alaska"
},
"registered": "2015-05-21",
"tags": [
"dolor",
"dolor",
"nulla",
"id",
"mollit",
"eiusmod",
"ut"
]
},
{
"_id": "57ef9cd82e8295c9e6edee99",
"index": 97,
"guid": "322617dc-ac2f-4076-bec9-7539ec959ad2",
"isActive": true,
"balance": "$1,675.25",
"picture": "http://placehold.it/32x32",
"age": 24,
"eyeColor": "brown",
"name": "Hall Atkinson",
"gender": "male",
"company": "UNIA",
"email": "hallatkinson@unia.com",
"phone": "+1 (847) 412-2997",
"address": {
"street": "864 Pierrepont Street",
"city": "Caroleen",
"state": "Oklahoma"
},
"registered": "2016-01-02",
"tags": [
"quis",
"id",
"quis",
"consequat",
"enim",
"esse",
"elit"
]
},
{
"_id": "57ef9cd8864bcc1330fa009c",
"index": 98,
"guid": "3c336f09-140a-423b-963d-e49a0864b214",
"isActive": false,
"balance": "$2,141.56",
"picture": "http://placehold.it/32x32",
"age": 28,
"eyeColor": "brown",
"name": "Paulette Sellers",
"gender": "female",
"company": "ARCTIQ",
"email": "paulettesellers@arctiq.com",
"phone": "+1 (937) 462-3359",
"address": {
"street": "130 Ovington Court",
"city": "Diaperville",
"state": "Federated States Of Micronesia"
},
"registered": "2014-10-28",
"tags": [
"aliqua",
"occaecat",
"ut",
"dolor",
"dolor",
"irure",
"pariatur"
]
},
{
"_id": "57ef9cd88bbbbcdd7e59618f",
"index": 99,
"guid": "6854d791-09f7-46c2-b66c-bcc0ba82d654",
"isActive": true,
"balance": "$1,683.05",
"picture": "http://placehold.it/32x32",
"age": 31,
"eyeColor": "brown",
"name": "Key Black",
"gender": "male",
"company": "MAINELAND",
"email": "keyblack@maineland.com",
"phone": "+1 (920) 536-2044",
"address": {
"street": "326 Melrose Street",
"city": "Denio",
"state": "Vermont"
},
"registered": "2016-08-06",
"tags": [
"tempor",
"enim",
"elit",
"cupidatat",
"ipsum",
"ipsum",
"in"
]
}
]
================================================
FILE: docs/.vuepress/config.js
================================================
module.exports = {
title: 'VueJs Smart Table',
base: '/vuejs-smart-table/',
description: 'Simple yet powerful Data Table for Vue',
configurewebpack: {
resolve: {
alias: {
'vue-smart-table': '../../src'
}
}
},
themeConfig: {
repo: 'tochoromero/vuejs-smart-table',
docsDir: 'docs',
editLinks: true,
sidebarDepth: 1,
sidebar: [
'/',
'the-basics/',
'filtering/',
'sorting/',
'pagination/',
'selection/'
]
}
}
================================================
FILE: docs/README.md
================================================
# Introduction
Vue Smart Table was created out of the need for a simple highly customizable data table plugin
that could take advantage of Vue's slots. It has no dependencies but Vue and because it
renders as a standard HTML table it is compatible with CSS Frameworks such as Bootstrap and Foundation.
Out of the box you will get filtering, column sorting, client side pagination and row selection.
## Installation
To install simply run
```
npm add vuejs-smart-table
```
or
```
yarn add vuejs-smart-table
```
Then in your `main.js`
```js
import SmartTable from 'vuejs-smart-table'
Vue.use(SmartTable)
```
This will globally register four Components: `v-table`, `v-th`, `v-tr` and `smart-pagination`
================================================
FILE: docs/filtering/README.md
================================================
# Filtering
Smart Table is only on charge of the actual row filtering based on the provided configuration.
The visual aspect of it are in your control, allowing you to use any UI Elements to interact while it frees you
from the actual filtering computation.
## Filters <Badge text="Property"/> <Badge text="filters: Object"/>
To enable filtering you need to provide the `filters` property on the `v-table` component.
The `filters` configuration object has the following form:
```js
{
name: {value: '', keys: ['name']}
}
```
The entry Key is just for you, so you can reference any of the filters by its name.
It is the entry Value Object what Smart Table will use to perform the filtering.
### value <Badge text="String" type="success"/>
This String is the value of the filter, you would normally bind it to the `v-model` of an input element. As you type,
the rows will be filtered.
Keep in mind that an empty `value` means there is no filter.
### keys <Badge text="Array" type="success"/>
This is an Array of Strings indicating what fields of each row the filter `value` will apply to.
You must provide at least one key. If more than one key is provided as long as one of the row fields matches the filter,
the row will be displayed.
#### Example
```js
import users from './users.json'
export default {
name: 'BasicFiltering',
data: () => ({
users,
filters: {
name: { value: '', keys: ['name'] }
}
})
}
```
```html
<template>
<div>
<label>Filter by Name:</label>
<input class="form-control" v-model="filters.name.value"/>
<v-table
:data="users"
:filters="filters"
>
<thead slot="head">
<th>Name</th>
<th>Age</th>
<th>Email</th>
<th>Address</th>
</thead>
<tbody slot="body" slot-scope="{displayData}">
<tr v-for="row in displayData" :key="row.guid">
<td>{{ row.name }}</td>
<td>{{ row.age }}</td>
<td>{{ row.email }}</td>
<td>
{{ row.address.street }},
{{ row.address.city }}
{{ row.address.state}}
</td>
</tr>
</tbody>
</v-table>
</div>
</template>
```
<BasicFiltering/>
## Custom Filters
You also have the option to provide a custom filter for more complex situations.
A Custom Filter is a function with two arguments: `filterValue` and `row`.
It should return `true` if the row should be displayed and `false` otherwise.
### custom <Badge text="Function" type="success"/>
To use a custom filter provide the filtering function on the `custom` property on the filter configuration object:
### value <Badge text="Any" type="success"/>
With custom filtering the `value` property is not limited to Strings, you can provide anything as the `value`,
it will just get passed along to your custom function.
#### Example
```js
<script>
import users from './users.json'
export default {
name: 'CustomFiltering',
data () {
return {
users,
filters: {
age: { value: { min: 21, max: 22 }, custom: this.ageFilter }
}
}
},
methods: {
ageFilter (filterValue, row) {
return row.age >= filterValue.min && row.age <= filterValue.max
}
}
}
</script>
```
```html
<template>
<div>
<label>Min Age:</label>
<InputSpinner
v-model="filters.age.value.min"
:min="0"
:max="filters.age.value.max"
/>
<label>Max Age:</label>
<InputSpinner
v-model="filters.age.value.max"
:min="filters.age.value.min"
:max="99"
/>
<v-table
:data="users"
:filters="filters"
>
<thead slot="head">
<th>Name</th>
<th>Age</th>
<th>Email</th>
<th>Address</th>
</thead>
<tbody slot="body" slot-scope="{displayData}">
<tr v-for="row in displayData" :key="row.guid">
<td>{{ row.name }}</td>
<td>{{ row.age }}</td>
<td>{{ row.email }}</td>
<td>
{{ row.address.street }},
{{ row.address.city }}
{{ row.address.state}}
</td>
</tr>
</tbody>
</v-table>
</div>
</template>
```
::: tip
Please think of the `InputSpinner` as a fancy `Input` with validation. The important bit is its `v-model`.
:::
<CustomFiltering/>
================================================
FILE: docs/pagination/README.md
================================================
# Pagination
Vue Smart Table supports client side pagination.
To enable it, you need to provide the `pageSize` and `currentPage` properties on the `v-table` Component.
## Page Size <Badge text="Property"/> <Badge text="pageSize: Number"/>
The `pageSize` property specify the amount of items each page should contain.
If this property is present, client side pagination will be enabled.
## Current Page <Badge text="Property"/> <Badge text="currentPage: Number"/> <Badge text="Sync"/>
The `currentPage` property indicates the current active page.
This property should be bound with a `sync` modifier, since the `v-table` itself
may update its value, e.g. if a new filter is applied and the amount of available items decreases
so the current active page is no longer valid.
::: tip
The `currentPage` property index starts at `1`,
this is to avoid confusion since visually the page links start at `1` not `0`.
:::
## Total pages <Badge text="Event"/> <Badge text="totalPagesChanged: Number"/>
The total amount of pages is calculated using the Total Items and the Page Size.
As the Total Items changes the Total Pages will also change and a `totalPagesChanged` event
will be emitted with the new amount as its payload.
## Total Items <Badge text="Event"/> <Badge text="totalItemsChanged: Number"/>
The total amount of items changes as the Filters change. When it changes `v-table` will emit a `totalItemsChanged` Event.
This event will also be emitted when the `v-table` mounts so it will have the right amount from the start.
## Pagination Controls
The pagination controls are handled outside of `v-table`, you can use whatever you want to control it, but we provide
a `SmartPagination` component so you can have it working out of the box.
The component requires the following properties:
### Current Page <Badge text="Property"/> <Badge text="currentPage: Number"/> <Badge text="Sync"/>
This should be the same `currentPage` property used for the `v-table` component
and it should also use the `sync` modifier, that way whenever either of them changes it the other one will be notified.
### Total Pages <Badge text="Property"/> <Badge text="totalPages: Number"/>
The `v-table` component emits a `totalPagesChanged` event, when the event happens we should save the event payload and
use it for the `totalPages` property on the `SmartPagination` component.
## Example
```html
<template>
<div>
<v-table
:data="users"
:currentPage.sync="currentPage"
:pageSize="5"
@totalPagesChanged="totalPages = $event"
>
<thead slot="head">
<th>Name</th>
<th>Age</th>
<th>State</th>
<th>Registered</th>
</thead>
<tbody slot="body" slot-scope="{displayData}">
<tr v-for="row in displayData" :key="row.guid">
<td>{{ row.name }}</td>
<td>{{ row.age }}</td>
<td>{{ row.address.state }}</td>
<td>{{ row.registered }}</td>
</tr>
</tbody>
</v-table>
<smart-pagination
:currentPage.sync="currentPage"
:totalPages="totalPages"
/>
</div>
</template>
```
```js
<script>
import users from './users.json'
export default {
name: 'Pagination',
data: () => ({
users,
currentPage: 1,
totalPages: 0
})
}
</script>
```
<Pagination/>
## Customizing Smart Pagination
Besides the `currentPage` and `totalPages` properties, there are many others used to configure the behaviour and look
and feel of the pagination controls.
### Hide Single Page <Badge text="Property"/> <Badge text="hideSinglePage: Boolean"/> <Badge text="default: true"/>
Determines whether or not we show the pagination controls when there is only a single page.
### Max Page Links <Badge text="Property"/> <Badge text="maxPageLinks: Number"/>
By default we will show every single page link, but you can use the `maxPageLinks` property to limit the amount of visible links.
### Boundary Links <Badge text="Property"/> <Badge text="boundaryLinks: Boolean"/> <Badge text="default: false"/>
Determines whether or not we should show two links to navigate to the First and Last page.
### First Text <Badge text="Property"/> <Badge text="firstText: String"/> <Badge text="default: First"/>
Specify the text for the First Page link.
### Last Text <Badge text="Property"/> <Badge text="lastText: String"/> <Badge text="default: Last"/>
Specify the text for the Last Page link.
### Direction Links <Badge text="Property"/> <Badge text="hideSinglePage: Boolean"/> <Badge text="default: true"/>
Determines whether or not we should have direction links to navigate back and forth between pages.
### CSS Customization
The HTML structure for the Smart Pagination component is as follows:
```html
<nav class="smart-pagination">
<ul class="pagination">
<li class="page-item">
<a class="page-link">1</a>
<a class="page-link">2</a>
<a class="page-link">3</a>
</li>
</ul>
</nav>
```
This structure is compatible with Bootstrap's Pagination. But you can easily customize it with your own Styles.
================================================
FILE: docs/selection/README.md
================================================
# Row Selection
## Table Row <Badge text="Component"/> <Badge text="v-tr"/>
To enable row selection you need to use the `v-tr` component. It only has one property: `row`
### Row <Badge text="Property"/> <Badge text="row: Object"/> <Badge text="Required"/>
You must provide the `row` property with the current Object for the row.
## Selection Options
You can configure the Selection Mode and the Selected Class in the `v-table` component.
### Selection Mode <Badge text="Property"/> <Badge text="selectionMode: String"/> <Badge text="default: single"/>
By default the selection mode is `single`, meaning only one row at a time can be selected.
You can use the `selectionMode` property in the `v-table` component to set it to `multiple`, so multiple rows
can be selected.
### Selected Class <Badge text="Property"/> <Badge text="selectedClass: String"/> <Badge text="default: vt-selected"/>
When a row is selected a class is added to the `tr` element, by default it is `vt-selected` byt you can change it to
something else with the `selectedClass` property in the `v-table` component.
## Selection Changed <Badge text="Event"/> <Badge text="selectionChanged: Array"/>
When the selected items changes the `v-table` component will emit a `selectionChanged` event with the
list of selected items as its payload.
## Example
```html
<template>
<div>
<v-table
class="table-hover"
:data="users"
selectionMode="multiple"
selectedClass="table-info"
@selectionChanged="selectedRows = $event"
>
<thead slot="head">
<th>Name</th>
<th>Age</th>
<th>State</th>
<th>Registered</th>
</thead>
<tbody slot="body" slot-scope="{displayData}">
<v-tr
v-for="row in displayData"
:key="row.guid"
:row="row"
>
<td>{{ row.name }}</td>
<td>{{ row.age }}</td>
<td>{{ row.address.state }}</td>
<td>{{ row.registered }}</td>
</v-tr>
</tbody>
</v-table>
<strong>Selected:</strong>
<div v-if="selectedRows.length === 0" class="text-muted">No Rows Selected</div>
<ul>
<li v-for="selected in selectedRows">
{{ selected.name }}
</li>
</ul>
</div>
</template>
```
```js
<script>
import users from './users.json'
export default {
name: 'Selection',
data: () => ({
users,
selectedRows: []
})
}
</script>
```
<Selection/>
================================================
FILE: docs/sorting/README.md
================================================
# Sorting
To enable column sorting, instead of using vanilla `th` elements we will use `v-th` Components for the columns
that will allow sorting.
## Table Header <Badge text="Component"/> <Badge text="v-th"/>
The `v-th` component renders to a regular `th` element but it allows you to sort the table, it has three properties:
`sortKey`, `customSort` and `defaultSort`.
### Sort Key <Badge text="Propery"/> <Badge text="sortKey: String | Function"/>
The `sortKey` property is used to get the Row value we will sort by it can either be a `String` or a `Function`.
#### String
As a `String`, it represents the path to the property in the Row we want to sort. You can even use nested paths.
```html
<thead slot="head">
<v-th sortKey="name">Name</v-th>
<v-th sortKey="address.state">State</v-th>
</thead>
```
#### Function
If you instead pass a `Function`, we will call it with the current `row` as a parameter and expect to get back
the value used for sorting.
```html
<thead slot="head">
<v-th :sortKey="nameLength">Name</v-th>
</thead>
```
```js
methods: {
nameLength (row) {
return `row.name.length`
}
}
```
Once we have used the `key` property to get the column values, we will compare them.
If the values are number we will just compare them by subtraction.
Otherwise we will call `toString()` on them and compare them with `localCompare`.
### Custom Sort <Badge text="Property"/> <Badge text="customSort: Function"/>
In some cases you need more control over sorting,
for instance if you have a complex object or your sorting depends in two or more values.
For those instances instead of providing a `key` property you can use the `custom` property to provide a sorting function.
The function will receive the 2 rows being compared and a third parameter with the sort order
where `1` represents ascending and `-1` represents descending.
The function needs to return `1` if the first row is greater, `-1` if the second row is greater
or `0` if they are the same.
```html
<thead slot="head">
<v-th :customSort="dateSort">Registered</v-th>
</thead>
```
```js
methods: {
dateSort(a, b) {
let date1 = new Date(a.registered).getTime();
let date2 = new Date(b.registered).getTime();
return date1 - date2;
}
}
```
### Default Sort <Badge text="Property"/> <Badge text="defaultSort: String"/>
You should provide this for the one column you want the table to be sorted by default.
The possible values are: `asc` for ascending ordering and `desc` for descending order.
```html
<thead slot="head">
<v-th sortKey="name" defaultSort="desc">Name</v-th>
</thead>
```
### Example
```html
<template>
<v-table
:data="users"
>
<thead slot="head">
<v-th :sortKey="nameLength" defaultSort="desc">Name</v-th>
<v-th sortKey="age">Age</v-th>
<v-th sortKey="address.state">State</v-th>
<v-th :customSort="dateSort">Registered</v-th>
</thead>
<tbody slot="body" slot-scope="{displayData}">
<tr v-for="row in displayData" :key="row.guid">
<td>{{ row.name }}</td>
<td>{{ row.age }}</td>
<td>{{ row.address.state }}</td>
<td>{{ row.registered }}</td>
</tr>
</tbody>
</v-table>
</template>
```
```js
<script>
import users from './users.json'
export default {
name: 'Sorting',
data: () => ({
users
}),
methods: {
nameLength (row) {
return row.name.length
},
dateSort(a, b) {
let date1 = new Date(a.registered).getTime();
let date2 = new Date(b.registered).getTime();
return date1 - date2;
}
}
}
</script>
```
<Sorting/>
## CSS icons
By default we include three SVG icons to indicate the sorting state of a column.
But you can use CSS Styles to change the sort icons.
The first thing you need to do is to disable the default sort icons with the `hideSortIcons` property on the `v-table` component:
```html
<v-table
:data="users"
:hideSortIcons="true"
>
...
</v-table>
```
Then you will get 4 CSS classes for `th` elements with sorting enabled:
* `vt-sort`: This class is always present, its purpose is to provide a constant CSS class for the columns with sorting.
* `vt-sortable`: This class indicates the column can be sorted and it is present when the column is not currently sorted.
* `vt-asc`: This class indicates the column is being sorted by an ascending order.
* `vt-desc`: This class indicates the column is being sorted by a descending order.
For this example we will use FontAwesome icons:
```css
.vt-sort:before{
font-family: FontAwesome;
padding-right: 0.5em;
width: 1.28571429em;
display: inline-block;
text-align: center;
}
.vt-sortable:before{
content: "\f0dc";
}
.vt-asc:before{
content: "\f160";
}
.vt-desc:before{
content: "\f161";
}
```
<SortingFA/>
================================================
FILE: docs/the-basics/README.md
================================================
# The Basics
The main goal for Vue Smart Table is to be intuitive to use while offering powerful features out of the box.
To achieve this we mix Vue Components and vanilla HTML Elements with the output being the same as a traditional HTML Table.
This will allow you to easily customize your tables with CSS or with a framework such as Bootstrap or Foundation.
For our examples we decided to use Bootstrap and Font Awesome, but you can use whatever your heart desires.
Here is the code for the simplest table you can create:
```js
<script>
import users from './users.json'
export default {
name: 'TheBasics',
data: () => ({
users
})
}
</script>
```
```html
<template>
<v-table :data="users">
<thead slot="head">
<th>Name</th>
<th>Age</th>
<th>Email</th>
<th>Address</th>
</thead>
<tbody slot="body" slot-scope="{displayData}">
<tr v-for="row in displayData" :key="row.id">
<td>{{ row.name }}</td>
<td>{{ row.age }}</td>
<td>{{ row.email }}</td>
<td>
{{ row.address.street }},
{{ row.address.city }}
{{ row.address.state}}
</td>
</tr>
</tbody>
</v-table>
</template>
```
<TheBasics/>
## Table <Badge text="Component" type="success"/> <Badge text="v-table"/>
The `v-table` component is the main element of Smart Table, here you will provide most of the configuration and listen for events.
But for now we will just focus on the `data` attribute.
### Data <Badge text="Property" type="success"/> <Badge text="data: Array"/>
Each `v-table` requires a `data` property, it must be an `array` even if it is initially empty.
Each entry in the array represents a row in the table.
It is important to note the array will not be mutated by Smart Table, internally it will create a shallow copy of it to perform
the operations.
## Head <Badge text="Slot" type="success"/> <Badge text="head"/>
The `head` slot is for the table `thead`, other than specifying the slot name with `slot="head"` there is nothing special about this.
You just need to provide vanilla `th` elements for each of your columns.
```html
<thead slot="head">
<th>Name</th>
<th>Age</th>
...
</thead>
```
## Body <Badge text="Scoped Slot" type="success"/> <Badge text="body"/>
The `body` slot is for the table `tbody`. This is a scoped slot which provides a `displayData` property.
### Display Data <Badge text="Slot Scope"/> <Badge text="displayData: array" type="success"/>
The `display-data` property provided by the `body` scoped slot is a shallow copy of the `data` array provided to the `v-table` component.
This array has all the plugins applied to it, for example, if filtering is enabled, this array will only contain the rows that pass the filters.
You will want to use a `v-for` directive to render the `tr` elements, remember, each entry in the `display-data` array represents a row.
```html
<tbody slot="body" slot-scope="{displayData}">
<tr v-for="row in displayData" :key="row.id">
<td>{{ row.name }}</td>
<td>{{ row.age }}</td>
...
</tr>
</tbody>
```
All right, this is the simplest table you can create, but right now Smart Table is effectively doing nothing you might as well just use a vanilla Html Table.
Keep browsing to discover how powerful Smart Table is out of the box.
================================================
FILE: package.json
================================================
{
"name": "vuejs-smart-table",
"keywords": [
"vue",
"table",
"grid",
"datagrid"
],
"version": "0.0.8",
"private": false,
"main": "src/main.js",
"author": "Hector Romero Granillo",
"repository": "https://github.com/tochoromero/vuejs-smart-table",
"license": "MIT",
"scripts": {
"build": "vue-cli-service build --target lib --name smart-table src/main.js",
"lint": "vue-cli-service lint",
"test:unit": "vue-cli-service test:unit --reporter mocha-junit-reporter --reporter-options mochaFile=./tests/results/test_results.xml",
"docs:dev": "vuepress dev docs",
"docs:build": "vuepress build docs"
},
"dependencies": {
"vue": "^2.5.17"
},
"devDependencies": {
"@fortawesome/fontawesome-free": "^5.6.3",
"@vue/cli-plugin-babel": "^3.1.1",
"@vue/cli-plugin-eslint": "^3.1.5",
"@vue/cli-plugin-unit-mocha": "^3.1.1",
"@vue/cli-service": "^3.1.4",
"@vue/eslint-config-standard": "^4.0.0",
"@vue/test-utils": "^1.0.0-beta.20",
"babel-eslint": "^10.0.1",
"bootstrap": "^4.2.1",
"chai": "^4.1.2",
"eslint": "^5.8.0",
"eslint-plugin-vue": "^5.0.0-0",
"lint-staged": "^7.2.2",
"mocha-junit-reporter": "^1.18.0",
"sass": "^1.30.0",
"sass-loader": "^10.1.0",
"vue-template-compiler": "^2.5.17",
"vuepress": "^0.14.8"
},
"gitHooks": {
"pre-commit": "lint-staged"
},
"lint-staged": {
"*.js": [
"vue-cli-service lint",
"git add"
],
"*.vue": [
"vue-cli-service lint",
"git add"
]
}
}
================================================
FILE: postcss.config.js
================================================
module.exports = {
plugins: {
autoprefixer: {}
}
}
================================================
FILE: src/SmartPagination.vue
================================================
<template replaceable part="pagination">
<nav v-show="!(hideSinglePage && totalPages === 1)" class="smart-pagination">
<ul class="pagination">
<li :class="{'disabled': currentPage === 1}" v-if="boundaryLinks" class="page-item">
<a href="javascript:void(0)" aria-label="Previous" @click="firstPage" class="page-link">
<span aria-hidden="true" v-html="firstText"></span>
</a>
</li>
<li :class="{'disabled': currentPage === 1}" v-if="directionLinks" class="page-item">
<a href="javascript:void(0)" aria-label="Previous" @click="previousPage()" class="page-link">
<slot name="previousIcon" :disabled="currentPage === 1">
<svg width="16" height="16" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512">
<path fill="currentColor"
d="M34.52 239.03L228.87 44.69c9.37-9.37 24.57-9.37 33.94 0l22.67 22.67c9.36 9.36 9.37 24.52.04 33.9L131.49 256l154.02 154.75c9.34 9.38 9.32 24.54-.04 33.9l-22.67 22.67c-9.37 9.37-24.57 9.37-33.94 0L34.52 272.97c-9.37-9.37-9.37-24.57 0-33.94z"></path>
</svg>
</slot>
</a>
</li>
<li
v-for="page in displayPages"
:key="page.value"
class="page-item"
:class="{'active': currentPage === page.value}"
>
<a href="javascript:void(0)" @click="selectPage(page.value)" class="page-link">{{page.title}}</a>
</li>
<li :class="{'disabled': currentPage === totalPages}" v-if="directionLinks"
class="page-item">
<a href="javascript:void(0)" aria-label="Next" @click="nextPage()" class="page-link">
<slot name="nextIcon" :disabled="currentPage === totalPages">
<svg width="16" height="16" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512">
<path fill="currentColor"
d="M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z"></path>
</svg>
</slot>
</a>
</li>
<li :class="{'disabled': currentPage === totalPages}" v-if="boundaryLinks"
class="page-item">
<a href="javascript:void(0)" aria-label="Previous" @click="lastPage()" class="page-link">
<span aria-hidden="true" v-html="lastText"></span>
</a>
</li>
</ul>
</nav>
</template>
<script>
export default {
name: 'SmartPagination',
props: {
currentPage: {
required: true,
type: Number
},
totalPages: {
required: true,
type: Number
},
hideSinglePage: {
required: false,
type: Boolean,
default: true
},
maxPageLinks: {
required: false,
type: Number
},
boundaryLinks: {
required: false,
type: Boolean,
default: false
},
firstText: {
required: false,
type: String,
default: 'First'
},
lastText: {
required: false,
type: String,
default: 'Last'
},
directionLinks: {
required: false,
type: Boolean,
default: true
}
},
computed: {
displayPages () {
if (isNaN(this.maxPageLinks) || this.maxPageLinks <= 0) {
return this.displayAllPages()
} else {
return this.limitVisiblePages()
}
}
},
methods: {
displayAllPages () {
const displayPages = []
for (let i = 1; i <= this.totalPages; i++) {
displayPages.push({
title: i.toString(),
value: i
})
}
return displayPages
},
limitVisiblePages () {
const displayPages = []
const totalTiers = Math.ceil(this.totalPages / this.maxPageLinks)
const activeTier = Math.ceil(this.currentPage / this.maxPageLinks)
const start = ((activeTier - 1) * this.maxPageLinks) + 1
const end = start + this.maxPageLinks
if (activeTier > 1) {
displayPages.push({
title: '...',
value: start - 1
})
}
for (let i = start; i < end; i++) {
if (i > this.totalPages) {
break
}
displayPages.push({
title: i.toString(),
value: i
})
}
if (activeTier < totalTiers) {
displayPages.push({
title: '...',
value: end
})
}
return displayPages
},
selectPage (page) {
if (page < 1 || page > this.totalPages || page === this.currentPage) {
return
}
this.$emit('update:currentPage', page)
},
nextPage () {
if (this.currentPage < this.totalPages) {
this.$emit('update:currentPage', this.currentPage + 1)
}
},
previousPage () {
if (this.currentPage > 1) {
this.$emit('update:currentPage', this.currentPage - 1)
}
},
firstPage () {
this.$emit('update:currentPage', 1)
},
lastPage () {
this.$emit('update:currentPage', this.totalPages)
}
}
}
</script>
<style>
.disabled svg {
color: grey;
}
.disabled a {
cursor: not-allowed
}
</style>
================================================
FILE: src/VTable.vue
================================================
<template>
<table>
<slot name="head"/>
<slot name="body" :displayData="displayData"/>
</table>
</template>
<script>
import { doFilter, doSort, calculateTotalPages, doPaginate } from './table-utils'
import store from './store'
import Vue from 'vue'
export default {
name: 'SmartTable',
props: {
data: {
required: true,
type: Array
},
filters: {
required: false,
type: Object
},
currentPage: {
required: false,
type: Number
},
pageSize: {
required: false,
type: Number
},
allowSelection: {
required: false,
type: Boolean,
default: false
},
selectionMode: {
required: false,
type: String,
default: 'single'
},
selectedClass: {
required: false,
type: String,
default: 'vt-selected'
},
customSelection: {
required: false,
type: Boolean
},
hideSortIcons: {
required: false,
type: Boolean
}
},
beforeCreate () {
this.store = new Vue(store)
},
provide () {
return {
store: this.store
}
},
data () {
return {
state: this.store._data,
initialLoad: false
}
},
computed: {
needsPaginationReset () {
return this.currentPage > this.totalPages
},
filteredData () {
if (this.data.length === 0) {
return []
}
if (typeof this.filters !== 'object') {
return this.data
}
return doFilter(this.data, this.filters)
},
totalItems () {
return this.filteredData.length
},
sortedData () {
if ((this.state.sortKey || this.state.customSort) && this.state.sortOrder !== 0) {
return doSort(this.filteredData, this.state.sortKey, this.state.customSort, this.state.sortOrder)
}
return this.filteredData
},
totalPages () {
if (!this.pageSize) return 0
return calculateTotalPages(this.totalItems, this.pageSize)
},
displayData () {
if (this.pageSize) {
return doPaginate(this.sortedData, this.pageSize, this.currentPage)
}
return this.sortedData
},
selectedRows () {
return this.state.selectedRows
}
},
watch: {
displayData: {
handler () {
if (!this.initialLoad) {
this.initialLoad = true
this.$emit('loaded', this)
}
},
immediate: true
},
selectionMode: {
handler (mode) {
this.state.selectionMode = mode
},
immediate: true
},
selectedClass: {
handler (selectedClass) {
this.state.selectedClass = selectedClass
},
immediate: true
},
customSelection: {
handler (customSelection) {
this.state.customSelection = customSelection
},
immediate: true
},
hideSortIcons: {
handler (hideSortIcons) {
this.state.hideSortIcons = hideSortIcons
},
immediate: true
},
needsPaginationReset: {
handler (needsReset) {
if (needsReset) {
this.$emit('update:currentPage', 1)
}
},
immediate: true
},
totalPages: {
handler (totalPages) {
this.$emit('totalPagesChanged', totalPages)
},
immediate: true
},
totalItems: {
handler (totalItems) {
this.$emit('totalItemsChanged', totalItems)
},
immediate: true
},
selectedRows: {
handler (selected) {
this.$emit('selectionChanged', selected)
},
immediate: true
}
},
methods: {
revealItem (item) {
if (!this.pageSize) {
return true
}
let index
if (typeof item === 'function') {
index = this.sortedData.findIndex(item)
} else {
index = this.sortedData.indexOf(item)
}
if (index === -1) {
return false
}
const currentPage = Math.ceil((index + 1) / this.pageSize)
this.$emit('update:currentPage', currentPage)
return true
},
revealPage (page) {
if (!this.pageSize || Number.isNaN(page) || page < 1) {
return
}
this.$emit('update:currentPage', page)
},
selectRow (row) {
this.store.selectRow(row)
},
selectRows (rows) {
this.store.selectRows(rows)
},
deselectRow (row) {
this.store.deselectRow(row)
},
deselectRows (rows) {
this.store.deselectRows(rows)
},
selectAll () {
if (this.selectionMode === 'single') return
this.store.selectAll([...this.data])
},
deselectAll () {
this.store.deselectAll()
}
}
}
</script>
================================================
FILE: src/VTh.vue
================================================
<template>
<th @click="sort" :class="sortClass" :aria-sort="ariaSortLabel">
<template v-if="!state.hideSortIcons">
<slot name="descIcon" v-if="order === -1">
<svg width="16" height="16" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512">
<path fill="currentColor" d="M41 288h238c21.4 0 32.1 25.9 17 41L177 448c-9.4 9.4-24.6 9.4-33.9 0L24 329c-15.1-15.1-4.4-41 17-41z"/>
</svg>
</slot>
<slot name="sortIcon" v-else-if="order === 0">
<svg width="16" height="16" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512">
<path fill="currentColor" d="M41 288h238c21.4 0 32.1 25.9 17 41L177 448c-9.4 9.4-24.6 9.4-33.9 0L24 329c-15.1-15.1-4.4-41 17-41zm255-105L177 64c-9.4-9.4-24.6-9.4-33.9 0L24 183c-15.1 15.1-4.4 41 17 41h238c21.4 0 32.1-25.9 17-41z"/></svg>
</slot>
<slot name="ascIcon" v-else-if="order === 1">
<svg width="16" height="16" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512">
<path fill="currentColor" d="M279 224H41c-21.4 0-32.1-25.9-17-41L143 64c9.4-9.4 24.6-9.4 33.9 0l119 119c15.2 15.1 4.5 41-16.9 41z"/>
</svg>
</slot>
</template>
<slot/>
</th>
</template>
<script>
import { uuid } from './table-utils'
export default {
name: 'v-th',
props: {
sortKey: {
required: false,
type: [String, Function]
},
customSort: {
required: false,
type: Function
},
defaultSort: {
required: false,
type: String,
validator: value => ['asc', 'desc'].includes(value)
}
},
inject: ['store'],
data () {
return {
id: uuid(),
order: 0,
orderClasses: ['vt-desc', 'vt-sortable', 'vt-asc'],
ariaLabels: ['descending', 'none', 'ascending'],
state: this.store._data
}
},
computed: {
sortEnabled () {
return this.sortKey || typeof this.customSort === 'function'
},
sortId () {
return this.state.sortId
},
sortClass () {
return this.state.hideSortIcons ? [this.orderClasses[this.order + 1], 'vt-sort'] : []
},
ariaSortLabel () {
return this.ariaLabels[this.order + 1]
}
},
watch: {
sortId (sortId) {
if (sortId !== this.id && this.order !== 0) {
this.order = 0
}
}
},
mounted () {
if (!this.sortKey && !this.customSort) {
throw new Error('Must provide the Sort Key value or a Custom Sort function.')
}
if (this.defaultSort) {
this.order = this.defaultSort === 'desc' ? -1 : 1
this.store.setSort({
sortOrder: this.order,
sortKey: this.sortKey,
customSort: this.customSort,
sortId: this.id
})
this.$nextTick(() => {
this.$emit('defaultSort')
})
}
},
methods: {
sort: function () {
if (this.sortEnabled) {
this.order = this.order === 0 || this.order === -1 ? this.order + 1 : -1
this.store.setSort({
sortOrder: this.order,
sortKey: this.sortKey,
customSort: this.customSort,
sortId: this.id
})
}
}
}
}
</script>
<style scoped>
.vt-sort {
cursor: pointer;
}
</style>
================================================
FILE: src/VTr.vue
================================================
<template>
<tr
:class="[rowClass]"
:style="style"
@click="handleRowSelected"
>
<slot></slot>
</tr>
</template>
<script>
export default {
name: 'v-tr',
props: {
row: {
required: true
}
},
inject: ['store'],
data () {
return {
state: this.store._data
}
},
mounted () {
if (!this.state.customSelection) {
this.$el.style.cursor = 'pointer'
}
},
beforeDestroy () {
if (!this.state.customSelection) {
this.$el.removeEventListener('click', this.handleRowSelected)
}
},
computed: {
isSelected () {
return this.state.selectedRows.find(it => it === this.row)
},
rowClass: function () {
return this.isSelected ? this.state.selectedClass : ''
},
style () {
return {
...(!this.state.customSelection ? { cursor: 'pointer' } : {})
}
}
},
methods: {
handleRowSelected (event) {
if (this.state.customSelection) return
let source = event.target || event.srcElement
if (source.tagName.toLowerCase() === 'td') {
if (this.isSelected) {
this.store.deselectRow(this.row)
} else {
this.store.selectRow(this.row)
}
}
}
}
}
</script>
================================================
FILE: src/main.js
================================================
import VTable from './VTable.vue'
import VTh from './VTh.vue'
import VTr from './VTr.vue'
import SmartPagination from './SmartPagination.vue'
export {
VTable,
VTh,
VTr,
SmartPagination
}
/**/
export default {
install (Vue) {
Vue.component('v-table', VTable)
Vue.component('v-th', VTh)
Vue.component('v-tr', VTr)
Vue.component('smart-pagination', SmartPagination)
}
}
================================================
FILE: src/store.js
================================================
export default {
data: () => ({
selectedRows: [],
selectionMode: 'single',
customSelection: null,
selectedClass: null,
hideSortIcons: null,
sortId: null,
sortKey: null,
customSort: null,
sortOrder: null
}),
methods: {
selectRow (row) {
if (this.selectionMode === 'single') {
this.selectedRows = [row]
return
}
const index = this.selectedRows.indexOf(row)
if (index === -1) {
this.selectedRows.push(row)
}
},
selectRows (rows) {
for (let row of rows) {
this.selectRow(row)
}
},
deselectRow (row) {
const index = this.selectedRows.indexOf(row)
if (index > -1) {
this.selectedRows.splice(index, 1)
}
},
deselectRows (rows) {
for (let row of rows) {
this.deselectRow(row)
}
},
selectAll (all) {
this.selectedRows = all
},
deselectAll () {
this.selectedRows = []
},
setSort ({ sortKey, customSort, sortOrder, sortId }) {
this.sortKey = sortKey
this.customSort = customSort
this.sortOrder = sortOrder
this.sortId = sortId
}
}
}
================================================
FILE: src/table-utils.js
================================================
export function doSort (toSort, sortKey, customSort, sortOrder) {
let local = [...toSort]
return local.sort((a, b) => {
if (typeof customSort === 'function') {
return customSort(a, b) * sortOrder
}
let val1
let val2
if (typeof sortKey === 'function') {
val1 = sortKey(a, sortOrder)
val2 = sortKey(b, sortOrder)
} else {
val1 = getPropertyValue(a, sortKey)
val2 = getPropertyValue(b, sortKey)
}
if (val1 === null || val1 === undefined) val1 = ''
if (val2 === null || val2 === undefined) val2 = ''
if (isNumeric(val1) && isNumeric(val2)) {
return (val1 - val2) * sortOrder
}
const str1 = val1.toString()
const str2 = val2.toString()
return str1.localeCompare(str2) * sortOrder
})
}
export function doFilter (toFilter, filters) {
let filteredData = []
for (let item of toFilter) {
let passed = true
for (let filterName in filters) {
if (!filters.hasOwnProperty(filterName)) {
continue
}
let filter = filters[filterName]
if (!passFilter(item, filter)) {
passed = false
break
}
}
if (passed) {
filteredData.push(item)
}
}
return filteredData
}
export function doPaginate (toPaginate, pageSize, currentPage) {
if (toPaginate.length <= pageSize || pageSize <= 0 || currentPage <= 0) {
return toPaginate
}
const start = (currentPage - 1) * pageSize
const end = start + pageSize
return [...toPaginate].slice(start, end)
}
export function calculateTotalPages (totalItems, pageSize) {
return totalItems <= pageSize ? 1 : Math.ceil(totalItems / pageSize)
}
export function passFilter (item, filter) {
if (typeof filter.custom === 'function' && !filter.custom(filter.value, item)) {
return false
}
if (filter.value === null || filter.value === undefined || filter.value.length === 0 || !Array.isArray(filter.keys)) {
return true
}
for (let key of filter.keys) {
const value = getPropertyValue(item, key)
if (value !== null && value !== undefined) {
const filterStrings = Array.isArray(filter.value) ? filter.value : [filter.value]
for (const filterString of filterStrings) {
if (filter.exact) {
if (value.toString() === filterString.toString()) {
return true
}
} else {
if (value.toString().toLowerCase().includes(filterString.toString().toLowerCase())) {
return true
}
}
}
}
}
return false
}
export function getPropertyValue (object, keyPath) {
keyPath = keyPath.replace(/\[(\w+)\]/g, '.$1')
keyPath = keyPath.replace(/^\./, '')
const a = keyPath.split('.')
for (let i = 0, n = a.length; i < n; ++i) {
let k = a[i]
if (k in object) {
object = object[k]
} else {
return
}
}
return object
}
export function isNumeric (toCheck) {
return !Array.isArray(toCheck) && !isNaN(parseFloat(toCheck)) && isFinite(toCheck)
}
export function uuid () {
return '_' + Math.random().toString(36).substr(2, 9)
}
================================================
FILE: tests/unit/.eslintrc.js
================================================
module.exports = {
env: {
mocha: true
}
}
================================================
FILE: tests/unit/table-utils.spec.js
================================================
import { expect } from 'chai'
import { calculateTotalPages, isNumeric, getPropertyValue, doPaginate } from '../../src/table-utils.js'
let scenario = [
{ totalItems: 10, pageSize: 5, result: 2 },
{ totalItems: 1, pageSize: 10, result: 1 },
{ totalItems: 11, pageSize: 10, result: 2 },
{ totalItems: 200, pageSize: 200, result: 1 },
{ totalItems: 200, pageSize: 1, result: 200 }
]
scenario.forEach(({ totalItems, pageSize, result }) => {
describe('calculateTotalPages', () => {
it(`Should be ${result} pages when totalItems is ${totalItems} and pageSize: is ${pageSize}`, () => {
expect(calculateTotalPages(totalItems, pageSize))
.to.equal(result)
})
})
})
scenario = [
{ toCheck: 5, result: true },
{ toCheck: 1.0, result: true },
{ toCheck: -1.0, result: true },
// eslint-disable-next-line no-floating-decimal
{ toCheck: .5, result: true },
{ toCheck: 0.8, result: true },
{ toCheck: '0.5', result: true },
{ toCheck: '-0.5', result: true },
{ toCheck: 'asd', result: false },
{ toCheck: '5,2', result: false },
{ toCheck: [1], result: false },
{ toCheck: [], result: false },
{ toCheck: { value: 1 }, result: false }
]
scenario.forEach(({ toCheck, result }) => {
describe('isNumeric', () => {
it(`${toCheck} should ${result ? '' : 'not'} be numeric`, () => {
expect(isNumeric(toCheck))
.to.equal(result)
})
})
})
scenario = [
{ object: { value: 'asd', values: 123 }, path: 'value', result: 'asd' },
{ object: { value: 'asd', values: 123 }, path: '[value]', result: 'asd' },
{ object: { a: { b: { c: 13 } } }, path: 'a.b.c', result: 13 },
{ object: { a: { b: { c: 13 } } }, path: 'a[b].c', result: 13 },
{ object: { value: 'asd' }, path: 'none', result: undefined },
{ object: {}, path: 'empty', result: undefined }
]
scenario.forEach(({ object, path, result }) => {
describe('getPropertyValue', () => {
it(`path '${path}' should be ${result}`, () => {
expect(getPropertyValue(object, path))
.to.equal(result)
})
})
})
let toPaginate = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
scenario = [
{ pageSize: 3, currentPage: 1, result: [1, 2, 3] },
{ pageSize: 5, currentPage: 2, result: [6, 7, 8, 9, 10] },
{ pageSize: 2, currentPage: 3, result: [5, 6] },
{ pageSize: 5, currentPage: 3, result: [] },
{ pageSize: 50, currentPage: 1, result: toPaginate },
{ pageSize: 0, currentPage: 1, result: toPaginate },
{ pageSize: 5, currentPage: 0, result: toPaginate }
]
scenario.forEach(({ pageSize, currentPage, result }) => {
describe('doPaginate', () => {
it(`With size: ${pageSize} and currentPage: ${currentPage} it should return ${result}`, () => {
expect(doPaginate(toPaginate, pageSize, currentPage))
.to.eql(result)
})
})
})
gitextract_kaf1xz4h/
├── .browserslistrc
├── .circleci/
│ └── config.yml
├── .editorconfig
├── .eslintignore
├── .eslintrc.js
├── .gitignore
├── LICENSE
├── README.md
├── babel.config.js
├── deploy.sh
├── docs/
│ ├── .vuepress/
│ │ ├── components/
│ │ │ ├── BasicFiltering.vue
│ │ │ ├── CustomFiltering.vue
│ │ │ ├── InputSpinner.vue
│ │ │ ├── Pagination.vue
│ │ │ ├── Selection.vue
│ │ │ ├── SelectionApi.vue
│ │ │ ├── Sorting.vue
│ │ │ ├── SortingFA.vue
│ │ │ ├── TheBasics.vue
│ │ │ └── data.json
│ │ └── config.js
│ ├── README.md
│ ├── filtering/
│ │ └── README.md
│ ├── pagination/
│ │ └── README.md
│ ├── selection/
│ │ └── README.md
│ ├── sorting/
│ │ └── README.md
│ └── the-basics/
│ └── README.md
├── package.json
├── postcss.config.js
├── src/
│ ├── SmartPagination.vue
│ ├── VTable.vue
│ ├── VTh.vue
│ ├── VTr.vue
│ ├── main.js
│ ├── store.js
│ └── table-utils.js
└── tests/
└── unit/
├── .eslintrc.js
└── table-utils.spec.js
SYMBOL INDEX (16 symbols across 3 files)
FILE: src/main.js
method install (line 14) | install (Vue) {
FILE: src/store.js
method selectRow (line 14) | selectRow (row) {
method selectRows (line 25) | selectRows (rows) {
method deselectRow (line 30) | deselectRow (row) {
method deselectRows (line 37) | deselectRows (rows) {
method selectAll (line 42) | selectAll (all) {
method deselectAll (line 45) | deselectAll () {
method setSort (line 48) | setSort ({ sortKey, customSort, sortOrder, sortId }) {
FILE: src/table-utils.js
function doSort (line 1) | function doSort (toSort, sortKey, customSort, sortOrder) {
function doFilter (line 34) | function doFilter (toFilter, filters) {
function doPaginate (line 61) | function doPaginate (toPaginate, pageSize, currentPage) {
function calculateTotalPages (line 72) | function calculateTotalPages (totalItems, pageSize) {
function passFilter (line 76) | function passFilter (item, filter) {
function getPropertyValue (line 107) | function getPropertyValue (object, keyPath) {
function isNumeric (line 122) | function isNumeric (toCheck) {
function uuid (line 126) | function uuid () {
Condensed preview — 38 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (147K chars).
[
{
"path": ".browserslistrc",
"chars": 33,
"preview": "> 1%\nlast 2 versions\nnot ie <= 8\n"
},
{
"path": ".circleci/config.yml",
"chars": 209,
"preview": "version: 2.1\njobs:\n build:\n docker:\n - image: circleci/node\n steps:\n - checkout\n - run: yarn insta"
},
{
"path": ".editorconfig",
"chars": 121,
"preview": "[*.{js,jsx,ts,tsx,vue}]\nindent_style = space\nindent_size = 2\ntrim_trailing_whitespace = true\ninsert_final_newline = true"
},
{
"path": ".eslintignore",
"chars": 6,
"preview": "/dist\n"
},
{
"path": ".eslintrc.js",
"chars": 348,
"preview": "module.exports = {\n root: true,\n env: {\n node: true\n },\n 'extends': [\n 'plugin:vue/essential',\n '@vue/stand"
},
{
"path": ".gitignore",
"chars": 249,
"preview": ".DS_Store\nnode_modules\ndist\n\ntests/results\n\n# local env files\n.env.local\n.env.*.local\n\n# Log files\nnpm-debug.log*\nyarn-d"
},
{
"path": "LICENSE",
"chars": 1070,
"preview": "MIT License\n\nCopyright (c) 2019 Hector Romero\n\nPermission is hereby granted, free of charge, to any person obtaining a c"
},
{
"path": "README.md",
"chars": 1089,
"preview": ":fire::fire::fire: Vue 3 support is comming :fire::fire::fire:\n\nVue 3 support is already in beta in the `next` branch, t"
},
{
"path": "babel.config.js",
"chars": 53,
"preview": "module.exports = {\n presets: [\n '@vue/app'\n ]\n}\n"
},
{
"path": "deploy.sh",
"chars": 197,
"preview": "#!/usr/bin/env sh\n\nset -e\n\nnpm run docs:build\ncd docs/.vuepress/dist\n\ngit init\ngit add -A\ngit commit -m 'deploy'\n\ngit pu"
},
{
"path": "docs/.vuepress/components/BasicFiltering.vue",
"chars": 1133,
"preview": "<template>\n <div class=\"card mt-3\">\n <div class=\"card-body\">\n <label>Filter by Name:</label>\n <input class"
},
{
"path": "docs/.vuepress/components/CustomFiltering.vue",
"chars": 1710,
"preview": "<template>\n <div class=\"card mt-3\">\n <div class=\"card-body\">\n <label>Min Age:</label>\n\n <InputSpinner\n "
},
{
"path": "docs/.vuepress/components/InputSpinner.vue",
"chars": 3729,
"preview": "<template>\n <div>\n <div class=\"input-group input-spinner\"\n :style=\"{width: componentWidth, 'min-width': '9re"
},
{
"path": "docs/.vuepress/components/Pagination.vue",
"chars": 1217,
"preview": "<template>\n <div class=\"card mt-3\">\n <div class=\"card-body\">\n <v-table\n :data=\"users\"\n :currentPa"
},
{
"path": "docs/.vuepress/components/Selection.vue",
"chars": 1417,
"preview": "<template>\n <div class=\"card mt-3\">\n <div class=\"card-body\">\n <v-table\n class=\"table-hover\"\n ref="
},
{
"path": "docs/.vuepress/components/SelectionApi.vue",
"chars": 1900,
"preview": "<template>\n <div class=\"card mt-3\">\n <div class=\"card-body\">\n <button\n class=\"btn btn-outline-secondary\""
},
{
"path": "docs/.vuepress/components/Sorting.vue",
"chars": 1288,
"preview": "<template>\n <div class=\"card mt-3\">\n <div class=\"card-body\">\n <v-table\n :data=\"users\"\n >\n <t"
},
{
"path": "docs/.vuepress/components/SortingFA.vue",
"chars": 1342,
"preview": "<template>\n <div class=\"card mt-3\">\n <div class=\"card-body\">\n <v-table\n :data=\"users\"\n :hideSortI"
},
{
"path": "docs/.vuepress/components/TheBasics.vue",
"chars": 695,
"preview": "<template>\n <v-table\n :data=\"data\"\n >\n <thead slot=\"head\">\n <th>Name</th>\n <th>Age</th>\n <th>Email</th>"
},
{
"path": "docs/.vuepress/components/data.json",
"chars": 67866,
"preview": "[\n {\n \"_id\": \"57ef9cd8e22df324d77c4f07\",\n \"index\": 0,\n \"guid\": \"9bc8e89d-658c-47cf-acc3-b0e225ddb549\",\n \"is"
},
{
"path": "docs/.vuepress/config.js",
"chars": 507,
"preview": "module.exports = {\n title: 'VueJs Smart Table',\n base: '/vuejs-smart-table/',\n description: 'Simple yet powerful Data"
},
{
"path": "docs/README.md",
"chars": 706,
"preview": "# Introduction\n\nVue Smart Table was created out of the need for a simple highly customizable data table plugin \nthat cou"
},
{
"path": "docs/filtering/README.md",
"chars": 4398,
"preview": "# Filtering\nSmart Table is only on charge of the actual row filtering based on the provided configuration. \nThe visual a"
},
{
"path": "docs/pagination/README.md",
"chars": 5122,
"preview": "# Pagination\n\nVue Smart Table supports client side pagination. \nTo enable it, you need to provide the `pageSize` and `cu"
},
{
"path": "docs/selection/README.md",
"chars": 2499,
"preview": "# Row Selection\n\n## Table Row <Badge text=\"Component\"/> <Badge text=\"v-tr\"/>\nTo enable row selection you need to use the"
},
{
"path": "docs/sorting/README.md",
"chars": 4817,
"preview": "# Sorting\n\nTo enable column sorting, instead of using vanilla `th` elements we will use `v-th` Components for the column"
},
{
"path": "docs/the-basics/README.md",
"chars": 3383,
"preview": "# The Basics\n\nThe main goal for Vue Smart Table is to be intuitive to use while offering powerful features out of the bo"
},
{
"path": "package.json",
"chars": 1557,
"preview": "{\n \"name\": \"vuejs-smart-table\",\n \"keywords\": [\n \"vue\",\n \"table\",\n \"grid\",\n \"datagrid\"\n ],\n \"version\": \"0"
},
{
"path": "postcss.config.js",
"chars": 59,
"preview": "module.exports = {\n plugins: {\n autoprefixer: {}\n }\n}\n"
},
{
"path": "src/SmartPagination.vue",
"chars": 5284,
"preview": "<template replaceable part=\"pagination\">\n <nav v-show=\"!(hideSinglePage && totalPages === 1)\" class=\"smart-pagination\">"
},
{
"path": "src/VTable.vue",
"chars": 4652,
"preview": "<template>\n <table>\n <slot name=\"head\"/>\n <slot name=\"body\" :displayData=\"displayData\"/>\n </table>\n</template>\n\n"
},
{
"path": "src/VTh.vue",
"chars": 3187,
"preview": "<template>\n <th @click=\"sort\" :class=\"sortClass\" :aria-sort=\"ariaSortLabel\">\n <template v-if=\"!state.hideSortIcons\">"
},
{
"path": "src/VTr.vue",
"chars": 1250,
"preview": "<template>\n <tr\n :class=\"[rowClass]\"\n :style=\"style\"\n @click=\"handleRowSelected\"\n\n >\n <slot></slot>\n </tr"
},
{
"path": "src/main.js",
"chars": 396,
"preview": "import VTable from './VTable.vue'\nimport VTh from './VTh.vue'\nimport VTr from './VTr.vue'\nimport SmartPagination from '."
},
{
"path": "src/store.js",
"chars": 1180,
"preview": "export default {\n data: () => ({\n selectedRows: [],\n selectionMode: 'single',\n customSelection: null,\n sele"
},
{
"path": "src/table-utils.js",
"chars": 3093,
"preview": "export function doSort (toSort, sortKey, customSort, sortOrder) {\n let local = [...toSort]\n\n return local.sort((a, b) "
},
{
"path": "tests/unit/.eslintrc.js",
"chars": 50,
"preview": "module.exports = {\n env: {\n mocha: true\n }\n}\n"
},
{
"path": "tests/unit/table-utils.spec.js",
"chars": 2786,
"preview": "import { expect } from 'chai'\nimport { calculateTotalPages, isNumeric, getPropertyValue, doPaginate } from '../../src/ta"
}
]
About this extraction
This page contains the full source code of the tochoromero/vuejs-smart-table GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 38 files (127.5 KB), approximately 42.8k tokens, and a symbol index with 16 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.