Add "model crud" component
This commit is contained in:
		
							parent
							
								
									2c728216a1
								
							
						
					
					
						commit
						c87fd1ab35
					
				
					 3 changed files with 391 additions and 0 deletions
				
			
		| 
						 | 
				
			
			@ -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…
	
	Add table
		Add a link
		
	
		Reference in a new issue