About Crud Boilerplate
Crud Boilerplate is an example project that includes the structure to display a resource table.
It has the automatic storage in localStorage of the filters and columns that you want to show
Furthermore it is possible to create, display, edit and delete elements through pop-up windows.
Usage
Install npm and composer dependencies
composer require tightenco/ziggy
npm install -s vue babel-helper-vue-jsx-merge-props vue-tables-2 debounce vue-js-modal @myshell/alvue bootstrap jquery popper.js font-awesome vue-toast-notification
Include all dependencies in the project
Configure webpack.mix.js
mix.webpackConfig({
resolve: {
alias: {
ziggy: path.resolve('vendor/tightenco/ziggy/dist/js/route.js')
}
}
});
Configure your package.json scripts to add generation of ziggy route.js file
{
"scripts": {
"development": "php artisan ziggy:generate 'resources/js/routes.js' | cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js",
"production": "php artisan ziggy:generate 'resources/js/routes.js' | cross-env NODE_ENV=production node_modules/webpack/bin/webpack.js --no-progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js"
}
}
Configure resources/js/bootstrap.js
/**
* We'll load jQuery and the Bootstrap jQuery plugin which provides support
* for JavaScript based Bootstrap features such as modals and tabs. This
* code may be modified to fit the specific needs of your application.
*/
try {
window.Popper = require('popper.js').default;
window.$ = window.jQuery = require('jquery');
require('bootstrap');
} catch (e) {}
/**
* Next we will register the CSRF Token as a common header with Axios so that
* all outgoing HTTP requests automatically have it attached. This is just
* a simple convenience so we don't have to attach every token manually.
*/
let token = document.head.querySelector('meta[name="csrf-token"]');
if (token) {
window.axios.defaults.headers.common['X-CSRF-TOKEN'] = token.content;
} else {
console.error('CSRF token not found: https://laravel.com/docs/csrf#csrf-x-csrf-token');
}
Configure resources/js/app.js
// Import Vue
window.Vue = require("vue");
// Import plugins and settings import {ServerTable} from "vue-tables-2";
import route from "ziggy";
import {Ziggy} from "./routes"
import components from "./components";
import tablesConfig from "./tables_config";
import VModal from "vue-js-modal"import ALVue from "@myshell/alvue";
import VueToast from 'vue-toast-notification';
import 'vue-toast-notification/dist/theme-sugar.css';
// Include plugins on Vue
Vue.use(VueToast);
Vue.use(VModal, {dialog: true});
Vue.use(ALVue);
Vue.use(ServerTable, tablesConfig.options);
Vue.mixin(tablesConfig.init);
// Add ziggy method to global mixin
Vue.mixin({
methods: {
route: (name, params, absolute) => route(name, params, absolute, Ziggy),
}});
// Include components from components.js
const app = new Vue({ el: "#app",
components: components
});
Include bootstrap, font-awesome and _table.scss into resources/sass/app.scss
// Bootstrap
@import '~bootstrap/scss/bootstrap';
//Font Awesome
@import "~font-awesome/scss/font-awesome";
//Custom tables
@import "tables";
Create Table
Copy these files to your project in the same path
resources/js/tables_config.js
resources/js/components.js
resources/sass/_tables.scss
Create a columns file like this
export default {
id: {
head: "ID", // Text of head
default: true, // Show column by default
filterable: true, // Show filter on column
filter: "", // Default value for filter
sorteable: "" // Set column as sorteable
},
name: {
head: "Nombre",
default: true,
filterable: true,
filter: ""
},
}
Create a component with this template
<template>
<div class="table p-5">
<v-server-table :options="options" ref="table" :columns="columns" class=" table-borderless" :url="route('users.index',{ filters: JSON.stringify(tableInterface.debouncedFilters), columns:JSON.stringify([])})">
<div :slot="`filter__${column}`" v-for="column in filterable" v-if="headings.length">
<input type="text" class="form-control" v-model="tableInterface.filters[column]" :style="'max-width:'+(column=='id'?'50px':'auto')">
</div>
<div slot="beforeTable" class="mb-1 d-inline-block" v-if="headings.length">
<div class="dropdown d-inline">
<button class="btn btn-outline-dark dropdown-toggle" type="button" id="dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
Modificar Filtros
</button>
<div class="dropdown-menu" aria-labelledby="dropdownMenuButton">
<template v-for="column in headings.map(c => c.column).filter(c => c != 'actions')">
<p class="dropdown-item">
<input type="checkbox" v-model="tableInterface.visibleColumns[column]" style="margin-right: 5px;">
{{ headings.find(col => col.column == column).text }}
</p>
</template>
</div>
</div>
<button class="btn btn-outline-dark" id="restore-filters">Restablecer filtros</button>
</div>
<div :slot="`h__${heading.column}`" v-for="heading in headings">
<span title="" class="VueTables__heading">{{heading.text}}</span>
</div>
</v-server-table>
</div>
</template>
<script>
import UsersTableColumns from "./UsersTableColumns" // Import columns file
export default {
name: "UsersTable",
data() {
return {
columns: [],
filterable: [],
headings: [],
options: {
columns: UsersTableColumns
}
}
}
}
</script>
Add edit modal
Create a component with a modal like this
<template>
<modal name="user-modal" height="auto" @before-open="beforeOpen">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel">
{{form.method === "post" ? "Crear" : "Editar"}} usuario
</h5>
<button type="button" class="close" @click="$modal.hide('user-modal')">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
<alv-form :action="form.action" id="user-create" :method="form.method" :spinner="true"
:data-object="user" @after-done="afterDone" ref="form">
<div class="form-group">
<label>Nombre:</label>
<input type="text" class="form-control" name="name" v-model="user.name">
</div>
<div class="form-group">
<label>Correo electrónico:</label>
<input type="text" class="form-control" name="email" v-model="user.email">
</div>
<div class="form-group">
<label>Contraseña:</label>
<input type="text" class="form-control" name="password" v-model="user.password">
</div>
</alv-form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-dark" @click="$modal.hide('user-modal')">
Cerrar
</button>
<button type="submit" class="btn btn-outline-success" form="user-create">Guardar</button>
</div>
</div>
</modal>
</template>
<script>
const default_user = {
name: "",
email: "",
password: ""
};
export default {
name: "UserModal",
data() {
return {
form: {
action: null,
method: null
},
user: Object.assign({}, default_user)
}
},
methods: {
afterDone() {
Object.assign(this.user, this.$options.data().user);
this.$modal.hide("user-modal");
this.$refs.form.unsetButtonLoading();
if (this.$refs.form.submitButton.style.display !== "none") {
Vue.$toast.open({duration: 5000, message: "Información actualizada correctamente"});
this.$emit("created")
}
},
beforeOpen(event) {
if (typeof event.params.id != "undefined") {
this.form.action = this.route("users.update", event.params.id);
this.form.method = "put";
axios.get(this.route("users.show", event.params.id)).then(response => {
this.user = response.data;
this.$nextTick(function () {
if (typeof event.params.show != "undefined") {
this.$refs.form.$refs.form.querySelectorAll("[name]").forEach(e => e.disabled = true);
this.$refs.form.submitButton.style.display = "none"
} else {
this.$refs.form.$refs.form.querySelectorAll("[name]").forEach(e => e.disabled = false);
this.$refs.form.submitButton.style.display = null
}
})
});
} else {
this.user = Object.assign({}, default_user);
this.form.action = this.route("users.store");
this.form.method = "post";
}
}
}
}
</script>
Add action column table to columns file
actions: {
head: "Acciones",
default: true,
filterable: false,
}
Include actions slot on table component
<div slot="actions" slot-scope="props" class="text-center">
<button class="btn p-0 mx-1" @click="editUser(props.row.id)">
<i class="fa fa-edit"></i>
</button>
</div>
Include modal component
<user-modal @created="reloadTable"></user-modal>
Import modal component and create reload table and edit methods
import UserModal from "./UserModal"
export default {
components: {
UserModal
},
methods: {
reloadTable() {
const debouncedFilters = Object.assign({}, this.tableInterface.debouncedFilters, {reload: Math.random()});
this.$set(this.tableInterface, "debouncedFilters", debouncedFilters);
this.$modal.hide("dialog");
},
editUser(id) {
this.$modal.show("user-modal", id);
}
}
}
Add show modal
Add button show to actions slot
<button class="btn p-0 mx-1" @click="showUser(props.row.id)"><i class="fa fa-eye"></i></button>
Add show method
export default {
methods: {
showUser(id) {
this.$modal.show("user-modal", {id: id, show: true});
},
}
}
Add delete dialog
Include v-dialog component into table component
<v-dialog/>
Add button delete to actions slot
<button class="btn p-0 mx-1" @click="deleteUser(props.row.id)">
<i class="fa fa-trash"></i>
</button>
Add delete method
export default {
methods: {
deleteUser(id) {
this.$modal.show("dialog", {
title: "Eliminar usuario",
text: "¿Desea eliminar el usuario?",
buttons: [
{
title: "Cancelar"
},
{
title: "Eliminar",
handler: () => {
axios.delete(this.route("users.destroy", id)).then(this.reloadTable);
}
}
]
});
}
}
}