542 lines
14 KiB
Vue
Executable File
542 lines
14 KiB
Vue
Executable File
<template>
|
|
<loading-view :loading="loading">
|
|
<custom-update-attached-header
|
|
class="mb-3"
|
|
:resource-name="resourceName"
|
|
:resource-id="resourceId"
|
|
/>
|
|
|
|
<heading class="mb-3" v-if="relatedResourceLabel && title">
|
|
{{
|
|
__('Update attached :resource: :title', {
|
|
resource: relatedResourceLabel,
|
|
title: title,
|
|
})
|
|
}}
|
|
</heading>
|
|
|
|
<form
|
|
v-if="field"
|
|
@submit.prevent="updateAttachedResource"
|
|
@change="onUpdateFormStatus"
|
|
autocomplete="off"
|
|
>
|
|
<card class="overflow-hidden mb-8">
|
|
<!-- Related Resource -->
|
|
<div
|
|
v-if="viaResourceField"
|
|
dusk="via-resource-field"
|
|
class="flex border-b border-40"
|
|
>
|
|
<div class="w-1/5 px-8 py-6">
|
|
<label
|
|
:for="viaResourceField.name"
|
|
class="inline-block text-80 pt-2 leading-tight"
|
|
>
|
|
{{ viaResourceField.name }}
|
|
</label>
|
|
</div>
|
|
<div class="py-6 px-8 w-1/2">
|
|
<span class="inline-block font-bold text-80 pt-2">
|
|
{{ viaResourceField.display }}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
<default-field
|
|
:field="field"
|
|
:errors="validationErrors"
|
|
:show-help-text="field.helpText != null"
|
|
>
|
|
<template slot="field">
|
|
<select-control
|
|
class="form-control form-select w-full"
|
|
dusk="attachable-select"
|
|
:class="{
|
|
'border-danger': validationErrors.has(field.attribute),
|
|
}"
|
|
:data-testid="`${field.resourceName}-select`"
|
|
@change="selectResourceFromSelectControl"
|
|
disabled
|
|
:options="availableResources"
|
|
:label="'display'"
|
|
:selected="selectedResourceId"
|
|
>
|
|
<option value="" disabled selected>
|
|
{{ __('Choose :field', { field: field.name }) }}
|
|
</option>
|
|
</select-control>
|
|
</template>
|
|
</default-field>
|
|
|
|
<!-- Pivot Fields -->
|
|
<div v-for="field in fields">
|
|
<component
|
|
:is="'form-' + field.component"
|
|
:resource-name="resourceName"
|
|
:resource-id="resourceId"
|
|
:field="field"
|
|
:errors="validationErrors"
|
|
:related-resource-name="relatedResourceName"
|
|
:related-resource-id="relatedResourceId"
|
|
:via-resource="viaResource"
|
|
:via-resource-id="viaResourceId"
|
|
:via-relationship="viaRelationship"
|
|
:show-help-text="field.helpText != null"
|
|
/>
|
|
</div>
|
|
</card>
|
|
<!-- Attach Button -->
|
|
<div class="flex items-center">
|
|
<cancel-button @click="$router.back()" />
|
|
|
|
<progress-button
|
|
class="mr-3"
|
|
dusk="update-and-continue-editing-button"
|
|
@click.native="updateAndContinueEditing"
|
|
:disabled="isWorking"
|
|
:processing="submittedViaUpdateAndContinueEditing"
|
|
>
|
|
{{ __('Update & Continue Editing') }}
|
|
</progress-button>
|
|
|
|
<progress-button
|
|
dusk="update-button"
|
|
type="submit"
|
|
:disabled="isWorking"
|
|
:processing="submittedViaUpdateAttachedResource"
|
|
>
|
|
{{
|
|
__('Update :resource', {
|
|
resource: relatedResourceLabel,
|
|
})
|
|
}}
|
|
</progress-button>
|
|
</div>
|
|
</form>
|
|
</loading-view>
|
|
</template>
|
|
|
|
<script>
|
|
import _ from 'lodash'
|
|
import {
|
|
PerformsSearches,
|
|
TogglesTrashed,
|
|
Errors,
|
|
PreventsFormAbandonment,
|
|
} from 'laravel-nova'
|
|
|
|
export default {
|
|
mixins: [PerformsSearches, TogglesTrashed, PreventsFormAbandonment],
|
|
|
|
metaInfo() {
|
|
if (this.relatedResourceLabel && this.title) {
|
|
return {
|
|
title: this.__('Update attached :resource: :title', {
|
|
resource: this.relatedResourceLabel,
|
|
title: this.title,
|
|
}),
|
|
}
|
|
}
|
|
},
|
|
|
|
props: {
|
|
resourceName: {
|
|
type: String,
|
|
required: true,
|
|
},
|
|
resourceId: {
|
|
required: true,
|
|
},
|
|
relatedResourceName: {
|
|
type: String,
|
|
required: true,
|
|
},
|
|
relatedResourceId: {
|
|
required: true,
|
|
},
|
|
viaResource: {
|
|
default: '',
|
|
},
|
|
viaResourceId: {
|
|
default: '',
|
|
},
|
|
viaRelationship: {
|
|
default: '',
|
|
},
|
|
viaPivotId: {
|
|
default: null,
|
|
},
|
|
polymorphic: {
|
|
default: false,
|
|
},
|
|
},
|
|
|
|
data: () => ({
|
|
loading: true,
|
|
submittedViaUpdateAndContinueEditing: false,
|
|
submittedViaUpdateAttachedResource: false,
|
|
viaResourceField: null,
|
|
field: null,
|
|
softDeletes: false,
|
|
fields: [],
|
|
validationErrors: new Errors(),
|
|
selectedResource: null,
|
|
selectedResourceId: null,
|
|
lastRetrievedAt: null,
|
|
title: null,
|
|
}),
|
|
|
|
created() {
|
|
if (Nova.missingResource(this.resourceName))
|
|
return this.$router.push({ name: '404' })
|
|
},
|
|
|
|
/**
|
|
* Mount the component.
|
|
*/
|
|
mounted() {
|
|
this.initializeComponent()
|
|
},
|
|
|
|
methods: {
|
|
/**
|
|
* Initialize the component's data.
|
|
*/
|
|
async initializeComponent() {
|
|
this.softDeletes = false
|
|
this.disableWithTrashed()
|
|
this.clearSelection()
|
|
await this.getField()
|
|
await this.getPivotFields()
|
|
await this.getAvailableResources()
|
|
this.resetErrors()
|
|
|
|
this.selectedResourceId = this.relatedResourceId
|
|
|
|
this.selectInitialResource()
|
|
|
|
this.updateLastRetrievedAtTimestamp()
|
|
},
|
|
|
|
/**
|
|
* Get the many-to-many relationship field.
|
|
*/
|
|
async getField() {
|
|
this.field = null
|
|
|
|
const { data: field } = await Nova.request().get(
|
|
'/nova-api/' + this.resourceName + '/field/' + this.viaRelationship,
|
|
{
|
|
params: {
|
|
relatable: true,
|
|
},
|
|
}
|
|
)
|
|
|
|
this.field = field
|
|
|
|
if (this.field.searchable) {
|
|
this.determineIfSoftDeletes()
|
|
}
|
|
|
|
this.loading = false
|
|
},
|
|
|
|
/**
|
|
* Get all of the available pivot fields for the relationship.
|
|
*/
|
|
async getPivotFields() {
|
|
this.fields = []
|
|
|
|
const {
|
|
data: { title, fields },
|
|
} = await Nova.request()
|
|
.get(
|
|
`/nova-api/${this.resourceName}/${this.resourceId}/update-pivot-fields/${this.relatedResourceName}/${this.relatedResourceId}`,
|
|
{
|
|
params: {
|
|
editing: true,
|
|
editMode: 'update-attached',
|
|
viaRelationship: this.viaRelationship,
|
|
viaPivotId: this.viaPivotId,
|
|
},
|
|
}
|
|
)
|
|
.catch(error => {
|
|
if (error.response.status == 404) {
|
|
this.$router.push({ name: '404' })
|
|
return
|
|
}
|
|
})
|
|
|
|
this.title = title
|
|
this.fields = fields
|
|
|
|
_.each(this.fields, field => {
|
|
if (field) {
|
|
field.fill = () => ''
|
|
}
|
|
})
|
|
},
|
|
|
|
resetErrors() {
|
|
this.validationErrors = new Errors()
|
|
},
|
|
|
|
/**
|
|
* Get all of the available resources for the current search / trashed state.
|
|
*/
|
|
async getAvailableResources(search = '') {
|
|
try {
|
|
const response = await Nova.request().get(
|
|
`/nova-api/${this.resourceName}/${this.resourceId}/attachable/${this.relatedResourceName}`,
|
|
{
|
|
params: {
|
|
search,
|
|
current: this.relatedResourceId,
|
|
first: true,
|
|
withTrashed: this.withTrashed,
|
|
},
|
|
}
|
|
)
|
|
|
|
this.viaResourceField = response.data.viaResource
|
|
this.availableResources = response.data.resources
|
|
this.withTrashed = response.data.withTrashed
|
|
this.softDeletes = response.data.softDeletes
|
|
} catch (error) {}
|
|
},
|
|
|
|
/**
|
|
* Determine if the related resource is soft deleting.
|
|
*/
|
|
determineIfSoftDeletes() {
|
|
Nova.request()
|
|
.get('/nova-api/' + this.relatedResourceName + '/soft-deletes')
|
|
.then(response => {
|
|
this.softDeletes = response.data.softDeletes
|
|
})
|
|
},
|
|
|
|
/**
|
|
* Update the attached resource.
|
|
*/
|
|
async updateAttachedResource() {
|
|
this.submittedViaUpdateAttachedResource = true
|
|
|
|
try {
|
|
await this.updateRequest()
|
|
|
|
this.submittedViaUpdateAttachedResource = false
|
|
this.canLeave = true
|
|
|
|
Nova.success(this.__('The resource was updated!'))
|
|
|
|
this.$router.push({
|
|
name: 'detail',
|
|
params: {
|
|
resourceName: this.resourceName,
|
|
resourceId: this.resourceId,
|
|
},
|
|
})
|
|
} catch (error) {
|
|
window.scrollTo(0, 0)
|
|
|
|
this.submittedViaUpdateAttachedResource = false
|
|
if (
|
|
this.resourceInformation &&
|
|
this.resourceInformation.preventFormAbandonment
|
|
) {
|
|
this.canLeave = false
|
|
}
|
|
|
|
if (error.response.status == 422) {
|
|
this.validationErrors = new Errors(error.response.data.errors)
|
|
Nova.error(this.__('There was a problem submitting the form.'))
|
|
}
|
|
|
|
if (error.response.status == 409) {
|
|
Nova.error(
|
|
this.__(
|
|
'Another user has updated this resource since this page was loaded. Please refresh the page and try again.'
|
|
)
|
|
)
|
|
}
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Update the resource and reset the form
|
|
*/
|
|
async updateAndContinueEditing() {
|
|
this.submittedViaUpdateAndContinueEditing = true
|
|
|
|
try {
|
|
await this.updateRequest()
|
|
|
|
this.submittedViaUpdateAndContinueEditing = false
|
|
|
|
Nova.success(this.__('The resource was updated!'))
|
|
|
|
// Reset the form by refetching the fields
|
|
this.initializeComponent()
|
|
} catch (error) {
|
|
this.submittedViaUpdateAndContinueEditing = false
|
|
|
|
if (error.response.status == 422) {
|
|
this.validationErrors = new Errors(error.response.data.errors)
|
|
Nova.error(this.__('There was a problem submitting the form.'))
|
|
}
|
|
|
|
if (error.response.status == 409) {
|
|
Nova.error(
|
|
this.__(
|
|
'Another user has updated this resource since this page was loaded. Please refresh the page and try again.'
|
|
)
|
|
)
|
|
}
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Send an update request for this resource
|
|
*/
|
|
updateRequest() {
|
|
return Nova.request().post(
|
|
`/nova-api/${this.resourceName}/${this.resourceId}/update-attached/${this.relatedResourceName}/${this.relatedResourceId}`,
|
|
this.updateAttachmentFormData,
|
|
{
|
|
params: {
|
|
editing: true,
|
|
editMode: 'update-attached',
|
|
viaPivotId: this.viaPivotId,
|
|
},
|
|
}
|
|
)
|
|
},
|
|
|
|
/**
|
|
* Select a resource using the <select> control
|
|
*/
|
|
selectResourceFromSelectControl(e) {
|
|
this.selectedResourceId = e.target.value
|
|
this.selectInitialResource()
|
|
|
|
if (this.field) {
|
|
Nova.$emit(this.field.attribute + '-change', this.selectedResourceId)
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Toggle the trashed state of the search
|
|
*/
|
|
toggleWithTrashed() {
|
|
this.withTrashed = !this.withTrashed
|
|
|
|
// Reload the data if the component doesn't support searching
|
|
if (!this.isSearchable) {
|
|
this.getAvailableResources()
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Select the initial selected resource
|
|
*/
|
|
selectInitialResource() {
|
|
this.selectedResource = _.find(
|
|
this.availableResources,
|
|
r => r.value == this.selectedResourceId
|
|
)
|
|
},
|
|
|
|
/**
|
|
* Update the last retrieved at timestamp to the current UNIX timestamp.
|
|
*/
|
|
updateLastRetrievedAtTimestamp() {
|
|
this.lastRetrievedAt = Math.floor(new Date().getTime() / 1000)
|
|
},
|
|
|
|
/**
|
|
* Prevent accidental abandonment only if form was changed.
|
|
*/
|
|
onUpdateFormStatus() {
|
|
if (
|
|
this.resourceInformation &&
|
|
this.resourceInformation.preventFormAbandonment
|
|
) {
|
|
this.updateFormStatus()
|
|
}
|
|
},
|
|
},
|
|
|
|
computed: {
|
|
/**
|
|
* Get the attachment endpoint for the relationship type.
|
|
*/
|
|
attachmentEndpoint() {
|
|
return this.polymorphic
|
|
? '/nova-api/' +
|
|
this.resourceName +
|
|
'/' +
|
|
this.resourceId +
|
|
'/attach-morphed/' +
|
|
this.relatedResourceName
|
|
: '/nova-api/' +
|
|
this.resourceName +
|
|
'/' +
|
|
this.resourceId +
|
|
'/attach/' +
|
|
this.relatedResourceName
|
|
},
|
|
|
|
/*
|
|
* Get the form data for the resource attachment update.
|
|
*/
|
|
updateAttachmentFormData() {
|
|
return _.tap(new FormData(), formData => {
|
|
_.each(this.fields, field => {
|
|
field.fill(formData)
|
|
})
|
|
|
|
formData.append('viaRelationship', this.viaRelationship)
|
|
|
|
if (!this.selectedResource) {
|
|
formData.append(this.relatedResourceName, '')
|
|
} else {
|
|
formData.append(this.relatedResourceName, this.selectedResource.value)
|
|
}
|
|
|
|
formData.append(this.relatedResourceName + '_trashed', this.withTrashed)
|
|
formData.append('_retrieved_at', this.lastRetrievedAt)
|
|
})
|
|
},
|
|
|
|
/**
|
|
* Get the label for the related resource.
|
|
*/
|
|
relatedResourceLabel() {
|
|
if (this.field) {
|
|
return this.field.singularLabel
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Determine if the related resources is searchable
|
|
*/
|
|
isSearchable() {
|
|
return this.field.searchable
|
|
},
|
|
|
|
/**
|
|
* Determine if the form is being processed
|
|
*/
|
|
isWorking() {
|
|
return (
|
|
this.submittedViaUpdateAttachedResource ||
|
|
this.submittedViaUpdateAndContinueEditing
|
|
)
|
|
},
|
|
},
|
|
}
|
|
</script>
|