Quantcast
Channel: SCN : Blog List - SAP BusinessObjects Design Studio
Viewing all 646 articles
Browse latest View live

Custom CSS made easy in SAP BusinessObjects DesignStudio 1.6

$
0
0

The latest release of SAP BusinessObjects Design Studio - 1.6 has a number of exciting new features and enhancements that makes it much easier to develop a dashboard application.

Although the tool has a whole new set of features and improvisation to offer, there was one improvisation which I thought was very notable. Every SAP BusinessObjects Design Studio developer would know the difficulty in uploading the updated CSS file and deleting the old ones every time he/she makes a change to the CSS code. There were days when I would delete the wrong CSS files and I’ve felt it would be great to have an option edit CSS directly in the client tool itself. And guess what? We now have a really cool enhancement with custom CSS.

 

How was it before SAP BusinessObjects Design Studio 1.6 ?

CSS stands for Cascading Style Sheets which describes how every HTML elements are to be displayed in the screen.  Every component in SAP BusinessObjects Design Studio has its own CSS.

External Style sheet which is written by the application developer is called Custom CSS.

When it comes to creating an analysis application in SAP BusinessObjects Design Studio, or any dashboard for that matter, it is very important to concentrate on the look and feel of the dashboard application. In order to make the dashboard aesthetically enhanced, an application developer has to spend a good amount of time in writing the CSS for the application.

In this process of developing an application for every trail / change in the UI, the developer has to undergo the following steps:

  1. Open the CSS file
  2. Make the changes that are required
  3. Save the file again
  4. From SAP BusinessObjects Design Studio client tool, upload the CSS file to the platform (BusinessObjects Platform or HANA Platform).
  5. Delete the previous versions of the CSS file already uploaded.

This process is not very tedious but is really time consuming. Now with the release of SAP BusinessObjects Design Studio 1.6, we have got a new way to edit the CSS and save it in the application on the fly.

 

What’s New with Custom CSS?

In order to demonstrate this improvised feature, I have created a simple application using the Blank template available in SAP UI5 templates and added a textbox component to the application.

 

new application window in SAP BusinessObjects DesignStudio 1.6.png

     Screenshot: “new application window in SAP BusinessObjects DesignStudio 1.6”

 

 

To provide better UI for the components, we need to create a CSS file for this application. Click on the Custom CSS option in the application properties. This opens an “Open CSS” window.

Till the previous version of SAP BusinessObjects Design Studio, we had to only upload the CSS files from the local system to the desired file repository in the platform. Now in SAP BusinessObjects Design Studio 1.6, we can directly create a new CSS file in the platform by clicking the add Custom CSS icon in the “Open CSS” window.

Open CSS window in SAP BusinessObjects DesignStudio 1.6 and 1.5.png

 

Screenshot: “Open CSS window in SAP BusinessObjects DesignStudio 1.6 and 1.5”




 

Add CSS file in SAP BusinessObjects DesignStudio 1.6 directly to the platform without uploading from local system.png

  Screenshot: “Add CSS file in SAP BusinessObjects DesignStudio 1.6 directly to the platform without uploading from local system”



The uploaded CSS file can now be editedin two ways:

 

1.       Clicking the icon in the header pane

 

SAP BusinessObjects DesignStudio 1.6 provides a shortcut icon in the header pane to edit the CSS file uploaded. To do this, you can click on the “Edit Custom CSS” icon (as shown in the picture below).

 

Edit Custom CSS option from shortcut icon in SAP BusinessObjects DesignStudio 1.6.png

Screenshot: “Edit Custom CSS option from shortcut icon in SAP BusinessObjects DesignStudio 1.6”

 

 

2. By clicking on edit option in the Custom CSS selection in the Application properties.

 

Edit Custom CSS option from application properties in SAP BusinessObjects DesignStudio 1.6.png

 

A new view/tab opens next to your analysis application where you can edit the CSS code.

Here I have declared a class “.textbox” and have provided various styles for the component.

Edit Custom CSS window with CSS for textbox component in SAP BusinessObjects DesignStudio 1.6.png

 

Screenshot: “Edit Custom CSS window with CSS for textbox component in SAP BusinessObjects DesignStudio 1.6”


The CSS editor view provides various pre-set options of various CSS properties which are  listed in the properties pane.  These options are grouped into various categories such as Aural, Box model, Colors and Backgrounds, Fonts, Generated Content/List , Paged Media , Tables, Text , User Interface and Visual.



 

CSS properties grouped into various categories in SAP BusinessObjects DesignStudio 1.6.png

Screenshot: “CSS properties grouped into various categories in SAP BusinessObjects DesignStudio 1.6”


These properties are displayed as two columns - property and value. With this option, you can just enter the values across the respective properties and generate the Custom CSS file as per the requirement. This feature saves lot of time spent for the syntax and typing the CSS scripts.

Once you have finished writing the CSS scripts, save the file by clicking on the save icon or by clicking on the Menu bar Application -> Save.

Now the component has to be assigned to the respective CSS class names. After  this step, refresh the application for the CSS to be  applied on the analysis application.

 

Custom CSS applied to textbox component in SAP BusinessObjects DesignStudio 1.6.png

 

   Screenshot: “Custom CSS applied to textbox component in SAP BusinessObjects DesignStudio 1.6”

 

 

Except for the blank template in SAP BusinessObjects Design Studio, every other template comes with a CSS file by default, in which the CSS scripts are written for the various components in the application.

 

Default Custom CSS for basic analysis template in SAP BusinessObjects DesignStudio 1.6.png

Screenshot: “Default Custom CSS for basic analysis template in SAP BusinessObjects DesignStudio 1.6”


 

So how does this enhancement help?

  1. There is no need to upload the CSS file each time for various iterations.
  2. The development time for an analysis application or a dashboard will reduce considerably.
  3. With the wide list of CSS properties, it is easy for application developers to key in the values across the respective properties.

 

This small yet extremely useful feature (my saviour feature) would help any developer in saving time and increasing his/her productivity. It feels amazing that SAP also concentrates on minute enhancement s of this kind which really aids in ease of use of the tool!  Look out for more update on new features of SAP BusinessObjects DesignStudio 1.6  here


DataGenius: Homicide worldwide

$
0
0

Bringing the data into life is one the most awesome work to do, to get some insightful information hidden between the neurons under the web of data.


The Homicide Rate Dataset contains country-level data on the total recorded intentional homicide rate (per million population) per year, from 1995 to 2008.

The United Nations Office on Drugs and Crime (UNODC), one of the most accredited sources for crime statistics at the global level, defines intentional homicide as "unlawful death purposefully inflicted on a person by another person."


Source: http://web.worldbank.org/WBSITE/EXTERNAL/TOPICS/EXTSOCIALDEVELOPMENT/EXTCPR/0,,contentMDK:22488819~menuPK:6835249~pagePK:148956~piPK:216618~theSitePK:407740,00.html



This shows the homicide rate in East Asia and Pacific region against the homicide rate in Africa.

 

This dashboard gives flexibility of drilling down to a particular country and year-wise seamlessly, hence the above screenshot shows the trend of rate of homicide in Australia against that of South Africa.

- See more at: https://ideas.sap.com/D30379#sthash.gHs0Iq5R.dpuf

DataGenius- World Development Indicator - Child welfare

$
0
0

World Development Indicators (WDI) is the primary World Bank collection of development indicators, compiled from officially-recognized international sources. It presents the most current and accurate global development data available, and includes national, regional and global estimates

 

My analysis is primarily children centric.

WDI_Children.jpg

At present there are 1.9 billion children in the world accounting to 27 % of total population with so little focus on the primary needs of children – education and health

Region wise Primary School dropouts:

Out-of-school children of primary school age. Total is the total number of primary-school-age children who are not enrolled in either primary or secondary schools. The analysis shows that South Asia and Sub Saharan Africa regions are have maximum dropouts with nearly 10 million and 32 million compared to North America where the dropout is nearly just 1 million.

 

PrimarySchool_Dropouts.JPG

HIV infected children in the age group on 5-14:

Nigeria and South Africa are worst affected. Percentage increase incidents reported in Nigeria from 2000 to 2014 is about 100% and in South Africa 126%, which is alarming!

HIVInfected.JPG

 

Mortality Rate:

Neonatal mortality rate:

Number of neonates dying before reaching 28 days of age, per 1,000 live births in a given year.

Mortality under 5:

The probability per 1,000 that a newborn baby will die before reaching age 5.

 

The analysis shows that the mortality rate in South Asia and in Sub Saharan Africa is higher than the world’s average.

MortalityRate.JPG

Thanks,

Sushma

How to create a facet navigation in SAP BusinessObjects Design Studio ?

$
0
0

Did you ever wonder how to create a facet style navigation like in SAP BusinessObjects Explorer with SAP BusinessObjects Design Studio ?

 

Perhaps you were trying to create a set of cascading filter in Design Studio and had to create a set of scripting functions to do so ?

 

Well... there is a much easier solution, called a Facet Filter.

 

With our upcoming release of our Extensions for SAP BusinessObjects Design Studio these requirements can be fulfilled with a few clicks.

 

facet_082.jpg

 

So lets start with our Data source in Design Studio, which has several data sources and several measures.

 

facet_080.jpg

When I assign the data source to the Facet Filter component, I get the default layout with a Facet for the list of measures and a facet for each of the dimensions listed in the Initial View of the data source.

 

In addition all of the facets are shown as a list by default (we come back to that) and the selected measure value is shown next to each of the dimension members.

 

facet_083.jpg

 

So now lets take a look at the configuration options - here shown for the dimension Product Group.

 

First of all I can configure the height and width for each of the facets. I can then also configure the Display - which allows me to choose between the Key, Text, or Key and Text for the dimension members.

 

The Control property allows me to choose how I would like to visualize the list of members. I can choose between a List, Radio Buttons, Check Boxes, or Chosen.

 

facet_084.jpg

 

If we take a screenshot above:

- Key Figures are shown as Radio Buttons

- Product Group is shown as Check Boxes

- Product Category is shown as Radio Button

- Product is shown as "Chosen" - basically listing the selected items only

- Country is shown as a list

 

In addition you can configure the option to include a search option, include an All Member, and to enable or disable the display of the measure value.

 

Automatically the Facet Filter will work in a cascading fashion as you can see in the small video at the end.

 

All that is left to do is to enter the name of the target data source and my facet filter is ready to go.

 

DataGeniusIV - Daughters of India - Trends in selective abortions of girls

$
0
0

Abortion is possibly the most divisive women's health issue that policy makers and planners face particularly in developing countries where safe abortion facilities are not available to most women. The health risk of abortion multiplies if a woman has to face it repeatedly. Given the fact that women in India have poor health, the chances are very high that they may not only experience abortion, which includes both spontaneous and induced abortion, once but perhaps more than once.

 

It was estimated that during 2001-07, the number of girls that were missing in India on account of prenatal *** selection was 6 lakh per year (on average), or 1600 per day (on average). States such as Uttar Pradesh, Bihar, Rajasthan and Maharashtra together account for nearly 4 lakh out of the 6 lakh girls missing at birth in the country on an annual basis.During 2001-07 for the country as a whole, on an average nearly 5 percent of female births did not occur because of prenatal *** selection.

 

Missing_Female_Birth.png

 

The NFHS collected information on abortion by asking two questions to ever married women. The questions were: "Have you ever had an abortion?" And, if "yes", "How many times have you had induced abortions and spontaneous abortions?" The major limitation of the NFHS data on abortion is that it provides only lifetime estimates of abortion. Of the total sample size of 89,777 the number of women who reported to have experienced abortion irrespective of the type of abortion, at any time during their reproductive span prior to the date of survey, was 12,928 (14.5 per cent). The present analysis pertains to these women only. Not only were the induced and spontaneous abortion rates high in Delhi, even the number of women reporting to have experienced abortions wag high: 27 percent of all women who reported to have had an abortion belonged to Delhi.

No_of_abortions.png

In India, the *** ratio for the second birth, when the firstborn is a girl, is much lower than if the firstborn is a boy. Selective abortion of female foetuses, especially for pregnancies following a firstborn girl, has increased substantially in India.

A natural variation of *** ratio is taken as 950 to 975 girls per 1000 boys, based on ranges reported in most high income countries where social pressures for fewer females do not exist.

Conditional_Second_Birth.png

 

NFHS_Data.png

Data taken from below sources:

http://www.ncbi.nlm.nih.gov/pmc/articles/PMC3166246/

http://countryoffice.unfpa.org/india/drive/SRBBooklet.pdf

 

See more at: https://ideas.sap.com/D30021#sthash.6b95v33C.dpuf

Design Studio 1.6 - View on Scorecard Component

$
0
0

This is an introduction of the new scorecard component.

 

The scorecard component introduced in release 1.6 will allow you to define highly customizable tables. You will have influence on all visualization properties for the content and more content types (small charts) as well. Binding any property of the content to the data view is allowing you to react on some values / master data and make any property value conditional.

 

The Idea in Overview

The main idea behind the scorecard component is the mapping between the data source and the visualization. The visualization is based on a table, so what you can define are columns of the table. A column has cell content with type, header and some general properties. A property has a value which can be maintained directly or bound to the data set using the known data selection model -> this is the binding concept. Every property is independent of each other, but you can make them a kind of dependent if you bind them to the same data cell value.

 

Selection for Row Scope

Before you can start definition of the scorecard, you need to understand the Row Scope concept. The row scope is defining how many rows you will have in the scorecard and which data are accessible for you in a single row. You need to select a dimension from row dimensions and then the scorecard will get as many rows as many combinations of the selected dimensions are available.

 

Property Binding

To fill a property with value you can bind it to a chosen aspect of a data source. It can be data cell, some metadata or master data. Of course, the majority of the properties must use the conditional bindings to translate the selected value from data source to the target value. E.g. you want to set the background color to RED when conditional format (BEx Exception) has value 5. For this you have to map it, 5 => RED. then the system will evaluate the value and match the conditional binding to fill in the correct color value.

 

Example on Simple Data Source

We will try to visualize the following data source (here as crosstab) with scorecard, reducing number of rows and compacting the information.

 

Crosstab. In my data source I have 3 dimensions in rows (Region / Product Group / Customer) and 2 dimensions in columns (Year Quarter and Measures). For the row scope we can select only from dimensions from rows. In my example I will select the row scope "up to Product Group". What does this mean? The scorecard will be defined by multiplied members of those 2 dimensions, the number of rows will be defined by number of members of the Regions by Product Group. Result members are counted as standard members.

sc-1.PNG

 

Scorecard. When the row scope is defined, you can generate an initial scorecard. What you get is this (see documentation for the generation routine).

sc-2.PNG

The columns are as below:

1. the members of "Region"

2. the members of corresponding "Product Group"

3. the "Result Member" for the dimension "Billed Quality" (as this is the first one in the measures)

4. a line chart for members of dimension "Customer".

5. comparison chart (which in this case makes not much sense, so we delete this column).

 

Few Changes to Achieve my View

