Files
online.tbbank.gov.tm-larave…/nova/resources/js/fields/Form/BelongsToField.vue
2024-09-01 18:54:23 +05:00

547 lines
15 KiB
Vue

<template>
<DefaultField
:field="currentField"
:errors="errors"
:show-help-text="showHelpText"
:full-width-content="fullWidthContent"
>
<template #field>
<div class="flex items-center">
<SearchInput
v-if="useSearchInput"
:dusk="`${field.resourceName}-search-input`"
:disabled="currentlyIsReadonly"
@input="performResourceSearch"
@clear="clearResourceSelection"
@selected="selectResource"
:has-error="hasError"
:debounce="currentField.debounce"
:value="selectedResource"
:data="filteredResources"
:clearable="
currentField.nullable ||
editingExistingResource ||
viaRelatedResource ||
createdViaRelationModal
"
trackBy="value"
class="w-full"
:mode="mode"
>
<div v-if="selectedResource" class="flex items-center">
<div v-if="selectedResource.avatar" class="mr-3">
<img
:src="selectedResource.avatar"
class="w-8 h-8 rounded-full block"
/>
</div>
{{ selectedResource.display }}
</div>
<template #option="{ selected, option }">
<SearchInputResult
:option="option"
:selected="selected"
:with-subtitles="currentField.withSubtitles"
/>
</template>
</SearchInput>
<SelectControl
v-else
class="w-full"
:has-error="hasError"
:dusk="`${field.resourceName}-select`"
:disabled="currentlyIsReadonly"
:options="availableResources"
v-model:selected="selectedResourceId"
@change="selectResourceFromSelectControl"
label="display"
>
<option value="" selected :disabled="!currentField.nullable">
{{ placeholder }}
</option>
</SelectControl>
<CreateRelationButton
v-if="canShowNewRelationModal"
v-tooltip="__('Create :resource', { resource: field.singularLabel })"
@click="openRelationModal"
:dusk="`${field.attribute}-inline-create`"
/>
</div>
<CreateRelationModal
:show="canShowNewRelationModal && relationModalOpen"
:size="field.modalSize"
@set-resource="handleSetResource"
@create-cancelled="closeRelationModal"
:resource-name="field.resourceName"
:resource-id="resourceId"
:via-relationship="viaRelationship"
:via-resource="viaResource"
:via-resource-id="viaResourceId"
/>
<TrashedCheckbox
v-if="shouldShowTrashed"
class="mt-3"
:resource-name="field.resourceName"
:checked="withTrashed"
@input="toggleWithTrashed"
/>
</template>
</DefaultField>
</template>
<script>
import find from 'lodash/find'
import isNil from 'lodash/isNil'
import storage from '@/storage/BelongsToFieldStorage'
import {
DependentFormField,
HandlesValidationErrors,
InteractsWithQueryString,
PerformsSearches,
TogglesTrashed,
} from '@/mixins'
import filled from '@/util/filled'
import findIndex from 'lodash/findIndex'
export default {
mixins: [
DependentFormField,
HandlesValidationErrors,
InteractsWithQueryString,
PerformsSearches,
TogglesTrashed,
],
props: {
resourceId: {},
},
data: () => ({
availableResources: [],
initializingWithExistingResource: false,
createdViaRelationModal: false,
selectedResource: null,
selectedResourceId: null,
softDeletes: false,
withTrashed: false,
search: '',
relationModalOpen: false,
}),
/**
* Mount the component.
*/
mounted() {
this.initializeComponent()
},
methods: {
initializeComponent() {
this.withTrashed = false
this.selectedResourceId = this.currentField.value
if (this.editingExistingResource) {
// If a user is editing an existing resource with this relation
// we'll have a belongsToId on the field, and we should prefill
// that resource in this field
this.initializingWithExistingResource = true
this.selectedResourceId = this.currentField.belongsToId
} else if (this.viaRelatedResource) {
// If the user is creating this resource via a related resource's index
// page we'll have a viaResource and viaResourceId in the params and
// should prefill the resource in this field with that information
this.initializingWithExistingResource = true
this.selectedResourceId = this.viaResourceId
}
if (this.shouldSelectInitialResource) {
if (this.useSearchInput) {
// If we should select the initial resource and the field is
// searchable, we won't load all the resources but we will select
// the initial option.
this.getAvailableResources().then(() => this.selectInitialResource())
} else {
// If we should select the initial resource but the field is not
// searchable we should load all of the available resources into the
// field first and select the initial option.
this.initializingWithExistingResource = false
this.getAvailableResources().then(() => this.selectInitialResource())
}
} else if (!this.isSearchable && this.currentlyIsVisible) {
// If we don't need to select an initial resource because the user
// came to create a resource directly and there's no parent resource,
// and the field is searchable we'll just load all of the resources.
this.getAvailableResources()
}
this.determineIfSoftDeletes()
this.field.fill = this.fill
},
/**
* Select a resource using the <select> control
*/
selectResourceFromSelectControl(value) {
this.selectedResourceId = value
this.selectInitialResource()
if (this.field) {
this.emitFieldValueChange(this.fieldAttribute, this.selectedResourceId)
}
},
/**
* Fill the forms formData with details from this field
*/
fill(formData) {
this.fillIfVisible(
formData,
this.fieldAttribute,
this.selectedResource ? this.selectedResource.value : ''
)
this.fillIfVisible(
formData,
`${this.fieldAttribute}_trashed`,
this.withTrashed
)
},
/**
* Get the resources that may be related to this resource.
*/
getAvailableResources() {
Nova.$progress.start()
return storage
.fetchAvailableResources(this.resourceName, this.fieldAttribute, {
params: this.queryParams,
})
.then(({ data: { resources, softDeletes, withTrashed } }) => {
Nova.$progress.done()
if (this.initializingWithExistingResource || !this.isSearchable) {
this.withTrashed = withTrashed
}
if (this.viaRelatedResource) {
let selectedResource = find(resources, r =>
this.isSelectedResourceId(r.value)
)
if (
isNil(selectedResource) &&
!this.shouldIgnoreViaRelatedResource
) {
return Nova.visit('/404')
}
}
// Turn off initializing the existing resource after the first time
if (this.useSearchInput) {
this.initializingWithExistingResource = false
}
this.availableResources = resources
this.softDeletes = softDeletes
})
.catch(e => {
Nova.$progress.done()
})
},
/**
* Determine if the relatd resource is soft deleting.
*/
determineIfSoftDeletes() {
return storage
.determineIfSoftDeletes(this.field.resourceName)
.then(response => {
this.softDeletes = response.data.softDeletes
})
},
/**
* Determine if the given value is numeric.
*/
isNumeric(value) {
return !isNaN(parseFloat(value)) && isFinite(value)
},
/**
* Select the initial selected resource
*/
selectInitialResource() {
this.selectedResource = find(this.availableResources, r =>
this.isSelectedResourceId(r.value)
)
},
/**
* Toggle the trashed state of the search
*/
toggleWithTrashed() {
let currentlySelectedResource
let currentlySelectedResourceId
if (filled(this.selectedResource)) {
currentlySelectedResource = this.selectedResource
currentlySelectedResourceId = this.selectedResource.value
}
this.withTrashed = !this.withTrashed
this.selectedResource = null
this.selectedResourceId = null
if (!this.useSearchInput) {
this.getAvailableResources().then(() => {
let index = findIndex(this.availableResources, r => {
return r.value === currentlySelectedResourceId
})
if (index > -1) {
this.selectedResource = this.availableResources[index]
this.selectedResourceId = currentlySelectedResourceId
} else {
// We didn't find the resource anymore, so let's remove the selection...
this.selectedResource = null
this.selectedResourceId = null
}
})
}
},
openRelationModal() {
Nova.$emit('create-relation-modal-opened')
this.relationModalOpen = true
},
closeRelationModal() {
this.relationModalOpen = false
Nova.$emit('create-relation-modal-closed')
},
handleSetResource({ id }) {
this.closeRelationModal()
this.selectedResourceId = id
this.initializingWithExistingResource = true
this.createdViaRelationModal = true
this.getAvailableResources().then(() => {
this.selectInitialResource()
this.emitFieldValueChange(this.fieldAttribute, this.selectedResourceId)
})
},
performResourceSearch(search) {
if (this.useSearchInput) {
this.performSearch(search)
} else {
this.search = search
}
},
clearResourceSelection() {
const id = this.selectedResourceId
this.clearSelection()
if (this.viaRelatedResource && !this.createdViaRelationModal) {
this.updateQueryString({
viaResource: null,
viaResourceId: null,
viaRelationship: null,
relationshipType: null,
}).then(() => {
Nova.$router.reload({
onSuccess: () => {
this.initializingWithExistingResource = false
this.initializeComponent()
},
})
})
} else {
if (this.createdViaRelationModal) {
this.selectedResourceId = id
this.createdViaRelationModal = false
this.initializingWithExistingResource = true
} else if (this.editingExistingResource) {
this.initializingWithExistingResource = false
}
if (
(!this.isSearchable || this.shouldLoadFirstResource) &&
this.currentlyIsVisible
) {
this.getAvailableResources()
}
}
},
onSyncedField() {
if (this.viaRelatedResource) {
return
}
this.initializeComponent()
if (isNil(this.syncedField.value) && isNil(this.selectedResourceId)) {
this.selectInitialResource()
}
},
emitOnSyncedFieldValueChange() {
if (this.viaRelatedResource) {
return
}
this.emitFieldValueChange(this.fieldAttribute, this.selectedResourceId)
},
syncedFieldValueHasNotChanged() {
return this.isSelectedResourceId(this.currentField.value)
},
isSelectedResourceId(value) {
return (
!isNil(value) &&
value?.toString() === this.selectedResourceId?.toString()
)
},
},
computed: {
/**
* Determine if we are editing and existing resource
*/
editingExistingResource() {
return filled(this.field.belongsToId)
},
/**
* Determine if we are creating a new resource via a parent relation
*/
viaRelatedResource() {
return Boolean(
this.viaResource === this.field.resourceName &&
this.field.reverse &&
this.viaResourceId
)
},
/**
* Determine if we should select an initial resource when mounting this field
*/
shouldSelectInitialResource() {
return Boolean(
this.editingExistingResource ||
this.viaRelatedResource ||
this.currentField.value
)
},
/**
* Determine if the related resources is searchable
*/
isSearchable() {
return Boolean(this.currentField.searchable)
},
/**
* Get the query params for getting available resources
*/
queryParams() {
return {
current: this.selectedResourceId,
first: this.shouldLoadFirstResource,
search: this.search,
withTrashed: this.withTrashed,
resourceId: this.resourceId,
viaResource: this.viaResource,
viaResourceId: this.viaResourceId,
viaRelationship: this.viaRelationship,
component: this.field.dependentComponentKey,
dependsOn: this.encodedDependentFieldValues,
editing: true,
editMode:
isNil(this.resourceId) || this.resourceId === ''
? 'create'
: 'update',
}
},
shouldLoadFirstResource() {
return (
(this.initializingWithExistingResource &&
!this.shouldIgnoreViaRelatedResource) ||
Boolean(this.currentlyIsReadonly && this.selectedResourceId)
)
},
shouldShowTrashed() {
return (
this.softDeletes &&
!this.viaRelatedResource &&
!this.currentlyIsReadonly &&
this.currentField.displaysWithTrashed
)
},
authorizedToCreate() {
return find(Nova.config('resources'), resource => {
return resource.uriKey === this.field.resourceName
}).authorizedToCreate
},
canShowNewRelationModal() {
return (
this.currentField.showCreateRelationButton &&
!this.shownViaNewRelationModal &&
!this.viaRelatedResource &&
!this.currentlyIsReadonly &&
this.authorizedToCreate
)
},
/**
* Return the placeholder text for the field.
*/
placeholder() {
return this.currentField.placeholder || this.__('—')
},
/**
* Return the field options filtered by the search string.
*/
filteredResources() {
if (!this.isSearchable) {
return this.availableResources.filter(option => {
return (
option.display.toLowerCase().indexOf(this.search.toLowerCase()) >
-1 || new String(option.value).indexOf(this.search) > -1
)
})
}
return this.availableResources
},
shouldIgnoreViaRelatedResource() {
return this.viaRelatedResource && filled(this.search)
},
useSearchInput() {
return this.isSearchable || this.viaRelatedResource
},
},
}
</script>