Last active
April 29, 2018 08:37
-
-
Save boynoiz/fb9556f74a32127a5f8f263bac9bab6b to your computer and use it in GitHub Desktop.
My User DataTable Snippet (BoostrapVue + VueJS + Vee-validate + Ziggy + Axios on Top Laravel API)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<template> | |
<!-- Main table element --> | |
<div class="main-content"> | |
<div class="card"> | |
<header class="card-header"> | |
<h4 class="card-title"> | |
<strong>User</strong> List | |
</h4> | |
<div class="card-header-actions"> | |
<div class="btn-group btn-group-sm"> | |
<button class="btn" title="Refresh" @click.stop="refreshTable"> | |
<i class="fa fa-refresh"></i> | |
</button> | |
<button class="btn" title="Add new" | |
@click.stop="openModalForm('create', $event.target)"><i | |
class="fa fa-plus"></i></button> | |
</div> | |
<div class="btn-group btn-group-sm ml-2 d-none d-sm-flex"> | |
<button class="btn dropdown-toggle">Export</button> | |
<div class="dropdown-menu dropdown-menu-right"> | |
<a class="dropdown-item" href="#">CSV</a> | |
<a class="dropdown-item" href="#">SQL</a> | |
<a class="dropdown-item" href="#">PDF</a> | |
<a class="dropdown-item" href="#">Text</a> | |
</div> | |
</div> | |
</div> | |
</header> | |
<div class="card-body"> | |
<div class="flexbox mb-20"> | |
<div class="lookup lookup-sm d-none d-lg-block"> | |
<b-form-input id="field_search" | |
v-model="filter" | |
type="text" | |
placeholder="Search"> | |
</b-form-input> | |
</div> | |
<div class="btn-toolbar"> | |
<div> | |
<b-form-select id="field_perpage_select" class="form-control-sm" :options="pageOptions" v-model="meta.per_page"/> | |
</div> | |
</div> | |
</div> | |
<b-table id="userTable" striped hover | |
ref="userTable" | |
:busy.sync="isBusy" | |
:items="!filtered ? listAllUsers : searchUser" | |
:fields="fields" | |
:current-page="meta.current_page" | |
:per-page="meta.per_page" | |
:filter="filtered" | |
> | |
<template slot="profile" slot-scope="row"> | |
{{ row.value ? row.value.company : '' }} | |
</template> | |
<template slot="is_activated" slot-scope="row"> | |
<span :class="row.value ? 'badge badge-lg badge-pill badge-success bg-success' : 'badge badge-lg badge-pill badge-danger bg-danger'"> | |
<i :class="row.value ? 'fa fa-lg fa-check-circle' : 'fa fa-lg fa-times-circle'"></i> | |
<template> {{ row.value ? 'Yes' : 'No' }}</template> | |
</span> | |
</template> | |
<template slot="actions" slot-scope="row"> | |
<!-- We use click.stop here to prevent a 'row-clicked' event from also happening --> | |
<button class="btn btn-w-md btn-round btn-primary" | |
@click.stop="openModalForm('show', $event.target, row.item)"><i | |
class="fa fa-search-plus"></i> Details | |
</button> | |
</template> | |
</b-table> | |
<b-pagination align="center" | |
:total-rows="meta.total" | |
:per-page="meta.per_page" | |
v-model="meta.current_page"/> | |
<!-- Open modal --> | |
<!-- Start create modal --> | |
<b-modal id="createUserForm" size="lg" :ok-disabled="errors.any()" @ok="createUser()" | |
@hidden="hideModalForm('createUserForm')"> | |
<div class="card"> | |
<header class="card-header no-border"> | |
<h4 class="card-title"><strong>Create </strong>new user</h4> | |
</header> | |
<div class="card-body"> | |
<b-form-group horizontal id="field_c_username" label="Username" | |
:label-cols="2" | |
:feedback="errors.has('c_username') ? errors.first('c_username') : null" | |
:state="errors.has('c_username') ? 'invalid' : 'valid'"> | |
<b-input-group> | |
<b-input name="c_username" id="c_username" | |
v-model="userDetails.username" | |
type="text" | |
:state="errors.has('c_username') ? 'invalid' : 'valid'" | |
v-validate.initial="{ | |
rules: { | |
UsernameUnique: true, | |
regex: /^[a-zA-Z0-9.\-_]{3,30}$/, | |
required: true | |
} | |
}"> | |
</b-input> | |
</b-input-group> | |
</b-form-group> | |
<b-form-group horizontal id="field_c_password" label="Password" | |
:label-cols="2" | |
:feedback="errors.first('c_password')"> | |
<b-form-input name="c_password" id="c_password" | |
v-model="userDetails.password" | |
type="password" | |
:state="!errors.has('c_password')" | |
v-validate.initial="{ | |
rules: { | |
regex: /^(?=.*[a-zA-Z$*_^-])(?=.*\d).+$/, | |
required: true, | |
min: 6 | |
} | |
}"> | |
</b-form-input> | |
</b-form-group> | |
<b-form-group horizontal id="field_c_password_confirmation" label="Password Confirm" | |
:label-cols="2" | |
:feedback="errors.first('c_password_confirmation')"> | |
<b-form-input name="c_password_confirmation" id="c_password_confirmation" | |
v-model="userDetails.password_confirmation" type="password" | |
:state="!errors.has('c_password_confirmation')" | |
v-validate.initial="'required|confirmed:c_password'"> | |
</b-form-input> | |
</b-form-group> | |
<b-form-group horizontal id="field_c_email" label="Email" | |
:label-cols="2" | |
:feedback="errors.first('c_email')"> | |
<b-form-input name="c_email" id="c_email" | |
v-model="userDetails.email" type="text" | |
:state="!errors.has('c_email')" | |
v-validate.initial="'required|email|EmailUnique'"> | |
</b-form-input> | |
</b-form-group> | |
<b-form-group horizontal id="field_c_company" label="Company" | |
:label-cols="2" | |
:feedback="errors.first('c_company')"> | |
<b-form-input name="c_company" id="c_company" | |
v-model="userDetails.company" | |
type="text" | |
:state="!errors.has('c_company')" | |
v-validate.initial="'required'"> | |
</b-form-input> | |
</b-form-group> | |
<b-form-group horizontal id="field_c_location" label="Location" | |
:label-cols="2" | |
:feedback="errors.first('c_location')"> | |
<b-form-input name="c_location" id="c_location" | |
v-model="userDetails.location" | |
type="text" | |
:state="!errors.has('c_location')" | |
v-validate.initial="{ | |
rules: { | |
regex:/^[a-zA-Z\d\-_\s]+$/ | |
} | |
}"> | |
</b-form-input> | |
</b-form-group> | |
</div> | |
</div> | |
</b-modal> | |
<!-- End create modal --> | |
<b-modal id="showUserForm" size="lg" | |
:title="'User detail: ' + userDetails.username" | |
@ok="editFormToggle ? updateUser():null" | |
@hidden="hideModalForm('showUserForm')"> | |
<label class="switch switch-lg"> | |
<input type="checkbox" v-model="editFormToggle"> | |
<span class="switch-indicator"></span> | |
<span class="switch-description">Edit</span> | |
</label> | |
<b-form-group id="field_s_username" horizontal label="Username"> | |
<b-form-input name="s_username" id="s_username" v-model.lazy="userDetails.username" | |
type="text" plaintext> | |
</b-form-input> | |
</b-form-group> | |
<b-form-group id="field_s_password" horizontal label="Password" | |
:state="errors.has('s_password')" | |
:feedback="errors.first('s_password')"> | |
<b-form-input name="s_password" id="s_password" v-model.lazy="userDetails.password" | |
type="password" | |
:plaintext="!editFormToggle" | |
v-validate.initial="{ | |
rules: { | |
regex: /^(?=.*[a-zA-Z$*_^-])(?=.*\d).+$/, | |
required: true, | |
min: 6 | |
} | |
}" | |
:class="{'input': true, 'is-invalid': errors.has('s_password') }"> | |
</b-form-input> | |
</b-form-group> | |
<b-form-group id="field_s_password_confirmation" horizontal label="Password Confirm" | |
:state="errors.has('s_password_confirmation')" | |
:feedback="errors.first('s_password_confirmation')"> | |
<b-form-input name="s_password_confirmation" id="s_password_confirmation" | |
v-model.lazy="userDetails.password_confirmation" type="password" | |
:plaintext="!editFormToggle" | |
v-validate.initial="'confirmed:s_password'" | |
:class="{'input': true, 'is-invalid': errors.has('s_password_confirmation') }"> | |
</b-form-input> | |
</b-form-group> | |
<b-form-group id="field_s_email" horizontal label="Email" | |
:state="errors.has('s_email')" | |
:feedback="errors.first('s_email')"> | |
<b-form-input name="s_email" id="s_email" v-model.lazy="userDetails.email" type="text" | |
:plaintext="!editFormToggle" | |
v-validate.initial="'required|email'" | |
:class="{'input': true, 'is-invalid': errors.has('s_email') }"> | |
</b-form-input> | |
</b-form-group> | |
<b-form-group id="field_s_company" horizontal label="Company" | |
:state="errors.has('s_company')" | |
:feedback="errors.first('s_company')"> | |
<b-form-input name="s_company" id="s_company" v-model.lazy="userDetails.company" type="text" | |
:plaintext="!editFormToggle" | |
v-validate.initial="'required'" | |
:class="{'input': true, 'is-invalid': errors.has('s_company') }"> | |
</b-form-input> | |
</b-form-group> | |
<b-form-group id="field_s_location" horizontal label="Location"> | |
<b-form-input name="s_location" id="s_location" v-model.lazy="userDetails.location" | |
type="text" | |
:plaintext="!editFormToggle"> | |
</b-form-input> | |
</b-form-group> | |
</b-modal> | |
<!-- End modal --> | |
</div> | |
<div v-show="isBusy" class="card-loading reveal"> | |
<svg class="spinner-circle-material-svg" viewBox="0 0 50 50"> | |
<circle class="circle" cx="25" cy="25" r="20"></circle> | |
</svg> | |
</div> | |
</div> | |
</div> | |
</template> | |
<script> | |
import axios from 'axios' | |
import Vue from 'vue' | |
import BootstrapVue from 'bootstrap-vue' | |
// import BootstrapVue from 'bootstrap-vue/dist/bootstrap-vue.esm' | |
import VeeValidate from 'vee-validate' | |
import {Validator} from 'vee-validate'; | |
// Config for VeeValidate | |
const config = { | |
errorBagName: 'errors', // change if property conflicts. | |
fieldsBagName: 'fields ', // Default is fields | |
delay: 0, | |
locale: 'en', | |
dictionary: { | |
en: { | |
attributes: { | |
c_username: 'Username', | |
c_password: 'Password', | |
c_password_confirmation: 'Password Confirm', | |
c_email: 'Email Address', | |
c_company: 'Company Name', | |
c_location: 'Location', | |
s_username: 'Username', | |
s_password: 'Password', | |
s_password_confirmation: 'Password Confirm', | |
s_email: 'Email Address', | |
s_company: 'Company Name', | |
} | |
} | |
}, | |
strict: true, | |
classes: false, | |
classNames: { | |
touched: 'touched', // the control has been blurred | |
untouched: 'untouched', // the control hasn't been blurred | |
valid: 'valid', // model is valid | |
invalid: 'invalid', // model is invalid | |
pristine: 'pristine', // control has not been interacted with | |
dirty: 'dirty' // control has been interacted with | |
}, | |
events: 'input|blur', | |
inject: true, | |
validity: false, | |
aria: true | |
}; | |
Vue.use(VeeValidate, config); | |
Vue.use(BootstrapVue); | |
export default { | |
name: 'user-table', | |
data: () => ({ | |
items: {}, | |
links: {}, | |
meta: { | |
current_page: 1, | |
from: null, | |
last_page: null, | |
path: '', | |
per_page: 15, | |
to: null, | |
total: null | |
}, | |
fields: [ | |
{key: 'username', label: 'Username', sortable: true, 'class': 'text-center'}, | |
{key: 'email', label: 'Email', sortable: true, 'class': 'text-center'}, | |
{key: 'profile', label: 'Company Name', sortable: true, 'class': 'text-center'}, | |
{key: 'is_activated', label: 'Activated?', 'class': 'text-center'}, | |
{key: 'updated_at', label: 'Updated at', 'class': 'text-center'}, | |
{key: 'actions', label: 'Actions', 'class': 'text-center'} | |
], | |
userDetails: { | |
id: null, | |
username: '', | |
password: '', | |
password_confirmation: '', | |
email: '', | |
company: '', | |
location: '', | |
is_activated: '' | |
}, | |
methodForm: '', | |
editFormToggle: false, | |
isBusy: false, | |
pageOptions: [ | |
{value: 15, text: 'Per page : 15 '}, | |
{value: 30, text: ' --- 30 '}, | |
{value: 60, text: ' --- 60 '}, | |
{value: 90, text: ' --- 90 '}, | |
{value: 120, text: ' --- 120 '} | |
], | |
sortBy: '', | |
sortDesc: false, | |
filter: null, | |
filtered: null, | |
searchTyping: false | |
}), | |
// Validate method via backend api | |
created() { | |
// Validate user exist | |
const isUsernameUnique = async (value) => { | |
try { | |
const promise = await axios.post(route('internal.api.user.validate'), {username: value}); | |
const data = await promise.data; | |
return { | |
valid: data.valid, | |
data: { | |
message: data.message | |
} | |
}; | |
} catch (e) { | |
console.log('Calling the API has been an error: ', e); | |
return [] | |
} | |
}; | |
Validator.extend('UsernameUnique', { | |
validate: isUsernameUnique, | |
getMessage: (field, params, data) => { | |
return data.message; | |
} | |
}); | |
// Validate email exist | |
const isEmailUnique = async (value) => { | |
try { | |
const promise = await axios.post(route('internal.api.email.validate'), {email: value}); | |
const data = await promise.data; | |
return { | |
valid: data.valid, | |
data: { | |
message: data.message | |
} | |
} | |
} catch (e) { | |
console.log('Calling api has been an error: ', e); | |
} | |
}; | |
Validator.extend('EmailUnique', { | |
validate: isEmailUnique, | |
getMessage: (field, params, data) => { | |
return data.message; | |
} | |
}); | |
}, | |
computed: {}, | |
watch: { | |
filter: _.debounce(function () { | |
this.filtered = this.filter; | |
console.log('Filtered!!') | |
}, 1000) | |
}, | |
methods: { | |
// List all user | |
async listAllUsers() { | |
try { | |
this.isBusy = true; | |
const promise = await axios.get(route('internal.api.user.all') + "?page=" + this.meta.current_page + "&perPage=" + this.meta.per_page); | |
const data = await promise.data; | |
console.log(data); | |
this.items = data.data; | |
this.links = data.links; | |
this.meta = data.meta; | |
this.isBusy = false; | |
this.refreshTable(); | |
return (data.data || []); | |
} catch (e) { | |
console.log('calling api error: ', e); | |
return [] | |
} | |
}, | |
// Search user | |
async searchUser() { | |
try { | |
this.isBusy = true; | |
const promise = await axios.get(route('internal.api.user.search') + "?q=" + this.filter); | |
const data = await promise.data; | |
console.log(data); | |
this.items = data.data; | |
this.links = data.links; | |
this.meta = data.meta; | |
this.refreshTable(); | |
this.isBusy = false; | |
return (data.data || []) | |
} catch (e) { | |
console.log('calling api error: ', e); | |
return [] | |
} | |
}, | |
// Create a new user via api | |
async createUser() { | |
try { | |
this.isBusy = true; | |
const promise = await axios.post(route('internal.api.user.create'), this.userDetails); | |
const data = await promise.data; | |
console.log(data); | |
this.isBusy = false; | |
this.refreshTable(); | |
return (data.data || []) | |
} catch (e) { | |
console.log('calling api error: ', e); | |
return [] | |
} | |
}, | |
// Update the user via api | |
async updateUser() { | |
try { | |
this.isBusy = true; | |
const promise = await axios.post(route('internal.api.user.update', {id: this.userDetails.id}), { | |
password: this.userDetails.password, | |
password_confirmation: this.userDetails.password_confirmation, | |
email: this.userDetails.email, | |
company: this.userDetails.company, | |
location: this.userDetails.location | |
}); | |
const data = await promise.data; | |
console.log(this.userDetails); | |
console.log(data); | |
this.isBusy = false; | |
this.refreshTable(); | |
return (data.data || []) | |
} catch (e) { | |
console.log('calling api error: ', e); | |
return [] | |
} | |
}, | |
openModalForm(method, button, item = null) { | |
if (method == 'create') { | |
this.methodForm = 'create'; | |
this.$root.$emit('bv::show::modal', 'createUserForm', button) | |
} | |
if (method == 'show') { | |
this.methodForm = 'show'; | |
this.userDetails = item; | |
this.$root.$emit('bv::show::modal', 'showUserForm', button) | |
} | |
}, | |
hideModalForm(modal) { | |
this.userDetails = []; | |
this.editFormToggle = false; | |
this.refreshTable(); | |
this.$root.$emit('bv::hide::modal', modal) | |
}, | |
refreshTable() { | |
this.$refs.userTable.refresh() | |
} | |
} | |
} | |
</script> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment