Three ways to make a REST call from Oracle JET

GET Data Into Your App

In this post, I will demonstrate three methods for loading data into your JET application using GET calls to retrieve data from a REST API.  You don’t need to know anything about JET to follow through the examples.  However, I do assume that you’re familiar with JET so I won’t be explaining most of the JET functionality.  If you would like to know more about Oracle JET, check out these resources.

Setup

The setup section will walk through creating a functioning JET application you can use to call each of the examples and display the data.  If you prefer to just read through the examples you can skip down to the REST GET Examples section.

In order to make the REST calls, we’ll need an application with a REST API.  I’ll be using DinoDate for the back end API.

Go to https://github.com/oracle/dino-date and follow the installation instructions.  Verify that DinoDate is running before proceeding.

DinoDate includes a JET front end that you can look through, but for this post, we’re going to replace it with the navbar JET template.

Start with a template

Rename the commonClient directory and create a new empty directory:

Install Oracle JET following the Oracle Jet Get Started guide:

Oracle JET is a very active open source project so the following may change over time.  If the version you’re using is different, try to locate the files mentioned in the following commands.  Locating the /src directory is required.  If you can’t find the rest of these files, the examples should still work, they just won’t be as pretty.

Alternatively, you can install the version of the generator used for this post instead of the global install above:

This JET template comes with a lot of pre-installed functionality added to the tempJet directory.  For this post, we’ll grab only the files that we need and put them in a jet directory

  • Create the directory structure ‘jet/css/libs/oj/v2.3.0/alta’.
  • Copy everything from tempJet/src to the root of the new jet folder.
  • Copy and rename the .css file.
  • Copy the fonts and images directories.
  • Delete the tempJet folder.
  • CD into jet.
Now if you haven’t already started DinoDate, follow the instructions to start your preferred middle tier.

Open a browser and go to http://localhost:3000/ (use the port for the mid-tier you started) and you should now see this.

Convert Existing Module

Rather than create a new module from scratch, I’m going to change the Customer module into a DinoDate member search module.  This way we can see what’s included in the template and how it’s ‘wired’ together.

If you’re not already there, navigate to dino-date/commonClient/jet/

Rename the files js/views/customers.html and js/viewModels/customers.js to search.html and search.js.

In order to change our the Navigation List Item from ‘Customer’ to ‘Search’, we need to edit js/appController.js.

Change the code on the following lines (Line numbers may change with future versions of JET):
22:  'customers': {label: 'Customers'}, to  'search': {label: 'Search'},

33:  {name: 'Customers', id: 'customers', to  {name: 'Search', id: 'search',

34:  iconClass: 'oj-navigationlist-item-icon demo-icon-font-24 demo-people-icon-24'}, we’ll use the magnifier icon  iconClass: 'oj-fwk-icon-magnifier oj-fwk-icon oj-navigationlist-item-icon'},

DinoDate creates a user token as part of the login process.  This token is required to access the API.  This process is not really relevant to the examples so while we’re in appController.js we’ll add a helper (cheater) function to make the examples work.

The $.ajax function will automatically log us in as the Administrator user and set the authorization token when the application starts.

The getHeaders function will be used later to generate the headers used by DinoDate for authorization and processing options.

Add this code after   self.navDataSource = .... , line 38.

Edit js/index.html and add an id property to the <div> with role=”main”.

Refresh the page and click the Search tab.

Prepare the viewModel

Edit js/viewModels/search.js

Add the Jet components we plan to use in our view, to the list of dependencies.

Change CustomerViewModel to SearchViewModel for the function name and in the return statement at the bottom.

Delete everything inside the SearchViewModel function except for var self = this;

Add a variable ‘rootViewModel’ to give us access to js/appController.js.  We’ll use this to access the getHeaders function.

If you used a different id value in the <div id="mainContent" role="main"  section above, use that id instead of ‘mainContent’.

Add some Knockout observables we’ll need for the view data-binding.  We’ll use the observable searchRun to display the method used to call the REST API.

Add a function to generate the URL for the search API.

Stub in the search functions.

Change the View

Now let’s switch over to our view, edit js/views/search.html.

Delete the all of the HTML in the file.

If you want to dig into the details on the components we’re using you can check out the CookBook.  For this example, we’ll just identify the components we’re using.

Add an ojInputText and an ojInputNumber to accept our search criteria for a Keyword and Distance.  Set their values to the correct observables in the viewModel.

