How to Use a REST Service in Scout JS

In this how-to a Jakarta RESTful Web Service is created and consumed from a Scout JS Page with Table.

The goal is to create a new Page with Table and load its data from a REST resource.

A working example using the same concepts can also be created using the Scout JS Sample Application. The easiest way to create such an application is to use the new project wizards provided by the Scout plugin for IntelliJ or Eclipse and then selecting TypeScript as the language for the user interface. The created PersonTablePage.ts can be used as a starting point.

In this how-to a similar setup is created step by step.

Validate Prerequisites

Maven Dependencies

Ensure the following Maven dependencies are added to the App module. Add missing dependencies if necessary.

hellojs.app/pom.xml

<dependencies>
  ...
  <!-- Jackson -->
  <dependency>
    <groupId>org.eclipse.scout.rt</groupId>
    <artifactId>org.eclipse.scout.rt.rest.jackson</artifactId>
  </dependency>

  <!-- Jakarta RESTful Web Services with Jersey -->
  <dependency>
    <groupId>org.glassfish.jersey.containers</groupId>
    <artifactId>jersey-container-servlet</artifactId>
  </dependency>
  <dependency>
    <groupId>org.glassfish.jersey.inject</groupId>
    <artifactId>jersey-hk2</artifactId>
  </dependency>
  <dependency>
    <groupId>org.glassfish.jersey.media</groupId>
    <artifactId>jersey-media-json-jackson</artifactId>
  </dependency>
</dependencies>

REST Servlet

Ensure the API servlet is registered using IServletContributor (i.e. AppServletContributors in hellojs.app).

/**
 * Register Jakarta RESTful Web Services Servlet from Jersey.
 */
@Order(20)
public static class ApiServletContributor implements IServletContributor {
  @Override
  public void contribute(ServletContextHandler handler) {
    ServletHolder servlet = handler.addServlet(ServletContainer.class, "/api/*");
    servlet.setInitParameter(ServerProperties.WADL_FEATURE_DISABLE, Boolean.TRUE.toString());
    servlet.setInitParameter(ServletProperties.JAXRS_APPLICATION_CLASS, RestApplication.class.getName());
    servlet.setInitOrder(1); // load-on-startup
  }
}

Provide a RESTful service

In this section the RESTful service and the data transfer objects (DataObjects) it uses are created. The design of the DataObject automatically reflects the design of the REST service.

DataObjects

The DataObjects are created in the hellojs.data module as they might be used in other modules as well. The sample Pizza REST service uses 3 DataObjects:

  1. An object holding the restrictions sent to the service. These restrictions are sent by the client and may be used to configure the result: which attributes should be returned, how many elements, which elements (attribute filter), etc.

    Listing 1. hellojs.data/src/main/java/org/eclipse/scout/apps/hellojs/data/pizza/PizzaRestrictionDo.java
    @TypeName("hellojs.PizzaRestriction")
    public class PizzaRestrictionDo extends DoEntity {
      ...
    }

    In this example, the restrictions are empty for now.

  2. The business object that represent one element in the result list. Here a single Pizza.

    Listing 2. hellojs.data/src/main/java/org/eclipse/scout/apps/hellojs/data/pizza/PizzaDo.java
    @TypeName("hellojs.Pizza")
    public class PizzaDo extends DoEntity {
      public DoValue<Integer> size() {
        return doValue("size");
      }
      public DoValue<BigDecimal> price() {
        return doValue("price");
      }
      ...
    }
  3. The response object. It holds a list of Pizza but may also contain more information or error messages.

    Listing 3. hellojs.data/src/main/java/org/eclipse/scout/apps/hellojs/data/pizza/PizzaResponse.java
    @TypeName("hellojs.PizzaResponse")
    public class PizzaResponse extends DoEntity {
      public DoList<PizzaDo> items() {
        return doList("items");
      }
      ...
    }

Create the RESTful service

Create the RESTful service in the hellojs.api module. This service will answer the request and provide the table data.

