Custom Field Renderers

Contact your Skuid representative to access this feature.

Field renderers are how Skuid renders the data of an individual field within a component—particularly the Form and Table components—with different strategies and rendering code for the different modes of those components. Skuid builders may create custom field renderers by writing JavaScript snippets, which allow you to display field data in ways Skuid’s standard field renderers may not be able to while also retaining the context and structure provided by an existing Form or Table component.

If you need to parse the data into a specific format, or use HTML to properly display data within a component, but do not require the extensive capabilities of a whole new component, custom field renderers are a useful tool. They could be used to display an embedded video, represent field values as icons, or style field elements in ways the DSS does not currently support.

Note

  • Custom field renderers can only be used on Form and Table components.
  • Writing custom field renderers requires a degree of knowledge in several areas related to JavaScript. This topic assumes you’re already familiar with the concepts covered in the UI code topic.
  • Custom field renderers cannot be used on template fields, which also means that child relationships—which are rendered as templates—may not use custom field renderers.
  • While the examples within this section may be useful and provide guidance on best practices, they are meant to be illustrative in nature and may need adjustment before use in a production environment.

Using custom field renderers

Create a snippet

Field renderers are JavaScript snippets that can be created from the Composer.

  1. Click the JavaScript tab in the Elements pane.

  2. Click add Add Javascript resource and configure

    • Resource type: Field Renderer.
    • Custom field renderer name: Enter an appropriate name describing your field renderers.
    • Snippet body: Edit to include your field renderer JavaScript code.

By creating a snippet in this way, Skuid supplies the boilerplate code with all necessary parameters for a functioning field renderer. This code may be altered as needed as long as the snippet returns a FieldRenderer with the return keyword. For more information on writing field renderers, see the Writing Custom Field Renderers section.

If you are implementing field renderer code written by someone else, you would paste that code within this snippet. Once the code is within a snippet, you can assign that field renderer in a Skuid component.

Assign the field renderer

To assign a field renderer to a field within a component:

  1. Click the field within the target component within the canvas.
  2. Change the Display as property to Custom.
  3. Select the field renderer you wish to assign within the Custom field renderer field.

Writing custom field renderers

The structure of a field renderer

When rendering a field on a component, Skuid expects an instance of the skuid.component.FieldRenderer class. Like Skuid’s own renderers, custom field renderers are made by defining a instance of that class with unique properties.

All field renderer code is structured around getting a reference to the FieldRenderer class, and then returning an instance of that class, created using the new JavaScript operator:

1
2
3
4
5
const FieldRenderer = skuid.component.FieldRenderer;
return new FieldRenderer({
  // The configuration of your field renderer
  // exists here in the "config" parameter
});

Note

See MDN docs for general information on the new operator or JavaScript classes.

The config parameter determines how a field renderer actually operates, containing several key-value pairs that determine how the renderer should behave in the different modes of a component.

Most field renderers will look similar to this example, though it is simplified for illustrative purposes:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
return new FieldRenderer({
  // Set the rendering strategy for read mode mode
  readModeStrategy: "virtual",
  // Code to render the element when in read mode
  read: function (fieldComponent) {
    // Set some variables to use in rendering
    let h = fieldComponent.h,
      context = fieldComponent.cpi.getContext(),
      { element, field, model, row, value } = context;

    // Rendering a very basic element
    return h("div", {}, [`${value}`])
  },
  // Set the strategy for edit mode
  editModeStrategy: "virtual",
  // Code to render when in edit mode
  edit: function (fieldComponent) {
    // Typically the same variables are set as in read mode,
    // but edit mode renderers must react to user input,
    // so you'll need additional logic for `oninput` to keep
    // the component and the model in sync:
    return h("input",
      {
        value: value,
        oninput(event) {
          model.updateRow(row,
            { [field.id]: event.target.value },
            { initiatorId: fieldComponent.id }
          );
        },
      });
  },
  // Set the strategy and render code for data grid mode
  dataGridStrategy: "virtual",
  dataGrid: function (fieldComponent) {
    // These functions usually look similar to edit mode functions
  }
})

Rendering strategies

The <modeType>Strategy parameters determine how Skuid will parse what’s generated by the functions in the read, edit, or dataGrid key values. The rendering strategy chosen for these modes affects how you code your field renderers.

You must set a strategy for each mode you are writing code for:

  • readModeStrategy sets the strategy for the read function
  • editModeStrategy sets the strategy for the edit function
  • dataGridStrategy sets the strategy for the dataGrid function

Note

For more information on the differences between these strategies, see the UI Code topic.

Two render strategies are available for general use:

  • Virtual: Utilizes the virtual DOM and the maquette library to quickly render and re-render components. Similar to the rendering used by Skuid’s own Ink components, and compatible with the Design System Studio. To use this rendering strategy, enter "virtual" as the value for the parameter.
  • Element: Renders and re-renders components by directly manipulating the DOM. Similar to v1’s custom field renderers. To use this rendering strategy, enter "element" as the value for the parameter.

The virtual rendering strategy is considered best practice and is advised in almost all field renderer use cases. This strategy leads to performance improvements and cleaner code. In contrast, the element rendering strategy’s direct DOM manipulation is more taxing on the browser. However, it can allow for quick conversion of v1 field renderers or other UI elements written in jQuery-esque syntax.

