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).
On our Live Demo, the very simple Smart Field fullname
is available on the customers
collection.
/forest/customers.jsconst { 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, you can simply return a Promise.
In the following example, we illustrate an async logic through our full_address
Smart Field also defined in the customers
collection.
/forest/customers.jsconst { collection } = require('forest-express-sequelize');const models = require('../models/');collection('customers', {fields: [{field: 'full_address',type: 'String',get: (customer) => {return models.addresses.findOne({ where: { customer_id: customer.id } }).then((address) => {return address.address_line_1 + '\n' +address.address_line_2 + '\n' +address.address_city + ' ' + address.country;});}}]});
On our Live Demo, the very simple Smart Field fullname
is available on the customers
collection.
/forest/customers.jsconst { collection } = require('forest-express-mongoose');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, you can simply return a Promise.
In the following example, we illustrate an async logic through our full_address
Smart Field also defined in the customers
collection.
/forest/customers.jsconst { collection } = require('forest-express-mongoose');const Address = require('../models/addresses');collection('customers', {fields: [{field: 'full_address',type: 'String',get: (customer) => {return Address.findOne({ customer_id: customer.id }).then((address) => {return address.address_line_1 + '\n' +address.address_line_2 + '\n' +address.address_city + ' ' + address.country;});}}]});
On our Live Demo, the very simple Smart Field fullname
is available on the Customer
collection.
/lib/forest_liana/collections/customer.rbclass Forest::Customerinclude ForestLiana::Collectioncollection :Customerfield :fullname, type: 'String' do"#{object.firstname} #{object.lastname}"endend
Very often, the business logic behind the Smart Field is more complex and must interact with the database. Here’s an example with the Smart Field full_address
on the Customer
collection.
/lib/forest_liana/collections/customer.rbclass Forest::Customerinclude ForestLiana::Collectioncollection :Customerfield :full_address, type: 'String' doaddress = Address.find_by(customer_id: object.id)"#{address[:address_line_1]} #{address[:address_line_2]} #{address[:address_city]} #{address[:country]}"endend
The collection name must be the same as the model name.
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.jsconst { 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.jsconst { 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: (customer, fullname) => {return new ResourceGetter(customers, {recordId: customer.id}).perform().then(record => {let names = fullname.split(' ');customer.firstname = `${names[0]} ${record.pseudo}`;});}}]});
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.
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
record is returned.
/forest/customers.jsconst { collection } = require('forest-express-mongoose');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.jsconst { collection, ResourceGetter } = require('forest-express-mongoose');const { customers } = require('../models');collection('customers', {fields: [{field: 'fullname',type: 'String',get: (customer) => {return customer.firstname + ' ' + customer.lastname;},set: (customer, fullname) => {return new ResourceGetter(customers, {recordId: customer._id}).perform().then(record => {let names = fullname.split(' ');customer.firstname = `${names[0]} ${record.pseudo}`;});}}]});
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.
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 user_params
is returned is returned including only the modified data.
/lib/forest_liana/collections/customer.rbclass Forest::Customerinclude ForestLiana::Collectioncollection :Customerset_fullname = lambda do |user_params, fullname|fullname = fullname.splituser_params[:firstname] = fullname.firstuser_params[:lastname] = fullname.last# Returns a hash of the updated values you want to persist.user_paramsendfield :fullname, type: 'String', set: set_fullname do"#{object.firstname} #{object.lastname}"endend
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.
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.jsconst { collection } = require('forest-express-sequelize');const models = require('../models/');const _ = require('lodash');const Op = models.Sequelize.Op;collection('customers', {fields: [{field: 'fullname',type: 'String',get: (customer) => {return customer.firstname + ' ' + customer.lastname;},search: function (query, search) {let s = models.sequelize;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.
/forest/customers.jsconst { collection } = require('forest-express-mongoose');const models = require('../models/');const _ = require('lodash');collection('customers', {fields: [{field: 'fullname',type: 'String',get: (customer) => {return customer.firstname + ' ' + customer.lastname;},search(search) {let names = search.split(' ');return {firstname: names[0],lastname: names[1]};}}]});
/lib/forest_liana/collections/customer.rbclass Forest::Customerinclude ForestLiana::Collectioncollection :Customersearch_fullname = lambda do |query, search|firstname, lastname = search.split# Injects your new filter into the WHERE clause.query.where_clause.send(:predicates)[0] << " OR (firstname = '#{firstname}' AND lastname = '#{lastname}')"queryendfield :fullname, type: 'String', set: set_fullname, search: search_fullname do"#{object.firstname} #{object.lastname}"endend
This feature is only available for version 6.7+ of the liana (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.jsconst { 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: [],});
/forest/customers.jsconst { collection } = require('forest-express-mongoose');const models = require('../models');collection('customer', {actions: [],fields: [{field: 'fullName',type: 'String',isFilterable: true,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 {$and: [{ firstname: firstWord },{ lastname: secondWord || '' },],};case 'ends_with':if (!secondWord) {return {lastname: { $regex: `.*${firstWord}` },};}return {$and: [{ firstname: { $regex: `.*${firstWord}` } },{ lastname: secondWord },],};// ... And so on with the other operators not_equal, starts_with, etc.default:return null;}},}],segments: [],});
/lib/forest_liana/customer.rbclass Forest::Customerinclude ForestLiana::Collectioncollection :Customerfilter_fullname = lambda do |condition, where|first_word = condition['value'] && condition['value'].split[0]second_word = condition['value'] && condition['value'].split[1]case condition['operator']when 'equal'"firstname = '#{first_word}' AND lastname = '#{second_word}'"when 'ends_with'if second_word.nil?"lastname LIKE '%#{first_word}'"else"firstname LIKE '%#{first_word}' AND lastname = '#{second_word}'"end# ... And so on with the other operators not_equal, starts_with, etc.endendfield :fullname, type: 'String', is_read_only: false, is_required: true, is_filterable: true, filter: filter_fullname do"#{object.firstname} #{object.lastname}"endend
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 on a Smart Field is not yet supported in Forest Admin.
Here are the list of available options to customize your Smart Field:
Name | Type | Description |
field | string | The name of your Smart Field. |
type | string | Type of your field. Can be |
enums | array of strings | (optional) Required only for the |
description | string | (optional) Add a description to your field. |
reference | string | (optional) Configure the Smart Field as a Smart Relationship. |
isReadOnly | boolean | (optional) If |
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.