Files
site/nova/resources/js/views/UpdateAttached.vue

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>