viewModelFactory

Source
import { viewModelFactory } from "@prestojs/viewmodel";

A ViewModel class is created using the viewModelFactory function. The factory function is documented immediately below followed by the documentation for the generated class.

Factory

Creates a ViewModel class with the specified fields.

const fields = {
userId: new IntegerField({ label: 'User ID' })
firstName: new CharField({ label: 'First Name' }),
// label is optional; will be generated as 'Last name'
lastName: new CharField(),
};
// Options are all optional and can be omitted entirely
const options = {
// Only one of pkFieldName or getImplicitPkField can be defined.
// If neither are provided a default field called 'id' will be created.
pkFieldName: 'userId',
// Multiple names can be specified for compound keys
pkFieldName: ['organisationId', 'departmentId']
// You can also specify a function to create the primary key
getImplicitPkField(model, fields) {
if ('EntityId' in fields) {
return ['EntityId', fields.EntityId];
}
// Generate a name base on model, eg. `userId`
const name = model.name[0].toLowerCase() + model.name.slice(1);
return [`${name}Id`, new NumberField()];
},
// Optionally can specify a baseClass for this model. When using `augment`
// this is automatically set to the class being augmented.
baseClass: BaseViewModel,
};
class User extends viewModelFactory(fields, options) {
// Optional; default cache is usually sufficient
static cache = new MyCustomCache();
// Used to describe a single user
static label = 'User';
// User to describe an indeterminate number of users
static labelPlural = 'Users';
}
ParameterTypeDescription
*fields{[ fieldName: string ]: Field}

A map of field name to an instance of Field

options.baseClassClass

Optional base class to extend. This must extend BaseViewModel.

When calling augment this is set the augmented class.

*options.pkFieldNamestring|string[]

Primary key name(s) to use. There should be field(s) with the corresponding name in the provided fields.

Only pkFieldName or getImplicitPkField should be provided. If neither are provided then a field called id will be used and created if not provided in fields.

ViewModel Class

ViewModel Class

The class created from viewModelFactory.

Static Class Methods

Create a new class that extends this class with the additional specified fields. To remove a field that exists on the base class set it's value to null.

class Base extends viewModelFactory({
id: new NumberField({
label: 'Id',
}),
firstName: new CharField({
label: 'First Name',
}),
lastName: new CharField({
label: 'Last Name',
}),
email: new EmailField({
label: 'Email',
}),
}) {
static label = 'User';
static labelPlural = 'Users';
}
class User extends BaseUser.augment({
region: new IntegerField({
label: 'region',
required: true,
helpText: 'Region Coding of the user',
choices: [
[1, 'Oceania'],
[2, 'Asia'],
[3, 'Africa'],
[4, 'America'],
[5, 'Europe'],
[6, 'Antarctica'],
[7, 'Atlantis'],
],
}),
photo: new ImageField({
helpText: 'Will be cropped to 400x400',
}),
}) {
}
// true
User instanceof BaseUser
// true
User.label === 'User'
// ['firstName, 'lastName', 'email', 'region', 'photo]
User.fieldNames
ParameterTypeDescription
*newFieldsFieldsMappingOrNull

Map of field name to a Field instance (to add the field) or null (to remove the field)

newOptionsPartial

Provide optional overrides for the options that the original class was created with

ViewModel Class

A new ViewModel class with fields modified according to newFields.

Get a field from this model or a related model

Accepts either a string for a field on this record or array notation for traversing RelatedViewModelField fields:

Subscription.getField(['user', 'group', 'owner'])
ParameterTypeDescription
*fieldNameFieldPath

Either a string or an array of strings where the last element is the final field name to return and each other element is a RelatedViewModelField on a ViewModel.

Field

Static Class Properties

Shortcut to get all field names including primary keys

The cache instance for this ViewModel. A default instance of ViewModelCache is created when first accessed or you can explicitly assign a cache:

class User extends viewModelFactory(fields) {
static cache = new MyCustomCache(User);
}

Shortcut to get the names of all fields excluding primary keys.

If you want all fields including primary key use allFieldNames

The bound fields for this ViewModel. These will match the fields passed in to ViewModel with the following differences:

  • If a primary key is created for you this will exist here
  • All fields are bound to the created class. This means you can access the ViewModel class from the field on the model property, eg. User.fields.email.model === User will be true.
  • All fields have the name property set to match the key in fields
  • All fields have label filled out if not explicitly set (eg. if name was emailAddress label will be created as Email Address)

See also getField for getting a nested field using array notation.

The singular label for this ViewModel. This should be set by extending the created class.

class User extends viewModelFactory(fields) {
static label = 'User';
}

The label used to describe an indeterminate number of this ViewModel. This should be set by extending the created class.

class User extends viewModelFactory(fields) {
static labelPlural = 'Users';
}

Name of the primary key field for this ViewModel (or fields for compound keys)

If options.pkFieldName is not specified a field will be created from options.getImplicitPk if provided otherwise a default field with name 'id' will be created.

Shortcut to get pkFieldName as an array always, even for non-compound keys

Shortcut to get the names of all relation fields

Instance Methods

Clone this record, optionally with only a subset of the fields

ParameterTypeDescription
fieldNamesstring[]|FieldPath[]
ViewModelInterface

Given records return the paths that are common between them.

A naive solution is to just check _assignedFieldsDeep:

const paths = intersectionBy(
...assignedData[key].map(record => record._assignedFieldsDeep),
p => flattenFieldPath(p).join('|')
);

but that would fail if some records had a null value for a relation and others didn't.

This function handles nested records such that a null relation is ignored. For example if you received:

[
{
id: 1,
nestedRecordId: null,
nestedRecord: null,
},
{
id: 2,
nestedRecordId: 1,
nestedRecord: {
id: 1,
name: 'Nested Record 1',
},
},
{
id: 3,
nestedRecordId: 2,
nestedRecord: {
id: 2,
name: 'Nested Record 2',
otherField: 'Name',
},
}
]

would result in

['id', 'nestedRecordId', ['nestedRecord', 'id'], ['nestedRecord', 'name']]

Noting that the first record has no nested fields (because they are null) and so get's ignored, and the last record has 'otherField' which the second doesn't so is excluded.

ParameterTypeDescription
*recordsViewModelInterface[]
FieldPath[]

Compares two records to see if they are equivalent.

  • If the ViewModel is different then the records are always considered different
  • If the records were initialised with a different set of fields then they are considered different even if the common fields are the same and other fields are all null
ParameterTypeDescription
*recordViewModelInterface|null
boolean

Return the data for this record as a plain object

__type

Instance Properties

The ViewModelFieldPaths instance for this record. This is a unique instance based on the actual assigned fields and can be compared to other instances to determine if the same fields are set.

List of field names with data available on this instance.

Deep field names set on this record. If no relations are set this is the same as _assignedFields.

A deep field is a field that is a relation to another model and is represented as an array, eg. ['group', 'name'] would be the the name field on the group relation.

The assigned data for this record. You usually don't need to access this directly; values for a field can be retrieved from the record directly using the field name

Get fields bound to this record instance. Each field behaves the same as accessing it via ViewModel.fields but has a value property that contains the value for that field on this record.

This is useful when you need to know both the field on the ViewModel and the value on a record (eg. when formatting a value from a record

const user = new User({ name: 'Jon Snow' });
user.name
// Jon Snow
user._f.name
// CharField({ name: 'name', label: 'Label' });
user._f.name.value
// Jon Snow

Returns the primary key value(s) for this instance. This is to conform to the Identifiable interface.

Get the actual ViewModel class for this instance