adminjs
Version:
Admin panel for apps written in node.js
301 lines (280 loc) • 7.39 kB
JavaScript
import { overrideFromOptions } from './utils/override-from-options.js';
/**
* Decorates property
*
* @category Decorators
*/
export class PropertyDecorator {
/**
* 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
*/
/**
* Indicates if given property has been created in AdminJS and hasn't been returned by the
* database adapter
*/
/**
* Array of all subProperties which were added in {@link ResourceOption} interface rather than
* in the database
*
* @private
*/
/**
* @param {Object} opts
* @param {BaseProperty} opts.property
* @param {AdminJS} opts.admin current instance of AdminJS
* @param {PropertyOptions} opts.options
* @param {ResourceDecorator} opts.resource
*/
constructor({
property,
admin,
options = {},
resource,
path,
isVirtual
}) {
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() {
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() {
const referenceResourceId = this.referenceName();
if (referenceResourceId) {
const resource = this._admin.findResource(referenceResourceId);
return resource;
}
return null;
}
referenceName() {
return this.options.reference || this.property.reference();
}
/**
* Name of the property
*
* @returns {string}
*/
name() {
return this.property.name();
}
/**
* Resource decorator of given property
*/
resource() {
return this._resource;
}
/**
* Label of a property
*
* @return {string}
*/
label() {
return this.propertyPath;
}
/**
* Property type
*
* @returns {PropertyType}
*/
type() {
if (typeof this.options.reference === 'string') {
return 'reference';
}
return overrideFromOptions('type', this.property, this.options);
}
/**
* If given property has limited number of available values
* it returns them.
*
* @returns {Array<{value: string, label: string}>}
*/
availableValues() {
if (this.options.availableValues) {
return this.options.availableValues;
}
const values = this.property.availableValues();
if (values) {
return values.map(value => ({
value,
label: value
}));
}
return null;
}
isArray() {
if (typeof this.options.isArray !== 'undefined') {
return !!this.options.isArray;
}
return this.property.isArray();
}
isDraggable() {
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) {
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() {
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() {
return !!overrideFromOptions('isId', this.property, this.options);
}
/**
* If property should be marked as a required with a star (*)
*
* @return {boolean}
*/
isRequired() {
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() {
return !!overrideFromOptions('isTitle', this.property, this.options);
}
/**
* If property should be disabled in the UI
*
* @return {boolean}
*/
isDisabled() {
return !!this.options.isDisabled;
}
/**
* Returns JSON representation of a property
*
* @param {PropertyPlace} [where]
*
* @return {PropertyJSON}
*/
toJSON(where) {
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 || {},
description: this.options.description ? this.options.description : undefined
};
}
/**
* Decorates subProperties
*
* @return {Array<PropertyDecorator>} decorated subProperties
*/
subProperties() {
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) {
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
*/
getOptionsForSubProperty(propertyPath) {
const propertyOptions = (this._resource.options || {}).properties || {};
return {
...propertyOptions[propertyPath]
};
}
}
export default PropertyDecorator;