Now, having a scorecard, I want to achieve a bit different view. I want to have the column chart in column 4 and I want to have line chart for the dimension "Year Quarter" to see the change between Q4 and Q1.

 

The first change is simple, we just need to change the chart type. all other properties stay.

 

The second change is also simple, we add new column and defined this to the type "Trend Chart, Line". The most important setting is the selection of the content for "Actual Values". For this, we use the binding type "Multiple Cell Values" (the only one for multiple values) and select the cells we want:

 

sc-7.PNG

 

What is important in this selection dialog is, the dimensions which are in tow scope do not matter. This is because those have values assigned by the rows. The dialog is visualizing the selection, but you can logically ignore it. Then, as we want to have all members of the dimension "Year Quarter", we will not touch this dimension, the system will add corresponding selection for it. We just need to specify the others dimensions to fix their members - in this case, the result for "Customer" and "Sale Value" for measures.

 

This is the effect of the selection:

sc-4.PNG


Additional Settings which helps you in Comparison.

Making the settings as above, you will see the line - but you need to be aware about the scale. The scale is based on each row values. It means you see the direction in each row (increasing or decreasing) but you cannot compare the rows with each other. For this, you can use the fixed values for minimum and maximum.

sc-8.PNG

Then, you can see the chart like here:

sc-9.PNG

or as columns, changing to columns and adjusting the fixed scale:

sc-10.PNG

I hope this will help with startup, I will try to provide more examples on different scenarios.

 

The example can be downloaded from this link:

applications/SCN_SCORECARD_1-20151203125915.zip at master · org-scn-design-studio-community/applications · GitHub

(download as raw content and import in Design Studio)

Design Studio 1.6: View on Offline Data (CSV files)

$
0
0

as there was already a discussion on this topic -SAP Design Studio 1.6 local mode CSV datasource not working - here some insides on the CSV data source in Design Studio 1.6.

 

What is this for?

The first question is - why this CSV data source and what is this for? As you can take from the documentation, the csv data source is available in the local mode and cannot be used when working on any platform. This is already "restricting" the usage of this feature. It is targeted for offline demonstrations, work with sample data and show cases of applications for POCs.


When using for some CSV based local reports, it is working good for this as well. You need to consider only that because of the "demo" scope, the performance of bigger data sets can suffer.


The difference to SDK and Community CSV data sources

There are two interesting difference to the SDK data sources which can provide CSV access as well.


The first one is, this CSV is fully underneath of BICS data access layer - it means also components which can be assigned only to real data sources can use it. By this, the full (basic) function scope can be used - including filter components and crosstab visualization.

 

The second one is, the data source contains not only the data, but also metadata and possible hierarchies for the data. This is helpful for use in the components (eg what is dimension and what is measure) and allows more functions than the SDK CSV data sources. With this we can come to the structure topic, as this is where many fail...

 

The structure

The CSV data source consists of 2 files, the data (*.csv) and the metadata (*_metadata.csv). Without the second file it will not recognize the CSV file as CSV data source. In addition, you can find also some state file (*.xml) which describes the changes made in the initial view editor on top of this data source.

 

The content of *.csv file

On the first view it is a normal semicolon separated file.

 

here are the first 2 lines of the example used in blog /community/businessobjects-design-studio/blog/2015/11/23/design-studio-16--view-on-scorecard-component

YEAR_QUARTER;CUSTOMER;CUSTOMER;LONGITUDE;LATTITUDE;CITY;PRODUCT_GROUP;PRODUCT_GROUP;DISTR_CHANNEL;DISTR_CHANNEL;AREA_CODE;AREA_CODE;REGION;REGION;BILLED_QUANTITY;BILLED_QUANTITY;SALES_VALUE;SALES_VALUE;PRICE;PRICE

 

201403;DS1;Sandstone  Ltd;-91.2547266;30.36812929;BATON ROUGE;DS10;Bag & Outdoor;0;Internet;8050322;Louisiana;80503;South East;1372;PC;15219;USD;11.09;USD

On second view, you can see that this structure has more content then you can expect - like duplicate columns for the same dimension, with text, key and attributes.

 

The first line is basically a description - but also here it is the "technical name" of the column. This is the main link to the corresponding metadata file.

The second and next lines are simple data which belongs to the result set.

 

Specialty of the content

When you check the first line, you will see duplicates..

201403;DS1;Sandstone  Ltd;-91.2547266;30.36812929;BATON ROUGE;DS10;Bag & Outdoor;0;Internet;8050322;Louisiana;80503;South East;1372;PC;15219;USD;11.09;USD

201403;DS1;Sandstone  Ltd;-91.2547266;30.36812929;BATON ROUGE;DS20;Accessories;0;Internet;8050322;Louisiana;80503;South East;8940;PC;21110;USD;2.36;USD

201403;DS1;Sandstone  Ltd;-91.2547266;30.36812929;BATON ROUGE;DS30;Office;0;Internet;8050322;Louisiana;80503;South East;7770;PC;16201;USD;2.09;USD

 

and this is because the CSV file is containing the data and also the masterdata in the same file. E.g. the customer "DS1" is having text "Sandstone  Ltd" and all 3 lines are having the same value. From this perspective it is probably not the most optimal format for CSV. The good news is, you can skip the columns if you do not distinguish on key text and do not need any atributes.

 

The content of *metadata.csv file

Now, let's look into the metadata file. Here is the full content:

 

/rows are numbered by me for explaination/

01 <<BEGIN OF METADATA>>;;;;;;

02 <<BEGIN OF ROLESUPPORT>>;;;;;;

03 Role;Name;Description;Field;Referenced Characteristic;Presentation;Data Types

04 CHARACTERISTIC;YEAR_QUARTER;Year Quarter;1;;KEY;STRING

05 CHARACTERISTIC;CUSTOMER;Customer;2;;KEY;STRING

06 CHARACTERISTIC;CUSTOMER;Customer;3;;TEXT;STRING

07 ATTRIBUTE;LONGITUDE;LONGITUDE;4;CUSTOMER;KEY;STRING

08 ATTRIBUTE;LATTITUDE;LATTITUDE;5;CUSTOMER;KEY;STRING

09 ATTRIBUTE;CITY;City;6;CUSTOMER;KEY;STRING

10 CHARACTERISTIC;PRODUCT_GROUP;Product Group;7;;KEY;STRING

11 CHARACTERISTIC;PRODUCT_GROUP;Product Group;8;;TEXT;STRING

12 CHARACTERISTIC;DISTR_CHANNEL;Distribution Channel;9;;KEY;STRING

13 CHARACTERISTIC;DISTR_CHANNEL;Distribution Channel;10;;TEXT;STRING

14 CHARACTERISTIC;AREA_CODE;Area Code;11;;KEY;STRING

15 CHARACTERISTIC;AREA_CODE;Area Code;12;;TEXT;STRING

16 CHARACTERISTIC;REGION;Region;13;;KEY;STRING

17 CHARACTERISTIC;REGION;Region;14;;TEXT;STRING

18 KEYFIGURE;BILLED_QUANTITY;Billed Quantity;15;;VALUE;DOUBLE

19 KEYFIGURE;BILLED_QUANTITY;Billed Quantity;16;;UNIT;STRING

20 KEYFIGURE;SALES_VALUE;Sales Value;17;;VALUE;DOUBLE

21 KEYFIGURE;SALES_VALUE;Sales Value;18;;CURRENCY;STRING

22 <<END OF ROLESUPPORT>>;;;;;;

23 <<BEGIN OF FORMAT SETTINGS>>;;;;;;

24 TYPE;VALUE;;;;;

25 GROUP SEPERATOR;,;;;;;

26 DECIMAL SEPERATOR;.;;;;;

27 DATE SEPERATOR;/;;;;;

28 STANDARD CURRENCY;$;;;;;

29 STANDARD TIMEZONE;GMT;;;;;

30 <<END OF FORMAT SETTINGS>>;;;;;;

31 <<BEGIN OF HIERARCHIES>>;;;;;;

32 Type;Name;Reference Characteritic;Level Count;;;

33 LEVEL;DistributionChannel;PRODUCT_GROUP;1;DISTR_CHANNEL;;

34 LEVEL;ProductGroupHier;DISTR_CHANNEL;1;PRODUCT_GROUP;;

35 LEVEL;Region;AREA_CODE;1;REGION;;

36 <<END OF HIERARCHIES>>;;;;;;

37 <<END OF METADATA>>;;;;;;

 

How to understand this and how to recreate for really custom CSV content.

 

Rows 03 to 21 are the most important for simple content.

Row 03 is description

Row 04 is introducing a characteristic (dimension) "YEAR_QUARTER" and it is containing the KEY. This dimension is very simple (only having the key)

Row 05 is introducing a dimension "CUSTOMER" and this is more complex.

Row 06 is adding the "TEXT"

Row 07 - 09 are introducing attributes linked to the dimension "CUSTOMER"

Rows 10 - 17 are similar for other dimensions

Row 18 is introducing a keyfigure (measure). Those will be "cummulated" in the Measure DImension

 

Rows 24 - 29 are containing some metadata to the metadata ;-)

Rows 32 - 35 are containing hierarchies (in this case some basic one)

 

In general you can try to define your  own CSV file and create corresponding metadata for it. As you see, the metadata file is not too complex and removing many lines you do not need you can simulate it of any custom CSV content. Also, the naming here is a kind of "BW-based", you can see characteristic and keyfigures instead of dimensions and measures - but this is how BICS work internally, so no not confuse yourself.

 

How to create such CSV files?

The simplest way is to add standard data source from any system into Design Studio and then do following:

 

1. open "initial view editor" (menu -> "Edit Initial View")

2. press "Extract CSV Data"

ex1-.PNG

and the files will be created.

 

Now, you can use the new files to add new CSV data source. You can chose such files from anywhere, those will be always copied to your application folder.

 

The file *.xml

If you have edited something in the initial view editor on this data source, you will get additional file *.xml but this one cannot be created by yourself for custom CSV, so it is not so important. This file is basically including all information about changes in the data source  (filters, drill down, settings for results etc). It means, if you do not want to start always from scratch, first edit the data source, save and then export to csv.

 

Any more questions?

Design Studio 1.6: View on Global Filters in Online Composition Dashboards

$
0
0

The global filters - a functionality frequently requested when talking about the online composition scenario.

 

The issue

Using the components "Fragment Gallery" and "Split Cell Container" you an easily allow users to create own fragments (called also smart objects) and dashboards. The only issue (or feature) was - every single fragment contains of the visualization and data source with all filter values. Now, when placing into split cell container it stays on the filters which were assigned at the creation time. Change of those is not possible at all, as there is no access to the data source (as those were dynamically created at runtime and have some generated name).

 

The solution

There is a new function in Split Cell Container -

 

SPLITCELLCONTAINER_1.getDataSources();

 

Using this function you can now access the data sources included in the split cell container.

 

In connection with Filter Panel function

FILTERPANEL_1.setDataSource(dataSourceAlias);

you can take the first data source and assign to the filter panel which will get the members from this data source.

 

How to find the first data source?

In split cell container, there is an event on Drop", there you can place this script:

var dataSources = SPLITCELLCONTAINER_1.getDataSources();

 

dataSources.forEach(function(element, index) {

  if(index == 0) {

  FILTERPANEL_1.setDataSource(element);

  }

});

(consider also some script in the "on Delete" event...)

 

How to distribute the filters?

In the "on Apply" event, you can again loop at all data sources of the split cell container and copy the filters.

var allDataSources = SPLITCELLCONTAINER_1.getDataSources();

var masterDataSource = FILTERPANEL_1.getDataSource();

 

allDataSources.forEach(function(element, index) {

  if (element != masterDataSource) {

  element.copyFilters(masterDataSource);

  }

});

Other aspects

The same way can be used with Filter Line component. There is not "on Accept" event, but there is a script function

FILTERLINE_1.setTargetDataSources(datasources);

which makes logically a filter copy.

 

Example.

For an example of this functions, you can refer to the template "Online Composition" (New -> select the template) and its function:

gf.PNG

Questions, Ideas welcome.


Design Studio 1.6 - View on Icon & Custom Font

$
0
0

The icon component - available in both modes - will allow you to create icons from SAP UI5 font and also add custom fonts.


Example.

Here you can see the component. it is called "Icon" and has some general display properties. From the area "Standard Font" you can select some icons.

ic1.PNG

Or you can add custom font file and select on the second screen some content from the custom font:

ic2.PNG

 

Properties

The component is a simple one, so there is not much to introduce on it. You can change some properties for the selected icon.

 

Other relations

It relates to the SCN community component Fiori Button - SCN Design Studio Communitywhich has similar function set for the icon part.

ASUG BI Webinar List - December 2015

$
0
0

Here the updated list of webinars for November 2015

 


For all webinars :

 

  • Start Time : 11:00 AM (CT), 12:00 PM (ET), 10:00 AM (MT), 9:00 AM (PT)
  • Duration : 1 hour



  • December 08 - Deploying BI in the Cloud

    Whether you want to deploy an enterprise grade, zero-footprint SAP BusinessObjects BI system in the cloud, or a maintenance a self-service BI cloud solution this session will give you the background, insights, and details to deploy the right SAP Cloud product.

 




 

 

 

 

I hope you enjoy these session.

 

Please note, that these are webinars organized by the ASUG BI group and for attending you need to be a ASUG Member.

Your First Extension: Part 4a - Introduction to the Additional Properties Sheet

$
0
0

This is part of a tutorial series on creating extension components for Design Studio.


In our last instalment, we noted that the gauge was always left (and top) justified within the component, when the padding was non-zero.  We could have built a heuristic to manage the location of the gauge, or dropped the padding altogether and simply used height and width to manage the location and size of the gauge.  The latter would be the simpler and easier to use solution, but instead, we're going to use it as an opportunity to investigate the Additional Properties Sheet (APS) and build a "positioning visualizer".

 

 

What is the Additional Properties Sheet?

 

Sometimes, the properties sheet is not sufficient for managing a component's properties.  It might be that the properties don't lend themselves to comfortably being worked on within the confines of a name/value list or it might be that the developer wants to use a very specific UI for managing properties, such as graphical controls.

 

The APS is a panel within the Design Studio Designer, usually on the right side of the screen, along with the Properties Sheet; though the designer is free to reposition it as she sees fit.  The APS contains an embedded browser, allowing us to put little web pages into the designer environment.  We could - in principle - put any arbitrary web content in there, however stock tickers and social media feeds built into the Design Studio environment are not very useful to us.  Where the APS web pages come into their own is when they are attached to the server and canvas, allowing us to interact with component properties canvas.  Over the next few installments, we'll design a padding visualizer using HTML5 Forms and D3 and then we'll use the Property Sheet Handler to connect this to the server.  Lastly, we'll introduce direct APS/canvas interaction.

 

This is the final goal of part 4:

 

4a.0.png

 

 

Next time, we'll examine the JavaScript needed to build the padding visualiser.

Your First Extension: Part 4b - The Positioning Visualizer