Add three ojButton components, one for each method we’re going to demonstrate. We’ll set them to be disabled until the token in appController.js is populated to prevent querying before we’re logged in.

Display which search function as been run and add an ojTable component to display the returned data.

And finally, an ojPagingControl to add pagination controls.

Notice the data property for the table and paging controls are both using the same ko.observable, memberData.

The setup is complete, let’s flesh out our search functions.

REST GET Examples

viewModel

Using the viewModel method is typically the default approach for making REST calls in JET.  The JET components (oj.Model and oj.Collection) implement a lot of functionality behind the scenes allowing you to focus on your application instead of generic plumbing.

  • Define the Member model by extending oj.Model .  Since we don’t plan to do any single record functions, we only need to define the idAttribute of a Member.
  • Define a collection of by extending oj.Collection.
  • Set the base URL for the REST API.
  • Set the model to Member, which we defined above.
  • Set customURL to our getHeaders function in appController.js.  This adds the headers needed for DinoDate.
  • Parse the returned object and return the member array ‘items’.
    Sometimes we only need part of the data returned by the REST API, so we need to define a function to parse the response.  For our examples, all we need is the members.items array.  If your REST API returns only the array, you shouldn’t need to define a parse function at all.
  • Create a new Members collection.
  • Create the members variable using membersColl to create a new oj.CollectionTableDataSource which is then used to create a new oj.PagingTableDataSource.
  • Modify the URL of membersColl using the searchURL function.  This will create the URL using the ko.observables keywords and maxDistance.
  • Call the fetch function.
  • Set self.memberData(self.members); after the fetch() has returned.

Replace the stubbed searchViewModel function with this code.

In your browser, refresh your page and search for the letter ‘a’ using the ViewModel button. You should see a list of Member names. The ojPagingControl defines a page as 10 records so you may have multiple pages of members.

Shared Model

Sometimes you will want to use the same Model and/or Collection across multiple functions or viewModels.  Trying to keep our application as DRY as possible, we’ll move the model and collection definitions out to their own files.

Let’s create a new directory /js/models  and two new files /js/models/Member.js  and /js/models/Members.js .

For both files, we’ll define components we need to include and also add the variable rootViewModel.

In Member.js, we’ll create a Members Model.  This model could be used by itself to work with a single Member object, see oj.Model for more information.

  • Create a Member by extending oj.Model, as shown in the last example.
  • Add the urlRoot for the members endpoint of your RESTFull API.
  • The customURL property is used for the DinoDate headers, just like in the previous Collection example.  If your application doesn’t need to modify the headers, you could exclude this property.
  • Return the new Model.

Edit Member.js and add the following code.

For our Members Collection:

  • Add the new member model to the define dependencies array.
  • Accept the member model as an argument to the function.
  • Create a Members collection by extending oj.Collection, like in the last viewModel example.
  • Set the URL.  In this case, it’s the same as the rootUrl for Member.
  • Set the model to Member.
  • We use the customURL for the DinoDate headers, the same as in the other examples.
  • Parse the returned object.  When DinoDate returns a set of members it includes some extra data.  We are only interested in the items array.
  • Return the collection.

Edit Members.js and add the following code.

Back to search.js.

Include our new collection in the define dependencies array, add ‘models/Members’ right after ‘jquery’.

Accept the Members Collection into the function definition by adding the Members argument.

To flesh out the searchSharedModel function we simply copy the code from searchViewModel() excluding the Model and Collection definitions.

  • Create a new Members collection.
  • Create the members variable using membersColl to create a new oj.CollectionTableDataSource which is then used to create a new oj.PagingTableDataSource.
  • Modify the URL of membersColl using the searchURL function.  This will create the URL using the ko.observables keywords and maxDistance.
  • Call the fetch function.
  • Set self.memberData(self.members); after the fetch() has returned.

Replace the stubbed searchSharedModel function with this code.

Refresh your page and search for ‘a’ again using the Shared Model button, you should see the same results as the ViewModel button.

AJAX

