Technology Blogs by SAP
Learn how to extend and personalize SAP applications. Follow the SAP technology blog for insights into SAP BTP, ABAP, SAP Analytics Cloud, SAP HANA, and more.
cancel
Showing results for 
Search instead for 
Did you mean: 

If somebody asks you just right now "What do you require most from your business software in general?", what will pop up in your mind first?

To be ...reliable, secure, feature rich? ... Cheaper? – Noo!...

What about  being configurable, flexible, or extendable?

Well, it is clear that for business applications, in comparison to other types of

software bundles like HTTP Servers, Databases, Office Packages, etc., it is vital to be adaptable to the particular business processes within a given company.

How much effort do businesses usually spend for customizations? What is the cost of that? What is the timeframe?

Sounds familiar? Then we are on the right track.

How does the product owner usually imagine the adaptable software?

As a single generic screen, fully "responsive" and depending on the user's type, role, use-case, and any other parameter one can imagine. On the other side it results in thousands of configuration entries.

This works! We proudly have enough examples based on this approach.

The product owner is on the safe side - "reusing"! He/she doesn't make any "double efforts"!

Unfortunately, there are several issues with this approach:

- Relatively big effort to develop such a highly configurable screen

- Relatively big effort to configure it - per user, role, etc.

- Relatively big effort to change it once being shipped

- Relatively big effort to support it

- Relatively low performance

- ...  And the saddest part is that, usually, it becomes equally complex and difficult for everyone.

(Disclaimer: Everything mentioned above is intentionally exaggerated.)

What to do then?

The other option is to build tailored screens and underlying services for a given role.

Can we build multiple end-to-end vertical stacks easily and fast with Dirigible on SAP HANA Cloud Platform?

You already know that, so we can finish the article now.

Unfortunately, this is not so simple either.

What if we want to reuse the services provided by the vendor of the packaged software,

and want to add just a few fields, but in the way to survive the next update/upgrade of the basis?

You would like to have something like a BAdI or Eclipse plugin mechanisms, right?

Let’s see whether the extensibility feature we have recently added to Dirigible can supply such a need...

To explore the capabilities of extensions in Dirigible, we will create two projects – “products” and “products_discounter”. The first project will be the one with the extension point and the second will be with the extension. The “products” project will display information about products

and the “products_discounter” will show the discounted items.

The final result will be:

In the “products” project we will create

  • Database table for products
  • Delimiter Separated Values (DSV) file that will provide sample data for the table
  • Discounts extension point
  • Menu extension point
  • RESTfull scripting service
  • Menu scripting service
  • UI

In the “products_discounter” project we will create

  • Database table for discounted products
  • Delimiter Separated Values (DSV) file with sample data
  • Discounts extension
  • Menu extension
  • Discounts extension implementation
  • Menu extension implementation
  • UI

We have plenty of work, so let’s get started.


  • First, create a new blank project "products".

  • Then, add a  new database table "products.table"

  • The easiest way to supply data for the newly created table is to go to the “DataStructures” folder and create a new file - "products.dsv". Enter the following records:

1|Dumbbell|100|http://www.aloyd.com/home/media/catalog/product/cache/1/image/9df78eab33525d08d6e5fb8d27136e95/r/u/rubber-hex-dumbbell.jpg
2|Leg extension|155|http://www.pacillo.com/products/kf_leg_extension_b.jpg
3|Squad rack|153|http://images.monstermarketplace.com/gym-flooring-and-fitness-equipment/valor-bd-19-sawtooth-squat-rack-bench-press-combo-600x600.jpg
4|Shoulder press|300|http://gymstogo.com/commercial-gym-equipment/plate-loaded/precor-fitness/shoulder-press--550-discovery-line.jpg




  • Now we want to get a discount for some “products”. This will be done through extension point. Create a new extension point in the “ExtensionDefinitions “ folder and give it a name of "discounted_products.extensionpoint".

It should look like this:

  • We want our final version to have a menu for navigation through different tabs. Also we want the UI for the discounted products to be provided by other project. For that purpose, we will add a new "Extension Point" - "products_menu.extensionpoint". It will look like this:

  • “Activate” and “Publish” the project.

  • Let's expose our table through “ScriptingService”. Choose “Entity Service on Table” and from the list find our "products" table. Give it the name  “products.js”.

  • Expand “ScriptingServices” folder and open "products.jslib".
  • Navigate to the "exports.readProductsList" function and add the following lines.