$
0
0

This is part of a tutorial series on creating extension components for Design Studio.

 

In this installment, we'll be constructing the basic positioning visualizer JavaScript code.  As we did with the initial arc definition in Part 2a and the initial variable controlled arc definition in Part 3a, we'll first use raw html5 as a sandbox.  The positioning visualizer that we're going to build for our gauge component will consist of the following:

 

  • It will represent the component at a 1:1 scale.

 

  • It will draw a black rectangle, indicating the borders of the component

 

  • It will draw the padding margins as blue lines, within the component.

 

  • It will represent the potential arc of the gauge (from -180° to +180°) as a black circle.

 

  • This circle will be positioned as the actual gauge within the canvas, allowing the designer to see where the padding margins are and how these padding margins affect the size and positioning of the gauge.

 

  • There will be white crosshairs, centered on the centroid of the circle, allowing the designer to easily see there the origin of the arc is.

 

 

The visualizer will look something like this:

4a.1.png

 

 

In order to construct it in D3, we'll break the visualizer down into its constituent components.  In terms of raw shapes, we have the following:

 

  • 4 blue rectangles.  D3 allows us to draw a rectangle, but we won't use that feature, as it is filled in by default.  Instead, we'll use paths and draw each rectangle by tracing through each of the corners and returning to the original, as if we were drawing with a pencil.

 

  • 1 black rectangle, drawn the same way.

 

  • The crosshairs, which will be drawn as two lines.

 

  • The circle, which will be drawn the same way as we've been drawing the gauge; as an arc.  The color will be fixed to black and the start/end angles will be -180° and 180° respectively.

 

  • We'll use a consistent line stroke thickness of two pixels.

 

4a.2.png

 

 

Constructing the basic Javascript for drawing the positioning visualizer

 

 

For our APS Javascript, we'll be using the four padding properties.  In the actual component, we'll be following the pattern of me._<propertyName> for the local copies of these four properties.  In in sandbox, we'll dispense with the me. Prefix, but we'll keep the underscore.  The padding sizes are defined as follows:

  //Outer Dimensions & Positioning

  _paddingTop = 0;

  _paddingBottom = 0;

  _paddingLeft = 0;

  _paddingRight = 0

 

We'll define the stroke thicknesses and set a variable for the height and width.  We're not following the underscore convention, even though height and width are properties.  They are not accessible via the normal methods however.  We'll cover synching them later, but for now we'll just define variables for height and width.

  //Viz definitiions

  var lineThickness = 2;

 

  //Height and Width Proxies

  widthProxy = 200;

  heightProxy = 200;

 

 

Next, we clear any SVG elements from the HTML file's content div and re-insert one.  While we're at it, we'll declare the PI variable

// Clear any existing content.  We'll redraw from scratch

d3.select("#content").selectAll("*").remove();

var vis = d3.select("#content").append("svg:svg").attr("width", "100%").attr("height", "100%");

 

var pi = Math.PI;

 

 

Drawing the Gauge Dummy

 

Now we'll actually start drawing the visualizer itself, starting with the gauge dummy.  The code should be familiar by now.  We determine what the largest padding value is and combine with the smaller of height and width to calculate the outer radius.  When we draw the "gauge", we'll draw a black 360° circle (from -180° to +180°).

//Determing the position of the gauge dummy (black circle)

// Find the larger left/right padding

var lrPadding = _paddingLeft + _paddingRight;

var tbPadding = _paddingTop + _paddingBottom;

var maxPadding = lrPadding;

if (maxPadding < tbPadding){

  maxPadding = tbPadding

}

 

 

//Do the same with the overall height and width

var smallerAxis = heightProxy;

if (widthProxy < smallerAxis){

  smallerAxis = widthProxy

}

 

var outerRad = (smallerAxis - 2*(maxPadding))/2;

 

 

//The offset will determine where the center of the arc shall be

var offsetLeft = outerRad + _paddingLeft;

var offsetDown = outerRad + _paddingTop;

 

 

//The black Circle

var arcDef = d3.svg.arc()

  .innerRadius(0)

  .outerRadius(outerRad)

  .startAngle(-180 * (pi/180)) //converting from degs to radians

  .endAngle(180 * (pi/180)); //converting from degs to radians

 

 

var guageDummy = vis.append("path")

  .style("fill", "black")

  .attr("width", widthProxy).attr("height", heightProxy) // Added height and width so arc is visible

  .attr("transform", "translate(" + offsetLeft + "," + offsetDown + ")")

  .attr("d", arcDef);

 

 

 

Drawing the Line Strokes

 

When we define our paths, we'll be defining them via a set of x/y coordinates and then D3 will draw its "pencil stokes" between them.  The waypoint "data" of a line is a list of Javascript Objects; each with a pair of x and y coordinate properties.  Strictly speaking, the properties don't have to be called "x" and "y".  We could also call them "fred" and "frank", but "x" and "y" are self descriptive, and we'll stick with them.

 

Below is an example Line Data list.  It defines an outer box - the component outline - for the component.  It:

  1. Starts at the upper left; 0,0
  2. Moves across to the upper right corner; widthProxy,0
  3. Moves down to the lower right corner; widthProxy, heightProxy
  4. Moves across to the lower left corner; 0, heightProxy
  5. Returns to the starting position in the upper left; 0,0

[

  {"x":0, "y":0},

  {"x": widthProxy, "y":0},

  {"x": widthProxy, "y":heightProxy},

  {"x":0, "y":heightProxy},

  {"x":0, "y":0}

];

 

We can now define all of our lines.  When we draw the padding boxes, keep in mind that each is anchored on the appropriate side of the component outline.  The crosshairs a s defined by two strokes, one from the top center of the circle, down to the bottom center.  The other goes from the left middle, to the right middle.

var lineDataOuter = [{"x":0, "y":0}, {"x": widthProxy, "y":0}, {"x": widthProxy, "y":heightProxy}, {"x":0, "y":heightProxy}, {"x":0, "y":0}];

var lineDataPaddingLeft = [{"x":0, "y":0}, {"x":_paddingLeft, "y":0}, {"x":_paddingLeft, "y":heightProxy}, {"x":0, "y":heightProxy}, {"x":0, "y":0}];

var lineDataPaddingRight = [{"x":( widthProxy - _paddingRight), "y":0}, {"x": widthProxy, "y":0}, {"x": widthProxy, "y":heightProxy}, {"x":( widthProxy - _paddingRight), "y":heightProxy}, {"x":( widthProxy - _paddingRight), "y":0}];

var lineDataPaddingUpper= [{"x":0, "y":0}, {"x": widthProxy, "y":0}, {"x": widthProxy, "y":_paddingTop}, {"x":0, "y":_paddingTop}, {"x":0, "y":0}];

var lineDataPaddingLower = [{"x":0, "y":(heightProxy - _paddingBottom)}, {"x": widthProxy, "y":(heightProxy - _paddingBottom)}, {"x": widthProxy, "y":heightProxy}, {"x":0, "y":heightProxy}, {"x":0, "y":(heightProxy - _paddingBottom)}];

 

var lineDataCrosshairsHorizontal = [{"x":_paddingLeft, "y":(_paddingTop + outerRad) }, {"x":(_paddingLeft + 2*outerRad), "y":(_paddingTop + outerRad) }];

var lineDataCrosshairsVertical = [{"x":(_paddingLeft  + outerRad), "y":_paddingTop }, {"x":(_paddingLeft  + outerRad), "y":(_paddingTop + 2*outerRad) }];

 

 

 

The Line Accessor

 

In between the coordinates that we just defined, D3 is going to do something called interpolation; effectively filling in the blanks to convert your waypoint coordinates to SVG paths.  There is an excellent overview of SVG paths in D3 at DashingD3.com if you are interested.  We'll simply use the "cookbook version" of a linear interpolator; one that consumes waypoint data defined in terms of X and Y coordinates and draws straight lines between them.

//Line Accessor Function

var lineAccessor = d3.svg.line()

  .x(function(d) { return d.x; })

  .y(function(d) { return d.y; })

  .interpolate("linear");

 

 

Adding the boxes

 

The lines of the boxes are drawn just like the gauge arc,  by appending the path defined by the data and line accessor to the vis svg element.   There are a couple of minor differences:

  • When we defined the path for the gauge arc, we created a d3.svg.arc() instance.  This time, we'll be filling the "d" attribute with the results of a lineAccessor() function.  The input property of the accessor will be our lne data.

 

  • We'll use an empty fill.

 

  • Paths can have stroke colors and widts attributes.  We will assign values to these attributes.

 

To add the component outline to the svg element, our code would look like this:

var borderLinesOuter = vis

  .attr("width",  widthProxy).attr("height", heightProxy) // Added height and width so line is visible

  .append("path")

  .attr("d", lineAccessor(lineDataOuter))

  .attr("stroke", "black")

  .attr("stroke-width", lineThickness)

  .attr("fill", "none");

 

For all lines, it would look like this:

var borderLinesPaddingLeft = vis

  .attr("width",  widthProxy).attr("height", heightProxy) // Added height and width so line is visible

  .append("path")

  .attr("d", lineAccessor(lineDataPaddingLeft))

  .attr("stroke", "blue")

  .attr("stroke-width", lineThickness)

  .attr("fill", "none");

var borderLinesPaddingRight = vis

  .attr("width",  widthProxy).attr("height", heightProxy) // Added height and width so line is visible

  .append("path")

  .attr("d", lineAccessor(lineDataPaddingRight))

  .attr("stroke", "blue")

  .attr("stroke-width", lineThickness)

  .attr("fill", "none");

var borderLinesPaddingUpper = vis

  .attr("width",  widthProxy).attr("height", heightProxy) // Added height and width so line is visible

  .append("path")

  .attr("d", lineAccessor(lineDataPaddingUpper))

  .attr("stroke", "blue")

  .attr("stroke-width", lineThickness)

  .attr("fill", "none");

var borderLinesPaddingLower = vis

  .attr("width",  widthProxy).attr("height", heightProxy) // Added height and width so line is visible

  .append("path")

  .attr("d", lineAccessor(lineDataPaddingLower))

  .attr("stroke", "blue")

  .attr("stroke-width", lineThickness)

  .attr("fill", "none");

 

 

var borderLinesOuter = vis

  .attr("width",  widthProxy).attr("height", heightProxy) // Added height and width so line is visible

  .append("path")

  .attr("d", lineAccessor(lineDataOuter))

  .attr("stroke", "black")

  .attr("stroke-width", lineThickness)

  .attr("fill", "none");

var borderLinesCrosshairHorizontal = vis

  .attr("width",  widthProxy).attr("height", heightProxy) // Added height and width so line is visible

  .append("path")

  .attr("d", lineAccessor(lineDataCrosshairsHorizontal))

  .attr("stroke", "white")

  .attr("stroke-width", lineThickness)

  .attr("fill", "none");

var borderLinesCrosshairVertical = vis

  .attr("width",  widthProxy).attr("height", heightProxy) // Added height and width so line is visible

  .append("path")

  .attr("d", lineAccessor(lineDataCrosshairsVertical))

  .attr("stroke", "white")

  .attr("stroke-width", lineThickness)

  .attr("fill", "none");

 

 

The completed html file look like this:

<!DOCTYPE html>

<html>

  <head>

  <meta http-equiv='X-UA-Compatible' content='IE=edge' />

  <title>Part 4</title>

  <div id='content'></div>

  <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.6/d3.min.js" charset="utf-8"></script>

  <script>

  //Outer Dimensions & Positioning

  _paddingTop = 0;

  _paddingBottom = 0;

  _paddingLeft = 0;

  _paddingRight = 0;

 

  //Viz definitiions

  var lineThickness = 2;

 

  //Height and Width Proxies

  widthProxy = 200;

  heightProxy = 200;

 

 

  // Clear any existing content.  We'll redraw from scratch

  d3.select("#content").selectAll("*").remove();

  var vis = d3.select("#content").append("svg:svg").attr("width", "100%").attr("height", "100%");

 

 

  var pi = Math.PI;

 

 

  ///////////////////////////////////////////

  //Gauge Dummy

  ///////////////////////////////////////////

 

 

  //Determing the position of the gauge dummy (black circle)

  // Find the larger left/right padding

  var lrPadding = _paddingLeft + _paddingRight;

  var tbPadding = _paddingTop + _paddingBottom;

  var maxPadding = lrPadding;

  if (maxPadding < tbPadding){

  maxPadding = tbPadding

  }

 

  //Do the same with the overall height and width

  var smallerAxis = heightProxy;

  if (widthProxy < smallerAxis){

  smallerAxis = widthProxy

  }

 

  var outerRad = (smallerAxis - 2*(maxPadding))/2;

 

 

  //The offset will determine where the center of the arc shall be

  var offsetLeft = outerRad + _paddingLeft;

  var offsetDown = outerRad + _paddingTop;

 

 

  //The black Circle

  var arcDef = d3.svg.arc()

  .innerRadius(0)

  .outerRadius(outerRad)

  .startAngle(-180 * (pi/180)) //converting from degs to radians

  .endAngle(180 * (pi/180)); //converting from degs to radians

 

 

  var guageDummy = vis.append("path")

  .style("fill", "black")

  .attr("width", widthProxy).attr("height", heightProxy) // Added height and width so arc is visible

  .attr("transform", "translate(" + offsetLeft + "," + offsetDown + ")")

  .attr("d", arcDef);

 

 

 

 

  ///////////////////////////////////////////

  //Line Data

  ///////////////////////////////////////////

  var lineDataOuter = [{"x":0, "y":0}, {"x": widthProxy, "y":0}, {"x": widthProxy, "y":heightProxy}, {"x":0, "y":heightProxy}, {"x":0, "y":0}];

  var lineDataPaddingLeft = [{"x":0, "y":0}, {"x":_paddingLeft, "y":0}, {"x":_paddingLeft, "y":heightProxy}, {"x":0, "y":heightProxy}, {"x":0, "y":0}];

  var lineDataPaddingRight = [{"x":( widthProxy - _paddingRight), "y":0}, {"x": widthProxy, "y":0}, {"x": widthProxy, "y":heightProxy}, {"x":( widthProxy - _paddingRight), "y":heightProxy}, {"x":( widthProxy - _paddingRight), "y":0}];

  var lineDataPaddingUpper= [{"x":0, "y":0}, {"x": widthProxy, "y":0}, {"x": widthProxy, "y":_paddingTop}, {"x":0, "y":_paddingTop}, {"x":0, "y":0}];

  var lineDataPaddingLower = [{"x":0, "y":(heightProxy - _paddingBottom)}, {"x": widthProxy, "y":(heightProxy - _paddingBottom)}, {"x": widthProxy, "y":heightProxy}, {"x":0, "y":heightProxy}, {"x":0, "y":(heightProxy - _paddingBottom)}];

 

 

  var lineDataCrosshairsHorizontal = [{"x":_paddingLeft, "y":(_paddingTop + outerRad) }, {"x":(_paddingLeft + 2*outerRad), "y":(_paddingTop + outerRad) }];

  var lineDataCrosshairsVertical = [{"x":(_paddingLeft  + outerRad), "y":_paddingTop }, {"x":(_paddingLeft  + outerRad), "y":(_paddingTop + 2*outerRad) }];

 

 

  //Line Accessor Function

  var lineAccessor = d3.svg.line()

  .x(function(d) { return d.x; })

  .y(function(d) { return d.y; })

  .interpolate("linear");

 

 

  var borderLinesPaddingLeft = vis

  .attr("width",  widthProxy).attr("height", heightProxy) // Added height and width so line is visible

  .append("path")

  .attr("d", lineAccessor(lineDataPaddingLeft))

  .attr("stroke", "blue")

  .attr("stroke-width", lineThickness)

  .attr("fill", "none");

  var borderLinesPaddingRight = vis

  .attr("width",  widthProxy).attr("height", heightProxy) // Added height and width so line is visible

  .append("path")

  .attr("d", lineAccessor(lineDataPaddingRight))

  .attr("stroke", "blue")

  .attr("stroke-width", lineThickness)

  .attr("fill", "none");

  var borderLinesPaddingUpper = vis

  .attr("width",  widthProxy).attr("height", heightProxy) // Added height and width so line is visible

  .append("path")

  .attr("d", lineAccessor(lineDataPaddingUpper))

  .attr("stroke", "blue")

  .attr("stroke-width", lineThickness)

  .attr("fill", "none");

  var borderLinesPaddingLower = vis

  .attr("width",  widthProxy).attr("height", heightProxy) // Added height and width so line is visible

  .append("path")

  .attr("d", lineAccessor(lineDataPaddingLower))

  .attr("stroke", "blue")

  .attr("stroke-width", lineThickness)

  .attr("fill", "none");

 

 

  var borderLinesOuter = vis

  .attr("width",  widthProxy).attr("height", heightProxy) // Added height and width so line is visible

  .append("path")

  .attr("d", lineAccessor(lineDataOuter))

  .attr("stroke", "black")

  .attr("stroke-width", lineThickness)

  .attr("fill", "none");

  var borderLinesCrosshairHorizontal = vis

  .attr("width",  widthProxy).attr("height", heightProxy) // Added height and width so line is visible

  .append("path")

  .attr("d", lineAccessor(lineDataCrosshairsHorizontal))

  .attr("stroke", "white")

  .attr("stroke-width", lineThickness)

  .attr("fill", "none");

  var borderLinesCrosshairVertical = vis

  .attr("width",  widthProxy).attr("height", heightProxy) // Added height and width so line is visible

  .append("path")

  .attr("d", lineAccessor(lineDataCrosshairsVertical))

  .attr("stroke", "white")

  .attr("stroke-width", lineThickness)

  .attr("fill", "none");

  </script>

    </head>

  <body class='sapUiBody'>

 

  </body>

