Add "model crud" component

This commit is contained in:
Lance Edgar 2019-11-06 20:18:07 -06:00
parent 2c728216a1
commit c87fd1ab35
3 changed files with 391 additions and 0 deletions

View file

@ -3,6 +3,7 @@ import ByjoveMenu from './menu'
import ByjoveLogo from './logo'
import ByjoveFeedback from './feedback'
import ByjoveModelIndex from './model-index'
import ByjoveModelCrud from './model-crud'
export {
ByjoveApp,
@ -10,4 +11,5 @@ export {
ByjoveLogo,
ByjoveFeedback,
ByjoveModelIndex,
ByjoveModelCrud,
}

View file

@ -0,0 +1,361 @@
<template>
<div :class="getModelSlug()">
<nav class="breadcrumb" aria-label="breadcrumbs">
<ul>
<li>
<router-link :to="getModelPathPrefix() + '/'">{{ getModelIndexTitle() }}</router-link>
</li>
<li v-if="isRow">
<router-link :to="getModelPathPrefix() + '/' + record._parent_uuid">{{ renderParentHeaderLabel(record) }}</router-link>
<!-- &nbsp; {{ renderParentHeaderLabel(record) }} -->
</li>
<li v-if="mode == 'creating'">
&nbsp; New
</li>
<li v-if="mode == 'viewing'">
&nbsp; {{ renderHeaderLabel(record) }}
</li>
<li v-if="mode == 'editing' || mode == 'deleting'">
<router-link :to="getModelPathPrefix() + '/' + record.uuid">{{ renderHeaderLabel(record) }}</router-link>
</li>
<li v-if="mode == 'editing'">
&nbsp; Edit
</li>
<li v-if="mode == 'deleting'">
&nbsp; Delete
</li>
</ul>
</nav>
<slot></slot>
<b-button v-if="allowEdit && mode == 'viewing' && hasModelPerm('edit')"
type="is-primary"
tag="router-link"
:to="getModelPathPrefix() + '/' + record.uuid + '/edit'">
Edit This
</b-button>
<div v-if="mode == 'creating' || mode == 'editing'"
class="buttons">
<b-button type="is-primary"
:disabled="saveDisabled"
@click="save()">
Save
</b-button>
<b-button v-if="mode == 'creating'"
tag="router-link"
:to="getModelPathPrefix() + '/'">
Cancel
</b-button>
<b-button v-if="mode == 'editing'"
tag="router-link"
:to="getModelPathPrefix() + '/' + record.uuid">
Cancel
</b-button>
</div>
<div v-if="hasRows && mode == 'viewing'">
<b-menu>
<b-menu-list>
<b-menu-item v-for="row in rows"
:key="row.uuid"
tag="router-link"
:to="getRowPathPrefix() + '/' + row.uuid">
<template slot="label" slot-scope="props">
<span v-html="renderRowLabel(row)"></span>
</template>
</b-menu-item>
</b-menu-list>
</b-menu>
</div>
<slot name="footer"></slot>
</div>
</template>
<script>
export default {
name: 'ByjoveModelCrud',
props: {
mode: String,
modelName: String,
modelSlug: String,
modelTitle: String,
modelTitlePlural: String,
modelIndexTitle: String,
modelPermissionPrefix: String,
modelPathPrefix: String,
modelRoutePrefix: String,
apiIndexUrl: String,
apiObjectUrl: String,
labelRenderer: Function,
parentHeaderLabelRenderer: Function,
headerLabelRenderer: Function,
rowLabelRenderer: Function,
hasRows: {
type: Boolean,
default: false,
},
apiRowsUrl: String,
isRow: {
type: Boolean,
default: false,
},
rowPathPrefix: String,
rowFilters: {
type: Function,
default: (uuid) => {
return JSON.stringify([{field: 'batch_uuid', op: 'eq', value: uuid}])
},
},
allowEdit: {
type: Boolean,
default: true,
},
saveDisabled: {
type: Boolean,
default: false,
},
},
data: function() {
return {
record: {},
rows: [],
}
},
// TODO: why doesn't beforeRouteUpdate() work instead?
// cf. https://router.vuejs.org/guide/essentials/dynamic-matching.html#reacting-to-params-changes
watch: {
'$route' (to, from) {
if (to.name == (this.getModelRoutePrefix() + '.edit') && !this.hasModelPerm('edit')) {
this.$buefy.toast.open({
message: "You do not have permission to access that page.",
type: 'is-danger',
position: 'is-bottom',
})
this.$router.push(this.getModelPathPrefix() + '/')
}
// re-fetch record in case it was just changed
if (to.name != (this.getModelRoutePrefix() + '.new')) {
this.fetch(to.params.uuid)
}
},
},
// beforeRouteUpdate (to, from, next) {
// // re-fetch record in case it was just changed
// if (to.name != (this.getModelRoutePrefix() + '.new')) {
// this.fetch(to.params.uuid)
// }
// next()
// },
mounted() {
// redirect if user doesn't have permission to be here
if ((this.mode == 'creating' && !this.hasModelPerm('create'))
|| (this.mode == 'editing' && !this.hasModelPerm('edit'))) {
this.$buefy.toast.open({
message: "You do not have permission to access that page.",
type: 'is-danger',
position: 'is-bottom',
})
this.$router.push(this.getModelPathPrefix() + '/')
}
// fetch initial page data unless 'creating'
if (this.mode != 'creating') {
this.fetch(this.$route.params.uuid)
}
},
methods: {
getModelSlug() {
if (this.modelSlug) {
return this.modelSlug
}
return this.modelName.toLowerCase() + 's'
},
getModelTitle() {
if (this.modelTitle) {
return this.modelTitle
}
return this.modelName
},
getModelTitlePlural() {
if (this.modelTitlePlural) {
return this.modelTitlePlural
}
return this.getModelTitle() + 's'
},
getModelIndexTitle() {
if (this.modelIndexTitle) {
return this.modelIndexTitle
}
return this.getModelTitlePlural()
},
getModelPathPrefix() {
if (this.modelPathPrefix) {
return this.modelPathPrefix
}
return '/' + this.getModelSlug()
},
getModelRoutePrefix() {
if (this.modelRoutePrefix) {
return this.modelRoutePrefix
}
return this.getModelSlug()
},
getRowPathPrefix() {
if (this.rowPathPrefix) {
return this.rowPathPrefix
}
return '/' + this.getModelSlug() + '/rows'
},
getApiIndexUrl() {
if (this.apiIndexUrl) {
return this.apiIndexUrl
}
return '/api/' + this.getModelSlug()
},
getApiObjectUrl() {
if (this.apiObjectUrl) {
return this.apiObjectUrl
}
let url = this.getApiIndexUrl()
// drop trailing 's' then add slash
return url.slice(0, -1) + '/'
},
getApiRowsUrl() {
return this.apiRowsUrl
},
getModelPermissionPrefix() {
if (this.modelPermissionPrefix) {
return this.modelPermissionPrefix
}
return this.getModelSlug()
},
hasPerm(perm) {
// if user is root then assume permission
if (this.$store.state.user && this.$store.state.user.is_root) {
return true
}
// otherwise do true perm check for user
return this.$store.state.permissions.includes(perm)
},
hasModelPerm(perm) {
// do normal check, but first add prefix
let prefix = this.getModelPermissionPrefix()
return this.hasPerm(prefix + '.' + perm)
},
renderLabel(obj) {
if (this.labelRenderer) {
return this.labelRenderer(obj)
}
return obj._str
},
renderHeaderLabel(obj) {
if (this.headerLabelRenderer) {
return this.headerLabelRenderer(obj)
}
return this.renderLabel(obj)
},
renderParentHeaderLabel(obj) {
if (this.parentHeaderLabelRenderer) {
return this.parentHeaderLabelRenderer(obj)
}
return this.renderLabel(obj)
},
renderRowLabel(row) {
if (this.rowLabelRenderer) {
return this.rowLabelRenderer(row)
}
return row._str
},
fetch(uuid) {
this.$http.get(this.getApiObjectUrl() + uuid).then(response => {
this.record = response.data.data
this.$emit('refresh', this.record)
if (this.hasRows) {
this.fetchRows(uuid)
}
}, response => {
if (response.status == 403) { // forbidden; redirect to model index
this.$buefy.toast.open({
message: "You do not have permission to access that page.",
type: 'is-danger',
position: 'is-bottom',
})
this.$router.push(this.getModelPathPrefix() + '/')
} else {
this.$buefy.toast.open({
message: "Failed to fetch page data!",
type: 'is-danger',
position: 'is-bottom',
})
}
})
},
fetchRows(uuid) {
let params = {
filters: this.rowFilters(uuid),
orderBy: 'modified',
ascending: 0,
}
this.$http.get(this.getApiRowsUrl(), {params: params}).then(response => {
this.rows = response.data.data
}, response => {
if (response.status == 403) { // forbidden; redirect to home page
this.$buefy.toast.open({
message: "You do not have permission to access that page.",
type: 'is-danger',
position: 'is-bottom',
})
this.$router.push('/')
} else {
this.$buefy.toast.open({
message: "Failed to fetch page data!",
type: 'is-danger',
position: 'is-bottom',
})
}
})
},
save() {
let url = this.getApiIndexUrl()
if (this.mode != 'creating') {
url = this.getApiObjectUrl() + this.record.uuid
}
this.$emit('save', url)
},
},
}
</script>

View file

@ -0,0 +1,28 @@
// Import vue component
import ByjoveModelCrud from './ByjoveModelCrud.vue'
// Declare install function executed by Vue.use()
export function install(Vue) {
if (install.installed) return;
install.installed = true;
Vue.component('ByjoveModelCrud', ByjoveModelCrud);
}
// Create module definition for Vue.use()
const plugin = {
install,
};
// Auto-install when vue is found (eg. in browser via <script> tag)
let GlobalVue = null;
if (typeof window !== 'undefined') {
GlobalVue = window.Vue;
} else if (typeof global !== 'undefined') {
GlobalVue = global.Vue;
}
if (GlobalVue) {
GlobalVue.use(plugin);
}
// To allow use as module (npm/webpack/etc.) export component
export default ByjoveModelCrud