var extensions = extensionManager.getExtensions("/products/discounted_products");
for (var i=0;i<extensions.length;i++) {
    var extension = require(extensions[i]);
    result = extension.getDiscounts();
}





It should look like this:


  • The menu for “products” project will be provided through “ScriptingService”. Create a new file into "ScriptingService" folder and name it "menu.js".
  • Copy and paste inside the following lines:

var menu = [
    {
        "name": "Products",
        "link": "../../web/products/products.html",
        "subMenu": []
    }
];
var extensions = extensionManager.getExtensions("/products/products_menu");
for (var i=0;i<extensions.length;i++) {
    var extension = require(extensions[i]);
    menu = extension.getMenuItems(menu);
}

response.setContentType("text/json");
  response.getWriter().println(JSON.stringify(menu));

  • Now we will create the UI for our products - "products.html" in “WebContent” folder. Use the following code:

<!DOCTYPE html>
<html lang="en" ng-app>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="">
<meta name="author" content="">
<title>Home</title>
<link rel="stylesheet"
href="../../components/bootstrap/dist/css/bootstrap.min.css">
</head>
<body>
    <div id="wrap">
          <div class="container">
              <div ng-controller="ListController">
                    <div class="col-lg-3 col-md-6 hero-feature ng-scope" ng-repeat="entry in data">
                        <a class="thumbnail btn btn-default" href="#/content" target="_self">
                              <img src="{{entry.image}}">
                              <div class="caption">
                                  <h3 class="ng-binding">{{entry.name}}</h3>
                                  <p>$ {{entry.price}}</p>
                              </div>
                        </a>
                    </div>
              </div>
          </div>
    </div>
    <script src="../../components/angular/angular.min.js"></script>
    <script src="../../components/angular-resource/angular-resource.min.js"></script>
    <script type="text/javascript">
          function ListController($scope, $http) {
              var url = '/dirigible/js/products/products.js';
              $http.get(url)
                    .success(function(data) {
                        $scope.data = data;
                    });
          }
    </script>
</body>
</html>






  • To make our project complete, we need to add a menu for navigation between different tabs. Create a new "User Interface" - "Index Page with Main Menu, Header, and Footer".

  • Leave the default file name ("index.html"), and for the title enter "Products".

So far, so good. To complete the UI part in the “products” project we need to make one last change.

  • Open "footer.html" and replace