</html>

 

 

 

And at this is what we see in the browser, if the padding values are all zero.

4b.1.png

 

 

 

 

Next time, we'll put this into the APS window.

Using custom icon font with SAP Design studio 1.6 icon component

$
0
0

With the recent SAP Design Studio 1.6 release, there are many new and interesting components in SAP Business Objects Design Studio. One component that really intrigued me was the icon component. In the previous versions of SAP Design Studio, we were able to add SAPUI5 font icons using a few work around methods but now these icons are available out-of-the-box.  So This is something that I thought was extremely useful, hence decided to explore more features of this component further to make it even more useful:)


How to include custom font?

I had to google and explore to know how a UI5 component works, since the icon component that’s available in SAP Design Studio 1.6  is built on the UI5 icon component. The URL mentioned below helped me understand the flow of the component to a great extent:


https://help.sap.com/saphelp_uiaddon10/helpdata/en/21/ea0ea94614480d9a910b2e93431291/content.htm

 

How it works?

When we declare a custom font File in the property sheet, it creates a font face with the file name as font family. We can reference  this with the collection icon name.

 

Note:

The required icon font file depends on the browser supported by your application. For Internet Explorer (IE) versions below IE9, the font files need the following extensions: .eotand .ttf. For other browser versions, only the .ttfextension is required.

 

How to implement custom font?

 

  1. First step is to obtain your custom font file. In my case, I have obtained fontawesome.ttf file from fontawesome website.

 

     11.png

 

   2.Start Open SAP Design Studio and create a new application.

 

   3.Drag and drop the icon component into the application.

 

     1.png

 

   4.In property sheet, look for the option custom font and click on it to open the file explorer. Direct it towards the custom font file you have (in my case,fontawesome ttf file). Once you point out the path, it will be uploaded to your repository.

 

     file.png

 

   5.Now in order to reference the icon, you can follow either of the following two ways:

 

1) From property sheet

 

       By calling collection and reference name in ICON URI like this sap-icon://fontawesome-webfont/acute.where fontawesome-webfont is my collection name (file name) and acute is my icon name

 

     3.png

 

 

2) From APS

 

     Select the tab custom font. It would be empty until you search your icon name in the search box (in my case for example arrow), then click on the option you want.

 

     2.png

 

 

     I have used  the ttf file which supports IE9 plus version. When you run the application, you will be able to see the font awesome icon.

 

      4.png

 

 

     We can switch between icons, background, color and shape using ztl function that makes this more dynamic to show alert driven data.

 

Since this icon component allows customization, our own company logo can even be easily incorporated into the SAP Design Studio instead of using the image component, which increases the server load. Icon component will be handy if we have lot of icons to be used in the dashboard.

Your First Extension: Part 4c - Putting the Padding Visualizer into the Additional Properties Sheet

$
0
0

This is part of a tutorial series on creating extension components for Design Studio.


In our last instalment, we built a padding visualizer in raw HTML, using D3.  Now we'll bring it into the design environment.  The Additional Properties Sheet (APS) runs in a separate browser instance, inside the Design Studio (Eclipse) designer environment.  It connects to the server using a very similar mechanism to what user's browser and design time canvas use.  The Design Studio SDK provides JavaScript infrastructure that connects to the server behind the scenes.  It is called the Property Sheet Handler and handles the communication and property synchronization between the APS and server. 

 

To use the Property Sheet Handler, you need to do three things:

  • Include its JavaScript file among the APS HTML file's scripts.  The Property Sheet Handler JavaScript file (called sdk_propertysheets_handler.js) is part of the SDK.
  • Add your own JavaScript file.  This file follows the same pattern as the Component JavaScript file in that you extend an SDK class and put your custom code inside this extension.  The class that you'll be extending for the APS is called sap.designstudio.sdk.PropertyPage.
  • Add a script element to the APS html file that instantiates an instance of this class.

 

When you have your PropertyPage subclass instance, your can use firePropertiesChanged() to push updated property values to the server and when property values are changed in the server (via the main property sheet) and tie your getter/setter functions into the SDK infrastructure.

 

In your contribution .xml file, you should have a reference to the additional properties sheet html and JavaScript files.  Unless you have modified it, the reference should read as:

propertySheetPath="res/additional_properties_sheet/additional_properties_sheet.html"

 

We'll begin by building the html file and then construct the JavaScript file.  Recall from Part 1,that the  we already have the html and JavaScript files  The html file is empty and the Javascript file currently looks like this:

sap.designstudio.sdk.PropertyPage.subclass("com.sap.sample.scngauge.SCNGaugePropertyPage",  function() {

}

 

 

Constructing additional_properties_sheet.html

 

This is a fairly straightforward file.  We are going to want an html5 form, where we can edit the four padding values and display the height and width.  Our visualizer diagram will sit below the form and be updated whenever the padding values are changed in the properties sheet, or whenever the form is submitted.

 

All we are going to do in the head element is state the title and import a few scripts.  The scripts are:

  • A reference to a public copy of D3.  We're not using the SDK copy of D3 here, but instead we'll use the "official" D3 URL.
  • The Design Studio SDK framework's property sheet handler.  It is always at /aad/zen.rt.components.sdk/resources/js/sdk_propertysheets_handler.js.
  • Our own JavaScript file.  It is customary to put this file into res/additional_properties_sheet, alongside the html file.

 

The head element should now look like this:

<head>

  <title>Gauge Padding Visualizer</title>

  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />

  <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.6/d3.min.js" charset="utf-8"></script>

  <script src="/aad/zen.rt.components.sdk/resources/js/sdk_propertysheets_handler.js"></script>

  <script src="additional_properties_sheet.js"></script>

</head>

 

 

In between the head and body elements, we'll slip a script in.  This will be the script that instantiates our PropertyPage subclass component, so that the APS can hook into the property sheet handler.  Since our (still empty) class is called "com.sap.sample.scngauge.SCNGaugePropertyPage", we'll instantiate that.

<script>

  new com.sap.sample.scngauge.SCNGaugePropertyPage();

</script>

 

Next, we build up the form.  HTML5 forms essentially enhances table.  If you are not familiar with html tables or forms, there are online tutorials for both tables and forms.

<form id="form">

  <fieldset>

  <legend>Gauge Padding Visualizer</legend>

  <table>

  <tr>

  <td>Padding Top</td>

  <td>

  <input id="aps_padding_top" type="number" name="paddingTop" size="40" maxlength="40"></input>

  </td>

  </tr>

  <tr>

  <td>Padding Bottom</td>

  <td>

  <input id="aps_padding_bottom" type="number" name="paddingBottom" size="40" maxlength="40"></input>

  </td>

  </tr>

  <tr>

  <td>Padding Left</td>

  <td>

  <input id="aps_padding_left" type="number" name="paddingLeft" size="40" maxlength="40"></input>

  </td>

  </tr>

  <tr>

  <td>Padding Right</td>

  <td>

  <input id="aps_padding_right" type="number" name="paddingRight" size="40" maxlength="40"></input>

  </td>

  </tr>

  <tr>

  <td>Width</td>

  <td>

  <input id="aps_width" type="number" name="widthProxy" size="40" maxlength="40"></input>

  </td>

  </tr>

  <tr>

  <td>Height</td>

  <td>

  <input id="aps_height" type="number" name="heightProxy" size="40" maxlength="40"></input>

  </td>

  </tr>

  <tr>

  <td>Outer Radius</td>

  <td>

  <p id="aps_radius"></p>

  </td>

  </tr>

  <tr>

  <td>

  <input name="submit"  type="submit" value="Refresh"/>

  </td>

  </tr>

 

  </table>

  </fieldset>

</form>

 

 

The last members of the body element are two divs.  The div with id #content will be the one where we insert the visualizer SVG that we defined last time, in Part 4b.  #componentproxy is something that we"ll get to know better later on, when we directly interact with the canvas.

<div id='content'></div>

<div id='componentproxy'></div>

 


The completed HTML file

<html>

  <head>

  <title>Gauge Padding Visualizert</title>

  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />

  <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.6/d3.min.js" charset="utf-8"></script>

  <script src="/aad/zen.rt.components.sdk/resources/js/sdk_propertysheets_handler.js"></script>

  <script src="additional_properties_sheet.js"></script>

  </head>

  <script>

  new com.sap.sample.scngauge.SCNGaugePropertyPage();

  </script>

  <body>

  <form id="form">

  <fieldset>

  <legend>Gauge Padding Visualizer</legend>

  <table>

  <tr>

  <td>Padding Top</td>

  <td>

  <input id="aps_padding_top" type="number" name="paddingTop" size="40" maxlength="40"></input>

  </td>

  </tr>

  <tr>

  <td>Padding Bottom</td>

  <td>

  <input id="aps_padding_bottom" type="number" name="paddingBottom" size="40" maxlength="40"></input>

  </td>

  </tr>

  <tr>

  <td>Padding Left</td>

  <td>

  <input id="aps_padding_left" type="number" name="paddingLeft" size="40" maxlength="40"></input>

  </td>

  </tr>

  <tr>

  <td>Padding Right</td>

  <td>

  <input id="aps_padding_right" type="number" name="paddingRight" size="40" maxlength="40"></input>

  </td>

  </tr>

  <tr>

  <td>Width</td>

  <td>

  <input id="aps_width" type="number" name="widthProxy" size="40" maxlength="40"></input>

  </td>

  </tr>

  <tr>

  <td>Height</td>

  <td>

  <input id="aps_height" type="number" name="heightProxy" size="40" maxlength="40"></input>

  </td>

  </tr>

  <tr>

  <td>Outer Radius</td>

  <td>

  <p id="aps_radius"></p>

  </td>

  </tr>

  <tr>

  <td>

  <input name="submit"  type="submit" value="Refresh"/>

  </td>

  </tr>

 

  </table>

  </fieldset>

  </form>

  <div id='content'></div>

  <div id='componentproxy'></div>

  </body>

</html>

 

 

 

Constructing additional_properties_sheet.js

 

Now we are going to refactor and migrate our visualise Javascript code from last time to the com.sap.sample.scngauge.SCNGaugePropertyPage class in additional_properties_sheet.js.  Most of the JavaScript code from the raw html document can be migrate with only minimal refactoring.  As we did in Part 2b, when we moved the gauge from plain html to the canvas, we'll be doing a few major things in our refactoring:

  • Just like with the canvas, any properties that need to be synchronized between the APS and server need to have getters and setters.
  • The variable definitions won't go into the me.redraw() method.  Instead, they'll move to the root of the class and take on the "me" namespace.  (e.g. _paddingTop becomes me._paddingTop)
  • The HTML form needs to be synchronized with the property values.  In practice, this means updating the property values whenever the form is submitted (the values in the forms, become the new property values) and updating the form values whenever the setter of a property is invoked.
  • The actual code governing the redraws (including the path definitions) will get packaged into a method, called me.redraw()
  • The SDK framework will be calling the class' init() function when it is instantiated, so we'll need to do some bootstrapping in there.

 

So let's build up the com.sap.sample.scngauge.SCNGaugePropertyPage class.

 

 

Variable Definitions

 

As usual, we declare the "self proxy"

var me = this;

 

Next, come the other class wide variables.  The line thickness and height/width proxies get a me. Prifix, as do the four padding properties.

//Viz definitiions

me.lineThickness = 2;

 

//Height and Width Proxies

me.widthProxy = 200;

me.heightProxy = 200;

 

//Outer Dimensions & Positioning

me._paddingTop = 0;

me._paddingBottom = 0;

me._paddingLeft = 0;

me._paddingRight = 0;

 

 

Getter/Setters

 

The getter/setter functions are fairly straightforward and follow the same pattern as in the canvas, with the getter returning the value of me.<property> and the setter setting it and then calling me.redraw().

me.paddingTop = function(value) {

  if (value === undefined) {

  return me._paddingTop

  }

  else {

  me._paddingTop = value;

  me.redraw();

  return me;

  }

};

 

 

me.paddingBottom = function(value) {

  if (value === undefined) {

  return me._paddingBottom

  }

  else {

  me._paddingBottom = value;

  me.redraw();

  return me;

  }

};

 

 

me.paddingLeft = function(value) {

  if (value === undefined) {

  return me._paddingLeft

  }

  else {

  me._paddingLeft = value;

  me.redraw();

  return me;

  }

};

 

 

me.paddingRight = function(value) {

  if (value === undefined) {

  return me._paddingRight

  }

  else {

  me._paddingRight = value;

  me.redraw();

  return me;

  }

};

 

 

Before we contineue - A note on JQuery selection

 

Note that unlike in the canvas, where we select the root of the component, we're have complete access to the APS document object model (DOM).  When we are working in the canvas, we would make an empty Jquery selection, which is the root node of the container and then select the first child.  In the APS, we don't have this single div restriction and are free to use Jquery selection in a more conventional and freeform manner.  Therefore, well jQuery element selection by ID approach.

 

E.g. if we want to select the form input element with the ID aps_padding_top and use it's value to fill me._paddingTop, then, we'd use:

me._paddingTop = $("#aps_padding_top").val();

 

Remember method chaining. 

 

Full documentation of Jquery's selectors is available in the Jquery documentation.

 

 

me.init()

 

We're going to do two things in our init() function:

  • We're going to trigger the redraw() function, just as in the canvas init() function (in component.js).

 

  • We'll want to attach a function to the form's submit event (HTML5 forms have a built in submit event) which will do the following:
    • Make sure that all editable variables are updated to whatever values the user has added to the form.
    • Fire firePropertiesChanged(), with the padding properties, so that the server side values can also be updated.
    • Trigger the redraw() event, so that the visualizer can be redrawn to match the new values.

me.init = function() {

  $("#form").submit(function() {

  me._paddingTop = $("#aps_padding_top").val();

  me._paddingBottom = $("#aps_padding_bottom").val();

  me._paddingLeft = $("#aps_padding_left").val();

  me._paddingRight = $("#aps_padding_right").val();

  me._widthProxy = $("#aps_width").val();

  me._heightProxy = $("#aps_height").val();

 

  me.firePropertiesChanged(["paddingTop", "paddingBottom", "paddingLeft", "paddingRight"]);

  me.redraw();

  return false;

  });

  me.redraw();

};

 

 

 

me.redraw()

 

The redraw function works similarly to its canvas based cousin.  It deletes any existing SVG elements from the #content div, inserts a new, empty SVG element and draws the component - in this case the visualizer - anew.  We are going to do one thing differently.  We're also going to write the height, width and padding values into the HTML form element.  Redraw is called whenever a setter is triggered, so it makes sense to update the form as well.

 

With method chaining, we can select each input element and fill it in a single statement.

$("#aps_padding_top").val(me._paddingTop);

$("#aps_padding_bottom").val(me._paddingBottom);

$("#aps_padding_left").val(me._paddingLeft);

$("#aps_padding_right").val(me._paddingRight);

$("#aps_width").val(me._widthProxy);

$("#aps_height").val(me._heightProxy);

 

The rest of the redraw function is the remainder of the main script from the html file, with the variable names refactored to match the new convention.

 

 

The full additional_properties_sheet.js

sap.designstudio.sdk.PropertyPage.subclass("com.sap.sample.scngauge.SCNGaugePropertyPage",  function() {

 

 

  var me = this;

 

  //Viz definitiions

  me.lineThickness = 2;

 

 

  //Outer Dimensions & Positioning

  me._paddingTop = 0;

  me._paddingBottom = 0;

  me._paddingLeft = 0;

  me._paddingRight = 0;

 

  //Height and Width Proxies

  me._widthProxy = 200;

  me._heightProxy = 200;

 

 

 

 

  me.init = function() {

  $("#form").submit(function() {

  me._paddingTop = $("#aps_padding_top").val();

  me._paddingBottom = $("#aps_padding_bottom").val();

  me._paddingLeft = $("#aps_padding_left").val();

  me._paddingRight = $("#aps_padding_right").val();

  me._widthProxy = $("#aps_width").val();

  me._heightProxy = $("#aps_height").val();

 

  me.firePropertiesChanged(["paddingTop", "paddingBottom", "paddingLeft", "paddingRight"]);

  me.redraw();

  return false;

  });

  me.redraw();

  };

 

 

  me.paddingTop = function(value) {

  if (value === undefined) {

  return me._paddingTop

  }

  else {

  me._paddingTop = value;

  me.redraw();

  return me;

  }

  };

 

  me.paddingBottom = function(value) {

  if (value === undefined) {

  return me._paddingBottom

  }

  else {

  me._paddingBottom = value;

  me.redraw();

  return me;

  }

  };

 

  me.paddingLeft = function(value) {

  if (value === undefined) {

  return me._paddingLeft

  }

  else {

  me._paddingLeft = value;

  me.redraw();

  return me;

  }

  };

 

  me.paddingRight = function(value) {

  if (value === undefined) {

  return me._paddingRight

  }

  else {

  me._paddingRight = value;

  me.redraw();

  return me;

  }

  };

 

  me.redraw = function() {

  $("#aps_padding_top").val(me._paddingTop);

  $("#aps_padding_bottom").val(me._paddingBottom);

  $("#aps_padding_left").val(me._paddingLeft);

  $("#aps_padding_right").val(me._paddingRight);

  $("#aps_width").val(me._widthProxy);

  $("#aps_height").val(me._heightProxy);

 

  // Clear any existing content.  We'll redraw from scratch

  d3.select("#content").selectAll("*").remove();

  var vis = d3.select("#content").append("svg:svg").attr("width", "100%").attr("height", "100%");

  var pi = Math.PI;

 

 

  //Line Accessor Function

  var lineAccessor = d3.svg.line()

  .x(function(d) { return d.x; })

  .y(function(d) { return d.y; })

  .interpolate("linear");

 

 

 

 

  ///////////////////////////////////////////

  //Gauge Dummy

  ///////////////////////////////////////////

 

 

  //Determing the position of the gauge dummy (black circle)

  // Find the larger left/right padding

  var lrPadding = me._paddingLeft + me._paddingRight;

  var tbPadding = me._paddingTop + me._paddingBottom;

  var maxPadding = lrPadding;

  if (maxPadding < tbPadding){

  maxPadding = tbPadding

  }

 

 

  //Do the same with the overall height and width

  var smallerAxis = me._heightProxy;

  if (me._widthProxy < smallerAxis){

  smallerAxis = me._widthProxy

  }

 

  var outerRad = (smallerAxis - 2*(maxPadding))/2;

  $("#aps_radius").text(outerRad);

 

 

  //The offset will determine where the center of the arc shall be

  var offsetLeft = outerRad + me._paddingLeft;

  var offsetDown = outerRad + me._paddingTop;

 

 

  //The black Circle

  var arcDef = d3.svg.arc()

  .innerRadius(0)

  .outerRadius(outerRad)

  .startAngle(-180 * (pi/180)) //converting from degs to radians

  .endAngle(180 * (pi/180)); //converting from degs to radians

 

 

  var guageDummy = vis.append("path")

  .style("fill", "black")

  .attr("width", me._widthProxy).attr("height", me._heightProxy) // Added height and width so arc is visible

  .attr("transform", "translate(" + offsetLeft + "," + offsetDown + ")")

  .attr("d", arcDef);

 

 

  ///////////////////////////////////////////

  //Line Data

  ///////////////////////////////////////////

  var lineDataOuter = [{"x":0, "y":0}, {"x": me._widthProxy, "y":0}, {"x": me._widthProxy, "y":me._heightProxy}, {"x":0, "y":me._heightProxy}, {"x":0, "y":0}];

  var lineDataPaddingLeft = [{"x":0, "y":0}, {"x":me._paddingLeft, "y":0}, {"x":me._paddingLeft, "y":me._heightProxy}, {"x":0, "y":me._heightProxy}, {"x":0, "y":0}];

  var lineDataPaddingRight = [{"x":( me._widthProxy - me._paddingRight), "y":0}, {"x": me._widthProxy, "y":0}, {"x": me._widthProxy, "y":me._heightProxy}, {"x":( me._widthProxy - me._paddingRight), "y":me._heightProxy}, {"x":( me._widthProxy - me._paddingRight), "y":0}];

  var lineDataPaddingUpper= [{"x":0, "y":0}, {"x": me._widthProxy, "y":0}, {"x": me._widthProxy, "y":me._paddingTop}, {"x":0, "y":me._paddingTop}, {"x":0, "y":0}];

  var lineDataPaddingLower = [{"x":0, "y":(me._heightProxy - me._paddingBottom)}, {"x": me._widthProxy, "y":(me._heightProxy - me._paddingBottom)}, {"x": me._widthProxy, "y":me._heightProxy}, {"x":0, "y":me._heightProxy}, {"x":0, "y":(me._heightProxy - me._paddingBottom)}];

 

 

  var lineDataCrosshairsHorizontal = [{"x":me._paddingLeft, "y":(me._paddingTop + outerRad) }, {"x":(me._paddingLeft + 2*outerRad), "y":(me._paddingTop + outerRad) }];

  var lineDataCrosshairsVertical = [{"x":(me._paddingLeft  + outerRad), "y":me._paddingTop }, {"x":(me._paddingLeft  + outerRad), "y":(me._paddingTop + 2*outerRad) }];

 

 

 

 

  var borderLinesPaddingLeft = vis

  .attr("width",  me._widthProxy).attr("height", me._heightProxy) // Added height and width so line is visible

  .append("path")

  .attr("d", lineAccessor(lineDataPaddingLeft))

  .attr("stroke", "blue")

  .attr("stroke-width", me.lineThickness)

  .attr("fill", "none");

  var borderLinesPaddingRight = vis

  .attr("width",  me._widthProxy).attr("height", me._heightProxy) // Added height and width so line is visible

  .append("path")

  .attr("d", lineAccessor(lineDataPaddingRight))

  .attr("stroke", "blue")

  .attr("stroke-width", me.lineThickness)

  .attr("fill", "none");

  var borderLinesPaddingUpper = vis

  .attr("width",  me._widthProxy).attr("height", me._heightProxy) // Added height and width so line is visible

  .append("path")

  .attr("d", lineAccessor(lineDataPaddingUpper))

  .attr("stroke", "blue")

  .attr("stroke-width", me.lineThickness)

  .attr("fill", "none");

  var borderLinesPaddingLower = vis

  .attr("width",  me._widthProxy).attr("height", me._heightProxy) // Added height and width so line is visible

  .append("path")

  .attr("d", lineAccessor(lineDataPaddingLower))

  .attr("stroke", "blue")

  .attr("stroke-width", me.lineThickness)

  .attr("fill", "none");

 

 

  var borderLinesOuter = vis

  .attr("width",  me._widthProxy).attr("height", me._heightProxy) // Added height and width so line is visible

  .append("path")

  .attr("d", lineAccessor(lineDataOuter))

  .attr("stroke", "black")

  .attr("stroke-width", me.lineThickness)

  .attr("fill", "none");

  var borderLinesCrosshairHorizontal = vis

  .attr("width",  me._widthProxy).attr("height", me._heightProxy) // Added height and width so line is visible

  .append("path")

  .attr("d", lineAccessor(lineDataCrosshairsHorizontal))

  .attr("stroke", "white")

  .attr("stroke-width", me.lineThickness)

  .attr("fill", "none");

  var borderLinesCrosshairVertical = vis

  .attr("width",  me._widthProxy).attr("height", me._heightProxy) // Added height and width so line is visible

  .append("path")

  .attr("d", lineAccessor(lineDataCrosshairsVertical))

  .attr("stroke", "white")

  .attr("stroke-width", me.lineThickness)

  .attr("fill", "none");

  }

});

 

 

Now when we debug the component and put a gauge into the canvas and select it, we have access to a padding visualizer in the APS.

4a.0.png

 

 

Height and Width of the component can't be directly determined within the APS.  Therefore, we're still manually asking the designer to maintain these values manually in the visualizer property values form.  This is a severe usability problem and we have to fix it.  It is possible to determine these values from within the canvas.  Forthermore, it is possible to call canvas (component.js) javascript functions from the APS.  Next time, we'll create a way for the APS to ask the canvas for the height and width, so that these values are always automatically synchronized.

Sneak Peak - SCN Design Studio 1.6 SDK Components

$
0
0

Now that Design Studio 1.6 is available and Reiner Hille-Doering has shared some of the new goodies for the SDK in his post What's New in Design Studio 1.6 SDK, Karol Kalisz and I are getting closer to releasing the next version of our components that are bundled in our releases as the SCN Design Studio SDK Development Community.

 

What is New?

 

  • About sap.m...


    As mentioned by Reiner, Design Studio 1.6 now offers 'official' support for sap.m components (aka the Fiori-like components.)  Before, we had a number of Fiori/sap.m-based extensions that we were "hacking" to work in terms of loading, and forcing the events to register.  Now that Design Studio supports two 'modes' of BI Applications, the old 'commons' mode, and now the new 'm' (or Main) mode, we have made some adjustments to our existing components and included some new ones, also.

    Since the direction is to move eventually to the new 'm' mode, I've chosen to deprecate the sap.m components at this point.  I've made sure they continue to work in 'commons' mode for 1.6, but due to eventing and loading differences in 'commons' and 'm' mode, I'll not be maintaining 'commons' mode for any fixes or enhancements for long.  I'd suggest if you are looking to use these types of components to consider migrating to 'm' mode when you can.

    Now along with the old 'Fiori-like' components, I've added a few new ones:

    • Fiori Switch
      Fiori Switch.png


    • Fiori Slider

      Fiori Slider.png

    • Fiori Input Field

      Fiori Input Field.png

    • Fiori Segmented Button

      Fiori Segmented Button.png
  • About new Complex Property Types and the Additional Properties Sheet...

    During Design Studio 1.4/1.5, I wanted to cut down on a lot of the work that it takes to create the Additional Properties Sheet (APS).  There are many reasons to need an APS instead of the Default Properties sheet, especially when it comes to complex properties or nested/array-like properties.  There's some good potential that has come with 1.6 complex properties which even make the Default Property Sheet a little more usable for properties defined in the new Object/Array format, however there are some limitations such as you cannot set a default value for these new types, and you have to stick to the structure in contribution.xml which means you cannot build deeply nested properties that go down to arbitrary depths for something like a menu path component, etc.

    So for 1.6, we still use String properties that are JSON-serialized, with our APS that can express these types of properties in an easy to use way.  In the future, our APS will support 'true' complex types, but for now we did not want to introduce regressions for people using our components in existing dashboards.

    All that being said, I've taken the opportunity to port some of my older components over to the new APS for consistent look & feel, as well as some of the components developed by others in our SCN development community that didn't have an APS.  Examples:


    • Bring Your Own Data Datasource Component

      BYOData.png

    • Base-64 Encoding Image

      (For those unfamiliar, clicking the Upload (Browse) button will base64-encode your image without having to upload it.)
      Base64.png
      When clicking 'Details':

      Base64-2.png
    • Rapid Prototyping Component

      I'm working on including some 'Preset' HTML, shown below that will render some common boiler-plate HTML as you see below on the right.  On the left is the HTML that is rendered.  But that's not all that is new...
      HTML-1.png

      I've moved the term placeholders and replacements to a 'Find/Replace' category, along with now an optional new data selections (the ones labeled 'KPI Replacement' to cut down BIAL scripting when you just want to use a KPI selection.  The older replacements still exist as 'Manual Replacement'.  In the example below, you can see the placeholder term {kpi1} has been replaced by KPI Replacement 1.

      HTML-2.png
    • Tag Cloud

      I've also enhanced James Rapp's Design Studio Extension: D3 Word Cloud to support the new APS and enhanced it with more properties.

      Tag Cloud.png
    • Arrowed Line

      Donnie Burhan's Design Studio SDK - Line with Arrow also now works with this new APS:
      arrow.png



    • Bullet Chart

      Like Tag Cloud, I ported over Jeroen van der A's Design Studio Extension: Bullet Chart to the new APS pattern.

      *Something to note before release, the Measure selectors and Dimension selectors will come as drop downs for ease of use, also we can take the long list and apply sub-tabs for quick location, etc.

      Bullet.png
    • D3 Tree

      As a final example, we can see Manfred Schwarz's D3 Hierarchy component now works with this APS:

      Tree.png
    • Others

      There are definitely other components that Karol or I are in process of porting over to make more consistent but these were just a few examples I've worked through.  Our goal is a more consistent collection.


  • Optimizations & RequireJS

    We're also taking this opportunity to fully embrace the 1.6 full RequireJS support.  These are mostly internal changes that nobody will care about expect those of us contributing to this project.  Basically, we are using the Require modularization methodology of loading JS and CSS files as-needed per-component.  I've actually taken this concept all the way to the APS which means depending on what types of property controls are needed in APS, only those are loaded.  This should translate into faster loading APSs and less memory usage.

When is it Coming?

 

"Soon"    We need to finalize some internal house-keeping topics and plugin clean-up, but my goal would be before end of December.

 

Questions/Comments/Concerns

 

Have a problem, idea, question?  As always, feel free to drop a line in comments!


How to add custom measures to the Charts in Design Studio?

$
0
0

In this blog I would like to show you, how you can add custom measures to the charts in Design Studio.

Usually adding custom measures to your dashboard is done by using the BEx Query Designer (in case of SAP BW) or by using calculation views (in case of SAP HANA)

 

Design Studio 1.6 offers this feature of adding a calculation via the context menu for the crosstab. Still, the scope of this feature is limited so far to the runtime of the standard crosstab. However via a Custom SDK extension, we can extend this feature to the charts as well and also extend this to the design time.

Our extension named What If Analyzer – part of our Visual BI Extensions Suite – enables the user to add a calculation as a measure into the chart.

Let’s consider a scenario where the data source has  key figures Cost Price and Selling price for the Characteristic “Item” and the user wants to display the Selling Price and a calculated measure Profit in a Chart.

Datasource:

datasource.jpg

Chart:

The chart below is showing our measure Selling Price along the dimension Item.

Chartdefault.jpg

 

 

Steps to add Custom Calculation:

  1.Drag and drop the What If Analyser into the Application and it looks like below.

Whatif.jpg

In the additional property of the What if Analyser, there will be two tabs.

1.Scenario

2.Components

 

All Charts assigned with the Data source same as that of “What if Analyzer” can be added with custom calculations and what if analysis can be done. In this Example , the Column Chart is assigned with the Data source which is assigned to the What if Analyzer.

 

Whatifprop.jpg

Select the Chart by checking the Check box.

chartselect.jpg

As per the scenario, a new measure “Profit” needs to be added into the chart based on the calculation Profit = (Selling  Price – Cost Price)

So when we add a calculation, then it will be added to the measure category.

Now the chart will look like the below

afteraddingcalc.jpg

You final chart looks as below with the added custom measure, "Profit".

finalchart.jpg

 

Here’s a short video showing the steps to add a calculation into the Chart using the What If Analyzer.

 

Apart from the above discussed functionality, What If Analyzer does What if analyses similar to MS excel and provides few other interesting options.

Consuming SAP App-Server based Classical Infosets within SAP Design Studio Applications

$
0
0

Hi All,

 

 

I am sure there are multiple blogs talking about how to consume application data stored in SAP tables using SAP Analytical applications. In this blog I will walk you through the steps on how we can consume classical Infosets directly within SAP BusinessObjects Design Studio in a BW on HANA scenario using BW Transient InfoProviders.

 

Let me start by defining a Transient Infoprovider, which I am sure intermediate and advanced BW users are already aware.

 

What is a Transient Provider: InfoProvider that allows analysis and reporting with BI tools on top of Business Suite applications. No data resides in BW and no modeling is required to access these artifacts.

 

Please also refer to the article What are Transient Providers?   See if you have BW inside your ERP Systemby Tammy Powlas to gain more insights

 

Note:

  • Transient Providers require ERP6.0 EhP 05.
  • Infosets and BW install should be in the same system
  • Only client-independent Infosets are available


Scenario: We will create an Infoset on Flight connections (SFLIGHTS) and Flight schedule (SPFLI). Map (join) the relevant

                    fields, generate and release the Infoset.


There are 3 parts to this process:


Part-1: Create a classical infoset joining 2 tables and generate the Transient Infoprovider

Part-2: Create a Bex query to consume it in Design Studio

Part-3: Create Design Studio application to consume the Bex query


Part-1 Steps to create a SAP App-Server based Classical Infosets

  1. Log into the SAP instance where BW is also installed.
  2. Go to transaction code SQ02.
  3. Give an Infoset name and press Create

fig_1_1.PNG

4. Give a description and define the table name as SFLIGHTS and press the Green Check Mark to proceed.

 

fig_1_2.PNG

 


5. Next, we add another table and map the fields.



fig_1_3.PNG


 

6. In the pop-up dialog, enter SPFLI as the 2nd table name and press the Green Check Mark :

 

 

fig_1_4.PNG

 

 

7. Fields are automatically mapped :



fig_1_5.PNG


 

8. Click on the Infoset Arrow icon and in the pop-up, select “Include All table Fields” radio button option and press the Green Check Mark:

 

 

fig_1_6.PNG

 


9. In the next screen, choose "Generate" to generate the Infoset:


fig_1_7.PNG



10. Upon generation, a message in the status indicates that the infoset was successfully generated:

 

fig_1_8.PNG

11. Press the Back button to open the Infoset main screen.arrow.PNG

 

12. Final step in the process is to release the Infoset. From the menu, click on Environment-->BI Properties:

 

fig_1_9.PNG

 

 

Press the Green Check Mark to proceed.

 

fig_1_10.PNG

 

 

13. In this screen, choose the Infoset you have created by checking the BI Allowed property and specify a BW InfoArea (directory structure to group your objects), and press Save and Generate. This generates the Transient Infoprovider on the BW instance.

 

 

fig_1_11.PNG

 

 

14. The log screen pops-up once the generation is complete. Press the green check mark to proceed.

 


fig_1_12.PNG


This concludes Part-1

 

 

Part-2 Steps to Create a BEx Query


1. Go to Start-->All Programs--> Business Explorer-->Query Designer

 

fig_2_0.PNG

 

 

 

2. Key-in your credentials in the SAP logon and launch the query designer.

3. Press the Create icon to go to the Infoprovider selection screen. Select InfoAreas from the left panel and search for the InfoArea                                      (Design Studio Infoset) under which you generated the Infoset:

 

fig_2_1.PNG

 

4. Open the Infoset and select the required attributes and measures. Save the query with an appropriate technical name and description.

 

fig_2_2.PNG

 

 

fig_2_3.PNG

 

 

This concludes Part-2

 

 

Part-3 Consuming Transient InfoProvider in Design Studio

 

1. Launch SAP BusinessObjects Design Studio from the following path:

          Program Files --> SAP BusinessObjects --> Design Studio



2. Once Design Studio application is launched, go to Application menu and create a new blank application:

 

fig_3_0.PNG

 

3. Give a name and select Blank template and press create

 

 

fig_3_1.PNG

 

4. In the outline section, right click on Data sources and select Add Data Source..:

 

fig_3_2.PNG

 

5. In the next screen, choose your connection by browsing through the list of connections configured on your SAP Logon pad.

 

fig_3_3.PNG

 

 

6. Once the Connection is Active turns “GREEN”, browse for Data Source. Click on the Search tab and key-in the Technical name of the query which you      have created in the previous section.


fig_3_4.PNG

 

7. Once you have your query, press Ok and return to the previous screen. Press Ok.


fig_3_5.PNG


8. Right click on the data source in the outline section, and select “Edit Initial View”

 

 

fig_3_6.PNG

 

9. In the Initial View screen, right click on the members in Rows, and under “Totals Display” select “Hide Totals”. Repeat these steps for all the members:

 

 

fig_3_7.PNG

10. The final display should look like this:

 

fig_3_8.PNG

 

11. At this point, press the “OK + Create Crosstab” button at the bottom right hand corner.

 

fig_3_9.PNG

 

 

12. This is how it looks within the designer mode :

 

fig_3_10.PNG

 

 

13. Save the application and launch it in local mode to see how it looks in the browser:

 

fig_3_11.PNG

 

14. Browser view:

 

fig_3_12.PNG

 

 

This concludes the blog on how to consume application data within SAP Design Studio applications. There might be also other ways worth exploring, but this is one of the ways you can surface ECC based Application data within SAP Design Studio. I hope you find this blog useful.

Extending Design Studio with an HTML extension and HTML bars

$
0
0

A seasonal December welcome to fans of HTML, SAP BusinessObjects Design Studio and extensions.

 

This post is about an HTML extension for SAP BusinessObjects Design Studio that I am about to open source.


Before making this extension I did try out the the excellent SCN Design Studio Community extension for rapid prototyping with HTML.

I liked it but I wanted a few more features:

 

  1. To be able to use HTML or SVG at design time or runtime without having to worry about script injection (which is why I call this safe HTML). It does this by using a whitelist to only allow safe HTML tags and safe HTML attributes.
  2. Optionally use scroll bars and allow scrolling not just on desktop but also when iOS using SAP BusinessObjects Mobile.

 

Background

 

But why did I need to use HTML in the first place?

 

Back in mid-2015 I needed to create a Fiori-like demo using SAP BusinessObjects Design Studio that would be part of a keynote at the ASUG SAP Analytics & BusinessObjects Conference (SABOC).

 

I talked with the UX team and we came up with a design for an overview page that would use the SAP Fiori comparison chart tiles so would look something like this:

Comparison Tile.png

I could create the basic tile border, title and total with out of the box controls in Design Studio, but what about the comparison bars? That's where HTML comes in handy.

 

HTML table

With a HTML table and some CSS I could create the basic layout for the Male and Female text, the values and leave room to draw the bars later on (note the outer gray border and white background are parts that would be drawn by Design Studio so they are not included in the HTML table):

HTML_layout.png

Here is the HTML with inline CSS:

<table width="100%" style="bottom: 14px; border-collapse: collapse; position: absolute; line-height: normal;                    font-family: Arial, Helvetica, sans-serif; font-size: 14px; font-weight: normal;" border="0">    <tbody>        <tr style="border-top-color: transparent; border-top-style: solid;">            <td style="color: rgb(51, 51, 51);">Male</td>            <td align="right" style="color: rgb(51, 51, 51);">5,845</td>        </tr>        <tr>            <td height="12" colspan="2" /> </tr>        <tr style="border-top-color: transparent; border-top-style: solid;">            <td style="color: rgb(51, 51, 51);">Female</td>            <td align="right" style="color: rgb(51, 51, 51);">4,957</td>        </tr>        <tr>            <td height="12" colspan="2" /> </tr>    </tbody></table>

 

HTML table with linear gradient bar chart

I got the initial idea for HTML bars from a post by Tim Stanley about HTML Horizontal and Vertical Bar Charts.

 

But instead of just one bar, I needed to fill the background with light gray as well.

I found that with The Ultimate CSS Gradient Generator I could create a linear gradient for both the blue part of the bar and the gray part in one go.

 

Here is an example of the linear gradient for the Female bar, which is at: 84.81%.

 

CSS linear gradient:

background: linear-gradient(to right, rgb(88, 181, 230) 0%, rgb(88, 181, 230) 84.81%,          rgb(211, 217, 221) 84.81%, rgb(211, 217, 221) 84.81%, rgb(211, 217, 221) 100%);

 

Putting it all together

So when you put the bar linear gradients into the HTML table you end up with this:

 

HTML_layout_and_bars.png

 

Here is the final HTML:

<table width="100%" style="bottom: 14px; border-collapse: collapse; position: absolute; line-height: normal;                    font-family: Arial, Helvetica, sans-serif; font-size: 14px; font-weight: normal;" border="0">    <tbody>        <tr style="border-top-color: transparent; border-top-style: solid;">            <td style="color: rgb(51, 51, 51);">Male</td>            <td align="right" style="color: rgb(51, 51, 51);">5,845</td>        </tr>        <tr>            <td height="12" style="background: linear-gradient(to right, rgb(88, 181, 230) 0%,                    rgb(88, 181, 230) 100%, rgb(211, 217, 221) 100%,                    rgb(211, 217, 221) 100%, rgb(211, 217, 221) 100%);" colspan="2" /> </tr>        <tr style="border-top-color: transparent; border-top-style: solid;">            <td style="color: rgb(51, 51, 51);">Female</td>            <td align="right" style="color: rgb(51, 51, 51);">4,957</td>        </tr>        <tr>            <td height="12" style="background: linear-gradient(to right, rgb(88, 181, 230) 0%,                    rgb(88, 181, 230) 84.81%, rgb(211, 217, 221) 84.81%,                    rgb(211, 217, 221) 84.81%, rgb(211, 217, 221) 100%);" colspan="2" /> </tr>    </tbody></table>


The HTML in Design Studio

Here is a screen shot of the HTML (in the Additional Properties sheet) of the HTML extension inside Design Studio:

HTML_in_DesignStudio.png


That's good, but what else can the HTML extension do?

As well as a lot of HTML tags, the extension also works with a lot of the SVG tags.

You can read more about which tags and tag attributes are supported in the About tab of the Additional Properties sheet in the HTML extension inside Design Studio.


Here is an example of the SVG tiger with the HTML extension:

SVG_tiger.png



Resources

  • Link to the HTML extension on github - coming soon...


Your First Extension: Part 4d - The APS Interacting with the Canvas

$
0
0

This is part of a tutorial series on creating extension components for Design Studio.

 

In the last instalment, we noted that the APS can't directly determine the component dimensions, height and width.  Therefore, we added a requirement that the user maintain those values.  This is problematic , from a usability perspective.  The user may end up with different values in the APS than in the canvas.  In this installment, we'll fix that; at least partially.

 

 

You may have noticed something in Part 3d, when we wired up our gauge for properties.  The height and width of the component are editable properties in the property sheet.  In fact, they are always there and are inherited from the SDK.  You might also have noticed that:

 

  • Unlike other properties, we did not mirror them in the canvas.

 

  • We had no getters and setters for them.

 

  • We used an odd syntax for determining them; something along the lines of

var myWidth = me.$().width();

 

 

Height and width are not like conventional, developer defined properties.  They are not synchronized via the normal SDK mechanisms and are handled via deeper Design Studio plumbing, but they are available directly from the component's container.  Hence the JQuery call fetch their values.  Since the APS is not inheriting these "hidden" properties, we can't simply make the same call in the APS.  Since they don't participate in the standard getter/setter based property synchronization, we can simply create getters and setters for them.  We can ask the canvas, however.  

 

 

Introducing  callRuntimeHandler()

 

One of the functions that you inheret, when you subclass sap.designstudio.sdk.PropertyPage is callRuntimeHandler().  With it, your APS JavaScript class can make calls directly into the canvas JavaScript class.  It is quite simple actually.  The canvas based function may return a value, or it may be void.  So you may use it to trigger an action in the canvas; from the APS.  Today, we're going to use it to extract some information.

 

We are going to do three things:

  • We'll add two "getter" methods for height and width to the canvas class in component.js; one for each of the two properties.

 

  • We'll use callRuntimeHandler() to call these two getter methods.  We'll do this in the redraw() function.

 

  • As we no longer need the Height and Width fields of the form input enabled, we'll change them from input to p elements.

 

In component.js from last time, we'll insert the two getter functions.

//Getters for the height and width of the component

me.getWidth = function(){

  return me.$().width();

};

 

me.getHeight = function(){

  return me.$().height();

};

 

 

In additional_properties_sheet.js, at the very start of the me.redraw() function, we'll insert the callRuntimeHandler() calls to get the height and width values and store them in our local copies.

//Update the height and width by getting them from the canvas

me._widthProxy = me.callRuntimeHandler("getWidth");

me._heightProxy = me.callRuntimeHandler("getHeight");

 

 

The width and height rows in additional_properties_sheet.html

<tr>

  <td>Width</td>

  <td>

  <p id="aps_width"></p>

  </td>

</tr>

<tr>

  <td>Height</td>

  <td>

  <p id="aps_height"></p>

  </td>

</tr>

 

 

That's it!  Now, whenever we refresh the padding visualizer, we'll be using the current value of the height and width from the properties sheet.  There is one caveat and we still have a minor usability problem, with no workaround.  If the user updated ONLY the height or width, then no property changed event is triggered and no refresh happens (because there is no place for us to trigger me.redraw()).  It is also not  possible to message out from the canvas, so we can't trigger anything in the APS from there.  This means that if the user modified these two settings and does nothing afterwards in the properties pane, she'll have to manually click on the refresh button in the APS.  If she edits a padding property afterwards, however, the visualizer will automatically be refreshed.

 

 

Reference

 

We'll show the full code of the three files that we changed in this installment.

 

Our component.js now looks like this:

sap.designstudio.sdk.Component.subclass("com.sap.sample.scngauge.SCNGauge", function() {

 

 

  var me = this;

  //Properties

  me._colorCode = 'blue';

  me._innerRad = 0.0;

  me._outerRad = 0.0;

  me._endAngleDeg = 90.0;

  me._startAngleDeg = -90.0;

  me._paddingTop = 0;

  me._paddingBottom = 0;

  me._paddingLeft = 0;

  me._paddingRight = 0;

  me._offsetLeft = 0;

  me._offsetDown = 0;

 

  //Validate the Inner and Outer Radii

  me.validateRadii = function(inner, outer) {

  if (inner <= outer) {

  return true;

  } else {

  return false;

  }

  };

 

 

  //Recalculate Outer Radius.  Also, double check that the new value fits with me._innerRad

  me.recalculateOuterRadius = function(paddingLeft, paddingRight, paddingTop, paddingBottom){

  // Find the larger left/right padding

  var lrPadding = paddingLeft + paddingRight;

  var tbPadding = paddingTop + paddingBottom;

  var maxPadding = lrPadding;

  if (maxPadding < tbPadding){

  maxPadding = tbPadding

  }

  var newOuterRad = (me.$().width() - 2*(maxPadding))/2;

  var isValid = me.validateRadii(me._innerRad, newOuterRad);

  if (isValid === true){

  me._outerRad = newOuterRad;

  return true;

  }

  else {

  return false;

  }

  }

 

  //Getters and Setters

  me.colorCode = function(value) {

  if (value === undefined) {

  return me._colorCode;

  } else {

  me._colorCode = value;

  me.redraw();

  return me;

  }

  };

 

  me.innerRad = function(value) {

  if (value === undefined) {

  return me._innerRad;

  } else {

 

  var isValid = me.validateRadii(value, me._outerRad);

  if (isValid === false){

  alert("Warning!  The gauge arc can't have a small inner radius than outer!  Inner Radius must be equal to or less than " + me._outerRad);

  alert("Please decrease the inner radius, or increase the size of the control.  Height & width (including subtraction for padding) must me at least twice as large as Internal Radius!");

  } else {

  me._innerRad = value;

  me.redraw();

  }

  return this;

  }

  };

 

  me.endAngleDeg = function(value) {

  if (value === undefined) {

  return me._endAngleDeg;

  } else {

  me._endAngleDeg = value;

  me.redraw();

  return this;

  }

  };

 

 

  me.startAngleDeg = function(value) {

  if (value === undefined) {

  return me._startAngleDeg;

  } else {

  me._startAngleDeg = value;

  me.redraw();

  return this;

  }

  };

 

  me.paddingTop = function(value) {

  if (value === undefined) {

  return me._paddingTop;

  } else {

  var isValid =me.recalculateOuterRadius(me._paddingLeft, me._paddingRight, value, me._paddingBottom);

  if (isValid === false){

  alert("Warning!  The gauge arc can't have a small inner radius than outer!  Outer Radius must be equal to or greater than " + me._innerRad);

  alert("Please decrease the inner radius, or increase the size of the control.  Height & width (including subtraction for padding) must me at least twice as large as Internal Radius!");

  } else {

  me._paddingTop = value;

  me.redraw();

  }

  return this;

  }

  };

 

  me.paddingBottom = function(value) {

  if (value === undefined) {

  return me._paddingBottom;

  } else {

  var isValid = me.recalculateOuterRadius(me._paddingLeft, me._paddingRight, me._paddingTop, value);

  if (isValid === false){

  alert("Warning!  The gauge arc can't have a small inner radius than outer!  Outer Radius must be equal to or greater than " + me._innerRad);

  alert("Please decrease the inner radius, or increase the size of the control.  Height & width (including subtraction for padding) must me at least twice as large as Internal Radius!");

  } else {

  me._paddingBottom = value;

  me.redraw();

  }

  return this;

  }

  };

 

  me.paddingLeft = function(value) {

  if (value === undefined) {

  paddingLeft = me._paddingLeft;

  return paddingLeft;

  } else {

  var isValid = me.recalculateOuterRadius(value, me._paddingRight, me._paddingTop, me._paddingBottom);

  if (isValid === false){

  alert("Warning!  The gauge arc can't have a small inner radius than outer!  Outer Radius must be equal to or greater than " + me._innerRad);

  alert("Please decrease the inner radius, or increase the size of the control.  Height & width (including subtraction for padding) must me at least twice as large as Internal Radius!");

  } else {

  me._paddingLeft = value;

  me.redraw();

  }

  return this;

 

 

  }

  };

 

  me.paddingRight = function(value) {

  if (value === undefined) {

  paddingRight = me._paddingRight;

  } else {

  var isValid = me.recalculateOuterRadius(me._paddingLeft, value, me._paddingTop, me._paddingBottom);

  if (isValid === false){

  alert("Warning!  The gauge arc can't have a small inner radius than outer!  Outer Radius must be equal to or greater than " + me._innerRad);

  alert("Please decrease the inner radius, or increase the size of the control.  Height & width (including subtraction for padding) must me at least twice as large as Internal Radius!");

  } else {

  me._paddingRight = value;

  me.redraw();

  }

  return this;

  }

  };

 

 

 

 

 

  me.redraw = function() {

 

 

  var myDiv = me.$()[0];

 

  // Clear any existing gauges.  We'll redraw from scratch

  d3.select(myDiv).selectAll("*").remove(); 

 

  var vis = d3.select(myDiv).append("svg:svg").attr("width", "100%").attr("height", "100%");

  var pi = Math.PI;

 

  // Find the larger left/right padding

  var lrPadding = me._paddingLeft + me._paddingRight;

  var tbPadding = me._paddingTop + me._paddingBottom;

  var maxPadding = lrPadding;

  if (maxPadding < tbPadding){

  maxPadding = tbPadding

  }

 

  me._outerRad = (me.$().width() - 2*(maxPadding))/2;

 

  //Don't let the innerRad be greater than outer rad

  if (me._outerRad <= me._innerRad){

  alert("Warning!  The gauge arc can't have a negative radius!  Please decrease the inner radius, or increase the size of the control.  Height & width (including subtraction for padding) must me at least twice as large as Internal Radius!");

  }

 

  //The offset will determine where the center of the arc shall be

  me._offsetLeft = me._outerRad + me._paddingLeft;

  me._offsetDown = me._outerRad + me._paddingTop;

 

  var arcDef = d3.svg.arc()

  .innerRadius(me._innerRad)

  .outerRadius(me._outerRad)

  .startAngle(me._startAngleDeg * (pi/180)) //converting from degs to radians

  .endAngle(me._endAngleDeg * (pi/180)); //converting from degs to radians

 

 

  var guageArc = vis.append("path")

     .style("fill", me._colorCode)

     .attr("width", me.$().width()).attr("height", me.$().height()) // Added height and width so arc is visible

     .attr("transform", "translate(" + me._offsetLeft + "," + me._offsetDown + ")")

     .attr("d", arcDef);

  };

 

 

  me.init = function() {

  me.redraw();

  };

 

 

  //Getters for the height and width of the component

  me.getWidth = function(){

  return me.$().width();

  };

 

  me.getHeight = function(){

  return me.$().height();

  };

 

 

 

 

});

 

 

 

Our additional_properties_sheet.js now looks like this:

sap.designstudio.sdk.PropertyPage.subclass("com.sap.sample.scngauge.SCNGaugePropertyPage",  function() {

 

 

  var me = this;

 

  //Viz definitiions

  me.lineThickness = 2;

 

 

  //Outer Dimensions & Positioning

  me._paddingTop = 0;

  me._paddingBottom = 0;

  me._paddingLeft = 0;

  me._paddingRight = 0;

 

  //Height and Width Proxies

  me._widthProxy = 200;

  me._heightProxy = 200;

 

 

 

 

  me.init = function() {

  $("#form").submit(function() {

  me._paddingTop = parseInt($("#aps_padding_top").val());

  me._paddingBottom = parseInt($("#aps_padding_bottom").val());

  me._paddingLeft = parseInt($("#aps_padding_left").val());

  me._paddingRight = parseInt($("#aps_padding_right").val());

 

  me.firePropertiesChanged(["paddingTop", "paddingBottom", "paddingLeft", "paddingRight"]);

  me.redraw();

  return false;

  });

  me.redraw();

  };

 

 

  me.paddingTop = function(value) {

  if (value === undefined) {

  return me._paddingTop

  }

  else {

  me._paddingTop = value;

  me.redraw();

  return me;

  }

  };

 

  me.paddingBottom = function(value) {

  if (value === undefined) {

  return me._paddingBottom

  }

  else {

  me._paddingBottom = value;

  me.redraw();

  return me;

  }

  };

 

  me.paddingLeft = function(value) {

  if (value === undefined) {

  return me._paddingLeft ;

  }

  else {

  me._paddingLeft = value;

  me.redraw();

  return me;

  }

  };

 

  me.paddingRight = function(value) {

  if (value === undefined) {

  return me._paddingRight;

  }

  else {

  me._paddingRight = value;

  me.redraw();

  return me;

  }

  };

 

  me.redraw = function() {

  //Update the height and width by getting them from the canvas

  me._widthProxy = me.callRuntimeHandler("getWidth");

  me._heightProxy = me.callRuntimeHandler("getHeight");

 

  $("#aps_padding_top").val(me._paddingTop);

  $("#aps_padding_bottom").val(me._paddingBottom);

  $("#aps_padding_left").val(me._paddingLeft);

  $("#aps_padding_right").val(me._paddingRight);

  $("#aps_width").text(me._widthProxy);

  $("#aps_height").text(me._heightProxy);

 

  // Clear any existing content.  We'll redraw from scratch

  d3.select("#content").selectAll("*").remove();

  var vis = d3.select("#content").append("svg:svg").attr("width", "100%").attr("height", "100%");

  var pi = Math.PI;

 

 

  //Line Accessor Function

  var lineAccessor = d3.svg.line()

  .x(function(d) { return d.x; })

  .y(function(d) { return d.y; })

  .interpolate("linear");

 

 

 

 

  ///////////////////////////////////////////

  //Gauge Dummy

  ///////////////////////////////////////////

 

 

  //Determing the position of the gauge dummy (black circle)

  // Find the larger left/right padding

  var lrPadding = me._paddingLeft + me._paddingRight;

  var tbPadding = me._paddingTop + me._paddingBottom;

  var maxPadding = lrPadding;

  if (maxPadding < tbPadding){

  maxPadding = tbPadding

  }

 

 

  //Do the same with the overall height and width

  var smallerAxis = me._heightProxy;

  if (me._widthProxy < smallerAxis){

  smallerAxis = me._widthProxy

  }

 

  var outerRad = (smallerAxis - 2*(maxPadding))/2;

  $("#aps_radius").text(outerRad);

 

 

  //The offset will determine where the center of the arc shall be

  var offsetLeft = outerRad + me._paddingLeft;

  var offsetDown = outerRad + me._paddingTop;

 

 

  //The black Circle

  var arcDef = d3.svg.arc()

  .innerRadius(0)

  .outerRadius(outerRad)

  .startAngle(-180 * (pi/180)) //converting from degs to radians

  .endAngle(180 * (pi/180)); //converting from degs to radians

 

 

  var guageDummy = vis.append("path")

  .style("fill", "black")

  .attr("width", me._widthProxy).attr("height", me._heightProxy) // Added height and width so arc is visible

  .attr("transform", "translate(" + offsetLeft + "," + offsetDown + ")")

  .attr("d", arcDef);

 

 

  ///////////////////////////////////////////

  //Line Data

  ///////////////////////////////////////////

  var lineDataOuter = [{"x":0, "y":0}, {"x": me._widthProxy, "y":0}, {"x": me._widthProxy, "y":me._heightProxy}, {"x":0, "y":me._heightProxy}, {"x":0, "y":0}];

  var lineDataPaddingLeft = [{"x":0, "y":0}, {"x":me._paddingLeft, "y":0}, {"x":me._paddingLeft, "y":me._heightProxy}, {"x":0, "y":me._heightProxy}, {"x":0, "y":0}];

  var lineDataPaddingRight = [{"x":( me._widthProxy - me._paddingRight), "y":0}, {"x": me._widthProxy, "y":0}, {"x": me._widthProxy, "y":me._heightProxy}, {"x":( me._widthProxy - me._paddingRight), "y":me._heightProxy}, {"x":( me._widthProxy - me._paddingRight), "y":0}];

  var lineDataPaddingUpper= [{"x":0, "y":0}, {"x": me._widthProxy, "y":0}, {"x": me._widthProxy, "y":me._paddingTop}, {"x":0, "y":me._paddingTop}, {"x":0, "y":0}];

  var lineDataPaddingLower = [{"x":0, "y":(me._heightProxy - me._paddingBottom)}, {"x": me._widthProxy, "y":(me._heightProxy - me._paddingBottom)}, {"x": me._widthProxy, "y":me._heightProxy}, {"x":0, "y":me._heightProxy}, {"x":0, "y":(me._heightProxy - me._paddingBottom)}];

 

 

  var lineDataCrosshairsHorizontal = [{"x":me._paddingLeft, "y":(me._paddingTop + outerRad) }, {"x":(me._paddingLeft + 2*outerRad), "y":(me._paddingTop + outerRad) }];

  var lineDataCrosshairsVertical = [{"x":(me._paddingLeft  + outerRad), "y":me._paddingTop }, {"x":(me._paddingLeft  + outerRad), "y":(me._paddingTop + 2*outerRad) }];

 

 

 

 

  var borderLinesPaddingLeft = vis

  .attr("width",  me._widthProxy).attr("height", me._heightProxy) // Added height and width so line is visible

  .append("path")

  .attr("d", lineAccessor(lineDataPaddingLeft))

  .attr("stroke", "blue")

  .attr("stroke-width", me.lineThickness)

  .attr("fill", "none");

  var borderLinesPaddingRight = vis

  .attr("width",  me._widthProxy).attr("height", me._heightProxy) // Added height and width so line is visible

  .append("path")

  .attr("d", lineAccessor(lineDataPaddingRight))

  .attr("stroke", "blue")

  .attr("stroke-width", me.lineThickness)

  .attr("fill", "none");

  var borderLinesPaddingUpper = vis

  .attr("width",  me._widthProxy).attr("height", me._heightProxy) // Added height and width so line is visible

  .append("path")

  .attr("d", lineAccessor(lineDataPaddingUpper))

  .attr("stroke", "blue")

  .attr("stroke-width", me.lineThickness)

  .attr("fill", "none");

  var borderLinesPaddingLower = vis

  .attr("width",  me._widthProxy).attr("height", me._heightProxy) // Added height and width so line is visible

  .append("path")

  .attr("d", lineAccessor(lineDataPaddingLower))

  .attr("stroke", "blue")

  .attr("stroke-width", me.lineThickness)

  .attr("fill", "none");

 

 

  var borderLinesOuter = vis

  .attr("width",  me._widthProxy).attr("height", me._heightProxy) // Added height and width so line is visible

  .append("path")

  .attr("d", lineAccessor(lineDataOuter))

  .attr("stroke", "black")

  .attr("stroke-width", me.lineThickness)

  .attr("fill", "none");

  var borderLinesCrosshairHorizontal = vis

  .attr("width",  me._widthProxy).attr("height", me._heightProxy) // Added height and width so line is visible

  .append("path")

  .attr("d", lineAccessor(lineDataCrosshairsHorizontal))

  .attr("stroke", "white")

  .attr("stroke-width", me.lineThickness)

  .attr("fill", "none");

  var borderLinesCrosshairVertical = vis

  .attr("width",  me._widthProxy).attr("height", me._heightProxy) // Added height and width so line is visible

  .append("path")

  .attr("d", lineAccessor(lineDataCrosshairsVertical))

  .attr("stroke", "white")

  .attr("stroke-width", me.lineThickness)

  .attr("fill", "none");

  }

});

 

 

Our additional_properties_sheet.html now looks like this:

<html>

  <head>

  <title>Gauge Padding Visualizert</title>

  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />

  <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.6/d3.min.js" charset="utf-8"></script>

  <script src="/aad/zen.rt.components.sdk/resources/js/sdk_propertysheets_handler.js"></script>

  <script src="additional_properties_sheet.js"></script>

  </head>

  <script>

  new com.sap.sample.scngauge.SCNGaugePropertyPage();

  </script>

  <body>

  <form id="form">

  <fieldset>

  <legend>Gauge Padding Visualizer</legend>

  <table>

  <tr>

  <td>Padding Top</td>

  <td>

  <input id="aps_padding_top" type="number" name="paddingTop" size="40" maxlength="40"></input>

  </td>

  </tr>

  <tr>

  <td>Padding Bottom</td>

  <td>

  <input id="aps_padding_bottom" type="number" name="paddingBottom" size="40" maxlength="40"></input>

  </td>

  </tr>

  <tr>

  <td>Padding Left</td>

  <td>

  <input id="aps_padding_left" type="number" name="paddingLeft" size="40" maxlength="40"></input>

  </td>

  </tr>

  <tr>

  <td>Padding Right</td>

  <td>

  <input id="aps_padding_right" type="number" name="paddingRight" size="40" maxlength="40"></input>

  </td>

  </tr>

  <tr>

  <td>Width</td>

  <td>

  <p id="aps_width"></p>

  </td>

  </tr>

  <tr>

  <td>Height</td>

  <td>

  <p id="aps_height"></p>

  </td>

  </tr>

  <tr>

  <td>Outer Radius</td>

  <td>

  <p id="aps_radius"></p>

  </td>

  </tr>

  <tr>

  <td>

  <input name="submit"  type="submit" value="Refresh"/>

  </td>

  </tr>

 

  </table>

  </fieldset>

  </form>

  <div id='content'></div>

  <div id='componentproxy'></div>

  </body>

</html>

Creating a Fiori-like comparison tile with Design Studio and two extensions

$
0
0

Hey everybody!

 

This blog assumes you have some experience creating applications with SAP BusinessObjects Design Studio with CSS and scripting.

 

Last time out in Extending Design Studio with an HTML extension and HTML bars I showed you how to use HTML (and avoid script injection) to create a comparison bar for SAP BusinessObjects Design Studio, so you could make something that looks a bit like this:

HTML_layout_and_bars.png

Now, I will show you how to use that HTML within Design Studio including how to update it at runtime with real data.

 

Pre-requisites

You can find links to these in the resources section below...

 

For this you will either need an understanding of HTML and scripting in Design Studio as well as:

  1. The HTML extension and the Design Studio SDK sample KPI Tile extension installed.
  2. OR the HTML extension and use out of the box Design Studio controls for the rest of the tile...
  3. OR if you could try the Rapid Prototyping HTML extension but you then need to watch out for script injection on your own.

 

The choice is yours... Although I'd recommend option 1 for following along with this blog


Steps

These steps are for SAP BusinessObjects Design Studio 1.6 to create a fiori-like Headcount tile:

Comparison+Tile+screenshot.png


Create the overall tile in your Design Studio application


  1. Add a Panel component to the canvas and use these settings:
    1. Width: 184
    2. Height: 176
  2. Add a KPI Tilecomponentinto your panel and use these settings:
    1. Top Margin: 0
    2. Left Margin: 0
    3. Width: 184
    4. Height: 176
    5. Header: Headcount
    6. Title:
    7. Footer:
    8. Value:
    9. Value Suffix:
  3. Add a HTMLcomponentinto your panel and use these settings:
    1. Top Margin: 40
    2. Left Margin: 10
    3. Width: 164
    4. Height: 100

 

The tile should look something like this:
KPI_tile_blank.png

 

Create the Design Studio script to set the HTML bars at runtime

 

Here is the HTML for the hard coded bars from the last blog:

 

<table width="100%" style="bottom: 14px; border-collapse: collapse; position: absolute; line-height: normal; font-family: Arial, Helvetica, sans-serif; font-size: 14px; font-weight: normal;" border="0">    <tbody>        <tr style="border-top-color: transparent; border-top-style: solid;">            <td style="color: rgb(51, 51, 51);">Male</td>            <td align="right" style="color: rgb(51, 51, 51);">5,845</td>        </tr>        <tr>            <td height="12" style="background: linear-gradient(to right, rgb(88, 181, 230) 0%, rgb(88, 181, 230) 100%, rgb(211, 217, 221) 100%, rgb(211, 217, 221) 100%, rgb(211, 217, 221) 100%);" colspan="2" /> </tr>        <tr style="border-top-color: transparent; border-top-style: solid;">            <td style="color: rgb(51, 51, 51);">Female</td>            <td align="right" style="color: rgb(51, 51, 51);">4,957</td>        </tr>        <tr>            <td height="12" style="background: linear-gradient(to right, rgb(88, 181, 230) 0%, rgb(88, 181, 230) 84.81%, rgb(211, 217, 221) 84.81%, rgb(211, 217, 221) 84.81%, rgb(211, 217, 221) 100%);" colspan="2" /> </tr>    </tbody></table>

We are now going to turn that script into global scripts within Design Studio:

 

  1. In Design Studio, under Technical Components, create a Global Scripts Object
  2. We will split the script into three parts:
    1. The header - the top of the HTML table
    2. A bar - we will call this for the Male and the Female entries
    3. The footer - the bottom of the HTML table
  3. Create the table header HTML global script function:
    1. On GLOBAL_SCRIPTS_1 create a script function:
      1. Name: createMiniChartHeader
      2. Input Parameters:
        1. None
      3. Return Type:
        1. String
      4. Code:
        return "<table width='100%' style='bottom: 14px; " +
         "border-collapse: collapse; " +
         "position: absolute; line-height: normal; " +
         "font-family: Arial, Helvetica, sans-serif; " +
         "font-size: 14px; font-weight: normal;' border='0'>" +
         "<tbody>";
  4. Create the bar HTML global script function:
    1. On GLOBAL_SCRIPTS_1 create a script function:
      1. Name: createMiniChartBar
      2. Input Parameters:
        1. title of type String
        2. valueText of type String
        3. valuePercent of type float
        4. color of type String
        5. noFillColor of type String
      3. Return Type:
        1. String
      4. Code:
        // Max bar is 100%.
        valuePercent = Math.min(100.0, valuePercent);
        // Return the title, value and linear gradient bar html for the value percentage.
  5. Create the table footer HTML global script function:
    1. On GLOBAL_SCRIPTS_1 create a script function:
      1. Name: createMiniChartFooter
      2. Input Parameters:
        1. None
      3. Return Type:
        1. String
      4. Code:
        return "</tbody></table>";
  6. The data behind this app is simple:

    TitleValue
    Male5845
    Female4957
  7. The data source DS_1 looks like this in Edit Initial View:
    KPI_tile_EditInitialView.png
  8. And finally for the scripting, create the script that uses the three mini chart scripts to create the bars at runtime:
    1. We are going to create the application startup script that:
      1. Reads the titles and values from from DS_1
      2. Keeps track of the totalvalue and maximumvalue (used to calculate the value percentages later on)
      3. Creates the mini chart header HTML
      4. For each title from DS_1 it adds a mini chart bar HTML based on the value percentage (so in this case one for Male and one for Female)
      5. Creates the mini chart footer
      6. Shows the mini chart in HTML_1
      7. Shows the total value in the KPI_TILE_1 footer
    2. On the application add the following to your On Startup event script:
    3. // Calculate max value.
      var title = "";
      var value = 0.0;
      var maxValue = 0.0;
      var totalValue = 0.0;
      var memberValues = DS_1.getMembers("Title", 2);
      memberValues.forEach(function(element, index) {  // Get the value title and value.  title = element.internalKey;  value = DS_1.getData("Value", {    "Title": title  }).value;  // Keep track of the total and maximum value.  totalValue = totalValue + value;  maxValue = Math.max(value, maxValue);
      });
      // Create the mini chart HTML header.
      var miniChart = GLOBAL_SCRIPTS_1.createMiniChartHeader();
      // Create the mini chart HTML bars.
      var valuePercent = 0.0;
      var valueText = "";
      var numFormat = "#,##0";
      var color = "rgb(88, 181, 230)";  // Blue.
      var noFillColor = " rgb(211, 217, 221)";  // Light gray.
      memberValues.forEach(function(element, index) {  // Get the value title and value.  title = element.internalKey;  value = DS_1.getData("Value", {    "Title": title  }).value;  // Convert the value into a formatted string.  valueText = Convert.floatToString(value, numFormat);  // Calculate the value percentage.  valuePercent = 0;  if (maxValue > 0) {    valuePercent = value / maxValue * 100.0;  }  // Add the bar HTML.  miniChart = miniChart +        GLOBAL_SCRIPTS_1.createMiniChartBar(title,            valueText, valuePercent, color, noFillColor);
      });
      // Create the mini chart HTML footer.
      miniChart = miniChart + GLOBAL_SCRIPTS_1.createMiniChartFooter();
      // Set the HTML bar.
      HTML_1.setHtml(miniChart);
      // Set the KPI Tile footer - the total value.
      KPITILE_1.setFooterText("Total: " +    Convert.floatToString(totalValue, numFormat));
  9. Test your app in a browser and it should look something like this:
    KPI_tile_before_CSS.png

Thats getting very close, finally we can add some CSS to the tile title and total.

 

Add CSS for styling the tile and bars

  1. Go to the Custom CSS setting for your application and if you don't have one assigned, click the blank field and then the Pencil button to create an empty CSS file. Then click the ... button to browse for your file.
  2. Add the following CSS to your Custom CSS file:
  3. .borderBox {
    box-sizing: border-box !important; /* do not include the margins in the overall size, so we match the width and height set in Design Studio */
    }
    .kpiTile {
    background-color: #FFFFFF;
    border: 1px solid #797979;
    border-radius: 4px;
    box-sizing: border-box !important; /* do not include the margins in the overall size, so we match the width and height set in Design Studio */
    }
    .kpiTileHeader {
    font-family: arial,sans-serif; 
    font-size: 16px; 
    font-weight: normal; 
    color: #333333 !important;
    box-sizing: border-box !important; /* do not include the margins in the overall size, so we match the width and height set in Design Studio */
    }
    .kpiTileFooter {
    font-family: arial,sans-serif; 
    font-size: 14px; 
    font-weight: normal; 
    color: #666666;
    box-sizing: border-box !important; /* do not include the margins in the overall size, so we match the width and height set in Design Studio */
    }
  4. Now we can assign these CSS classes to your components:
    1. Change your panel component settings:
      1. CSS Class: borderBox
    2. Change the KPI Tile settings:
      1. CSS Class: kpiTile
      2. Header CSS Class: kpiTileHeader
      3. Footer CSS Class: kpiTileFooter
      4. Footer Horizontal Alignment: right
  5. And thats it!

 

Here is the finished tile running in a web browser

KPI_tile_finished.png

 

And as a bonus exercise you can tidy up the Design Studio script by moving the header and bar styles into your CSS file.

 

Thanks for tuning in, Happy Holidays, see you again in 2016!


Resources


Viewing all 646 articles
Browse latest View live


<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>