Options
All
  • Public
  • Public/Protected
  • All
Menu

The problem

When working with components, which aren't of a primitive type (e.g. records), it is best practice to create Typescript interfaces for easier access to the columns/properties.

interface MyRecord {
  Key: number;
  Column1: string;
  Column2: number;
  //...
}

While Hive itself is case-insensitive, accessing object properties in Javascript isn't. That means MyRecord.CoLuMn1 would work in Hive, but not in CustomJS! This is not only a bit uncomfortable for the developer but can also lead to errors due to the caching behaviour of Hive.

Example

A little casing typo which is corrected afterwards

  1. Create a Record Component with column MYTexT=Text
  2. Change column name to MyText=Text afterwards

After this you can already see a difference between the Record-Editor and the Result-View.

Example Record Component

Hive is case-insensitive and works as intended:

Example Value Component Rule

Javascript can only use it with one specific definition.
The logical approach of the developer would be, to create an interface with the correct/latest casing. But this won't work because it's already cached with the 'wrong' (initial) casing.

interface TestRecord {
  Key: string;
  MyText: string; // doesn't work
  // MyTexT: string; // would work (cached property-name)
}

Solution

In order to solve this issue, it is now mandatory to register every interface at runtime, so that it can be matched in a case-insensitive manner. When component data is retrieved with one of the intended functions (e.g.: CmpUtils.getRecordCmpValue) the object will be automatically altered, so that the casing is identical to the one from the interface.

This requires the following steps (already part of the examples projects .template-project or custom-js-4-partners):

  1. Add webpack-plugin to automatically convert interfaces in typings/*.hive.d.ts to js-objects

    • The js-objects are used at runtime to automatically fix casing mismatches in typings
  2. Start a watch/build-process to convert these files

  3. Import the converted file in your CJS-project

  4. Call the register-function

Although this might seem like many additional steps, it only has to be done once when used as intended and it improves the code quality and project structure. See usage below.

Full Example

Record Component

Component Name Colors

{
    { Key=Text, Name=Text, HexCode=Text, Price=Number, Roughness=Number, Metallic=Number }
    { "Bl1"   , "Black"  , "#171C28"   , 1600        , 0               , 0.8             }
}

Interface

./src/typings/cbn-car-typings.hive.d.ts

interface ColorsRecord {
  Key: number;
  Name: string;
  HexCode: string;
  Roughness: number;
  Metallic: number;
}

// Column "Price" isn't required in CJS

Auto-generated file

./src/typings-generated-objs/cbn-car-typings.hive.d-ti.js

-> The webpack plugin automatically creates/updates this file whenever the content of cbn-car-typings.hive.d.ts is changed.

CJS project

import HiveTypings from './typings-generated-objs/cbn-car-typings.hive.d-ti';

// Interface 'ColorsRecord' should match with cmp 'Colors'
CmpUtils.registerInterfaceSuite(HiveTypings, {
  namePostfix: 'Record',
  mappings: { [CmpNames.SecondaryColor]: HiveTypings.ColorsRecord },
});

CfgrUtils.onCfgrReady(() => {
  // Possible case mismatches are now automatically fixed due the call to `registerInterfaceSuite` above
  const color = /** @type {ColorsRecord} */ (CmpUtils.getRecordCmpValue(CmpNames.Colors));
  console.log(color.HexCode);
  // Value-Cmp which uses the same interface (see `mappings` above)
  const secColor = /** @type {ColorsRecord} */ (CmpUtils.getCmpValue(CmpNames.SecondaryColor));
});

Usage

Each record which is accessed from a CJS project has to be assigned to a corresponding interface. For easier usage this can be done with a whole interface-file at once.

Create interface file

The interfaces for all required records can be created in one file or seperated in different ones. Requirements:

  • Folder: ./src/typings/
  • Extension: *.hive.d.ts

Not required columns

It's not mandatory to create a property for every record column. Columns which are never touched in CJS can simply be omitted.

// Record: Key=Number, Name=Text, Address=Text, City=Text
interface MyRecord {
  Key: number;
  Name: string;
}

Optional interface properties

It's possible to add optional properties to the interface, if you want to store additional data for the record in CJS. Simply add a ? after the key of your property to make it optional.

interface MyRecord {
  Key: number;
  Name: string;
  CustomCjsValue?: string;
}

Register in CJS code

Import the generated files from the folder /typings-generated-objs/ and pass the imported object to the register-function.

For details see CmpUtils.registerInterfaceSuite and its options Cbn.CJS.CmpInterfaceSuiteOptions.

At initial load there's only a basic check for missing interfaces according to the content of CustomJSCmps. Specific issues or errors related to the interface itself might stay unnoticed until it is retrieved the first time. Therefore check for any warnings or errors in the console during development.

Map multiple components to same interface

Since v6.0.0 value-components, which aren't of a primitive type, are also automatically parsed to objects. This makes it necessary to register interfaces for them as well. If one or more value-components retrieve data from a record-component or some kind of "BaseObject" it's recommended to define the interface with its "most basic name" and map other components to this type or derive from it.

// typings/index.hive.d.ts
interface FurnitureRecord {
  id: number;
  name: string;
}

type SelectedFurniture = FurnitureRecord[];

// cfgr-main.js
CmpUtils.registerInterfaceSuite(HiveCmpItf, {
  namePostfix: 'Record',
  mappings: {
    [CmpNames.Chairs]: HiveCmpItf.FurnitureRecord,
    [CmpNames.Tables]: HiveCmpItf.FurnitureRecord,
    [CmpNames.SelectedFurniture]: HiveCmpItf.SelectedFurniture, // Value-Cmp
  },
});

Additional info: Cbn.CJS.CmpInterfaceSuiteOptions.mappings

Register Value-Component of type List/Array

To register an array it's required to do the typing with the type keyword instead of interface.

interface Element {
  id: number;
  name: string;
}

type ElementList = Element[];
// OR
type Elements = Element[];

Shortcut if the single Element isn't required:

type ElementList = {
  id: number;
  name: string;
}[];

Troubleshooting

Errors during watch/build process

Project folder

  • Check package.json for installed packages (see .template-project or custom-js-4-partners)
    • dependencies: ts-interface-checker
    • devDependencies: ts-interface-builder
  • Run npm i inside the project folder

Root folder

  • Check package.json for installed packages
    • devDependencies: @combeenation/webpack-hive-itf-to-obj-plugin
    • Get latest version from branch master
  • Run npm i inside the root folder

Folder and files in /typings-generated-objs/ aren't created

  • Check for errors during watch/build process
  • Are there files in ./src/typings/ with the extension *.hive.d.ts

Errors in browser console even after interface was corrected

It might happen that the watch process doesn't detect the changes from the auto-generated file.

  • Restart the watch process
  • Clear cache of the browser-page
  • Restart VSCode

Generated using TypeDoc