Monday, February 19, 2018

Oracle JET: Bookmark a secured pages

Problem Description: Oracle JET allows us to create SinglePage application (SPA). It changes url when we navigate between pages. What if application is secured application and pages require login to visit. In such case if we book mark a page and later try to launch it, application should verify if user is already logged in, If not redirect user to login page and once logged in user should be shown same page, which he has requested. In this blog we are trying to achieve it.

Also if user has directly started from login page then if login is successful we need to take him to default page mentioned in router configuration.

Solution:
 To implement this we need to achieve following flow

Here are the steps to achieve above flow

1. User can either launch login page or a secured page (say Page-X). If user launches login page, we can directly show him login page, but if he launches Page-X, we need to verify if user is already logged in.To verify if user has already logged in depends on server side logic.
   Here in this blog I assume there is a REST service available, which returns profile of logged in user (myprofile service). we will call this service, without passing any Authorization header. In such case browser will pass SESSIONID from cookie if user is already logged in. If not logged in then we will get 401 from server. If we get error from server, we can redirect user to login page, but we will add current url in login page url additionally, so that we can use it later to redirect user back to Page-X. Here is the common code checkIfLoginDone function written in appcontroller.js

     function ControllerViewModel() {
        $.ajaxSetup({
          xhrFields: {
            withCredentials: true
          }
        }); 

        var self = this;
        self.baseURL='http://localhost:7101';
        self.serviceContextRoot = '/myapp';
        self.serviceInitialURL = self.baseURL + self.serviceContextRoot + '/resources';

        self.router = oj.Router.rootInstance;
        self.checkIfLoginDone = function checkIfLoginDone(){
          console.log('starting login check');
          
          var promise = $.ajax({
             type: "GET",
             url: self.serviceInitialURL+ "/v1/myprofile",
             contentType: "application/json; charset=utf-8",
             crossDomain: true,
             dataType: "json"});
           
            promise.then(function(response) {
                  
                 

              }, function(error) {
                 // error handler
                 var currenturl = window.location.pathname + window.location.search ;
                window.location.href = '/?root=login&origurl='+currenturl;
              });

            return promise;
         
          
        }

In above code if we get error from server, we will be redirecting user to login page and add url of secured page as a parameter origurl. Login page url will appear like
http://<host>:<port>/?root=login&origurl=<url-path-of-secured-page>

[Assuming that login will be router state for login page]

2. To perform login check we can it checkIfLoginDone from all secured pages's ViewModel as
define(['ojs/ojcore', 'knockout', 'jquery', 'appController'],
 function(oj, ko, $, app) {
      self.handleActivated = function(info) {
        // Implement if needed
        return app.checkIfLoginDone();
      };

3. Create a login page: For this you can follow below steps
      a. Create login.html in js/views directory. Content could be
         

      <div class="oj-hybrid-padding">
        <h1>Login Content Area</h1>
          <div id="sampleDemo" style="" class="demo-container">
            <div id="componentDemoContent" style="width: 1px; min-width: 100%;">
              <div id="form-container" class="oj-form-layout">
                <div class="oj-form">
                  <div class="oj-flex">  
                    <div class="oj-flex-item">
                      <oj-label show-required for="username">User Name</oj-label>
                      <oj-input-text id="username" required value="{{username}}"></oj-input-text>
                    </div>
                  </div>
                  <div class="oj-flex">  
                    <div class="oj-flex-item">
                      <oj-label for="password" show-required>Passward</oj-label>
                      <oj-input-password id="password" required value="{{password}}"></oj-input-password>
                    </div>
                  </div>
                </div>
                <oj-button id='submit' on-click='[[doLogin]]'>Login</oj-button>
              </div>              
            </div>
    </div>
  </div>
   
In above page, we have created two fields username/password and a button Login.
Username and password are bound to ViewModel and Login button click calls doLogin method of ViewModel

    b. Create login.js as a ViewModel in js/viewModels directory. Its code would be

define(['ojs/ojcore', 'knockout', 'jquery', 'appController','ojs/ojknockout','ojs/ojlabel','ojs/ojinputtext', 'ojs/ojcheckboxset'],
 function(oj, ko, $, app) {
  
    function LoginViewModel() {
      var self = this;
      function param(name) {
            return (location.search.split(name + '=')[1] || '').split('&')[0];
        }
      self.username = ko.observable("");
      self.password = ko.observable("");
      self.doLogin = function doLogin(event){
        $("body").css("cursor", "wait");
       //do server login here
       var string = self.username() + ':' + self.password();
      var encodedString = 'Basic ' + btoa(string);
             
      var promise = $.ajax({
              type: "POST",
              url: app.serviceInitialURL + '/v1/auth/login',
              contentType: "application/json; charset=utf-8",
              headers: {
                   "Content-Type": "text/plain",
                    "Authorization": encodedString
              },
              crossDomain: true,
              dataType: "json"});
         
      promise.then(function(response){
              var origurl = param('origurl');
              if(origurl){
                window.location.href = origurl;
              }
              else{
                oj.Router.rootInstance.go('dashboard');
              }

              $("body").css("cursor", "default");
         }, function(response){
             //write logic here to show error message to end user.
         }) ;   

      }
      // Header Config
      self.headerConfig = {'viewName': 'header', 'viewModelFactory': app.getHeaderModel()};

      
     

    return new LoginViewModel();
  }
);

Important piece of above code is 
    i. username/password created as ko observable.
   ii. username password is used to create base 64 encoded authorization string
  iii. server side login using $.ajax POST request
   iv. If login is success then verify if there is any url parameter as origurl present. Navigate to the location whereever origurl points to. If not specified then get default page from router and navigate there. 

c. register login page in router configuration

self.router.configure({
          'login': {label: 'Login'},
         'dashboard': {label: 'Dashboard', isDefault: true},
         'incidents': {label: 'Incidents'},
         'customers': {label: 'Customers'},
         'profile': {label: 'Profile'},
         'about': {label: 'About'}
        });

4. Finally we need a logout button. We can keep it in header.html
<div class="oj-flex-bar-end">
    <oj-button id='button1' on-click="[[logout]]">Logout</oj-button>
</div>

logout implementation is written in appcontroller.js by changing getHeaderModel method.
 self.logout = function(event){
           $.ajax({
                 type: "GET",
                 url: self.serviceInitialURL+ "/v1/auth/logout",
                 contentType: "application/json; charset=utf-8",
                 crossDomain: true,
                 dataType: "json",
                 success: function (data, status, jqXHR) {
                        oj.Router.rootInstance.go('login');
                 },

                 error: function (jqXHR, status) {
                     // error handler
                     
                 }
              });

      }


 self.getHeaderModel = function() {
          var headerFactory = {
            createViewModel: function(params, valueAccessor) {
              var model =  {
                pageTitle: self.router.currentState().label,
                handleBindingsApplied: function(info) {
                  // Adjust content padding after header bindings have been applied
                  self.adjustContentPadding();
                },
                toggleDrawer: self.toggleDrawer,
                logout: self.logout
              };
              return Promise.resolve(model);
            }
          }
          return headerFactory;
        }

Above logout method calls server side logout and then redirect user to login page.



Saturday, February 10, 2018

ADF: Attribute Defaulting

Problem Description: Recently I came across a forum question where requirement was to make an EO attribute to be fetched value from database but should be ignored completely while saving data. User should definitely be able to update attribute from UI but it should not be posted to database.
https://community.oracle.com/message/14677169#14677169

Solution: Let us see what are our options

In ADF either we can make attribute persistent or transient. If we make persistent, it will be mapped to a database column and its value will be fetched from database and also it will get posted back to database. There is no option to say only fetch from database but ignore while posting to database.


As a solution we can mark our attribute transient so that its value will be ignored while posting to database. Now to default its value from database field we can specify default value. Value can be picked from SQL and good part is we can directly specify column name from which we want to default its value. Of course column must be present in same table. 

What if we want to fetch default value from some other table. 
You have two option
1. Write a complete SELECT statement on default value field. You can pass values from current row of EO. For example let say you are querying employee table (which has manager id) and now you want to fetch his manager name. You can simply write SQL query as [SELECT MGR.FIRST_NAME FROM EMPLOYEES MGR WHERE MGR.EMPLOYEE_ID = EmployeeEO.MANAGER_ID]

2. You can also use a ViewAccessor with expression to default value. In this case you will use VO and add it as view accessor in EO. Now in default value, you can write groovy expression to get value from view accessor. 

Same thing is applied for VO transient attributes as well. 

Out of option 1 and 2, I would chose option 1 as preference. Reason for that is if you write SQL based default expression, a single query is fired against database and result is used to set default value of all rows. Query if formed something like
SELECT EmployeeEO.EMPLOYEE_ID,         EmployeeEO.FIRST_NAME,         EmployeeEO.LAST_NAME,         EmployeeEO.EMAIL,   
EmployeeEO.PHONE_NUMBER,         EmployeeEO.HIRE_DATE,         EmployeeEO.JOB_ID,         EmployeeEO.SALARY,       
EmployeeEO.COMMISSION_PCT,         EmployeeEO.MANAGER_ID,         EmployeeEO.DEPARTMENT_ID,  
(SELECT MGR.FIRST_NAME FROM EMPLOYEES MGR WHERE MGR.EMPLOYEE_ID = EmployeeEO.MANAGER_ID) AS MGR_NAME
FROM EMPLOYEES EmployeeEO

In option 2, View accessor will be fired once for every row fetched. For example if we fetch 100 employee records and for each record a manager-vo query will fire to get manager name. You will end up with 101 queries. To improve View accessor queries, you can set RowLevelBinds = false if same view-accessor query result is ok for all records. In above case it will not fit. You need to fire separate view-accessor query with bind parameter as employee-id from source row to get correct manager name. 

Thats all