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
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):
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?
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="<script>" title="<script>" />
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.
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:
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
ojet serve --server-port=8147 --livereload-port=8148
How to data bind the src attribute of an IFRAME
<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)
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/.
Hello, i have try to dowload you´project on my pc.But i dont have acess to see the changes like sign in
okey sir
Hi Sir,
I have downloaded login code from git url but it is not working. It is showing Failed to load resource: net::ERR_CONNECTION_REFUSED.
My requirement is to login my app with a login API (parameter: username and password). Please help me.
Dear amiteng,
I am afraid I have not recently worked on these resources and I do not have the opportunity to walk you through it in detail. I am afraid that you will have to debug the code = and perhaps fix any inconsistencies with the version of JET you are working with.
good luck,
Lucas
Hey, where did you find the Code. I have downloaded the project from git, but still don’t know how it works. As for me i am trying to put the login page at the center so that i dont have on this login page any header and footer. Do you have any idea?