Quantcast
Channel: SAPUI5 Developer Center
Viewing all 789 articles
Browse latest View live

How to create Smart Templates annotations within CDS views - part 3

$
0
0

Introduction

This is the continuation of the blog started here!


This blog has been split in 3 parts:

LinkContent
How to create Smart Templates annotations within CDS views - part 1Development tool installation and DDL Source creation
How to create Smart Templates annotations within CDS views - part 2Service registration and Smart Template creation in the NW Gateway and consumed by Smart Templates
This partEnhancing the annotation

 

Step 6 - Enhancing the annotation

In this last part of the blog we are going to see how to enrich and improve the CDS view with more annotation terms in order to complete our sample application.

If you have followed the two previous parts you should have come now to a point where the application starts, but it doesn't show anything  yet.

The first useful thing that we want to add to our CDS view is the UI.LineItem annotation term to show a list of all the products when the application starts. This can be done by specifying in the CDS view, before each field we want to see in the grid, the position where we want to see it and its importance.

 

1 - Open first SAP Web IDE and open the existing annotation file with the AM. In this way we can check step by step how the annotation changes

40.png

 

2 - Open Eclipse

 

3 - Open the CDS view Z_SAMPLE_DEMO_PRODUCTS and change the code to be the following

 

@AbapCatalog.sqlViewName:'Z_VW_PRODUCTS'

@AbapCatalog.compiler.compareFilter:true

@AccessControl.authorizationCheck:#CHECK

@EndUserText.label:'Products'

@OData.publish:true

 

@UI.headerInfo:{

    typeName:'Product',

    typeNamePlural:'Products',

    imageUrl:{value:'PictureUrl'},

    title:{value:'Name'},

    description:{value:'Description'}

}

 

defineview Z_Sample_Demo_Products

  asselectfrom sepm_iproduct as Products

  association[1..*]to Z_Sample_Demo_Soli as _SOItems  on  $projection.productuuid = _SOItems.ProductID

  association[1..1]to sepm_iproductt    as _ProductT on  $projection.productuuid = _ProductT.productuuid

                                                        and _ProductT.language      ='E'

  association[1..1]to sepm_ibupa        as _Supplier on  $projection.supplieruuid = _Supplier.businesspartneruuid

