Create a Web App with Oracle JET

by Kenneth Lange  

Oracle has finally thrown its hat in the JavaScript ring by launching Oracle JET, which is their brand new JavaScript framework.

The framework is made up of several popular JavaScript libraries together with a number of new Oracle modules (including some really nice UI widgets for data visualization and charting).

In this post we will get some hands-on experience with Oracle JET by creating a simple app from scratch.

What is Oracle JET?

Oracle JET is not a single, integrated JavaScript framework (like Angular), but a modular one where you can replace the individual modules, if you like. The thinking behind the modular approach is that the world of JavaScript is highly volatile, so if one of the modules in the stack falls out of favor, you only need to replace that module and you only need to rewrite a subset of your application.

It is hard to overstate how important it is for enterprise customers (which Oracle targets) that a framework is future-proof and will not suddenly be abandoned. I mean the AngularJS team’s bold decision to ditch backward compatibility in AngularJS 2.0 was not well-received in the enterprise world, and led to the same reaction as when Luke is told that Darth Vader is his father!

Our Simple App

The launch of Oracle JET has been surrounded by a lot of marketing hype, so to cut through the hype I thought it could be interesting to create a small app to get hands-on experience with how the framework actually works.

So let’s just for fun reimplemented the Movie App (from Sandeep Panda’s epic blog post Creating a CRUD App in Minutes with Angular’s $resource) in Oracle JET.

The Movie App is a simple CRUD app where you can maintain your movie database. While it’s obviously a demo app, which could easily be beaten by an Excel spreadsheet, I think for demo purposes it has several interesting features:

  • A Single Page App with multiple “pages” (or states as they are also called).
  • Reuse of functionality among pages.
  • Passing parameters between pages.
  • Integration with a RESTful web service.

Below, you can see a screenshot of the Movie App implemented in Oracle JET:

In this demo app the UI has not been a priority. In a future post, I might use Oracle’s new Alta UI and add some of those nice charts that are included in Oracle JET.

Getting Started

You can download the Oracle JET framework from here (choose “Base Distribution”).

