@itwin/presentation-common
Version:
Common pieces for iModel.js presentation packages
728 lines • 27.9 kB
JavaScript
"use strict";
/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
/** @packageDocumentation
* @module Content
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.FieldDescriptor = exports.FieldDescriptorType = exports.getFieldByDescriptor = exports.getFieldByName = exports.NestedContentField = exports.StructPropertiesField = exports.ArrayPropertiesField = exports.PropertiesField = exports.Field = void 0;
const core_bentley_1 = require("@itwin/core-bentley");
const EC_js_1 = require("../EC.js");
const Error_js_1 = require("../Error.js");
const RelatedPropertiesSpecification_js_1 = require("../rules/content/modifiers/RelatedPropertiesSpecification.js");
const Utils_js_1 = require("../Utils.js");
const Property_js_1 = require("./Property.js");
function isPropertiesField(field) {
return !!field.properties;
}
function isArrayPropertiesField(field) {
return !!field.itemsField;
}
function isStructPropertiesField(field) {
return !!field.memberFields;
}
function isNestedContentField(field) {
return !!field.nestedFields;
}
/**
* Describes a single content field. A field is usually represented as a grid column
* or a property pane row.
*
* @public
*/
class Field {
/** Category information */
category;
/** Unique name */
name;
/** Display label */
label;
/** Description of this field's values data type */
type;
/** Are values in this field read-only */
isReadonly;
/** Priority of the field. Higher priority fields should appear first in the UI */
priority;
/** Property renderer used to render values of this field */
renderer;
/** Property editor used to edit values of this field */
editor;
/** Extended data associated with this field */
extendedData;
/** Parent field */
_parent;
constructor(categoryOrProps, name, label, type, isReadonly, priority, editor, renderer, extendedData) {
/* c8 ignore next 14 */
const props = "category" in categoryOrProps
? categoryOrProps
: {
category: categoryOrProps,
name: name,
label: label,
type: type,
isReadonly: isReadonly,
priority: priority,
editor,
renderer,
extendedData,
};
this.category = props.category;
this.name = props.name;
this.label = props.label;
this.type = props.type;
this.isReadonly = props.isReadonly;
this.priority = props.priority;
this.editor = props.editor;
this.renderer = props.renderer;
this.extendedData = props.extendedData;
}
/**
* Is this a [[PropertiesField]]
*/
isPropertiesField() {
return isPropertiesField(this);
}
/**
* Is this a [[NestedContentField]]
*/
isNestedContentField() {
return isNestedContentField(this);
}
/**
* Get parent
*/
get parent() {
return this._parent;
}
clone() {
const clone = new Field(this);
clone.rebuildParentship(this.parent);
return clone;
}
/**
* Serialize this object to JSON.
* @deprecated in 5.0 - will not be removed until after 2026-06-13. Use [[toCompressedJSON]] instead.
*/
toJSON() {
return this.toCompressedJSON({});
}
/** Serialize this object to compressed JSON */
toCompressedJSON(_classesMap) {
return (0, Utils_js_1.omitUndefined)({
category: this.category.name,
name: this.name,
label: this.label,
type: this.type,
isReadonly: this.isReadonly,
priority: this.priority,
renderer: this.renderer,
editor: this.editor,
extendedData: this.extendedData,
});
}
/**
* Deserialize [[Field]] from JSON.
* @deprecated in 5.0 - will not be removed until after 2026-06-13. Use [[fromCompressedJSON]] instead.
*/
static fromJSON(json, categories) {
if (!json) {
return undefined;
}
if (isPropertiesField(json)) {
// eslint-disable-next-line @typescript-eslint/no-deprecated
return PropertiesField.fromJSON(json, categories);
}
if (isNestedContentField(json)) {
return new NestedContentField({
...json,
...fromNestedContentFieldJSON(json, categories),
nestedFields: json.nestedFields
// eslint-disable-next-line @typescript-eslint/no-deprecated
.map((nestedFieldJson) => Field.fromJSON(nestedFieldJson, categories))
.filter((nestedField) => !!nestedField),
});
}
return new Field({
...json,
category: this.getCategoryFromFieldJson(json, categories),
});
}
/** Deserialize a [[Field]] from compressed JSON. */
static fromCompressedJSON(json, classesMap, categories) {
if (!json) {
return undefined;
}
if (isPropertiesField(json)) {
return PropertiesField.fromCompressedJSON(json, classesMap, categories);
}
if (isNestedContentField(json)) {
return NestedContentField.fromCompressedJSON(json, classesMap, categories);
}
return new Field({
...json,
category: this.getCategoryFromFieldJson(json, categories),
});
}
static getCategoryFromFieldJson(fieldJson, categories) {
return getCategoryFromFieldJson(fieldJson, categories);
}
/** Resets field's parent. */
resetParentship() {
this._parent = undefined;
}
/** Sets provided [[NestedContentField]] as parent of this field. */
rebuildParentship(parentField) {
this._parent = parentField;
}
/**
* Get descriptor for this field.
* @public
*/
getFieldDescriptor() {
return {
type: FieldDescriptorType.Name,
fieldName: this.name,
};
}
/**
* Checks if this field matches given field descriptor
* @see [[getFieldDescriptor]]
*/
matchesDescriptor(descriptor) {
return FieldDescriptor.isNamed(descriptor) && descriptor.fieldName === this.name;
}
}
exports.Field = Field;
/**
* Describes a content field that's based on one or more similar
* EC properties.
*
* @public
*/
class PropertiesField extends Field {
/** A list of properties this field is created from */
properties;
constructor(categoryOrProps, name, label, type, isReadonly, priority, properties, editor, renderer) {
/* c8 ignore next 14 */
const props = "category" in categoryOrProps
? categoryOrProps
: {
category: categoryOrProps,
name: name,
label: label,
type: type,
isReadonly: isReadonly,
priority: priority,
editor,
renderer,
properties: properties,
};
super(props);
this.properties = props.properties;
}
/** Is this a an array property field */
isArrayPropertiesField() {
return false;
}
/** Is this a an struct property field */
isStructPropertiesField() {
return false;
}
clone() {
const clone = new PropertiesField(this);
clone.rebuildParentship(this.parent);
return clone;
}
/**
* Serialize this object to JSON
* @deprecated in 5.0 - will not be removed until after 2026-06-13. Use [[toCompressedJSON]] instead.
*/
toJSON() {
return {
// eslint-disable-next-line @typescript-eslint/no-deprecated
...super.toJSON(),
properties: this.properties,
};
}
/** Serialize this object to compressed JSON */
toCompressedJSON(classesMap) {
return {
...super.toCompressedJSON(classesMap),
properties: this.properties.map((property) => Property_js_1.Property.toCompressedJSON(property, classesMap)),
};
}
/**
* Deserialize [[PropertiesField]] from JSON.
* @deprecated in 5.0 - will not be removed until after 2026-06-13. Use [[fromCompressedJSON]] instead.
*/
static fromJSON(json, categories) {
if (!json) {
return undefined;
}
if (isArrayPropertiesField(json)) {
// eslint-disable-next-line @typescript-eslint/no-deprecated
return ArrayPropertiesField.fromJSON(json, categories);
}
if (isStructPropertiesField(json)) {
// eslint-disable-next-line @typescript-eslint/no-deprecated
return StructPropertiesField.fromJSON(json, categories);
}
return new PropertiesField({
...json,
category: this.getCategoryFromFieldJson(json, categories),
});
}
/**
* Deserialize a [[PropertiesField]] from compressed JSON.
* @public
*/
static fromCompressedJSON(json, classesMap, categories) {
if (isArrayPropertiesField(json)) {
return ArrayPropertiesField.fromCompressedJSON(json, classesMap, categories);
}
if (isStructPropertiesField(json)) {
return StructPropertiesField.fromCompressedJSON(json, classesMap, categories);
}
return new PropertiesField({
...json,
category: this.getCategoryFromFieldJson(json, categories),
properties: json.properties.map((propertyJson) => fromCompressedPropertyJSON(propertyJson, classesMap)),
});
}
/**
* Get descriptor for this field.
* @public
*/
getFieldDescriptor() {
const pathFromPropertyToSelectClass = new Array();
let currAncestor = this.parent;
while (currAncestor) {
pathFromPropertyToSelectClass.push(...currAncestor.pathToPrimaryClass);
currAncestor = currAncestor.parent;
}
return {
type: FieldDescriptorType.Properties,
pathFromSelectToPropertyClass: EC_js_1.RelationshipPath.strip(EC_js_1.RelationshipPath.reverse(pathFromPropertyToSelectClass)),
properties: this.properties.map((p) => ({
class: p.property.classInfo.name,
name: p.property.name,
})),
};
}
/**
* Checks if this field matches given field descriptor
* @see [[getFieldDescriptor]]
*/
matchesDescriptor(descriptor) {
if (!FieldDescriptor.isProperties(descriptor)) {
return false;
}
// ensure at least one descriptor property matches at least one property of this field
if (!this.properties.some(({ property: fieldProperty }) => descriptor.properties.some((descriptorProperty) => fieldProperty.name === descriptorProperty.name && fieldProperty.classInfo.name === descriptorProperty.class))) {
return false;
}
// ensure path from select to property in field and in descriptor matches
let stepsCount = 0;
let currAncestor = this.parent;
while (currAncestor) {
const pathFromCurrentToItsParent = EC_js_1.RelationshipPath.reverse(currAncestor.pathToPrimaryClass);
for (const step of pathFromCurrentToItsParent) {
if (descriptor.pathFromSelectToPropertyClass.length < stepsCount + 1) {
return false;
}
if (!EC_js_1.RelatedClassInfo.equals(step, descriptor.pathFromSelectToPropertyClass[descriptor.pathFromSelectToPropertyClass.length - stepsCount - 1])) {
return false;
}
++stepsCount;
}
currAncestor = currAncestor.parent;
}
return true;
}
}
exports.PropertiesField = PropertiesField;
/**
* Describes a content field that's based on one or more similar EC array properties.
* @public
*/
class ArrayPropertiesField extends PropertiesField {
itemsField;
constructor(categoryOrProps, name, label, type, itemsField, isReadonly, priority, properties, editor, renderer) {
/* c8 ignore next 15 */
const props = "category" in categoryOrProps
? categoryOrProps
: {
category: categoryOrProps,
name: name,
label: label,
type: type,
isReadonly: isReadonly,
priority: priority,
editor,
renderer,
properties: properties,
itemsField: itemsField,
};
super(props);
this.itemsField = props.itemsField;
}
isArrayPropertiesField() {
return true;
}
clone() {
const clone = new ArrayPropertiesField(this);
clone.rebuildParentship(this.parent);
return clone;
}
/**
* Serialize this object to JSON.
* @deprecated in 5.0 - will not be removed until after 2026-06-13. Use [[toCompressedJSON]] instead.
*/
toJSON() {
return {
// eslint-disable-next-line @typescript-eslint/no-deprecated
...super.toJSON(),
// eslint-disable-next-line @typescript-eslint/no-deprecated
itemsField: this.itemsField.toJSON(),
};
}
/** Serialize this object to compressed JSON */
toCompressedJSON(classesMap) {
return {
...super.toCompressedJSON(classesMap),
itemsField: this.itemsField.toCompressedJSON(classesMap),
};
}
/**
* Deserialize [[ArrayPropertiesField]] from JSON.
* @deprecated in 5.0 - will not be removed until after 2026-06-13. Use [[fromCompressedJSON]] instead.
*/
static fromJSON(json, categories) {
return new ArrayPropertiesField({
...json,
category: this.getCategoryFromFieldJson(json, categories),
// eslint-disable-next-line @typescript-eslint/no-deprecated
itemsField: PropertiesField.fromJSON(json.itemsField, categories),
});
}
/**
* Deserialize an [[ArrayPropertiesField]] from compressed JSON.
* @public
*/
static fromCompressedJSON(json, classesMap, categories) {
return new ArrayPropertiesField({
...json,
category: this.getCategoryFromFieldJson(json, categories),
properties: json.properties.map((propertyJson) => fromCompressedPropertyJSON(propertyJson, classesMap)),
itemsField: PropertiesField.fromCompressedJSON(json.itemsField, classesMap, categories),
});
}
}
exports.ArrayPropertiesField = ArrayPropertiesField;
/**
* Describes a content field that's based on one or more similar EC struct properties.
* @public
*/
class StructPropertiesField extends PropertiesField {
memberFields;
constructor(categoryOrProps, name, label, type, memberFields, isReadonly, priority, properties, editor, renderer) {
/* c8 ignore next 15 */
const props = "category" in categoryOrProps
? categoryOrProps
: {
category: categoryOrProps,
name: name,
label: label,
type: type,
isReadonly: isReadonly,
priority: priority,
editor,
renderer,
properties: properties,
memberFields: memberFields,
};
super(props);
this.memberFields = props.memberFields;
}
isStructPropertiesField() {
return true;
}
clone() {
const clone = new StructPropertiesField(this);
clone.rebuildParentship(this.parent);
return clone;
}
/**
* Serialize this object to JSON.
* @deprecated in 5.0 - will not be removed until after 2026-06-13. Use [[toCompressedJSON]] instead.
*/
toJSON() {
return {
// eslint-disable-next-line @typescript-eslint/no-deprecated
...super.toJSON(),
// eslint-disable-next-line @typescript-eslint/no-deprecated
memberFields: this.memberFields.map((m) => m.toJSON()),
};
}
/** Serialize this object to compressed JSON */
toCompressedJSON(classesMap) {
return {
...super.toCompressedJSON(classesMap),
memberFields: this.memberFields.map((m) => m.toCompressedJSON(classesMap)),
};
}
/**
* Deserialize [[StructPropertiesField]] from JSON.
* @deprecated in 5.0 - will not be removed until after 2026-06-13. Use [[fromCompressedJSON]] instead.
*/
static fromJSON(json, categories) {
return new StructPropertiesField({
...json,
category: this.getCategoryFromFieldJson(json, categories),
// eslint-disable-next-line @typescript-eslint/no-deprecated
memberFields: json.memberFields.map((m) => PropertiesField.fromJSON(m, categories)),
});
}
/**
* Deserialize a [[StructPropertiesField]] from compressed JSON.
* @public
*/
static fromCompressedJSON(json, classesMap, categories) {
return new StructPropertiesField({
...json,
category: this.getCategoryFromFieldJson(json, categories),
properties: json.properties.map((propertyJson) => fromCompressedPropertyJSON(propertyJson, classesMap)),
memberFields: json.memberFields.map((m) => PropertiesField.fromCompressedJSON(m, classesMap, categories)),
});
}
}
exports.StructPropertiesField = StructPropertiesField;
/**
* Describes a content field that contains [Nested content]($docs/presentation/content/Terminology#nested-content).
*
* @public
*/
class NestedContentField extends Field {
/** Information about an ECClass whose properties are nested inside this field */
contentClassInfo;
/** Relationship path to [Primary class]($docs/presentation/content/Terminology#primary-class) */
pathToPrimaryClass;
/**
* Meaning of the relationship between the [primary class]($docs/presentation/content/Terminology#primary-class)
* and content class of this field.
*
* The value is set up through [[RelatedPropertiesSpecification.relationshipMeaning]] attribute when setting up
* presentation rules for creating the content.
*/
relationshipMeaning;
/**
* When content descriptor is requested in a polymorphic fashion, fields get created if at least one of the concrete classes
* has it. In certain situations it's necessary to know which concrete classes caused that and this attribute is
* here to help.
*
* **Example:** There's a base class `A` and it has two derived classes `B` and `C` and class `B` has a relationship to class `D`.
* When content descriptor is requested for class `A` polymorphically, it's going to contain fields for all properties of class `B`,
* class `C` and a nested content field for the `B -> D` relationship. The nested content field's `actualPrimaryClassIds` attribute
* will contain ID of class `B`, identifying that only this specific class has the relationship.
*/
actualPrimaryClassIds;
/** Contained nested fields */
nestedFields;
/** Flag specifying whether field should be expanded */
autoExpand;
constructor(categoryOrProps, name, label, type, isReadonly, priority, contentClassInfo, pathToPrimaryClass, nestedFields, editor, autoExpand, renderer) {
/* c8 ignore next 17 */
const props = "category" in categoryOrProps
? categoryOrProps
: {
category: categoryOrProps,
name: name,
label: label,
type: type,
isReadonly: isReadonly,
priority: priority,
editor,
renderer,
contentClassInfo: contentClassInfo,
pathToPrimaryClass: pathToPrimaryClass,
nestedFields: nestedFields,
autoExpand,
};
super(props);
this.contentClassInfo = props.contentClassInfo;
this.pathToPrimaryClass = props.pathToPrimaryClass;
this.relationshipMeaning = props.relationshipMeaning ?? RelatedPropertiesSpecification_js_1.RelationshipMeaning.RelatedInstance;
this.nestedFields = props.nestedFields;
this.autoExpand = props.autoExpand;
this.actualPrimaryClassIds = props.actualPrimaryClassIds ?? [];
}
clone() {
const clone = new NestedContentField({
...this,
nestedFields: this.nestedFields.map((n) => n.clone()),
});
clone.rebuildParentship(this.parent);
return clone;
}
/**
* Get field by its name
* @param name Name of the field to find
* @param recurse Recurse into nested fields
*/
getFieldByName(name, recurse) {
return (0, exports.getFieldByName)(this.nestedFields, name, recurse);
}
/**
* Serialize this object to JSON.
* @deprecated in 5.0 - will not be removed until after 2026-06-13. Use [[toCompressedJSON]] instead.
*/
toJSON() {
return {
// eslint-disable-next-line @typescript-eslint/no-deprecated
...super.toJSON(),
contentClassInfo: this.contentClassInfo,
pathToPrimaryClass: this.pathToPrimaryClass,
relationshipMeaning: this.relationshipMeaning,
actualPrimaryClassIds: this.actualPrimaryClassIds,
// eslint-disable-next-line @typescript-eslint/no-deprecated
nestedFields: this.nestedFields.map((field) => field.toJSON()),
...(this.autoExpand ? { autoExpand: true } : undefined),
};
}
/** Serialize this object to compressed JSON */
toCompressedJSON(classesMap) {
const { id, ...leftOverInfo } = this.contentClassInfo;
classesMap[id] = leftOverInfo;
return {
...super.toCompressedJSON(classesMap),
contentClassInfo: id,
relationshipMeaning: this.relationshipMeaning,
actualPrimaryClassIds: this.actualPrimaryClassIds,
pathToPrimaryClass: this.pathToPrimaryClass.map((classInfo) => EC_js_1.RelatedClassInfo.toCompressedJSON(classInfo, classesMap)),
nestedFields: this.nestedFields.map((field) => field.toCompressedJSON(classesMap)),
...(this.autoExpand ? { autoExpand: true } : undefined),
};
}
/** Deserialize a [[NestedContentField]] from compressed JSON. */
static fromCompressedJSON(json, classesMap, categories) {
(0, core_bentley_1.assert)(classesMap.hasOwnProperty(json.contentClassInfo));
return new NestedContentField({
...json,
...fromNestedContentFieldJSON(json, categories),
category: this.getCategoryFromFieldJson(json, categories),
nestedFields: json.nestedFields
.map((nestedFieldJson) => Field.fromCompressedJSON(nestedFieldJson, classesMap, categories))
.filter((nestedField) => !!nestedField),
contentClassInfo: { id: json.contentClassInfo, ...classesMap[json.contentClassInfo] },
pathToPrimaryClass: json.pathToPrimaryClass.map((stepJson) => EC_js_1.RelatedClassInfo.fromCompressedJSON(stepJson, classesMap)),
});
}
/** Resets parent of this field and all nested fields. */
resetParentship() {
super.resetParentship();
for (const nestedField of this.nestedFields) {
nestedField.resetParentship();
}
}
/**
* Sets provided [[NestedContentField]] as parent of this fields and recursively updates
* all nested fields parents.
*/
rebuildParentship(parentField) {
super.rebuildParentship(parentField);
for (const nestedField of this.nestedFields) {
nestedField.rebuildParentship(this);
}
}
}
exports.NestedContentField = NestedContentField;
/** @internal */
const getFieldByName = (fields, name, recurse) => {
if (name) {
for (const field of fields) {
if (field.name === name) {
return field;
}
if (recurse && field.isNestedContentField()) {
const nested = (0, exports.getFieldByName)(field.nestedFields, name, recurse);
if (nested) {
return nested;
}
}
}
}
return undefined;
};
exports.getFieldByName = getFieldByName;
/** @internal */
const getFieldByDescriptor = (fields, fieldDescriptor, recurse) => {
for (const field of fields) {
if (field.matchesDescriptor(fieldDescriptor)) {
return field;
}
if (recurse && field.isNestedContentField()) {
const nested = (0, exports.getFieldByDescriptor)(field.nestedFields, fieldDescriptor, recurse);
if (nested) {
return nested;
}
}
}
return undefined;
};
exports.getFieldByDescriptor = getFieldByDescriptor;
/**
* Types of different field descriptors.
* @public
*/
var FieldDescriptorType;
(function (FieldDescriptorType) {
FieldDescriptorType["Name"] = "name";
FieldDescriptorType["Properties"] = "properties";
})(FieldDescriptorType || (exports.FieldDescriptorType = FieldDescriptorType = {}));
/** @public */
// eslint-disable-next-line @typescript-eslint/no-redeclare
var FieldDescriptor;
(function (FieldDescriptor) {
/** Is this a named field descriptor */
function isNamed(d) {
return d.type === FieldDescriptorType.Name;
}
FieldDescriptor.isNamed = isNamed;
/** Is this a properties field descriptor */
function isProperties(d) {
return d.type === FieldDescriptorType.Properties;
}
FieldDescriptor.isProperties = isProperties;
})(FieldDescriptor || (exports.FieldDescriptor = FieldDescriptor = {}));
function fromCompressedPropertyJSON(compressedPropertyJSON, classesMap) {
return {
property: fromCompressedPropertyInfoJSON(compressedPropertyJSON.property, classesMap),
};
}
function fromCompressedPropertyInfoJSON(compressedPropertyJSON, classesMap) {
(0, core_bentley_1.assert)(classesMap.hasOwnProperty(compressedPropertyJSON.classInfo));
const { navigationPropertyInfo, ...leftOverPropertyJSON } = compressedPropertyJSON;
return {
...leftOverPropertyJSON,
classInfo: { id: compressedPropertyJSON.classInfo, ...classesMap[compressedPropertyJSON.classInfo] },
...(navigationPropertyInfo ? { navigationPropertyInfo: EC_js_1.NavigationPropertyInfo.fromCompressedJSON(navigationPropertyInfo, classesMap) } : undefined),
};
}
function getCategoryFromFieldJson(fieldJson, categories) {
const category = categories.find((c) => c.name === fieldJson.category);
if (!category) {
throw new Error_js_1.PresentationError(Error_js_1.PresentationStatus.InvalidArgument, `Invalid content field category`);
}
return category;
}
function fromNestedContentFieldJSON(json, categories) {
return {
category: getCategoryFromFieldJson(json, categories),
relationshipMeaning: json.relationshipMeaning ?? RelatedPropertiesSpecification_js_1.RelationshipMeaning.RelatedInstance,
actualPrimaryClassIds: json.actualPrimaryClassIds ?? [],
autoExpand: json.autoExpand,
};
}
//# sourceMappingURL=Fields.js.map