UNPKG

admin-bro

Version:
325 lines (294 loc) 8.57 kB
import AdminBro from '../../../admin-bro' import PropertyOptions from './property-options.interface' import BaseResource from '../../adapters/resource/base-resource' import BaseProperty, { PropertyType } from '../../adapters/property/base-property' import ResourceDecorator from '../resource/resource-decorator' import { PropertyPlace, BasePropertyJSON } from '../../../frontend/interfaces' import { overrideFromOptions } from './utils' /** * Decorates property * * @category Decorators */ class PropertyDecorator { public property: BaseProperty /** * Property path including all parents. * For root property (this without a parent) it will be its name. * But when property has children their paths will include parent path: * `parentName.subPropertyName`. * * This path serves as a key in {@link PropertyOptions} to identify which * property has to be updated */ public propertyPath: string /** * Indicates if given property has been created in AdminBro and hasn't been returned by the * database adapter */ public isVirtual: boolean private _admin: AdminBro private _resource: ResourceDecorator public options: PropertyOptions /** * Array of all subProperties which were added in {@link ResourceOption} interface rather than * in the database * * @private */ private virtualSubProperties: Array<PropertyDecorator> /** * @param {Object} opts * @param {BaseProperty} opts.property * @param {AdminBro} opts.admin current instance of AdminBro * @param {PropertyOptions} opts.options * @param {ResourceDecorator} opts.resource */ constructor({ property, admin, options = {}, resource, path, isVirtual }: { property: BaseProperty; admin: AdminBro; options?: PropertyOptions; resource: ResourceDecorator; path?: string; isVirtual?: boolean; }) { this.property = property this._admin = admin this._resource = resource this.propertyPath = path || property.name() this.isVirtual = !!isVirtual this.virtualSubProperties = [] /** * Options passed along with a given resource * @type {PropertyOptions} */ this.options = options } /** * True if given property can be sortable * * @returns {boolean} */ isSortable(): boolean { return !!overrideFromOptions('isSortable', this.property, this.options) } /** * When given property is a reference to another Resource - it returns this Resource * * @return {BaseResource} reference resource */ reference(): BaseResource | null { const referenceResourceId = this.referenceName() if (referenceResourceId) { const resource = this._admin.findResource(referenceResourceId) return resource } return null } referenceName(): string | null { return this.options.reference || this.property.reference() } /** * Name of the property * * @returns {string} */ name(): string { return this.property.name() } /** * Resource decorator of given property */ resource(): ResourceDecorator { return this._resource } /** * Label of a property * * @return {string} */ label(): string { return this._admin.translateProperty(this.propertyPath, this._resource.id()) } /** * Property type * * @returns {PropertyType} */ type(): PropertyType { if (typeof this.options.reference === 'string') { return 'reference' } return overrideFromOptions('type', this.property, this.options) as PropertyType } /** * If given property has limited number of available values * it returns them. * * @returns {Array<{value: string, label: string}>} */ availableValues(): null | Array<{value: string; label: string}> { if (this.options.availableValues) { return this.options.availableValues } const values = this.property.availableValues() if (values) { return values.map(val => ({ value: val, label: this._admin.translateProperty( `${this.propertyPath}.${val}`, this._resource.id(), { defaultValue: val }, ), })) } return null } isArray(): boolean { if (typeof this.options.isArray !== 'undefined') { return !!this.options.isArray } return this.property.isArray() } isDraggable(): boolean { if (typeof this.options.isDraggable !== 'undefined') { return this.isArray() && !!this.options.isDraggable } return this.property.isDraggable() } /** * Indicates if given property should be visible * * @param {'list' | 'edit' | 'show' | 'filter'} where */ isVisible(where: PropertyPlace): boolean { if (typeof this.options.isVisible === 'object' && this.options.isVisible !== 'null') { return !!this.options.isVisible[where] } if (typeof this.options.isVisible === 'boolean') { return this.options.isVisible } if (where === 'edit') { return this.property.isEditable() } return this.property.isVisible() } /** * Position of the field * * @return {number} */ position(): number { if (this.options.position) { return this.options.position } if (this.isTitle()) { return -1 } if (this.isId()) { return 0 } return 100 + this.property.position() } /** * If property should be treated as an ID field * * @return {boolean} */ isId(): boolean { return !!overrideFromOptions('isId', this.property, this.options) } /** * If property should be marked as a required with a star (*) * * @return {boolean} */ isRequired(): boolean { return !!overrideFromOptions('isRequired', this.property, this.options) } /** * If property should be treated as an title field * Title field is used as a link to the resource page * in the list view and in the breadcrumbs * * @return {boolean} */ isTitle(): boolean { return !!overrideFromOptions('isTitle', this.property, this.options) } /** * If property should be disabled in the UI * * @return {boolean} */ isDisabled(): boolean { return !!this.options.isDisabled } /** * Returns JSON representation of a property * * @param {PropertyPlace} [where] * * @return {PropertyJSON} */ toJSON(where?: PropertyPlace): BasePropertyJSON { return { isTitle: this.isTitle(), isId: this.isId(), position: this.position(), custom: typeof this.options.custom === 'undefined' ? {} : this.options.custom, isSortable: this.isSortable(), isRequired: this.isRequired(), availableValues: this.availableValues(), name: this.name(), propertyPath: this.propertyPath, isDisabled: this.isDisabled(), label: this.label(), type: this.type(), hideLabel: !!this.options.hideLabel, reference: this.referenceName(), components: this.options.components, subProperties: this.subProperties() .filter(subProperty => !where || subProperty.isVisible(where)) .map(subProperty => subProperty.toJSON(where)), isArray: this.isArray(), isDraggable: this.isDraggable(), resourceId: this._resource.id(), isVirtual: this.isVirtual, props: this.options.props || {}, } } /** * Decorates subProperties * * @return {Array<PropertyDecorator>} decorated subProperties */ subProperties(): Array<PropertyDecorator> { const dbSubProperties = this.property.subProperties().map((subProperty) => { const path = `${this.propertyPath}.${subProperty.name()}` const decorated = new PropertyDecorator({ property: subProperty, admin: this._admin, options: this.getOptionsForSubProperty(path), resource: this._resource, path, }) return decorated }) return [...dbSubProperties, ...this.virtualSubProperties] } addSubProperty(subProperty: PropertyDecorator): void { this.virtualSubProperties.push(subProperty) } /** * Returns PropertyOptions passed by the user for a subProperty. Furthermore * it changes property name to the nested property key. * * @param {String} propertyPath * @return {PropertyOptions} * @private */ private getOptionsForSubProperty(propertyPath: string): PropertyOptions { const propertyOptions = (this._resource.options || {}).properties || {} return { ...propertyOptions[propertyPath], } } } export default PropertyDecorator