2019-11-06 20:18:07 -06:00
|
|
|
<template>
|
2019-11-08 19:45:58 -06:00
|
|
|
<div class="model-crud" :class="getModelSlug()">
|
2019-11-06 20:18:07 -06:00
|
|
|
<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>
|
2020-02-26 21:31:38 -06:00
|
|
|
<li v-if="mode == 'editing' || mode == 'executing' || mode == 'deleting'">
|
|
|
|
<!-- <router-link :to="getRowPathPrefix() + '/' + record.uuid">{{ renderHeaderLabel(record) }}</router-link> -->
|
|
|
|
<router-link :to="getModelPathPrefix() + '/' + record.uuid">{{ renderHeaderLabel(record) }}</router-link>
|
2019-11-06 20:18:07 -06:00
|
|
|
</li>
|
|
|
|
<li v-if="mode == 'editing'">
|
|
|
|
Edit
|
|
|
|
</li>
|
2020-02-26 21:31:38 -06:00
|
|
|
<li v-if="mode == 'executing'">
|
|
|
|
Execute
|
|
|
|
</li>
|
2019-11-06 20:18:07 -06:00
|
|
|
<li v-if="mode == 'deleting'">
|
|
|
|
Delete
|
|
|
|
</li>
|
|
|
|
</ul>
|
|
|
|
</nav>
|
|
|
|
|
|
|
|
<slot></slot>
|
|
|
|
|
2019-11-12 17:51:10 -06:00
|
|
|
<div v-if="showButtons"
|
2019-11-06 20:18:07 -06:00
|
|
|
class="buttons">
|
|
|
|
<b-button type="is-primary"
|
2020-02-26 17:45:58 -06:00
|
|
|
:icon-left="saveButtonIcon"
|
2019-11-06 20:18:07 -06:00
|
|
|
:disabled="saveDisabled"
|
|
|
|
@click="save()">
|
2020-02-26 17:45:58 -06:00
|
|
|
{{ saveButtonText }}
|
2019-11-06 20:18:07 -06:00
|
|
|
</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"
|
2020-02-23 21:09:51 -06:00
|
|
|
:to="getViewURL()">
|
2019-11-06 20:18:07 -06:00
|
|
|
Cancel
|
|
|
|
</b-button>
|
|
|
|
</div>
|
|
|
|
|
2020-02-23 21:09:51 -06:00
|
|
|
<div v-if="shouldAllowEdit() || shouldAllowDelete()" class="buttons">
|
|
|
|
|
|
|
|
<br /><br />
|
|
|
|
|
|
|
|
<b-button v-if="shouldAllowEdit()"
|
|
|
|
type="is-primary"
|
|
|
|
icon-left="edit"
|
|
|
|
tag="router-link"
|
|
|
|
:to="getEditURL()">
|
|
|
|
Edit This
|
|
|
|
</b-button>
|
|
|
|
|
|
|
|
<b-button v-if="shouldAllowDelete()"
|
|
|
|
type="is-danger"
|
|
|
|
icon-left="trash"
|
|
|
|
tag="router-link"
|
|
|
|
:to="getDeleteURL()">
|
|
|
|
Delete This
|
|
|
|
</b-button>
|
|
|
|
</div>
|
|
|
|
|
2019-11-11 11:27:55 -06:00
|
|
|
<slot name="quick-entry"></slot>
|
|
|
|
|
2019-11-06 20:18:07 -06:00
|
|
|
<div v-if="hasRows && mode == 'viewing'">
|
2019-11-12 19:05:24 -06:00
|
|
|
<slot name="row-filters"></slot>
|
2019-11-06 20:18:07 -06:00
|
|
|
<b-menu>
|
|
|
|
<b-menu-list>
|
2019-11-06 22:29:59 -06:00
|
|
|
<b-menu-item v-for="row in rowData.data"
|
2019-11-06 20:18:07 -06:00
|
|
|
:key="row.uuid"
|
|
|
|
tag="router-link"
|
2019-11-15 10:30:51 -06:00
|
|
|
:to="getRowRoute(row)">
|
2019-11-06 20:18:07 -06:00
|
|
|
<template slot="label" slot-scope="props">
|
|
|
|
<span v-html="renderRowLabel(row)"></span>
|
|
|
|
</template>
|
|
|
|
</b-menu-item>
|
|
|
|
</b-menu-list>
|
|
|
|
</b-menu>
|
2019-11-06 22:29:59 -06:00
|
|
|
<b-pagination v-if="rowsPaginated"
|
|
|
|
:total="rowData.total"
|
|
|
|
:current.sync="rowPage"
|
|
|
|
:per-page="rowsPerPage"
|
|
|
|
@change="changeRowPagination"
|
|
|
|
>
|
|
|
|
<!-- icon-pack="fas" -->
|
|
|
|
</b-pagination>
|
2019-11-06 20:18:07 -06:00
|
|
|
</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,
|
|
|
|
},
|
2019-11-06 22:29:59 -06:00
|
|
|
rowsPaginated: {
|
|
|
|
type: Boolean,
|
|
|
|
default: true,
|
|
|
|
},
|
|
|
|
rowsPerPage: {
|
|
|
|
type: Number,
|
|
|
|
default: 20,
|
|
|
|
},
|
2019-11-15 10:30:51 -06:00
|
|
|
rowRouteGetter: Function,
|
2019-11-06 20:18:07 -06:00
|
|
|
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,
|
|
|
|
},
|
2020-02-23 21:09:51 -06:00
|
|
|
allowDelete: {
|
|
|
|
type: Boolean,
|
|
|
|
default: false,
|
|
|
|
},
|
2019-11-12 17:51:10 -06:00
|
|
|
hideButtons: {
|
|
|
|
type: Boolean,
|
|
|
|
default: false,
|
|
|
|
},
|
2019-11-06 20:18:07 -06:00
|
|
|
saveDisabled: {
|
|
|
|
type: Boolean,
|
|
|
|
default: false,
|
|
|
|
},
|
2020-02-26 17:45:58 -06:00
|
|
|
saveButtonText: {
|
|
|
|
type: String,
|
|
|
|
default: "Save Data",
|
|
|
|
},
|
|
|
|
saveButtonIcon: {
|
|
|
|
type: String,
|
|
|
|
default: 'save',
|
|
|
|
},
|
2019-11-06 20:18:07 -06:00
|
|
|
},
|
|
|
|
data: function() {
|
|
|
|
return {
|
|
|
|
record: {},
|
2019-11-06 22:29:59 -06:00
|
|
|
rowData: {},
|
|
|
|
rowPage: 1,
|
2019-11-06 20:18:07 -06:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2019-11-12 17:51:10 -06:00
|
|
|
computed: {
|
|
|
|
showButtons: function() {
|
|
|
|
if (this.hideButtons) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
if (this.mode == 'creating') {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
if (this.mode == 'editing') {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
2019-11-06 20:18:07 -06:00
|
|
|
// 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() {
|
|
|
|
|
2019-11-12 11:49:07 -06:00
|
|
|
// TODO: this seems like a "good" idea, but in practice, when reloading
|
|
|
|
// the page/app via browser (Ctrl+Shift+R), the app must re-fetch the
|
|
|
|
// session details before it knows which user/permissions are in
|
|
|
|
// effect, and that takes "too long" which means these checks fail!
|
|
|
|
|
|
|
|
// // redirect if user doesn't have permission to be here
|
|
|
|
// if ((this.mode == 'viewing' && !this.hasModelPerm('view'))
|
|
|
|
// || (this.mode == 'creating' && !this.hasModelPerm('create'))
|
|
|
|
// || (this.mode == 'editing' && !this.hasModelPerm('edit'))
|
|
|
|
// || (this.mode == 'deleting' && !this.hasModelPerm('delete'))) {
|
|
|
|
// 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() + '/')
|
|
|
|
// return
|
|
|
|
// }
|
2019-11-06 20:18:07 -06:00
|
|
|
|
|
|
|
// 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'
|
|
|
|
},
|
|
|
|
|
2020-02-23 21:09:51 -06:00
|
|
|
getViewURL() {
|
|
|
|
if (this.isRow) {
|
|
|
|
return `${this.getRowPathPrefix()}/${this.record.uuid}`
|
|
|
|
}
|
|
|
|
return `${this.getModelPathPrefix()}/${this.record.uuid}`
|
|
|
|
},
|
|
|
|
|
|
|
|
getEditURL() {
|
|
|
|
if (this.isRow) {
|
|
|
|
return `${this.getRowPathPrefix()}/${this.record.uuid}/edit`
|
|
|
|
}
|
|
|
|
return `${this.getModelPathPrefix()}/${this.record.uuid}/edit`
|
|
|
|
},
|
|
|
|
|
|
|
|
getDeleteURL() {
|
|
|
|
if (this.isRow) {
|
|
|
|
return `${this.getRowPathPrefix()}/${this.record.uuid}/delete`
|
|
|
|
}
|
|
|
|
return `${this.getModelPathPrefix()}/${this.record.uuid}/delete`
|
|
|
|
},
|
|
|
|
|
2019-11-06 20:18:07 -06:00
|
|
|
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,
|
|
|
|
}
|
2019-11-06 22:29:59 -06:00
|
|
|
if (this.rowsPaginated) {
|
|
|
|
params.per_page = this.rowsPerPage
|
|
|
|
params.page = this.rowPage
|
|
|
|
}
|
2019-11-06 20:18:07 -06:00
|
|
|
this.$http.get(this.getApiRowsUrl(), {params: params}).then(response => {
|
2019-11-06 22:29:59 -06:00
|
|
|
this.rowData = response.data
|
2019-11-06 20:18:07 -06:00
|
|
|
|
|
|
|
}, 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',
|
|
|
|
})
|
|
|
|
}
|
|
|
|
})
|
|
|
|
},
|
|
|
|
|
2019-11-15 10:30:51 -06:00
|
|
|
getRowRoute(row) {
|
|
|
|
if (this.rowRouteGetter) {
|
|
|
|
return this.rowRouteGetter(row)
|
|
|
|
}
|
|
|
|
return this.getRowPathPrefix() + '/' + row.uuid
|
|
|
|
},
|
|
|
|
|
2019-11-06 22:29:59 -06:00
|
|
|
changeRowPagination(value) {
|
|
|
|
this.fetchRows(this.record.uuid)
|
|
|
|
},
|
|
|
|
|
2020-02-23 21:09:51 -06:00
|
|
|
shouldAllowEdit() {
|
|
|
|
if (!this.allowEdit) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
if (this.mode != 'viewing') {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
if (!this.hasModelPerm('edit')) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
},
|
|
|
|
|
|
|
|
shouldAllowDelete() {
|
|
|
|
if (!this.allowDelete) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
if (this.mode == 'creating' || this.mode == 'deleting') {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
if (!this.hasModelPerm('delete')) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
},
|
|
|
|
|
2019-11-06 20:18:07 -06:00
|
|
|
save() {
|
|
|
|
let url = this.getApiIndexUrl()
|
|
|
|
if (this.mode != 'creating') {
|
|
|
|
url = this.getApiObjectUrl() + this.record.uuid
|
|
|
|
}
|
|
|
|
this.$emit('save', url)
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
</script>
|