Mode functions

After selecting a rendering strategy, the read, edit, or dataGrid key values are written as functions that determine either the virtual DOM node (VNode) or DOM node that will be rendered when the field enters the appropriate mode. The rendering strategy selected above determines how these mode functions must be written.

Note

Regardless of rendering strategy, custom field renderers are mostly logicless by default, meaning some features of Skuid’s default field renderers (e.g., triggering the row updated event on value changes) are not automatically triggered with custom field renderers. However, this event is included in the boilerplate code supplied for the virtual rendering strategy.

The fieldComponent parameter and context

Regardless of rendering strategy, the methods for the read, edit, or dataGrid all receive fieldComponent as their parameter. This is the actual field element within the Form or Table component.

One of the most important functions available within the fieldComponent’s CPI is getContext(). This retrieves the context object of the current field. Using the information available in this object is the key to creating a dynamic, custom field renderer.

  • context.element: The DOM element displayed when the element rendering strategy is used
  • context.field: The field itself as a JavaScript object
  • context.model: The model of the field, as a skuid.model.Model object
  • context.row: The row of the field, as a JavaScript object
  • context.value: The value of the field

It can often be helpful to assign these to variables using destructuring in read and edit functions:

1
2
3
4
5
6
read: function (fieldComponent) {
  let h = fieldComponent.h,
    context = fieldComponent.cpi.getContext(),
    { element, field, model, row, value } = context; // Destructure one or more of these variables this way.
    console.log(value); // This would be now be equivalent to context.value.
    ...

Warning

Always ensure child nodes within your mode functions are distinguishable. See the related section in the UI Code topic for more info.

Additional data grid mode features with dataGridOptions

The Table component’s data grid mode has several additional options that determine how a field cell responds to an end user’s actions. These options are passed into the field renderer through with dataGridOptions.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
dataGridOptions: {
  needsFocusToEdit: true,
  onCopy(fieldComponent) {
    // Custom copy logic code
  },
  onCut(fieldComponent) {
    // Custom cut logic code
  },
  onPaste(fieldComponent, text) {
    // Custom paste logic code
  }
}
  • needsFocusToEdit: Determines whether or not the cell is disabled until the user focuses on it—i.e. requiring the user double click to edit its value. This defaults to true, but this may not be desirable for all fields types For example, end users typically want to interact with boolean checkboxes without focusing the cell first.

  • onCopy and onCut: Determine what happens when the end user copies or cuts the cell, respectively. The fieldComponent calling the renderer is passed into these methods as the first argument.

    Errors thrown within these methods are caught and shown at runtime as toast messages. For example, a password field could prevent copying values with new Error("Can't copy passwords");

1
2
3
4
5
6
onCopy(fieldComponent) {
  throw new Error("Can't copy passwords");
},
onCut(fieldComponent) {
  throw new Error("Can't cut passwords");
}
  • onPaste: Determines what happens when the end user pastes within the cell. The fieldComponent calling the renderer is passed into the method as the first argument, and the field’s value is passed in as its second argument (text), so that value can be used for custom paste logic.

When using the virtual rendering strategy

When using the virtual rendering, you must return a Hyperscript function:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// A basic read mode function using the element strategy
read: function (fieldComponent) {
  let h = fieldComponent.h,
    context = fieldComponent.cpi.getContext(),
    { element, field, model, row, value } = context;

  // This element just displays the value of the field,
  // which is retrieved from context above
  return h("div", {}, [`${value}`])
}

CSS definitions are passed in as key-value pairs in the styles object. These keys can be camelCase or strings that reflect the kebab-case name of the CSS property. For example, the following styles settings will result in the same CSS values.

1
2
3
4
5
6
styles: {
  fontSize: "20px",
}
styles: {
  "font-size": "20px",
}

Using the element rendering strategy

In contrast to virtual rendering, which requires a HyperScript node be returned, element rendering looks to the fieldComponent’s context.element attribute, which is an empty div by default. So in this strategy, you must update that element:

1
2
3
4
5
6
7
8
9
// A basic read mode function using the element strategy
read: function (fieldComponent) {
  let context = fieldComponent.cpi.getContext(),
    { element, field, model, row, value } = context;

  // This element just displays the value of the field,
  // which is retrieved from context above
  element.innerText = value;
}

Changes to this DOM element must be made using standard DOM element APIs and properties. Additional elements must be made using other DOM manipulation APIs like document.createElement() or jQuery functions a la skuid.$, and then appended to the context.element.

After creating the DOM element, the context.element parameter must then be set to that newly created node. Style variants are not applied.

Troubleshooting

Because custom field renderer implementations vary widely by use case, troubleshooting custom code can often be a bit more involved than Skuid’s declarative options. If you’re seeing issues with your field renderers, be sure to try the following:

  • Test the runtime of your field renderer and look for errors in the browser console.
  • Try console.log(variable) statements to see the state of variables at different points in the field renderer. This can be particularly helpful for context.
  • Consider adding debugger statement as breakpoints.
  • If your field renderer has multiple VNodes and you’re seeing odd UI behavior, always ensure those VNodes have unique keys.