{

  key Products.productuuid,

      @UI:{

        lineItem:{position:10,importance:#HIGH}

      }

      Products.product                                                      as ProductID,

      Products.producttype                                                  as TypeCode,

      @UI:{

        lineItem:{position:30,importance:#HIGH}

      }

      Products.productcategory                                              as Category,

      @UI:{

        lineItem:{position:20,importance:#HIGH,label:'Product Name'}

      }

      _ProductT.productname                                                  as Name,

      'EN'                                                                  as NameLanguage,

      @UI:{

        lineItem:{position:40,importance:#HIGH,label:'Prod.Descrip.'}

      }

      _ProductT.productdescription                                          as Description,

      'EN'                                                                  as DescriptionLanguage,

      _Supplier.businesspartner                                              as SupplierID,

      _Supplier.companyname                                                  as SupplierName,

      Products.productvalueaddedtax                                          as TaxTarifCode,

      Products.productbaseunit                                              as MeasureUnit,

      Products.weight                                                        as WeightMeasure,

      Products.weightunit                                                    as WeightUnit,

      Products.currency                                                      as CurrencyCode,

      @UI:{

        lineItem:{position:50,importance:#HIGH}

      }

      Products.price                                                        as Price,

      Products.width                                                        as Width,

      Products.depth                                                        as Depth,

      Products.height                                                        as Height,

      Products.dimensionunit                                                as DimUnit,

      Products.creationdatetime                                              as CreatedAt,

      Products.lastchangeddatetime                                          as ChangedAt,

      concat(concat('/webapp/images/',Products.product),'.jpg')              as PictureUrl,

      Products.supplieruuid,

      _SOItems

}

 

3 - Save and activate

 

4 - If you refresh the annotation file in SAP Web IDE you should see that a new UI.LineItem annotation term has been added to it in disabled mode, because it's coming directly from the server

41.png

 

5 - Let's add now some selection fields: we want to use them to filter records in the application. The annotation term we need to add is UI.SelectionField and since we want to filter by  ProductID and Category, we need to add it just before these two fields in the view, like the following

42.png


6 - Save and activate. Refreshing the annotation, now you should see this

43.png


7 - We can make this view a little bit more complex by adding the UI.Identification term. This can be done at the same way we did for the the UI.LineItem. The view becomes:

 

@AbapCatalog.sqlViewName:'Z_VW_PRODUCTS'

@AbapCatalog.compiler.compareFilter:true

@AccessControl.authorizationCheck:#CHECK

@EndUserText.label:'Products'

@OData.publish:true

 

@UI.headerInfo:{

    typeName:'Product',

    typeNamePlural:'Products',

    imageUrl:{value:'PictureUrl'},

    title:{value:'Name'},

    description:{value:'Description'}

}

 

defineview Z_Sample_Demo_Products

  asselectfrom sepm_iproduct as Products

  association[1..*]to Z_Sample_Demo_Soli as _SOItems  on  $projection.productuuid = _SOItems.ProductID

  association[1..1]to sepm_iproductt    as _ProductT on  $projection.productuuid = _ProductT.productuuid

                                                        and _ProductT.language      ='E'

  association[1..1]to sepm_ibupa        as _Supplier on  $projection.supplieruuid = _Supplier.businesspartneruuid

{

  key Products.productuuid,

      @UI:{

        identification:{position:10,importance:#HIGH},

        lineItem:{position:10,importance:#HIGH},

        selectionField:{position:10}

      }

      Products.product                                                      as ProductID,

      @UI:{

        identification:{position:40,importance:#HIGH}

      }

      Products.producttype                                                  as TypeCode,

      @UI:{

        identification:{position:30,importance:#HIGH},

        lineItem:{position:30,importance:#HIGH},

        selectionField:{position:20}

      }

      Products.productcategory                                              as Category,

      @UI:{

        identification:{position:20,importance:#HIGH,label:'Product Name'},

        lineItem:{position:20,importance:#HIGH,label:'Product Name'}

      }

      _ProductT.productname                                                  as Name,

      'EN'                                                                  as NameLanguage,

      @UI:{

        identification:{position:50,importance:#HIGH,label:'Prod.Descrip.'},

        lineItem:{position:40,importance:#HIGH,label:'Prod.Descrip.'}

      }

      _ProductT.productdescription                                          as Description,

      'EN'                                                                  as DescriptionLanguage,

      _Supplier.businesspartner                                              as SupplierID,

      _Supplier.companyname                                                  as SupplierName,

      Products.productvalueaddedtax                                          as TaxTarifCode,

      Products.productbaseunit                                              as MeasureUnit,

      Products.weight                                                        as WeightMeasure,

      Products.weightunit                                                    as WeightUnit,

      Products.currency                                                      as CurrencyCode,

      @UI:{

        identification:{position:60,importance:#HIGH},

        lineItem:{position:50,importance:#HIGH}

      }

      Products.price                                                        as Price,

      Products.width                                                        as Width,

      Products.depth                                                        as Depth,

      Products.height                                                        as Height,

      Products.dimensionunit                                                as DimUnit,

      Products.creationdatetime                                              as CreatedAt,

      Products.lastchangeddatetime                                          as ChangedAt,

      concat(concat('/webapp/images/',Products.product),'.jpg')              as PictureUrl,

      Products.supplieruuid,

      _SOItems

}

 

8 - Save and activate. The annotation in the AM should show now this new UI.Identification term

44.png

 

9 - We can do the same for the UI.StatusInfo to show the creation and the change dates at the end of the view

 

      ....

      Products.dimensionunit                                                as DimUnit,

      @UI:{

        statusInfo:{position:10,importance:#MEDIUM}

      }

      Products.creationdatetime                                              as CreatedAt,

      @UI:{

        statusInfo:{position:20,importance:#MEDIUM}

      }

      Products.lastchangeddatetime                                          as ChangedAt,

 

      concat(concat('/webapp/images/',Products.product),'.jpg')              as PictureUrl,

      Products.supplieruuid,

      _SOItems

}

 

10 - Save and activate. The UI.StatusInfo has been successfully added:

45.png

 

11 - Let's add a couple of UI.DataPoint terms in order to show the Price and the Tax Code

 

      ....

      _Supplier.businesspartner                                              as SupplierID,

      _Supplier.companyname                                                  as SupplierName,

      @UI:{

        dataPoint:{title:'TaxCode'}

      }

      Products.productvalueaddedtax                                          as TaxTarifCode,

      Products.productbaseunit                                              as MeasureUnit,

      Products.weight                                                        as WeightMeasure,

      Products.weightunit                                                    as WeightUnit,

      Products.currency                                                      as CurrencyCode,

      @UI:{

        identification:{position:60,importance:#HIGH},

        lineItem:{position:50,importance:#HIGH},

        dataPoint:{title:'Price'}

      }

      Products.price                                                        as Price,

      ....

 

12 - Save and activate. Here it's the new annotation file

46.png

 

13 - Some UI.FieldGroup terms will help us to show Technical Data information about the product in the ObjectPage, so let's add these new terms in the view

 

@AbapCatalog.sqlViewName:'Z_VW_PRODUCTS'

@AbapCatalog.compiler.compareFilter:true

@AccessControl.authorizationCheck:#CHECK

@EndUserText.label:'Products'

@OData.publish:true

 

@UI.headerInfo:{

    typeName:'Product',

    typeNamePlural:'Products',

    imageUrl:{value:'PictureUrl'},

    title:{value:'Name'},

    description:{value:'Description'}

}

 

defineview Z_Sample_Demo_Products

  asselectfrom sepm_iproduct as Products

  association[1..*]to Z_Sample_Demo_Soli as _SOItems  on  $projection.productuuid = _SOItems.ProductID

  association[1..1]to sepm_iproductt    as _ProductT on  $projection.productuuid = _ProductT.productuuid

                                                        and _ProductT.language      ='E'

  association[1..1]to sepm_ibupa        as _Supplier on  $projection.supplieruuid = _Supplier.businesspartneruuid

{

  key Products.productuuid,

      @UI:{

        identification:{position:10,importance:#HIGH},

        lineItem:{position:10,importance:#HIGH},

        selectionField:{position:10}

      }

      Products.product                                                      as ProductID,

      @UI:{

        identification:{position:40,importance:#HIGH}

      }

      Products.producttype                                                  as TypeCode,

      @UI:{

        identification:{position:30,importance:#HIGH},

        lineItem:{position:30,importance:#HIGH},

        selectionField:{position:20}

      }

      Products.productcategory                                              as Category,

      @UI:{

        identification:{position:20,importance:#HIGH,label:'Product Name'},

        lineItem:{position:20,importance:#HIGH,label:'Product Name'}

      }

      _ProductT.productname                                                  as Name,

      'EN'                                                                  as NameLanguage,

      @UI:{

        identification:{position:50,importance:#HIGH,label:'Prod.Descrip.'},

        lineItem:{position:40,importance:#HIGH,label:'Prod.Descrip.'}

      }

      _ProductT.productdescription                                          as Description,

      'EN'                                                                  as DescriptionLanguage,

      _Supplier.businesspartner                                              as SupplierID,

      _Supplier.companyname                                                  as SupplierName,

      @UI:{

        dataPoint:{title:'TaxCode'}

      }

      Products.productvalueaddedtax                                          as TaxTarifCode,

      Products.productbaseunit                                              as MeasureUnit,

      @UI:{

        fieldGroup:{qualifier:'FGTechData',groupLabel:'Technical Data',position:10,importance:#MEDIUM}

      }

      Products.weight                                                        as WeightMeasure,

      Products.weightunit                                                    as WeightUnit,

      Products.currency                                                      as CurrencyCode,

      @UI:{

        identification:{position:60,importance:#HIGH},

        lineItem:{position:50,importance:#HIGH},

        dataPoint:{title:'Price'}

      }

      Products.price                                                        as Price,

      @UI:{

        fieldGroup:{qualifier:'FGTechData',groupLabel:'Technical Data',position:20,importance:#MEDIUM}

      }

      Products.width                                                        as Width,

 

      @UI:{

        fieldGroup:{qualifier:'FGTechData',groupLabel:'Technical Data',position:40,importance:#MEDIUM}

      }

      Products.depth                                                        as Depth,

 

      @UI:{

        fieldGroup:{qualifier:'FGTechData',groupLabel:'Technical Data',position:30,importance:#MEDIUM}

      }

      Products.height                                                        as Height,

 

      Products.dimensionunit                                                as DimUnit,

      @UI:{

        statusInfo:{position:10,importance:#MEDIUM}

      }

      Products.creationdatetime                                              as CreatedAt,

      @UI:{

        statusInfo:{position:20,importance:#MEDIUM}

      }

      Products.lastchangeddatetime                                          as ChangedAt,

 

      concat(concat('/webapp/images/',Products.product),'.jpg')              as PictureUrl,

      Products.supplieruuid,

      _SOItems

}

 

14 - Save and activate. This is how the annotation appears now

47.png

 

15 - As the last thing, let's add a UI.LineItem annotation even for Sales Order Line Items. Open in Eclipse the view Z_SAMPLE_DEMO_SOLI and add the annotation in this way:

 

@AbapCatalog.sqlViewName:'Z_VW_SOLITEMS'

@AbapCatalog.compiler.compareFilter:true

@AccessControl.authorizationCheck:#CHECK

@EndUserText.label:'Sales Order Line Items'

 

defineview Z_Sample_Demo_Soli

  asselectfrom sepm_isoi as SOLItems

{

  key SOLItems.salesorderitemuuid,

      SOLItems.salesorderuuid                    as SalesOrderID,

      @UI:{

        lineItem:{position:10,importance:#HIGH}

      }

      SOLItems.salesorderitem                    as ItemPosition,

      SOLItems.productuuid                        as ProductID,

      SOLItems.shorttextgroupuuid                as NoteID,

      SOLItems.transactioncurrency                as CurrencyCode,

      @UI:{

        lineItem:{position:40,importance:#HIGH}

      }

      SOLItems.grossamountintransaccurrency      as GrossAmount,

      @UI:{

        lineItem:{position:30,importance:#HIGH}

      }

      SOLItems.netamountintransactioncurrency    as NetAmount,

      @UI:{

        lineItem:{position:20,importance:#HIGH}

      }

      SOLItems.taxamountintransactioncurrency    as TaxAmount,

      SOLItems.productavailabilitystatus          as StatusAvailability,

      SOLItems.opportunityitem                    as OpportunityItem

}

 

16 - Save and activate. In the annotation file, if you refresh it and switch to the Z_SAMPLE_DEMO_SOLI entity, you should see this

48.png

 

17 - Let's run the application now and check what we have done so far

49.png

 

18 - The application looks quite promising. We still cannot see the buttons for creating, updating and deleting records. This can be achieved by adding the following @ObjectModel annotation to the beginning of the Z_SAMPLE_DEMO_PRODUCTS view:

 

@AbapCatalog.sqlViewName:'Z_VW_PRODUCTS'

@AbapCatalog.compiler.compareFilter:true

@AccessControl.authorizationCheck:#CHECK

@EndUserText.label:'Products'

@OData.publish:true

 

@ObjectModel:{

  createEnabled:true,

  updateEnabled:true,

  deleteEnabled:true

}

 

@UI.headerInfo:{

    typeName:'Product',

    typeNamePlural:'Products',

    imageUrl:{value:'PictureUrl'},

    title:{value:'Name'},

    description:{value:'Description'}

}

 

defineview Z_Sample_Demo_Products

....

 

19 - Save and activate. Now the application looks like this:

50.png

 

20 - For products pictures we need to add to the web app folder a subfolder named images containing all the images named as the products

51.png

 

21 - The application looks like this

52.png

 

22 - Unfortunately we cannot add Facets to our CDS view: this is not yet supported. In order to add them we need to do it directly in the annotation file with the AM.

The process is the same I showed in the blog here.

 

23 - For example we can add a UI.HeaderFacet term containing 3 UI.ReferenceFacets and a UI.Facet containing a UI.CollectionFacet with 2 UI.ReferenceFacets within and another UI.ReferenceFacet

53.png

 

24 - Running the application you should get something like this:

54.png

 

25 -  We are done now with your Smart Template application based on CDS Views. You should have learnt now how to create a new CDS view, how to inject annotations terms in it, how to create a new application is SAP Web IDE and how to enhance the annotation coming from the service with other annotation terms.

 

Congratulations! You have successfully created your first Smart Template app based on CDS views!


Deploying UI5: Getting our Grunt on.

$
0
0

So far we have covered the fact that we need a build script for our wondrous JavaScript / ECMAScript / SAPUI5 / OpenUI5 code.

 

Now we are going to get into the nuts and bolts of building that script.

 

Installation


Step 1. Install - Grunt our task horse.

But you need to do step zero

Step 0. Install nodejs.

 

These steps are as simple as 3.14 so head over to https://nodejs.org/ and click on the big green button.

 

install-node.PNG

 

You will get offered the software appropriate for your operating system. (clever huh)

 

Once you have node you should be able type on the command line / terminal / powershell :

 

node -v

 

and get something sensible.

 

Now we can do step 1. Install grunt

 

npm install -g grunt-cli

 

You may have to sudo that

 

Now you are laughing...

 

Creating the build project

 

Lets start with a directory with a really short path. Node dependencies have a reputation of creating really long paths so start short and you will have room to grow. I am going with /var/www/build but you might like c:\build or even ~/build or something to get you going

 

mkdir build

cd build

 

Once I have my build directory I am going to check out my project or projects from source control under this directory. The reason I didn't put it anywhere else is that npm dependencies often create long path names under the node_modules directory and it easier. It does mean and an additional step but there can be a grunt task for that.

 

In one of your ui5 projects you have checked out you will need a project.json and a Gruntfile.js

You can copy project.json from the grunt getting started page or use npm init to create one or a bit or both.

 

A simple package.json

{  "name": "my-project-name",  "version": "0.1.0",  "devDependencies": {  "grunt": "~0.4.5",  "grunt-contrib-uglify": "~0.5.0"  }
}

 

 

Now do an npm install to install all the dependencies in the project.json

 

 

 

OK ... now that that is done a Gruntfile.js

Again you can grab a template from the grunt getting started page.

 

module.exports = function(grunt) {  // Project configuration.  grunt.initConfig({  pkg: grunt.file.readJSON('package.json'),  uglify: {  options: {  banner: '/*! <%= pkg.name %> <%= grunt.template.today("yyyy-mm-dd") %> */\n'  },  build: {
src:  './**/*.js',dest: 'build/WebContent/' ,  }  }  });  // Load the plugin that provides the "uglify" task.  grunt.loadNpmTasks('grunt-contrib-uglify');  // Default task(s).  grunt.registerTask('default', ['uglify']);
};

 

Note that you will need to create a build directory for the content to go in. Once we get the hang of this we can use grunt to create directories and clean them up.

With that in place you now should be able to grunt to your hearts content.

Run the command grunt command on the command line and you will get something like this:

 

Running "uglify" (uglify) task
File build/WebContent/Component.js created: 3.53 kB → 2.13 kB
File build/WebContent/MyRouter.js created: 2.41 kB → 1.15 kB
File build/WebContent/model/Config.js created: 518 B → 343 B

 

The basic task process

 

When you type the command grunt on the command line it runs the default task in the Gruntfile. In our case this is the uglify task. When we ran grunt it failed because there was no source to minify.

 

I will leave it to you to play around with that if you like. When you are ready we will move on.

 

The basic workflow for building up our gruntfile is:

  1. Search for the module on npm that solves the task you want to acheive. ( There is a grunt task for that. )
  2. Install it with npm install <module> --save-dev This also adds the line to the package.json
  3. Insert the config in the gruntfile
  4. Load the task in the gruntfile with grunt.loadNpmTasks(
  5. Register the task in the gruntfile wiith  grunt.registerTask   
  6. Run grunt

 

A twist in the tale

This is all fantastic and I hope you are getting the hang of it but before we cover too much more ground I want to introduce a grunt task that will help a lot.

 

Most of our projects, if not all, have the same requirements. Minify, create dbg etc so it would be a little redundant if we had to create a gruntfile in each of our projects. We could copy and paste or clone but what if there is a new task we want to add to the work flow do we need to go and edit every grunt file in every project? That sound like a maintenance nightmare.

 

Enter grunt-source with great fanfare.

 

Grunt-source solves this very issue.

 

With it we can define our gruntfile once in another folder as we have done in this blog. I chose _build so it would sort to the top of all projects. In all our other projects include a grunt-source.json or a gruntsource element in our package.json with custom parameters for that project and we are done. I chose to add a gruntSource element in in my project.json file:

{    "version": "0.0.23",    "gruntSource": {        "source": "../_build",        "nsPrefix": "myui5/namespace",        "appname": "My AppName",        "zipfile": "myzipfilename.zip",        "ui5app" : "Z_MY_APP_NAME_IN_SE80"    }
}

The key element in the gruntSource section is the source directive that tells grunt where to find the task and config to run. All the other elements are parts of the fun we discover in my next blog in this series when we string a whole bunch of tasks together.

 

<-- Previous blog: Mapping the scenarios  Next blog : Putting all the tasks together -->

How to create a SAP Web IDE plugin - part 1

$
0
0

Introduction

SAP Web Integrated Development Environment (or SAP Web IDE) is a next-generation cloud-based meeting space where multiple project stakeholders can work together from a common web interface, connecting to the same shared repository with virtually no setup required. It includes multiple interactive features that allow you to collaborate with your colleagues and accelerate the development of your HTML5/UI5 applications.

 

Background Information

In this How-To Guide we will see how to create a new SAP Web IDE plugin from scratch and how to share it to other users.

The plugin we are going to create will add a new functionality to the Edit menu of our SAP Web IDE. It will be just showing a simple greeting message when calling the related menu item. Furthermore we will explore the creation of a custom template in order to extend the existing SAPUI5 Application template putting it in a new template category.

 

Prerequisites

As a prerequisite you need to have your installation of SAP Web IDE up and running.

 

Step-by-Step Procedure

This is the sequence of steps:

  1. Create a new plugin project
  2. Make some changes and run the plugin again
  3. Create a folder for the plugin repository and copy the plugin into it
  4. Create the catalog.json file
  5. Deploy the project repository on the SAP HANA Cloud
  6. Create a destination in the SAP HANA Cloud cockpit
  7. Activate the plugin in SAP Web IDE
  8. Create a second plugin for extending an existing template
  9. Make some changes and test the new template
  10. Copy the new plugin in the repository folder
  11. Update the catalog.json file
  12. Update the project repository on SAP HANA Cloud
  13. Activate the plugin SAP Web IDE


*** NOTE: The first 7 steps will be discussed here the other 6 ones will be shown in the second part of this article.

 

 

Step 01 - Create a new plugin project

This first step will take care of creating the basic structure of the entire plugin. SAP Web IDE provides us with a wizard template for this in order to facilitate us in the creation of all the needed components.

 

1 - Open SAP Web IDE. Do File --> New --> Project from Template

01.png

 

2 - Select the Empty Plugin template and click Next

02.png

 

3 - Enter the name of the project (i.e. “coolpluginproject”) and click Next

03.png

 

4 -  Enter a name for the new plugin and a short description; choose to include the sample implementation code and click Finish

04.png


5 - This is the final structure of the entire project

05.png

 

6 - If you want to test the new created plugin you can do it: select the plugin.json file and click on Run --> Run Plugin Project

06.png

 

7 - A new tab will open in your browser. This new tab contains a copy of your SAP Web IDE environment in debug mode with the plugin already enabled in it. If you click on the Tools menu, you should see the new plugin. For the moment it just contains some sample code

07.png

 

8 - By clicking on Tools --> Sample --> Hello World you will see that the new plugin is working fine. Click on OK to close this message

08.png

 

9 - Close the second tab in the browser. You have successfully created and tested your first plugin.

09.png

 

 

 

Step 02 -  Make some changes and run the plugin again

 

1 - Be sure you have closed the second tab in the browser and that you are working on the normal SAP Web IDE environment (no debug mode!)

 

2 - Just for showing how to make some basic changes to this project, we can try to change the labels in the i18n property file and the name of the plugin. Go in the i18n folder and double click on the file i18n.properties. Change the value for the command_helloworld property to “Welcome” and the value for the property commandgroup_sample to “Greetings”, then save the file

10.png

 

3 - Double click on the plugin.json file. It will be opened on the right side. Replace all the occurrences of the word “tools” with “edit” and save the file

11.png

 

4 - Now if you run the plugin again you will see that there is a new item, “Greetings --> Welcome”, in the Edit menu

12.png

 

5 - Close the Debug Mode - SAP Web IDE

13.png

 

6 - You have successfully changed your first plugin!

 

 

 

Step 03 -  Create a folder for the plugin repository and copy the plugin into it

Since you may have several plugins in your environment, you need to create a plugin repository to host them. Of course you can use repositories in order to group plugins by functionality. Each plugin repository will contain a file named catalog.json with all the information related to any included plugin. We will create this file in the next chapter

 

1 - Open SAP Web IDE

 

2 - Right click on the Workspace root folder and choose New --> Folder. Enter the name of the plugin repository (i.e. “bigpluginrepo”) and click on OK

14.png

 

3 - Now select the folder containing the plugin project we have created previously, right click on it and choose Copy. Select the new plugin repository folder, right click on it and choose Paste to copy the plugin project into this new container. This is the final structure of your plugin repository. It now contains your first plugin

15.png

 

 

 

Step 04 - Create the catalog.json file

This file is required because the system needs to know which are the plugins contained in your repository.

 

1 - Right click on bigpluginrepo, the plugin repository folder, and choose New --> File. Enter catalog.json as the name of the new file and click on OK

16.png

 

2 - An empty file opens on the right. Type the following code in the editor and save the file

{

    "name" : "Big Plugin Repository",

    "plugins" : [

        {

            "name" : "coolplugin",

            "description" : "This is my cool plugin",

            "path" : "/coolpluginproject",

            "version" : "1.0.0"

        }

    ]

}

 

NOTE: be careful if you copy & paste this code because it could be pasted with wrong characters

 

3 - You have successfully created the catalog.json file.

 

 

 

Step 05 - Deploy the project repository on the SAP HANA Cloud

This step is mandatory to see the plugin among all the available external plugins. We need to deploy our plugin repository folder to the SAP HANA Cloud. This operation will also take care of automatically activate the new plugin.

 

1 - Open SAP Web IDE

 

2 - Right click on the plugin repository folder and select Deploy --> Deploy to SAP HANA Cloud Platform

18.png

 

3 - Enter the password for your account and click on Login

 

4 - Click on Deploy

20.png

 

5 - Your plugin repository has been successfully deployed to SAP HANA Cloud. Click on Open the application's page in the SAP HANA Cloud Platform cockpit

21.png


6 - If you check the status of your application it should be now started. Look at the Application URL on the same page and write down/copy in the clipboard this link: it will be used in the next chapter

22.png


7 - You have successfully deployed your project to SAP HANA Cloud. You can close this message box on SAP Web IDE

26.png

 

 

 

Step 06 - Create a destination in the SAP HANA Cloud cockpit

In order for the system to recognize the new plugin, you need to create a new destination in the SAP HANA Cloud cockpit. This is a special destination witch points to the application URL of your plugin application in the SAP HANA Cloud.

 

1 - Open the SAP HANA Cloud cockpit (i.e. https://account.hanatrial.ondemand.com/cockpit). Go on Destinations and click on New Destination

23.png


2 - Enter the following values to create the new destination:

ParameterValue
Namebigpluginrepo
TypeHTTP
DescriptionMy Plugin Repository
URL<the application URL copied in the clipboard in the previous chapter>
Proxy TypeInternet
Cloud Conn. Version2
AuthenticationAppToAppSSO

 

 

Before saving the destination, we need to add some properties to the destination. These are the properties we need to add:

PropertyValue
WebIDEEnabledtrue
WebIDEUsageplugin_repository

 

24.png

 

3 - You have successfully created a destination for your plugin repository.

 

 

 

Step 07 - Activate the plugin in SAP Web IDE

Let’s activate the plugin. This is made directly from in SAP Web IDE

 

1 - Open SAP Web IDE or refresh it if you have it already open

27.png

 

2 - Click on the Settings button on in the left side toolbar. Select the Plugins branch. Choose the plugin repository("My Plugin Repository") you have created and enable your plugin, then click on Save

28.png

 

3 - Refresh SAP Web IDE

29.png

 

4 - Now, if you look in the Edit menu you should see your plugin successfully activated

30.png

 

5 - Congratulations! You have successfully activated your first SAP Web IDE plugin.

 

Let's continue with the second part of this article where we will learn how to create a new template!

How to create a SAP Web IDE plugin - part 2

$
0
0

This is the continuation of the blog started here.

In this second part we are going to see how to create a new SAP Web IDE template after having already created our first plugin and a plugin repository in the previous part.

Let's continue then with the other steps.

 

 

 

Step 08 - Create a second plugin for extending an existing template

A plugin structure needs to be created even in the case you want to extend an existing template with some new features.

 

1 - Open SAP Web IDE. Do File --> New --> Project from Template

01.png

 

2 - Select the Empty Plugin template and click Next

02.png

 

 

3 - Enter the name of the project (i.e. “templatepluginproject”) and click Next

02.png

 

4 - Enter the name of the plugin and a short description; please notice that this time we are NOT including the sample implementation code because we don’t need it. We just need the plugin structure in order to host our template. Click Finish

03.png

 

5 - Now we can extend the template inside this new plugin. Right click on the plugin's name in the Project Explorer and choose New --> Template from Existing Template

04.png

 

6 - Provide the name for the new template and a short description. Let the type for this template be “project” and choose to create a new category where this template will be put into. Then click Next

05.png

 

7 - Select one of the available templates and click Next. In this case we are choosing SAPUI5 Application

06.png

8 - Click on Finish

 

9 - A new template ("mynicetemplate") has been added in this second plugin. This template has its own folder structure. If you click on the plugin.json file in this new plugin project, you see that some extra code has been automatically added for the template configuration

08.png

 

10 - As a last step here, add the services filesystem.documentProvider, projectType and setting.project in the requires section of the plugin.json file. These are required in order to use the template. Then save the file. Don't forget to put a comma just after the templateCustomizationStep service when adding the new lines

 

{

    "name": "templateplugin",

    "description": "This is a plugin for templates",

    "i18n": "templateplugin/i18n/i18n",

    "requires": {

        "services": [

            "template",

            "templateCustomizationStep",

            "filesystem.documentProvider",

            "projectType",

            "setting.project"

        ]

    },

...

08_2.png

11 - You have successfully created your second plugin for extending an existing template.

 

 

 

Step 09 - Make some changes and test the new template

Let’s make some easy changes to the template so that we can see that we can really change it according to our requirements. We will:

  • change the sample screenshot shown during the wizard procedure
  • change the default view name from View1 to Main

 

1 - Right click on the image subfolder inside the <plugin project>/<template project> folder and choose Import --> From File System

09.png

 

2 - Browse for a new image file and click on OK. I've previously prepared a new .png file named "flower.png" for this example

10.png

 

3 - Once the new file has been uploaded, you can delete the old one by right clicking on it and choosing Delete

11.png

 

4 - Open the plugin.json file and change the name of the preview image to be the same of the uploaded image, then save the file

12.png

 

5 - Now let's try to change the name of the only view which is created during the wizard of the SAPUI5 Application template. The default name is View1, we want to change it to be Main. Open the mynicetemplate.js file and change the viewName variable from View1 to Main, then save the file

13.png

 

6 - Open the model.json file and change the value at line 53 from View1 to Main, then save the file

14.png

 

7 - Run the plugin and, in the Debug Mode tab, try to create a new application from template. You should be able to select the new mynicecategory category and see the mynicetemplate. Click Next

15.png

 

8 - Going forward with the next screens, you should be also able to see that the name for the default view is now set to Main

16.png

 

9 - You have successfully tested the new template, now you can close the tab where SAP Web IDE is running in debug mode.

17.png

 

 

 

Step 10 - Copy the new plugin in the repository folder

We need simply to copy the new plugin under the plugin repository folder.

 

1 - Select the plugin project and right click on it. Then choose Copy

 

2 - Select the plugin repository folder, right click on it and choose Paste

 

3 - At the end you should have two plugins in your plugin repository folder

18.png

 

 

 

Step 11 - Update the catalog.json file

Now that we have added this new plugin to the repository we have also to update the catalog.json file in order to make it available.

 

1 - Double click on the catalog.json file in the project explorer

 

2 - Replace the code in the editor with this one and save the file; this will declare the new plugin in the repository.

 

{

    "name" : "Big Plugin Repository",

    "plugins" : [

        {

            "name" : "coolplugin",

            "description" : "This is my cool plugin",

            "path" : "/coolpluginproject",

            "version" : "1.0.0"

        },

        {

            "name" : "templateplugin",

            "description" : "This is my template plugin",

            "path" : "/templatepluginproject",

            "version" : "1.0.0"

        }

    ]

}

NOTE: be careful if you copy & paste this code because it could be pasted with wrong characters

19.png

 

3 - You have successfully updated your catalog.json file

 

 

 

Step 12 - Update the project repository on SAP HANA Cloud

Since we have done some changes to the project repository in SAP Web IDE, we need also to update the project repository on SAP HANA Cloud.

 

1 - Right click on the plugin repository project in the project explorer and click on Deploy --> Deploy to SAP HANA Cloud Platform

20.png

 

2 - Enter your password if required and click on Login

 

3 - Select Update an existing application and let the version be the proposed one. Click on Deploy

22.png

 

4 - Click on Close when finished

23.png

 

5 - You have successfully updated the project on SAP HANA Cloud.

 

 

 

Step 13 - Activate the plugin in SAP Web IDE

The final step is to activate this second plugin.

 

1 - Reopen or refresh SAP Web IDE

 

2 - Click on the Settings button on in the left side toolbar and select the Plugins branch. Choose the plugin repository("My Plugin Repository") you have created and enable this second plugin ("templateplugin"), then click on Save

24.png


3 - Click on Refresh

25.png

 

4 - If you try now to create a new project from templates you should be able to find the new template

26.png

 

5 - Congratulations! You have successfully extended an existing template.

 

 

 

Appendix

In this appendix I would like to give you some further details regarding how a plugin project is structured.

The file and folder structure is created by the wizard template according to the Plugin File and Folder Structure paradigm, which is something like this:

 

     <plugin_name> - the plugin root folder

          |command - command implementations

          |service - service implementations & service interfaces

          |lib - open source libs

          |image - all image files

          |css - all css files

          |i18n - all internationalization files

          |model - all model classes

          |control - all UI5 controls

          |view - all UI5 views

          |Plugin.js - the plugin module

          |plugin.json - the plugin configuration file

 

 

 

The plugin.json file

As you can see in the plugin folder's root there is a file named plugin.json. It contains:

  • the name of the plugin class
  • the dependencies of the plugin
  • the inherent service configurations for the plugin

 

In general, its content are settings inherent to the plugin not likely to be changed by an administrator or by the user via personalization: this file is used to tell to SAP Web IDE what services are needed by the plugin and what services the plugin itself provides. A plugin is defined by this plugin.json file with the following properties:

 

PropertyDescription
name:<string>the unique name of the plugin (e.g. sap.watt.uitools.myplugin)
description:<string>the plugin's description
i18n:<string>optional. The path to a i18n file, which consists of a namespace identifier, a folder name (normally i18n) and a file name (e.g. sap.watt.uitools.myplugin/i18n/i18n)
module:<string>optional. Path to a module that is used as a private event handler target of the plugin. By convention this name must be named Plugin.js
requires:<object>

all required dependencies of the plugin are listed here.

  • services:<array> - the list of all the services required by the plugin
provides:<object>

public contributions of the plugin to the overall system. The following contributions are possible:

  • services:<object> - registration of public services
  • interfaces:<object> - registration of interfaces
configures:<object>

the plugin configurations

  • services:<array> - configuration of required or provided services
subscribes:<object>subscriptions to events of required or provided services, they can be only made to the plugin module or provided services

 

If you open the existing plugin.json file, you see that it has exactly this structure:

 

{

"name": "coolplugin",

"description": "This is a cool plugin",

"i18n": "coolplugin/i18n/i18n",

"requires": {

"services": [

"usernotification",

"log",

"command",

"commandGroup"

]

},

"provides": {

"services": {

"sample": {

"implements": "coolplugin.service.Sample",

"module": "coolplugin/service/Sample"

}

},

 

"interfaces": {

"coolplugin.service.Sample": "coolplugin/service/Sample"

}

},

"configures": {

"services": {

"command:commands": [{

"id": "coolplugin.helloWorld",

"label": "{i18n>command_helloWorld}",

"service": "coolplugin/command/HelloWorld"

}],

 

"commandGroup:groups": [{

"id": "edit.sample",

"label": "{i18n>commandgroup_sample}"

}, {

"id": "edit.sample.helloWorld"

}],

 

"commandGroup:items": [{

"parent": "edit",

"type": "menu",

"group": "edit.sample",

"prio": 100

}, {

"parent": "edit.sample",

"type": "inline",

"group": "edit.sample.helloWorld",

"prio": 10

}, {

"parent": "edit.sample.helloWorld",

"type": "action",

"command": "coolplugin.helloWorld",

"prio": 10

}]

}

},

 

"subscribes": {

"sample:notificationDisplayed": "sample:onAfterNotificationDisplayed"

}

}

 

The first information we have to provide is the name of the plugin, a short description and the name of the file containing the internationalization strings. This file is normally located under the i18n folder and has the following structure:

 

#__ldi.translation.uuid=e27c95ce-405c-406e-1c47-71995165ee23

# XTXT:

sample_helloMessage = Hello {0}!

 

# XMIT:

command_helloWorld = Welcome

 

# XMIT:

commandgroup_sample = Greetings

 

The first line contains an ID to uniquely identify the translation file. All the other lines are some comments (starting with “#”), a unique parameter that identifies the command and the value associated to that parameter. You can have as many i18n files as the languages you want for your plugin. In the source code of your plugin, when the parser encounters a string like "label": "{i18n>command_helloWorld}", it searches in the i18n files of your language for the string command_helloWorld and replaces it with the corresponding value, which is in our case “Welcome”. This is how the translation mechanism works.

 

The second block to examine is the requires section. In this section it’s specified what services this plugin requires.

The plugin in this particular example requires

  • usernotification - because it wants to count how many times the greeting message is shown
  • log - because it wants to print the number of the shown messages to the console
  • command - because we need to add a command to the menu
  • commandGroup - because we want to add a new group "requires":

"requires": {

"services": [

"usernotification",

"log",

"command",

"commandGroup"

]

},

 

Which services will this plugin provide? We define this in the following section. This section specifies the names of the services that will be later available to other plugins in the “context.service”, the interfaces that are implemented and the module with the implementation. The interfaces section is important because it also specifies the path to the service. Notice that the strings are case sensitive.

In this example, our plugin will provide a service named “sample”, which implements the interface described in the Sample.json file and uses the module Sample.js file, both located under the service folder of the plugin.

 

"provides": {

"services": {

"sample": {

"implements": "coolplugin.service.Sample",

"module": "coolplugin/service/Sample"

}

},

 

"interfaces": {

"coolplugin.service.Sample": "coolplugin/service/Sample"

}

},

 

The configures section defines two things: the command that we are going to call from the menu item and the menu item itself. The command we are going to execute has the id = myfirstplugin.helloWorld and is located under the command folder in the Sample.js file. We will see this file later. The command will be inserted in the Edit menu under a subgroup labeled Sample and it will appear with the label defined in the i18n file by the label command_helloWorld. The priority of 100 means that it will be placed after all other commands with lower priority.

 

"configures": {

"services": {

"command:commands": [{

"id": "coolplugin.helloWorld",

"label": "{i18n>command_helloWorld}",

"service": "coolplugin/command/HelloWorld"

}],

 

"commandGroup:groups": [{

"id": "edit.sample",

"label": "{i18n>commandgroup_sample}"

}, {

"id": "edit.sample.helloWorld"

}],

 

"commandGroup:items": [{

"parent": "edit",

"type": "menu",

"group": "edit.sample",

"prio": 100

}, {

"parent": "edit.sample",

"type": "inline",

"group": "edit.sample.helloWorld",

"prio": 10

}, {

"parent": "edit.sample.helloWorld",

"type": "action",

"command": "coolplugin.helloWorld",

"prio": 10

}]

}

},

 

The latest section is the one related to the subscribes. It means that every time a notification is displayed the onAfterNotificationDisplayed event is fired and the inner code of this event is executed.

 

"subscribes": {

"sample:notificationDisplayed": "sample:onAfterNotificationDisplayed"

}

 

 

 

The command file (HelloWorld.js)

When you click on the menu Edit --> Greetings --> Welcome the file that is executed is located under the command folder and it’s named HelloWorld.js.

There are three functions that we need to implement:

  • execute: this will call the required function (“sayHello”) inside the implementation file (Sample.js) in order to execute the command;
  • isAvailable: this function specifies if the command needs to be always available or if it’s available only under certain conditions: in this case we have made it always available. This means that it will be always visible in the menu;
  • isEnabled: this function specifies if the command needs to be always enabled or if it’s enabled only under certain conditions: in this case we have made it always enabled.

 

/**

* A command sample for calling the 'sample' service.

*

* The command is added to the menu bar at 'Tools->Sample->Hello World' as defined in the plugin.json file.

*/

define({

 

execute: function() {

return this.context.service.sample.sayHello("World");

},

 

isAvailable: function() {

return true;

},

 

isEnabled: function() {

return true;

}

});

 

 

 

The interface file (Sample.json)

This file is located under the service folder. In this file are exposed all the methods that the plugin’s service needs to implement. In this case for example the service needs to implement the following two functions:

  • sayHello: to display the greeting message
  • getNotificationCount: to get the number of the times the greeting is displayed on the screen.

 

Apart these two methods, the service will have to implement the handler for the notificationDisplayed event.

 

 

{

"name": "coolplugin.service.Sample",

"description": "The sample service interface",

"methods": {

"sayHello": {

"description": "Display a greeting message notification",

"params": [{

"name": "sName",

"type": "string",

"description": "The name of the user to greet"

}]

},

"getNotificationCount": {

"description": "Get the number of greeting notifications displayed so far",

"returns": {

"type": "number",

"description": "Number of greeting notifications displayed so far"

}

}

},

 

"events": {

"notificationDisplayed": {

"params": [{

"name": "notificationCount",

"type": "number",

"description": "The number of greeting notifications displayed so far"

}]

}

}

}

 

 

 

The service implementation file (Sample.js)

This file is located under the service folder as well. It contains a service implementation for displaying a greeting notification and counting the number of alerts displayed. The service provides a public API, which is defined in its interface (in this example, Sample.json file) and can be used by other plugins.  Every method call on a service is asynchronous and returns a Q-promise. If not done explicitly by the method, the return value is automatically wrapped with a promise object. Please refer to the following guide for further information about asynchronous Javascript (http://documentup.com/kriskowal/q/).

Other services (which are required by this service plugin, as defined in the plugin.json file) can be accessed using this.context.service property.

A service can fire events that are defined in its interface. These events can be handled by any other service. A service can also handle events from any other service (including its own). The events subscription along with the handler methods must be defined in the plugin.json file.

In particular in this file we have the following functions implemented:

  • init: this function is automatically executed when the service is initialized;
  • sayHello: this is the function which does the job of showing the popup message and increasing the counter of the times when the message is displayed;
  • getNotificationCount: returns the number of times the popup message is shown;
  • onAfterNotificationDisplayed: each time the popup message is displayed an event is fired: this event takes care of displaying the number of notifications in the log console.

 

define({

 

_iNotificationCount: null,

 

init: function() {

this._iNotificationCount = 0;

},

 

sayHello: function(sName) {

var that = this;

this._iNotificationCount++;

var sMessage = this.context.i18n.getText("i18n", "sample_helloMessage", [sName]);

// Display greeting notification and fire event

return this.context.service.usernotification.info(sMessage).then(function() {

return that.context.event.fireNotificationDisplayed({

notificationCount: that.getNotificationCount()

});

});

},

 

getNotificationCount: function() {

return this._iNotificationCount;

},

 

onAfterNotificationDisplayed: function(oEvent) {

var iCount = oEvent.params.notificationCount;

// Display log message to the SAP River RDE console (accessed via 'View->Console' menu)

// Log messages don't need to be translatable

this.context.service.log.info("Sample", "Number of Hello notifications shown so far: " + iCount, ["user"]).done();

}

});

 

Good luck with SAP Web IDE plugin development!

A small tip I learn from UI5 Diagnostics tool - a practice of AOP programming

$
0
0

We know that UI5 framework provides a convenient Diagnostics tool for application developer to set breakpoint on a given method of control class. The Diagnostics tool could be launched via Ctrl+Alt+Shift+S.

 

We can select the control and its methods where we would like to set breakpoint. Once we click “Add breakpoint” button, next time if the corresponding method is called, the breakpoint would be triggered, without application developers' manual set in Chrome development tool any more.

clipboard1.png

It looks like a magic? Today my colleague asked me how this feature is implemented, so I have a look at UI5 framework source code.

 

We can again simply use Chrome development tool for research.

For example I would like to set breakpoint on method _bindAggregation:

clipboard2.png

Here the AOP idea is used.

clipboard3.png

clipboard4.png

The hook implementation is simply returning a new function via closure within which the original method is called ( line 521 ) with the new feature injected via keyword debugger.

clipboard5.png

After the logic is understood, we can practice in our application code.

 

Suppose I would like to have my button press event handler supported by this mechanism, I can simply write the following pseudo code:

 

var myButton = new sap.ui.commons.Button("btn",{   text: "press me~"  });
var handler = function(oEvent){   oController.onPress(oEvent);
};
handler = bDebugModeActivated? util.tool.methodHook(handler): handler;
myButton.attachPress(handler);

In the runtime, once I press the button, debugger will be triggered with the following callstack:

clipboard6.png

Just step into line 36:


clipboard7.png

and then our event handler could be debugged:

clipboard8.png

SAP_UI 750 on top of SAP_UI 740

$
0
0

SAP_UI 750 on top of SAP NW 7.40

 

 

 

 

 

I had to upgrade my current SAP_UI which was on 740 to SAP_UI 750.

 

 

 

The reason I had to upgrade is because of the HR Renewal 2.0.

From the note 1976498 - HRSP Information for HR Renewal 2.0, http://service.sap.com/sap/support/notes/1976498 SAP suggested that the UI dependencies changes with HRSP 28 from SAP_UI 740 to SAP_UI 750.

 

See note 2217489 Maintenance and Upgrade Strategy for UI Add-On and User Interface Technology.

 

 

So here is the simple upgrade process of the SAP_UI.

 

From the MOPZ, It is visible from OS/DB Dependent files:

 

1.jpg

 

Download the XML file

 

Use SAINT to perform the Upgrade.

2.jpg

 

Hope this help, the catch is SAP suggested that the UI dependencies changes with HRSP 28 from SAP_UI 740 to SAP_UI 750. so if you are on SP 28, some functionalities that UI services may not work the way they should, so the upgrade is suggested.

 

Regards,

Sebastian

Coloring Table rows conditionally in SAP UI5 XML Views, based on oData Service

$
0
0

Summary and Background

 

There are quite a lot of discussions within SCN about conditionally coloring of table-rows in SAP UI5. Kiran Kumar Valluru posted a very good blog about table coloring with addStyleClass() function:

 

This step by step procedure shows how to solve this problem by using XML-Views, an oData Service and custom CSS File without any additional JavaScript coding.


Procedure

 

Step1: OData Service


The OData Service with the Entity “PLSTRUCList” has the following properties (unused properties are hidden):

 

1.png

 

“Rfarea” and “KslCurrStr” will be used for table binding. The property “Style” declares, which Stylesheet should be used for the table row.

If we take a look to the SAP Gateway Client, we will get the following result for this Entityset:

 

2.png

 

Step2: Consuming oData Service in XML-View


This is how the table is declared in the XML-view:

 

3.png

 

In order to render the rows in different styles we need to add Custom Data to DOM. In this example we use the key “mydata”:

4.png

This will generate the HTML with “data-mydata” tag (screenshot from chrome debugging):

5.png

 

Step 3: Custom Style-Sheet


Finally, we have to add a custom Style Sheet to the workspace:

 

6.png

 

…and add the following line to index.html:

 

7.png

The myCSS.class File has only one code-line in this example:

 

8.png

 

It ensures, that every UI-Element with “data-mydata = emphcolor1” should be rendered with the appropriate background-color.

 

Result

 

The screenshot below shows the final result:

 

9.png

 

Related information

 

http://help.sap.com/saphelp_hanaplatform/helpdata/en/1e/f9fefa2a574735957dcf52502ab8d0/content.htm

How test the SapUi5 Offline capabilities ? (cache & local db)

$
0
0

1 - Create a simple SAPUI5 application

 

To create a simple application, use template "SAPUI5 Application" in WebIde.

 

This step will create the structure and files to have a simple SAPUI5 application.

 

2 - Initialize model with a JSON File

 

To init model, several ways are possible.

In this example I create a very simple local json file name data.json in folder model :

{    "firstname":"John",    "lastname":"Smith"
}

In component.js, create model :

  var oJSONDataModel;  oJSONDataModel= new sap.ui.model.json.JSONModel("model/data.json");  this.setModel(oJSONDataModel);

 

3 - Change view to add fields and buttons and bind data

 

In view, add two input fields binded to model:

 

<Input width="100%" id="__input0" value="{/firstname}"/><Input width="100%" id="__input1" value="{/lastname}"/>

Then add two buttons :

 

    <Button text="Save" width="100px" id="__button0" press="handleSave"/>    <Button text="Read" width="100px" id="__button1" press="handleRead"/>

The first one will be used to save data from our form to local database, and the second one to read data from local database to our form.

 

 

4 - Change controller to add actions for buttons

 

Now, we will adapt controller to add functions to manage button actions.

 

For save button, the code below create DB & Object if needed and add data from model to local DB :

 

handleSave : function (evt) {        var oData = this.getView().getModel().oData; //get data from Model        var request = window.indexedDB.open("offline_db", 1); //Open DB    request.onupgradeneeded = function(event){ //Object Stores don't exist    var db = event.target.result;    var objectStore = db.createObjectStore("mydata"); //create object store  };  request.onsuccess = function(event){//Objet Stores exist  this.myDB = event.target.result;
// Write to local DB            var oTransaction = this.myDB.transaction(["mydata"], "readwrite");            var oDataStore = oTransaction.objectStore("mydata");            oDataStore.delete(1);            oDataStore.add(oData, 1); //1 is the key out-of-line  };  }

 

For read button, the code below will open the local DB & read object to get data saved in local DB.

Then update model with this data.

 

  handleRead : function (evt) {        var oJSONDataModel;        var oView = this.getView();        var request = window.indexedDB.open("offline_db", 1); //Open DB        request.onsuccess = function(event){//Objet Stores exist  this.myDB = event.target.result;
//Read from local DB            var oTransaction = this.myDB.transaction(["mydata"], "readwrite");            var oDataStore = oTransaction.objectStore("mydata");            var items = [];            oDataStore.openCursor().onsuccess = function(event) {    var cursor = event.target.result;          if (cursor) {              items.push(cursor.value); //add value to items table              cursor.continue();              }          else { //All data in items    oJSONDataModel = new sap.ui.model.json.JSONModel();    oJSONDataModel.setData(items[0]); //Create model with local DB data    oView.setModel(oJSONDataModel); //Update model used by view    oView.getModel().refresh(true);          }};        };  }

 

Application is now ready to do first test !!

- Deploy & run application.

- Click on save button

- Open chrome dev tools and check in resources tab "Indexed DB". "offline_db" should be there and in mydata, you should see data.

 

devtools.PNG

 

- Now, change form data or clear them.

- Click on read button, form should be updated with data stored in local DB

 

Now, we can use local storage to save data but to be able to use application offline, we have to cache all needed files.

 

 

5 - Cache application

 

To cache application, we have use cache.manifest to list files to cache in browser.

 

Create a file cache.manifest.

 

This file looks like this one :

CACHE MANIFEST
#Version 0.15
CACHE:
/index.html
/css/style.css
/resources/sap/ui/core/themes/sap_bluecrystal/library.css
/resources/sap-ui-core.js
/resources/sap/ui/core/library-preload.json
/resources/sap/m/library-preload.json
/resources/sap/ui/core/themes/sap_bluecrystal/library.css
/resources/sap/m/themes/sap_bluecrystal/library.css
/resources/sap/ui/core/themes/sap_bluecrystal/library-parameters.json
/resources/sap/m/themes/sap_bluecrystal/library-parameters.json
/resources/sap/m/messagebundle_fr.properties
/Component-preload.js
/manifest.json
/i18n/i18n.properties
/resources/sap/ui/layout/library-preload.json
/resources/sap/ui/layout/themes/sap_bluecrystal/library.css
/resources/sap/ui/layout/themes/sap_bluecrystal/library-parameters.json
/resources/sap/ui/core/messagebundle_fr.properties
/model/data.json
NETWORK:
*

Now, add it in index.html file

<html manifest="/cache.manifest">

 

Our application will be now cached by browser and be able to be used offline.

Check it with devtools in network tab that files are loaded from cache (after the first load !):

cache.PNG

 

Don't forget to change cache.manifest file when your update application to be sure that browser get the last version of files changed.

 

7 Useful links

 

Below several useful links:

- Going offline with SAP UI5

- Offline Storage | SAPUI5 | Using IndexedDB & JQuery API


How to use filter in Analytics Path Framework

$
0
0

I am recently doing self study on Analytics Path Framework and as a beginner I have finished some "Hello world" exercise and I have written down my steps to finish those "Hello world" project in these two blogs:


 

In this blog, I will learn how to use filter in Analytics Path Framework. Source Code of used CDS views could be found from previous blog mentioned above.


In APF Configuration Modeler, there is a filter with type Smart Filter Bar automatically generated.

clipboard1.png

When I change it to "Individually Configured Filters", it is then possible for me to create filter manually:

clipboard2.png

Then choose a property for filtering operand ( which will be used in OData filtering operation ) from drop down list:

clipboard3.png

And below OData request is responsible for rendering the value list in filter.

clipboard4.png

Let's observe the roundtrip in the runtime. Once we click execute button, the OData request maintained in above screenshot is called to retrieve the product id list, which will be used to render the drop down list of filter.

clipboard5.png

This is OData request response:

clipboard6.png

The response is used to render filter drop down list when hyperlink is clicked:

clipboard7.png

When we deselect Z02 and click OK button:

clipboard8.png

The filtering OData request is sent to backend to get filtered data. The chart is refreshed accordingly.

clipboard9.png

Creating a New Compound SAPUI5 Component

$
0
0

Hello *,

 

Creating a new SAPUI5 component is rather easy. In this short tutorial I intend to show you how to create the TreeMultiComboBox SAPUI5 compound component that you can see in the image below, or on JS Bin.

 

result.png

 

First a small disclaimer: The code you are about to see was written exclusively for demonstration purposes. It is not optimal and it is not to be used in any productive environment without further modifications.

 

The following json Object will come in handy for testing the component:

 

var oData = {    1: {        name: "item1",        value: "item1 description",        checked: "Unchecked",        subs: [            {                name: "subitem1-1",                value: "subitem1-1 description",                checked: "Unchecked"            },            {                name: "subitem1-2",                value: "subitem1-2 description",                checked: "Unchecked"            }]    },    2: {        name: "item2",        value: "item2 description",        checked: "Unchecked",        subs: [            {                name: "subitem2-1",                value: "subitem2-1 description",                checked: "Unchecked"            },            {                name: "subitem2-2",                value: "subitem2-2 description",                checked: "Unchecked"            }]    },    3: {        name: "item3",        value: "item3 description",        checked: "Unchecked"    },    4: {        name: "item4",        value: "item2 description",        checked: "Unchecked",        subs: [            {                name: "subitem4-1",                value: "subitem2-1 description",                checked: "Unchecked"            },            {                name: "subitem4-2",                value: "subitem2-2 description",                checked: "Unchecked"            },            {                name: "subitem4-3",                value: "subitem2-2 description",                checked: "Unchecked"            },            {                name: "subitem4-4",                value: "subitem2-2 description",                checked: "Unchecked"            }]    }
};
var oModel = new sap.ui.model.json.JSONModel();
oModel.setData(oData);

 

First, we begin by extending the core component. Inheritance in SAPUI5 is achieved by calling the extend method on the object that we wish to inherit from. For this controller I will extend the base controller object. This will guarantee that the new component will have all the base Controller features, and nothing more.

 

sap.ui.core.Control.extend(controllerName, configObject);

 

Where controllerName is the name of the new component and configObject is a JSON object that describes to SAPUI5 how the controller should behave. Inside the configObject we can add a metadata object where all the variables and events that belong to the controller are declared.

 

The metadata object is declared inside the configObject and is defined as such:

 

,metadata: {    properties: {      field1: Type    },    events: {        "eventName": EventDescriptionObject    },    aggregations: {        aggregate1: aggregate1DescriptionObject    },    associations: {        association1: association1DescriptionObject    }
},

 

For a thorough list of what is possible with the metadata object, please refer to the following page.

 

Field Types are strings that can be one of the following: string, int, float, int[].., sap.ui.core.CSSSize. SAPUI% will then create the getter and the setter functions on your behalf. So if we declare a property, say prop, then SAPUI5 creates two functions: setProp() and getProp() which can be overridden.


Events are defined by name only. For each event, three functions are defined for registering, unregistering and for firing it. For an event go, attachGo, detachGo, and fireGo are all declared.


Finally, aggregations and associations are other components that belong to the component that we are trying to create. Aggregations are those components that are to be treated as a part of the object while associations are components that are only associated with the object.


For our component the metadata object looks as follows:


metadata: {    aggregations: {        header: {            type: "sap.m.MultiComboBox",            multiple: false        },        pop: {             type: "sap.ui.ux3.ToolPopup",             multiple: false        },    },
}


Inside the configObject we can also define a few other methods that the SAPUI5 components can utilize. These methods are:


  1. init: executed once the component is initiated
  2. onAfterRendering: executed after every time the component is redrawn
  3. onBeforeRendering: executed before every time the component is redrawn
  4. onExit: executed when the component is destroyed


For a full list of what is available refer here.


The last important bit of the configObject is the rendererObject which contains the render function that creates the HTML.

 

    renderer: {        render: function (oRm, oControl) {            oRm.write("<div ");            oRm.writeControlData(oControl);            oRm.write("id=\"cntrl\">");            oRm.renderControl(oControl.getAggregation("btn"));            oRm.write("</div>");        }    }

 

Interestingly, the object oRm that is passed to the render function can write direct html (lines 3,5,7), or an entire controller (line 6). It is also worth mentioning that the controller data needs to be written to the html element that would contain the component using the method writeControlData, as in line 4.

How to use Push Notifications with iOS and Android Hybrid applications - part 1

$
0
0

Introduction

I've decided to write this blog to show clearly how you can enable your mobile devices, iOS or Android, to leverage the capabilities of Push Notifications provided by the HCPms.

There are of course several other blogs around, talking about this, but here I would like to start from the very scratch. In the past I've had a lot of difficulties trying to understand how to configure push notifications APIs, specially for iOS devices. Here I'm going to show how I managed to do it, so that anyone who wants to get to grips with this stuff.

 

The entire blog will go through the following steps:

  1. Enable push notifications for Android
  2. Enable push notifications for iOS
  3. Create an application on HCPms
  4. Create an application in SAP Web IDE
  5. Deploy the app to mobile devices
  6. Send push notifications and test the apps

 

This blog has been split in 3 parts:

LinkContent
This partStep 1 - Enable push notifications for Android
How to use Push Notifications with iOS and Android Hybrid applications - part 2Step 2 - Enable push notifications for iOS
How to use Push Notifications with iOS and Android Hybrid applications - part 3Steps 3 -> 6 - Create an app on HCPms, then another in SAP Web IDE, deploy the app to mobile services and test the app with some push notifications

 

 

Prerequisites

  • An account on SAP HANA Cloud Platform Trial Landscape
  • Hybrid Application Toolkit up and running
  • A mobile device (iOS or Android) to test our sample application
  • A MACOSX machine if you are going to test push notifications on iOS device

 

 

Let's get started!

 

 

Step 1 - Enable push notifications for Android

Follow these steps to request an API Key and get the Sender ID from the Google Developer portal. These two string will be used in HCPms in order to identify the application to send push notifications.

 

1 - Open your Internet browser and go to Add Google Services &amp;nbsp;|&amp;nbsp; Google Developers

01.png

 

2 - Enter an App name and the Android package name. This last one is very important because it will be used to identify our app in HCPms. Once done click on Choose and configure services

02.png

 

3 - Click on Cloud Messaging

03.png

 

4 - Click on Enable Google Cloud Messaging

04.png

 

5 - Write down somewhere the Server Api Key and the Sender ID because they will be used in HCPms configuration

05.png

 

 

Let's continue with the second part of this article. If you are NOT a MAC user or you are NOT interested using Push Notifications on iOS, you can skip the second part and go directly to the last part of this blog.

How to use Push Notifications with iOS and Android Hybrid applications - part 2

$
0
0

Introduction

 

This is the continuation of the blog started here. You only need this part if you are going to develop and test push notification for iOS devices. If you are not interested in this part you can simply skip it an go to the last part of this article.I've decided to write this blog to show clearly how you can enable your mobile devices, iOS or Android, to leverage the capabilities of Push Notifications provided by the HCPms.

 

 

This blog has been split in 3 parts:

LinkContent
How to use Push Notifications with iOS and Android Hybrid applications - part 1Step 1 - Enable push notifications for Android
This partStep 2 - Enable push notifications for iOS
How to use Push Notifications with iOS and Android Hybrid applications - part 3Steps 3 -> 6 - Create an app on HCPms, then another in SAP Web IDE, deploy the app to mobile services and test the app with some push notifications

 

 

Step 2 - Enable push notifications for iOS

For iOS is a little bit more complicated because instead of having just a server API key and a sender ID, you need to request a certificate and upload it to HCPms.

NOTE: For this step a MAC is required, you CANNOT perform these steps on a Windows machine.

 

1 - Open Keychain

 

2 - Click on Keychain Access --> Certificate Assistant --> Request a Certificate From a Certificate Authority…

08.png

 

3 - Enter your email, select Saved to disk and click on Continue

 

4 - The certificate (CertificateSigningRequest.certSigningRequest) will be saved somewhere on your disk

 

5 - Click on Done

 

6 - Go to Apple Developer portal  https://developer.apple.com/ and click on Account

06.png

 

7 - Enter your credentials

 

8 - Select your team if needed

 

9 - Click on Certificates, Identifiers & Profiles

07bis.png

 

10 - Click on on Identifiers --> App Ids

10.png

 

11 - Click on the "+" sign in order to create a new App ID

 

12 - Enter a name for your application (i.e. "Acme Push Notifications") and choose the right App ID Prefix.

12.png

 

13 - Normally you can use the App ID Prefix related to your Team ID. You can get it by looking in the Membership Information in Xcode

13.png

 

14 - In the App Services section, enable Push Notifications and click on Continue

14.png

 

15 - Push Notifications are now Configurable. Just click on Register

 

16 - Click on Done

 

17 - Search for the application ID that you have just created, select it and click on Edit

17.png

 

18 - Select Push Notifications and choose Create Certificate in the Development SSL section. We are just using our application for testing.

18.png

 

19 - A new window opens. Click on Continue

 

20 - Click on Choose File to upload your CSR certificate we have generated some steps ago.

20.png

 

21 - Select the certificate and click on Choose

 

22 - Click on Continue

22.png

 

23 - Your certificate is ready. Click on Download to download it somewhere on your machine

23.png

 

24 - The name of the certificate will be something like aps_development.cer.

24.png

 

25 - Click on Done

 

26 - Go in the folder where you downloaded the aps_development.cer and double click on it. The certificate is automatically installed in your Keychain. You can check that it's installed correctly by going in the Keychain tool, selecting the Login Keychain and looking in the Category section under My Certificates.

26.png

 

27 - The certificate is installed correctly! Now we need to export it in a different format so that it can be used to create a provisioning certificate. Right click on it in the Keychain Tool and choose to Export it somewhere on your machine

27.png


28 - Save it as a .p12 format

28.png

 

29 - Enter a password to encrypt the certificate

29.png

 

30 - The new certificate is stored on your machine

30.png

 

31 - On the Apple Developer portal, go to the Provisioning Profiles --> All section

31.png

 

32 - Click on the "+" sign to create a new Provisioning Profile

 

33 - Select iOS App Development  and click on Continue

33.png

 

34 - Locate your app in the combo box and click on Continue

34.png

 

35 - Select the certificate that you want to include in this provisioning profile. In this case I'm choosing just the one related to my name

35.png

 

36 - Select the devices on which the application has to run and click on Continue. You can select several devices

36.png

 

37 - Keep the proposed name and click on Continue

37.png

 

38 - Download the provisoning profile on your machine

38.png

 

39 - Click on Done

40.png

 

40 - Double click on the provisioning profile certificate you just downloaded. It will be automatically installed in XCode

41.png

 

41 - Open Xcode and go in Xcode --> Preferences

42.png

 

42 - Click on View Details

43.png

 

43 - Under the iOS Development session you should be able to see your certificate

44.png

 

Let's continue now with the last part of this article where you will learn how to use push notification with a hybrid application on your mobile devices.

How to use Push Notifications with iOS and Android Hybrid applications - part 3

$
0
0

Introduction

 

This is the continuation of the blog started here. This is the final part of this blog. In this part you will learn how to use the information collected in the previous two parts within HCPms and how to send push notifications to a hybrid application deployed to your mobile devices.

 

 

This blog has been split in 3 parts:

LinkContent
How to use Push Notifications with iOS and Android Hybrid applications - part 1Step 1 - Enable push notifications for Android
How to use Push Notifications with iOS and Android Hybrid applications - part 2Step 2 - Enable push notifications for iOS
This partSteps 3 -> 6 - Create an app on HCPms, then another in SAP Web IDE, deploy the app to mobile services and test the app with some push notifications

 

 

 

Step 3 - Create an application on HCPms

1 -  Go to your HCPms Administration page (https://hcpmsadmin-<your_trial_account>.dispatcher.hanatrial.ondemand.com). Replace the string "<your_trial_account>" with your HCP Trial Landscape account

 

2 - From the toolbar click on the button to create a new application

46.png

 

3 - Specify an Application ID and other parameters and click on Save

ParameterValue
Application IDcom.sap.pushnotific
NameAcme Products
TypeHybrid
DescriptionProducts for Push Notifications
VendorSAP
Security ConfigurationNone


NOTE: The application ID must be the same you used to create the Server API Key on Google and the certificate on the Apple Developer portal, otherwise the application will not be recognized as recipient of the incoming notifications.

47.png

 

4 - Click on the Back End tab and configure your back end connection. This step is not really required, because we are not going to consume data from the backend, but for the sake of completeness we are going to specify this information as well. I'm using here, as back-end URL, the public SAP Gateway service.

ParameterValue
Back-end URLhttps://sapes4.sapdevcenter.com/sap/opu/odata/IWBEP/GWSAMPLE_BASIC
Proxy-TypeInternet
Authentication TypeBasic Authentication

48.png

 

5 -  Click on the Push tab and here you have the possibility to configure the push mechanism. Depending on which platform you want to use you can configure push notifications for Apple or for Android. In my case I'm going to specify both, because I want to show you how to deal with both platforms.

So for Apple I'm going to specify that I want to use the Sandbox as APNS Endpoint (I'm not going on production right now!), I load the certificate I generated in the previous part and I enter the password I assigned to it.

For Android I just need to specify the API Key and the Sender ID I've got in the first part of this blog

When finished click on Save

49.png

 

 

6 - The application on HCPms is correctly configured.

50.png

 

 

 

Step 4 - Create an application in SAP Web IDE

Let's create now our hybrid application with SAP Web IDE which will be deployed on mobile devices to receive push notifications. The application I'm creating here is really basic, but feel free to extend it with the necessary enhancements.

 

1 - Open SAP Web IDE and go to File --> New --> Project From Template

 

2 - Choose a SAPUI5 Application

 

3 - Enter a name for the project (i.e. PushNotificationsDemo) and a namespace (i.e. com.push.notifications). The namespace I'm using here is important because it's referenced several times in the code you'll see later in this article. So I recommend you to use the same for this testing phase: if you want to specify a different namespace you will have to change the code accordingly. Don't forget to set this project as Hybrid Mobile Application

51.png

 

4 - Keep the proposed Type and Name for the view and click on Next

52.png

 

5 - Click on Finish

 

6 - Your application is ready. Starting the app you will only get a blank window with a title

 

7 - Inside the webapp folder create a new subfolder named util

 

8 - Inside the util subfolder create a new javascript file named PushController.js

 

9 - Paste the following code:

 

jQuery.sap.declare("com.push.notifications.util.PushController");
com.push.notifications.util.PushController = {  regSuccess: function(result) {  console.log("Successfully registered: " + JSON.stringify(result));  },  regFailure: function(errorInfo) {  console.log("Error while registering.  " + JSON.stringify(errorInfo));  },  resetBadgeSuccess: function(result) {  console.log("Badge has been reset: " + JSON.stringify(result));  },  processNotification: function(notification) {  console.log("Received a notifcation: " + JSON.stringify(notification));  if (sap.Push.isPlatformIOS()) {  var notif_alert = JSON.parse(notification).payload.aps.alert;  var notif_sound = JSON.parse(notification).payload.aps.sound;  var notif_badge = JSON.parse(notification).payload.aps.badge;  var notif_data = JSON.parse(notification).payload.data;  } else {  var notif_alert = notification.payload.alert;  var notif_sound = notification.payload.sound;  var notif_badge = notification.payload.badge;  var notif_data = notification.payload.data;  }  jQuery.sap.require("sap.m.MessageBox");  sap.m.MessageBox.show(  notif_data, {  icon: sap.m.MessageBox.Icon.INFORMATION,  title: notif_alert,  actions: [sap.m.MessageBox.Action.OK]  }  );  if (sap.Push.isPlatformIOS()) {  sap.Push.resetBadge(this.resetBadgeSuccess);  }  },  registerForPush: function() {  console.log("Device is = " + sap.ui.Device.os);  var sender = (sap.ui.Device.os.android ? "XXXXXXXXXXXX" : "");  console.log("Sender is [" + sender + "]");  console.log("attempting to register for notifications");  var nTypes = sap.Push.notificationType.SOUNDS | sap.Push.notificationType.BADGE | sap.Push.notificationType.ALERT;  sap.Push.registerForNotificationTypes(nTypes, this.regSuccess, this.regFailure, this.processNotification, sender); //GCM Sender ID, null for APNS  },  unregCallback: function(result) {  console.log("Successfully unregistered: " + JSON.stringify(result));  },  unregisterForPush: function() {  sap.Push.unregisterForNotificationTypes(this.unregCallback);  },  processMissedNotification: function(notification) {  if (notification) {  console.log("Received a missed notification: " + JSON.stringify(notification));  alert("Received a missed notification: " + JSON.stringify(notification));  }  if (sap.Push.isPlatformIOS()) {  sap.Push.resetBadge(this.resetBadgeSuccess);  }  },  checkForNotification: function(notification) {  setTimeout(function() {  console.log("Checking for notifications...");  sap.Push.checkForNotification(this.processMissedNotification);  // TODO: do your thing!  }, 0);  }
};

 

10 - There is one thing that you need to change in this file. It's the Sender ID you can find at line 39: you need to replace the string "XXXXXXXXXXXX" with the Sender ID you got in the first step

 

11 - Save the file

 

12 - Open the file webapp/controller/View1.controller.js, paste the following code and save the file:

 

sap.ui.define([  "sap/ui/core/mvc/Controller",  "com/push/notifications/util/PushController"
], function(Controller, PushController) {  "use strict";  return Controller.extend("com.push.notifications.controller.View1", {  onBeforeRendering: function() {  if (!sap.ui.Device.system.desktop) {  //push notifications  PushController.registerForPush();  alert("Registered for push!");  }  }  });
});

 

13 - Open the webapp/index.html file and add the following code just before closing the last <script> tag. Then save the file

 

  window.onload = function() {    console.log("In the onload function");  if (window.cordova || getUrlParameterName("companionbuster")) {    console.log("is cordova");    //push notifications            document.addEventListener("resume",com.push.notifications.util.PushController.checkForNotification,false);  }  }

66.png

NOTE: Again, pay attention to the fact if you used a different namespace you need to replace it around all the proposed javascript code. The same if you used different paths.

 

14 - Your application is ready. You just need to deploy it on your mobile device.

 

 

 

Step 5 - Deploy the app to mobile devices

Let's deploy it first on your local hybrid application toolkit.

 

 

1 - Start your HAT tool. You can check that HAT is running from the settings menu

53.png

 

2 - In SAP Web IDE right click on the name of the project and configure HAT settings

ParameterValue
App NamePushNotificationsDemo
App IDcom.sap.pushnotific
DescriptionPush Notifications Demo
Version0.0.1
PlatformsAndroid, iOS or both
Plugins --> KapselLogon Manager, Push
HCPms Hosthcpms-<your_trial_account>.hanatrial.ondemand.com

54.png

 

3 - We are ready to deploy the app on our devices. Let's start with iOS. If you don't need this part you can go directly to point 9. Do Deploy --> Deploy to Local Hybrid Application toolkit and wait until the project is successfully deployed

 

4 - Right click again on the name of the project and run it on the iOS Device

55.png

 

5 - The application starts on the device. Just click on Register. We are not going to authenticate with any backend because with this basic app we don't consume it

56.png

 

6 - Click on Disable Pascode and then on Submit

57.png

 

7 - You might be requested to allow this app to receive push notifications. Accept the request by pressing OK

58.png

 

8 - The application is successfully registered for receiving push notifications

59.png

 

9 - Do the same for Android if you need it

60.png

 

10 - For Android you need to specify if you want to use a Test or a Custom Key. For the purpose of this exercise, just choose to use a Test Key and click on OK

61.png

 

11 - Even the Android application is ready and registered for push notifications

62.png

 

 

 

Step 6 - Send push notifications and test the apps

All is ready now. We just need to find a way to send push notifications to our application on the two different devices. For this goal I'm going to present here 2 ways. The first way is to send push notifications through a NodeJS application; the second way is to use a REST Client like Postman that you can install as a pluing in Google Chrome. Both ways are valid and will send a post request to the notification end point.

 

1 - Let's start with the first way. You need NodeJS installed. I'm using version 5.4.1, but even older versions are fine.

 

2 - Create a new push.js file on your machine and fill it with the following code:

 

var requestify = require('requestify');
requestify.request('https://hcpms-<your_trial_account>.hanatrial.ondemand.com/restnotification/application/com.sap.pushnotific/', {    method: 'POST',  body: {"alert": "Information!", "badge": 1, "data": "You just received a push notification!", "sound": "soundval"},    headers: {        'Content-Type': 'application/json'    },    auth: {        username: '<your_trial_user>',        password: '<your_trial_password>'    },    dataType: 'json'
})
.then(function(response) {    // get the response body    console.log(response.getBody());    // get the response headers    //console.log(response.getHeaders());    // get specific response header    //response.getHeader('Accept');    // get the code    //console.log(response.getCode());    // Get the response raw body    //console.log(response.body);
});

3 - Replace the following strings

StringInformation
<your_trial_account>with your HCP Trial Landscape account
<your_trial_user>with your HCP Trial Username
<your_trial_password>with your HCP Trial password

 

4 - Save the file

 

5 - From the terminal window run the command "node push.js"

 

6 - The script is executed. In a few seconds your mobile devices should be able to receive the notification

63.png

 

 

7 - If you feel more comfortable to use Postman, just create a new request with the following parameters and click on Send

64.png

 

8 - The result should be exactly the same

65.png

9 - Congratulations! You successfully sent push notifications to your devices!

My last UI5 app in eclipse - How to migrate your UI5 Eclipse app to WebIDE

$
0
0

In this blog post we will show how you can migrate ui5 app that was developed in Eclipse into Web IDE.

Web IDE is the recommended tool for developing/extending/deploying UI5 apps either to the cloud or to your on-premise network. More details about WebIDE can be found here: Getting Started with SAP Web IDE

 

 

Prerequisites

 

 

 

Let's get started!

 

The app that we will migrated is a very simple one. This app will contain only one table elements which display data from ES4 backend system. The service that we will use is GWDEMO http://sapes4.sapdevcenter.com/sap/opu/odata/IWBEP/gwdemo and our app will output the list of sales order items which are exists under the SalesOrderCollection.

 

2016-05-23_10-44-03.png

 

Eclipse project structure vs. WebIDE project structure

 

Our eclipse project structure looks like the following:

 

2.png

 

While the structure for the same project in Web IDE will look like the following:

 

3.png

 

From the images above we can immediately see the differences. A new project in eclipse contains very basic files of view, controller and index.html while a new project in WebIDE contains more artifacts which are relevant like Component.js, manifest.json etc.

It is possible to create the same structure in eclipse but it will require some effort, something that WebIDE can give us for "free".

Note! UI5 project structure is influence by the UI5 runtime version. The project structure that we see above is the project structure for the latest and greatest version of UI5.

 

In this blog we will show how you can import an app that was developed in eclipse to WebIDE and run it without changing the project structure.

 

Export from eclipse and import to WebIDE

 

In eclipse

 

  • Select the WebContent folder under the root folder
  • Right click > Export

    4.png
  • In the dialog select Archive File and click Next
  • Uncheck the WebContent folder, expand it and select only the mylasteclipseui5app folder and index.html file which appear on the right side. Also make sure that Save in zip format is selected.
  • Specify under To archive file the path where the zip file will be saved. Make sure that the name of the zip file is MyLastEclipseUI5App.zip (relevant for this blog post)

    5.png
  • Click on Finish


In Web IDE

 

  • Right click on Workspace folder > Import > From File System
  • In the dialog browse for the file MyLastEclipseUI5App.zip (the one that was exported from eclipse) and make sure that Extract archive option is checked then click on OK

    7.png
  • A new folder with the name MyLastEclipseUI5App will be created under your workspace
  • Expand the root folder of the project until you see the index.html file

    8.png
  • Next we will cut all the content (files and folders) which exist under the WebContent folder and then paste it under the project root folder

    9.png

    10.png
  • Then delete all the unnecessary folders under the root folder (make sure that you delete the MyLastEclipseUI5App folder which exist under the root folder and not the root folder itself)

    11.png
  • At the end our app structure in WebIDE should look like the following:

    12.png

 

 

Add WebIDE artifacts

    • First we will need to create cloud connectivity configuration file in our project. this file will allow HCP front-end server to run our app with all the necessary resources (ui5 resources, our oData service etc.)
  • To  create the cloud connectivity configuration files, go trough the following steps:
    • Select the root folder
    • Right click > New > Cloud Connectivity Configuration

      13.png
    • A new file with the name neo-app.json will be created under our project root folder
  • Next we will need to create a destination in HCP cockpit that will point to ES4 system. We will not explain how to create a new destination here but we will show how you can import a new one to your HCP cockpit. To import a new destination into your HCP cockpit, go trough the following steps:
    • Download the ES4 file which attached to this blog post
    • Open your HCP cockpit by clicking on the following link: Cloud Cockpit
    • Select Destinations and then click on Import Destination button
    • Select the destination file that you download and then click on the Save button at the bottom of the page to save it

      15.png
  • Next we will need to add the destination to our cloud connectivity configuration file. To do so please go trough the following steps:
    • Double click on neo-app.json file to open it in the text editor
    • Add the following objects under the routes array

      16.png
    • The full neo-app.json file structure is attached to this blog post so please feel free to download it and compare it with yours.

 

 

 

Modify and run the app

 

The last thing that left now is to do some minor modification to our controller in order to run it on HCP. To do so please go trough the following steps:

  • Double click on SalesOrders.controller.js file to open it in the text editor
  • Go to the row where you initiate a new oDataModel object
  • replace proxy/sap/opu/odata/IWBEP/gwdemo/ with /sap/opu/odata/IWBEP/gwdemo (just remove the proxy at the beginning)
  • Change user and password parameters to your user and password

    17.png

  • Save the file
  • Select the index.html file and click on the Run button (at the top) to run the app

    18.png
  • If everything went well, you should see the same app but this time runs on HCP. From here you can deploy your app either to on-premise or to the cloud, use WYSIWYG to build your UI using a simple and intuitive drag and drop editor, write your code with WebIDE code editor which provide fast auto complete and syntax validation for both JavaScript and XML files, use GIT as your source control and more... and more...


That's it

A quick way to find which code has changed the UI5 application page title by debugging

$
0
0

In Fiori launchpad, the page title will display default value "Home". When I click a given tile to enter an UI5 application, the page title is changed for example from "Home" to "My Tasks".


clipboard1.png

Today I am dealing with an incident that once I have entered one application, the page title is not displayed correctly, see below error screenshot:

clipboard2.png

In order to fix this issue, I need to know exactly which line has changed the value of page title.

Since I don't have any clue why page title becomes like this when I enter the erroneous application, so my thought is if I can test with an application where the page title works correctly, and if I can find out the line of code for title value assignment, then I can set breakpoint on that line and debug back with erroneous application.

 

Thanks to the power of Chrome development tool, I can finish trouble shooting in a quite efficient way.

 

First step, Identify which html tag holds the value of page title. Open Chrome development tool, click Elements tab and locate the tag "title". Change its value and the change will refresh in browser immediately. This proves the fact that it is title tag which holds page title.

clipboard3.png

Then right click this title element, choose "Break on..." and mark all three options.

clipboard4.png

Now go back from application to launchpad by clicking back button, break point is triggered.

clipboard5.png

Navigate back to an application where page title display works. So it is Shell.controller.js who is responsible to change page title with variable sTitle. Now my task is to find out how variable sTitle is filled.

clipboard6.png

Go back to an outer callstack frame, sTitle comes from one attribute of object oMetadata. So where is oMetadata filled?

clipboard7.png

In line 892, now I understand: the oMetadataConfig comes from metadata configuration defined in Fiori application:

clipboard8.png

clipboard9.png

And this is i18n.properties file:

clipboard10.png


For a complete blog list of how I use Chrome development tool in my daily work, please find it from this blog: Chrome Development Tool tips used in my daily work.


Simple Mapping of UI5 Tree Table to JSON Model

$
0
0

I love trees

In my experience it's very common to need hierarchies in apps, and a tree is a great way to represent them.  Of course, we need to be careful where we use them, especially when it comes to mobile devices with small screens.  We should always have a good user experience in mind.  That said, I'm sure trees will find a place in many UI5 apps.

 

The problem

You can see the UI5 Treetable control in the Explored section of the UI5 SDK (sap.ui.table.TreeTable).  The sample app maps the control to a JSON model, and the JSON file begins like this

 

  1. {
  2.   "catalog":{
  3.   "clothing":{
  4.   "categories":[
  5.   {"name":"Women","categories":[
  6.   {"name":"Clothing","categories":[
  7.   {"name":"Dresses","categories":[
  8.   {"name":"Casual Red Dress","amount":16.99,"currency":"EUR","size":"S"},
  9.   {"name":"Short Black Dress","amount":47.99,"currency":"EUR","size":"M"},
  10.   {"name":"Long Blue Dinner Dress","amount":103.99,"currency":"USD","size":"L"}
  11.   ]},
  12.   {"name":"Tops","categories":[
  13.   {"name":"Printed Shirt","amount":24.99,"currency":"USD","size":"M"},
  14.   {"name":"Tank Top","amount":14.99,"currency":"USD","size":"S"}
  15.   ]},

 

This is what you might think of as a recursive JavaScript data format.  Each node in the structure can have an attribute called categories which contains an array of child node objects.  Each child node can similarly contain an array of its own children.

 

This kind of structure is common in the JavaScript world, but not so common in the ABAP world.  If I expand an HR org. structure with function module RH_GET_STRUC (or a PM functional location tree with PM_HIERARCHY_CALL_REMOTE) I will get the data in a flat format.  There will be one table row per node, with each row having an ID, a level, a parent ID and a text.

 

How can we convert that kind of data to the JSON format you see above?

 

The solution

 

Tomasz Mackowski has explained hereTreeTable Odata binding how you can map the TreeTable to an OData service.  Today I'd like to show you another approach, which might be simple for you to implement.  It maps the control to a JSON model, just like the sample used in the SDK.  If your hierarchy is small, and you don't need lazy-loading (i.e. you prefer eager-loading), it might be a good way to go.  Those SAP-standard function modules are quite efficient at returning a few levels of a tree in one go.  A large number of tiny, unbatched OData calls (a consequence of lazy-loading) can be disproportionately 'expensive'. You may be able to make an asynchronous call to get the tree data before the user reaches the point where the tree is shown, and hence they won't need to wait before using the tree.

 

With this approach the OData service can be very simple, returning the tree data in a flat structure of one entity per node.  As you can see, the JavaScript is very simple too.  This function does all the transformation we need, in about 25 lines.

 

transformTreeData: function(nodesIn) {

 

            var nodes = [];          //'deep' object structure

            var nodeMap = {};      //'map', each node is an attribute

 

            if (nodesIn) {

                var nodeOut;

                var parentId;

                for (var i = 0; i < nodesIn.length; i++) {

                      var nodeIn = nodesIn[i];

                      nodeOut = {      id: nodeIn.ID,

                                            text: nodeIn.Text,

                                            type: nodeIn.Type,

                                            children: [] };

       

                      parentId = nodeIn.ParentID;

 

                      if (parentId && parentId.length > 0) {

                          //we have a parent, add the node there

                          //NB because object references are used, changing the node

                          //in the nodeMap changes it in the nodes array too

                          //(we rely on parents always appearing before their children)

                          var parent = nodeMap[nodeIn.ParentID];

                          if (parent) {

                                parent.children.push(nodeOut);

                          }

                    } else {

                          //there is no parent, must be top level

                          nodes.push(nodeOut);

                      }

   

                      //add the node to the node map, which is a simple 1-level list of all nodes

                      nodeMap[nodeOut.id] = nodeOut;

            }

        }

 

        return nodes;

}

 

The only explanation required (I hope) is around the use of the nodeMap.  Each node must be added to the children array of its parent.  This could be tricky due to the recursive nature of the structure (how do we find the parent?).  To make it simple we create a 'map' (in fact just an object) which contains an entry for each node, keyed on the nodeId.  This map contains a reference to the node, not the node itself.  Therefore if we add a node to the parent in the map it is also added in the main structure (nodes).

 

The function transforms this (an array of simple objects)....

 

Before.jpg

 

..to this (a complex, recursive object), which matches the example from the SDK shown above

 

After.jpg

If we store this object in a JSON model we can map our TreeTable directly to it, and the result looks like this:


Result.jpg

 

Options are good

 

In many cases you will want your tree to lazy-load and it will be best to map the TreeTable control directly to an OData model.  I hope that this example will prove helpful for the 'other' times.  Perhaps the core transformation function will come in handy for whenever you want to use a deep, recursive JavaScript object, even without the TreeTable control.

 

 

 

Appendix: Build the demo app

 

If you would like to build a demo app, which you can then extend to meet your own requirements then then follow these steps.  To keep things simple we will use a local JSON-format file rather than an external datasource.

 

  • Log into Web-IDE.  Create a new project from the SAPUI5 Application template.  I used the name TreeTableDemo and the namespace com.scn.demo.treetable.  I left the view details at the default of type=XML and name = View1
  • Add the new functions readFile, transformTreeData and setModelData to Component.js (see attachment Component.txt).  NB Be careful with your commas, you must have one after every function except the last.  Add the following 3 lines to the end of the Init function:

              var flatData = this.readFile();

              var deepData = this.transformTreeData(flatData);

              this.setModelData(deepData);

  • Copy the contents of the attachment View1.view.xmlinto your View1.view.xml. NB You must be sure to include the extra xmlns entries, but don't alter your controllerName
  • Import the attached FlatData.txt into the model folder.  Rename it as FlatData.json
  • Add this line to your i18n.properties file: treetitle=My Team
  • Right-click on your project and choose Run->Run as->Web Application to launch a preview
  • Change the code, try new things, change the data in the JSON file, add an external datasource........

         

Mock non-OData RESTfull services using SAPUI5 MockServer

$
0
0

The SAPUI5/OpenUI5 MockServer component is a simple yet powerful tool to simulate the backend connectivity to a server. Any server.

 

We already know that the MockServer can simulate OData services. This is specifically useful during development and testing of OData-based SAP Fiori and SAPUI5 apps.

 

However, it is also possible to simulate non-OData RESTfull services with the MockServer. Since the MockServer simulates HTTP connectivity it can be used for simulation of any REST API.


In order to do that all you have to do is to instantiate the MockServer with your own custom mock requests instead of using the simulation API (that is OData specific).


For example:


Open Weather API

The 3rd party API that we will mock is the Open Weather API.  The specific URL that we will use in this post is described here: forecast5.

 

Create a (mocked) request handler

The documentation for SAPUI5 MockServersetRequestsgives a good description of the request structure:

method: “GET”|”POST”|”DELETE|”PUT”,

path: “/path/to/resource”,

response: function(xhr, param1, param2, …) { }


We begin to define our request according to this structure:

var oByCityName = {      method: “GET”,      path: new RegExp(“forecast\?q=(.*)”),      response: function(oXhr, sUrlParams) {            jQuery.sap.log.debug("Incoming request for Call 5 day / 3 hour forecast data");            var sCityNameAndCode = sUrlParams;            switch (sCityNameAndCode) {                  case “Shuzenji,jp”:                              oXhr.respondJSON(200, {}, JSON.stringify({                              "city":{"id":1851632,"name":"Shuzenji",                              "coord":{"lon":138.933334,"lat":34.966671},                              "country":"JP",                              "cod":"200",                              "message":0.0045,                              "cnt":38,                              "list":[{                                           "dt":1406106000,                                           "main":{                                          "temp":298.77,                                          "temp_min":298.77,                                          "temp_max":298.774,                                          "pressure":1005.93,                                          "sea_level":1018.18,                                          "grnd_level":1005.93,                                          "humidity":87                                          "temp_kf":0.26},                                         "weather":[{"id":804,"main":"Clouds","description":"overcast clouds","icon":"04d"}],                                         "clouds":{"all":88},                                         "wind":{"speed":5.71,"deg":229.501},                                         "sys":{"pod":"d"},                                         "dt_txt":"2014-07-23 09:00:00"}                                 ] }));               break;               default:                     oXhr.respondJSON(200, {}, JSON.stringify({});               }               return true;          }      }
};


Set up the MockServer instance
var oMockServer = new MockServer({
rootUri: “/api.openweathermap.org/data/2.5/”,
requests: [oByCityName, …]
});
//start the mockserver
oMockServer.start();

That's it

My way to UI5

$
0
0

As I recognized, many of my fellow UI5-developer are coming from the SAP-world and have a unique perspective and a different background than I have. I'm a frontend developer who used JavaScript with different frameworks like jQuery and Angular and CSS with things like LESS and Bootstrap. When we started to use SAP UI5 at my company, I tried to apply my knowledge from other areas to this new world. And in fact, many concepts are the same. I was used to modularized JavaScript, I knew databinding from angular, using MVC was part of my daily work and lots more.

 

So I tried to dig into UI5 and started to build an application from day one. The first steps were great. Since I was used to code everything on my own from scratch, this was exactly what I was doing here too. The big problems began, when I tried to display some data from an Odata-service in a list, make it clickable, navigate, select some items and call some more services. Basically, when I started a small, but real project with limited knowledge of the underlying concepts. It took me a lot of hours to get this small task to work the way everybody wanted it to be. Nevertheless, in the end it worked and everybody was happy.

 

Afterwards I took a step back and looked at my work. It was kind of ok, but not beautiful. That's when I started to learn it again from the beginning. I read a lot of blog posts here, tutorials and introduction in the concepts and the architecture of UI5 and Fiori. As you might have guessed, this opened my eyes. Once I learned how UI5 is intended to work, making any component run was way easier. To be honest, this turned my mind from "UI5 is ok" to "UI5 rocks and I don't want anything else". Everything I learned so far convinces me, that UI5 is the was to go and that there a many, many great ideas realized in a professional way.

 

I'm really looking forward to work more and more with UI5.

Git Basics in SAP Web IDE

$
0
0

We've created a new video that show the basics for beginners on setting up Git for your projects, using some of the latest features. You can now easily create a local Git repository for your projects without deploying to SAP HANA Cloud Platform, and you can connect that local repository to any remote repository.

 

The video shows a simple scenario for getting started, as well as showing how 2 developers might work together.

 

 

In the future, I hope to create Git videos for additional scenarios, like cherry-picking and merging.

 

Please let me know if you have any questions about this video, and if its clear how to connect to any remote Git repository, not just in SAP HANA Cloud Platform, or how to use the Git pane, or anything else you can think of.

 

P.S.: Also check out some of the great Git blogs created by Lidor Tal in the last year, like How to Use the SAP Web IDE Git Client or Working with Multiple Branches with the Git Client.

Build a JavaScript front-end MVC framework in 30 lines of code

$
0
0

In this article, I'll try to build a JavaScript front-end MVC 'framework' in 30 lines of code XD, with this new 'awesome' yet 'powerful' 'framework', you'll be able to create a button that has the 'Hello World' value on it, sounds like something fancy you wanna do? Let's get into it.

button.png

Let's first take a look at how our button is implemented with this new 'framework'.

 

(function(){    new Controller(function() {        var myButton = new View({            type: 'button'        });        var myModel = new Model();        myModel.bindElement(myButton);        myModel.setData('Hello World');    });
}())

 

Here we created a controller, within the controller, we created a button control by instantiating the View class, then we created a new model and set its data to 'Hello World'. That's the MVC that we all familiar with, in the most simplicity terms, View for presentation, Model for holding the data, and Controller that clues the Model and the View together.

 

 

With that in mind, we now code up this 'framework'.

 

 

First, we'll need three 'class', Model, View, and Controller.

 

 

function Model() {}
function View() {}
function Controller() {}

 

Let's work on the View first, the View class has one instance method, as its name suggested, it creates a html element, then put it on the page.

 

function View(metadata) {    return this.render(metadata.type);
}
View.prototype.render = function(type) {    var element = document.createElement(type);    document.body.appendChild(element);    return element;
};

 

Next, we'll move on to the Model.

 

function Model(data) {    this._data = data;
}
Model.prototype.setData = function(data) {    this._data = data;
};

 

The first iteration is very straight forward. Next, we'll take care of the interaction between the Model and the View, here's what we'll want to accomplish:


  • User should be able bind the view to the model.
  • The view should be updated automatically when there's changes to the model which the view was binded.

 

 

In order to do that, that's where we introduce the observer pattern into our framework.

 

function Model(data) {    this._data = data;    this._observers = [];
}
Model.prototype.setData = function(data) {    this._data = data;    this._observers.forEach(function(observer) {        observer.call(this, data);    }, this);
};
Model.prototype._addObserver = function(observer) {    this._observers.push(observer);
};
Model.prototype.bindElement = function(element) {    this._addObserver(function(data) {        element.innerHTML = data;    });
};

 

Dang, dang, dang! The Model class has two public instance methods, `bindElement` and `setData`. (as a side note, as the best practice, we probably should use the JavaScript modular pattern, to hide the private attribute and method, expose only the public ones, but we took the easy way out for simplicity sake). What the `bindElement` method essential does, is to add the function that changes the view to the observer queue, notice the view (element) is kept inside of the inner function with JavaScript's closure mechanism, which enables the view (element) be available later after the `bindElement` method had been executed and popped out of the stack. And the `setData` method loops through the queue, executes the observer (function), as the result of it, our view get updated, mission accomplished XD

 

yes.png

 

Lastly, again, we'll take a easy way out with our Controller class.

 

function Controller(callback) {    callback();
}

 

Here's the final version of our 30 lines 'framework'.

 

 

function Model(data) {    this._data = data;    this._observers = [];
}
Model.prototype.setData = function(data) {    this._data = data;    this._observers.forEach(function(observer) {        observer.call(this, data);    }, this);
};
Model.prototype._addObserver = function(observer) {    this._observers.push(observer);
};
Model.prototype.bindElement = function(element) {    this._addObserver(function(data) {        element.innerHTML = data;    });
};
function Controller(callback) {    callback();
}
function View(meta) {    return this.render(meta.type);
}
View.prototype.render = function(type) {    var element = document.createElement(type);    document.body.appendChild(element);    return element;
};

 

You could also find this article on my github repo GitHub - j1wu/openui5-source-code-study: Explore the magic behind SAPUI5 / OpenUI5

Viewing all 789 articles
Browse latest View live




Latest Images