Smart Fields

Please be sure of your agent type and version and pick the right documentation accordingly.

This is the documentation of the forest-express-sequelize and forest-express-mongoose Node.js agents that will soon reach end-of-support.

forest-express-sequelize v9 and forest-express-mongoose v9 are replaced by @forestadmin/agent v1.

Please check your agent type and version and read on or switch to the right documentation.

Smart Fields

What is a Smart Field?

A field that displays a computed value in your collection.

A Smart Field is a column that displays processed-on-the-fly data. It can be as simple as concatenating attributes to make them human friendly, or more complex (e.g. total of orders).

Creating a Smart Field

On our Live Demo, the very simple Smart Field fullname is available on the customers collection.

/forest/customers.js
const { collection } = require('forest-express-sequelize');

collection('customers', {
  fields: [
    {
      field: 'fullname',
      type: 'String',
      get: (customer) => {
        return customer.firstname + ' ' + customer.lastname;
      },
    },
  ],
});

Very often, the business logic behind the Smart Field is more complex and must be asynchronous. To do that, please have a look at this section.

The collection name must be the same as the model name.

Updating a Smart Field

By default, your Smart Field is considered as read-only. If you want to update a Smart Field, you just need to write the logic to “unzip” the data. Note that the set method should always return the object it’s working on. In the example hereunder, the customer object is returned including only the modified data.

/forest/customers.js
const { collection } = require('forest-express-sequelize');

collection('customers', {
  fields: [
    {
      field: 'fullname',
      type: 'String',
      get: (customer) => {
        return customer.firstname + ' ' + customer.lastname;
      },
      set: (customer, fullname) => {
        let names = fullname.split(' ');
        customer.firstname = names[0];
        customer.lastname = names[1];

        // Don't forget to return the customer.
        return customer;
      },
    },
  ],
});

Working with the actual record can be done this way:

/forest/customers.js
const { collection, ResourceGetter } = require('forest-express-sequelize');
const { customers } = require('../models');

collection('customers', {
  fields: [
    {
      field: 'fullname',
      type: 'String',
      get: (customer) => {
        return customer.firstname + ' ' + customer.lastname;
      },
      set: async (customer, fullname) => {
        const customerBeforeUpdate = await customers.findOne({
          where: { id: customer.id },
        });

        const names = fullname.split(' ');
        customer.firstname = `${names[0]} ${customerBeforeUpdate.pseudo}`;
        return customer;
      },
    },
  ],
});

For security reasons, the fullname Smart field will remain read-only, even after you implement the set method. To edit it, disable read-only mode in the field settings.

Searching, Sorting and Filtering on a Smart Field

To perform a search on a Smart Field, you also need to write the logic to “unzip” the data, then the search query which is specific to your zipping. In the example hereunder, the firstname and lastname are searched separately after having been unzipped.

/forest/customers.js
const { collection } = require('forest-express-sequelize');
const models = require('../models/');
const _ = require('lodash');
const Op = models.objectMapping.Op;

collection('customers', {
  fields: [
    {
      field: 'fullname',
      type: 'String',
      get: (customer) => {
        return customer.firstname + ' ' + customer.lastname;
      },
      search: function (query, search) {
        let split = search.split(' ');

        var searchCondition = {
          [Op.and]: [
            { firstname: { [Op.like]: `%${split[0]}%` } },
            { lastname: { [Op.like]: `%${split[1]}%` } },
          ],
        };

        query.where[Op.and][0][Op.or].push(searchCondition);

        return query;
      },
    },
  ],
});

For case insensitive search using PostgreSQL database use iLike operator. See Sequelize operators documentation.

Filtering

This feature is only available on agents version 6.7+ (version 6.2+ for Rails).

To perform a filter on a Smart Field, you need to write the filter query logic, which is specific to your use case.

In the example hereunder, the fullname is filtered by checking conditions on the firstname and lastname depending on the filter operator selected.

/forest/customers.js
const { collection } = require('forest-express-sequelize');
const models = require('../models/');

const { Op } = models.Sequelize;

collection('customers', {
  fields: [
    {
      field: 'fullname',
      isFilterable: true,
      type: 'String',
      get: (customer) => {
        return customer.firstname + ' ' + customer.lastname;
      },
      filter({ condition, where }) {
        const firstWord = !!condition.value && condition.value.split(' ')[0];
        const secondWord = !!condition.value && condition.value.split(' ')[1];

        switch (condition.operator) {
          case 'equal':
            return {
              [Op.and]: [
                { firstname: firstWord },
                { lastname: secondWord || '' },
              ],
            };
          case 'ends_with':
            if (!secondWord) {
              return {
                lastName: { [Op.like]: `%${firstWord}` },
              };
            }
            return {
              [Op.and]: [
                { firstName: { [Op.like]: `%${firstWord}` } },
                { lastName: secondWord },
              ],
            };

          // ... And so on with the other operators not_equal, starts_with, etc.

          default:
            return null;
        }
      },
    },
  ],
  segments: [],
});

Make sure you set the option isFilterable: true in the field definition of your code. Then, you will be able to toggle the "Filtering enabled" option in the browser, in your Fields Settings.

Sorting

Sorting on a Smart Field is not natively supported in Forest Admin. However you can check out those guides:

Available Field Options

Here are the list of available options to customize your Smart Field:

NameTypeDescription

field

string

The name of your Smart Field.

type

string

Type of your field. Can be Boolean, Date, Json,Dateonly, Enum, File, Number, ['String'] or String .

enums

array of strings

(optional) Required only for the Enum type. This is where you list all the possible values for your input field.

description

string

(optional) Add a description to your field.

reference

string

(optional) Configure the Smart Field as a Smart Relationship.

isReadOnly

boolean

(optional) If true, the Smart Field won’t be editable in the browser. Default is true if there’s no set option declared.

isRequired

boolean

(optional) If true, your Smart Field will be set as required in the browser. Default is false.

You can define a widget for a smart field from the settings of your collection.

Building Performant Smart Fields

To optimize your smart field performance, we recommend using a mechanism of batching and caching data requests.

Implement them using the DataLoader which is a generic utility to be used as part of your application's data fetching layer to provide a simplified and consistent API over various remote data sources.

Smart field declaration

forest/post.js
const DataLoader = require('dataloader');

const authorLoader = new DataLoader(async (authorKeys) => {
  const authors = await users.findAll({
    where: { id: authorKeys },
  });

  const authorsById = new Map(authors.map((user) => [user.id, user]));

  return authorKeys.map((authorKey) => authorsById.get(authorKey));
});

collection('posts', {
  actions: [],
  fields: [
    {
      field: 'author_name',
      type: 'String',
      get: async (record) => {
        const author = await authorLoader.load(record.authorKey);

        return author.name;
      },
    },
  ],
  segments: [],
});

Last updated