Listing 4. hellojs.api/src/main/java/org/eclipse/scout/apps/hellojs/api/pizza/PizzaResource.java
@Path("pizza") (1)
public class PizzaResource implements IRestResource {
  @POST (2)
  @Path("list") (3)
  @Consumes(MediaType.APPLICATION_JSON) (4)
  @Produces(MediaType.APPLICATION_JSON)
  public PizzaResponse list(PizzaRestrictionDo restrictions) { (5)
    MaxResultsHelper.ResultLimiter limiter = BEANS.get(MaxResultsHelper.class).limiter(restrictions); (6)
    List<PizzaDo> pizzas = listPizzas(restrictions, limiter.getQueryLimit()).collect(Collectors.toList()); (7)
    PizzaResponse response = BEANS.get(PizzaResponse.class);
    return response.withItems(limiter.limit(pizzas, response)); (8)
  }

  protected Stream<PizzaDo> listPizzas(PizzaRestrictionDo restrictions, int maxNumberOfRows) {
    // TODO implement service to return data
    return BEANS.get(PizzaService.class).list(restrictions, maxNumberOfRows);
  }
}
1 The path under which the service will be provided. Note: the full path is /api/pizza since the apiServlet is mapped to /api/* in the AppServletContributors class.
2 Even this entry point only reads data, POST is used instead of GET. This allows to pass the restrictions in the body which can hold much more data than the URL query string.
3 The sub path of the list entry point. The full path to this entry point will therefore be /api/pizza/list.
4 This entrypoint expects the restrictions to be passed as JSON and returns the resulting response as JSON.
5 The request data object as input and the response data object as output.
6 Create a result limiter which will be configured using the data provided by the Scout JS Page (see below) and a server side hard limit. The limits are attached to the restriction data object in a special contribution (MaxRowCountContributionDo).
7 Loads the pizzas and uses the query-limit of the limiter as maximum number of rows to return. This limit is one element more than the requested limit to detect if all items have been loaded or more would be available.
8 Fills the response with the items limited to the number of maximum requested rows. The information if more rows would be available is automatically attached to the response by using a contribution (LimitedResultInfoContributionDo). This information can be consumed by the Page on client side to display corresponding messages to the user.

Create the Page with Table

Listing 5. hellojs.ui/src/main/js/pizza/PizzaTablePage.ts
import {ajax, DoEntity, NumberColumn, ObjectOrModel, PageWithTable, PageWithTableModel, systems, Table, TableRow } from '@eclipse-scout/core';

export class PizzaTablePage extends PageWithTable {
  protected override _jsonModel(): PageWithTableModel {
    return {
      leaf: true,
      text: 'Pizzas',
      detailTable: {
        objectType: Table,
        maxRowCount: 10, (1)
        columns: [
          {
            id: 'SizeColumn',
            objectType: NumberColumn,
            text: 'Size',
            width: 200
          },
          {
            id: 'PriceColumn',
            objectType: NumberColumn,
            text: 'Price',
            width: 200
          }
        ]
      }
    };
  }

  protected override _loadTableData(searchFilter: any): JQuery.Promise<PizzaResponse> {
    const resourceUrl = systems.getOrCreate().getEndpointUrl('pizza', 'pizza'); (2)
    const restriction = this._withMaxRowCountContribution(searchFilter); (3)
    return ajax.postJson(resourceUrl + '/list', restriction); (4)
  }

  protected override _transformTableDataToTableRows(tableData: PizzaResponse): ObjectOrModel<TableRow>[] {
    return tableData.items.map(pizza => {
      return { (5)
        cells: [
          pizza.size,
          pizza.price
        ]
      };
    });
  }
}

(6)
export interface PizzaResponse extends DoEntity {
  items: PizzaDo[];
}

 (7)
export interface PizzaDo extends DoEntity {
  size: number;
  price: number;
}
1 A small inline model for the PizzaTablePage having two columns. The Table is configured to only load the first 10 Pizzas.
2 Gets the URL to the PizzaResource. The second argument is the default value which corresponds to the @Path on the PizzaResource class.
3 Attaches the MaxRowCountContributionDo (contains the maxRowCount of the table) to the restrictions.
4 Send the POST to the given URL having the restriction in the body encoded as JSON.
5 Map a single PizzaDo from the response to the column of the table. The mapping is done by index here.
6 The declaration of the PizzaResponse on client side. Attribute names must correspond the PizzaResponse class in Java.
7 The declaration of the PizzaDo on client side. Attribute names must correspond the PizzaDo class in Java.