Quantcast
Channel: SCN : Blog List - SAP CRM: Webclient UI - Framework
Viewing all 195 articles
Browse latest View live

BPath. Lesson 08. Data type and operation table. Smaller topics.

$
0
0
<< 07. Model Check, exceptions and attribute longtextupcoming.... >>

 

Hello everybody,

 

the last lesson caused some discussions on various difficulties to formulate correct BPath statements as some pieces of information were simply not yet communicated fully in this Blog Series. So before describing new content I want to go back some steps and clarify some topics and provide some tables with information.

 

A rule for in-between: 1+1+1 is not 3, but a BPath Error.

 

Keep in mind that the BPath Parser only deals with operations with 2 operands, so (1+1+1) will result in an error and should be rephrased to ((1+1)+1). This of course also applies to comparisons, so ((x=0)&(y=0)&(z=0)) cannot be parsed. Instead (((x=0)&(y=0))&(z=0)) should be used.

 

Available data types

 

There was a request in respect to raw data types, this is not supported. Raw fields might be read, but the content can not be compared to literals or other values as now corresponding data type exists.

 

Short

Name

Null

Description

S

String

""

Character strings, encapsulated with ". Every "-character in the string has to be escaped with "" (double double quotes).

N

Numeric

0

numeric values without sign, e.g 3.141. Negative literals have to be written as 0-value

D

Date

#19000101#

Date values in the following format #yyyymmdd#.

B

Boolean

false

true/false value

R

Data ref.

reference to data entity, typically returned by a sub function

O

Object ref.

reference to object

X

invalid

internally used for invalid objects, which are later handled by the GET() function


The Null value is the only value of the specific type where the function NOT(...) returns true.

 

Operation/Function Table

 

Meaning
String
Num
Date
Bool
&

and,

concat

S & S -> S (concat)
B & B -> B (and)
|
or
B | B -> B (or)
+

add, add days,

number to string

string to number

S + S -> S (concat)
S + N -> S *3
N + N -> N (add)
N + S -> N *4
D + N -> D *1
B + B -> B (or)
-

minus, substr

replace first,

substract days,

remove last chars

S - S -> S *6
S - N -> S *7
N - N -> N (substr.)
D - N -> D *2
B - B -> B *5
*
(string) multiply,if
S * N -> N *8
S * B -> S *9
N * N -> N (mlt)
N * B -> N *9
D * B -> D *9
B * B -> S (and / if)
%

divide, replace all,

remove first chars

S % S -> S *10
S % N -> S *11
N % N -> N (Div)
B % B -> B *12
Comp*13
comparison
S X S -> B
N X N -> B
D X D -> B
B X B -> B
NOT
not
NOT(S) -> B
NOT(N) -> B
NOT(D) -> B
NOT(B) -> B
IFF
? Operator
IFF(B,X,X)->X

 

 

1. Adds number of days to a date

2. Subtracts number of days from a date

3. Converts number into string (as provided by ABAP) and concatenates it to the given string

4. Converts string into number (as provided by ABAP) and adds it to number.

5. Op1 = True or Op2 = False

6. Replace first occurrence of op2 in Op1 with empty string

7. Removes last n characters from string (if n <= strlen)

8. String multiplication (e.g. "la" * 3 = "lalala" ) [if n >= 0]

