How to Create a Custom Field

This document is referring to a past Scout release. Please click here for the recent version.

This cheat sheet shows how to implement your own custom field for a ScoutJS application. In this example we will write a FlipCard field that will show a playing card. Clicking on the card will flip it from one side to the other.

Setup

For this example we use the helloscout git repository, but you can easily create the field in your own code base as well. In case you want to use the helloscout repo, clone and import it into your favourite IDE:

git clone https://github.com/bsi-software/helloscout.git

Read the readme and start the hellojs application to make sure it works.

Add Empty TS and CSS Files

Create the following files in the folder org.eclipse.scout.hellojs.ui.html

  • src/main/js/flipcard/FlipCardField.ts The TypeScript file representing the field.

  • src/main/js/flipcard/FlipCardField.less The LESS file containing the styles of the field.

Add the JS file to the JS index

Listing 1. index.ts
...
export * from './flipcard/FlipCardField';
...

Add the LESS file to the LESS index

Listing 2. index.less
...
@import "flipcard/FlipCardField";
...

Minimal Code for a New FormField

Create a Minimal FormField

The FlipCard will inherit from FormField. Every form field consists of a container, a label, the actual field, a mandatory-indicator and a status.

Listing 3. FlipCardField.ts
import {FormField} from '@eclipse-scout/core';

export class FlipCardField extends FormField {
  protected override _render() {
    // Create the container
    this.addContainer(this.$parent, 'flip-card-field');

    // Add a label
    this.addLabel();

    // Create the actual field. This will be your flip card.
    let $field = this.$parent.appendDiv('content');
    // add the field to the form field.
    this.addField($field);

    // Add other required form field elements
    this.addMandatoryIndicator();
    this.addStatus();
  };
}

Since we use JQuery in our FlipCardField, we should add it as dependency to our package.json to get code completion and proper syntax highlighting. Don’t forget to add the types as well because JQuery does not include them.

Listing 4. package.json
{
  ...
  "devDependencies": {
    ...
    "@types/jquery": "3.5.29"
  },
  "dependencies": {
    ...
    "jquery": "3.7.1"
  }
}

Add the FlipCard to the HelloForm

Don’t forget to add the import for your FlipCardField.

Listing 5. HelloFormModel.ts
import {FlipCardField, FormModel} from '@eclipse-scout/core';

export default (): FormModel => ({
  ...
  rootGroupBox: {
    ...
    fields: [
      {
        id: 'DetailBox',
        ...
        fields: [
          {
            id: 'NameField',
            ...
          },
          (1)
          {
            id: 'FlipCardField',
            objectType: FlipCardField,
            label: 'Flip the card',
            gridDataHints: {
              h: 5,
              weightY: 0
            }
          },
          {
            id: 'GreetButton',
            ...
          }
        ]
      }
    ]
  }
});
1 The FlipCard field

Now reload your browser and you should get the following result:

Result Minimal Form Field
flipCard01
Listing 6. FlipCardField.ts
import {FormField} from '@eclipse-scout/core';

export class FlipCardField extends FormField {
  $card: JQuery;
  $front: JQuery;
  $back: JQuery;
  flipped: boolean;
  frontImage: string;
  backImage: string;

  constructor() {
    super();
    this.flipped = false;
  }

  protected override _render() {
    // Create the container
    this.addContainer(this.$parent, 'flip-card-field');
    // Add a label
    this.addLabel();

    // Create the actual field (1)
    let $field = this.$parent.appendDiv('content');
    // Create the card inside the field
    this.$card = $field.appendDiv('card')
      .on('mousedown', this._onCardMouseDown.bind(this)); (2)
    this.$front = this.$card.appendDiv('front');
    this.$back = this.$card.appendDiv('back');
    // Add the field to the form field. It will be available as this.$field.
    this.addField($field);

    // Add other required form field elements
    this.addMandatoryIndicator();
    this.addStatus();
  }

  protected override _renderProperties() { (3)
    super._renderProperties();
    this._renderFrontImage();
    this._renderBackImage();
    this._renderFlipped();
  }

  protected _renderFrontImage() {
    if (this.frontImage) {
      this.$front.append(`<img src="${this.frontImage}">`);
    }
  }

  protected _renderBackImage() {
    if (this.backImage) {
      this.$back.append(`<img src="${this.backImage}">`);
    }
  }

  protected override _remove() { (4)
    super._remove();
    this.$card = null;
    this.$front = null;
    this.$back = null;
  }

  protected _onCardMouseDown(event: JQuery.MouseDownEvent) { (2)
    this.setFlipped(!this.flipped);
  }

  setFlipped(flipped: boolean) {
    this.setProperty('flipped', flipped);
  }

  protected _renderFlipped() {
    this.$card.toggleClass('flipped', this.flipped);
  }
}
1 Create the dom elements in the render function.
2 Add event handler which toggles the CSS class flipped.
3 Initial rendering of the properties. Applies the state to the DOM.
4 Keep the references clean. Reset DOM references when the field has been removed.
Listing 7. HelloFormModel.ts
import {FlipCardField} from '../index';

export default {
  id: 'hellojs.HelloForm',
  ...
  rootGroupBox': {
    ...
    fields: [
      {
        id: 'DetailBox',
        ...
        fields: [
          {
            id: 'NameField',
            ...
          },
          (1)
          {
            id: 'FlipCardField',
            objectType: FlipCardField,
            label: 'Flip the card',
            frontImage: 'img/card-back.jpg',
            backImage: 'img/card-front.jpg',
            gridDataHints: {
              h: 5,
              weightY: 0
            }
          },
          {
            id: 'GreetButton',
            ...
          }
        ]
      }
    ]
  }
}
1 FlipCard field is inserted after the name field.
Listing 8. FlipCardField.less
.flip-card-field {

  .card {
    position: absolute;
    cursor: pointer;
    height: 100%;
    width: 152px;
    transition: transform 1s; (1)
    transform-style: preserve-3d;

    &.flipped {
      transform: rotateY( 180deg );
    }

    & > div {
      display: block;
      height: 100%;
      width: 100%;
      position: absolute;
      backface-visibility: hidden; (2)

      &.back {
        transform: rotateY( 180deg ); (3)
      }

      & > img {
        height: 100%;
        width: 100%;
      }
    }
  }
}
1 Animation of the card.
2 Ensure back side is not visible.
3 Rotation to back side.

Finally, create a folder img in the WebContent folder (org.eclipse.scout.hellojs.ui.html.app/src/main/resources/WebContent) and paste the two images of the card into that folder. You should be able to find the images using Google ;-)

Result Flip Card
flipCard02