$.getJSON("main.menu", function(data) {





with


$.getJSON("../../js/products/menu.js", function(data) {





  • Apply the same change in “index.html”, as well.
  • “Activate” and “Publish” the project.

Now we are ready with the main project.

Let’s add an extension project that will provide the discounted products.


  • Create a new "Blank project" and name it "products_discounter".
  • In this project, we need a table with the discounted products. Create a new "Database table". It looks like this:

  • Name it "discounted_products.table".
  • Create a file “discounted_products.dsv" into the "DataStructures" folder and enter some data.

1|0.25
2|0.40





  • Create "products_discounter.extension" extension. The value for "extension-point" should be "/products/discounted_products" – the extension point from “products” project.


  • For the menu, we need to create a new "Extension" - "products_menu_provider.extension".
  • For the target "extension-point", set "/products/products_menu".

  • Go to "ScriptingServices" folder and create a new file named "products_discounter.jslib". Here will be the implementation for the “products_discounter” extension. Place inside the following code:

function createEntity(resultSet, data) {
    var result = {};
    result.id = resultSet.getInt("ID");
    result.name = resultSet.getString("NAME");
    result.image = resultSet.getString("IMAGE");
    result.price = resultSet.getString("PRICE");
    var discount = resultSet.getString("DISCOUNT");
    if(discount !== null && discount > 0 ){
        result.discount_price = result.price - (result.price * discount);
    }
    return result;
}

exports.getDiscounts = function() {
    var discounts = [];
    var connection = datasource.getConnection();
    try {
        var sql = "select p.id, p.name, p.image, p.price, d.discount from products p left join discounted_products d on p.id = d.product_id order by d.discount";
        var statement = connection.prepareStatement(sql);
        var resultSet = statement.executeQuery();
        var value;
        while (resultSet.next()) {
                discounts.push(createEntity(resultSet));
        }
    } catch(e){
       
    } finally {
        connection.close();
        return discounts;
    }
};




  • To finish with the extensions, we need to provide logic for the “products_menu_provider” extension. Create a new file in "ScriptingService" folder with name "products_menu_provider.jslib".
  • Place inside the following code:

exports.getMenuItems = function(menu) {
    for(var i = 0 ; i < menu.length; i ++ ){
        if(menu[i].name == "Products"){
            menu[i].link = "../../web/products_discounter/discounted.html";
        }
    }
    return menu;
};




Last but not least, let's add some UI.

  • In the "WebContent" folder, create a file named "discounted.html".
  • Copy and paste the following code:

<!DOCTYPE html>
<html lang="en" ng-app>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="">
<meta name="author" content="">
<title>Home</title>
<link rel="stylesheet" href="../../components/bootstrap/dist/css/bootstrap.min.css">
<style>
  h1,h2,h3,h4,h5,h6,p {
    white-space: normal;
  }
  .discounted{
    border-style: solid;
    border-color: red;
    border-radius: 10px;
  }
  .discounted:hover
  {
    background-color: red;
  }
  .discounted:hover h3 {
    color:white;
  }
  .discounted:hover span {
    color:white;
  }
  .old-price {
    text-decoration:line-through;
    font-size: 50%
  }
  .new-price {
    color: red;
    font-weight: bold;
  }
</style>
</head>
<body>
  <div id="wrap">
  <div class="container">
        <div ng-controller="ListController">
          <div class="col-lg-3 col-md-3 col-sm-4 col-xs-6 hero-feature ng-scope" ng-repeat="entry in data">
    <a class="thumbnail btn btn-default" ng-class="{discounted: entry.discount_price}" href="#/content" target="_self">
              <img src="{{entry.image}}">
    <div class="caption">
  <h3 class="ng-binding">{{entry.name}}</h3>
  <h3 ng-hide="entry.discount_price" >$ {{entry.price}}</h3>
                <div ng-show="entry.discount_price">
        <h3><span class="old-price">$ {{entry.price}}</span> <span class="new-price">$ {{entry.discount_price}}</span></h3>
                </div>
  </div>
      </a>
      </div>
        </div>
  </div>
  </div>
  <script src="../../components/angular/angular.min.js"></script>
  <script src="../../components/angular-resource/angular-resource.min.js"></script>
  <script type="text/javascript">
  function ListController($scope, $http) {
    var url = '/dirigible/js/products/products.js';
  $http.get(url)
  .success(function(data) {
      $scope.data = data;
  });
  }
  </script>
</body>
</html>




Because the page with discounted products is in another project wherethere is neither "header.html" nor "footer.html", a menu is lacking, too. The first approach is just to copy and paste them, but this is not a good solution. We don't want double maintenance of our code, we just want to reuse the "header" and "footer" files. For that purpose, we’ll make little magic in the “products_discounter” project.

  • In the "WebContent" folder, create "header.ref" and "footer.ref" files. Yes we will just reference the original "header" and "footer".
  • In the "header.ref" file, enter:


/products/header.html




  • In the "footer.ref" file, enter:


/products/footer.html




  • “Activate” and “Publish” the project.
  • Now click on "discounted.html" and see the result in the "Web Viewer" tab.

Finally we are done. We have reached our goal! :smile:

Do you think this approach can be helpful for your extensibility story ... or you still prefer the "configurations" way?

References:

The project site: http://www.dirigible.io

The source code is available at GitHub - http://github.com/SAP/cloud-dirigible

Forum: http://forum.dirigible.io

Twitter: https://twitter.com/dirigible_io

Youtube: https://www.youtube.com/channel/UCYnsiVQ0M9iQLqP5DXCLMBA/

Help: http://help.dirigible.io

Samples: http://samples.dirigible.io

Google Group: https://plus.google.com/111171361109860650442/posts

Blog: http://dirigible-logbook.blogspot.com/

Dirigible on SAP HANA Cloud Platform:Dirigible on SAP HANA Cloud Platform

1 Comment