9. Logical if. If Boolean value is true original element is returned otherwise NULL element ("" / 0 / #19000101# / FALSE ).

10. Replace all occurrences of op2 in Op1 with empty string

11. Removes first n characters from string (if n <= strlen)

12. Op1 = True and Op2 = False

13. Supported are the standard comparisons =, <, >, <=, >=, <>. The types of the compared elements must be equal.

14. NOT: returns true if element is equal to NULL element

15. IFF: returns Parameter 2 if Parameter 1 is true otherwise Parameter 3. Parameter 1 must be of type Boolean, the other parameters are not checked (theoretically P2 and P3 must not be of the same type, but logically they should)

 

Additional remark:

 

As BPath has no concept of returnings errors some of the functions are defined in a way that the error case is caught and a Special value is returned. This is the case for the simple divide operation X % 0, which will always return 0 if the second Operand is 0. Attempts to convert a string, which does not contain a number to a number, will also result in a 0. Example would be the Expression: 1+"test"

 

 

Numeric Values

 

With version 2.2 BPath was enhanced to Support full numeric values (instead of integer), internally they base on DECFLOAT34. Some remarks:

 

  • the return types of fields basing on internal calculations are also DECFLOAT34 now
  • numeric literals including decimal points as 0.5 and 23.001 are possible
  • The dot may not be the first character of the literal. Use 0.5 instead of .5
  • negative numeric literals as -1 can still not be expressed directly, since the the minus sign would be interpreted as minus. Use 0-1 instead.

 

~*/SearchResFlightRel/FlightBookRel[@LUGGWEIGHT>23.5]{!A=@LUGGWEIGHT;!B=@LUGGWEIGHT*2}$

Code Example 35, [@LUGGWEIGTH>23.5], numeric calculations

 

Direct Attribute Retrieval

 

In case you simply want to copy an attribute from the current structure you might use the syntax as specified in the example:

 

~*/SearchResFlightRel/FlightBookRel{@FORCURAM;@FORCURKEY;@@CUSTTYPE}$
Code Example 36,{@FORCURAM;@FORCURKEY;@@CUSTTYPE}, direct attribute retrieval

 

This should work for enhanceable structures as well as for non-enhanceable structures. For normal fields a target with the same name is used, for long text fields the suffix '_LONG' will be appended to the target name. The assignment block in the example above is simply a shortcut for the slightly longer syntax: {!FORCURAM:=@FORCURAM;!FORCURKEY:=@FORCURKEY;!CUSTTYPE_LONG:=@@CUSTTYPE}.

 

Non-reliable assignments using subs (note 1533088 in case your version is 2.3)

 

Typically any assignment based on expressions or functions has a well defined result. This may be no more true in case the right side of the assignment contains a sub-function, since the sub function returns initial if the BPATH query can not be executed till the end (typically if a relation is used which delivers no elements).

Originally the initial reference was assigned to the target structure, but this leads to problems (means dumps) in case a following entry returns a valid entry which is not of type 'data reference'.

This leads to the following change: Assignments to initial references are not executed. If there is a valid entry following afterwards, the column will be created then including the omitted entries. If there is no valid entry following the column will not be part of the target structure.

Please note that this handling has some side effects:

  • If the mentioned column is the only one returned in the query all entries till the first valid entries are omitted and not constructed afterwards. If this is a problem add a second field to the target structure with a dummy entry.
  • It is not guaranteed that a column using a non-reliable assignment is actually available in the result. If this is a problem add a initialization sequence before the sub ( e.g. {!X="";!X=SUB(...)}).
  • internally the value is still an initial reference. So if a sub-method is used within a calculation (e.g. !X=1+SUB(...) ) it is responsibility of the user that the initial case is never occurring. If this can not be guaranteed it is advisable to use results of sub-functions only after a assignment. A "safe" solution would be: {!X=0;!X=SUB(...);!Y=1+!X}.

 

Masking of slashes in relation names, structures, attributes and targets (note 1535509 if your version is 2.3)

 

As with the language definition it was overseen that it is allowed to have relation names, structure names and field names containing a slash. This is very uncommon in development (at least in the areas we developed) but may occur at customer site.

The change, which is available with the mentioned note (or with version 2.4 of course) allows to mask a slash with a preceeding backslash within the following entites:

  • relation names
  • target names
  • attribute names
  • structure names
  • function names (hardly useful, we still recommend to define function names without slashes).

This means, if your BPATH wants to make use of a relation CRM/CustomerRel with the attribute CRM/CustomerNumer the BPATH fragment in this respect looks like: /CRM\/CustomerRel/@CRM\/CustomerNumber.

All surrounding generic consumers of BPATH should be able to handle this accordingly.

 

As the following topics are of a bigger size, I will stop the lesson here. Next Topic is Groupings.


Using OTR text contexts to adapt texts in the SAP CRM Web UI

$
0
0

Introduction

Recently we performed a enhancement package upgrade of the SAP CRM system of a customer form EhP0 to EhP3. During the implementation of the system numerous OTR texts were changed by the implementation partner to adapt the texts visible in the Web UI. For example, the text "Account" was changed to "Geschäftspartner" (i.e. German for business partner) across the system. After the upgrade all these OTR texts where reset again to the SAP standard. As  result, the OTR text needed to be changed again. In this process I started looking for a better solution that would not require changing OTR texts after each upgrade.

While there are quite a view blogs related to OTR texts and SAP CRM Web UI I couldn't really find a solution for the problem. The best practices for SAP CRM Web UI on the SCN (Best Practices for SAP CRM Web UI Customization - CRM - SCN Wiki) also only states that SAP standard texts should not be changed. Through some playing around with the OTR transaction I finally found a nice solution within the SAP standard which I'd like to share here. The basic idea is to create a custom context for the OTR and use this context to adapt the necessary OTR texts.

Creating a custom context

In order to create a custom context one needs to implement the BAdI BTFR_CONTEXT. This BAdI consist of a single method CUSTOMER_CONTEXT that returns the custom context for the OTR. Below is the code of the simple implementation of this BAdI that I created. Note that I have used the |-operator to created the two strings that are returned from this method which is only available in the ABAP application server 7.40 and above.

 

METHOD if_ex_btfr_context~customer_context.     customer_context-country = |DE|.     customer_context-extension = |FACTUR|.
ENDMETHOD

.After implementing the BAdI BTFR_CONTEXT and activating it the custom context is immediately available. To check this open transaction SOTR_EDIT and choose the menu "Edit -> Context -> System Context".

2014-06-15 18_12_40-CVD(1)_100 OTR_ Maintain Initial Screen.png

This opens a pop up displaying the custom system context for OTR texts. If everything worked the country and extension that are set in the BAdI are displayed.

2014-06-15 18_13_53-.png

Creating OTR texts in the custom context

The next step is to create custom OTR texts in the custom context. As an example I'll use the view DetailAccount in the UI component IUICMD. This view contains a button with the label "Account bestätigen" (if the log on language is german).

2014-06-15 18_25_39-Identifikation - [Interaction Center ] - Internet Explorer bereitgestellt von Ih.png

The label for this button is stored in the OTR in the package CRM_IU_IC_6X_MD and is available via the alias CRM_IU_IC_6X_MD/CONFIRMBP.

To create a custom text open transaction SOTR_EDIT, enter CRM_IU_IC_6X_MD/CONFIRMBP as the alias value an choose the menu "Edit -> Context -> Create".

2014-06-15 18_31_51-.png

In the next step the system asks for the context in which the text should be created. The custom context provided by the BAdI implementation is already suggested.

2014-06-15 18_32_55-.png

The next screen is already populated with the SAP standard values of the OTR text CRM_IU_IC_6X_MD/CONFIRMBP. Except the context is set to the custom context that was selected in the pop up (in this case the context provided by the BAdI implementation). Now we can change the default text "Account bestätigen" to the custom text "Geschäftspartner bestätigen" and save the new context.

In order to add the new value to a transport request, click the transport icon.

2014-06-15 18_33_24-CVD(1)_100 OTR_ Change Concept.png

After saving the entry select "Edit -> Context -> Overview" to display an overview of the contexts available to this OTR text. The overview shows text values for different context. In this case the default context as well as the one that was just created.

2014-06-15 18_46_57-.png

As a final step the OTR buffer needs to be reset using the transaction code /$OTR. After restarting the SAP CRM Web UI the label for the button is changed to the new text.

2014-06-15 18_54_02-Identifikation - [Interaction Center ] - Internet Explorer bereitgestellt von Ih.png

Custom contexts and upgrade

Using this approach, the OTR text will not be overwritten in a future system upgrade. However, the system context will be reset in an upgrade. Therefore, the system context needs to be set to the custom context after an upgrade. This can be done in the transaction SOTR_EDIT via the menu "Edit -> Context -> Adjust".

Dropdown list issue in CRM Webclient UI - a very funny trouble shooting process

$
0
0

Issue description

 

 

Today I received a ticket with priority very high complaining that the drop down list for Business Role in Work center "Sales Operation" is empty:

clipboard1.png

However, Using my user I could indeed see lots of drop down list entries there:

clipboard2.png

Issue analysis

 

I didn't start debugging immediately but made the following analysis beforehand:

 

1. I could reproduce this issue in my laptop with my colleagues' user

 

Based on this finding I made the following comparison between my colleague's user and mine:

 

a. compare the authorization settings:

 

result: The two are exactly the same ( both copied from the same user group and we didn't have authorization to make changes ).

Also there is no corresponding entries found in SU53 when I test with my colleague's user.

 

b. compare the user parameter in tcode SU3

result: no difference found related to this issue

 

c. compare the business switch setting

result: the same setting for both user

 

2. compare the final native html source code

 

I do believe there must be differences when the page is rendered via different users. So I use the development tool ( click F12 ) in IE, and soon found the difference:

 

This one is the html source code generated by my user and it works perfectly:

 

clipboard3.png


And this below is the one generated by my colleague's user. The title are completely missing!


clipboard4.png

3. Use mini-system approach to isolate and locate root cause

 

 

Since this issue occurs in CRM Organizational Model management and the underlying business model is huge and complex. In this situation I always prefer to use mini-system approach I described in this blog to try to make the issue also reproducible in the small nutshell. At least I could have the following benefits from the nut shell:


 

a. the nut shell could eliminate all impacts from unrelated components so that I could concentrate on the key part of program which might lead to the issue

 

 

b. the nut shell makes debugging be more efficient. For example sometimes I have to debug how does webclient framework class reacts to my input for my UI component, the method is so generic so that it would be triggered too frequently( like method PROCESS_NAV_QUEUE of class CL_BSP_WD_VIEW_MANAGER, which have helped me to resolve many issues ). Using nutshell I do not need to press "deactivate all BPs again and again.

 

clipboard5.png


I just spent 5 minutes to build a nutshell, which contains a view, a value node and one attribute. The attribute contains GET_V and GET_P method whose source code are copied from standard Org management UI component.


clipboard6.png

The nutshell in the runtime looks like below. The issue could be reproduced within it by using my colleague's user.

clipboard7.png

4. Debug the nutshell

 

 

When I debug the GET_V, I soon found the root cause:

 

The following code plan to retrieve profile and description from table crmc_ui_prof_t:

 

clipboard8.png

profile will act as drop down list key and description as drop down list value:


clipboard9.png

Unfortunately between profile and description column in table crmc_ui_prof_t there is another column LANGUAGE:

clipboard10.png

As a result the select statement does not work as expected at all. The SELECT xxx INTO CORRESPONDING FIELDS OF TABLE must be used instead.

clipboard11.png

5. why I could see entries with my own user?

 

 

First of all, the entries displayed with my own user are not correct at all, they are just the wrong entries populated in internal table LT_ROLES, column PROFILE in screenshot above. In the wrong implementation, only drop down list key is filled with the incorrect entries.

 

 

For my user, I have enabled that key will also be displayed in dropdown list, so I could see the incorrect keys in UI. However my colleague didn't enable this so he could only see nothing there, since the drop down list value is not filled due to wrong implementation.

clipboard12.png

6. Why the native HTML source code is different based on different personalization setting?

 

Why my user could see the not initial title attribute in native html source code but my colleague's user could not?

 

It is related to your personalization settings regarding whether dropdown list key must be displayed or not.

 

Just set a breakpoint on method CL_THTMLB_PERSONALIZATION~GET_DDLB_KEY_MODE, start the nutshell.

 

It stops at the place where the drop down list UI element is to be rendered. The callstack is also very helpful if we would like to study how the UI element defined by SAP tag like chtmlb, thtmlb is finally converted to html source code.

clipboard13.png


This setting is evaluated here and populating drop down list entries accordingly:

clipboard14.png

All the entries to be displayed in drop down list is available in the current callstack:

clipboard15.png

Getting the SQL-Select-Statement of a Query in WebUI

$
0
0

Hello,

 

I often wondered how the SQL-Select-Statement of a Query in WebUI looks like. For this you could use the SQL-trace of transactions ST01 or ST05. Alternatively you could put a breakpoint in class CL_CRM_REPORT_ACC_DYNAMIC method DATABASE_ACCESS.

 

Situation: You have some kind of business query in WebUI and you wonder how the SQL_Select-Statement is

 

1.png

 

Step 1: You put a break-point in the mentionned method

 

2.png

 

Here you see the From- and Where-statements.

 

Step 1a: Add a dynamic break-point for select-statements by pressing F9

 

3.png

 

Of course this will give you much more positions in coding.

 

Good luck when investigating.

 

Best regards,

 

Thomas Wagner

BPath. Lesson 09. Groupings

$
0
0
Table of Contents ...... Table of Code Examples
<< Lesson 08. Data types, op. table, smaller topics.upcoming.... >>

 

With this lesson the content gets slightly more complex . And I have to admit: In a way more experimental as it is pretty uncovered ground.

 

Groupings

 

You probably know the term and the meaning from SQL-queries, so you can guess where we are heading to. For full usage of groupings aggregations are required also, but let us begin with basic groupings as with the following easy example:

 

~*/SearchResFlightRel/FlightBookRel$(@AGENCYNUM,UPPER(@SMOKER))
Code Example 37,$(@AGENCYNUM,UPPER(@SMOKER)), basic groupings

 

The $ is now attributed with a list of group elements (agencynum and smoker). Please note that the group element is not necessarily an attribute, any expression is valid. New entries are written to the result table only in case the combination of group elements in the actual data set did not already appear in the result table yet. The result table will contain the first entry of every group. In the chosen example a list of 4116 entries is condensed to about 70 entries.

 

The standard behaviour of the grouping adds the grouping conditions to the result table automatically. The naming of the group conditions follows the following rules:

 

  • if a the condition consists of an unchanged attribute, the name of the attribute is taken
  • if an attribute is used but changed by the usage of a function or an operator, but without usage of a second attribute or a target, the attribute name will be preceeded by a "F_".
  • in all other cases name will be EXPR_i. i is the number of the respective condition.


In the examples above the two attribute would be named AGENCYNUM and F_SMOKER.

 

In some cases it might be required to suppress the automatic adding of the group condition (e.g. it is added with different naming manually in the init part, see next chapter). This is possible with an exclamation mark after the dollar as in the following example:

 

~*/SearchResFlightRel/FlightBookRel$!(@AGENCYNUM,@SMOKER){!DEMO=@AGENCYNUM&@SMOKER}
Code Example 38,$!(@AGENCYNUM,UPPER(@SMOKER)), basic grouping without group condition takeover

 

Grouping with assignments (hand-made aggregations)

 

The grouping syntax is now extended with the possibilities to build assignments based on the group data.

The syntax is as follows: $(ListGroupIdentifier:Initblock:Loopblock)

 

  • ListGroupIdentifier is a non-empty comma separated list of expressions defining the groups as described in the chapter before.
  • Initblock is a block with assignments (separated with ; ) processed with the first element of every group. It is usually used to initialize target variables. The initblock may be empty (the two doublepoints have to be stated nevertheless).
  • Loopblock is a non-empty block with assignments processed with every element of a group.
  • Any used target variable in one of the two group assignment blocks refers to the "group" element.
  • As with normal assignments, an assignment might consist of an attribute only, abbreviating an assignment to a target with the same name. Since it is natural to have grouping attributes in the result, it may be added to the init block as in the example below.
  • Of course it is also possible to sort the result on any of the fields, including the ones built by aggregation.

 

The following example:


~*[!AVG]/SearchResFlightRel/FlightBookRel$(@FORCURKEY:!Sum=0;!Amt=0:!Sum=!Sum+1;!Amt=!Amt+@LOCCURAM;!AVG=!Amt%!Sum)
Code Example 39,$(@FORCURKEY:!X=0:!X=!X+1), hand made grouping aggregations

 

groups the bookings according to the foreign currency and calculates the number of bookings in this curreny, the complete amount (in USD) and the average amount (also in USD). Please note that !AVG never appears on the right side of assignment hence it must not be initialized.

 

Limitations:

 

  • sub() calls in the grouping assignments are not supported
  • dereferencing in grouping assingments may lead to problems
  • the syntax as above looks a bit over-crowded and too complex. It is targeted to solve this problem with standard aggregations without the need of initializations and incremental coding.
  • The usage of the GET() function is not sufficient in the loop block since it initializes only once.

 

Group to structure

 

~*/SearchResFlightRel/FlightBookRel$(*:!Sum=0:!Sum=!Sum+1)
Code Example 40,$(*:!X=0:!X=!X+1), group to a structure

 

As shown in the example above it is also possible to group the complete content to one row. There is a technical difference compared to the other examples, since the result here is a structure with the field sum, and not a table.

Note: the original example here tried to group to an INT2 field (see unstructured returns). Unfortunately, this is not working since it is not possible to access unstructured returns for reading, which is required for aggregations.

 

There is the plan to exploit this feature in future together with an enhanced sub()-handling.

 

Some new functions

 

 

List()

 

With the work on groups I realized that the way to realize enumerations is rather unhandy. Consequently I decided to spend a new feature aiming to assist the user in generating lists.

List has the following syntax: LIST(target,separator,content).
See also the example, note that the := assignment is required here:

 

~STRING/SearchResFlightRel/FlightBookRel{!Ret:=LIST(!Ret,",",@PASSNAME)}$(*)

Code Example 41,{!X:=List(!X,",",@Feld)}, LIST function

 

The list function has the following characteristics:

  • The separator separates two entries, it will not appear as first or last character.
  • As like the GET() function, the first parameter may not be available. If !X is not existing it is defaulted with an empty string value.
  • LIST considers empty fields (in the example @PASSNAME is "" ) as empty and they will not be added to the list.

 

Round() and Int()

 

Round and Int are using the ABAP round function with the modes ROUND_HALF_UP and ROUND_DOWN. The second parameter - if available - will be handed over as precision.

 

Log() and Exp()

 

Returns log respectively exponential value of parameter 1. If second parameter is provided it will be used as basis, otherwise e.

 

You want to know why the heck one needs logarithm in a business application? Well, I'll show you:

 

~*/SearchResFlightRel/FlightBookRel$(int(Log(@LOCCURAM,10)):!LMIN=round(exp(int(Log(@LOCCURAM,10)),10),2);!LMAX=round(exp(1+int(Log(@LOCCURAM,10)),10),2):!CNT=Count())

Code Example 42,$(int(Log(@LOCCURAM,10)): Log Function and logged grouping

 

It looks a bit more complex as it is. Try it out and and take a look on the result, which speaks for itself.Well, to explain it nevertheless. We group to the whole value of the decimal logarithm of our base value. This means nothing else, then the number of digits of the dollar amount. The LMIN and the LMAX value simply calculate the boarders of our area, in our example this is probable [100 .. 1000] and [1000 .. 10000]. Note that the border calculation is part of the init block, hence it will be calculated only one time for every group.The example here might be a bit artificial, nevertheless the concept might be useful to group customers/products/projects where the range is between some Pennys to millions of bucks.

 

Exists(), Safe() and Initial() and a word on comparisons on data references

 

Several functions deal with the possibility that a sub function may return no result, i.e. an initial reference. The above methods are designed to catch that problem or react on it.

 

  • exists has one parameter, typically a sub-call. It returns true if the parameter is not an initial reference, i.e. if the sub query returned data
  • safe has two parameters, the first one typically a sub-call, the second one a default value. The default value is chosen in case the first parameter is an initial reference. Please distinguish with the GET() functionality which has similar syntax but different semantics.
  • initial returns the initial value of the handed over type ('S', 'D', 'B', 'N', 'R', 'O') if set. If the function is used without parameter an initial reference will be returned.

 

The comparison functions were also enhanced to allow a comparison against the INITIAL-value. Please note the following two points:

  1. comparisions of references unequal to an initial reference return an undefined result. To the current point the extension is only designed to check against INITIAL, this means at least one parameter should be INITIAL().
  2. comparisions of values of different type are causing a parser error. Checks like sub(...)<>INITIAL() do only work in case the sub-query returns data references.

 

That's the content for lesson 9. In respect to lesson 10, I have to say: "no, we are not finished with aggregations. Not at all."

SAP CRM Web UI Simple Objects - Creating custom 1...n Bol entities

$
0
0

This document shows how to create custom 1...n entities in sap crm via bol browser, which can be linked to any other crm objects by
keeping the guid of that object in our simple object.There are multiple simple object genil classes provided by sap which provided the basic CRUD operations by default, we don't have to modify these genil classes for create/read/update/delete (CRUD) operations. In this e.g we had a requirement to create ZFORECAST as a table view which will be embebded in Sales opportunity page as a new assignment block.This table will store multiple Forecast data aginast opportunity header guid.


To start with...

 

  1. Created new table :

1.png

 

  2. Create a lock object "EZFORECAST" for our table ZFOREACT. (Lock mode E)

2.png

 

3. Define the customizing entry for simple objects  in :

 

"spro->crm->cross appl. comp->genil->comp. spe. Setting->define SO"

3.png

 

4 . Create a SO entry in the object definition.

Object Name  - 'ZFORECAST'

Implementation
Class  - 'CL_CRM_GENIL_GEN_TABLE_OBJ'

 

 

Att str --  ' ZFORECAST'  "Define attribute
structure of your SO, usually it would be similar to the DB table which will
store data"

Structure of mand.Field at create: ' ZFORECAST' -

 

Key Struc. - GUID (key structure would be "GUID" only as the genil class CL_CRM_GENIL_GEN_TABLE_OBJ

4.png

Create  entry in the search object definition under
simple object...Search Object name -- 'ZFORECAST_SRC'

 

Structure
name - '
ZFORECAST' 

 

4.1.png

4.1 .  Add the SO2 object to our ONEORDER Component
4.2.png4.3.png

 

4.4.png

 

5. Create a mapping entry for our "Simple object - 'ZFORECAST', table name - 'ZFORECAST'  and lock object - 'EZFORECAST " in the table "CRMC_TBLOBJ_MAP"

5.png

6 . Execute Genil_bol_browser
6.png

Create new root object... select 'ZFORECAST_SRC'-->'ZFORECAST'

6.1.png

Check the new entries in Table 'ZFORECAST':

6.2.png

So up till now we created the SO object for ZFORECAST table which is now
part of BOL in SO2 component and in the below steps now in below steps we will create
Table View In Web UI


7. Let's start with creating new view "ZFOREC" under "BT111H_OPPT" bsp component in BSP Workbench.

 

7.png

 

Specify Context node name "ZFOREC" and Bol entity as the simple object created from the previous "ZFORECAST".Click
next till last step, in the last screen select view type as "Table view", tick the Configurable and Change/Display check boxes

7.1.png
After adding ZFOREC node also add BTADMINH which will be needed further to read the opportunity header Guid

8. Declare these Button attributes in the attribute section of the ZFOREC view's implementation class.
8.png

9. Modify the code of "ZFOREC.htm" BSP page code with the below code.

<%@page language="abap" %>
<%@extension name="htmlb" prefix="htmlb" %>
<%@extension name="xhtmlb" prefix="xhtmlb" %>
<%@extension name="crm_bsp_ic" prefix="crmic" %>
<%@extension name="bsp" prefix="bsp" %>
<%@extension name="chtmlb" prefix="chtmlb" %>
<%@extension name="thtmlb" prefix="thtmlb" %>

<%
data lv_xml type string.

lv_xml =  controller->configuration_descr->get_config_data( ).
%>

<thtmlb:areaFrameSetter toolbarButtons =
"<%= controller->tb_button %>"

                        maxButtonNumber =
"2"

                        displayMode  =
"<%= controller->view_group_context->is_view_in_display_mode( controller ) %>"/>

<chtmlb:tableExtension tableId =
"Table1"

                        layout  =
"FIXED">

<chtmlb:configTable
xml              = "<%= lv_xml %>"

                   
id                    = "Table1"

                    navigationMode        =
"BYPAGE"

                    onRowSelection        =
"select"

                   
table                = "//ZFOREC/Table"

                   
width                = "100%"

                    displayMode          =
"<%= controller->view_group_context->is_view_in_display_mode( controller ) %>"

                    headerVisible        =
"FALSE"

                    hasLeadSelection      =
"TRUE"

                    usage                =
"ASSIGNMENTBLOCK"

                    personalizable        =
"FALSE"

                    actions              =
"<%= controller->gt_button %>"

                    allRowsEditable      =
"TRUE"

                    actionsMaxInRow      =
"3"

                    downloadToExcel      =
"TRUE"

                    selectedRowIndex      =
"<%= ZFOREC->SELECTED_INDEX %>"

                    selectedRowIndexTable =
"<%= ZFOREC->SELECTION_TAB %>"

                    selectionMode        =
"<%= ZFOREC->SELECTION_MODE %>"

                    visibleFirstRow      =
"<%= ZFOREC->VISIBLE_FIRST_ROW_INDEX %>"

                    visibleRowCount      =
"6"

                    />



</chtmlb:tableExtension>


10.Click on Configuration tab of  "ZFOREC" view move required field to be shown on the right side under displayed field.

10.png

11. Define three buttons Create, Delete, Edit List in do_prepare_output method of the implementation class.

11.png
DATA:  ls_button    TYPE crmt_thtmlb_button.

 
DATA : lr_entity    TYPEREFTO cl_crm_bol_entity,

        lv_locked   
TYPE char1,

        lv_enabled 
TYPE crmt_boolean VALUE abap_true,

        lr_coll     
TYPEREFTO if_bol_bo_col,

        lv_coll_size
TYPE sytabix,

        lv_display 
TYPE boolean.


*me->view_group_context->set_view_editable( me ).



  lv_display = me->view_group_context->is_view_in_display_mode( me ).
*lv_display = me->view_group_context->set_view_editable.



 
IF lv_display EQ abap_true.

    typed_context->zforec->set_selection_mode( iv_selection_mode = cl_bsp_wd_context_node_tv=>selmode_none ).

 
ELSE.

    typed_context->zforec->set_selection_mode( iv_selection_mode = cl_bsp_wd_context_node_tv=>selmode_lineedit ).

 
ENDIF.



 
CALLMETHOD super->do_prepare_output.



  lr_coll =  me->typed_context->zforec->collection_wrapper->get_marked( ).

 
IF lr_coll ISBOUND.

    lv_coll_size = lr_coll->size( ).

   
IF lv_coll_size = 0.

      lv_enabled = abap_false.

   
ELSE.

      lv_enabled = abap_true.

   
ENDIF.

 
ENDIF.
************************



 
CLEAR : tb_button, ls_button.

  ls_button-
text    = 'Edit List'.

  ls_button-on_click =
'EDIT'.                              "#EC NOTEXT

  ls_button-enabled  = me->view_group_context->is_view_in_display_mode( me ).

 
APPEND ls_button TO tb_button.



 
CLEAR gt_button.

  ls_button-
text    = 'Insert'.

  ls_button-on_click =
'CREATE'.                            "#EC NOTEXT

  ls_button-enabled  = abap_true.

 
APPEND ls_button TO gt_button.

 
CLEAR ls_button.



  ls_button-
type    = cl_thtmlb_util=>gc_icon_delete.

  ls_button-on_click =
'DELETE'.                            "#EC NOTEXT

  ls_button-enabled  = abap_true.     
"lv_enabled.

 
APPEND ls_button TO gt_button.

 
CLEAR ls_button.


12.Create three event's "CREATE","DELETE","EDIT" in "ZFOREC" view via wizard & add the required logic in each event method as per below.
12.png

 

 

method EH_ONCREATE.

   
DATA :  lr_entity TYPEREFTO cl_crm_bol_entity,

          lv_collection
TYPEREFTO if_bol_bo_col,

          lr_comp
TYPEREFTO cl_bt111h_o_bspwdcomponen_impl.
*          cl_bt111h_o_bspwdcompone0_impl.



 
DATA : lr_core    TYPEREFTO cl_crm_bol_core,

        lr_fac     
TYPEREFTO cl_crm_bol_entity_factory,

        lt_params 
TYPE        crmt_name_value_pair_tab,

        ls_params 
TYPE        crmt_name_value_pair,

        lr_ent     
TYPEREFTO cl_crm_bol_entity,

        lv_objid
TYPE crmd_orderadm_h-object_id,

        lv_quot_id
TYPE crmd_orderadm_h-object_id,

        lv_pred_guid 
TYPE        crmt_object_guid,

        lv_cmp_guid 
TYPE        crmt_object_guid,

        ls_borident 
TYPE borident,

        it_neighbor_objects
TYPETABLEOF relroles,

        is_neighbor_objects
TYPE relroles,

        lt_zforecast
TYPETABLEOF zforecast,

        lv_new_month
TYPE zforecast-z_month,

        lv_cur_zvalue
TYPE zforecast-z_value,

        lv_new_zvalue
TYPE zforecast-z_value,

        lr_coco
TYPEREFTO zl_bt111h_o_bspwdcomponen_impl,

        lr_entity_so 
TYPEREFTO cl_crm_bol_entity,

        lr_entity_cur 
TYPEREFTO cl_crm_bol_entity,

        ls_zforecast
TYPE zforecast.



  lr_comp ?= me->comp_controller.

 
CHECK lr_comp ISBOUND.

  lr_entity ?= lr_comp->typed_context->btadminh->collection_wrapper->get_current( ).
*"Reading the btadminh entity, which is bound to component controller's
*  btadminh context node.

  me->typed_context->zforec->deselect_all( ).
*    me->typed_context->ZFOREC->select_all( ).



 
IF lr_entity->is_changeable( ) = abap_true.

    lr_core = cl_crm_bol_core=>get_instance( ).

    lr_fac = lr_core->get_entity_factory(
'ZFORECAST' ).

    lt_params = lr_fac->get_parameter_table( ).

   
TRY.

        lr_ent = lr_fac->create( lt_params ).

       
IF lr_ent ISBOUND.

         
CHECK lr_ent->lock( ) = abap_true.
*****************

         
CLEAR:lv_cmp_guid,lv_objid,

                lv_pred_guid,lv_quot_id.
* Read the parent entity BTADMINH guid
* " Copy BtAdminh Guid to Forecast table Opportunity Guid

          lr_entity->get_property_as_value(
EXPORTING iv_attr_name = 'GUID'

                                           
IMPORTING ev_result = lv_cmp_guid ).

          lr_ent->set_property( iv_attr_name =
'Z_HD_OPPT_GUID'

                                iv_value = lv_cmp_guid ).


** Objectid/ Doc no is genearted only after save , so may be blank while in create mode.

          lr_entity->get_property_as_value(
EXPORTING iv_attr_name = 'OBJECT_ID'

                                           
IMPORTING ev_result = lv_objid ).

          lr_ent->set_property( iv_attr_name =
'Z_HDR_OPPT_NO'

                                iv_value = lv_objid ).


******************  Default the values for quot  no *******************************
*PREDECESSOR_GUID Quot Guid when create follow up doc is selected

          lr_entity->get_property_as_value(
EXPORTING iv_attr_name = 'PREDECESSOR_GUID'

                                           
IMPORTING ev_result = lv_pred_guid ).

          lr_ent->set_property( iv_attr_name =
'Z_HDR_QUOT_GUID'

                                iv_value = lv_pred_guid ).


** SOMETIMES PREDECESSOR_GUID is empty then get it from transaction history

         
IF lv_pred_guid ISINITIAL.

            ls_borident-objkey  = lv_cmp_guid.

            ls_borident-objtype =
'BUS2000111'.

            ls_borident-logsys  =
''.



           
CALLFUNCTION'Z_DISPLAY_LIST_OF_RELATIONS'

             
EXPORTING

                object              = ls_borident

                max_hops            =
'99'

             
TABLES

                it_neighbor_objects = it_neighbor_objects

             
EXCEPTIONS

                no_logsys          =
1

                internal_error      =
2

               
OTHERS              = 3.

           
IF sy-subrc = 0.
* MESSAGE ID SY-MSGID TYPE SY-MSGTY NUMBER SY-MSGNO
*        WITH SY-MSGV1 SY-MSGV2 SY-MSGV3 SY-MSGV4.

             
LOOPAT it_neighbor_objects INTO is_neighbor_objects

               
WHERE roletype = 'VORGAENGER'"Preceding doc

               
AND objtype = 'BUS2000115'"quotes

                lv_pred_guid = is_neighbor_objects-objkey.

             
ENDLOOP.

           
ENDIF.



         
ENDIF.
*Get PREDECESSOR Quot no when create follow up doc is selected

         
IF lv_pred_guid ISNOTINITIAL.

           
SELECTSINGLE object_id INTO lv_quot_id

             
FROM crmd_orderadm_h WHERE guid = lv_pred_guid.
*          lr_entity->get_property_as_value( EXPORTING iv_attr_name = 'PREDECESSOR_GUID'
*                                            IMPORTING ev_result = lv_pred_guid ).

            lr_ent->set_property( iv_attr_name =
'Z_HDR_QUOT_NO'

                                  iv_value = lv_quot_id ).

         
ENDIF.
*****************************************************************************
*Read the last record from collection wrapper

          lr_coco ?= me->comp_controller.

         
IF lr_coco ISBOUND.

           
CLEAR: lv_new_month,lv_new_zvalue.
* Get the Last entity

            lr_entity_so ?= lr_coco->ztyped_context->zforecast->collection_wrapper->get_last( ).



           
IF lr_entity_so ISBOUND.
**Get prev month

              lr_entity_so->get_property_as_value(
EXPORTING iv_attr_name = 'Z_MONTH'

                                               
IMPORTING ev_result = lv_new_month ).
*** Add 1 month for new entry

             
IF lv_new_month+4(2) = '12'"if december that add new yr

                lv_new_month+
0(4) = lv_new_month+0(4) + 1 .

                lv_new_month+
4(2) = '1'.

                lv_new_month = lv_new_month.

             
ELSE.

                lv_new_month = lv_new_month +
1" add 1 month

             
ENDIF.



              lr_ent->set_property( iv_attr_name =
'Z_MONTH'

                                                iv_value =  lv_new_month ).
**********
**** Get the current zvalue
*              lr_entity_cur ?= lr_coco->ztyped_context->zforecast->collection_wrapper->get_current( ).
*              IF lr_entity_cur IS BOUND.
*                lr_entity_cur->get_property_as_value( EXPORTING iv_attr_name = 'Z_VALUE'
*                                                IMPORTING ev_result = lv_cur_zvalue ).
*              ENDIF.
***

              lr_entity_so->get_property_as_value(
EXPORTING iv_attr_name = 'Z_VALUE'

                                               
IMPORTING ev_result = lv_new_zvalue ).
**new value =  curr qty  * ????
*              lr_ent->set_property( iv_attr_name = 'Z_VALUE'
*                                                iv_value =  lv_new_zvalue ).



              lr_entity_so->get_property_as_value(
EXPORTING iv_attr_name = 'Z_CUR_QTY'

                                               
IMPORTING ev_result = ls_zforecast-z_cur_qty ).

              lr_ent->set_property( iv_attr_name =
'Z_PREV_QTY'

                                            iv_value =  ls_zforecast-z_cur_qty ).



              lr_entity_so ?= lr_coco->ztyped_context->zforecast->collection_wrapper->get_next( ).



           
ENDIF.

         
ENDIF.
*****************************





          me->typed_context->zforec->collection_wrapper->add( iv_entity = lr_ent ).

          me->typed_context->zforec->visible_first_row_index = me->typed_context->zforec->collection_wrapper->size( ).

       
ENDIF.

     
CATCH cx_crm_genil_model_error.

       
EXIT.

     
CATCH cx_sy_ref_is_initial.

   
ENDTRY.

 
ENDIF.


endmethod.




 

 

 

 

 

 

 

method EH_ONDELETE.

 
DATA :    lr_entity TYPEREFTO cl_crm_bol_entity,

          lr_current
TYPEREFTO if_bol_bo_property_access,

          lr_col
TYPEREFTO if_bol_bo_col,

          lr_core
TYPEREFTO cl_crm_bol_core,

          lv_size
TYPEi.



  lr_col ?= typed_context->ZFOREC->collection_wrapper->get_marked( ).

  lv_size = lr_col->size( ).

 
IF lv_size > 0.

   
DO lv_size TIMES.

     
IF sy-index = 1.

        lr_current = lr_col->get_first( ).

     
ELSE.

        lr_current = lr_col->get_next( ).

     
ENDIF.

      lr_entity ?= lr_current.

      typed_context->ZFOREC->collection_wrapper->remove( lr_current ).

      lr_entity->delete( ).

   
ENDDO.

    lr_core = cl_crm_bol_core=>get_instance( ).

    lr_core->modify( ).

    typed_context->ZFOREC->deselect_all( ).



 
ENDIF.
endmethod.








method EH_ONEDIT.

   
DATA:    lr_tx          TYPEREFTO if_bol_transaction_context,

          lr_entity     
TYPEREFTO cl_crm_bol_entity,

          lr_comp
typeREFTO CL_BT111H_O_BSPWDCOMPONEN_IMPL.
*          cl_bt111h_o_bspwdcompone0_impl.


*lr_entity ?= me->typed_context->ZFOREC->collection_wrapper->get_first( ).
*lr_entity ?= me->typed_context->ZFOREC->collection_wrapper->get_next( ).


*me->view_group_context->set_view_editable( me ).





  lr_comp ?= me->comp_controller.

 
Check lr_comp isBOUND.

  lr_entity ?= lr_comp->typed_context->btadminh->collection_wrapper->get_current( ).

 
CHECK lr_entity ISBOUND.

 
IF lr_entity->lock( ) = abap_true.

    me->view_group_context->set_view_editable( me ).

 
ENDIF.



  lr_entity ?= me->typed_context->ZFOREC->collection_wrapper->get_first( ).

 
WHILE lr_entity ISBOUND.

    lr_entity->lock( ).

    lr_entity ?= me->typed_context->ZFOREC->collection_wrapper->get_next( ).

 
ENDWHILE.
endmethod.

 

 

Create On_new_focus method in the "ZFOREC" context node, specify it as a event handler for collection wrapper new_focus event.
12.1.png

12.2.png
Create the  event parameter
12.3.png

 

13. Set handler in Connect nodes method of the CTXT class.

13.png
  method CONNECT_NODES.
DATA: coll_wrapper TYPEREFTO cl_bsp_wd_collection_wrapper,         

      comp_controller TYPEREFTO ZL_BT111H_O_BSPWDCOMPONEN_IMPL.

coll_wrapper = me->BTADMINH->get_collection_wrapper( ).

setHANDLER me->ZFOREC->on_new_focus for coll_wrapper ACTIVATION iv_activate.
endmethod.

 

14. As Save event is handled in different view (BT111H_OPPT/OpportunityOVViewSet), ZFOREC data has to be bind to custom/component controller to access it in other views.

14.png

15. Now create new Custom controller "ZFORECAST" with ZFORECAST and BTADMINH node and perform the binding as shown below using the binding wizard.

15.png

15.1.png

 

 

 

So with this binding now ZFORECAST can be called in any page view in this component

16 .Further now also add the ZFOREC node to BT111H_OPPT/OpptDetailsCuCo custom component as per  below, so that ZFOREC can be now displayed in the opportunity details page.

 

 

 

 

16.png

 

17 . Redefine eh_onsave event of the save method in  "BT111H_OPPT/OpportunityOVViewSet" component/view.

17.png
method EH_ONSAVE.
*CALL METHOD SUPER->EH_ONSAVE
**  EXPORTING
**    htmlb_event    =
**    htmlb_event_ex =
*    .
 
DATA: lv_items TYPEREFTO cl_bt111h_o_itemslistov_impl.

 
DATA: lv_success TYPE crmt_boolean.

 
DATA: lv_success_save TYPE crmt_boolean.

 
DATA:lr_ent              TYPEREFTO      cl_crm_bol_entity,

      lr_tx             
TYPEREFTO      if_bol_transaction_context.

 
data: lr_controller TYPEREFTO cl_bsp_wd_appl_controller,

        lr_msg_srv
TYPEREFTO cl_bsp_wd_message_service.

 
data:lr_tx_ctxt TYPEREFTO if_bol_transaction_context, "lr_tx

      lr_coll_wr
TYPEREFTO cl_bsp_wd_collection_wrapper,

      my_tx_context 
TYPEREFTO cl_crm_bol_custom_tx_ctxt, " Z cntx

      lr_entity_so 
TYPEREFTO cl_crm_bol_entity,

      rv_success   
TYPE        abap_bool,

      lr_tx_manager
TYPEREFTO if_crm_uiu_bt_channel_aspects,"cl_crm_uiu_bt_channel_asp_fac,
*    lr_tx_manager TYPE REF TO cl_mktprj_transaction_mgr.

      lr_coco
TYPEREFTO ZL_BT111H_O_BSPWDCOMPONEN_IMPL.
* cancel save because of mandatory fiels

  lr_controller ?= comp_controller->m_parent.

  lr_msg_srv ?= me->view_manager->get_message_service( ).

 
IF cl_crm_uiu_bt_tools=>mandatory_flds_no_save_allowed( ir_controller = lr_controller

                                                          ir_msg_srv    = lr_msg_srv ) = abap_true.

   
EXIT.

 
ENDIF.

 
IF cl_crm_uiu_bt_partner_popup=>get_quick_create_status( ) = abap_true OR gc_abc EQ abap_true.
*Save business partner-accounts and related objects (contacts, conditions,..)

    lv_success = cl_crm_uiu_bp_tools=>save( ).

    cl_crm_uiu_bt_partner_popup=>set_quick_create_status(
EXPORTING iv_quick_create = abap_false ).

    gc_abc = abap_false.

 
ELSE.

    lv_success = abap_true.

 
ENDIF.



 
CALLMETHOD cl_crm_uiu_bt_tools=>save( EXPORTING ir_node = me->typed_context->btorder ).

 
CLEAR:lr_tx_ctxt,my_tx_context.



 
CREATE OBJECT my_tx_context.
  lr_coco ?= me->comp_controller.

 
IF lr_coco ISBOUND.
* Get the First entity

    lr_entity_so ?= lr_coco->ztyped_context->ZFORECAST->collection_wrapper->get_first( ).
* Loop on the collection

   
WHILE lr_entity_so ISBOUND.

      lr_tx_ctxt = lr_entity_so->get_transaction( ).

      my_tx_context->add_tx_context( lr_tx_ctxt ).

      my_tx_context->if_bol_transaction_context~save( ).

      my_tx_context->if_bol_transaction_context~
commit( ).
*      * Get the next entity

      lr_entity_so ?= lr_coco->ztyped_context->ZFORECAST->collection_wrapper->get_next( ).

   
ENDWHILE.
 
ENDIF.
  rv_success = cl_crm_uiu_bp_tools=>save( ).


 
IF rv_success = abap_true AND me->view_group_context ISNOTINITIAL.
*    eh_oncancel( ).

 
ENDIF.
****************


* remove and add entity in order to refresh all context nodes (selections, etc.)
* and lock registration is done

  lr_ent ?= me->typed_context->btorder->collection_wrapper->get_current( ).

  me->typed_context->btorder->collection_wrapper->clear( ).

 
IF lr_ent ISBOUND.

    me->typed_context->btorder->collection_wrapper->add( iv_entity    = lr_ent

                                                        iv_set_focus = abap_true ).

 
ENDIF.


*  ** get current

  lr_entity_so ?= lr_coco->ztyped_context->ZFORECAST->collection_wrapper->get_current( ).

  me->ztyped_context->ZFOREC->collection_wrapper->clear( ).
endmethod.

 

Add the following code in on_new_focus method,here logic is to retrieve the order entities collection related to Order only if parent entity opportinity changes

18.png

 

18.Add the ZFOREC view to runtime repository..

 

18.png


19. Expose "ZFOREC" view on BT111H_OPPT/OpportunityOVViewSet Configuration
page, maintain view title and save...
19.png

20. That's it ...this is how it appears within WebUi page of opportunity with Create/Edit/Delete functionality.



 

Work area titles

Conditional formatting for Tables in WebUI

$
0
0

In comments to one of my blog posts I indicated that conditional formatting in tables was quite awaited feature for me. There are plenty of scenarios when we need to highlight or indicate or distinguish something in tables in WebUI. And previous tricks were quite difficult to implement.

Just take a look at these discussions:

How to set particular column or cell color of result view in CRM WEB UI ?

How to set background color of cell to red

 

I expected that standard implementation would finally take into account already existed P_STYLE and/or P_CLASS changing parameters from IF_HTMLB_TABLEVIEW_ITERATOR~RENDER_CELL_START method. But it does not. I've already described the issue with table iterator and those parameters in this message Re: How to set particular column or cell color of result view in CRM WEB UI ?

 

And here is the good news. As mentioned in SAP User Interface Technologies - Road Map (page 25) "Conditional formatting for Tables" is already available. Partially I think... Now it's much easier to format tables based on some conditions. But "formatting" here means only table cell coloring. I was really surprised because I (again) expected more flexibility in formatting like an option to set styles or CSS classes to cells or rows. Yes, setting background color covers most of the needs. But "coloring" is not "formatting".

 

To be completely sure that there is nothing else in shortly upcoming SPs I raised OSS message and received the oficial answer:

As per development there's no new development considered regarding "Conditional Formatting of the cellerator's cells" at this current time and there is no further information concerning the topic.

 

So how to do conditional formatting? The note 1937399 - Conditional formatting of the table cells introduces this feature. Need to admit that this feature is available starting from SAP CRM 7.0 Ehp1. How-To Guide is attached to the note. With this note we get one more property for table cell. Property name is

if_bsp_wd_model_setter_getter=>fp_bgcolor (or simply 'backgroundColor'). We need to return color name or RGB hexcode as a string (for example 'pink' or '#FFC0CB').

 

So some GET_P method should simply look like:

 

METHOD get_p_<your_cell>.  IF iv_property EQ if_bsp_wd_model_setter_getter=>fp_bgcolor  AND <your_condition>.    rv_value = 'pink'. " or '#FFC0CB'  ENDIF.
ENDMETHOD.

 

The result is:

Cells.png

 

Cool! But what about a row? Certainly we can go and define as many GET_P methods to return this property as many attributes we have. And implement new GET_P method for each and every new attribute we add in the future.

 

Here I would suggest another way.

  • Define static attribute in your context node class which represents table of pairs 'index; color' for buffering.
  • Redefine GET_P_T_TABLE method of your context node class (inherited from CL_BSP_WD_CONTEXT_NODE_TV class).
  • Handle the backgroundColor property in this method passing all other properties to super-method.
  • For each new index trigger your condition and store the result in the index-color table.
  • Retrieve the color from the table if possible.

 

Here is a code example:

 

METHOD get_p_t_table.  DATA: lr_current TYPE REF TO if_bol_bo_property_access.  FIELD-SYMBOLS: <fs_color> TYPE zts_color.  IF iv_property = if_bsp_wd_model_setter_getter=>fp_bgcolor.    READ TABLE gt_colors ASSIGNING <fs_color> WITH KEY index = iv_index.    IF <fs_color> IS NOT ASSIGNED.      APPEND INITIAL LINE TO gt_colors ASSIGNING <fs_color>.      <fs_color>-index = iv_index.      lr_current = me->get_bo_by_index(          iv_index        = iv_index          iv_change_focus = abap_false ).      IF lr_current IS BOUND.
* There are some logic and conditions
* Here it's based on current line entity in zcl_util_misc=>get_color as an example        <fs_color>-color = zcl_util_misc=>get_color( lr_current ).      ENDIF.    ENDIF.    rv_value = <fs_color>-color.  ELSE.    CALL METHOD super->get_p_t_table      EXPORTING        component       = component        iv_index        = iv_index        iv_property     = iv_property        iv_display_mode = iv_display_mode      RECEIVING        rv_value        = rv_value.  ENDIF.
ENDMETHOD.

 

Where types and attribute are defined as:

PRIVATE SECTION.  TYPES:    BEGIN OF zts_color,          index TYPE i,          color TYPE string,         END OF zts_color .  TYPES:    ztt_color TYPE TABLE OF zts_color .  DATA gt_colors TYPE ztt_color .

 

Also we have to clean up gt_colors somewhere after completing the output. Because otherwise there will be issues during next rendering. For example when sorting is applied to this UI table. I used method CLEAR_LAST_LINE in context node class which is also inherited from CL_BSP_WD_CONTEXT_NODE_TV. It's called from handler of CL_BSP_WD_VIEW_CONTROLLER~OUTPUT_RENDERED event and satisfied my needs.

 

And here is the result:

Rows.png

 

Sure enough, it's easier and much better than previous approaches. But still there is a lack of flexibility... Anyway it should cover most of requirements. At least I hope so.


How to call Web Client UI Components via URL

$
0
0

Hello everyone,

 

I published a WIKI page which explains how to start the Web Client UI with a particular component and optional data. For example how a URL must be entered to display the overview page of a particular contact person in the workarea of the WebClient UI. Customers can benefit of this feature when it comes to using non SAP Portals where they want to have links that directly launch specific components.

How to navigate inside a UI component

$
0
0

Dear colleagues,

 

I just published a WIKI page explaining the navigation inside a UI component. A navigation changes the current view assembly. It always leaves the current view (via an outbound plug) and goes to a different view (via an inbound plug). The possible navigations (i.e. connections from outbound plugs to inbound plugs) are part of the runtime repository of the current UI component. It is also possible to go beyond the boundaries of the current UI component. Then the navigation either leads into an embedded UI component (outside in) or it leaves to the embedding UI component (inside out). In this case it is necessary to define plugs as part of the UI component. Happy reading.

 

Best Regards,

 

Hasan

How to use Dynamic Cross Component Navigation

$
0
0

Dear colleagues,

 

I just published a WIKI page that explains different kinds of cross component navigation and creation of descriptor objects.

 

Happy reading

 

Hasan

How to Implement a Central Edit Button on an Overview Page

BPath. Lesson 10. Aggregations

$
0
0
Table of Contents ...... Table of Code Examples
<< Lesson 09. Groupingsupcoming.... >>

 

Hello everybody,

 

as indicated in the last lesson groupings and aggregations somehow belong together. So I speeded a bit up with this lesson, which is a available a bit earlier. Content will be aggregations solely, with just one small exception. Nevertheless it will be an extensive lesson.

 

First and Last and Always...Aggregations. Theory.

 

Aggregations are aggregating a number of datasets to one value. The most prominent representants here are the COUNT, SUM and the AVERAGE function. But one should not forget "easy" aggregations like MIN, MAX, FIRST or LAST. On the other side there are also a couple of more complex aggregations as SEARCH, GEOAVG and STDEV. The full list can be obtained in the table at the end of this blog post.

 

Some general remarks to the usage of aggregations:

  • All aggregations refer to a group, technically they can only be used in the Loop-Block of a group definition. Attempts to use it in other locations will cause a dump.
  • Aggregations on a full dataset (not on a group) always have to be done via $(*::....)-approach. Either in the main-query or in a sub query.
  • The initialization part is automatically done by the aggregation. Unlike the examples of the hand-made aggregations, the Initblock of the group definition will typically remain empty.
  • Aggregations can be built upon every valid BPATH expression.
  • Any aggregation which is at the intermediate state of processing row i ( smaller then the total number of rows in that group) will have the value as if i would be the last row in that group. This has the consequence that nested aggregations may return different results then anticipated. E.g. the inner statement in COUNT(@AMOUNT=MAX(@AMOUNT)), is true for all rows, which have no bigger AMOUNT in a row smaller then its row. In case you really want to count the rows which are equal to the global maximum, the used approach is not sufficient. In this case a hand-made solution has to be taken (For the sake of completeness: $(MyGroup:!t=0;!a=0:!t=iff(@AMOUNT>!a,1,!t+iff(@AMOUNT=!a,1,0);!a=iff(@AMOUNT>!t,@AMOUNT,!a))

 

Aggregations. Practice

 

Basic aggregations: Count, Sum() & Product()

 

~*[!CLASS]/SearchResFlightRel/FlightBookRel$(@CLASS::!COUNT=Count();!SUM=Sum(@LOCCURAM))
Code Example 43,$(@GROUP::!X=Count();!Y=Sum(@FELD)), Count() and Sum()

 

Well, that's easy now, isn't it? Slightly easier compared to the hand-made approach.

 

The code explains itself. Hopefully. Count can be enhanced with a condition, so only rows fullfilling the condition are counted. As with the sum() function the same can be achieved using a sum(iff(condition,@Feld,0)) construction.

 

Product() works similar to Sum() but muliplies all available parameters. Might be useful for probabilities. Since the result may be a fracture, possibility to round result is provided.

 

Advanced Aggregations: Conc, Avg() / Geoavg() and Stddev()

 

~*[!CLASS_LONG,!FORCURKEY]/SearchResFlightRel/FlightBookRel$(@@CLASS,@FORCURKEY::!Average=AVG(@LOCCURAM,2);!PASSENGERLIST=CONC(@PASSNAME,",",@PASSNAME<>""))
Code Example 44,$(@@GROUP::!X=Avg(@Feld,Prec);!Y=Conc(@FELD,Sep,Cond)), Avg() and Conc()

 

The Average function without second parameter returns the value with 34 digits. This is probably more exact than you need, so it is possible to add the number of decimal places (supported by all 3 "advanced aggregations" which work on Numbers).

 

Conc() (please distinguish with the concatenate function, which is not an aggregation function) concatenates the handed over (with parameter 1) value separated by parameter 2. As third parameter it is possible to specify a condition. In this case it is used to sort out non-filled entries. In contrast to the LIST() function, this is not done automatically.

 

Please note that the Group condition is a long text field here, consequently the sort condition has to reflect this also.

 

Geoavg() calculates the geometrical average (nth root on the product of all elements). Calculation is done using logarithm. All numbers in the calculations must be positive.

 

Stddev() calculates the standard deviation.

Minima. Maxima.

 

~*/SearchResFlightRel/FlightBookRel$(@@CLASS,@FORCURKEY::!R=Min(@LOCCURAM,concatenate(@PASSNAME," paid ",""+@FORCURAM," ",@FORCURKEY)))
Code Example 45, $(@GROUP::!X=Min(@Feld,Returnvalue)), Min() and Max()

 

The example shows the usage of the Min/Max method. The result is assembled based on the first Min/Max row found with the second parameter. If no second parameter is provided, the min/max value is returned directly.

 

Example 46 shows, that aggregations are not necesarilly basing on attributes directly. The example calculates the maximum paid for a kilogram of luggage, with the side condition that the weight should be bigger than zero. Using the iff() function is a standard way to deal with these side condition, but please note that in this case it is actually not needed since the division is anyway defined in a way that 0 is returned if it is attempted to divide by zero. This is done to avoid dumps at run time which can not be detected at design time.

 

~*/SearchResFlightRel/FlightBookRel$(*::!R=Max(iff(@LUGGWEIGHT>0,@LOCCURAM%@LUGGWEIGHT,0)))
Code Example 46, $(*::!X=Max(function(@Feld1,@Feld2))), Max based on complex expression

 

First(), Last(), GrpIndex(), Any()

 

Some "easy" aggregations. First() returns the value corresponding to the first row, the aggregation may be equiped with a condition . Last() basically does ... nothing. It is just included for the sake of completeness, you will have the same effect if you state the parameter directly.

 

With GrpIndex() you might access the nth row (n is the first parameter, second parameter specifies the value to be returned). In contrast to direct indexing, n has to be positive here.

 

Any() uses the randomizer library to choose an arbitrary row from which the return is assembled.

Search(), SearchFirst()

 

SearchFirst() and Search() are searching for specific rows fullfilling the specified search condition. SearchFirst chooses the first found entry, Search all - or if no self linking is used, in consequence the last row.

 

~*/SearchResFlightRel/FlightBookRel$(*::!R=SearchFirst(@LUGGWEIGHT>30,@Passname))
Code Example 47, $(*::!X=SearchFirst(@F1,@F2)), SearchFirst

 

The following example shows the teamwork of the Search and the list function. The search function searches all bookings with "overweight" (considered to be above 24) and adds the name of the passenger to the list of overweighted passengers:

 

~*/SearchResFlightRel/FlightBookRel$(@@CLASS::!OVERWGHT=Search(@LUGGWEIGHT>24,LIST(!OVERWGHT,",",@PASSNAME)))
Code Example 48, $(*::!X=Search(Cond,func(!X))), Searches returning lists

Median()

 

Median() sorts the attributes and chooses the one in the middle (respectively one of the middle pair). Median is supported for Numeric Values, Strings and Dates. Median() has no optional rounding parameter.

 

Even though it sounds similar to the average, technically it is a bit more costly. For all other aggregations it is sufficient to keep one value per group, for the Median the complete list has to be kept. Performance seems to be harmed only by a smaller factor as the sort_in features of ABAP tables are used.

 

A Small leftover: ToRoot-Relation ("...")

 

~*/SearchResFlightRel/FlightBookRel/...$/*
Code Example 49,..., to root

 

Additional to the relation to the object itself ("."), the relation to the parent (".."), the relation to the root object is now available using the ... syntax. In contrast to the parent, the root is unambigous.

 

Aggregation Table


The letters refer to the 4 possible datatypes ( String, Date, Boolean, Number ). The GRPINDEX function for example has a first parameter of type Number follwed by a second parameter of any type. Conc has either two parameters of type String, or two string parameters followed by a Boolean parameter as third parameter.
Name
Main Parameters
Optional Parmeters
Description
COUNT
no
B
counts all elements where the condition evals to true
SUM
N
no
sums up handed over value
PRODUCT
N
N
multiplizes handed over entries. Optional parameter provided possibility to round the return
MIN
SDNB
SDNB
returns second parameter for the first found global minimum. If second parameter is not supplied first parameter will be returned
MAX
SDNB
SDNB
maximum, equivalent to minimum
AVG
DN
N
returns arithmetical average. Optional parameter provided possibility to round the return (not in case of date values), e.g. 0 rounds to whole digits, 2 to 2 decimal places.
GEOAVG
DN
N
returns geometrical average. Optional parameter see AVG.
FIRST
SDNB
no
returns entry from first row
LAST
SDNB
no
returns entry from last row (equivalent to the usage of the parameter directly)
GRPINDEX
N, SDNB
no
returns entry from row specified with parameter 1. Negative indexing is not possible here, since the size of the group is not known.
ANY
SDNB
no
returns one of the rows chosen by equal probability
STDEV
N
N
returns standard deviation. Optional parameter provided possibility to round the return.
CONC
S, S
B
concatenates first parameter separated by second parameter. Optionally only rows which fullfill the condition specified as third parameter are taken into account. If all rows evaluate to false, an empty string will be returned.
SEARCH
B
SNDB
returns parameter 2 belonging to row where condition is met. This means typically the last row fulfilling the condition, or, in case parameter 2 includes its own target an hand-made aggregation of all found rows (see examples)
SEARCHFIRST
B
SNDB
returns parameter 2 belonging to first row where condition is met.
MEDIAN
SND
no
returns median element from the list of attributes

 

Lesson 11 will deal with enhancements done mainly on the sub-function.

A example about how to analyze SYSTEM_NO_ROLL error in Webclient UI

$
0
0


In my previous blog Dropdown list issue in CRM Webclient UI - a very funny trouble shooting processI have shared with you how I deal with an drop-down list issue and how I finally find root cause.


In this blog I will share with you another example how I struggle with another issue which is drop down list relevant and how I decide which place in the code to set breakpoint to start my trouble shooting.


Issue description

 

Today I received one incident from customer, complaining that they could not successfully create an ERP order. They meet with out of memory issue in UI. How could a creation of ERP order ask for more than 700MB memory???

 

clipboard1.png

When I check in ST22, there are lots of SYSTEM_NO_ROLL errors.

clipboard2.png

Checking Active Calls/Events, I have only two findings:

 

1. according to callstack 62, this error occurs when UI framework tries to populate native html source code to be rendered for the end user.

2. callstack 51 shows the overview page is to be rendered. According to the reproduce steps provided by customer, to be more exact, the issue occurs when ERP order overview page is to be rendered.

 

clipboard3.png

Click tab "Source Code Extract", and I know the system fails to allocate necessary memory for this CONCATENATE operation.

I can not imagine how big the m_content or value could be.

 

clipboard4.png

The information in "Chosen variables" could not help me too much:

clipboard5.png

I realize I have to use debugging once again. But where do I set breakpoint?

 

My issue analysis process

 

Since all callstacks displayed in ST22 are webclient ui framework stuff, if I set breakpoints there, they will no doubt be triggered again and again, do nothing helpful for my analysis.

 

I just set two breakpoints in DO_PREPARE_OUTPUT of ERP Order header view and item view.

clipboard6.png

clipboard7.png

Both methods are executed successfully in debugger, nothing strange.

 

What kinds of UI element would lead to a large response stream to browser

 

Since this issue is caused by the huge memory allocation for the to-be-rendered html page, we can think about what kinds of UI element would lead to a huge response stream?

 

As far as I know, there are following candidates:

 

1. A long long web page with lots of text content.

 

When talking about webclient ui, the possible scenario is that a text-area has lots of characters. However this is not the case for this incident.

In ERP creation page it is impossible to have a long text which needs 700MB to display.

 

2. A table with so many entries.

 

In ERP_IT/ItemTab there is indeed a table element, however since currently we are in ERP order creation page, there is no item record at the creation phase.

 

3. A drop down list with so many entries.

 

If you have gone through my previous blogyou can know that the keys and descrptions for each entry in a drop down list will be included in the html source code. So in theory, if a drop down list has a hug number of entries, it is also possible to generate a large response stream.

 

So now all context node attributes which has implemented method GET_V_XXX are suspects. So I have set breakpoint on all of them.

clipboard8.png

And soon I have found the one who makes troubles, the GET_V_SPART. Totally 408089 entries are returned by method cl_crm_uiu_erp_order_vh=>get_values_for_spart.

clipboard9.png

When this big drop down list is finally being rendered, the handler class CL_THTMLB_SELECTION_BOX has to populate the final html source code for these 408090 entries, and run out of memory.

clipboard10.png

Since root cause is found, I asked responsible colleague to have a look why such huge numbers of entries are returned, as this absolutely does not make sense for end users.

 

Hope this tip of mine to choose where to set breakpoint could help for your trouble shooting. Of course in this case memory inspector s_memory_inspector could also help you identify code which generates high memory demand.

Using Word-Integration within Web UI Client

$
0
0

What's this all about?

This blog is about my experience in using the Microsoft Word Integration within the Web UI Client Framework. Primarily I use it for own documentation purposes thus I do not want to maintain lots of Word documents on my own storage any more.

In short there are three steps you have to take when using Word templates:

- create the template (Web UI application)

- create data provisioning (by implementing the later mentioned enhancement spot)

- add the function to call your Word template

 

The target

The business case is to print individual text-heavy letters with less dynamic Content (documents with a lot of dynamic Content (e.g. tables) should be implemented using Adobe Interactive Forms By Adobe).

 

 

Development Environment

You can start the development Environment by using the UI Component Workbench and testing the UI component  CRM_OI_TEMPLDSG.

bsp_wd_cmpwb.PNG

language to English. But you can see this as an exercise to improve your German skills (but you do not receive an certificate :-(). The following screen appears:

dtd_01.PNG

type (Objekttyp). This is the classic object type introduced a long time ago with the SAP Business Workflow environment (transaction code SWO1) type delivers the data needed for our Word template. Beside the other self explananing select-options it's important to know that you can administrate different types of templates:

  • Microsoft Office Word ("new" XML based documents, e.g. docx)
  • Microsoft Word (old doc Format)
  • Adobe XML form

 

The object-type is the base for processing the Microsoft Word templates as the coding of the SAP Standard processing class CL_CRM_OI_T_TEMPLATES_IMPL references this within the method OPEN_DOCUMENT().

 

You receive a list of all templates available by applying the selection-criteria:

result_list.png

There you can find the functionality to create, edit, copy and delete templates.

 

Creating a new Word template

Just press the "New"-Button and the following Screen appears:

new_template_01.PNG

After filling the gaps it's time to design the layout, this is the next step to proceed. So I thought using the button "start designer" (Designer starten) is the way to do so. But if you press this button Microsoft Word is being opened in an read-only mode - so you cannot modify the document (maybe another feature). You can find this in the following screenshot with the German word "Schreibgeschützt" (this means read-only):

word_read_only.PNG

So this is not the way we can edit our new template. Close the Microsoft Word application and you will see the "New" View again. Have a look at the radiobutton "use macros" (Makros verwenden). It seems to be a "feature" that the option to edit the template only is available when "use macros" is set to active.

 

Now the option for downloading (Button called "Herunterladen") the template appears so we can edit this document locally on our client by using Microsoft Word:

 

new_template_02.PNG

Using merge-fields

 

Adding dynamic fields to the Word template is very easy. During editing your document just use the "Add Field" function and choose "merge field". Please consider appropiate naming conventions in order to avoid confusing implementations with an outlook on the maintainance in the future.

All these merge-fields have to be accessed and filled within your BadI implementation (method GET_VALUES()).

merge_field_1.PNG

 

After you have finished editing the document just use the "upload functionality" (here called "Hochladen", in the screenshot before).

 

 

Data provisioning

Now we have a closer look at the data provisioning layer. Therefore it's essential to know the Enhancement-Spot CRM_OFFICE_INT_ENHANCEMENT. This BadI is called before the document is being opened. Thus we need an implementation of this EH-Spot.

 

The relevant Interface is called IF_EX_CRM_OFFICE_TEMP_BADI. It provides three methods:

Method nameDescription
GET_ATTRIBUTESDefinition of all available field-symbols (placeholders, fields) for use within the Word templates
GET_VALUES

Provide data to the fields

  • Parameter CT_VALUES
    • the most important parameter: you have to fill this key-value-pair with the IDs of your placeholders defined within in your template and fill the values which replace the placeholders
GET_DESC_NAMESnot used, yet

 

Go to transaction SE18 and open the mentioned enhancement spot and create a new enhancement implementation:

 

se18_01.png

You have to define the implementation's name and assign a composite enhancement-implementation if desired. At least you have to define the class' name. It might be a good approach to copy the pre-delivered sample class:

se18_02.PNG

After creating a new implementation class you first have to focus on the Method GET_ATTRIBUTES().field" in your template.

 

An implementation could look like this for the example above (merge-field names):

APPEND INITIAL LINE TO ct_attributes[] ASSIGNING <lf_attribute>.<lf_attribute>-name = |FIRST_NAME|.<lf_attribute>-description = space.
UNASSIGN <lf_attribute>.
APPEND INITIAL LINE TO ct_attributes[] ASSIGNING <lf_attribute>.<lf_attribute>-Name = |LAST_NAME|.<lf_attribute>-description = space.

 

The next step is to supply the attributes with values. This happens within the method GET_VALUES() which could look like this:

LOOP AT ct_values[] ASSIGNING <lf_value>. "CT_VALUES will be supplied with all the attributes added to ct_attributes[]
CASE <lf_value>-name.
WHEN 'FIRST_NAME'.<lf_value>-value = lv_first_name. "imagine lv_first_name contains the matching attribute from BOL-model
WHEN 'LAST_NAME'.
...
ENDCASE.
ENDLOOP.

It's recommended to implement the data provisioning logic (access to BOL-model) in a separate class in order to not overload the BadI implementation and be more flexible in the future. Maybe you've got many documents so it's better to practice "separation of concern".

 

 

Transport Management


Please keep in mind that Word templates are not connected to transport management automatically. For this you can use the report CRM_KW_TEMPLATE_TRANSPORT or use the SPRO -> CRM -> Basic functions -> Content mgmt. -> Transport template


Data Retrieval Parameters in SAP CRM Accounts

$
0
0

You can use configurable data retrieval parameter to limit the data retrieved in assignment blocks, based on selection parameters defined in the UI configuration tool.


Below is the List of Assignment Blocks of Accounts for which Configurable data retrieval is supported


Assignment blocks:

  • Interaction History
  • Planned Activities
  • Opportunities
  • Leads
  • Sales Orders
  • Sales Agreements
  • Quotations
  • Service Arrangements


Configuration: I will configure data retrieval configuration for Interaction History Assignment block to show only the Business activity with status completed.

 

T-Code: BSP_WD_CMPWB

 

Component= BP_BPBT and Click Display

 

1.png

 

Click BP_BPBT/AccountInterHistOV under View  and click tab Configuration

 

2.png

 

Click Edit button and add the Data Retrieval Parameters

Status= Completed and Transaction Category = Business Activity

3.png

 

Features

  • For assignment blocks that have been set up for configurable data retrieval, you can define which data records are selected and displayed by defining selection parameters as part of the user interface (UI) configuration. As a result, only specific data is displayed in the assignment block.
  • The system performance can be improved because of a reduced amount of data retrieved from the database. Whether the system performance is improved or not depends on how the configurable data retrieval has been implemented for a certain assignment block.
  • You can change the selection parameters at any time, altering the data selection presented to the business user.
  • You can set selection parameters for each view usage (business role), because the selection parameters are specified as part of the view configuration.
  • Report Views with Configurable Data Retrieval option  (BSP_DLC_CONF_DAT_RETRIEV_USAGE) provides you with a list of all views that support configurable data retrieval.

Dynamic navigation with simple steps

$
0
0

Hi All,

 

 

I saw a couple of posts related to dynamic navigation already. However, I felt of writing this since there are still many questions arise on the same.

 

Let us try a simple dynamic navigation where I will be taken to an opportunity details when I click on the number/name(In my case I give number) of the opportunity.

 

Step 1: Create a custom field in the opportunity header by AET with name Reference oppt. Here, user is going to give a reference opportunity ID. It's an independent reference opportunity ID and not created as a followup.

 

GET_P of the field:

 

CASE iv_property.
     WHEN if_bsp_wd_model_setter_getter=>fp_fieldtype.
       IF iv_display_mode EQ abap_true.
         rv_value = cl_bsp_dlc_view_descriptor=>field_type_event_link.
       ELSE.
         rv_value = cl_bsp_dlc_view_descriptor=>field_type_input.
       ENDIF.
     WHEN if_bsp_wd_model_setter_getter=>fp_onclick.
       rv_value = 'toref'.
   ENDCASE.

 

Step 2: Now the field is available in the opportunity as a hyperlink when in display mode.

Opport.PNG

 

Step 3: Next step is to create an event handler for this. Create an event called 'TOREF'.

 

1.PNG

 

Step 4: Write following code in event handler.

 

DATA: lr_entity      TYPE REF TO cl_crm_bol_entity,
           lr_col         TYPE REF TO cl_crm_bol_bo_col,
           ls_attributes  TYPE crmt_bsp_opportunity_bp ,
           lv_object_guid TYPE crmt_genil_object_guid,
           lr_core        TYPE REF TO cl_crm_bol_core,
           lr_entity_bt   TYPE REF TO cl_crm_bol_entity,
           lr_nav_serv    TYPE REF TO if_crm_ui_navigation_service,
           lr_nav_descr   TYPE REF TO if_bol_bo_property_access,
           lv_opportid    TYPE CRMT_DOC_FLOW_ID_WRK.

     CONSTANTS:
         lc_ui_action   TYPE crmt_ui_actions VALUE 'B'. "Display

     lr_entity ?= me->typed_context->btopporth->collection_wrapper->get_current( ).

     CHECK lr_entity IS BOUND.
     lr_entity->get_property_as_value( EXPORTING iv_attr_name = 'Z_FIELD'
                                       IMPORTING ev_result    lv_opportid ).

     lr_core = cl_crm_bol_core=>get_instance( ).

* Get the corresponding GUID of the ID with your code and assign to lv_object_guid.

     lr_entity_bt = lr_core->get_root_entity( iv_object_name = 'BTOrder'
                                              iv_object_guid lv_object_guid ).
     IF lr_entity_bt IS BOUND.
       CREATE OBJECT lr_col.
       lr_col->if_bol_bo_col~add( lr_entity_bt ).
     ENDIF.

* Depends on the view, give the right object id.

     cl_crm_ui_descriptor_obj_srv=>create_ui_object_based(
              EXPORTING iv_component        = 'BT111H_OPPT'
                        iv_ui_object_type   = 'BT111_OPPT'
                        iv_ui_object_action = 'B'               "Display mode.
              RECEIVING rr_result           = lr_nav_descr ).

     lr_nav_serv = cl_crm_ui_navigation_service=>get_instance( ).

* check if navigation is possible
     IF lr_nav_serv->is_dynamic_nav_supported( ir_descriptor_object = lr_nav_descr ) = abap_true.
       lr_col->if_bol_bo_col~insert( iv_bo    = lr_nav_descr
                                       iv_index = 1 ).
*   start the navigation
       lr_nav_serv->navigate_dynamically( iv_data_collection = lr_col ).
     ENDIF.


Step 5: By this much, the main part is done. Only thing left is to maintain the navigation bar profile values in SPRO.

Go to tcode:CRMS_UI_TLINK to get the target ID. Take the opportunity details and get the right target ID.

 

2.PNG

In our case we are taking the display. Hence choose TBT111MOV.

 

Step 6: Maintain SPRO values.

Take define navigation bar profile.

3.PNG

Take the navigation bar profile of you and maintain the values.

 

4.PNG

5.PNG

 

You are done with all the steps now. Go to your opportunity and see it's navigating to the correpsonding opportunity. Please let me know if in case of any doubts.

 

Thanks,

Faisal

CRM Rapid Applications - An Advanced Tutorial (Part 1)

$
0
0

Introduction

Recently I started to play around with the CRM Rapid Applications (Rapid Applications - SAP Library). I wanted to use it to create a little application with some basic functionality. The test application should prefill some data by e.g. using some number range object. Furthermore, it should perform some data validation. As I had attended sitMUC (SAP Inside Track Munich 2014 - Community Events - SCN Wiki) just a few weeks ago where Daniel Ridder and Tobias Trapp gave a presentation on BRFplus (SAP Business Rules Management) I wanted to implement the validations using BRFplus rules.

As with most of the interesting SAP technologies there are basic tutorials available for both, CRM Rapid Applications (e.g. SAP CRM 7.0 EhP1 - Rapid Applications (Part 1) and SAP CRM 7.0 EhP1 - Rapid Applications (Part 2) by Tzanko Stefanov) and BRFplus. However, more advanced tutorials (like e.g. AXT Rapid Applications Currency/Quantity fields by Luis Pérez Grau) are hard to find.

Therefore, I took me quite a while to implement my sample application. I only succeeded by asking questions in the BRFplus forum and by extensive debugging in the CRM Rapid Application framework. Consequently, I decided to collect the knowledge gained in the process in this mini blog series in order to provide an advanced tutorial for CRM Rapid Applications as well as BRFplus. The first part of the blog series is focused on the Rapid Application part and the integration of BRFplus, the second part (CRM Rapid Applications - An Advanced Tutorial (Part 2)) will provide details on the design of the the BRFplus rules.

 

The Application Design

The applications shown in the existing rapid application tutorials are based on existing database tables. In contrast to that I will show how to create an application from scratch including the data base tables using only the rapid application functionality. For the sample application I defined the following requirements which are quite common in business applications. The application should be able to store:

  1. Header data and corresponding attributes
  2. The header should consist of an ID, a reference to a business parter, a creation and change date and user
  3. The attributes should be simple key value pairs.

In addition to that

  • It should be possible to add but not to delete records
  • The ID should be taken from a number range object
  • The creation date and user and change date and user should be set automatically by the system
  • The validity of the attributes should be check according to the following rules
    1. "Attribute 1" is mandatory, "Attribute 2" is optional.
    2. Allowed values for "Attribute 1" are "A", "B" and "C",
    3. Depending on the value of "Attribute 1" the following values are allowed for "Attribute 2"
      Attribute 1Attribute 2
      A1 - 99
      B100 - 299
      C300 - 999

According to these requirements the following table show valid records for the sample application:

 

IDBusiness PartnerCreated AtCreated ByChanged AtChanged By
0000000001100009001.11.2014DRUMM2.11.2014DRUMM
0000000002140001233.11.2014DRUMM3.11.2014DRUMM

 

Header IDAttributeValue
0000000001Attribute1A
0000000002Attribute2B
0000000002Attribute2125

 

These requirements seemed simple enough for an introductory  tutorial when I defined them in the first place. However, it turned out, that there are quite some difficulties hidden in them, both for CRM Rapid Applications and the BRFplus implementation.

 

Creating the Rapid Application

Rapid Applications are created using a wizard provided by the Rapid Application framework. The wizard is started by creating a new Rapid Application. As the example application for this blog should be based on database table select "Create from DB Table" to create the new application.

2014-11-04 16_42_21-Manage Rapid Applications - [SAP] - Internet Explorer bereitgestellt von Ihrer r.png

The Rapid Application wizard consists of the following steps:

1.    Defining name and package for the application

2.    Defining the database model

3.    Defining the UI model

4.    Optionally assigning the application to some UI profiles.

During each steps it is possible to generate and test the application. For this blog I created the application ZCD_RAPIDAPP_EX in the $TMP package.

2014-11-04 16_43_59-Manage Rapid Applications - [SAP] - Internet Explorer bereitgestellt von Ihrer r.png

Defining the Database Model

As mentioned above the goal of this blog is to show how to create a Rapid Application from scratch. Therefore, the next step is to define the database model. First the table to store the header data needs to be created. In order to create a new database table click on the "New" in the "DB Model" step of the wizard. This creates a new database table with an empty field list. Initially the new database table has a auto-generated name (ZCTAB000000N in the screen shot below). It is possible to change this name by clicking on "Details" and providing a custom name for the table (ZCD_RAE_HEADER in the screen shot below).

2014-11-04 16_47_41-Manage Rapid Applications - [SAP] - Internet Explorer bereitgestellt von Ihrer r.png

Although the field list initially appears to be empty the Rapid Application framework automatically generates some "technical" database fields. These field are CLIENT, RECORD_ID, PARENT_ID and OBJECT_ID, with CLIENT and RECORD_ID being the key fields of the database. The auto-generated fields are used by the framework to e.g. link entries in different tables.

The next step is to define the table fields required by the application. The following screen shot shows the table fields I generated for the table ZCD_RAE_HEADER. Note that the field ZZ_OBJECT_ID is defined as a logical key. The definition of a logical key is optional. However, in order to being able to define dependant tables a logical key is required in the superordinate table. Furthermore note, that some of the fields (e.g. ZZ_PARTNER or ZZ_CREATED_BY) have the field type "Application Reference". Fields of type Application Reference are used to store references to other business objects like e.g. account or employee. Application references are used during the execution of the application to provide links in the UI to the respective objects. Finally, the fields available as search criteria need to be defined. This is done in the details view for each field.

2014-11-04 16_54_47-.png

To complete the database model for the application I created the dependent table ZCD_RAE_ATTRIBS to store the attributes. The following screen shot shows the details of this table. Note, that for the field ZZ_ATTRIBUTE I created a list of possible values. The values will be rendered as a drop down list when the application is executed.

2014-11-04 21_13_40-Field Details -- Webseitendialog.png

Defining the UI Model

The next step is to define the UI model. The definition of the UI model consists of two steps, generating the views and adjusting the UI customizing. The first step is to automatically generate the UI model. This will create a search page, an overview page and a embedded search page. After the views have been generated the next step is to adjust the view. The only necessary adjustment for the example application is to remove the delete function from the overview page (cf. the following screen shot).

2014-11-04 21_26_49-Manage Rapid Applications - [SAP] - Internet Explorer bereitgestellt von Ihrer r.png

I didn't add the the Rapid Application to a UI profile for this blog. Consequently, the final step is to save and generate the application. After the application has been generated it can be tested using the "Test" button. Note however, that not the complete functionality of the application is available in the test mode. For example, navigating to different business objects is not supported in the test mode.

The final step in defining the UI is to adjust the UI configuration. As with all SAP CRM applications this can either be done using the configuration mode or transaction BSP_WD_CMPWB_NEW. I change the standard configuration of the header details view as shown in the screen shot below. The business partner field is defined as mandatory, all other fields are display only. The other field should be automatically filled when a new record is created.

2014-11-04 22_19_54-http___srv06164.dataw1.de_8000_sap(bD1lbiZjPTEwMCZkPW1pbg==)_bc_bsp_sap_bspwd_cm.png

Extending the Rapid Application

In order to automatically populate the read only fields as well as to check the attributes according to the rules defined above it is necessary to extend the rapid application. In order to execute custom code during creating, changing and checking of a record the BAdI AXT_RT_TABLES_API is provided by SAP. The BAdI provides the methods ON_CREATE, ON_CHANGE, CHECK and QUERY. In this blog I will use the methods ON_CREATE, ON_CHANGE and CHECK to implement the requirements defined in the application design.

 

Automatically Populating Fields

The application design defines the following requirements:

  1. The Record ID should be taken from a number range object
  2. The creation date and user and change date and user should be set automatically by the system.

In order to achieve this I created the implementation ZCD_AXT_TABLES_RAE_HEADER_IMPL of the BAdI AXT_RT_TABLES_API. The execution of the BAdI is controlled by a filter on the table name. Therefore, I created the filter value ZCD_RAE_HEADER = TABLE_ID (see screen shot below).

2014-11-05 20_45_42-ABAP - CFD_100_drumm_en_[CFD] ZCD_RAPID_APPS_EX_ENH - Eclipse.png

The code snipped below shows the implementation of the ON_CREATE and ON_CHANGE methods of the BAdI implementation class. In the ON_CREATE method a new ZZ_OBJECT_ID is taken from a number range (lines 7 - 21). Besides that, only the fields ZZ_CREATED_BY, ZZ_CREATED_AT, ZZ_CHANGED_BY and ZZ_CHANGED_AT are initialized. The method GET_PARTNER_GUID_FOR_UNAME used in line 29 and 43 is a simple functional wrapper around the function module BP_CENTRALPERSON_GET.

 

  METHOD if_ex_axt_rt_tables_api~on_create.    DATA: header_data TYPE zcd_rae_header_work,          partner_guid TYPE bu_partner_guid.            header_data = cs_work_structure.    IF header_data-zz_object_id IS INITIAL.      CALL FUNCTION 'NUMBER_GET_NEXT'        EXPORTING          nr_range_nr             = '01'          object                  = 'ZCD_RAPID'        IMPORTING          number                  = header_data-zz_object_id        EXCEPTIONS          interval_not_found      = 1          number_range_not_intern = 2          object_not_found        = 3          quantity_is_0           = 4          quantity_is_not_1       = 5          interval_overflow       = 6          buffer_overflow         = 7          OTHERS                  = 8.      IF sy-subrc <> 0.        RETURN.      ENDIF.    ENDIF.    IF header_data-zz_created_by IS INITIAL.      partner_guid = me->get_partner_guid_from_uname( ).      header_data-zz_created_by = partner_guid.      header_data-zz_created_at = sy-datum.      header_data-zz_changed_by = partner_guid.      header_data-zz_changed_at = sy-datum.    ENDIF.    cs_work_structure = header_data.  ENDMETHOD.  METHOD if_ex_axt_rt_tables_api~on_change.    DATA: header_data TYPE zcd_rae_header_work.    header_data = cs_work_structure.    header_data-zz_changed_by = me->get_partner_guid_from_uname( ).    header_data-zz_changed_at = sy-datum.  ENDMETHOD.

Issue Invoking the BAdI

The problem with the current status of the example application is that the BAdI implementation ZCD_AXT_TABLES_RAE_HEADER_IMPL will never get called. The reason is, that the Rapid Application framework checks if a logical key (the field ZZ_OBJECT_ID in the example application) before calling the BAdI. If the logical key is initial, an error message is raised an the BAdI is not called. Consequently it is impossible to implement the requirement to get the object ID from a number range object using only the BAdI AXT_RT_TABLES_API.

The only solution I found to circumvent this problem was to overwrite the DO_CREATE_ROOT method in the implementation class of the overview page ZCD_RAE_HEADER_OVP.

2014-11-12 17_02_20-ABAP - CFD_100_drumm_en - Eclipse.png

In general I don't like the idea of having to enhance the overview page to enable the automatic creation of the object ID. Because the Rapid Application framework forces me to do this I have the logic to create an object is distributed across different parts of the application. However, I found a rather elegant solution for overwriting the method DO_CREATE_ROOT, which is shown in the following code snippet.

In order to create the root entity I simply invoke ON_CREATE method of the BAdI implementation in line 8. The loop in lines 17-22 then maps the resulting data to the creation parameters of the root entity. The root entity is then create in line 24.

 

METHOD do_create_root.    DATA: header_data TYPE zcd_rae_header_work.    DATA(bol_core) = cl_crm_bol_core=>get_instance( ).    DATA(entity_factory) = bol_core->get_entity_factory( mv_main_entity ).    DATA(creation_params) = entity_factory->get_parameter_table( ).    NEW zcl_axt_tables_rae_header_impl( )->if_ex_axt_rt_tables_api~on_create(      EXPORTING        iv_object_id      = ''        iv_parent_id      = ''        iv_record_id      = ''      CHANGING        cs_work_structure = header_data    ).    LOOP AT creation_params ASSIGNING FIELD-SYMBOL(<creation_param>).      ASSIGN COMPONENT <creation_param>-name OF STRUCTURE header_data TO FIELD-SYMBOL(<comp>).      IF <comp> IS ASSIGNED.        <creation_param>-value = <comp>.      ENDIF.    ENDLOOP.    DATA(bol_col) = bol_core->root_create( iv_object_name = mv_main_entity                                           iv_create_param = creation_params                                           iv_number = 1 ).    eo_entity =  bol_col->get_first( ).  ENDMETHOD.

Checking the Attribute Validity

In order to check the attribute validity using BRFplus I created another implementation  ZCD_AXT_TABLES_RAE_ATTRIB_IMPL of the BAdI AXT_RT_TABLES_API. For this implementation, however, the filter value ZCD_RAE_ATTRIBS = TABLE_ID is used (see screen shot below).

2014-11-12 18_15_17-ABAP - CFD_100_drumm_en_[CFD] ZCD_RAPID_APPS_EX_ENH - Eclipse.png

In the implementation class of the BAdI only the CHECK method is used. The code snippet below shows the implementation of the method. In order to check the attributes validity according to the defined rules, it is important to get all attributes of a header record. This is done by first getting the current attribute entity using the RECORD_ID and PARENT_ID in lines 8-13. From the current attribute entity the header entity is read in lines 19. In lines 24-34 all attribute entities related to the header entity are read and the attribute table for the invocation of the BRFplus function is prepared. The simplest way to find the correct entity names and relations is, as with other CRM applications, to use the BOL Model Browser in the transaction BSP_WD_CMPWB_NEW. Finally, the BRFplus function is invoked in line 36.

  METHOD if_ex_axt_rt_tables_api~check.    DATA: bol_key TYPE axts_ca_bol_key,          attrib_properties TYPE zcd_rae_attribs_attr,          attrib_properties_tab TYPE STANDARD TABLE OF zcd_rae_attribs_attr.    DATA(bol_core) = cl_crm_bol_core=>get_instance( ).    ASSIGN COMPONENT 'RECORD_ID' OF STRUCTURE is_work_structure TO FIELD-SYMBOL(<record_id>).    ASSIGN COMPONENT 'PARENT_ID' OF STRUCTURE is_work_structure TO FIELD-SYMBOL(<parent_id>).    bol_key-parent_id = <parent_id>.    bol_key-record_id = <record_id>.    DATA(current_entity) = bol_core->get_entity(                                        EXPORTING                                          iv_object_name = 'ZAET_CA_CTAB000014 '                                          iv_object_id   = cl_crm_genil_container_tools=>build_object_id( is_object_key = bol_key ) ).    CHECK current_entity IS BOUND.    DATA(header_entity) = current_entity->get_parent( ).    CHECK header_entity IS BOUND.    DATA(attrib_entities) = header_entity->get_related_entities( iv_relation_name = 'ZAET_CA_TO_CTAB000014' ).    DATA(iterator) = attrib_entities->get_iterator( ).    DATA(attrib_entity) = iterator->get_first( ).    WHILE attrib_entity IS BOUND.      attrib_entity->get_properties(        IMPORTING          es_attributes = attrib_properties      ).      APPEND attrib_properties TO attrib_properties_tab.      attrib_entity = iterator->get_next( ).    ENDWHILE.    ct_messages = me->execute_checks_using_brfplus( attrib_properties_tab ).  ENDMETHOD.

Summary

The implementation of the second BAdI concludes the first part of the application. With only a few lines of code an mostly only configuration of the Rapid Application the result is a running example application that already fulfils most of the requirements stated in the application design. In the second part of the blog I'll show how implement the validity checks for the attributes (ie. the implementation of the method EXECUTE_CHECKS_USING_BRFPLUS  as well as the underlying BRFplus function.

 

Christian

CRM Rapid Applications - An Advanced Tutorial (Part 2)

$
0
0

Introduction

In part 1 of this tutorial (CRM Rapid Applications - An Advanced Tutorial (Part 1)) I described how to implement a basic application using the CRM Rapid Application framework (Rapid Applications - SAP Library). In this part of the tutorial I will show how the BRFplus (SAP Business Rules Management) can be used to perform data validations in CRM Rapid Applications.

 

Designing the BRFplus Application

As described in the requirements in the first part of this tutorial the attributes in the example application should be checked according to the following rules:

  1. "Attribute 1" is mandatory, "Attribute 2" is optional.
  2. Allowed values for "Attribute 1" are "A", "B" and "C",
  3. Depending on the value of "Attribute 1" the following values are allowed for "Attribute 2"
    Attribute 1Attribute 2
    A1 - 99
    B100 - 299
    C300 - 999

The basic approach to check the attributes in BRFplus will be the following (thanks to Christian Lechner for helping me out with the initial design):

  1. Create a BRFplus application with one BRFplus function CHECK_ATTRIBUTE_VALIDITY. The function takes a table of type ZCD_RAE_ATTRIBS_ATTR as input and returns if the attributes are valid and optionally an error message.
  2. The functions is implemented using a single rule set CHECK_ATTRIBUTE_VALIDITY_RS.
  3. In the rule set the attribute validity is checked in three steps using different expressions (e.g. table operations, formulae or decision tables):
    1. Check if the mandatory "Attribute 1" is present. If yes, store its value in a local variable in the rule set. Otherwise set an error message and exit.
    2. Check if the optional "Attribute 2" is present. If yes, store its value in a local variable in the rule set. If more the one "Attribute 2" is present set an error message and exit
    3. Check the attribute value ranges using the local variables and a decision table. If the value ranges are valid return success other set an error message and exit.

Especially allowing an optional "Attribute 2" while trying to check the value ranges in the decision table required some special handling. There are two reasons for this. First, it is necessary to convert the string values of the attributes into number. Second, optional attributes are not easy to handle in a decision table. I'll discuss both topics in more detail in the subsequent sections.

 

Implementing the BRFplus Application

Implementing the validity checks described above in a BRFplus application require to some changes to the implementation approach an experienced ABAP developer would take. The reasons for these are twofold. First, BRFplus has some special properties, like e.g. the handling of data objects, that are counter-intuitive for an ABAP developer. Second, an even more important, is the need to think in rules and expressions instead of objects, attributes and methods. This was, at least for me, the biggest difference.

In the following sections I'll describe the necessary steps in BRFplus as well as the underlying rational to the approach as detailed as possible. This way I hove to simplify the first steps in BRFplus for developers by helping them to avoid common mistakes and trap doors.

 

Creating the BRFplus Application

The first step in order to implement the validity checks in BRFplus is to create a BRFplus application. In order to create a the application start the BRFplus workbench using transaction BRF+. In the BRFplus workbench us "Create Application" to create a new application. for this blog I created the application ZCD_RAPIDAPP_EX as a local application.

2014-11-16 14_02_26-BRFplus Workbench - Internet Explorer bereitgestellt von Ihrer regio iT.png

 

Creating the DDIC Objects and BRFplus Data Objects

With the application in place the next step is to create the required BRFplus data objects. BRFplus offers two options for creating data objects like elements, structures and tables. Either these data objects can be created in BRFplus using build in data types or they can be created on the basis of existing DDIC object. As mentioned before BRFplus handles data objects differently then an ABAP developer would expect. In particular it is not possible to reuse a data object across functions. As a ABAP developer one would naturally try to reuse a data structure like ATTRIBUTE_1_DATA e.g. as the data type of a variable and the line type of a table data object. However, this is not possible in BRFplus. Each time a data type is used in BRFplus a separate data object needs to be created for it. In order to reduce the manual modelling effort I will therefore base the data objects in BRFplus on DDIC objects.

Most of the required DDIC objects where already created in the first part of this blog. The only additional DDIC object necessary is the structure ZCD_BRF_RAPID_APP_RESULT_S that I will use as the data type for the result of the BRFplus function. The following table shows the details of this structure:

ComponentComponent Type
ARE_ATTRIBS_VALIDBOOLE_D
ERROR_MESSAGEBAPIRET2

 

With the necessary DDIC objects in place it is now possible to create the required data objects. The first data object that I created is the table object ATTRIBUTE_TABLE. Data objects are created by  right clicking on the application in the repository explorer and selecting "Create -> Data Object -> Table".

2014-11-06 20_11_24-BRFplus Workbench - Internet Explorer bereitgestellt von Ihrer regio iT.png

In the following dialogue I set the name and description for the table data object. Furthermore, I set the binding type to "DDIC Table" and selected the DDIC type ZCD_RAE_ATTRIBS_ATTR_T. This table type was automatically created during the modelling of the Rapid Application in part one of the tutorial.

2014-11-06 20_10_06-BRFplus Workbench - Internet Explorer bereitgestellt von Ihrer regio iT.png

After clicking on create a number of data objects are automatically created. Besides the table data object ATTRIBUTE_TABLE the structure ZCD_RAE_ATTRIBS_ATTR and the two data elements ZZ_ATTRUTE and ZZ_VALUE are created. The following screen shot shows the created data object:

2014-11-06 20_12_03-BRFplus Workbench - Internet Explorer bereitgestellt von Ihrer regio iT.png

In addition to the table data object I also created the following data objects

Data ObjectDDIC TypeDescription
Structure ATTRIBUTE_1_DATAZCD_RAE_ATTRIBS_ATTRData object for local variable in the rule set to store the values of Attribute 1.
Structure ATTRIBUTE_2_DATAZCD_RAE_ATTRIBS_ATTRData object for local variable in the rule set to store the values of Attribute 2.
Structure CHECK_RESULTSZCD_BRF_RAPID_APP_RESULT_SData object for the result of the function CHECK_ATTRIBUTE_VALIDITY.

 

Creating the Function and the Rule Set

With the data objects in place the next step is to create the CHECK_ATTRIBUTE_VALIDITY function and the rule set implementing this function. A function is created by right clicking the BRFplus application and selecting "Create -> Function". Once the function is create make sure it is set to "Event Mode" (cf. the following screen shot).

In the signature of the function the function context (ie. the input data to the function) as well as the result data object are defined. For the CHECK_ATTRIBUTE_VALIDITY function I choose the table data object ATTRIBUTE_TABLE as the context and the structure data object CHECK_RESULT as the result data object. Note that the context of a function can consists of more then one data object. However, for this example application on the attribute table is needed.

2014-11-06 20_30_16-BRFplus Workbench - Internet Explorer bereitgestellt von Ihrer regio iT.png

After creating the function the next step is to create the CHECK_ATTRIBUTE_VALIDITY_RS rule set implementing the function. Again, a function can have multi rule sets assigned but for the example application only one rule set is needed. Rule sets can be created by selection the tab "Assigned Rulesets" and clicking on "Create Ruleset". Once the rule set is created I added the two data objects ATTRIBUTE_1_DATA and ATTRIBUTE_1_DATA as variable to the rule set.

2014-11-06 20_32_33-BRFplus Workbench - Internet Explorer bereitgestellt von Ihrer regio iT.png

Creating Auxiliary Expressions

Before modelling the rules implementing the rule set the next step is to create some auxiliary expressions. These auxiliary expression are necessary to implement the rules. As mentioned in the application design the input data to the BRFplus application will be the table data object ATTRIBUTE_TABLE. To create the BRFplus rules the following auxiliary expressions are necessary:

  1. A table expression IS_ATTRIB_1_VALID to check if exactly on attribute named "Attribute 1" is contained in the input table.
  2. A table expressions GET_ATTRIB1_VALUE to select the data of "Attribute 1" form the input table.
  3. A table expression IS_ATTRIB_2_VALID to check if zero or one attribute named "Attribute 2" is contained in the input table.
  4. A table expression IS_ATTRIB_2_SET to check if exactly one attributes named "Attribute 2" is contained in the input table.
  5. A table expressions GET_ATTRIB2_VALUE to select the data of "Attribute 2" form the input table.

From the application design most of the table expressions should be pretty clear. The first table expressions is needed to check if the "Attribute 1" is contained in the attribute table. If this is the case, the second table expression is used to select the data of "Attribute 1". The rules three and five will be used in a similar fashion. The fourth rule is needed in the decision table to distinguish between the case where the "Attribute 2" is set from the one where it is not set.

 

The following screen shots show the implementation of the two table expressions IS_ATTRIB_1_VALID andGET_ATTRIB1_VALUE. IS_ATTRIB_1_VALID uses the operation "Has exactly". Using this operation it checks if the attribute table contains exactly one row where the value of ZZ_ATTRIBUTE is equal to Attrib1. Note that BRFplus automatically provides the possible values for the data object in the value help. I modelled the possible values using the Rapid Application Tool in the first part of the tutorial.

2014-11-06 20_35_03-BRFplus Workbench - Internet Explorer bereitgestellt von Ihrer regio iT.png

The GET_ATTRIB1_VALUE function uses the operation "First line" to select the first row where the value of ZZ_ATTRIBUTE is equal to Attrib1.

2014-11-06 20_46_33-BRFplus Workbench - Internet Explorer bereitgestellt von Ihrer regio iT.png

The other table expressions are implemented accordingly.

In addition to the table expressions a value range expression CONTAINS_ONLY_NUMBER and a formula expression CONVERT_ATTRIBUTE_TO_NUMBER are needed. The CONVERT_ATTRIBUTE_TO_NUMBER expression uses the CONTAINS_ONLY_NUMBER expression and the build in expression TONUMBER to convert the values of Attribute 2 in the internal number representation of BRFplus. Only if the data is in this format checking the values ranges of Attribute 2 works as required. The following screen shots show the implementation details of these expressions:

2014-11-16 22_41_36-BRFplus Workbench - Internet Explorer bereitgestellt von Ihrer regio iT.png2014-11-16 22_42_22-BRFplus Workbench - Internet Explorer bereitgestellt von Ihrer regio iT.png


Testing the Expressions

With the auxiliary functions in place it is a good time to introduce the simulation tool that is part of the BRFplus workbench. This tool can be used to simulate the execution of functions, rules and expressions during development. In order to, for example, simulate the execution of a expressions simply click on "Start Simulation" in the "Detail" section of the table expression.

2014-11-06 20_42_41-BRFplus Workbench - Internet Explorer bereitgestellt von Ihrer regio iT.png

Next, on the simulation screen simply accept the default values.

2014-11-06 20_43_30-BRFplus Workbench - Internet Explorer bereitgestellt von Ihrer regio iT.png

In the following screen it is now possible to provide input data for the simulation. As one would expect it is also possible to store the entered data as test variants or to upload test data from an excel. In my simple example I simply entered the test data manually. By clicking on "Execute and Display Processing Steps" the function is executed and the result as well as the executed processing steps are displayed. Especially when developing complex expressions or functions the simulation is helpful to test the development and analyse errors.

2014-11-06 20_44_08-BRFplus Workbench - Internet Explorer bereitgestellt von Ihrer regio iT.png

 

Creating the Decision Table

Using the auxiliary expressions it is now possible to define the decision table CHECK_ATTRIBUTE_VALUES. In the table settings of the decision table I used to "Insert Column" button to add the field ZZ_VALUE from the rule set variable ATTRIBUTE_1_DATA as the first column, the expression IS_ATTRIB_2_SET as the second column and the expression CONVERT_ATTRIBUTE_TO_NUMBER as the third column. As the result column of the decision table I selected a simple boolean value.

2014-11-16 22_46_43-BRFplus Workbench - Internet Explorer bereitgestellt von Ihrer regio iT.png

Using this set up I modelled the following rules in the decision table, which basically checks the third requirement of the application design.

 

ZZ_VALUEIS_ATTRIB_2_SETCONVERT_ATTRIBUTE_TO_NUMBERResult
A;B;Cfalse...true
AtrueBetween 1 and 99true
BtrueBetween 100 and 299true
CtrueBetween 299 and 999true

 

The following screen shot shows the implementation of this decision table in the BRFplus workbench.

2014-11-16 22_47_16-BRFplus Workbench - Internet Explorer bereitgestellt von Ihrer regio iT.png

 

Creating the Rules in the Rule Set

Finally, all the necessary building block to implement the rule set are in place. Using the rule set the following logic (mention already in the application design) will be implemented:

  1. Check if the mandatory "Attribute 1" is present. If yes, store its value in a local variable in the rule set. Otherwise set an error message and exit.
  2. Check if the optional "Attribute 2" is present. If yes, store its value in a local variable in the rule set. If more the one "Attribute 2" is present set an error message and exit
  3. Check the attribute value ranges using the local variables and a decision table. If the value ranges are valid return success other set an error message and exit.

The following screen shot shows the implementation of the first step in the logic implement in the rule set. The first rule uses the IS_ATTRIB_1_VALID table expression to check if the Attribute 1 is present. If this is the case the CHECK_RESULT-ARE_ATTRIBS_VALID is set to true and the variable ATTRIBUTE_1_DATA to the result of the GET_ATTRIB1_VALUE table expression. The second rule is an exit condition that checks if CHECK_RESULT-ARE_ATTRIBS_VALID is false. If this is the case the error message of the CHECK_RESULT is set to some message class value and the rule execution is terminated.

Note that the combination of the expressions IS_ATTRIB_1_VALID and GET_ATTRIB1_VALUE assures the correct data is selected. If IS_ATTRIB_1_VALID returns true only one Attribute 1 is present in the input data. So GET_ATTRIB1_VALUE which selects the first row containing the value Attribute 1 will always select the right value.

2014-11-16 23_05_30-BRFplus Workbench - Internet Explorer bereitgestellt von Ihrer regio iT.png

The following screen shot show the implementation of the second and third step of the logic. Rules three and four are similar to the rules one and two. This time only Attribute 2 is checked instead of Attribute 1. The fifth rule finally uses the decision table CHECK_ATTRIBUTE_VALUES to check the validity of the attribute values.

2014-11-16 23_13_19-BRFplus Workbench - Internet Explorer bereitgestellt von Ihrer regio iT.png

Integrating BRFplus in the Rapid Application

Implementing the rule set completes the implementation of the BRFplus application. The final step that is missing is to integrate the BRFplus application with the Rapid Application. BRFplus simplifies this integration by providing an ABAP code generator. In order to use this code generator it is necessary to switch the user mode to Expert in the personalization of the BRFplus workbench.

2014-11-06 19_56_15-BRFplus Workbench - Internet Explorer bereitgestellt von Ihrer regio iT.png

Once the expert mode is set the "Create Code Template" button becomes visible in the BRFplus function. The code generated using the button contains nice comments as documentation. Consequently, the generated code is basically self-explanatory. The following code snippet contains the method EXECUTE_CHECKS_USING_BRFPLUS which I omitted in the first part of the tutorial. Lines 13-35 is the code generated by BRFplus with its comments removed. Lines 37-45 is some addition error handling I added for the integration with the CRM Rapid Application framework.

 

METHOD execute_checks_using_brfplus.    "Code template generated by BRF+    CONSTANTS:lv_function_id TYPE if_fdt_types=>id VALUE '0050568F55091ED499BCB76706ABF315'.    DATA:lv_timestamp TYPE timestamp,         lt_name_value TYPE abap_parmbind_tab,         ls_name_value TYPE abap_parmbind,         lr_data TYPE REF TO data,         lx_fdt TYPE REF TO cx_fdt,         la_attributes_table TYPE zcd_rae_attribs_attr_t,         la_check_result TYPE zcd_brf_rapid_app_result_s.    FIELD-SYMBOLS: <la_any> TYPE any,                   <error_message> TYPE bapiret2.    GET TIME STAMP FIELD lv_timestamp.    ls_name_value-name = 'ATTRIBUTES_TABLE'.    la_attributes_table = i_attribute_properties.    GET REFERENCE OF la_attributes_table INTO lr_data.    cl_fdt_function_process=>move_data_to_data_object( EXPORTING ir_data             = lr_data                                                                 iv_function_id      = lv_function_id                                                                 iv_data_object      = '0050568F55091ED499BD0B2AE3281315' "ATTRIBUTES_TABLE                                                                 iv_timestamp        = lv_timestamp                                                                 iv_trace_generation = abap_false                                                                 iv_has_ddic_binding = abap_true                                                       IMPORTING er_data             = ls_name_value-value ).    INSERT ls_name_value INTO TABLE lt_name_value.    GET REFERENCE OF la_check_result INTO lr_data.    ASSIGN lr_data->* TO <la_any>.    TRY.        cl_fdt_function_process=>process( EXPORTING iv_function_id = lv_function_id                                                    iv_timestamp   = lv_timestamp                                          IMPORTING ea_result      = <la_any>                                          CHANGING  ct_name_value  = lt_name_value ).      CATCH cx_fdt INTO lx_fdt.        APPEND INITIAL LINE TO r_error_messages ASSIGNING <error_message>.        <error_message>-message = lx_fdt->get_text( ).    ENDTRY.    "Fill error message table    IF la_check_result-are_attribs_valid = abap_false.      APPEND INITIAL LINE TO r_error_messages ASSIGNING <error_message>.      <error_message>-id = 'ZCD_RAPID_APP_EX'.      <error_message>-number = '004'.      <error_message>-type = 'E'.    ENDIF.    IF la_check_result-error_message IS NOT INITIAL.      APPEND la_check_result-error_message TO r_error_messages.    ENDIF.  ENDMETHOD.

The final Application

Finally, the following screen shot show the complete example application with some error messages returned from the value check in BRFplus.

2014-11-16 23_35_26-http___srv06164.dataw1.de_8000_sap(bD1lbiZjPTEwMCZkPW1pbg==)_bc_bsp_sap_bspwd_cm.png

Summary

To implement the BRFplus application I used a bottom up modelling approach. I started modelling the basic building block and combined them in the very end. The reason I used this approach is that I think it helps understanding which parts are necessary and why they are necessary to build an BRFplus application. Besides that, BRFplus also support a top down approach, where you basically create the necessary objects as you need them.

One thing that always puzzled me while working on this blog was the questions if performing the value checks in BRFplus is worth the effort. After all, the simple validity rules could be checked in just a few lines of ABAP code. In my opinion using the BRFplus approach is useful if either of the following hold

  • the business logic changes frequently
  • developers start to build customizing tables to enable to change the business logic independent of the program code
  • business users want to be able to change the rules themselves.

In the example application I could easily add a third attribute and value checks for it without touching any of the ABAP code. The change could be performed by just modelling in the Rapid Application and the BRFplus application. This is in my opinion a very important factor as it will reduce development and test effort.

 

As this blog was my first endeavour in the area of BRFplus I would be happy to discuss my approach in the comments section. I'm sure there are areas where my design could be improved.

 

In summary, this two part blog showed that the combination of CRM Rapid Applications and BRFplus is a very effective approach to develop small business applications from scratch.

 

Christian

Set default values on a search view

$
0
0
ComponentSupport Package
BBPCRMSAPKU70204
SAP_ABAPSAPKA73105
SAP_BASISSAPKB73105
WEBCUIFSAPK-73105INWEBCUIF

SAP_BW

SAPKW73105

 

Before start, sorry for awful look of the code snippets, SAP is currently working on this: Re: Why does the syntax highlighting not work

 

The Scenario

 

We want to set default values as search criteria and we can't implement the note 2045936 - Default Values for Advanced Search Pages (which looks very promising, but lit's not compatible with my current release )

 

What, Where and How

 

That's the most difficult part, decide what to do and most important WHERE, once you got that, the How comes alone, I will focus on a specific scenario because I don't want to transform this blog in a philosophical thing, but between me and you, it is .

 

The Example

 

Let's enhance the BP search (BP_HEAD_SEARCH) to don't show the "set to archive"  BPs, as I see it right know you have at least 3 options:

 

Use a BADI enabled for the search (BADI_CRM_BUPA_IL_SEARCH_EXT)

 

I always choose the BADI as long as is possible, don't forget the UI is the technology which is evolving faster, specially in latest years, Imagine tomorrow SAP decide to replace the whole WebUI for a SAP Fiori ecosystem, you will need to place, so why we don't use always the BADI approach? in this scenario the filter will be totally transparent to the user and this can be good or bad: "why this is giving no results?"

 

Another consideration before going straight to the BADI method is evaluate the performance, my selection should be performed before the standard? after? and that depends on the detail of the BADI, in our example, the BADI is called before the standard selects any data.

 

Enhance the WebUI controller method (CL_BP_HEAD__MAINSEARCH/DO_PREPARE_OUTPUT)

 

For the scenario which I described, the best solution, easy to maintain and user friendly, he or she can decide which criteria to use before the search. Important: don't forget to update the context node  me->typed_context->search->build_parameter_tab( ). otherwise the BOL changes won't be published and you will lose the information.

 

DATA: lo_qs              TYPE REF TO cl_crm_bol_dquery_service,           lo_qs_col        TYPE REF TO if_bol_bo_col,           lo_iterator      TYPE REF TO if_bol_bo_col_iterator,           lo_param_ent TYPE REF TO if_bol_bo_property_access.   super->do_prepare_output( EXPORTING iv_first_time = abap_false ).   IF iv_first_time = abap_true.     lo_qs ?= me->typed_context->search->collection_wrapper->get_current( ).     lo_qs_col ?= lo_qs->get_selection_params( ).     lo_iterator = lo_qs_col->get_iterator( ).     lo_param_ent = lo_iterator->find_by_property( iv_attr_name = |ATTR_NAME|                                                                               iv_value       = |XDELE| ).     IF lo_param_ent IS BOUND.       lo_param_ent->set_property( EXPORTING iv_attr_name = |SIGN|                                                                             iv_value         = |I|   ).                                           lo_param_ent->set_property( EXPORTING iv_attr_name = |OPTION|                                                                             iv_value         = |EQ|   ).       lo_param_ent->set_property( EXPORTING iv_attr_name = |LOW|                                                                             iv_value         = |N|   ).       me->typed_context->search->build_parameter_tab( ).     ENDIF.   ENDIF.

Enhance the WebUI controller method for the search (CL_BP_HEAD__MAINSEARCH/EH_ONSEARCH)

 

Well that's probably the worst option, if we talk about behaviour is a mix of the other two, you can use the parameter and hide it or you can use the parameter and show it after search.

 

If the parameter is not on the screen, we'll add it, use it for the search and update the query parameters on the frontend. If is not in the screen, we just set the values as we wish.

 

  DATA: lo_qs        TYPE REF TO cl_crm_bol_dquery_service,        lo_qs_col    TYPE REF TO if_bol_bo_col,        lo_iterator  TYPE REF TO if_bol_bo_col_iterator,        lo_param_ent TYPE REF TO if_bol_bo_property_access. lo_qs ?= me->typed_context->search->collection_wrapper->get_current( ).  lo_qs_col ?= lo_qs->get_selection_params( ).  lo_iterator = lo_qs_col->get_iterator( ).  lo_param_ent = lo_iterator->find_by_property( iv_attr_name = |ATTR_NAME|                                                iv_value     = |XDELE| ).  IF lo_param_ent IS NOT BOUND.    lo_param_ent ?= lo_qs->insert_selection_param( iv_index     = lo_qs_col->size( ) + 1                                                   iv_attr_name = |XDELE|                                                   iv_sign      = |I|                                                   iv_option    = |EQ|                                                   iv_low       = |N| ).  ELSE.    lo_param_ent->set_property( EXPORTING iv_attr_name = |SIGN|                                          iv_value     = |I|   ).    lo_param_ent->set_property( EXPORTING iv_attr_name = |OPTION|                                          iv_value     = |EQ|   ).    lo_param_ent->set_property( EXPORTING iv_attr_name = |LOW|                                          iv_value     = |N|   ).  ENDIF.  me->typed_context->search->build_parameter_tab( ).  super->eh_onsearch( EXPORTING htmlb_event    = htmlb_event                                htmlb_event_ex = htmlb_event_ex ).

This option is in case we disabled the field via configuration so we use it for the search but we don't want to show it to the user.

 

DATA: lo_qs        TYPE REF TO cl_crm_bol_dquery_service,         lo_qs_col    TYPE REF TO if_bol_bo_col,         lo_iterator  TYPE REF TO if_bol_bo_col_iterator,         lo_param_ent TYPE REF TO if_bol_bo_property_access.   lo_qs ?= me->typed_context->search->collection_wrapper->get_current( ).   lo_qs_col ?= lo_qs->get_selection_params( ).   lo_iterator = lo_qs_col->get_iterator( ).   lo_param_ent = lo_iterator->find_by_property( iv_attr_name = |ATTR_NAME|                                                 iv_value     = |XDELE| ).   IF lo_param_ent IS NOT BOUND.     lo_param_ent ?= lo_qs->insert_selection_param( iv_index     = lo_qs_col->size( ) + 1                                                    iv_attr_name = |XDELE|                                                    iv_sign      = |I|                                                    iv_option    = |NE|                                                    iv_low       = |Y| ).   ENDIF.   me->typed_context->search->build_parameter_tab( ).   super->eh_onsearch( EXPORTING htmlb_event    = htmlb_event                                 htmlb_event_ex = htmlb_event_ex ).   IF lo_param_ent IS BOUND.     lo_qs_col->remove( iv_bo = lo_param_ent ).   ENDIF.

 

Conclusion

 

As you see there's no straight forward solution, the solution depends plenty on your landscape, business process and user demands. Every solution has its pros and cons, if you are in control of all of this, everything will go smooth

 

 

Cheers!

 

Luis

Viewing all 195 articles
Browse latest View live