Fetch data from a REST API in an Oracle JET web app
Introduction
This tutorial shows you how to access a REST service, integrate it into your Oracle JavaScript Extension Toolkit (Oracle JET) web app, and bind data to a list view in your user interface.
Objectives
In this tutorial, you will learn how to create instances of the RESTDataProvider
class. This class represents data available from JSON-based REST services.
Prerequisites
-
A development environment set up to create Oracle JET web apps that includes an installation of Node.js
-
(Option 1) Completion of the final tutorial in the previous learning path in this series: Handle Selection Events in an Oracle JET Web Application
-
(Option 2) If you haven’t completed the previous learning path in this series: the jet_web_application_temp.zip downloaded
Task 1: Download the Starter App
Skip this task if you’re continuing to work in an app that you created in the previous learning path.
-
Rename
jet_web_application_temp.zip
asJET_Web_Application.zip
. Extract the contents to theJET_Web_Application
folder. -
Navigate to the
JET_Web_Application
folder, and restore the Oracle JET web app.npm install
The app is ready to use.
Task 2: Access the REST Service
Click the Apex link to view the REST data for the Activities resource endpoint.
The data contains a list of activities with various attributes.
{
"items": [
{
"id": 1,
"name": "Baseball",
"short_desc": "Equipment we carry for baseball players.",
"image": "css/images/product_images/baseball.jpg"
},
. . .
],
"hasMore": false,
"limit": 25,
"offset": 0,
"count": 4,
"links": [
{
"rel": "self",
"href": "https://apex.oracle.com/pls/apex/oraclejet/lp/activities/"
},
. . .
]
}
Familiarize yourself with the data and the properties that the endpoint returns. You’ll need to understand these details when you create an instance of RESTDataProvider
later in this tutorial. Note, for example, how the endpoint returns an items
property that references a series of individual activities.
Task 3: Create a Data Provider to Fetch Activity Data in the ViewModel
-
Navigate to the
JET_Web_Application/src/ts/viewModels
directory and open thedashboard.ts
file in an editor. -
At the start of the
dashboard.ts
file, import theRESTDataProvider
class.import * as AccUtils from "../accUtils"; . . . import { RESTDataProvider } from "ojs/ojrestdataprovider"; . . . type Activity = { id: number; }; . . . type ActivityItems = { id: number; name: string; items: Array<Item>; short_desc: string; image: string; }; class DashboardViewModel { . . .
Note: If you did not complete the previous learning path in this series, you also need to declare a type alias for
Activity
, which is the type that theactivityDataProvider
instance in the next step requires. You specify this type alias before theDashboardViewModel
class declaration. -
In the
DashboardViewModel
class, declare the following variables and comment out or delete the preexistingactivityDataProvider
of typeMutableArrayDataProvider<Activity["id"], Activity>
.. . . class DashboardViewModel { // Activity key attribute that you'll pass as a parameter when creating // RESTDataProvider instance keyAttributes = "id"; // REST endpoint that returns Activity data restServerURLActivities = "https://apex.oracle.com/pls/apex/oraclejet/lp/activities/"; // RESTDataProvider instance activityDataProvider: RESTDataProvider<Activity["id"], Activity>; // MutableArraryDataProvider that we no longer use, and // so we comment it out. // activityDataProvider: MutableArrayDataProvider<Activity["id"], Activity>;
-
In the
constructor()
method of theDashboardViewModel
class, comment out or delete the preexistingactivityDataProvider
variable from theMutableArrayDataProvider
class and create theRESTDataProvider
instance.. . . class DashboardViewModel { . . . restServerURLActivities = "https://apex.oracle.com/pls/apex/oraclejet/lp/activities/"; activityDataProvider: RESTDataProvider<Activity["id"], Activity>; . . . constructor() { . . . // this.activityDataProvider = new MutableArrayDataProvider<Activity["id"], // Activity>(JSON.parse(storeData), { // keyAttributes: "id", // }); this.activityDataProvider = new RESTDataProvider({ keyAttributes: this.keyAttributes, url: this.restServerURLActivities, transforms: { fetchFirst: { request: async (options) => { const url = new URL(options.url); const { size, offset } = options.fetchParameters; url.searchParams.set("limit", String(size)); url.searchParams.set("offset", String(offset)); return new Request(url.href); }, response: async ({ body }) => { const { items, totalSize, hasMore } = body; return { data: items, totalSize, hasMore }; }, }, }, }); . . .
Note: The
response
function above extracts data and other properties from the endpoint response body and must return an object with adata
property. Given that the endpoint we work with returns anitems
property, we assign this latter property todata
in theresponse
function.
Task 4: Create a Data Provider to Fetch Item Data in the ViewModel
You use another RESTDataProvider
instance to fetch a subset of the data that is the list of items for a particular activity. You do this by supplying a new URL that contains the selected activity ID. You’ll create an initial placeholder RESTDataProvider
instance to use until such time as a user selects an activity, at which point you’ll create the actual RESTDataProvider
instance in the selectedActivityChanged
event listener method.
-
In the
DashboardViewModel
class of the opendashboard.ts
file, initialize a placeholder instance of theRESTDataProvider
for activity items, declare types for the additional variables needed to create the REST endpoint to return activity item data, and comment out or delete both theitemsDataProvider
declaration of typeMutableArrayDataProvider<Item["id"], Item>
and theitemsDataProvider
variable itself in theconstructor()
method.class DashboardViewModel { // Activity key attribute that you'll pass as a parameter when creating // RESTDataProvider instance keyAttributes = "id"; // REST endpoint that returns Activity data restServerURLActivities = "https://apex.oracle.com/pls/apex/oraclejet/lp/activities/"; // RESTDataProvider instance activityDataProvider: RESTDataProvider<Activity["id"], Activity>; // Initialize activityKey to 3 to construct an initial REST call activityKey: number = 3; // Initialize placeholder RESTDataProvider itemsDataProvider: RESTDataProvider<Item["id"], Item> = new RESTDataProvider< Item["id"], Item >({ keyAttributes: "id", url: "", transforms: { fetchFirst: { request: null!, response: (): any => { return { data: [] }; }, }, }, }); // REST endpoint that returns Item data restServerURLItems = "https://apex.oracle.com/pls/apex/oraclejet/lp/activities/" + this.activityKey + "/items/"; constructor() { . . . // this.itemsDataProvider = new MutableArrayDataProvider<Item["id"], Item>( // itemsArray, // { keyAttributes: "id" } // );
-
In the
selectedActivityChanged
method of theDashboardViewModel
class, write code to create a new instance of theitemsDataProvider
variable with the new activity items when a new activity is selected.This
itemsDataProvider
also includes a text filter to filter on the activity itemname
field.selectedActivityChanged = ( event: ojListView.firstSelectedItemChanged<ActivityItems['id'], ActivityItems> ) => { /** * If no items are selected then the firstSelectedItem property returns an object * with both key and data properties set to null. */ let itemContext = event.detail.value.data; if (itemContext != null) { // If selection, populate and display list // Hide currently-selected activity item this.activitySelected(false); this.activityKey = event.detail.value.data.id; this.restServerURLItems = 'https://apex.oracle.com/pls/apex/oraclejet/lp/activities/' + this.activityKey + '/items/'; // Create the itemsDataProvider instance of RESTDataProvider this.itemsDataProvider = new RESTDataProvider({ keyAttributes: this.keyAttributes, capabilities: { filter: { textFilter: true, }, }, url: this.restServerURLItems, textFilterAttributes: ['name'], transforms: { fetchFirst: { request: async (options) => { const url = new URL(options.url); const { size, offset } = options.fetchParameters; url.searchParams.set('limit', String(size)); url.searchParams.set('offset', String(offset)); const filterCriterion = options.fetchParameters.filterCriterion as TextFilter<Item>; const { textFilterAttributes } = options.fetchOptions; if (filterCriterion && filterCriterion.text && textFilterAttributes) { const { text } = filterCriterion; textFilterAttributes.forEach((attribute) => { url.searchParams.set(attribute, text); }); } return new Request(url.href); }, response: async ({ body }) => { const { items, totalSize, hasMore } = body; return { data: items, totalSize, hasMore }; }, }, }, }); // Set List View properties this.activitySelected(true); this.itemSelected(false); this.selectedItem(); this.itemData(); } else { // If deselection, hide list this.activitySelected(false); this.itemSelected(false); } };
-
At the start of the
dashboard.ts
file, import theTextFilter
interface.import * as AccUtils from "../accUtils"; . . . import { RESTDataProvider } from "ojs/ojrestdataprovider"; import { TextFilter } from "ojs/ojdataprovider"; . . .
Your file should look similar to final-dashboard-rest-dp-ts.txt.
Task 5: Add an Error Handler to Manage a Failure to Fetch Data
The RESTDataProvider
instance provides an error option that you can use to invoke a callback function when an attempt to fetch data fails. You’ll implement this capability for the scenario where an attempt to fetch the list of activities fails.
-
Navigate to the
JET_Web_Application/src/ts/viewModels
directory and open thedashboard.ts
file in an editor. -
In the
DashboardViewModel
class, before theconstructor()
method, add an observable for the status of the data fetch, plus a variable to reference an error message.class DashboardViewModel { . . . firstSelectedItem = ko.observable(); dataFetched = ko.observable(true); fetchError: string = (''); constructor() { . . .
-
In the
activityDataProvider
variable that referencesRESTDataProvider
, add theerror
option and a reference to the callback function that it invokes (fetchErrorHandler
).this.activityDataProvider = new RESTDataProvider({ keyAttributes: this.keyAttributes, url: this.restServerURLActivities, error: this.fetchErrorHandler, transforms: { fetchFirst: { . . .
-
After the
constructor()
method of theDashboardViewModel
class, write code for thefetchErrorHandler
callback function that theRESTDataProvider
invokes if an attempt to fetch data fails.. . . } // end constructor fetchErrorHandler = (errorDetail: RESTDataProvider.FetchErrorDetail<number, Activity> | RESTDataProvider.FetchResponseErrorDetail<number, Activity>) => { this.dataFetched(false); if (errorDetail.hasOwnProperty('response')) { this.fetchError = `${(errorDetail as RESTDataProvider.FetchResponseErrorDetail<number, Activity>).response.status}`; } else { this.fetchError = (errorDetail as RESTDataProvider.FetchErrorDetail<number, Activity>).error.message; } } . . .
-
Save the
dashboard.ts
file.Your
dashboard.ts
file should look similar to final-dashboard-error-handler-ts.txt. -
Navigate to the
JET_Web_Application/src/ts/views
directory and open thedashboard.html
file in an editor. -
Find the
div
element whereid="parentContainer1"
and wrap it with anoj-bind-if
custom HTML element with thetest
attribute set to the state of thedataFetched()
observable.. . . <h1>Product Information</h1> <oj-bind-if test="[[dataFetched()]]"> <div id="parentContainer1" class="oj-flex oj-flex-init"> . . . </div> </oj-bind-if>
-
Before the last
div
element in thedashboard.html
file add anotheroj-bind-if
custom HTML element with thetest
attribute set to the state of thedataFetched()
observable.This newer
oj-bind-if
custom HTML element wraps the HTML elements to display if theRESTDataProvider
failed to fetch data. In our example, we render a<p>
HTML element with a short message for the end user.. . . <oj-bind-if test="[[!dataFetched()]]"> <p>Sorry that we couldn't get your product information right now. Please contact your system administrator.</p> </oj-bind-if> </div>
-
Save the
dashboard.html
file.Your
dashboard.html
file should look similar to final-dashboard-error-handler-html.txt.
Task 6: Test the Web App
-
In the terminal window, change to the
JET_Web_Application
directory and run the web app.npx ojet serve
-
In the browser window, view the dynamic changes in your web app.
-
Close the browser window or tab that displays your running web app.
-
In the terminal window, press Ctrl+C, and if prompted, enter
y
to exit the Oracle JET tooling batch job. -
In the terminal window, run the web app using the following additional command-line arguments.
npx ojet serve --server-port=8144 --livereload-port=8145
On this occasion, the web app displays the following message because the REST service that it tries to access only accepts requests on the server port that the
ojet serve
command uses by default (8000
), so the attempt by theRESTDataProvider
to fetch from the REST service failed.Sorry that we couldn't get your product information right now. Please contact your system administrator.
Next Step
To proceed to the next tutorial in this learning path, click here.
More Learning Resources
Explore other labs on docs.oracle.com/learn or access more free learning content on the Oracle Learning YouTube channel. Additionally, visit education.oracle.com/learning-explorer to become an Oracle Learning Explorer.
For product documentation, visit Oracle Help Center.
Fetch data from a REST API in an Oracle JET web app
F11593-11
September 2024