Add "model crud" component
This commit is contained in:
parent
2c728216a1
commit
c87fd1ab35
|
@ -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,
|
||||
}
|
||||
|
|
361
src/components/model-crud/ByjoveModelCrud.vue
Normal file
361
src/components/model-crud/ByjoveModelCrud.vue
Normal 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>
|
||||
<!-- {{ renderParentHeaderLabel(record) }} -->
|
||||
</li>
|
||||
<li v-if="mode == 'creating'">
|
||||
New
|
||||
</li>
|
||||
<li v-if="mode == 'viewing'">
|
||||
{{ 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'">
|
||||
Edit
|
||||
</li>
|
||||
<li v-if="mode == 'deleting'">
|
||||
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>
|
28
src/components/model-crud/index.js
Normal file
28
src/components/model-crud/index.js
Normal 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
|
Loading…
Reference in a new issue