I already have EasyPHP installed locally, so I simply copy/pasted Oracle JET into its own directory (movies) and open (http://localhost/movies) in a browser and that was basically the installation!

You can download the demo code from this post here, which you need to copy/paste into your JET directory, and then it should just work.

  movies
    index.html
    /js
      main.js
      MovieFactory.js
      /viewModels
        addMovie.js
        editMovie.js
        movies.js
        viewMovie.js
      /views
        header.html
        addMovie.html
        editMovie.html
        movies.html
        viewMovie.html

Now, let’s walk through the main files in the app.

Create the Single Page App

The index.html page is the entry point to our single page app:

<!DOCTYPE html>
<html>
  <head>
    <meta http-equiv="content-type" content="UTF8"/>
    <title>The JET Movie App</title>
    <link rel="stylesheet" href="css/libs/oj/v1.1.2/alta/oj-alta-min.css" type="text/css"/>
    <script data-main="js/main" src="js/libs/require/require.js"></script>
  </head>
  <body>
    <div id="globalBody" style="display: none">
      <header id="headerWrapper" role="banner" data-bind="ojModule: {viewName: 'header'}"></header>
      <div class="oj-row oj-md-center">
        <div class="oj-md-6 oj-col">
          <!-- This is where the views will be loaded -->
          <div id="mainContainer" role="main" data-bind="ojModule: router.moduleConfig"></div>
        </div>
      </div>
    </div>
  </body>
</html>

As you can see with the oj- CSS classes, Oracle JET is using a grid system similar to Bootstrap for responsive web design.

The views (i.e. pages) of the movie app will be shown inside the mainContainer HTML element, and a router (i.e. router.moduleConfig) controls which one should be displayed.

The router is configured in main.js and it’s here we configure all the views in the application that the user can navigate to.

// Initialize the router
var router = oj.Router.rootInstance;
router.configure({"movies":    {label: "Movies",     value: "movies", isDefault: true},
                  "viewMovie": {label: "View Movie", value: "viewMovie"},
                  "editMovie": {label: "Edit Movie", value: "editMovie"},
                  "addMovie":  {label: "Add Movie",  value: "addMovie"}});

The value field is important, because it is used to figure out what view and view model should be used for a specific page. For example, for editMovie the router will use views/editMovie.html for the view, and viewModels/editMovie.js for the view model.

The only thing I miss compared to Angular UI Router is that you cannot specify parameters to a page. For example, editMovie/{id} to say that the editMovie page needs an ID of the movie that it should edit. Oracle JET has the concept of a child router that you could probably use, but the Angular way seems more neat.

Calling the REST Service

Once we have setup the basic structure of the movie app, we need to code the functionality.

We know that all the pages in this app will need to interact with a REST Service, so let’s put that functionality in a shared module to avoid redundant code.

The module is called MovieFactory.js:

define(['ojs/ojcore'], function(oj) {

  var MovieFactory = {
    resourceUrl: "http://movieapp-sitepointdemos.rhcloud.com/api/movies",

    // Create a single movie instance.
    createMovieModel: function() {
      var Movie = oj.Model.extend({urlRoot:     this.resourceUrl,
                                   idAttribute: "_id"});
      return new Movie();
    },

    // Create a movie collection.
    createMovieCollection: function() {
      var Movies = oj.Collection.extend({url:   this.resourceUrl,
                                         model: this.createMovieModel()});
      return new Movies();
    }
  }

  return MovieFactory;
});

If you are not familiar with RequireJS modules, then the define line is where we tell which modules that this module depends on, and the parameters (i.e. oj) are instances of these modules that can be used in the code.

We then create a JavaScript object (MovieFactory) with two factory methods for creating a Model and a Collection. A model in Oracle JET is based on Martin Fowler’s Active Record pattern, and the collection is basically a glorified array of models (this model/collection approach is pretty similar to Backbone.js).

In the Movie App, a model represents a single movie, and a collection represents a group of movies. You use the collection when you work with more than one movie (for example, if you have a query to find all adventure movies) and you use a model when you need to manipulate a single movie.

ViewModel

The ViewModel holds all the data and functions that are used by the view. For example, movies.js:

define(['ojs/ojcore', 'knockout', 'MovieFactory', 'ojs/ojmodel', 'ojs/ojtable', 'ojs/ojbutton'],
function(oj, ko, MovieFactory) {

  var viewModel = {
    movieCollection: MovieFactory.createMovieCollection(),
    dataSource: ko.observable(),

    // Called each time the view is shown to the user.
    initialize: function() {
      this.dataSource(new oj.CollectionTableDataSource(this.movieCollection));
      this.movieCollection.fetch();
    },

    viewMovie: function(movieId) {
      oj.Router.rootInstance.store(movieId);
      oj.Router.rootInstance.go("viewMovie");
    },

    deleteMovie: function(parent, movieId) {
      parent.movieCollection.get(movieId).destroy();
    },

    addMovie: function() {
      oj.Router.rootInstance.go("addMovie");
    }
  };

  return viewModel;
});

In the top you can see that the view model depends on MovieFactory.

The initialize function is call each time the view model is called (i.e. when a user navigates to that page), so here we fetch all the movies, and put them in a data source that can be used by a table UI widget.

The viewMovie function is called when a user clicks on a movie. It first saves the movieId in the router, and then goes to the viewMovie page.

View

The view is the page that the user interacts with. For example, movies.html:

<div>
  <h1>All Movies</h1>

  <table id="table"
        summary="Demo Table"
        data-bind="ojComponent: {component:'ojTable',
                                 data: dataSource,
                                 columns: [{headerText: 'Movie Title',  field: 'title'},
                                           {headerText: 'Director',     field: 'director'},
                                           {headerText: 'Release Year', field: 'releaseYear'},
                                           {headerText: 'Movie Genre',  field: 'genre'},
                                           {headerText: '',             field: 'operations'}],
                                 rowTemplate: 'row_template'}">
  </table>
  <div>
    <br>
    <button data-bind="click: addMovie, ojComponent: {component: 'ojButton', label: 'Add New Movie'}"></button>
  </div>
</div>

<script type="text/html" id="row_template">
  <tr>
    <!-- The $data check is for when the field doesn't exists. If no value has been specified for a field then the web service might exclude that field from the reponse. -->
    <td data-bind="text: $data.title       ? title       : '"></td>
    <td data-bind="text: $data.director    ? director    : '"></td>
    <td data-bind="text: $data.releaseYear ? releaseYear : '"></td>
    <td data-bind="text: $data.genre       ? genre       : '"></td>
    <td>
      <button data-bind="click: function() {$parent.viewMovie(_id)}, ojComponent: {component: 'ojButton', label: 'View'}"></button>
      <button data-bind="click: function() {$parent.deleteMovie($parent, _id)}, ojComponent: {component: 'ojButton', label: 'Delete'}"></button>
    </td>
  </tr>
</script>

The view is using Knockout to bind the UI elements to the view model. If you are unfamiliar with Knockout then you should try their interactive tutorial. It’s the one of the best ones that I have ever seen!

We bind the table to the datasource from the view model. Due to the special requirement about having an extra column in the table (for the Delete and Edit buttons), I created a row template that is used when rendering the table (see the script element).

The Add New Movie button is binded to addMovie function in the view model.

The other views and view models are somewhat similar to these ones, so in the name of brevity they are left out of this post, but you can see all of them in the ZIP file.

Conclusion

Some argue that Oracle JET has a steep learning curve, but after my limited hands-on experience with it, I don’t really think that this is the case…

I mean this is the first public release and the framework will need a few iterations to mature. And, given its newness you cannot just google your way to most answers (like older JS frameworks), but with that being said, I think once you get a hang of it, it’s pretty easy (and even fun!) to use.

If you plan to play around with Oracle JET, I can recommend Developing Applications with Oracle JET for general information about the framework, the cookbook for examples of how to use individual components, and the JSDoc for technical details about the APIs.

Share this post:


Subscribe to this blog:

Just enter your email below and press the button:


Related Posts: