Some of my Solutions for challenges with Oracle JET image 49

Some of my Solutions for challenges with Oracle JET

This article is not some sophisticated treatise on Oracle JET fundamentals.It is merely a collection of challenges I had to deal with and found solutions for – that work, even if they are perhaps not the best approach around. This article is first of all a personal notebook. If you can get anything useful from it, then by all means take it and enjoy it.

The code for the application referenced in this article can be found on GitHub: https://github.com/lucasjellema/webshop-portal-soaring-through-the-cloud-native-sequel.

How to define a global context that is accessible from all modules?

The challenge is a simple one: I want to be able to set a value in one module and have access to that value in other modules. For example: when I enter my username in one module

image

 

I want to make that value available in the very root of the application (index.html and ViewModel appController.js) as well in a second module, called dashboard (accessible through the Home tab):

image

Here I was helped by Geertjan’s article: https://blogs.oracle.com/geertjan/intermodular-communication-in-oracle-jet-part-1

The username field in the customers module is bound to the observable self.username. When the login button is clicked, a function loginButtonClick is invoked on the ViewModel. This function reads the observable’s value, retrieves the root ViewModel (using the Knock Out feature dataFor) and sets the username on the global variable – an observable userLogin defined on the root ViewModel. It also sets the observable userLoggedIn on the Root ViewModel – a flag that for example controls tabs in the navigation list.

    function CustomersViewModel() {
      var self = this;
      self.username = ko.observable("You");
      self.password = ko.observable();

      self.loginButtonClick = function (event) {       
        var rootViewModel = ko.dataFor(document.getElementById('globalBody'));
        rootViewModel.userLogin(self.username());
        rootViewModel.userLoggedIn("Y");
        return true;
      }

The definitions in the Root ViewModel (in appController.js):

 function ControllerViewModel() {
      var self = this;
      // Header
      // Application Name used in Branding Area
      self.appName = ko.observable("Soaring through the Clouds Webshop Portal");
      // User Info used in Global Navigation area
      self.userLogin = ko.observable("Not yet logged in");
      self.userLoggedIn = ko.observable("N");

 

 

How to make Tabs (Navigation List Items) conditionally displayed?

In this application, the tabs to be displayed are dependent on the question whether or not the user has logged in. My challenge in this case: how to display the tabs (items in an oj-navigation-list component) based on a condition?

image

The items are rendered by a template specified on the item.renderer attribute of the oj-navigation-list.

It turns out that by data-binding the visible attribute on the outermost HTML element in the template, I can have the tabs rendered based on a logical expression referencing the global (root) variable that indicates whether or not the user is logged in:

 <!-- Template for rendering navigation items -->
    <img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-wp-preserve="%3Cscript%20type%3D%22text%2Fhtml%22%20id%3D%22navTemplate%22%3E%20%20%20%20%20%20%0A%20%20%20%20%20%20%3C%2Fp%3E%0A%3Cli%20data-bind%3D%22visible%3A%20(!%24data%5B'loggedInOnly'%5D%7C%7C%20%24root.userLoggedIn()%20%3D%3D'Y')%22%3E%3Ca%20href%3D%22%23%22%3E%0A%20%20%20%20%20%20%20%20%20%20%3Cspan%20data-bind%3D%22css%3A%20%24data%5B'iconClass'%5D%22%3E%3C%2Fspan%3E%0A%20%20%20%20%20%20%20%20%20%20%3C!--%20ko%20text%3A%20%24data%5B'name'%5D%20--%3E%20%3C!--%2Fko--%3E%0A%20%20%20%20%20%20%20%20%3C%2Fa%3E%3C%2Fli%3E%0A%3Cp%3E%20%0A%20%20%20%20%3C%2Fscript%3E" data-mce-resize="false" data-mce-placeholder="1" class="mce-object" width="20" height="20" alt="&lt;script&gt;" title="&lt;script&gt;" />

The definition of the navigation list items is in appController.js; the array navData contains the items that can be turned into tabs. Each item defined the name (the label displayed to the user) as well as the associated module and the iconClass. I have added an optional property  loggedInOnly – that indicates whether or not a tab should be displayed only when the user is logged in; this property is used in the expression in the template shown overhead.

      // Navigation setup
      var navData = [
        {
          name: 'Home', id: 'dashboard', loggedInOnly: false,
          iconClass: 'oj-navigationlist-item-icon demo-icon-font-24 demo-chart-icon-24'
        },
        {
          name: 'Browse Catalog', id: 'products',
          iconClass: 'oj-navigationlist-item-icon demo-icon-font-24 demo-fire-icon-24'
        },
        {
          name: 'Browse Orders', id: 'orders', loggedInOnly: true,
          iconClass: 'oj-navigationlist-item-icon demo-icon-font-24 demo-people-icon-24'
        },
        {
          name: 'Your Profile', id: 'customers', loggedInOnly: true,
          iconClass: 'oj-navigationlist-item-icon demo-icon-font-24 demo-info-icon-24'
        }
      ];
      self.navDataSource = new oj.ArrayTableDataSource(navData, { idAttribute: 'id' });

 

How to dynamically set the label of oj-option elements

The drop down menu contains an item that changes its label, depending on whether the user is logged in.

image

I wanted to have two items and control their visibility – for some reason I got sidetracked and solved it a little differently.

I have defined a <span> element inside the oj-option and defined the text attribute through a data binding. This binding subsequently uses a ternary expression to determine which label to display:

              <oj-menu id="menu1" slot="menu" style="display:none" on-oj-action="[[menuItemAction]]">
                <oj-option id="help" value="help">Help</oj-option>
                <oj-option id="about" value="about">About</oj-option>
                <oj-option id="sign" value="sign">
                  <span data-bind="text: (userLoggedIn() =='Y'?'Sign Out':'Sign In/Sign Up')"></span>
                </oj-option>
              </oj-menu>

 

How to react to the User Clicking on a Menu Option in a oj-menu component

Not surprisingly, when the user clicks on an menu item in the drop down menu shown overhead, the application should respond somehow. I was wondering how to trigger my code for the click-a-menu-item event. Then I found out about the on-oj-action attribute on oj-menu.

<oj-menu id="menu1" slot="menu" style="display:none" on-oj-action="[[menuItemAction]]">
   <oj-option id="help" value="help">Help</oj-option>
   ...

It refers to a function in the viewModel – that can take an event and from that event’s path[0] element get the id of the selected oj-option item. It can then do whatevwer needs to be done.

 self.menuItemAction = function (event) {
        var selectedMenuOption = event.path[0].id
        console.log(selectedMenuOption);
        if (selectedMenuOption == "sign") {
           ....

 

How to programmatically navigate to a module – by activating the Router

When the Sign In/Up option is selected in the menu above, I want the application to navigate to the customers module, where the user can login:

image

This navigation is to be done programmatically – in the function handling the click on menu item event. The programmatic manipulation of the router turns out to be extremely simple in the function:

 self.menuItemAction = function (event) {
        var selectedMenuOption = event.path[0].id
        if (selectedMenuOption == "sign") {
          if (self.userLoggedIn() == "N") {
            // navigate to the module that allows us to sign in
            oj.Router.rootInstance.go('customers');
          } else {
            // sign off
            self.userLogin("Not yet logged in");
            self.userLoggedIn("N");
            oj.Router.rootInstance.go('dashboard');
          }
        }

This is all it takes to present the customers.html center page. This of course depends on the module binding in index.html:


<div role="main" class="oj-web-applayout-max-width oj-web-applayout-content" data-bind="ojModule: router.moduleConfig">
</div>

and the module definitions in the ViewModel appController.js

     // Router setup
      self.router = oj.Router.rootInstance;
      self.router.configure({
        'dashboard': { label: 'Dashboard', isDefault: true },
        'products': { label: 'Products' },
        'orders': { label: 'Orders' },
        'customers': { label: 'Customers' }
      });
      oj.Router.defaults['urlAdapter'] = new oj.Router.urlParamAdapter();

Dynamically selecting the module to display

  <div data-bind="ojModule:$root.userLoggedIn() =='N'?'sign':'profile'"/>           

How to run Oracle JET on a different Port with OJET Serve

With ojet serve we can very conveniently run the Oracle JET application locally, with the reload changes live in browser feature. However, if you want to run more than one Oracle JET application at the same time, you have to take a little extra step to prevent port conflicts. The command ojet serve can take additional command line parameters: server-port and livereload-port through which you can specify different port numbers. A start up command could now look like this:

ojet serve --server-port=8147 --livereload-port=8148

How to data bind the src attribute of an IFRAME

I want to load an IFRAME in my JET application, and the URL used as the value for the src attribute of the iframe should be determined dynamically (from runtime settings). Data binding the src attribute is done using data-bind using the attr object, like this:

  <iframe id="customersIframe" data-bind="attr: {src:  $root.CUSTOMER_PORTAL_URL}" style="overflow-y : hidden ; height : 400px ; width :  100% ;border-width:0;" scrolling="no" /></div>

The reference $root refers to the root viewmodel in the JET Application – typically in the appController.js file.

How to retrieve environment values in the Oracle JET application (from the Node backend)

Some aspects of a JET application could be environment dependent or otherwise be determined at runtime. The client side JET application has no ready access to such environment settings. A common approach is to have the JET application make an AJAX HTTP call to a backend API (typically on the same server that serves the JET application) to retrieve these environment values. Subsequently, the values that are retrieved are made available throughout the JET application, for example by retaining them in the root ViewModule.

Code I have used in the root ViewModel for this:

      self.loadEnvironmentSettings = function () {
        var environmentSettingsURL = '/environmentSettings'
        if (location.hostname == 'localhost') {
          environmentSettingsURL = location.protocol + "//" + location.hostname + ":" + "3000" + "/environmentSettings"
        }
        console.log("environmentSettingsURL:" + environmentSettingsURL)
        $.get(environmentSettingsURL, function (data) {
          console.log("Load was performed." + JSON.stringify(data));
          self.CUSTOMER_PORTAL_URL = data.CUSTOMER_PORTAL_URL || 'http://localhost:8147/'
          self.PRODUCT_PORTAL_URL = data.PRODUCT_PORTAL_URL || 'http://localhost:8145/'
        });
      }
      self.init = function () { self.loadEnvironmentSettings() }
      $(document).ready(function () { self.init(); })

The backend in this case is a Node with Express application that is configured for serving the JET application’s static resources as well as the environment setings:

app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));

app.use(function(req, res, next) {
  res.header("Access-Control-Allow-Origin", "*");
  res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
  next();
});

app.get('/environmentSettings', function (req, res) {
  var settings ={
    "PRODUCT_PORTAL_URL" : process.env.PRODUCT_PORTAL_URL
    ,"CUSTOMER_PORTAL_URL" : process.env.CUSTOMERS_PORTAL_URL
    ,"APP_VERSION ": APP_VERSION 
  }
  res.json( settings);
})

Utility Library for accessing Model Context

Edit (2nd March 2018): I just learned about a convenient utility library for Oracle JET that provides utility functions for accessing a model’s knockout context

See: https://medium.com/enpit-developer-blog/power-up-your-oracle-jet-introducing-our-knockout-helper-library-531e89fbdb4c and directly in GitHub: https://github.com/enpit/knockout-context-util/.

5 Comments

  1. Paul April 9, 2020
  2. amiteng August 21, 2019
  3. amiteng August 21, 2019
    • Lucas Jellema August 21, 2019
    • Paul April 9, 2020