Sometimes you may need some specific functionality not supported by the framework.  If extending oj.Collection or oj.Model doesn’t meet all of your needs, you can use the jQuery ajax method.

  • We’re using a standard $.ajax call with a type of “GET”.
  • Set the headers needed by DinoDate using the getHeaders() function in appController.js.
  • The searchURL function will create the URL using the ko.observables keywords and maxDistance.
  • Assuming everything is setup properly and we get a successful response we need to create the proper object types for the Jet components we’re using in the view.
    • Since we are using a paging control, we’ll need an oj.PagingTableDataSource object which we’ll create from an oj.ArrayTableDataSource object.
    • The oj.ArrayTableDataSource object is created from the items array returned in the GET response.
    • We also need to identify the idAttribute of our returned objects, which is ‘memberId’.
  • Finally, we’ll set the Knockout Observable self.memberData to our new resData object.
  • If there’s a failure, show it in an alert.

Replace the stubbed searchAJAX function with this code.

Refresh your page and search for ‘a’ using the AJAX button, you should see the same results as the other buttons.

If you are watching the execution time in the returned object, be aware that the time is only tracking the call from the middle tier to the database, so these changes to the client side code do not affect the execution time. Any differences for these examples should be ignored.

Be Flexible

As you build applications with Oracle JET, you will probably use the viewModel method the most.  However, don’t be afraid to use other methods when they make more sense for your application. It is perfectly acceptable to mix and match these different approaches as needed. Use the shared model to try and stay DRY and the AJAX method for special cases.

As the saying goes; When you only have a hammer, everything looks like a nail.  Keep your toolbox full.

3 thoughts on “Three ways to make a REST call from Oracle JET”

  1. Hi.

    Great info I’m learning a lot. Up so far I’ve been fetching the data with the JQuery $.getJSON method but this way seems much cleaner.

    How does that work if your results are broken down in many JSON pages ? I’m using Oracle ORDS to test Oracle Jet and it have a 500 results limit. If there’s more than that, there’s a I get a pointer to the next document like below.

    {“next”:{“$ref”:”http://example.com/apex/xyz/logs/get/ ?page=1″},”items”:[{“thing”:”thing1″}, … ]}
    ..
    {“next”:{“$ref”:”http://example.com/apex/xyz/logs/get/ ?page=2″},”items”:[{“thing”:”thing501″}, … ]}
    ..
    {“next”:{“$ref”:”http://example.com/apex/xyz/logs/get/ ?page=N”},”items”:[{“thing”:”thingNN1″}, … ]}

    How can I handle this kind of paginated query with the Model and Collection?

    Thanks!

    1. Dom,

      Basically, ORDS and JET should know how to work with each other. When you don’t return all the rows (by setting the fetchSize), JET considers this a virtual collection:
      http://www.oracle.com/webfolder/technetwork/jet/jsdocs/oj.Collection.html

      ORDS should send through some properties that JET knows how to work with, such as “count” and “hasMore”. Then, if you use the JET Collection with, say the Table component and the Paging control, it should all just work:
      http://www.oracle.com/webfolder/technetwork/jet/jetCookbook.html?component=pagingControl&demo=basicPagingTable

      What is the “Source Type” for your REST handler in ORDS? Is it set to “feed”? If so, switch it to Collection Query.

  2. Other comments/request if you don’t mind:

    First I tried it and the table didn’t updated itself when I put the self.memberData(members); in the ..”fetch().then” function.

    It worked perfectly fine if I put it before the call to the fetch function. Do you have an idea of why it behaves like this?

    self.memberData = ko.observable();

    var Member = oj.Model.extend({
    idAttribute: “key”
    });

    var Members = oj.Collection.extend({
    url: “http://example.com/abc/stats”,
    model: Member,
    //used to generate authentication and config headers
    // The parse function modifies the response object, extracting and returning the members array.
    parse: function(obj) {
    console.log(obj.aggregations.by_1d.buckets);
    return obj.aggregations.by_1d.buckets;
    }
    });

    var membersColl = new Members();

    // Data source for ojTable and ojPagingControl controls.
    var members = new oj.PagingTableDataSource(new oj.CollectionTableDataSource(membersColl));
    //

    //work here
    self.memberData(members);
    membersColl.fetch().then(function(){
    //doesn’t work here
    self.memberData(members);
    });

    My other question is how can you make this work with a visualization the need series AND groups. I’d need to be able to parse some fields to be the series and another one for the group but i don,t see how to do it this way.. Do you have a small code example that could help me to understand how to do it with the fetch (instead of an ajax query wth $getJSON) ?

    Thanks for the help!

Leave a Reply

Your email address will not be published. Required fields are marked *