Category: SAP

  • DocuSign Integration in SAP UI5, Node.js and SAP ABAP

    Preface – This post is part of the UI5 Integration Programs series.

    Introduction

    DocuSign is a cloud based software (SaaS) to electronically sign (eSign) your agreements and documents. It is accessed via browser based access or via its Android and iOS apps. It provides APIs and SDKs to integrate and embed its services to your Apps and websites. In this article, we will

    Prerequisite

    Before we start with integration, it is mandatory to have a DocuSign account, SAP Cloud Platform (for Open Connector and Cloud based Integration only), SAP Open Connector Access (for open connector Integration only).

    Once you have created a DocuSign account successfully, get the following details from the developer account (steps discussed later – DocuSign API Provider Setup):

    • iss: This is integration key or Client ID
    • sub: This is User ID
    • aud: account-d.docusign.com (for demo environment) and account.docusign.com (for production environment)
    • scope: This will be signature (for Authorization Code Grant and Implicit Grant) or signature impersonation (for JWT)
    • iat: This is the unix time from when your oAuth will be valid
    • exp: This is the unix time until when your oAuth will be valid
    • Public Key, Private Key: These are the keys generated on the platform
    • Response Type: This can be response_type=code (for Authorization Code Grant and JWT Grant) or response_type=token (for Implicit Grant)
    • Grant Type: This can be grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer (for JWT Grant) or grant_type=authorization_code (for Authorization Code Grant). It is not required for Implicit Grant
    • API_URI: This can be https://demo.docusign.net (for Demo Developer Account), https://account-d.docusign.com (for Developer Account) or https://account.docusign.com (for Production Account)
    • Secret: This is the secret key
    • Redirect URI: This is the URI, where your user will be sent once they complete the eSigning. This has to be maintained in your DocuSign Account
    • Link for getting consent for the very first time:
      https://account-d.docusign.com/oauth/auth?
      response_type=code
      &scope=YOUR_REQUESTED_SCOPES
      &client_id=YOUR_INTEGRATION_KEY
      &redirect_uri=YOUR_REDIRECT_URI
    • Account_ID: This is API Account ID

    We will save all the above details in a local file so that it will be easy for us to provide whenever and wherever required in later steps.

    Types of DocuSign Integrations

    There are thousands of scenarios where you can plan to have your document/agreements signed by a user. To get it signed, you need to either send the document to that user via an email or get that user at your platform to get the document signed there itself. Thus we will discuss these two scenarios in brief:

    ·         To Send a mail from UI for document signing

    In this scenario, you can either

    1. Visit DocuSign platform, upload the document and send it to a particular email ID
    2. Integrate the DocuSign to your UI in such a way that you pass document, subject of mail and mail content with required user’s email IDs via API. This API will therefore trigger a mail. Then you can retrieve the status of the document signature and update the same in your UI.

    ·         To get the document signed from UI

    In this scenario, you can either

    1. Visit DocuSign platform, upload the document and get a link for integration. You can use this link directly in the UI (via iframe) and then user can click and sign using the link. This scenario is valid only in a use case where same document is signed by every user i.e. the document remains same for all the user. e.g. A document related to the security of the office that can be signed by every visitor.
    2. Integrate the DocuSign to your UI in such a way that you pass document, subject of mail and mail content with required user’s email IDs via API. This API will therefore trigger a mail as well as return a signing URL. Then you can integrate this URL and the user can sign the document via UI. Thereafter retrieve the status of the document signature and update the same in your UI.

    DocuSign API Provider Setup

    Step 01: Sign into DocuSign Developer Account: Visit here and login with your credentials.

    Step 02: Go to Settings and click “Apps and Keys”

    DocuSign Settings

    DocuSign Apps and Keys

    Step 03: Save the following

    DocuSign Credentials
    Now we have aud, Account_ID and API_URI.

    Step 04: Add App and Integration Key

    DocuSign Add Integration Key and App
    This will ask for your App name, here we will give it “Test”. It will navigate to next page.

    DocuSign get Integration Key

    And you can get your Integration Key (or iss).

    Step 05: Generate Secret Keys
    Now generate and save your key:

    DocuSign get Secret Key

    Here we have generated Secret.

    Step 06: Generate RSA
    Now generate RSA:

    DocuSign generate RSA

    This will generate Public and Private Key:

    DocuSign get Public and Private Key

    Save these keys. Remember the starting (—–BEGIN PUBLIC KEY—–) and ending part (—–END PUBLIC KEY—–) of both the keys are important and should be always copied and used with the keys. Without these parts, the keys are invalid.

    Step 07: Add a Redirect URI
    Now add a redirect URI:

    DocuSign Redirect URI

    This can be any link that you want your users to see once they are done with signing.

    Step 08: Save the changes
    Now we have gathered all the information required for integration, you need to save them all something like this:

    {
    iss: 9a5a2a7a-5f25-4526-bc62-82dc62fae2b1
    sub: 0211f444-eab3-4b75-ce7c-8b95adf890
    aud: account-d.docusign.com
    Public Key, Private Key: These are the keys generated on the platform
    API_URI: https://demo.docusign.net
    Secret: f2e1c847-bdc3-48da-a444-603f228988de
    Redirect URI: https://www.docusign.com/
    Account_ID: 92222dc4-6430-44c6-81c9-0742c14f82f5
    }

    Different Types of DocuSign Integration

    1.      Direct Integration using iframe: PowerForms

    This is the simplest integration option. This is only valid for the use case where document remains same for all the users.
    1. Go to your account
    2. Create a Template: Upload your document and add the place where you want signature.

    DocuSign Create Template

    3. Enable PowerForm option on that template:

    DocuSign Enable Powerforms

    As you can see above, a URL is generated. This URL can be integrated in your website using the concept of iframe or sent directly to the users via email and sms. This feature is not available for Demo accounts.
    The status of the signatures will be available on DocuSign platform only.

    2.      Postman Testing of DocuSign

    Many developers want to try the DocuSign via Postman before they jump into actual development and integration. You can find the setup of the postman for DocuSign here and also download the postman collection. Apart from this, you can directly access the cloud version of postman with preloaded collection here.
    In both scenario, setup the environment:
    DocuSign Postman open Environment

    And add all the details that we have created above [We have not added, as we directly change values in the URL, you can too ignore this]

    DocuSign Postman set Environment

    Now the very next step for us will be to generate an Authorization key so that it can be used to perform all other operations. Here we will be generating a token key based upon JWT Authorization method because same method we will be using for Integration using Rest APIs in UI5, Node.js as well as ABAP.
    For JWT token, postman call looks like this:

    DocuSign Postman set JWT

    As you can see, we need to pass assertion value here.

    To generate an assertion, we need to create a JWT token. It can be created here. Change the algorithm type as RS256, in verify signature enter the Public and Private keys that we have generated earlier, and in Payload enter an object like this:

    {
      "iss": "<we have generated above> ",
      "sub": ""<we have generated above> ",
      "aud": "account-d.docusign.com",
      "iat": <Get current unix time e.g. 1623513795>,
      "exp": <Get 1 hour delayed unix time>,
      "scope": "signature impersonation"
    }
    

     

    To get the unix time, visit here.

    Get Unix timestamp

    Using above details as well as change Public & Private key below, and then it will generated the token:

    Generate JWT

    Now copy and paste it in assertion in postman.
    And then, under Headers tab we need to add a bearer token:

    DocuSign Postman set Authorization Bearer

    You can simply convert your integratiokey:secret as base 64 or generate here

    Generate Encoded Bearer

    After this one more important step is left, else you will “consent_required” error. We need to take consent using the below link:
    https://account-d.docusign.com/oauth/auth?
    response_type=code
    &scope=YOUR_REQUESTED_SCOPES
    &client_id=YOUR_INTEGRATION_KEY
    &redirect_uri=YOUR_REDIRECT_URI

    For our use case, it will be: https://account-d.docusign.com/oauth/auth?response_type=code&scope=signature impersonation&client_id=9a5a2a7a-5f25-4526-bc62-82dc62fae2b1&redirect_uri=https://www.docusign.com/

    It will ask you to provide consent (after logging in):

    DocuSign get Consent

    Note: For Prod version, do the below [Grant change to Implicit] and again grant access via the link that we created earlier, in our case: https://account-d.docusign.com/oauth/auth?response_type=code&scope=signature impersonation&client_id=9a5a2a7a-5f25-4526-bc62-82dc62fae2b1&redirect_uri=https://www.docusign.com/

    Go to your DocuSign account and convert your grant to implicit [Do not change for Postman testing]:

    DocuSign change Grant

    And now we are all set to receive our Authentication token from DocuSign.

    DocuSign Postman generate token

    Once you get the Authorization, you can use the same to perform all other operations. Now we will be performing the following operations in postman:

    1. Create an Envelope with a pdf document and send it to a user

    To get an Envelope with a pdf document, we will need to upload that pdf and provide user details (email ID and name). This will create an envelope in DocuSign and trigger a mail to the user. In case, you just want to send a mail to the user then you can skip the next step.
    Before you try in postman, try in DocuSign Swagger API as shown below:
    POST /v2.1/accounts/{accountId}/envelopes

    Create an Envelope with a pdf document and send it to a user

    Here we have provided following test data under parameters:

    {
      "documents": [
        {
          "documentBase64": "<Base64BytesHere>",
          "documentId": "1",
          "name": "test"
        }
      ],
      "emailSubject": "test",
      "recipients": {
        "signers": [
          {
            "clientUserId": "test",
            "email": "test@test.com",
            "name": "test",
            "recipientId": "1"
          }
        ]
      },
      "status": "sent"
    }
    

     

    The above data created an envelope as shown below:

    Create an Envelope expected output

    Now in postman, first get the access token as we did earlier and then open “Create an Envelope” API within collection as shown below at given URI and above payload: POST https://demo.docusign.net/restapi/v2.1/accounts/<Your_Account_ID>/envelopes

    Postman to create an Envelope with a pdf document and send it to a user

    Now, it will too generate an envelope as the swagger API did.

    1. Get a URL based upon the above Envelope

    In case, you want the user to sign the document via browser, then you will have to generate a signing url. This url can be then integrate in an iFrame. For that purpose, you need to retain the envelopeId generated above i.e. “4098ec03-461f-4081-b0d8-ac59618bfc1a”.

    Now, we will try generating first via DocuSign Swagger API:

    POST /v2.1/accounts/{accountId}/envelopes/{envelopeId}/views/recipient

    Create Sender

    Get a URL based upon the above Envelope

    The JSON for above:

    {
      “clientUserId”: “test”,
      “email”: “gocodingorg@gmail.com”,
      “userName”: “test”,
      “returnUrl”: “https://www.docusign.com/”,
      “authenticationMethod”: “none”
    }

    As shown above, we clicked on createRecipient, entered our accoundId, envelopId, a return url (where user will be redirected after signing). This will generate a link as shown below:

    Get a URL based upon the above Envelope Expected Output

    This link can be embedded anywhere as per requirement.

    Preview DocuSign in Website

    In postman, first get the access token as we did earlier and create a new request as shown below:

    Postman to Get a URL based upon the above Envelope

    This will too generate a link as above which can be embedded wherever required.

    1. Get status of the envelope to check if the user has signed the document or not

    Now, we are at the last stage where a user has already signed a document and we want to know the status. Go to DocuSign Swagger API, and click get within Envelopes, provide accountId and envelopeId to get the status as shown below:

    GET /v2.1/accounts/{accountId}/envelopes/{envelopeId}/recipients

    Get status of the envelope to check if the user has signed the document or not

    Status before signing the document:

    Output of the status of the envelope

    Status after signing the document:

    Status after signing a document

    Same can be called via Postman as shown below:

    Postman to sign a document in DocuSign

    These same above three steps will be used in all next integrations types.

    3.      Integration using Open Connector

    This use case is covered here: https://blogs.sap.com/2019/07/22/digital-signatures-in-sapui5-using-docusign-and-openconnectors/

    In this particular use case, remember we need to authenticate and that is covered here: https://blogs.sap.com/2019/03/13/cloud-integration-how-to-create-a-sample-integration-scenario-using-open-connectors-adapter/

    In case readers want this use case and are not able to implement it out, then let us know in the comment section, we will try to cover that in a separate article.

    4.      Integration using DocuSign Rest APIs in UI5

    We can directly use the Rest APIs provided by DocuSign to integrate it in our UI5 Application. We have already used them to test in step 02. Here, I will mention them once again with AJAX calls. The Authorization and assertion part is explained in initial part of the article.

    Step 01: Get JWT Authentication token

    var settings = {
    
      "url": "https://account-d.docusign.com/oauth/token",
    
      "method": "POST",
    
      "timeout": 0,
    
      "headers": {
    
        "Authorization": "Basic OWE1YTJhN2EtNWYyNS00NTI2LWJjNjItODJkYzYyZmFlMmIxOjI0NDllNTdhLThiNjYtNDZlOC1hOTRjLTdlMjZlYzUyN2E5Yw==",
    
        "Content-Type": "application/x-www-form-urlencoded"
    
      },
    
      "data": {
    
        "assertion": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiI5YTVhMmE3YS01ZjI1LTQ1MjYtYmM2Mi04MmRjNjJmYWUyYjEiLCJzdWIiOiIwMjExZjQzMy1lYWEzLTRkNzMtYmU3Yi04Yjk1YWRmYzRlMDgiLCJhdWQiOiJhY2NvdW50LWQuZG9jdXNpZ24uY29tIiwiaWF0IjoxNjIzNTE3MTA5LCJleHAiOjE2MjM1MjA3MjQsInNjb3BlIjoic2lnbmF0dXJlIGltcGVyc29uYXRpb24ifQ.EqpyIrpytjyMX6xZqRsfgxMyRq2gkhotmAM8kUbtgJ83FK0dbolZ-gEtixckUIeAQvbb-gk5KV6_kwsWs0CgO-waxWtV3XV8k8jXPW42UxQXT1zBVwCm8TjzYpSvIdOGJKGGaPzMj9qJN8a2SSVB2FwkqoMgprEvnWESY3RjT-hzAYbqh5KdiLoFj_EO6KbVS8IUsusXtUYm10YFFduhJ0xN90m50wku0ChijQxKZNyDPai5v-C4NBuhlQA58RqB8OKq4B9WDSnyYHP87R8TaAoqAv8fsLZe8TtOM4J8ZAVG_CJQiKGJOabtFsW1zgNCTr8KCZvL5t6gEJg2t_vx4g",
    
        "grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer"
    
      }
    
    };
    
    $.ajax(settings).done(function (response) {
      console.log(response);
    });

     

    Use the response in step 01, and get the token that will be passed in step 02, step 03 and step 04.

    Step 02: Create an Envelope with a pdf document and send it to a user

    var settings = {
    
      "url": "https://demo.docusign.net/restapi/v2.1/accounts/<account ID>/envelopes ",
    
      "method": "POST",
    
      "timeout": 0,
    
      "headers": {
    
        "Accept": "application/json",
    
        "Authorization": "Basic OWE1YTJhN2EtNWYyNS00NTI2LWJjNjItODJkYzYyZmFlMmIxOjI0NDllNTdhLThiNjYtNDZlOC1hOTRjLTdlMjZlYzUyN2E5Yw==",
    
        "Content-Type": "text/plain"
    
      },
    
      "data": "{\n  \"documents\": [\n    {\n      \"documentBase64\": \"<Base64BytesHere>\",\n      \"documentId\": \"1\",\n      \"name\": \"test\"\n    }\n  ],\n  \"emailSubject\": \"Test\",\n  \"recipients\": {\n    \"signers\": [\n      {\n        \"clientUserId\": \"1\",\n        \"email\": \"rudanboss@gmail.com\",\n        \"name\": \"Rudra\",\n        \"recipientId\": \"1\"\n      }\n    ]\n  },\n  \"status\": \"sent\"\n}",
    
    };
    
    
    
    
    $.ajax(settings).done(function (response) {
    
      console.log(response);
    
    });

     

    The envelope ID will be received in response and will be used in step 03 and step 04.

    Step 03: Get a URL based upon the above Envelope

    var settings = {
    
      "url": "https://demo.docusign.net/restapi/v2.1/accounts/<account ID>/envelopes/<envelop ID>/views/recipient",
    
      "method": "POST",
    
      "timeout": 0,
    
      "headers": {
    
        "Accept": "application/json",
    
        "Authorization": "Basic OWE1YTJhN2EtNWYyNS00NTI2LWJjNjItODJkYzYyZmFlMmIxOjI0NDllNTdhLThiNjYtNDZlOC1hOTRjLTdlMjZlYzUyN2E5Yw==",
    
        "Content-Type": "text/plain"
    
      },
    
      "data": "{\r\n  \"authenticationMethod\": \"None\",\r\n  \"clientUserId\": \"test\",\r\n  \"email\": \"test@test.com\",\r\n  \"recipientId\": \"1\",\r\n  \"returnUrl\": \"https://www.docusign.com/\",\r\n  \"userName\": \"test\"\r\n}   ",
    
    };
    
    
    
    
    $.ajax(settings).done(function (response) {
    
      console.log(response);
    
    });

     

    In the response we will get an URL, this URL can be embedded in an iframe or via a new tab in browser.

    Step 04: Get status of the envelope to check if the user has signed the document or not

    var settings = {
    
      "url": "https://demo.docusign.net/restapi/v2.1/accounts/<Account ID>/envelopes/<Envelope ID>/recipients",
    
      "method": "GET",
    
      "timeout": 0,
    
      "headers": {
    
        "Authorization": "Basic OWE1YTJhN2EtNWYyNS00NTI2LWJjNjItODJkYzYyZmFlMmIxOjI0NDllNTdhLThiNjYtNDZlOC1hOTRjLTdlMjZlYzUyN2E5Yw=="
    
      },
    
    };
    
    
    $.ajax(settings).done(function (response) {
    
      console.log(response);
    
    });

     

    In the response we will get the status of the envelope. In case it is completed, then the user has completed the document signing.

    5.      Integration using Node.js packages

    For the security purpose, it would be good to implement all the calls in backend via Node.Js such that the UI only gets the final URL that can be embedded via iframe and signed by the user. Here we will just show you how to get DocuSign authentication token via Node.Js, also we have used the concept of destination here, that is actually a place in SAP Cloud Foundry to maintain APIs to make calls secure. That part can be ignored and directly second function of the node can be used with hard coded values in case you are not using SAP environment.

    For this purpose we will need the following node packages:

    "moment": "^2.24.0",
     "jsonwebtoken": "^8.5.1"

     

    Node.Js functions to get Authentication token for DocuSign:

                   const  moment = require('moment')
    
        , jwt = require('jsonwebtoken');
    
                    /**
    
                     ** Get Access details from Destination and then pass it to next function docusignAccessToken
    
                     ** @in: GET call
    
                     ** @out: Response Success - Auth_Token
    
                     */
    
                    getDocusignAuthDetails() {
    
                    return new Promise(async (resolve, reject) => {
    
                    try {
                    let destUri, tokenUrl, clientId, clientSecret;
                    clientId = <Your Client ID>; "Client ID of Destination in SAP Cloud Foundry
                    clientSecret = <Your Client Secret>; "Client Secret of Destination in SAP Cloud Foundry
                    tokenUrl = `<Your Token URL>`; "URL to get the token, this is saved on Destination in SAP Cloud Foundry
                    destUri = <Your Destination URL>; "URL of Destination in SAP Cloud Foundry that contains the actual URL of DocuSign, for security purpose
     let options = {
     uri: tokenUrl,
     method: 'GET',
     headers: {
     'content-type': 'application/json',
     "Authorization": "Basic " + Buffer.from(clientId + ":" + clientSecret).toString("base64"),
    }
    
    };
    
    
    
     let tokenResp = await httpHandler.execPOSTReq(options);
     tokenResp = JSON.parse(tokenResp);
     // Here we are calling SAP Cloud Foundry Destination to get the access_token, this can be replaced by a simple HTTP call too
    
      options = {
      uri: destUri + '/destination-configuration/v1/destinations/docusign_get_token',
      method: 'GET',
     headers: {
     'content-type': 'application/json',
     Authorization: 'bearer ' + tokenResp.access_token
      }
     };
    
    
      let destConfig = await httpHandler.execPOSTReq(options);
     destConfig = JSON.parse(destConfig).destinationConfiguration;
      resolve(destConfig);
    } catch (err) {
     console.log(err);
     reject(err)
      }
    })
     },
    
      /**
    
     ** Get Access Token for docusign
    
      ** @in: POST call
    
      ** @out: Response Success - Auth_Token
      */
    
     async docusignAccessToken(req, res) {
      try {
     var authCreds = await self.getDocusignAuthDetails();
     let privateKEY = Buffer.from(authCreds.RSA_pvt_key, 'base64');
    // Step 1. Create a JWT token for DocuSign
     const now = moment(),
     iat = now.unix(),
      exp = now.add((9 * 60) + 30, 's').unix();
     // this is already explained in initial part of the article
     const ghJWT =
    jwt.sign({
     "iss": authCreds.clientId,
     "sub": authCreds.sub,
     "aud": authCreds.aud,
     "scope": authCreds.scope,
     "iat": iat,
     "exp": exp
     },
     privateKEY, {
     algorithm: 'RS256'
      });
     var request = require('request');
    var options = {
    'method': 'POST',
     'url': authCreds.URL,
    'headers': {
     "Authorization": "Basic " + Buffer.from(authCreds.clientId + ":" + authCreds.clientSecret).toString("base64"),
     'Content-Type': 'application/x-www-form-urlencoded'
      },
     form: {
     'assertion': ghJWT,
     'grant_type': 'urn:ietf:params:oauth:grant-type:jwt-bearer'
    }
     };
    
      request(options, function (error, response) {
      if (error) throw new Error(error);
      console.log(response.body);
      res.status(200).send({ //Success response contains Access token of DocuSign
     status: true,
     message: response.body
     });
     });
    
    } catch (err) {
     errorMethods.handleError(err, res, req, 'ErrDocusignAccessToken');
     }
     }

     

    6.      Integration using ABAP

    You can follow the steps given in Step 02 and convert them in ABAP API calls.

    An example to create token is shown below, replace them with valid keys and values:

      METHOD get_token.
        DATA: lo_http_client TYPE REF TO if_http_client.
        DATA: response TYPE string,
              lv_url   TYPE string.
        CONSTANTS: lv_initial_url TYPE string VALUE '<URL>',
                   lv_auth        TYPE string VALUE '<Bearer Auth code>'.
        "create HTTP client by url
        CALL METHOD cl_http_client=>create_by_url
          EXPORTING
            url                = lv_initial_url
          IMPORTING
            client             = lo_http_client
          EXCEPTIONS
            argument_not_found = 1
            plugin_not_active  = 2
            internal_error     = 3
            OTHERS             = 4.
    
        "Available API Endpoints
    
        IF sy-subrc <> 0.
    
          "error handling
    
        ENDIF.
    
        "setting request method
    
        lo_http_client->request->set_method('GET').
    
        "adding headers
    
        lo_http_client->request->set_header_field( name = 'Authorization' value = lv_auth ).
    
        "Available Security Schemes for productive API Endpoints
    
        "OAuth 2.0
    
        CALL METHOD lo_http_client->send
    
          EXCEPTIONS
    
            http_communication_failure = 1
    
            http_invalid_state         = 2
    
            http_processing_failed     = 3
    
            http_invalid_timeout       = 4
    
            OTHERS                     = 5.
    
        IF sy-subrc = 0.
    
          CALL METHOD lo_http_client->receive
    
            EXCEPTIONS
    
              http_communication_failure = 1
    
              http_invalid_state         = 2
    
              http_processing_failed     = 3
    
              OTHERS                     = 5.
    
        ENDIF.
        IF sy-subrc <> 0.
    
          "error handling
    
        ENDIF.
        response = lo_http_client->response->get_cdata( ).
       GV_TOKEN = response.
    
      ENDMETHOD.
    
    Using the above GV_TOKEN we will get the keys and this key can be passed in other required GET/POST calls.
    
    An example of GET/POST call in ABAP is shown below:
      METHOD get_proof_history.
        DATA: lo_http_client TYPE REF TO if_http_client.
    
        DATA: response TYPE string,
    
              lv_url   TYPE string,
    
              lv_auth  TYPE string,
    
              lv_auth2 TYPE string.
    
        CONSTANTS : lv_initial_url TYPE string VALUE '<URL>'.
        IF iv_object_id IS NOT INITIAL.
    
    *** Getting Token
    
          TYPES:
    
            BEGIN OF t_entry,
    
              access_token TYPE string,
    
              token_type   TYPE string,
    
              expires_in   TYPE n LENGTH 8,
    
              scope        TYPE string,
    
              jti          TYPE string,
    
            END OF t_entry .
    
          TYPES:
    
            t_entry_map TYPE SORTED TABLE OF t_entry WITH UNIQUE KEY access_token.
    
          DATA: m_entries TYPE t_entry.
    
          DATA: lr_instance  TYPE REF TO  /ui5/cl_json_parser.
    
          CREATE OBJECT lr_instance.
    
          CALL METHOD me->get_token.
    
          IF gv_token IS NOT INITIAL.
    
    *        data: itab TYPE TABLE OF string.
    
    *        data: access_tok type string.
    
    *        SPLIT gv_token at '"' INTO TABLE itab.
    
    *        try.
    
    *            lv_auth2 = itab[ 4 ].
    
    *          catch cx_sy_itab_line_not_found.
    
    *        ENDTRY.
    
            /ui2/cl_json=>deserialize(
    
            EXPORTING json = gv_token pretty_name = /ui2/cl_json=>pretty_mode-camel_case CHANGING data = m_entries ).
    
            lv_auth2 = m_entries-access_token.
    
            gv_token = gv_token+17.
    
            CONCATENATE 'Bearer' lv_auth2 INTO lv_auth SEPARATED BY space.
    
          ENDIF.
    
          DATA lv_object_id TYPE string.
    
          lv_object_id = iv_object_id.
    
          TRANSLATE lv_object_id TO LOWER CASE.
    
          CONCATENATE lv_initial_url lv_object_id INTO lv_url. "Appending Fix URL and the Object ID to get the Request URL
    
          "create HTTP client by url
    
          CALL METHOD cl_http_client=>create_by_url
    
            EXPORTING
    
              url                = lv_url
    
            IMPORTING
    
              client             = lo_http_client
    
            EXCEPTIONS
    
              argument_not_found = 1
    
              plugin_not_active  = 2
    
              internal_error     = 3
    
              OTHERS             = 4.
    
          "Available API Endpoints
    
          "https://blockchain-service.cfapps.sap.hana.ondemand.com/blockchain/proofOfHistory/api/v1
    
          "https://blockchain-service.cfapps.eu10.hana.ondemand.com/blockchain/proofOfHistory/api/v1
    
          "https://blockchain-service.cfapps.us10.hana.ondemand.com/blockchain/proofOfHistory/api/v1
          IF sy-subrc <> 0.
    
            "error handling
    
          ENDIF.
          "setting request method, here you can do POST or GET calls
    
          lo_http_client->request->set_method('GET').
    
          "creatung Auth value
    
    *       lv_auth2 = 'Basic <URL>'.
    
          "adding headers
    
    *      lo_http_client->request->set_header_field( name = 'Content-Type' value = 'application/x-www-form-urlencoded' ).
    
          lo_http_client->request->set_header_field( name = 'Accept' value = 'application/json' ).
    
          lo_http_client->request->set_header_field( name = 'Authorization' value = lv_auth ).
    
    *      lo_http_client->request->set_header_field( name = 'APIKey' value = 'zBoCpDtkaT9jexRjtMk0J98Rs8izmQi1' ).
          "Available Security Schemes for productive API Endpoints
    
          "OAuth 2.0
    
          CALL METHOD lo_http_client->send
    
            EXCEPTIONS
    
              http_communication_failure = 1
    
              http_invalid_state         = 2
    
              http_processing_failed     = 3
    
              http_invalid_timeout       = 4
    
              OTHERS                     = 5.
          IF sy-subrc = 0.
            CALL METHOD lo_http_client->receive
    
              EXCEPTIONS
    
                http_communication_failure = 1
    
                http_invalid_state         = 2
    
                http_processing_failed     = 3
    
                OTHERS                     = 5.
          ENDIF.
          IF sy-subrc = 1.
            "error handling
            ev_response = 'http_communication_failure'.
          ELSEIF sy-subrc = 2.
            ev_response = 'http_invalid_state'.
          ELSEIF sy-subrc = 3.
            ev_response = 'http_processing_failed'.
          ELSEIF sy-subrc = 0.
            response = lo_http_client->response->get_cdata( ).
    
    *WRITE: 'response: ', response.
            ev_response = response.
          ELSE.
            ev_response = 'Unknown Error'.
          ENDIF.
        ENDIF.
      ENDMETHOD.

     

    7.      Other Integration Options

    Apart from the integrations that we have discussed above, there are other types of integrations too that is supported by DocuSign. These are (with GitHub/reference links):

    Troubleshoot errors

    While performing the above operations, you can face following issues:

    Error Reason Solution
    Invalid authentication request Here you will get more reason of this error.

     

    If the error is “The response type is not supported”

    Then

    response_type value should be set as code

    Token type mismatch It means the provided token type is wrong ·         Use correct type of token with correct payload:

    1.       Response Type: This can be response_type=code (for Authorization Code Grant  and JWT Grant) or response_type=token (for Implicit Grant)

    2.       Grant Type: This can be grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer  (for JWT Grant) or grant_type=authorization_code (for Authorization Code Grant). It is not required for Implicit Grant

    Expired Client token It means you access token has expired ·         Refresh or generate a new access token
    Client ID is disabled It means your app on DocuSign developer account is disabled ·         Activate your App on DocuSign developer portal
    Invalid user site ID It means that the URL used for generating the access token and performing DocuSign operations are not same ·         Use the same URI for all the operations
    Issuer not found It means the User is invalid ·         Make sure the User has access to the DocuSign developer account that is being used to perform operations
    User not found It means the User is invalid ·         Make sure the User has access to the DocuSign developer account that is being used to perform operations
    Refresh token mismatch It means the token has either expired or it is wrong ·         Regenerate access or refresh token
    Bad Request It occurs mainly due to wrong authentication URI ·         For the developer demo environment, you should use URIs from the https://account-d.docusign.com/oauth domain.

    ·         For the production platform, you should use URIs from the https://account.docusign.com/oauth domain.

     

    Invalid authentication request: The response type is not supported. It means authorization code request is not set to code. ·         Set response_type as code
    Invalid Grant Either the authorization code is incomplete or expired. ·         You can regenerate the Authorization Code

    ·         You can check if the authorization code provided is correct and no extra white spaces are added in that

    Invalid RedirectUri It means the redirect URI that is maintained within DocuSign Integration key is not the one being sent via payload. ·         Use the same redirect URI that you maintain within your DocuSign Integration Key
    Consent_required For the very first time, you need to provide consent manually, if not then you will get this error ·         Link for getting consent for the very first time:
    https://account-d.docusign.com/oauth/auth?
    response_type=code
    &scope=YOUR_REQUESTED_SCOPES
    &client_id=YOUR_INTEGRATION_KEY
    &redirect_uri=YOUR_REDIRECT_URI
    Page cannot be displayed after requesting the code It means the redirect URI is not a valid webpage ·         Maintain a valid URI as redirect URI within the Integration Key at DocuSign
    invalid_grant: no_valid_keys_or_signatures It means something is wrong in JWT assertion. It can be either in private key, missing exp, invalid aud, ·         Refer to the Prerequisite section of this article and provide right values as mentioned there
    Partner Authentication Failure / Integration key disabled It means your app on DocuSign developer account is disabled ·         Activate your App on DocuSign developer portal
    CORS: Cross Origin You might be using the APIs directly in your AJAX calls. You need to have it integrated via backend or via a destination.
    ONESIGNALLSIGN_NOT_SATISFIED Freeform signing is not allowed for your account because it conflicts with other settings, please place signing tabs for each signer.. Go to Admin setting in DocuSign, under senders settings, switch the drop down for “Document Visibility” as off

    Reference Links

    You can explore more here:

  • SAP RAP Architecture

    Preface – This post is part of the SAP ABAP RAP series.

    Introduction

    THE SAP ABAP RESTful Application Programming model (RAP) supports the efficient end-to-end development of SAP OData based SAP Fiori services and Web APIs intrinsically optimized for SAP HANA on SAP BTP (Business Technology Platform) ABAP environment or on-prem SAP S/4 HANA.

    Architecture Overview

    The below rough version of SAP RAP Architecture depicts the major functionalities that we can utilize:

    What is SAP RAP

    The below figure displays the major artifacts you have to deal with while developing the OData services using the SAP ABAP RESTful Application Programming model. It depicts the bottom-up approach that illustrates the application development flow.

    SAP RAP Architecture

    Figure 1: SAP ABAP RAP Architecture Overview

    The SAP ABAP RAP is three-layered architecture: Data Modelling and Behaviour; Business Services and; Service Consumption. Let’s see briefly.

    Data Modelling and Behaviour

    The base layer of RAP is DATA Modelling and Behaviour. Here, we define the business object models, query definitions and their transactional behaviour. Taking an example of a simple SAP Fiori based application, we define the database table and expose the data using CDS. As soon as the CDS for the root entity is created, we define the transactional behaviour also.

    Business Services

    In the middle layer, the OData exposer is done in two steps: the first step is where the protocol service scope definition of relevant data entities is defined, and the second step is data binding to the OData model. For example, we have 50 CDS views relevant to Sales-Order but we want only 1 view to be exposed to the outside world. This scenario can be achieved via this layer. (See Figure 2)

    Design-time artifacts

    Figure 2: Design-time artifacts

    Service Consumption

    The topmost layer is the Service Consumption layer. Here, an OData Service can be exposed as a UI service which can be consumed by SAP Fiori or Web API, that can later be consumed by any client.

    Illustration of OData Service consumption by SAP Fiori UI and WEB API

    Figure 3: Illustration of OData Service consumption by SAP Fiori UI and WEB API

  • Introduction to SAP RAP: ABAP RESTful Application Programming Model

    Preface – This post is part of the SAP ABAP RAP series.

    Introduction

    SAP has introduced various programming models helping the organizations via development of efficient applications that meets their business needs. Over time, with changing requirements and technologies, these programming models have evolved from DYNPRO and list programming models for SAP GUI based applications to WEB DYNPRO model for web-based applications and thereafter to SAP ABAP programming model for SAP Fiori.

    While SAP ABAP programming model for SAP Fiori is a key advantage in terms of evolving landscape, flexibility and efficient modelling for SAP Fiori, SAP HANA; lacks certain criteria like simplicity and typed access to business entities which seems to be the basic need for application development. Here RAP, a new programming model by SAP, fills the gap.

    SAP ABAP RAP provides the intrinsic approach to build SAP Fiori based applications that are optimized for S/4 HANA and can run over on-premise as well as on the cloud.

    Evolution of ABAP programming model

    Let’s take a look at how programming models are changed over the years:

    Evolution of ABAP programming modelFigure 1: The evolution of the ABAP Programming model

    Traditional ABAP programming model

    The traditional ABAP programming models include Classical ABAP Programming- such as DYNPRO and list programming for SAP GUI based applications and business servers and WEB DYNPRO for web-based applications. This model was designed for a traditional on-prem environment.

    ABAP Programming model for SAP Fiori

    The model is based on CDS, BOPF and SAP Gateway. It reduces the complexity with advanced SEGW option reference data source (RDS) to expose the scope of the CDS model to an OData service.

    Additionally, it supports service to transactional applications. To enable transactional services we need to provide the special annotation that links the CDS model to the corresponding BOPF framework model which supports transactional oriented behaviour via artifacts:  validations, determinations and actions.

    Both CDS and BOPF are different in nature and as they both are modelling frameworks; you must need to maintain both in parallel. CDS is a typed framework that is embedded in ABAP design time and runtime where type checking is done at design time, in contract BOPF is very generic and most of the errors happen at runtime which is difficult to debug. Also, while developing a typical standard application, many artifacts are created which adds to the complexity while implementation or changing model parts. To overcome this limitation of the ABAP Programming model for SAP Fiori, SAP ABAP RAP model was introduced.

    SAP ABAP RESTful Application Programming model

    SAP ABAP RESTful Application Programming Model (RAP) was introduced by SAP Cloud Platform ABAP environment and is available with 1808 release and higher.

    The RESTful Application Programming model is built on the top of the semantic data model (CDS) and the transactional services are exposed in behaviour definition and implementation in implementing behaviour class. It also allows adapting the existing applications to be modelled which is intended to be used over a long time.  You can start from scratch (greenfield implementation) or you can reuse existing business logic (brownfield implementation).

    One can develop the following end-to-end scenarios:

    • SAP Fiori service
    • Service consumption
    • Web APIs

    What is SAP RAP?

    In simple words, SAP RAP is a new way to develop Fiori Applications based upon HANA features. This programming model uses ABAP programming methodology.
    It uses the following:
    1. ADT (ABAP Development Tools in eclipse or HANA Studio): This is used to develop tasks

    2. CDS (Core Data Services in eclipse or HANA Studio): This is used for the implementation of business object layer

    3. A New Framework: This is used to handle business logics. It generates web APIs that can be consumed by Fiori or any other applications.

    The rough diagram of SAP RAP is shown below:

    What is SAP RAP

    This programming model looks very similar to the classical ABAP OData and CDS based UI5 Development. This programming model will replace the old way of developing apps via OData SEGW and BOPF.

  • Azure DevOps Interview Questions

    Preface – This post is part of the SAP on Azure series.

    Introduction

    With cloud and GitHub, every IT company is now capable to deliver high quality product in small duration. But as the development and product gets complex, it gets important to manage the Build and Release of the product via a software tool. Among available tools, Azure DevOps is the best tool for CI/CD. As the demand for this tool is increasing, so is the requirement for new resources. In this article we will discuss the most common interview questions asked regarding Azure DevOps including the SAP deployment over it.

    Azure DevOps Basic Interview Questions

    1. What is DevOps?
    2. What is the need of DevOps?
    3. Explain the process flow of DevOps.
    4. Name the popular DevOps tools for Continuous Deployment and Continuous Integration.
    5. What are Continuous Integration and Continuous Deployment features? How they can be implemented?
    6. What are Continuous Testing and Continuous Monitoring features? How they can be implemented?

    Azure DevOps Basic Interview Questions

    1. What is the difference between Azure DevOps Services and Azure DevOps Server? Which one should we choose?
    2. What is Azure Repository? How a branch is created?
    3. What is the concept of Branches in Azure DevOps?
    4. How to add security in branches of Azure DevOps?
    5. How to create a pipeline in Azure DevOps?
    6. How to debug errors in Azure DevOps build and release?
    7. Explain the concept of cherry pick.

    Azure DevOps Scenario based Interview Questions

    1. If you need to deploy a particular commit from Development directly into Production, how will you do that?
    2. You are asked to cherry pick a particular commit, while doing that it failed on Azure DevOps. What will you do if Cherry pick fails on Azure DevOps?
    3. If a small change is causing issue in cherry pick, what alternative you can use apart from manual cherry pick?
    4. If a lot of bugs are there in Quality system and a lot of new development is going on Development system, then how will you proceed with deployment of both bug fixes and new feature deployments?
    5. If a stable build needs to be kept aside for Demo/POC version, how can it be done?

    SAP on Azure DevOps Interview Questions

    1. Explain the process flow of DevOps in terms of SAP.
    2. Explain the SAP Cloud Environment.
    3. How SAP in configured on Azure DevOps?
    4. How a SAP failed deployment is debugged in Azure DevOps?
    5. How to deploy builds to SAP Cloud Platform manually via command line.
    6. Explain the concept of Global Account, Sub-accounts, space and Org in SAP Cloud Platform.
    7. How we can partially deploy DB in SAP Cloud Platform?
  • Qualtrics Integration in UI5 or any website

    Preface – This post is part of the UI5 Integration Programs series.

    Introduction

    SAP Qualtrics is an employee management platform which measures employee experience with the help of a survey form. SAP acquired Qualtrics in 2018. One can create a free demo account to create a form that can be integrated via iframe. In case you want to integrate it in a UI5 app based upon any event (like press of button or completion of an operation), then SAP Qualtrics predefined ways.

    Prerequisite

    • You need to have an account of SAP Qualtrics (click here to create) with authorization to create feedback survey forms
    • You need to have access of SAP WebIDE (In case you are integrating in a non UI5 website, you don’t need Web IDE)
    • You have already created a survey form in Qualtrics

    Types of Qualtrics Integration

    You can integrate Qualtrics in UI5 App using following three ways:

    1. Direct integration of survey form in UI5 using iframe
    2. UI5 Integration using Qualtrics script (not supported in free accounts)
    3. UI5 Integration using WebIDE Fiori plugin (not supported in free accounts)

    Creating Survey on Qualtrics

    Step 01: To create a survey form, login into your Qualtrics account, and create a new project:

    Creating Survey on Qualtrics

    Step 02: Create a survey either from scratch or create using template:

    create using templateStep 03: Give a suitable name to the project:

    Give a suitable name to the projectStep 04: Create your questions and click publish:

    Create your questions and click publishStep 05: Once the survey is activated, you will get a link as shown below. This link will be used for iframe purpose.

    Qualtrics Survey

    Step 06: Get the code snippet (not supported in free account), this will be used for script based as well as plugin based integration. To get the code snippet, go to settings and click on deployment, as shown below:

    Qualtrics Settings

    Here, you will see a code snippet, similar to the one shown below:
    Qualtrics Snippet code

    Steps to Integrate Qualtrics in UI5

    1.      Direct integration of survey form in UI5 using iframe

    Step 01: In above steps, we have received a link of the survey, which looks like this: https://qfreeaccountssjc1.az1.qualtrics.com/jfe/form/SV_3CqJj9JLKoH54ii

    Step 02: Go to webIDE and create a simple App.

    Step 03: Add following code snippet in the view (nothing is required here in the controller)

    <mvc:View controllerName="Test.Test.controller.Main" xmlns:mvc="sap.ui.core.mvc" displayBlock="true" xmlns="sap.m" xmlns:html="http://www.w3.org/1999/xhtml">
    
    <Shell id="shell">
      <App id="app">
         <pages>
           <Page id="page" title="Qualtrics Integration using iFrame">
           <content>
    <html:iframe height="100%" width="100%" src="https://qfreeaccountssjc1.az1.qualtrics.com/jfe/form/SV_3CqJj9JLKoH54ii"/>
            </content>
            </Page>
          </pages>
    
       </App>
     </Shell>
    </mvc:View>

    Step 04: Run the App now, you will see the survey loaded within the App. Now as per your requirement, you can add this iframe code either in a Dialog or fragment.

    Qualtrics Integration in UI5 using iframeStep 05: Since the code snippet can open up anywhere and in any browser, therefore it is important to make it secure. For that Qualtrics provide something called “HTTP Referer” or “Add a referral website URL”. The purpose is to allow people to take your survey only if they select a survey link included on a specific website.
    To do that, go to the survey, and click survey options, and then turn on “Add a referral website URL”. Then enter the base URL of your website or UI5 App.

    Secure qualtrics form2.      UI5 Integration using Qualtrics script

    Step 01: In above steps, we have received the Qualtrics snippet. That will look something like this:

    <!--BEGIN QUALTRICS WEBSITE FEEDBACK SNIPPET-->
    
    <script type='text/javascript'>
    
    (function(){var g=function(e,h,f,g){
    
    this.get=function(a){for(var a=a+"=",c=document.cookie.split(";"),b=0,e=c.length;b<e;b++){for(var d=c[b];" "==d.charAt(0);)d=d.substring(1,d.length);if(0==d.indexOf(a))return d.substring(a.length,d.length)}return null};
    
    this.set=function(a,c){var b="",b=new Date;b.setTime(b.getTime()+6048E5);b="; expires="+b.toGMTString();document.cookie=a+"="+c+b+"; path=/; "};
    
    this.check=function(){var a=this.get(f);if(a)a=a.split(":");else if(100!=e)"v"==h&&(e=Math.random()>=e/100?0:100),a=[h,e,0],this.set(f,a.join(":"));else return!0;var c=a[1];if(100==c)return!0;switch(a[0]){case "v":return!1;case "r":return c=a[2]%Math.floor(100/c),a[2]++,this.set(f,a.join(":")),!c}return!0};
    
    this.go=function(){if(this.check()){var a=document.createElement("script");a.type="text/javascript";a.src=g;document.body&&document.body.appendChild(a)}};
    
    this.start=function(){var t=this;"complete"!==document.readyState?window.addEventListener?window.addEventListener("load",function(){t.go()},!1):window.attachEvent&&window.attachEvent("onload",function(){t.go()}):t.go()};};
    
    try{(new g(100,"r","<Unique Code>","<Unique Link>")).start()}catch(i){}})();
    
    </script><div id='ZN_eDoIXhSDN98YlPo'><!--DO NOT REMOVE-CONTENTS PLACED HERE--></div>
    
    <!--END WEBSITE FEEDBACK SNIPPET-->

    Step 02: Go to webIDE and create a simple App.

    Step 03: Add following code snippet in the controller (nothing is required here in the view). This code can be either written in onInit function or click of an event (like button click event). The snippet you received above will be used here in string format from “var g ..to.. catch(i){}”. So we need to remove the (function(){ from begining and })(); from end.

    sap.ui.define([
    
                    "sap/ui/core/mvc/Controller"
    
    ], function (Controller) {
    
                    "use strict";
             return Controller.extend("Test.Test.controller.Main", {
             onInit: function () {
    
             var snippet = 'var g=function(e,h,f,g){this.get=function(a){for(var a=a+"=",c=document.cookie.split(";"),b=0,e=c.length;b<e;b++){for(var d=c[b];" "==d.charAt(0);)d=d.substring(1,d.length);if(0==d.indexOf(a))return d.substring(a.length,d.length)}return null};this.set=function(a,c){var b="",b=new Date;b.setTime(b.getTime()+6048E5);b="; expires="+b.toGMTString();document.cookie=a+"="+c+b+"; path=/; "};this.check=function(){var a=this.get(f);if(a)a=a.split(":");else if(100!=e)"v"==h&&(e=Math.random()>=e/100?0:100),a=[h,e,0],this.set(f,a.join(":"));else return!0;var c=a[1];if(100==c)return!0;switch(a[0]){case "v":return!1;case "r":return c=a[2]%Math.floor(100/c),a[2]++,this.set(f,a.join(":")),!c}return!0};this.go=function(){if(this.check()){var a=document.createElement("script");a.type="text/javascript";a.src=g;document.body&&document.body.appendChild(a)}};this.start=function(){var t=this;"complete"!==document.readyState?window.addEventListener?window.addEventListener("load",function(){t.go()},!1):window.attachEvent&&window.attachEvent("onload",function(){t.go()}):t.go()};};try{(new g(100,"r","<Unique Code>","<Unique Link>")).start()}catch(i){}';
    
           var functionCall = new Function(snippet);            
           return (functionCall());
    }
    
                    });
    
    });

    Step 04: Run the App now, you will see the survey loaded within the App.

    Qualtrics in UI5Step 05: To secure this code snippet, you can load this particular script during runtime. To do that, you need to save this code snippet in the backend in a table in encrypted format and get it during runtime. Once, you get the code, decrypt it and run it.

    3.      UI5 Integration using WebIDE Fiori plugin

    This is the most complex way to integrate Qualtrics in UI5 App. Here, firstly we create a Fiori plugin.

    Step 01: Create a Plugin (check how to create a plugin here)

    Step 02: In above steps, we have received the Qualtrics snippet. That will look something like this:

    <!--BEGIN QUALTRICS WEBSITE FEEDBACK SNIPPET-->
    
    <script type='text/javascript'>
    
    (function(){var g=function(e,h,f,g){
    
    this.get=function(a){for(var a=a+"=",c=document.cookie.split(";"),b=0,e=c.length;b<e;b++){for(var d=c[b];" "==d.charAt(0);)d=d.substring(1,d.length);if(0==d.indexOf(a))return d.substring(a.length,d.length)}return null};
    
    this.set=function(a,c){var b="",b=new Date;b.setTime(b.getTime()+6048E5);b="; expires="+b.toGMTString();document.cookie=a+"="+c+b+"; path=/; "};
    
    this.check=function(){var a=this.get(f);if(a)a=a.split(":");else if(100!=e)"v"==h&&(e=Math.random()>=e/100?0:100),a=[h,e,0],this.set(f,a.join(":"));else return!0;var c=a[1];if(100==c)return!0;switch(a[0]){case "v":return!1;case "r":return c=a[2]%Math.floor(100/c),a[2]++,this.set(f,a.join(":")),!c}return!0};
    
    this.go=function(){if(this.check()){var a=document.createElement("script");a.type="text/javascript";a.src=g;document.body&&document.body.appendChild(a)}};
    
    this.start=function(){var t=this;"complete"!==document.readyState?window.addEventListener?window.addEventListener("load",function(){t.go()},!1):window.attachEvent&&window.attachEvent("onload",function(){t.go()}):t.go()};};
    
    try{(new g(100,"r","QSI_S_ZN_eDoIXhSDN98YlPo","<Unique Link>")).start()}catch(i){}})();
    
    </script><div id='ZN_eDoIXhSDN98YlPo'><!--DO NOT REMOVE-CONTENTS PLACED HERE--></div>
    
    <!--END WEBSITE FEEDBACK SNIPPET-->

    Step 03: Now go to the component.js file in the plugin application that you created in step 01, and add that full code in the onInit function.

    Step 03: Now save it and run. You will be able to see the feedback button at the corner of the screen something like below:

    Qualtrics Output

    Now your Fiori plugin is ready to deploy and use in your UI5 Application.

    Step 04: Deploy your Fiori Plugin App in WebIDE via SAP Cloud Platform.

    Step 05: Now, whenever you will create a Fiori Launchpad, you can add this plugin as an App where App type is selected as Shell Plugin. You can read it in detail here. The plugin on Fiori Launchpad will look like this:

    Qualtrics Integration using Fiori Pluggin

  • App View and Controller to Initialize the UI5 Application

    Preface – This post is part of the UI5 Programs series.

    Introduction

    When we create a blank UI5 Application, it is created with a blank view and controller. It is recommended to create an App view on top of these Individual views for pages, so that it can act as a root view for others. SAP recommends to have this file as a root view.

    App View and Controller to Initialize the UI5 ApplicationAs a developer you might have these questions in mind:

    1. Why App View is required in UI5?
      Ans.
      It is a root element of UI5 mobile App. It adds certain header tags which is important for mobile apps. Also, it provides navigation capabilities.
    2. What will happen if no App view is added?
      Ans.
      You might get issue during navigation or during mobile view.
    3. What all you can do with the help of App view?
      Ans.
      You can modify the background colour, background image, set home icon, set initial screen loader with the help of App view.

    App View

    You can simple add a new UI5 View by right clicking at project level and name it “App”. This will add a view and a controller in respective folders with name “App.view.xml” and “App.controller.js”. Remember, we don’t need any routing and navigation for the App view, hence you should delete that if it is created automatically.
    Add the given code in view file:

    <mvc:View controllerName="TestApp.controller.App" xmlns:mvc="sap.ui.core.mvc" displayBlock="true" xmlns="sap.m">
        <Shell id="shell">
        <App id="app"
             busy="{appView>/busy}"
             busyIndicatorDelay="{appView>/delay}"/>
        </Shell>
    </mvc:View>
    

    App Controller

    Add the following code in controller file:

    sap.ui.define([
        "sap/ui/core/mvc/Controller"
    ], function (Controller) {
        "use strict";
    
        return Controller.extend("TestApp.controller.App", {
            onInit: function () {
     
            }
        });
    });
    

    Manifest Changes

    Inside manifest file, modify the object “rootView” as shown below:

    "rootView": {
                "viewName": "TestApp.view.App",
                "type": "XML",
                "id": "app"
            }
    

    Also set the controlId as “app” under config of routing.

    Now, you have successfully added an App Root view in your custom UI5 App.

    Full Manifest Code

    {
        "_version": "1.12.0",
        "sap.app": {
            "id": "testApp",
            "type": "application",
            "i18n": "i18n/i18n.properties",
            "applicationVersion": {
                "version": "1.0.1"
            },
            "title": "{{appTitle}}",
            "description": "{{appDescription}}",
            "sourceTemplate": {
                "id": "servicecatalog.connectivityComponentForManifest",
                "version": "0.0.0"
            },
            "dataSources": {
                "oDataService": {
                    "uri": "<Your Service Name>",
                    "type": "OData",
                    "settings": {
                        "odataVersion": "2.0",
                        "localUri": "<local Metadata>"
                    }
                }
            }
        },
        "sap.ui": {
            "technology": "UI5",
            "icons": {
                "icon": "",
                "favIcon": "",
                "phone": "",
                "phone@2": "",
                "tablet": "",
                "tablet@2": ""
            },
            "deviceTypes": {
                "desktop": true,
                "tablet": true,
                "phone": true
            }
        },
        "sap.ui5": {
            "flexEnabled": false,
            "dependencies": {
                "minUI5Version": "1.65.6",
                "libs": {
                    "sap.ui.layout": {},
                    "sap.ui.core": {},
                    "sap.m": {}
                }
            },
            "contentDensities": {
                "compact": true,
                "cozy": true
            },
            "models": {
                "i18n": {
                    "type": "sap.ui.model.resource.ResourceModel",
                    "settings": {
                        "bundleName": "testApp.i18n.i18n"
                    }
                },
                "@i18n": {
                    "type": "sap.ui.model.resource.ResourceModel",
                    "uri": "i18n/i18n.properties"
                },
                "ODataModel": {
                    "type": "sap.ui.model.odata.v2.ODataModel",
                    "settings": {
                        "defaultOperationMode": "Server",
                        "defaultBindingMode": "TwoWay",
                        "defaultCountMode": "Request",
                        "json": true,
                        "defaultUpdateMethod": "PUT",
                        "useBatch": false
                    },
                    "dataSource": "oDataService",
                    "preload": true
                }
            },
            "resources": {
                "css": [{
                    "uri": "css/style.css"
                }]
            },
            "rootView": {
                "viewName": "testApp.view.App",
                "type": "XML",
                "id": "app"
            },
            "routing": {
                "config": {
                    "routerClass": "sap.m.routing.Router",
                    "viewPath": "testApp.view",
                    "controlId": "app",
                    "bypassed": {
                        "target": ["NotFound"]
                    }
                },
                "routes": [{
                    "name": "Tab_1",
                    "pattern": "",
                    "target": ["masterTarget", "Tab_1"]
                }, {
                    "name": "Tab_2",
                    "pattern": "Tab_2",
                    "target": ["masterTarget", "Tab_2"]
                }],
                "targets": {
                    "masterTarget": {
                        "viewType": "XML",
                        "transition": "slide",
                        "controlAggregation": "masterPages",
                        "viewId": "idMaster",
                        "viewName": "Master",
                        "viewLevel": 1,
                        "controlId": "Splitapp"
                    },
                    "Tab_1": {
                        "viewType": "XML",
                        "transition": "slide",
                        "controlAggregation": "detailPages",
                        "viewId": "idTab_1",
                        "viewName": "Tab_1",
                        "viewLevel": 1,
                        "controlId": "Splitapp"
                    },
                    "Tab_2": {
                        "viewType": "XML",
                        "transition": "slide",
                        "controlAggregation": "detailPages",
                        "viewId": "idTab_2",
                        "viewName": "Tab_2",
                        "viewLevel": 2,
                        "controlId": "Splitapp"
                    }
                }
            }
        },
        "sap.platform.hcp": {
            "uri": "webapp",
            "_version": "1.1.0"
        }
    }
    

     

  • Manifest File for Adding UI Annotation in custom UI5 App

    Preface – This post is part of the UI5 Programs series.

    Introduction

    Many times we need to add UI Annotation for custom UI5 application. This requirement is generated as backend is unable to provide some metadata that is required for Smart controls. To add those metadata, we create an annotation file and connect with required service. In this article, we will mainly focus regarding the setup of UI annotation using manifest.json file. We will discuss how the annotations are added in a different article.

    How to start Adding Annotation in a Service

    We need to follow given steps to add an Annotation in custom UI5 App:

    1. Download the metadata of the service for which you want to add Annotation. For that you need to open “<app url>/<your service name>/$metadata” and save it locally in your system. Here, we will take example of Northwind metadata.
      Adding local Metadata in UI5
    2. Add a new OData service (by right clicking at the project) and choose “File System” as a source. It will ask to upload a metadata file. Upload the one that you have just downloaded in step 01. Choose default or named model in next step. This will automatically add a service in your manifest file. Also, a new folder “localService” will be created with your metadata file.
      Adding local Service in UI5

    The manifest will look like this now:

    Manfiest for Local Service in UI5

    1. Add a new Annotation File (by right clicking at the folder “LocalService”), choose your model (here it is Northwind) and finish.

    Adding UI Annotation in custom UI5 AppThis will create an annotation file within the “LocalService” folder. Also, it will change the metadata as below:

    Manifest for UI Annotation in custom UI5 App

    As you can see a new Annotation is added in the manifest now.

    1. Now you can open your Annotation file and your respective changes.

    Annotation File in custom UI5Changes in Manifest File for Adding UI Annotation in custom UI5 App

    For Annotation, following changes are incorporated in a Manifest file. As you saw, all the changes were auto generated:

    "dataSources": {
        "NorthwindModel": {
            "uri": "/here/goes/your/serviceurl/",
            "type": "OData",
            "settings": {
                "localUri": "localService/metadata.xml",
                "annotations": [
                    "annotation0"
                ]
            }
        },
        "annotation0": {
            "type": "ODataAnnotation",
            "uri": "localService/annotation0.xml",
            "settings": {
                "localUri": "localService/annotation0.xml"
            }
        }
    }
    

    Full Manifest Code

    {
        "_version": "1.12.0",
        "sap.app": {
            "id": "Test.Test",
            "type": "application",
            "i18n": "i18n/i18n.properties",
            "applicationVersion": {
                "version": "1.0.0"
            },
            "title": "{{appTitle}}",
            "description": "{{appDescription}}",
            "sourceTemplate": {
                "id": "servicecatalog.connectivityComponentForManifest",
                "version": "0.0.0"
            },
            "dataSources": {
                "NorthwindModel": {
                    "uri": "/here/goes/your/serviceurl/",
                    "type": "OData",
                    "settings": {
                        "localUri": "localService/metadata.xml",
                        "annotations": [
                            "annotation0"
                        ]
                    }
                },
                "annotation0": {
                    "type": "ODataAnnotation",
                    "uri": "localService/annotation0.xml",
                    "settings": {
                        "localUri": "localService/annotation0.xml"
                    }
                }
            }
        },
        "sap.ui": {
            "technology": "UI5",
            "icons": {
                "icon": "",
                "favIcon": "",
                "phone": "",
                "phone@2": "",
                "tablet": "",
                "tablet@2": ""
            },
            "deviceTypes": {
                "desktop": true,
                "tablet": true,
                "phone": true
            }
        },
        "sap.ui5": {
            "flexEnabled": false,
            "rootView": {
                "viewName": "Test.Test.view.Main",
                "type": "XML",
                "async": true,
                "id": "Main"
            },
            "dependencies": {
                "minUI5Version": "1.65.6",
                "libs": {
                    "sap.ui.layout": {},
                    "sap.ui.core": {},
                    "sap.m": {}
                }
            },
            "contentDensities": {
                "compact": true,
                "cozy": true
            },
            "models": {
                "i18n": {
                    "type": "sap.ui.model.resource.ResourceModel",
                    "settings": {
                        "bundleName": "Test.Test.i18n.i18n"
                    }
                },
                "": {
                    "type": "sap.ui.model.odata.v2.ODataModel",
                    "settings": {
                        "defaultOperationMode": "Server",
                        "defaultBindingMode": "OneWay",
                        "defaultCountMode": "Request"
                    },
                    "dataSource": "NorthwindModel",
                    "preload": true
                },
                "@i18n": {
                    "type": "sap.ui.model.resource.ResourceModel",
                    "uri": "i18n/i18n.properties"
                }
            },
            "resources": {
                "css": [
                    {
                        "uri": "css/style.css"
                    }
                ]
            },
            "routing": {
                "config": {
                    "routerClass": "sap.m.routing.Router",
                    "viewType": "XML",
                    "async": true,
                    "viewPath": "Test.Test.view",
                    "controlAggregation": "pages",
                    "controlId": "app",
                    "clearControlAggregation": false
                },
                "routes": [
                    {
                        "name": "RouteMain",
                        "pattern": "RouteMain",
                        "target": [
                            "TargetMain"
                        ]
                    }
                ],
                "targets": {
                    "TargetMain": {
                        "viewType": "XML",
                        "transition": "slide",
                        "clearControlAggregation": false,
                        "viewId": "Main",
                        "viewName": "Main"
                    }
                }
            }
        }
    }
    

     

  • UI5 Manifest File for Multiple OData Services

    Preface – This post is part of the UI5 Programs series.

    Introduction

    Whenever we create a UI5 application using Fiori Wizard, it creates the full layout of the App by itself. With the layout, it also creates required Manifest.json file with this. Also, it asks for required service, and if provided add a service with an unnamed model within the Manifest file. This model is the default OData model for the full application.
    In many cases, we need multiple OData services for a single App. In that case, it gets important to manually add OData services with named Models. These models have to be accessed via their respective name from controller files. In this article, we will show how to create and use both named and unnamed OData models in UI5.

    Adding OData Services in Manifest

    To access an OData or XSOData in UI5, we follow following three steps:

    1. Add a new service in Manifest
    2. Add a new model in Manifest
    3. Get this Model in Controller

    Add a new service in Manifest

    In the Manifest, you need to an OData Service as shown below:

            "dataSources": {
                "oDataServiceName": {
                    "uri": "<Your Service Name>",
                    "type": "OData",
                    "settings": {
                        "odataVersion": "2.0",
                        "localUri": "<local Metadata>",
    "annotations": [
                            "annotation0"
                        ]
                    }
                }
            }
    

    Here we have added a new OData service and called it “oDataServiceName”. Then we have added the following:

    • uri: This is name of the service. In case it is an ABAP OData service then it looks like this: “/sap/opu/odata/sap/<name_of_OData>;v=0002/”
      And in case it is XSOData and accessed via MTA file, then it looks something like this:
      “../<MTA_Service_Name>r/xsodata/<Name_of_XSODATA>.xsodata/”
    • type: For OData case it is always “OData”
    • settings: This is the place where we provide additional information like:
      • odataVersion: OData is getting updated on regular basis, hence we use the one compatible with UI5 or our requirement
      • localUri: Local URI points to the local service in case we link our OData to a local mock-up data. This is required mainly during development and connect a mock-up data to our service
      • annotations: This is an array of the annotation file attached to a particular service. It is used for adding frontend based annotations. We will discuss this functionality in detail in a different article.

    Add a new model in Manifest

    In the Manifest, you need to an OData Model as shown below:

    "ODataModelName": {
        "type": "sap.ui.model.odata.v2.ODataModel",
        "settings": {
            "defaultOperationMode": "Server",
            "defaultBindingMode": "TwoWay",
            "defaultCountMode": "Request",
            "json": true,
            "defaultUpdateMethod": "PUT",
            "useBatch": false
        },
        "dataSource": " oDataServiceName ",
        "preload": true
    }
    

    Here we have added a new OData model and called it “ODataModelName”. Then we have added the following:

    • type: It is again for specifying the OData Model version. It should be similar to the one we mentioned in service above
    • settings: It is used to add the following relevant configurations:
      • defaultOperationMode: It tells if the required operations (filter/orderby/etc) are executed on server or client. It mostly remains “Server” for OData use case.
      • defaultBindingMode: A binding mode can be “OneWay”, “TwoWay” or “OneTime”. These values mean exactly how the services are called. For Smart operations, we always use “TwoWay”
      • defaultCountMode: It represents if we need to get count separately via a request or via inline count. The values can be: “Inline”, “InlineRepeat”, “None” or “Request”
      • json: It tells the type of Output of this model is JSON or not.
      • defaultUpdateMethod: It tells if we will use “Merge” or “Put” for our update operation.
      • useBatch: It tells if we are using batch operations or not. Batch is OData operation.
    • dataSource: It is where we specify the name of service we have create earlier.
    • preload: It tells, if we need to load this model before it is called or not. Generally, we load all our Models when we open our UI5 App.

    In above use case, it is a named model. You can also create an unnamed model. For that you only need to make the name of the model as “”. Yes, that is blank. In this way it becomes a default model. In full Manifest file below, we have added both types of models.

    Accessing named OData Model via Controller

    Once we have given a name to an OData model (above we have called it “ODataModelName”), then we can access this model in controller via using below code:

    // Binding based on Model defined in Manifest
    var oModel = this.getOwnerComponent().getModel("ODataModelName");
    

    Now this local variable “oModel” can be used to access the model data and bind to whatever table or element you want via controller.

    Accessing default or unnamed OData Model via Controller

    The concept is similar to the named Model, just we don’t mention name of any model here.

    // Binding based on Model defined in Manifest
    var oModel = this.getOwnerComponent().getModel();
    

    Now this local variable “oModel” can be used to access the model data and bind to whatever table or element you want via controller.

    Full Manifest Code

    {
        "_version": "1.12.0",
        "sap.app": {
            "id": "testApp",
            "type": "application",
            "i18n": "i18n/i18n.properties",
            "applicationVersion": {
                "version": "1.0.1"
            },
            "title": "{{appTitle}}",
            "description": "{{appDescription}}",
            "sourceTemplate": {
                "id": "servicecatalog.connectivityComponentForManifest",
                "version": "0.0.0"
            },
            "dataSources": {
                "oDataServiceName": {
                    "uri": "<Your Service Name>",
                    "type": "OData",
                    "settings": {
                        "odataVersion": "2.0",
                        "localUri": "<local Metadata>"
                    }
                },
                "oDataDefaultServiceName": {
                    "uri": "<Your Service Name>",
                    "type": "OData",
                    "settings": {
                        "odataVersion": "2.0",
                        "localUri": "<local Metadata>"
                    }
                }
            }
        },
        "sap.ui": {
            "technology": "UI5",
            "icons": {
                "icon": "",
                "favIcon": "",
                "phone": "",
                "phone@2": "",
                "tablet": "",
                "tablet@2": ""
            },
            "deviceTypes": {
                "desktop": true,
                "tablet": true,
                "phone": true
            }
        },
        "sap.ui5": {
            "flexEnabled": false,
            "dependencies": {
                "minUI5Version": "1.65.6",
                "libs": {
                    "sap.ui.layout": {},
                    "sap.ui.core": {},
                    "sap.m": {}
                }
            },
            "contentDensities": {
                "compact": true,
                "cozy": true
            },
            "models": {
                "i18n": {
                    "type": "sap.ui.model.resource.ResourceModel",
                    "settings": {
                        "bundleName": "testApp.i18n.i18n"
                    }
                },
                "@i18n": {
                    "type": "sap.ui.model.resource.ResourceModel",
                    "uri": "i18n/i18n.properties"
                },
                "ODataModelName": {
                    "type": "sap.ui.model.odata.v2.ODataModel",
                    "settings": {
                        "defaultOperationMode": "Server",
                        "defaultBindingMode": "TwoWay",
                        "defaultCountMode": "Request",
                        "json": true,
                        "defaultUpdateMethod": "PUT",
                        "useBatch": false
                    },
                    "dataSource": "oDataServiceName",
                    "preload": true
                },
                "": {
                    "type": "sap.ui.model.odata.v2.ODataModel",
                    "settings": {
                        "defaultOperationMode": "Server",
                        "defaultBindingMode": "TwoWay",
                        "defaultCountMode": "Request",
                        "json": true,
                        "defaultUpdateMethod": "PUT",
                        "useBatch": false
                    },
                    "dataSource": "oDataDefaultServiceName",
                    "preload": true
                }
    
            },
            "resources": {
                "css": [{
                    "uri": "css/style.css"
                }]
            },
            "rootView": {
                "viewName": "testApp.view.App",
                "type": "XML",
                "id": ""
            },
            "routing": {
                "config": {
                    "routerClass": "sap.m.routing.Router",
                    "viewPath": "testApp.view",
                    "controlId": "app",
                    "bypassed": {
                        "target": ["NotFound"]
                    }
                },
                "routes": [{
                    "name": "Tab_1",
                    "pattern": "",
                    "target": ["masterTarget", "Tab_1"]
                }, {
                    "name": "Tab_2",
                    "pattern": "Tab_2",
                    "target": ["masterTarget", "Tab_2"]
                }],
                "targets": {
                    "masterTarget": {
                        "viewType": "XML",
                        "transition": "slide",
                        "controlAggregation": "masterPages",
                        "viewId": "idMaster",
                        "viewName": "Master",
                        "viewLevel": 1,
                        "controlId": "Splitapp"
                    },
                    "Tab_1": {
                        "viewType": "XML",
                        "transition": "slide",
                        "controlAggregation": "detailPages",
                        "viewId": "idTab_1",
                        "viewName": "Tab_1",
                        "viewLevel": 1,
                        "controlId": "Splitapp"
                    },
                    "Tab_2": {
                        "viewType": "XML",
                        "transition": "slide",
                        "controlAggregation": "detailPages",
                        "viewId": "idTab_2",
                        "viewName": "Tab_2",
                        "viewLevel": 2,
                        "controlId": "Splitapp"
                    }
                }
            }
        },
        "sap.platform.hcp": {
            "uri": "webapp",
            "_version": "1.1.0"
        }
    }
    

     

  • Manifest File for Routing for Master Details (SplitApp)

    Preface – This post is part of the UI5 Programs series.

    Introduction

    Sometime in UI5 it is required to make a Master Detail Application, which looks something like this:

    UI5 Manifest for SplitAppThere are two ways in which we can achieve the above layout:

    1. By using Fragments for each Master tab pages (with single controller)
    2. By using Individual View for each Master tabs (with individual controller)

    In case, you are planning for a very small App, then go with the first use case, else choose second one.

    For the first use case, the navigation among tabs will work as mentioned below:

    this.getView().byId("<ID of NavContainer>").to(this.getView().byId("<Page_ID of the tab>"));

    In the above statement, we get the required Page ID (this page can have a fragment), and then open that page. All the pages are defined within single view. To ease the coding and visibility of the code, we divide each page into fragments. Still in case of a big project, this is not a recommended way.

    For the second use case, we will divide the code into given three steps:

    1. App View Code: The App is defined as SplitApp here.
    2. Manifest Code: The Navigation is divided among Master and Detail using target
    3. Master Section: This section contains a tnt:NavigationList which has the same key as the name of the views

    Changes in App View File

    When we create a blank UI5 Application, it is create with a blank view and controller. It is important to create a App view on top of these Individual views for pages, so that it can act as a root view for others. SAP recommends to have this file as a root view.
    For our requirement, the App will have following code:

    App.view.xml

    <mvc:View controllerName="testApp.controller.App" xmlns:mvc="sap.ui.core.mvc" displayBlock="true" xmlns="sap.m">
        <Shell id="shell">
            <App id="app"> 
                <pages>
                    <!--Split App to Enable Master Detail Configuration-->
                    <SplitApp id="Splitapp" mode="ShowHideMode"></SplitApp>
                </pages>
            </App>
        </Shell>
    </mvc:View>
    

     

    App.controller.js

    sap.ui.define([
        "sap/ui/core/mvc/Controller"
    ], function (Controller) {
        "use strict";
    
        return Controller.extend("testApp.controller.App", {
            onInit: function () {
     
            }
        });
    });
    

     

    Creation of Master View

    Till now, we have created a Split App. Now it is required to create a separate Master Page which will just hold all the navigation tabs as shown below:

    Master.view.xml

    <mvc:View xmlns:core="sap.ui.core" xmlns:mvc="sap.ui.core.mvc" xmlns="sap.m" controllerName="testApp.controller.Master"
        xmlns:html="http://www.w3.org/1999/xhtml" xmlns:tnt="sap.tnt">
        <Page id="master" title="{i18n>Actions}" backgroundDesign="List" class="sapUiStdPage">
            <!--All Master Pane tabs are maintained here-->
            <tnt:NavigationList id="navigationList" selectedKey="Tab_1" itemSelect="onListItemPress">
                <tnt:NavigationListItem id="MasterData" text="{i18n>Tab_1}" icon="sap-icon://org-chart">
                    <tnt:NavigationListItem text="{i18n>Tab_1_1}" key="Tab_1_1"/>
                    <tnt:NavigationListItem text="{i18n>Tab_1_2}" key="Tab_1_2"/>
                    <tnt:NavigationListItem text="{i18n>Tab_1_3}" id="subItemThree" key="Tab_1_3"/>
                </tnt:NavigationListItem>
                <tnt:NavigationListItem text="{i18n>Tab_2}" icon="sap-icon://leads" key="Tab_2"></tnt:NavigationListItem>
            </tnt:NavigationList>
        </Page>
    </mvc:View>
    

     

    Master.controller.js

    /**
     * onListItemPress is invoked on click from UI. 
     * Input: Key Maintained in Master View
     * Output: Navigation to the Master Page
     */
    onListItemPress: function (oEvent) {
        var that = this;
        var sTimeOutErrorMsg = this.oBundle.getText("TimeOutError");
        var sError;
        var sToPageId = oEvent.getParameter("item").getKey();
        if (sToPageId) {
            try {
                this.oRouter.navTo(sToPageId);
            } catch (err) {
                if (err.statusCode === 401) {
                    sError = sTimeOutErrorMsg;
                } else {
                    sError = err.error;
                }
                MessageBox.error(sError);
                window.location.href = '../logout';
            }
        }
    }
    

     

    Changes in Manifest File

    Changes in Manifest File

    Here we will do two changes:

    1. Add Routes: It specifies the pattern, name of the route and the target. The pattern specifies what will be visible after the url. Like in below example, pattern will look something like this in URL: test.com/worklist
    "routes": [{
        "pattern": "Tab_1",
        "name": "Tab_1",
        "target": [
            "masterTarget",
            "Tab_1"
        ]
    }]
    

     

    Sometimes, it is required to pass some values too during navigation, from one page to other. In that case, the routes looks something like below. Here we are passing an ID during navigation. This ID looks something like this in URL: www.test.com/overview/{Id}

    In case, passed ID is 10, then the URL will look like this: www.test.com/overview/10

    "routes": [{
        "pattern": "Tab_1/{Id}",
        "name": "Tab_1",
        "target": [
            "masterTarget"
                             "Tab_1"
        ]
    }]
    

     

    *Note: As you can see, we have added double targets here. The first one keeps the Master View always open and the second one will navigate to a view called Tab_1.

    1. Add Targets: Once we have added routes, each route point to a particular target. This target actually points out the right file for navigation. Here we will use the first route that we discussed above to reach our target view. The viewName mentioned below is actually pointing to the view that will open up, after navigation is performed. For both of the above routes, targets will look something like below, only the name, title and IDs will change.
    "targets": {
                            "masterTarget": {
            "viewType": "XML",
            "transition": "slide",
            "controlAggregation": "masterPages",
            "viewId": "idMaster",
            "viewName": "Master",
            "viewLevel": 1,
            "controlId": "Splitapp"
        },
                    "Tab_1": {
            "viewType": "XML",
            "transition": "slide",
            "controlAggregation": "detailPages",
            "viewId": "idTab_1",
            "viewName": "Tab_1",
            "viewLevel": 2,
            "controlId": "Splitapp"
        }
    }
    

     

    Note: In the above targets, the controlAggregation decides which type of page it is. Also the controlId remains same for all the targets.

    Full Manifest Code

    {
        "_version": "1.12.0",
        "sap.app": {
            "id": "testApp",
            "type": "application",
            "i18n": "i18n/i18n.properties",
            "applicationVersion": {
                "version": "1.0.1"
            },
            "title": "{{appTitle}}",
            "description": "{{appDescription}}",
            "sourceTemplate": {
                "id": "servicecatalog.connectivityComponentForManifest",
                "version": "0.0.0"
            },
            "dataSources": {
                "oDataService": {
                    "uri": "<Your Service Name>",
                    "type": "OData",
                    "settings": {
                        "odataVersion": "2.0",
                        "localUri": "<local Metadata>"
                    }
                }
            }
        },
        "sap.ui": {
            "technology": "UI5",
            "icons": {
                "icon": "",
                "favIcon": "",
                "phone": "",
                "phone@2": "",
                "tablet": "",
                "tablet@2": ""
            },
            "deviceTypes": {
                "desktop": true,
                "tablet": true,
                "phone": true
            }
        },
        "sap.ui5": {
            "flexEnabled": false,
            "dependencies": {
                "minUI5Version": "1.65.6",
                "libs": {
                    "sap.ui.layout": {},
                    "sap.ui.core": {},
                    "sap.m": {}
                }
            },
            "contentDensities": {
                "compact": true,
                "cozy": true
            },
            "models": {
                "i18n": {
                    "type": "sap.ui.model.resource.ResourceModel",
                    "settings": {
                        "bundleName": "testApp.i18n.i18n"
                    }
                },
                "@i18n": {
                    "type": "sap.ui.model.resource.ResourceModel",
                    "uri": "i18n/i18n.properties"
                },
                "ODataModel": {
                    "type": "sap.ui.model.odata.v2.ODataModel",
                    "settings": {
                        "defaultOperationMode": "Server",
                        "defaultBindingMode": "TwoWay",
                        "defaultCountMode": "Request",
                        "json": true,
                        "defaultUpdateMethod": "PUT",
                        "useBatch": false
                    },
                    "dataSource": "oDataService",
                    "preload": true
                }
            },
            "resources": {
                "css": [{
                    "uri": "css/style.css"
                }]
            },
            "rootView": {
                "viewName": "testApp.view.App",
                "type": "XML",
                "id": ""
            },
            "routing": {
                "config": {
                    "routerClass": "sap.m.routing.Router",
                    "viewPath": "testApp.view",
                    "controlId": "app",
                    "bypassed": {
                        "target": ["NotFound"]
                    }
                },
                "routes": [{
                    "name": "Tab_1",
                    "pattern": "",
                    "target": ["masterTarget", "Tab_1"]
                }, {
                    "name": "Tab_2",
                    "pattern": "Tab_2",
                    "target": ["masterTarget", "Tab_2"]
                }],
                "targets": {
                    "masterTarget": {
                        "viewType": "XML",
                        "transition": "slide",
                        "controlAggregation": "masterPages",
                        "viewId": "idMaster",
                        "viewName": "Master",
                        "viewLevel": 1,
                        "controlId": "Splitapp"
                    },
                    "Tab_1": {
                        "viewType": "XML",
                        "transition": "slide",
                        "controlAggregation": "detailPages",
                        "viewId": "idTab_1",
                        "viewName": "Tab_1",
                        "viewLevel": 1,
                        "controlId": "Splitapp"
                    },
                    "Tab_2": {
                        "viewType": "XML",
                        "transition": "slide",
                        "controlAggregation": "detailPages",
                        "viewId": "idTab_2",
                        "viewName": "Tab_2",
                        "viewLevel": 2,
                        "controlId": "Splitapp"
                    }
                }
            }
        },
        "sap.platform.hcp": {
            "uri": "webapp",
            "_version": "1.1.0"
        }
    }
    

     

     

  • File Routing in UI5

    Preface – This post is part of the UI5 Programs series.

    File Routing in UI5

    SAP UI5 uses Routing concept to navigate among views (Pages). To enable routing, one needs to do specific changes in Manifest file and then perform navigation wherever required. In this article we will show both of these changes in detail.

    How to perform File Routing in UI5

    Changes in Manifest File

    The very first change is required in Manifest file, here we will do two changes:

    1. Add Routes: It specifies the pattern, name of the route and the target. The pattern specifies what will be visible after the url. Like in below example, pattern will look something like this in URL: test.com/worklist
    "routes": [{
        "pattern": "worklist",
        "name": "worklist",
        "target": [
            "worklist"
        ]
    }]
    

    Sometimes, it is required to pass some values too during navigation, from one page to other. In that case, the routes looks something like below. Here we are passing an ID during navigation. This ID looks something like this in URL: www.test.com/overview/{Id}

    In case, passed ID is 10, then the URL will look like this: www.test.com/overview/10

    "routes": [{
        "pattern": "overview/{Id}",
        "name": "overView",
        "target": [
            "overView"
        ]
    }]
    
    1. Add Targets: Once we have added routes, each route point to a particular target. This target actually points out the right file for navigation. Here we will use the first route that we discussed above to reach our target view. The viewName mentioned below is actually pointing to the view that will open up, after navigation is performed. For both of the above routes, targets will look something like below, only the name, title and IDs will change.
    "targets": {
        "worklist": {
            "viewName": "Worklist",
            "viewId": "worklist",
            "viewLevel": 1,
            "title": "{i18n>worklistViewTitle}"
        } }
    

    Changes in Controller File

    Once we have specified all our routes and target based on correct views, then we can perform navigation on click of a button or any elements. For that, on click a function is called from view. That function loads all the routing related configuration and then uses the name of the route to perform navigation.

    For both of the above discussed routes, following are the codes;

    1. Simple Navigation

      // Get Router Info
      this.oRouter = this.getOwnerComponent().getRouter();
      this.oRouter.navTo("worklist ");
      
    2. Navigation with a value

      // Get Router Info
      this.oRouter = this.getOwnerComponent().getRouter();
      this.oRouter navTo("overview ", {
              Id: <to what ever value you might have fetched for navigation>
          });
      

    Full Manifest Example

    {
            "_version": "1.7.0",
            "sap.app": {
                "id": "${project.artifactId}",
                "type": "application",
                "resources": "resources.json",
                "i18n": "i18n/i18n.properties",
                "title": "{{appTitle}}",
                "description": "{{appDescription}}",
                "applicationVersion": {
                    "version": "${project.version}"
                },
                "ach": "set-ach",
                "dataSources": {
                    "mainService": {
                        "uri": <your_service>;v=0002/",
                        "type": "OData",
                        "settings": {
                            "odataVersion": "2.0",
                            "localUri": "localService/metadata.xml",
                            "annotations": [
                                "annotation0"
                            ]
                        }
                    },
                    "annotation0": {
                        "type": "ODataAnnotation",
                        "uri": "annotations/annotation0.xml",
                        "settings": {
                            "localUri": "annotations/annotation0.xml"
                        }
                    }
                },
                "sourceTemplate": {
                    "id": "sap.ui.ui5-template-plugin.1worklist",
                    "version": "1.58.1"
                }
            },
            "sap.fiori": {
                "registrationIds": [],
                "archeType": "transactional"
            },
            "sap.ui": {
                "technology": "UI5",
                "icons": {
                    "icon": "sap-icon://task",
                    "favIcon": "",
                    "phone": "",
                    "phone@2": "",
                    "tablet": "",
                    "tablet@2": ""
                },
                "deviceTypes": {
                    "desktop": true,
                    "tablet": true,
                    "phone": true
                }
            },
            "sap.ui5": {
                "resources": {
                    "js": [],
                    "css": [{
                        "uri": "css/style.css"
                    }]
                },
                "rootView": {
                    "viewName": "Test.TestApp.view.App",
                    "type": "XML",
                    "async": true,
                    "id": "app"
                },
                "dependencies": {
                    "minUI5Version": "${sap.ui5.dist.version}",
                    "libs": {
                        "sap.ui.core": {},
                        "sap.m": {},
                        "sap.f": {},
                        "sap.ushell": {},
                        "sap.collaboration": {
                            "lazy": true
                        }
                    }
                },
                "contentDensities": {
                    "compact": true,
                    "cozy": true
                },
                "models": {
                    "i18n": {
                        "type": "sap.ui.model.resource.ResourceModel",
                        "settings": {
                            "bundleName": "Test.TestApp.i18n.i18n"
                        }
                    },
                    "": {
                        "dataSource": "mainService",
                        "settings": {
                            "defaultCountMode": "InlineRepeat"
                        },
                        "preload": false
                    },
                    "@i18n": {
                        "type": "sap.ui.model.resource.ResourceModel",
                        "uri": "i18n/i18n.properties"
                    }
                },
                "services": {
                    "ShellUIService": {
                        "factoryName": "sap.ushell.ui5service.ShellUIService",
                        "lazy": false,
                        "settings": {
                            "setTitle": "auto"
                        }
                    }
                },
                "routing": {
                    "config": {
                        "routerClass": "sap.m.routing.Router",
                        "viewType": "XML",
                        "viewPath": "Test.TestApp.view",
                        "controlId": "app",
                        "controlAggregation": "pages",
                        "bypassed": {
                            "target": [
                                "notFound"
                            ]
                        },
                        "async": true
                    },
                    "routes": [{
                        "pattern": "",
                        "name": "worklist",
                        "target": [
                            "worklist"
                        ]
                    }, {
                        "pattern": "CustomerDataEntitySet/{objectId}",
                        "name": "object",
                        "target": [
                            "object"
                        ]
                    }, {
                        "pattern": "CaseOverView/{ID1}/{ID2}",
                        "name": "overView",
                        "target": [
                            "overView"
                        ]
                    }],
                    "targets": {
                        "worklist": {
                            "viewName": "Worklist",
                            "viewId": "worklist",
                            "viewLevel": 1,
                            "title": "{i18n>worklistViewTitle}"
                        },
                        "object": {
                            "viewName": "Object",
                            "viewId": "object",
                            "viewLevel": 2,
                            "title": "{i18n>objectViewTitle}"
                        },
                        "overView": {
                            "viewName": "Overview",
                            "viewLevel": 3
                        },
                        "objectNotFound": {
                            "viewName": "ObjectNotFound",
                            "viewId": "objectNotFound"
                        },
                        "notFound": {
                            "viewName": "NotFound",
                            "viewId": "notFound"
                        },
                        "Overview": {
                            "viewType": "XML",
                            "viewName": "Overview"
                        }
                    }
                }
            }
        }
    

     

    Explanation

    • In case you plan to have the very first view like worklist above, then you don’t need to provide any pattern. It will open with initial URL of the website.
    • In case you want to fix what view will open initially, then you can define that as rootView above. In this particular case we initially launch the whole site via an App. Thus App is set as a root view. This App ultimately loads the views within itself.