Quantcast
Channel: Business Server Pages (BSP)
Viewing all 20 articles
Browse latest View live

BSP with Flash Movie - Flex-Ajax Bridge

$
0
0

This weblog is about creating Rich Internet Application using BSP and Adobe Flex. BSP is great but it misses the richness of Adobe Flex applications. To leverage flex capabilities within BSP applications we have to create flex applications using flex tools (Flex Builder), which can get data from SAP systems using WebServices or HTTP services and we just embed the compiled (from Flex Builder)  flash movie into or BSP application. This results in much lesser control (runtime) over flash movie contents from BSP application. Flex now also provides a nice tool called Flex - Ajax bridge (FABridge), which can be used to dynamically create flash objects and push data to it using simple JavaScript. In this weblog i am going to show about creating a small BSP application utilizing FABridge. 

If you are new to Flex, check out the following links to learn more about Adobe Flex and Flex-Ajax Bridge.

 

Adobe Flex

 

Flex-Ajax Bridge

 

How this is done.

 

    1. A blank (just with containers) Flash movie is generated using the following code and the finished movie will look like below.

 

blank flash movie:

fabblank.JPG

the flex's MXML code which generated the above movie:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" >
<mx:Script>
<![CDATA[
import mx.controls.dataGridClasses.DataGridColumn;
import mx.controls.Alert;
import mx.controls.*;
import mx.charts.*;
import mx.charts.series.*;
import mx.charts.effects.*;
import mx.graphics.LinearGradient;
import mx.graphics.GradientEntry;
import mx.graphics.IFill;
import mx.charts.series.items.ColumnSeriesItem;
import mx.charts.Legend;
//utils
import flash.utils.Timer;
import flash.utils.clearInterval;
//effects
import mx.effects.*;
import mx.effects.easing.*;
import mx.effects.effectClasses.*;
// these just force datagrid to be linked into the application
public var refs:Array = [DataGrid, Legend, DataGridColumn, ColumnChart, ColumnSeries, LineSeries, CategoryAxis, LinearAxis, AxisRenderer, SeriesInterpolate, SeriesSlide, SeriesZoom, LinearGradient, GradientEntry, Label ];
]]>
</mx:Script>
<mx:VDividedBox horizontalAlign="center" width="100%" height="100%" id="vdbox">
<mx:Panel width="70%" roundedBottomCorners="true" height="50%" id="gpanel"paddingLeft="0" paddingTop="0" title="">
</mx:Panel>
<mx:HDividedBox height="50%" width="100%" id="hdbox">
<mx:Panel width="50%" roundedBottomCorners="true" height="100%" id="cpanela" paddingLeft="0" paddingTop="0" title="">
</mx:Panel>
<mx:Panel width="50%" roundedBottomCorners="true" height="100%" id="cpanelb" paddingLeft="0" paddingTop="0" title="">
</mx:Panel>
</mx:HDividedBox>
</mx:VDividedBox>
<fab:FABridge xmlns:fab="bridge.*" />
</mx:Application>

 

2. This blank movie is used in bsp and its contents (datagrid, chart) are  \ dynamically created from BSP.

 

What do you need to develop/run this demo BSP application.

 

    1. FABridge.js from \ \ FABridge B4 013007.zip

 

    2. Adobe \ \ Flash Player 9 .

 

    3. swfobject.js from \ \ Download SWFObject 1.5

 

    4. blank flash movie  \ dviewer.swf  (to download \ right click and  \ save target as).

 

Demo BSP Application

 

Lets get started and create the demo BSP application.

 

    1. Create a BSP application and set it as stateful  \ application.

 

    2. Import FABridge.js , swfobject.js and dviewer.swf into  \ the MIME folder of this application. (do not change their names)

 

    3. Create a page with following details.

 

 

Type Definition:

ftd.JPG    

Page Attributes:

pa1.JPG   

OnInitialization code:

* event handler for data retrieval
REFRESH flights .
REFRESH: sumflights .
CLEAR : wa_flights , sumflights, wa_sumflights .
REFRESH: fields .
 
SELECT
carrid
seatsmax
seatsocc
seatsmax_b
seatsocc_b
seatsmax_f
seatsocc_f UP TO 200 ROWS FROM sflight INTO TABLE sumflights .
 
LOOP AT sumflights INTO wa_sumflights .
COLLECT wa_sumflights INTO flights .
ENDLOOP .

CLEAR wa_fields .
wa_fields-name = 'CARRID' .
wa_fields-value = 'Carrier' .
APPEND wa_fields TO fields .
  
CLEAR wa_fields .
wa_fields-name = 'SEATSMAX' .
wa_fields-value = 'Eco. Max Capacity' .
APPEND wa_fields TO fields .

CLEAR wa_fields .
wa_fields-name = 'SEATSOCC' .
wa_fields-value = 'Eco. Occupied' .
APPEND wa_fields TO fields .
 
CLEAR wa_fields .
wa_fields-name = 'SEATSMAX_B' .
wa_fields-value = 'Buss. Max Capacity' .
APPEND wa_fields TO fields .

CLEAR wa_fields .
wa_fields-name = 'SEATSOCC_B' .
wa_fields-value = 'Buss. Occupied' .
APPEND wa_fields TO fields .

 

CLEAR wa_fields .
wa_fields-name = 'SEATSMAX_F' .
wa_fields-value = 'First Max Capacity' .
APPEND wa_fields TO fields .

 

CLEAR wa_fields .
wa_fields-name = 'SEATSOCC_F' .
wa_fields-value = 'First Occupied' .
APPEND wa_fields TO fields .

Layout code:

<%@page language="abap" %>
<%@extension name="htmlb" prefix="htmlb" %>
<htmlb:content design = "design2003"
controlRendering = "sap" >
<htmlb:document>
<htmlb:documentHead title="Flex-BSP Flex-Ajax Bridge Sample" >
<script type="text/javascript" src="swfobject.js"></script>
<script src="FABridge.js" ></script>
<script>
<!--
function buildm()
{
var flexApp = FABridge.dviewer.root();
var grid = FABridge.dviewer.create("mx.controls.DataGrid");
grid.setStyle("headerColors", [0x277DC6,0x50ABF7]);
grid.setId("gd");
<%
clear wa_fields .
field-symbols: <l_line> type any,
<l_field> type any.
data: fieldsstring type string ,
rowstring type string ,
fval type string ,
rownum type i ,
colnum type i ,
srnum type string ,
scnum type string .
clear fieldsstring .
loop at fields into wa_fields .
if fieldsstring is initial .
concatenate fieldsstring 'col' wa_fields-name into fieldsstring .
else .
concatenate fieldsstring `, ` 'col' wa_fields-name into fieldsstring .
endif .
%>
var col<%= wa_fields-name %> = FABridge.dviewer.create("mx.controls.dataGridClasses.DataGridColumn");
col<%= wa_fields-name %>.setDataField("<%= wa_fields-name %>");
col<%= wa_fields-name %>.setHeaderText("<%= wa_fields-value %>");
<%
endloop .
%>
grid.setColumns( [<%= fieldsstring %>] );
grid.setHeight(250);
var gddp = FABridge.dviewer.create("mx.collections.ArrayCollection");
<%
clear: wa_flights, wa_fields , rownum, colnum, srnum, scnum.
loop at flights into wa_flights .
clear srnum .
move: rownum to srnum .
condense srnum no-gaps .
%>
<%
clear rowstring .
loop at fields into wa_fields .
clear fval .
assign component wa_fields-name of structure wa_flights to <l_field> .
if <l_field> is assigned.
fval = <l_field> .
condense fval no-gaps .
clear: scnum .
move: colnum to scnum .
condense scnum no-gaps .
if rowstring is initial .
concatenate rowstring '{' wa_fields-name ':' '"' fval '"' into rowstring      .
else .
concatenate rowstring ',' wa_fields-name ':' '"' fval '"' into rowstring    .
endif .
%>
<%
endif .
clear wa_fields .
colnum = colnum + 1 .
endloop .
concatenate rowstring '}' into rowstring .
%>
var a<%= srnum %> = <%= rowstring %> ;
gddp.addItem(a<%= srnum %>);
<%
clear rowstring .
clear : colnum, wa_flights .
rownum = rownum + 1 .
endloop .
%>
grid.setDataProvider(gddp);
var pan = flexApp.getGpanel();
pan.setTitle("Flex - BSP FA Bridge Sample - Flights Grid");
pan.setWidth(725) ;
flexApp.getGpanel().addChild(grid);
var genchar = function(event)
{
var abc = event.getTarget().getSelectedItem() ;
var nnchart = flexApp.getCpanelb().getChildByName("nchart");
nnchart.setDataProvider(abc);
}
grid.addEventListener("change", genchar);
var chart = FABridge.dviewer.create("mx.charts.ColumnChart");
chart.setName("chart");
chart.setShowDataTips("true");
chart.setWidth(475);
chart.setHeight(264);
chart.setId("cc1");
var nchart = FABridge.dviewer.create("mx.charts.ColumnChart");
nchart.setName("nchart");
nchart.setShowDataTips("true");
nchart.setWidth(475);
nchart.setHeight(264);
nchart.setId("ncc1");
var neffect = FABridge.dviewer.create("mx.charts.effects.SeriesInterpolate");
neffect.setMinimumElementDuration(300);
var effect = FABridge.dviewer.create("mx.charts.effects.SeriesZoom");
effect.setMinimumElementDuration(300);
<%
clear : fieldsstring, rowstring, wa_fields .
loop at fields into wa_fields .
if wa_fields-name = 'CARRID' .
%>
var catXAxis = FABridge.dviewer.create("mx.charts.CategoryAxis");
catXAxis.setDisplayName("<%= wa_fields-value %>");
catXAxis.setDataProvider(gddp);
catXAxis.setCategoryField("<%= wa_fields-name %>");
chart.setHorizontalAxis(catXAxis);
var ncatXAxis = FABridge.dviewer.create("mx.charts.CategoryAxis");
ncatXAxis.setDisplayName("<%= wa_fields-value %>");
ncatXAxis.setCategoryField("<%= wa_fields-name %>");
nchart.setHorizontalAxis(ncatXAxis);
<%
else .
clear fval .
move: sy-tabix to fval .
if rowstring is initial .
concatenate rowstring 's' fval into rowstring .
else.
concatenate rowstring ',' 's' fval into rowstring .
endif .
if fieldsstring is initial .
concatenate fieldsstring 'ns' fval into fieldsstring .
else.
concatenate fieldsstring ',' 'ns' fval into fieldsstring .
endif .
%>
var s<%= sy-tabix %> =  \ \     \     FABridge.dviewer.create("mx.charts.series.ColumnSeries");
s<%= sy-tabix %>.setYField("<%= wa_fields-name %>");
s<%= sy-tabix %>.setDisplayName("<%= wa_fields-value %>");
s<%= sy-tabix %>.setStyle("hideDataEffect", effect);
s<%= sy-tabix %>.setStyle("showDataEffect", effect);
var ns<%= sy-tabix %> =  \ \     \     FABridge.dviewer.create("mx.charts.series.ColumnSeries");
ns<%= sy-tabix %>.setYField("<%= wa_fields-name %>");
ns<%= sy-tabix %>.setDisplayName("<%= wa_fields-value %>");
ns<%= sy-tabix %>.setStyle("hideDataEffect", null);
ns<%= sy-tabix %>.setStyle("showDataEffect", effect);
<%
endif .
endloop .
%>
chart.setSeries( [<%= rowstring %>] );
chart.setDataProvider(gddp);
var cpa = flexApp.getCpanela();
cpa.setTitle("Flex - BSP FA Bridge Sample - Flights Chart");
flexApp.getCpanela().addChild(chart);
nchart.setSeries( [<%= fieldsstring %>] );
var cpb = flexApp.getCpanelb();
cpb.setTitle("Flex - BSP FA Bridge Sample - Carrier Details");
flexApp.getCpanelb().addChild(nchart);
}
-->
</script>
</htmlb:documentHead>
<htmlb:documentBody>
<div id="flashoutput">

 

</div>
<script language="javascript" >
var so;
var version = deconcept.SWFObjectUtil.getPlayerVersion();
if (version['major'] >= 9 ){
so = new SWFObject("dviewer.swf", "myflash", "100%", "100%", "8",  "#B3D2D7");

 

so.addParam("FlashVars", "bridgeName=dviewer");
so.addParam("vmode", "opaque");
so.addParam("allowScriptAccess", "always");
so.write("flashoutput");
FABridge.addInitializationCallback("dviewer",buildm);
}
</script>
</htmlb:documentBody>
</htmlb:document>
</htmlb:content>

 

    4. Save and activate the BSP application/page and test the page. Try clicking on the grid column header to sort the grid and see how it affects the chart and also click on a record in the grid and see new chart getting generated you can also drag drop columns in the grid to rearrange the column order.

 

Do you like what you see?

 

The finished BSP page will look like below

fab.JPG

Hope you like the look and feel of this application. If you run into problems running this or creating this bsp application, you can post your questions either here in comment section or in the bsp forum.


BSP With Workflow: Part -I

$
0
0
*UPDATE: *Type definition updated. 

*Introduction:-* Your Business Workplace on Web!!!. This weblog will explain you how to trigger the Workflow and Execute (Decision making) the Workitem from Web using BSP Application. Also you can play around with Workitem with various options like Reserve/Re-submit/Reset etc... *Scenario:-* I took simple scenario to let everyone understand. Applying leave request from web, Applicant will get the initial notification and Manager will get an Workitem to "Approve"/"Reject" the Leave request. As soon as the decision is taken from Manager, applicant will be informed regarding the status. Note: This blog is not intended to explain Workflow concepts. *Highlights: - *Let's look at the roadmap before we start.

  1. A Quick view of Workflow, which I used.
  2. Development of Leave request BSP application - To apply a leave. Also this will send the initial notification to applicant and submit the request for approval in the form of workitem in Manager's (as set in Workflow) Inbox.
  3. Result-1: - Notification in applicant inbox and Workitem in Manager's Inbox.
  4. Development of Web Business Workplace where your Manager can look at all the Workitems & carry out the necessary actions (decision making).
  5. Configuration involved to link your Workflow Task & BSP Application to make it more dynamic.
  6. Result-2: - View all the responsible Workitems and select one of the item to carry out the decision making.
Before we start our steps, lets look at the Workflow, which we are going to use.
  1. Workflow used.

  2. image
    image

    Let's start with simple task...
  3. *Development of Leave request application *Create the BSP Application called *YMY_LEAVE*.*Page attribute:*0.1. image 0.2. *OnInputProcessing:* * event handler for checking and processing user input and * for defining navigation * Container Set Element DEFINE SWC_SET_ELEMENT. CALL FUNCTION 'SWC_ELEMENT_SET' EXPORTING ELEMENT = &2 FIELD = &3 TABLES CONTAINER = &1 EXCEPTIONS OTHERS = 1. END-OF-DEFINITION. DATA : IT_CONTAINER TYPE TABLE OF SWCONT, WF_ID TYPE SWWWIHEAD-WI_ID. DATA: FIELDS TYPE TIHTTPNVP, DB_START TYPE SYDATUM, DB_END TYPE SYDATUM, WA_FIELDS LIKE LINE OF FIELDS. DATA EVENT TYPE REF TO CL_HTMLB_EVENT. EVENT ?= CL_HTMLB_MANAGER=>GET_EVENT_EX( REQUEST ). IF NOT EVENT IS INITIAL. IF EVENT->SERVER_EVENT EQ 'myLeave'. CALL METHOD REQUEST->GET_FORM_FIELDS CHANGING FIELDS = FIELDS. READ TABLE FIELDS INTO WA_FIELDS WITH KEY NAME = 'start_date'. IF SY-SUBRC EQ 0. START_DATE = WA_FIELDS-VALUE. CALL FUNCTION 'CONVERT_DATE_TO_INTERNAL' EXPORTING DATE_EXTERNAL = START_DATE IMPORTING DATE_INTERNAL = DB_START EXCEPTIONS DATE_EXTERNAL_IS_INVALID = 1 OTHERS = 2. ENDIF. READ TABLE FIELDS INTO WA_FIELDS WITH KEY NAME = 'end_date'. IF SY-SUBRC EQ 0. END_DATE = WA_FIELDS-VALUE. CALL FUNCTION 'CONVERT_DATE_TO_INTERNAL' EXPORTING DATE_EXTERNAL = END_DATE IMPORTING DATE_INTERNAL = DB_END EXCEPTIONS DATE_EXTERNAL_IS_INVALID = 1 OTHERS = 2. ENDIF. READ TABLE FIELDS INTO WA_FIELDS WITH KEY NAME = 'reason'. IF SY-SUBRC EQ 0. REASON = WA_FIELDS-VALUE. ENDIF. * Set the data into Workflow container SWC_SET_ELEMENT IT_CONTAINER 'start_date' DB_START. SWC_SET_ELEMENT IT_CONTAINER 'end_date' DB_END. SWC_SET_ELEMENT IT_CONTAINER 'reason' REASON. * Start the Workflow CALL FUNCTION 'EWW_WORKFLOW_START' EXPORTING X_TASK = 'WS90200031' IMPORTING Y_WORKFLOW_ID = WF_ID TABLES X_CONTAINER = IT_CONTAINER EXCEPTIONS INVALID_TASK = 1 NO_ACTIVE_PLVAR = 2 START_FAILED = 3 GENERAL_ERROR = 4 OTHERS = 5. IF SY-SUBRC = 0. COMMIT WORK. NAVIGATION->GOTO_PAGE( 'result.htm' ). ELSE. V_MESSAGE = 'Your Leave Request is not posted into System, Try again later...'. ENDIF. ENDIF. ENDIF. 0.1. Create the *result.htm* page to display the results with following Layout.*OnInputProcessing:-* DATA EVENT TYPE REF TO CL_HTMLB_EVENT.EVENT ?= CL_HTMLB_MANAGER=>GET_EVENT_EX( REQUEST ).IF NOT EVENT IS INITIAL. IF EVENT->SERVER_EVENT EQ 'back'. NAVIGATION->GOTO_PAGE( 'Apply_Leave.htm' ). ENDIF.ENDIF. 0.1. 0.2. Let's look at the outcome of the Step-1. 0.1. Execute the BSP Application YMY_LEAVE and enter the data as follows: 0.2. image 0.3. Click on Submit button: 0.4. image 0.5. Let me look at my Business workplace (Tcode: SBWP), I must have received a notification mail in my Inbox. 0.6. image 0.7. Let me login into my approver's Inbox; must have got the workitem as follows: 0.8. image 0.9. Select the workitem & Click on execute button to look at the workitem details. 0.10. image 0.11. Now the challenging part is how we are going to achieve Decision steps of workflow on web. I would like to make this process *dynamically* so that it will work for any workitems, not only for Leave request workflow. 0.1. 0.2. *Development of web workplace* 0.1. Develop a BSP Application called YWORKPLACE where you can see & execute all the workitems. 0.2. 0.1. Create a page called *main.htm* *Note:* This page will show all the workitems from Inbox and Re-submission folder with options to Execute/Reserve/Re-submit/Reset etc. 0.1. Make the page as "Stateful" 0.2. 0.3. *Layout*:*Page attributes:*0.1. image 0.2. *Type Definition:* * types: t_swr_wihdr type table of swr_wihdr.types: begin of uwl_line, wi_text type witext, statustext type sww_statxt, wi_cd type sww_cd, wi_rhtext type sww_rhtext, wi_stat type sww_wistat, wi_id type sww_wiid, wi_rh_task type sww_task, end of uwl_line.types: uwl type table of uwl_line.types: begin of mail_line, subject type SO_OBJ_DES, OBJTP type SO_OBJ_TP, OBJYR type SO_OBJ_YR, OBJNO type SO_OBJ_NO, end of mail_line.types: mail type table of mail_line.types: t_swfvtv type table of swfvtv.types: tvc type table of tableviewcontrol. 0.1. *OnInitialization:* * event handler for data retrieval DATA: USERNAME TYPE SY-UNAME, ERROR TYPE BAPIRET2_T, ADDR TYPE BAPIADDR3. DATA: STATUS_FILTER TYPE SWRTSTATUS. CONSTANTS: WAITING TYPE CHAR12 VALUE 'WAITING'. USERNAME = SY-UNAME. CALL FUNCTION 'BAPI_USER_GET_DETAIL' EXPORTING USERNAME = USERNAME IMPORTING ADDRESS = ADDR TABLES RETURN = ERROR. CONCATENATE ADDR-LASTNAME ADDR-FIRSTNAME INTO V_NAME SEPARATED BY SPACE. CONCATENATE 'Workitems for' V_NAME INTO HEADER SEPARATED BY SPACE. * Select the customizing for visulization of the workflow tasks REFRESH: I_SWFVTV. SELECT * FROM SWFVTV INTO TABLE I_SWFVTV WHERE VTYP EQ 'BSP_STD'. * Add status waiting if submission is selected IF RE_SUB IS NOT INITIAL. APPEND WAITING TO STATUS_FILTER. ENDIF. * Select workitems for user REFRESH: WORKLIST, I_WORKITEM. CLEAR: WORKLINE, WA_WORKITEM. CALL FUNCTION 'SAP_WAPI_CREATE_WORKLIST' EXPORTING USER = USERNAME READ_TASK_TEXT = 'X' IM_STATUS_FILTER = STATUS_FILTER TABLES WORKLIST = WORKLIST. * Match Workitems wit customizing LOOP AT WORKLIST INTO WORKLINE. READ TABLE I_SWFVTV INTO I_SWFVTV_LINE WITH KEY TASK = WORKLINE-WI_RH_TASK. IF SY-SUBRC EQ 0. MOVE-CORRESPONDING WORKLINE TO WA_WORKITEM. APPEND WA_WORKITEM TO I_WORKITEM. CLEAR WA_WORKITEM. ENDIF. ENDLOOP. 0.1. *OnInputProcessing:* * event handler for checking and processing user input and * for defining navigation DATA: EVENT TYPE REF TO CL_HTMLB_EVENT. DATA: TABLEVIEW_EVENT TYPE REF TO CL_HTMLB_EVENT_TABLEVIEW. * Default values CONSTANTS: NEW_WINDOW TYPE XFELD VALUE 'X', "Open new window = Yes READY TYPE CHAR12 VALUE 'READY', "Status ready WAITING TYPE CHAR12 VALUE 'WAITING'. "Status waiting CLEAR: URL, I_SWFVTV_LINE, OPEN_WINDOW. * Get event of process EVENT = CL_HTMLB_MANAGER=>GET_EVENT( RUNTIME->SERVER->REQUEST ). * Select appropriate action IF EVENT IS NOT INITIAL. CASE EVENT->ID. WHEN 'worklist'. * If an entry in the workitemlist is selected TABLEVIEW_EVENT ?= EVENT. * The data of the selected row is read into wa_workitem IF NOT TABLEVIEW_EVENT->ROW_INDEX EQ SEL_ROW. CLEAR: WA_WORKITEM, SEL_ROW. SEL_ROW = TABLEVIEW_EVENT->ROW_INDEX. READ TABLE I_WORKITEM INTO WA_WORKITEM INDEX SEL_ROW. ELSE. CLEAR: WA_WORKITEM, SEL_ROW. ENDIF. WHEN 'execute'. IF RE_SUB IS INITIAL. * Select URL from customizing READ TABLE I_SWFVTV INTO I_SWFVTV_LINE WITH KEY TASK = WA_WORKITEM-WI_RH_TASK VTYP = 'BSP_STD' VPAR = 'APPLICATION'. IF SY-SUBRC EQ 0. * Prepare URL of BSP page to process work item task (*.htm) CONCATENATE I_SWFVTV_LINE-VVAL '?workitem_id=' WA_WORKITEM-WI_ID INTO URL. * Set work item to reserved CALL FUNCTION 'SAP_WAPI_RESERVE_WORKITEM' EXPORTING WORKITEM_ID = WA_WORKITEM-WI_ID. CLEAR: I_SWFVTV_LINE, OPEN_WINDOW. * Selects settings for workitem task for new window READ TABLE I_SWFVTV INTO I_SWFVTV_LINE WITH KEY TASK = WA_WORKITEM-WI_RH_TASK VTYP = 'BSP_STD' VPAR = 'OPEN_WINDOW'. IF SY-SUBRC NE 0 OR I_SWFVTV_LINE-VVAL IS INITIAL. * If no new window is selected, call URL directly NAVIGATION->GOTO_PAGE( URL = URL ). ELSE. MOVE I_SWFVTV_LINE-VVAL TO OPEN_WINDOW. ENDIF. CLEAR I_SWFVTV_LINE. ENDIF. ENDIF. WHEN 'work_rebox'. * Set conrols for page display IF RE_SUB IS INITIAL. RE_SUB = 'X'. ELSE. CLEAR RE_SUB. ENDIF. WHEN 'submission'. IF RE_SUB IS INITIAL. * Set status of work item to waiting CALL FUNCTION 'SAP_WAPI_SET_WORKITEM_STATUS' EXPORTING WORKITEM_ID = WA_WORKITEM-WI_ID STATUS = WAITING. ELSE. * Set status of work item to ready CALL FUNCTION 'SAP_WAPI_SET_WORKITEM_STATUS' EXPORTING WORKITEM_ID = WA_WORKITEM-WI_ID STATUS = READY. * Set status of work item to reserved CALL FUNCTION 'SAP_WAPI_RESERVE_WORKITEM' EXPORTING WORKITEM_ID = WA_WORKITEM-WI_ID. ENDIF. WHEN 'reset'. * If button 'Reset' is pressed IF NOT WA_WORKITEM-WI_STAT EQ WAITING. * Set status of work item to ready (if work item was reserved) CALL FUNCTION 'SAP_WAPI_SET_WORKITEM_STATUS' EXPORTING WORKITEM_ID = WA_WORKITEM-WI_ID STATUS = READY. ENDIF. WHEN 'reserve'. * SET STATUS OF WORK ITEM TO RESERVED CALL FUNCTION 'SAP_WAPI_RESERVE_WORKITEM' EXPORTING WORKITEM_ID = WA_WORKITEM-WI_ID. WHEN OTHERS. ENDCASE. ENDIF. 0.1. Let's work on Decision making page. This page has *Dynamic* capability to make decision making for any workitem, just you need to pass the workitem as parameter to it. You can create separate BSP Application or else you can create another page in YWORKPLACE Application itself. Name the Page as *"decision.htm". *


  4. *Layout**Page Attribute:*0.1. image 0.2. *OnInitialization* 0.1. CALL FUNCTION 'SAP_WAPI_DECISION_READ' EXPORTING WORKITEM_ID = WORKITEM_ID IMPORTING DECISION_TITLE = DECISION_TITLE TABLES ALTERNATIVES = ALTERNATIVES. CALL FUNCTION 'SAP_WAPI_WORKITEM_DESCRIPTION' EXPORTING WORKITEM_ID = WORKITEM_ID TABLES TEXT_LINES = TEXT_LINES HTML_TEXT_LINES = HTML_TEXT_LINES. 0.2. *OnInputProcessing:* DATA: EVENT TYPE REF TO CL_HTMLB_EVENT. DATA: RETURN_CODE LIKE SY-SUBRC, NEW_STATUS TYPE SWW_WISTAT. DATA: WA_MESSAGE_LINES LIKE LINE OF MESSAGE_LINES. EVENT = CL_HTMLB_MANAGER=>GET_EVENT( RUNTIME->SERVER->REQUEST ). IF EVENT->NAME = 'button' AND EVENT->EVENT_TYPE = 'click'. DECISION_CODE = EVENT->SERVER_EVENT. CALL FUNCTION 'SAP_WAPI_DECISION_COMPLETE' EXPORTING WORKITEM_ID = WORKITEM_ID DECISION_KEY = DECISION_CODE IMPORTING RETURN_CODE = RETURN_CODE NEW_STATUS = NEW_STATUS TABLES MESSAGE_LINES = MESSAGE_LINES. ENDIF. 0.1. Last step is, linking our workflow Task (Decision making Task) & BSP Application (Basically Page/controller which used for decision making) using *Customization*. Follow the steps: 0.1. Goto Tcode SWFVISU & Click on "New Entries" 0.2. image 0.3. Enter the Task number which is responcisble for desicion making & Visualization Type as "BSP Standard" 0.4. image 0.5. Select the entered row & double click on Visualization Parameter under "Task Visualization" 0.6. image 0.7. Enter the following Parameters. In my case, I created another page in same BSP Application. You can also create the another BSP Application for decision making process. 0.8. image 0.9. Save the entries. It might as you customizing request to save. 0.10. 0.1. 0.2. Now we all set to see output 0.1. Let me Execute BSP Application "YWORKPLACE" Page "main.htm" using "Approvers" user ID. Page will display all the workitems as shown below: 0.2. image 0.3. Once you select the workitem, relevant buttons will be enabled to process the workitems 0.4. image 0.5. You can Execute/Reserve/Re-submit/Reset etc. Just for demo, let me click on Reserve button 0.6. image 0.7. Look at the workitem in GUI, status should changed into "Reserve" as shown below: 0.8. image 0.9. Key thing here is, execution of workitem. Let's click on "Execute". *Dynamic decision-making *screen will pop-up as per our customization. 0.10. image 0.11. Click on "Approve": 0.12. image 0.13. The workitem must have vanished since it's approved and applicant will be informed via mail as shown below: 0.14. image

Organize your business attachments from Web

$
0
0
Introduction: This series of blog will explain how to organize your attachment in SAP from web applications like BSP and Webdynpros.

There are many blogs in SDN, which explains how to upload and download the documents in SAP from web.
But there are some scenario where in attachment needs to be managed from web as well as from SAP GUI.
This series of blog will serve & share my views on the same.

There are many ways you can store the attachments in SAP like Records Management, Document Management and Business Document Service (BDS).
I'm going to use BDS to organize the documents.The reasons are,
  1. There is no customizing settings required, that's the good news for developers.
  2. The documents can be viewed in Business Transaction like IE01, VA01 etc.

In order to view the attachments in web, we need an custom application. In addition, user can view the attachments along with Business data in SAP without any further development effort.

Ways to store:

There are multiple way to store the documents in BDS. Using

  1. Business Object Repository object - We are going to explore in detail.
    1. The advantage is, you can view the documents in SAP along with Business data in one place.
  2. Object from class library
    1. In order to store using Class, you need to link our class to document class in Transaction code SBDSV1.
  3. Other objects - Settings required

Attachment can also be viewed in BDS Navigator (Transaction code OAOR).

We are going to discuss the below items using Business object option in detail:

  1. BSP
    1. BSP/How To: Create attachment in Business transactions
    2. BSP/How To: Display/Delete attachment from Business transactions
  2. Webdynpro - Java
    1. How To: Create attachments in Business Transaction from Webdynpro(Java)
    2. Display/Delete attachment from Business transactions using Webdynpro (Java)

BSP/How To: Create attachment in Business transactions

$
0
0
Introduction: This blog describes how to Attach the documents from BSP Application to any SAP Business transactions like IE01, VA01, FB01, WTY etc.



In order to achieve this, we are going to use Business Document Service (BDS).



Before we see the code, let us look at the result.



As I said earlier, you can attach it to any Business transaction. I took an example of attaching the document into ISR: Notification (IQS22) Transaction.



Note: This example will not show how to create the Notification. Assume that Notification already exists.



image
    1. Once you click on attachment, you can see the attachment list in pop-up where you can select & view the documents.


    image

     

      1. Just for information: All the attachment can also be viewed in Transaction called OAOR irrespective of any Objects. Go to Tcode OAOR & enter the below detail and execute:



      2. !https://weblogs.sdn.sap.com/weblogs/images/11786/Attach_SAP3.JPG|height=178|alt=image|width=569|src=https://weblogs.sdn.sap.com/weblogs/images/11786/Attach_SAP3.JPG|border=0!

      image


      Let's look at the procedure to create attachment:

       

      Here is the Function module, which can be used to upload the document to any business transaction. All you need to do is, finding a proper business object as per your requirement:


      Note: Make it as "Remote Enabled", so that it can be called outside SAP also.



      FUNCTION ZATTACH_CREATE.*"----
      ""Local Interface:*"  IMPORTING*"     VALUE(FILE) TYPE  SAPB-SAPFILES*"     VALUE(STR_FILE_CONTENT) TYPE  XSTRING*"     VALUE(OBJECT_KEY) TYPE  BAPIBDS01-OBJKEY*"     VALUE(BUS_OBJECT) TYPE  BAPIBDS01-CLASSNAME*"  TABLES*"      RETURN STRUCTURE  BAPIRET2*"----

       

        DATA: LOGICAL_SYSTEM TYPE BAPIBDS01-LOG_SYSTEM,        CLASSNAME TYPE  BAPIBDS01-CLASSNAME,        CLASSTYPE TYPE  BAPIBDS01-CLASSTYPE.

       

       

        DATA:  IT_FILES TYPE TABLE OF  BAPIFILES,         IT_SIGNATURE TYPE TABLE OF  BAPISIGNAT,         FILES TYPE  BAPIFILES,         SIGNATURE TYPE   BAPISIGNAT.

       

        DATA: I_TOADD TYPE TOADD,        FILENAME_ALL TYPE SAPB-SAPFILES,        FILE_PATH TYPE SAPB-SAPFILES,        FILE_PATH_MEMORY(250) TYPE C,    " path für SAP memory        FILE_PATH_LENGTH TYPE I,         " length of the file_path        FILE_CONTENT_LENGTH TYPE I,       " length of the file_CONTENT        FILE_NAME TYPE SAPB-SAPFILES,        FILE_EXTENSION TYPE TOADD-DOC_TYPE.     " file-extension.

       

        DATA: I_FILE_CONTENT TYPE TABLE OF BAPICONTEN.  DATA: WA_FILE_CONTENT TYPE  BAPICONTEN.

       

       

        DATA:        I_FILES TYPE TABLE OF BAPIFILES ,        I_SIGNATURE TYPE TABLE OF BAPISIGNAT ,        I_COMPONENTS TYPE TABLE OF BAPICOMPON ,        COMPONENTS TYPE BAPICOMPON ,        I_PROPERTIES TYPE TABLE OF SDOKPROPTY ,        I_PROPERTIES2 TYPE TABLE OF BAPIPROPER ,        I_PROPERTIES_DEL TYPE TABLE OF BAPIDPROPT ,        MIMETYPE TYPE TOADD-MIMETYPE,        COMPID TYPE BAPICOMPON-COMP_ID,  " compid for popup        COMPID_FLAG(1) TYPE C,           " compid can't be changed        MODIFY_FLAG(1) TYPE C,           " document is modified        ATTRI_FLAG(1) TYPE C,            " attri can't be changed        TITLE_FLAG(1) TYPE C,            " titel of the popup        CANCEL_FLAG(1) TYPE C,           " cancel button Yes/No        BUTTON_TEXT(10) TYPE C,        INFO_TEXT(100) TYPE C,        VERSION_NR TYPE BDN_CON-DOC_VER_NO,     " version_nr for popup        VERSION_LANGU2 TYPE T002T-SPTXT, " language field in POPUP        VERSION_LANGU TYPE BDN_CON-LANGU," SPRAS language field        TITLE_TEXT(100) TYPE C,          " title of the popup        DESCRIPTION TYPE BDN_CON-DESCRIPT.      " descr. for popup

       

       

        FILE_CONTENT_LENGTH = XSTRLEN( STR_FILE_CONTENT ).

       

        FILENAME_ALL = FILE .

       

       

        DATA: OUT_LINES TYPE TABLE OF XSTRING.

       

        CALL FUNCTION 'SCMS_XSTRING_TO_BINARY'    EXPORTING      BUFFER     = STR_FILE_CONTENT    TABLES      BINARY_TAB = I_FILE_CONTENT.

       

      1. -- split filename                                                 -- *

       

        DATA: LENGTH TYPE I.  DATA: PATH2(1200).  " LIKE sapb-sapfiles.

       

        DATA: L_FILE(1200),        L_DIR(1200),        L_DELIMITER        TYPE C,        L_OFFSET           TYPE I,        L_BYTES            TYPE I,        L_DELIMITER_OFFSET TYPE I,        L_DELIMITER_BYTES  TYPE I,        L_CHAR             TYPE C,        L_PREVIOUS_CHAR    TYPE C,        L_LAST_CHAR        TYPE C.

       

        PATH2 = FILENAME_ALL.  LENGTH = STRLEN( PATH2 ).  L_OFFSET = 0.  WHILE L_OFFSET < LENGTH.    L_LAST_CHAR = L_CHAR. "remember last character    L_BYTES = CHARLEN( PATH2+L_OFFSET ).    L_CHAR = PATH2+L_OFFSET(L_BYTES). "get current character    IF L_CHAR = '/' OR L_CHAR = '\' OR L_CHAR = ':'.      IF L_DELIMITER IS INITIAL OR L_DELIMITER = ':'.        L_DELIMITER = L_CHAR.      ENDIF.      IF L_CHAR = L_DELIMITER.        L_DELIMITER_OFFSET = L_OFFSET.        L_DELIMITER_BYTES = L_BYTES.        "remember character in front of delimiter:        L_PREVIOUS_CHAR = L_LAST_CHAR.      ENDIF.    ENDIF.    L_OFFSET = L_OFFSET + L_BYTES.  ENDWHILE.

       

        IF NOT L_DELIMITER IS INITIAL.    L_OFFSET = L_DELIMITER_OFFSET + L_DELIMITER_BYTES.    L_FILE = PATH2+L_OFFSET.    IF L_PREVIOUS_CHAR = ':' OR L_DELIMITER = ':'.      "path ends with or after drive identifer, include the delimiter      L_DIR = PATH2(L_OFFSET).    ELSEIF NOT L_DELIMITER_OFFSET IS INITIAL.      "do not include a trailing delimiter for compatibility reasons      L_DIR = PATH2(L_DELIMITER_OFFSET).    ELSE.      CLEAR L_DIR.    ENDIF.  ELSE.    L_DIR = SPACE.    L_FILE = PATH2.  ENDIF.

       

        FILE_PATH = L_DIR.  FILE_NAME = L_FILE.

       

       

      **----
      1. -- set new file_path to SAP memory                                -- *
        FILE_PATH_LENGTH = STRLEN( FILE_PATH ).

       

        IF FILE_PATH <> SPACE AND FILE_PATH_LENGTH < 250.    FILE_PATH_MEMORY = FILE_PATH.    SET PARAMETER ID 'OAP' FIELD FILE_PATH_MEMORY.  ELSE.    FILE_PATH_MEMORY = SPACE.    SET PARAMETER ID 'OAP' FIELD FILE_PATH_MEMORY.  ENDIF.

       

       

      1. -- data declaration ------------------------------------------------ *
        DATA:        SINGLE_C TYPE C.
      1. -------------------------------------------------------------------- *
        CLEAR: SINGLE_C.

       

        LENGTH = STRLEN( FILE_NAME ).  IF LENGTH > 0.    WHILE LENGTH > 0.      SINGLE_C = FILE_NAME+LENGTH(1).      IF SINGLE_C CO '.'.        LENGTH = LENGTH + 1.        EXIT.      ELSE.        LENGTH = LENGTH - 1.      ENDIF.    ENDWHILE.    IF LENGTH > 0.      FILE_EXTENSION = FILE_NAME+LENGTH.    ELSE.      FILE_EXTENSION = SPACE.    ENDIF.  ELSE.    FILE_EXTENSION = SPACE.  ENDIF.

       

        IF FILE_EXTENSION <> SPACE.    SET LOCALE LANGUAGE SY-LANGU.    TRANSLATE FILE_EXTENSION TO UPPER CASE.              "#EC TRANSLANG    SET LOCALE LANGUAGE SPACE.  ENDIF.

       

       

        DATA: I_TOADV TYPE TABLE OF TOADV.  DATA: WA_TOADV TYPE TOADV.  DATA: OBJECT TYPE SAEOBJART.

       

        SELECT * FROM TOADV INTO CORRESPONDING FIELDS OF TABLE          I_TOADV WHERE STANDARD = 'X'.

       

        DELETE I_TOADV WHERE AR_OBJECT CS 'BDS' OR AR_OBJECT CS 'ZSEST'.

       

        READ TABLE I_TOADV INTO WA_TOADV WITH KEY DOC_TYPE = FILE_EXTENSION.*
      1. -- get name and description for the document                      -- *
        CLEAR: COMPID,         DESCRIPTION,         VERSION_NR,

       

               MODIFY_FLAG,         COMPID_FLAG,         ATTRI_FLAG,         TITLE_FLAG.

       

        COMPID_FLAG    = ' '.              " -> compid can't be changed  MODIFY_FLAG    = 'S'.              " -> popup in save modus  ATTRI_FLAG     = 'X'.              " -> attri's can be changed  COMPID         = FILE_NAME.  VERSION_NR     = '00000001'.  VERSION_LANGU2 = SY-LANGU.  TITLE_FLAG     = '1'.              " -> BDN

       

       

      1. -- get the mimetype of the docuclass                              -- *

       

        DATA: I_DOCUMENTCLASS TYPE TOADD-DOC_TYPE.  MOVE FILE_EXTENSION TO I_DOCUMENTCLASS.

       

        SET LOCALE LANGUAGE SY-LANGU.  TRANSLATE I_DOCUMENTCLASS TO UPPER CASE.               "#EC TRANSLANG  SET LOCALE LANGUAGE SPACE.

       

        SELECT SINGLE * FROM TOADD INTO I_TOADD WHERE                             DOC_TYPE EQ I_DOCUMENTCLASS.  IF SY-SUBRC NE 0.                    " nothing found -> default!    I_TOADD-DOC_TYPE = I_DOCUMENTCLASS.    I_TOADD-MIMETYPE = 'application/x-docType'.             "#EC NOTEXT  ENDIF.

       

        MOVE I_TOADD-MIMETYPE TO MIMETYPE.

       

      1. -- fill file and signature structure                              -- *
        CLEAR: I_FILES, I_SIGNATURE.  REFRESH : I_FILES, I_SIGNATURE.

       

        DESCRIPTION = FILE_NAME.

       

        FILES-DOC_COUNT = 1.  FILES-DIRECTORY = FILE_PATH.  FILES-FILENAME  = FILE_NAME.  FILES-MIMETYPE  = MIMETYPE.

       

        APPEND FILES TO I_FILES.

       

      1. -- fill components and signature structure                        -- *
        COMPONENTS-DOC_COUNT  = 1.  COMPONENTS-COMP_COUNT = 1.  COMPONENTS-MIMETYPE   = MIMETYPE.  COMPONENTS-COMP_ID         = FILE_NAME.

       

        APPEND COMPONENTS TO I_COMPONENTS.

       

        SIGNATURE-DOC_COUNT  = 1.  SIGNATURE-PROP_NAME  = 'BDS_DOCUMENTCLASS'.  SIGNATURE-PROP_VALUE = FILE_EXTENSION.  APPEND SIGNATURE TO I_SIGNATURE.

       

        SIGNATURE-PROP_NAME = 'BDS_CONTREP'.  SIGNATURE-PROP_VALUE = ' '.                               "#EC NOTEXT  APPEND SIGNATURE TO I_SIGNATURE.

       

        SIGNATURE-PROP_NAME  = 'BDS_DOCUMENTTYPE'.  SIGNATURE-PROP_VALUE = WA_TOADV-AR_OBJECT. "'ZSRVCXLS'."TR001+

       

        APPEND SIGNATURE TO I_SIGNATURE.

       

        SIGNATURE-PROP_NAME  = 'DESCRIPTION'.  SIGNATURE-PROP_VALUE = DESCRIPTION.

       

        APPEND SIGNATURE TO I_SIGNATURE.

       

        SIGNATURE-PROP_NAME  = 'LANGUAGE'.  SIGNATURE-PROP_VALUE = VERSION_LANGU.

       

        APPEND SIGNATURE TO I_SIGNATURE.

       

       

        CALL FUNCTION 'BDS_BUSINESSDOCUMENT_CREA_TAB'    EXPORTING      CLASSNAME       = BUS_OBJECT      CLASSTYPE       = 'BO'" classtype_select      CLIENT          = SY-MANDT      OBJECT_KEY      = OBJECT_KEY      BINARY_FLAG     = 'X'    TABLES      SIGNATURE       = I_SIGNATURE      COMPONENTS      = I_COMPONENTS      CONTENT         = I_FILE_CONTENT    EXCEPTIONS      NOTHING_FOUND   = 1      PARAMETER_ERROR = 2      NOT_ALLOWED     = 3      ERROR_KPRO      = 4      INTERNAL_ERROR  = 5      NOT_AUTHORIZED  = 6      OTHERS          = 7.

       

        COMMIT WORK AND WAIT.  IF SY-SUBRC <> 0.

      PERFORM BAPI_MESSAGE TABLES RETURN
      USING SY-MSGTY
      SY-MSGID
      SY-MSGNO
      SY-MSGV1
      SY-MSGV2
      SY-MSGV3
      SY-MSGV4
      SPACE
      0
      SPACE.

      ENDIF.

      ENDFUNCTION.



      Subroutines used:


      &----
      *&      Form  bapi_message&----
      1.       Add message to BAPI message table
      ----
      1.      <--P_RETURN     Error table
      2.      -->P_MSGTY      Message type
      3.      -->P_MSGID      Message id
      4.      -->P_MSGNO      Message number
      5.      -->P_MSGV1      Message variable 1
      6.      -->P_MSGV1      Message variable 2
      7.      -->P_MSGV1      Message variable 3
      8.      -->P_MSGV1      Message variable 4
      9.      -->P_PARAMETER  Parameter name
      10.      -->P_ROW        Line no in case the parameter is a table
      11.      -->P_FIELD      Field name within parameter structure
      ----
      FORM BAPI_MESSAGE TABLES   P_RETURN     STRUCTURE BAPIRET2                  USING    P_MSGTY      LIKE SY-MSGTY                           P_MSGID      LIKE SY-MSGID                           P_MSGNO      LIKE SY-MSGNO                           P_MSGV1                           P_MSGV2                           P_MSGV3                           P_MSGV4                           P_PARAMETER  LIKE BAPIRET2-PARAMETER                           P_ROW        LIKE BAPIRET2-ROW                           P_FIELD      LIKE BAPIRET2-FIELD.

       

      1. Enter message into system variables

       

        SY-MSGTY = P_MSGTY.  SY-MSGID = P_MSGID.  SY-MSGNO = P_MSGNO.  SY-MSGV1 = P_MSGV1.  SY-MSGV2 = P_MSGV2.  SY-MSGV3 = P_MSGV3.  SY-MSGV4 = P_MSGV4.

       

        CALL FUNCTION 'BALW_BAPIRETURN_GET2'    EXPORTING      TYPE      = SY-MSGTY      CL        = SY-MSGID      NUMBER    = SY-MSGNO      PAR1      = SY-MSGV1      PAR2      = SY-MSGV2      PAR3      = SY-MSGV3      PAR4      = SY-MSGV4      PARAMETER = P_PARAMETER      ROW       = P_ROW      FIELD     = P_FIELD    IMPORTING      RETURN    = P_RETURN.

       

        APPEND P_RETURN.

       

      ENDFORM.                    " bapi_message

      Let's look at the BSP Application which we used to upload the documents:

      Note: I didnt handle any validation in my BSP Application. Just kept in mind to attach the document & developed quick BSP Application :).

      1. Create below BSP Application & Pages as shown below:


      2. !https://weblogs.sdn.sap.com/weblogs/images/11786/BSP_attach_appl1.JPG|height=152|alt=image|width=357|src=https://weblogs.sdn.sap.com/weblogs/images/11786/BSP_attach_appl1.JPG|border=0!

      3. Layout:

      <htmlb:content id               = "content"               design           = "classicdesign2002design2003"               controlRendering = "sap"               rtlAutoSwitch    = "true" >  
      1. event handler for checking and processing user input and
      2. for defining navigation

       

      DATA: FILE        TYPE REF TO CL_HTMLB_FILEUPLOAD,      FILE_DETAIL TYPE SAPB-SAPFILES,      OBJKEY TYPE BAPIBDS01-OBJKEY,      RETURN  TYPE  BAPIRET2_T.

       

      FILE ?= CL_HTMLB_MANAGER=>GET_DATA( REQUEST = REQUEST ID = 'uploadID' NAME = 'fileUpload' ).

       

      V_KEY = REQUEST->GET_FORM_FIELD( 'keyid' ).V_BO = REQUEST->GET_FORM_FIELD( 'busid' ).

       

      CALL FUNCTION 'CONVERSION_EXIT_ALPHA_INPUT'  EXPORTING    INPUT  = V_KEY  IMPORTING    OUTPUT = V_KEY.

       

      OBJKEY = V_KEY.

       

      FILE_DETAIL = FILE->FILE_NAME.

       

      CALL FUNCTION 'ZATTACH_CREATE' DESTINATION 'NONE'  EXPORTING    FILE             = FILE_DETAIL           " file Name    STR_FILE_CONTENT = FILE->FILE_CONTENT    "File content in XSTRING format    OBJECT_KEY       = OBJKEY                "Key Field    BUS_OBJECT       = V_BO                  "Business object  TABLES    RETURN           = RETURN.


        1. Now we are done with the uploading documents to Business transaction.

        BSP/How To: Display/Delete attachment from Business transactions

        $
        0
        0
        * *Introduction: This blog describes how to display/delete the documents, which are stored in any SAP Business transactions like IE01, VA01, FB01 and WTY etc from BSP Application.

         

        If you have not read the [overview | Organize your business attachments from Web] & {code:html}BSP/How To: Create attachment in Business transactions{code}, I would recommend to read those before you continue this blog.

         

        Let's look at the outcome of this blog before we jump into the details.

         

        *Note:* We will display & delete those documents, which we created in {code:html}BSP/How To: Create attachment in Business transactions{code} of this series blog.{code:html}

         

        1. Let's say my notification number has 2 attachment as shown below.
        2. Using this BSP Application we will view those attachment in web.
        3. Once you execute the BSP Application, enter the Object Key Number for which you already created the attachment. In my case it's Notification.
        4. You will get the list of attachments based on your Object key.

        5. You can see 2 buttons at footer to display & delete the attachment. Select the one of the row & click on display button -
        6. The moment you click on display button, popup window will come up with attachment as shown below.
        7. Lets look at the delete functionality. Again select anyone of the line & click on delete button -
        8. Look at the updated attachment list below.
        h3. Let's look at the development steps:
        1. Create the BSP Application, let's name it as ZATTACH_DISPLAY with Page List.htm (Page With Flow Logic)
        2. I used Iterator methodology to display the tableview. Here are the steps:
          • Create the Iterator class; let's name it as ZCL_ATTACH_ITR.
          • Under the Interface Tab, mention "IF_HTMLB_TABLEVIEW_ITERATOR" as interface.
          • Place the below code in method GET_COLUMN_DEFINITIONS

            method IF_HTMLB_TABLEVIEW_ITERATOR~GET_COLUMN_DEFINITIONS.

            CLEAR p_column_definitions.
            CLEAR p_overwrites.

            DATA: tv_column TYPE TABLEVIEWCONTROL.
            tv_column-COLUMNNAME = 'FILE_DATE'.
            tv_column-sort = 'X'.
            tv_column-TITLE = 'Date'.
            tv_column-WIDTH = '70'.
            APPEND tv_column TO p_column_definitions.

            CLEAR tv_column.
            tv_column-COLUMNNAME = 'FILE_NAME'.
            tv_column-TITLE = 'File Name'.
            tv_column-WIDTH = '120'.
            APPEND tv_column TO p_column_definitions.
            endmethod.

        3. Create the below variable in page attribute:

        BSP/How To: HRFORMS in BSP Application

        $
        0
        0
        This blogs explains how to display the HRFORMS in PDF format from the BSP Application.   *Note:* I hope, this topic remains unresolved one so far. If there are any similar posts on this topic, please let me know, I will pull this one off.   Let's take the scenario of displaying the current Payslip for given employee. The payslip period and employee number are hardcoded in the program itself just for demo.   Let's look at the output of the BSP Application before we get into the technical details. *Prerequisite: *You need to know the driver program of HRFORMS & parameters to call the driver program.

         

        *Development Steps:*

         

        • Create the BSP Application with page name called "HRFORMS".

         

        • *Layout code:**OnInitialization:* * event handler for data retrieval CONSTANTS: C_DATASETNAME(6) TYPE C VALUE 'HRFORM', C_REPORTID(40) TYPE C VALUE '/1PYXXFO/ZUS_PAYSLIP'. DATA: IT_PARAMS TYPE TABLE OF RSPARAMS, WA_PARAMS TYPE RSPARAMS, PRINTPARAMETERS TYPE PRI_PARAMS, SPL_ID TYPE RSPOID, LISTNAME TYPE SYPLIST, IT_SPOOLREQ TYPE TABLE OF RSPORQ, WA_SPOOLREQ TYPE RSPORQ. FIELD-SYMBOLS TYPE X. DATA: NUMBER TYPE PERNR_D VALUE '4545885', PDF_TABLE TYPE TEXT_LINE_TAB, CONTENT TYPE TLINE, STRFILE_CONTENT TYPE STRING, FILE_XCONTENT TYPE XSTRING, LEN TYPE I, CACHED_RESPONSE TYPE REF TO IF_HTTP_RESPONSE, GUID TYPE GUID_32, MIMETYPE TYPE STRING, JOBNUMBER TYPE BTCJOBCNT, JOBNAME TYPE BTCJOB. REFRESH PDF_TABLE. *Set parameters for HRFORMS Driver program REFRESH IT_PARAMS. * Set the Personnel number WA_PARAMS-SIGN = 'I'. WA_PARAMS-OPTION = 'EQ'. WA_PARAMS-SELNAME = 'PNPPERNR'. WA_PARAMS-LOW = NUMBER. APPEND WA_PARAMS TO IT_PARAMS. WA_PARAMS-SELNAME = 'P_NOSTAT'. WA_PARAMS-LOW = 'X'. APPEND WA_PARAMS TO IT_PARAMS. WA_PARAMS-SELNAME = 'PNPBEGDA'. WA_PARAMS-LOW = '20081101'. APPEND WA_PARAMS TO IT_PARAMS. WA_PARAMS-SELNAME = 'PNPENDDA'. WA_PARAMS-LOW = '20081130'. APPEND WA_PARAMS TO IT_PARAMS. * Get print parameters CONCATENATE C_DATASETNAME SY-UZEIT INTO LISTNAME. CALL FUNCTION 'GET_PRINT_PARAMETERS' EXPORTING DATA_SET = C_DATASETNAME EXPIRATION = '1' IMMEDIATELY = SPACE LIST_NAME = LISTNAME NEW_LIST_ID = 'X' NO_DIALOG = 'X' MODE = 'BATCH' IMPORTING OUT_PARAMETERS = PRINTPARAMETERS EXCEPTIONS OTHERS = 4. CONCATENATE SY-UNAME 'HR_PAYSLIP' INTO JOBNAME. CALL FUNCTION 'JOB_OPEN' EXPORTING JOBNAME = JOBNAME IMPORTING JOBCOUNT = JOBNUMBER EXCEPTIONS CANT_CREATE_JOB = 1 INVALID_JOB_DATA = 2 JOBNAME_MISSING = 3 OTHERS = 4. * Call report in background mode SUBMIT (C_REPORTID) WITH SELECTION-TABLE IT_PARAMS TO SAP-SPOOL SPOOL PARAMETERS PRINTPARAMETERS WITHOUT SPOOL DYNPRO VIA JOB JOBNAME NUMBER JOBNUMBER AND RETURN. IF SY-SUBRC = 0. COMMIT WORK. CALL FUNCTION 'JOB_CLOSE' EXPORTING JOBCOUNT = JOBNUMBER JOBNAME = JOBNAME STRTIMMED = 'X' EXCEPTIONS CANT_START_IMMEDIATE = 1 INVALID_STARTDATE = 2 JOBNAME_MISSING = 3 JOB_CLOSE_FAILED = 4 JOB_NOSTEPS = 5 JOB_NOTEX = 6 LOCK_FAILED = 7 OTHERS = 8. IF SY-SUBRC <> 0. * Error RETURN. ENDIF. ELSE. * Error RETURN. ENDIF. * Generate PDF File DO. CALL FUNCTION 'RSPO_FIND_SPOOL_REQUESTS' EXPORTING RQ0NAME = C_DATASETNAME RQ2NAME = LISTNAME TABLES SPOOLREQUESTS = IT_SPOOLREQ EXCEPTIONS OTHERS = 0. READ TABLE IT_SPOOLREQ INTO WA_SPOOLREQ INDEX 1. IF SY-SUBRC EQ 0. SPL_ID = WA_SPOOLREQ-RQIDENT. CALL FUNCTION 'CONVERT_OTFSPOOLJOB_2_PDF' EXPORTING SRC_SPOOLID = SPL_ID NO_DIALOG = ' ' TABLES PDF = PDF_TABLE EXCEPTIONS ERR_NO_OTF_SPOOLJOB = 1 ERR_NO_SPOOLJOB = 2 ERR_NO_PERMISSION = 3 ERR_CONV_NOT_POSSIBLE = 4 ERR_BAD_DSTDEVICE = 5 USER_CANCELLED = 6 ERR_SPOOLERROR = 7 ERR_TEMSEERROR = 8 ERR_BTCJOB_OPEN_FAILED = 9 ERR_BTCJOB_SUBMIT_FAILED = 10 ERR_BTCJOB_CLOSE_FAILED = 11. IF SY-SUBRC NE 0. *Error EXIT. ENDIF. EXIT. " Exit from do..enddo loop ELSE. * Spool not updated, Try again ENDIF. ENDDO. LOOP AT PDF_TABLE INTO CONTENT. ASSIGN CONTENT TO CASTING TYPE X. CONCATENATE FILE_XCONTENT INTO FILE_XCONTENT IN BYTE MODE. ENDLOOP. * Assign the MIME Type. MIMETYPE = 'application/pdf'. LEN = XSTRLEN( FILE_XCONTENT ). * Display PDF CREATE OBJECT CACHED_RESPONSE TYPE CL_HTTP_RESPONSE EXPORTING ADD_C_MSG = 1. LEN = XSTRLEN( FILE_XCONTENT ). CACHED_RESPONSE->SET_DATA( DATA = FILE_XCONTENT LENGTH = LEN ). CACHED_RESPONSE->SET_HEADER_FIELD( NAME = IF_HTTP_HEADER_FIELDS=>CONTENT_TYPE VALUE = MIMETYPE ). CACHED_RESPONSE->SET_STATUS( CODE = 200 REASON = 'OK' ). CACHED_RESPONSE->SERVER_CACHE_EXPIRE_REL( EXPIRES_REL = 180 ). CALL FUNCTION 'GUID_CREATE' IMPORTING EV_GUID_32 = GUID. CONCATENATE RUNTIME->APPLICATION_URL '/' GUID INTO DISPLAY_URL. CL_HTTP_SERVER=>SERVER_CACHE_UPLOAD( URL = DISPLAY_URL RESPONSE = CACHED_RESPONSE ).     *Page attribute:*   0.1. This conclude the development steps.   0.1. If you want to check the spool request, goto Tcode SP01

        BSP With Workflow: Part - II

        $
        0
        0
        *Introduction:* This weblog explains how to launch the BSP Application from SAP Business Workplace via the workitem and Execute (Decision making) the Workitem from Web using BSP Application. Also it covers how to pass the parameters from SAP Inbox to BSP & vice versa.   If you haven't gone thru' the {code:html}BSP With Workflow: Part -I{code}, please have a look. We will be re-using some of the components from {code:html}BSP With Workflow: Part -I{code}.   I will be covering below steps in this blog:   * Testing of Service Handler. * Creating the Webflow Service for BSP & Generating the Web Task. * Linking the Web task into the workflow. * Developing the BSP to interact with webtask to carry out the decision making. * Testing   Let's look at the Step#1 & 2. In this step, we will be setting up the Webflow Service handler which is required to start & end the services.   0.1. Go to Transaction code WF_HANDCUST 0.2. The 'Launch Handler' tab shows the URL of Launch Handler. Maintain the value. Below is the example. 0.3. Just by clicking on '*Generate'* button, it will populate the values automatically.0.1. Click on *'Generate URL'* button, it will populate the URL as shown below. 0.1. Perform a test by clicking on *'Test URL'* button. It will open up a browser and make sure you don't receive any error. If you receive an error like 'forbidden', then activate the service WSHANDLER in Tcode SICF.0.1. Repeat the same step for 'Callback Handler' tab. We need to do these settings to receive the response back from BSP to workflow. 0.1. Configure the 'Callback Dialog' by clicking the 'Generate' & 'Generate URLs' buttons. 0.2. Make sure the services are working by clicking the 'Test URL' button at end of each URL. Activate the the service WSCB in Tcode SICF if you get a 'forbidden' error . 0.3. Configure the System Status & Client systems for handler as shown below. 0.4. The steps we did so far is one time settings. Don't have to repeat for each BSP Application/Workflow.   So far we have completed the Step#1 & 2. Let's look at the Step#3 - Customizing of Webflow Service. This is the place where we are going to link the BSP Application which needs to be called from the SAP Inbox.   At end of this step, the workflow Task (basically web task) will be generated, that is linked with our BSP application. This task will be plugged into our workflow as an approval step. Hope it's clear so far. Let's look at the steps:   0.1. Go to Tcode WF_EXTSRV and maintain the values. Below is the example. 0.2. Save & Double click on "Parameters" folder and mention the parameters which need to be passed back & forth between BSP & workitem. 0.3. Save & go to the previous screen and click on *"Generate Task"* button. In pop-up screen, follow the step. 0.1. Click on *"Continue"* button & Enter the Work Item text. This text can be changed in the task. 0.1. Click on "Continue" button and select the Logical System 0.1. Click on "Continue" button and end of the step will generate the task as shown below:   0.1. Plug the generated task into the workflow and do the Data Binding/Agent assignment. If you would like, you can customize the work item text with dynamic variable. 0.1. The data binding between the workflow & generated web task looks as shown below: 0.1. Here is the overview of workflow which is used in this example:   0.1. We're in the final step of the development & configuration. Here is the BSP application which will be called from SAP Inbox.    We'll be re-using the BSP application *YMY_LEAVE *from part-I to submit the leave*. *I created the BSP Application YDECISON/Decision.htm with below Layout/Page attribute/event handling code:

         

        *Layout:** OnInputProcessing:*

         

        Developing Content Rich Tool tips in CRM UI using jQuery

        $
        0
        0

        Tool tips showing at glance information of a particular object is one quick and easy time-saver that you can use in a web application. Ever wondered just hovering mouse on a link in your CRM UI will show the entire relationship linkage of an Account in the tool tip. This blog is about my experiences of building a content rich tool tip using jQuery in CRM UI.   Web based CRM applications such as salesforce.com provide fancy tool tips. When you hover on link for “Leads” on the Account Overview page, the tool tip shows the leads for this account in a fancy table and can be further navigated.   We had a similar request from our user community to build a fancy tool tip to show the entire Relationship Linkage of an Account on hovering over the link in Account Overview page in SAP CRM UI.   The only way I could think of building this feature was by using ready to use jQuery plugins in a BSP page. I started looking for jQuery plugins available for building fancy tool tips and found out this link which has 30 different plugins available. Out of all them clueTip seemed to fancy and simple to implement. Here are some links for further information:  

         

        Here is how the tool tip looks on the Accounts Overview page:    

         

        http://www.divulgesap.com/media/userfiles/flash/CRM_UI_Hover_Tooltip.swf

         

         

        CRM UI Configuration

         

         

         

        We are going to create a new empty view in the Accounts Overview page to display two links. When you hover on these links this tool tip is triggered. Here are the basic CRM UI configuration steps performed:

         

        1. Enhance the component BP_HEAD from transaction BSP_WD_CMPWB with your Enhancement set.
        2. Create a new empty view, say ZHOVER.
        3. In the Runtime Repository Editor add this new empty view ZHOVER to the ViewSet BP_HEAD/BPHEADOverview under the ViewArea OverviewPage.
        4. Now configure your viewset BP_HEAD/BPHEADOverview to display the empty view ZHOVER on the top Assignment block.

         

        Here is the screen shot of my Viewset assignment block configuration:

         

         

        We are done with the UI configuration now. When this empty view is created, the system creates a new BSP application ZBP_HEAD with a view ZHOVER.htm and a controller ZHOVER.do.

        Now you can work on this BSP application from SE80.

        First step is download jQuery and clueTip plugins from the links mentioned before. Include the following JS and CSS files required for displaying the tool tip into the MIME Repository of the BSP application.

         

        • jquery-1.4.2_min.js
        • jquery.cluetip.css
        • jquery.cluetip.js
        • jquery.hoverIntent.js (not mandatory)

        Here is how your BSP application will look:

         

         

        Another better approach is to add these files in MIME repository under path /SAP/PUBLIC/<FOLDER>. This way you can reuse these files in any BSP application.

         

        Now, in the ZHOVER.do controller class add a new method called GET_SELECTED_PARTNER. This method is used to retrieve the BP number displayed in the overview page. This method will have an exporting parameter EV_PARTNER TYPE BU_PARTNER. This method will be called from the view and BP number will be passed to the JS. Here is the code for this method:

         

         

        Now, let’s see the layout of the view “ZHOVER.htm”. This page will have two links “Relationship Linkage” and “Attachments”. When you mouse-over on “Relationship Linkage” link the content of the page “detail.htm” is shown as tooltip. In the same way the content of page “attachments.htm” is shown as tooltip for “Attachments” link.

         

        I have used HTMLB tree to display relationship linkage of an account in detail.htm page. My detail.htm page has an auto page attribute “param1” which is the BP number. The BP number is passed to this page as a URL parameter from ZHOVER.htm page. In the same the way attachments.htm page is developed using HTMLB tags. I have not included the coding for these pages. You can develop your our own detail.htm page.

         

        Here is complete coding for ZHOVER.htm page:

         

        <%@page language="ABAP" %><%@page language="abap" %><%@extension name="htmlb" prefix="htmlb" %><%@extension name="xhtmlb" prefix="xhtmlb" %><%@extension name="crm_bsp_ic" prefix="crmic" %><%@extension name="thtmlb" prefix="thtmlb" %><%@extension name="chtmlb" prefix="chtmlb" %><%@extension name="bsp" prefix="bsp" %><link href="/sap/bc/bsp/sap/zbp_head/jquery.cluetip.css" rel="stylesheet" type="text/css" /><link id="urstyle" href="/sap/public/bc/ur/Design2002/themes/sap_tradeshow/ur/ur_nn7.css?6.0.17.0.0" type="text/css" rel="stylesheet"></link><script src="/sap/bc/bsp/sap/zbp_head/jquery-1.4.2_min.js" type="text/javascript"></script><script src="/sap/bc/bsp/sap/zbp_head/jquery.hoverIntent.js" type="text/javascript"></script><script src="/sap/bc/bsp/sap/zbp_head/jquery.cluetip.js" type="text/javascript"></script><script src="/sap/public/bc/ur/Design2002/js/sapUrMapi_nn7.js?6.0.17.0.0" language="JavaScript" type="text/javascript"></script><script src="/sap/public/bc/ur/Design2002/js/popup_nn7.js?6.0.17.0.0" language="JavaScript" type="text/javascript"></script><script src="/sap/bc/bsp/sap/zbp_head/custom_scripts.js" type="text/javascript"></script><script type="text/javascript">
        var $j=jQuery.noConflict();
         function LoadMyJs(myElement){     $j(myElement).cluetip({     cluetipClass: 'jtip',     width: 450,     arrows: true,     attribute: 'rel',     dropShadow: false,     hoverIntent: false,     sticky: true,     mouseOutClose: true,     closePosition: 'title',     showTitle:false,     topOffset:85     });     }
        function populateIframe(id,path)
        {    var ifrm = document.getElementById(id);    ifrm.src = "/sap/bc/bsp/sap/zbp_head/data.htm?file_id="+path;
        }</script><%  data lv_partner type bu_partner.  lv_partner = controller->GET_SELECTED_PARTNER( ).
        %><a id="detail-link"   href="#"   rel="/sap/bc/bsp/sap/zbp_head/detail.htm?param1=<%= lv_partner %>"   onmouseover="LoadMyJs(this)" ><%-- LoadMyJs(this) --%><b>Relationship Linkage</b></a>&nbsp&nbsp&nbsp<a id="att-link"   href="#"   rel="/sap/bc/bsp/sap/zbp_head/attachments.htm?partner=<%= lv_partner %>"   onmouseover="LoadMyJs(this)" ><b>Attachments</b></a>

         

        Some notes on this page:

         

        1. Include all the scripts and styles with their full path as this page is going to be a fragment inside the overview page and the absolute path can’t be accessible at that level. For example instead of using href="jquery.cluetip.css" simply use full path href="/sap/bc/bsp/sap/zbp_head/jquery.cluetip.css".
        2. The way the clueTip creates the tooltip is, it just creates a “<div>” element in the Overview page with the detail.htm page content using jQuery Ajax calls. Also, as mentioned earlier, I had used htmlb:Tree tag in detail.htm page to display the Relationship view in a tree. As our “Overview” page is built with CRM UI specific tags such as thtmlb, tajax etc., it can’t render a htmlb:Tree. So we need to include the JS and CSS required for htmlb in “head” tag part of “hover” page. If some one has a better idea than including the JS and CSS files for HTMLB, please write them in the comments section of this blog.
        3. LoadMyJS() function calls the clueTip plugin to create tooltip. There are various options for clueTip which can be found in the link above. As you can observe anchor tags in this page have the required URL in “rel” attribute and the same is passed in options part of clueTip.
        4. For sticky tooltip I have used options sticky: true and mouseoutclose: true in the clueTip plugin.
        5. In order to avoid jQuery JS library to conflict with other HTMLB JS libraries, make sure jQuery.noConflict() is called.

         

        Another approach is to create a nice BSP extension using clueTip and jQuery. Use this BSP extension where ever you need to generate this kind of tooltip.

         

        Last but not the least, I would like to thank my lead Gautam Mandal who had given me the base idea for building this.

         

        I hope this blog will help SAP CRM UI developers to develop nice content rich tool tips in SAP CRM at par with other browser based CRM applications.


        How to implement JQuery autocomplete functionality in SAP BSP

        $
        0
        0

        If I had to guess a majority of people today do not realize the power within SAP BSP. In this blog I want to introduce some of this power by showing how to implement autocomplete functionality like google search.

         

        The first thing we need to do is create a new BSP application.

         

        1. Go to SE80, select BSP Application.

        Screen Shot 2012-10-08 at 10.55.58 AM.png

         

        2. Next create a controller class. Right click on your object and select create -> Controller

        Screen Shot 2012-10-08 at 10.58.46 AM.png

         

        Screen Shot 2012-10-08 at 11.00.18 AM.png

        3. Next enter a name for the ABAP class that will be associated with the index.do, double click on the name and the system will automatically create the class for you.

         

        Screen Shot 2012-10-08 at 11.01.44 AM.png

         

        4. Now we need to redefine the method do_request.

        5. Once we have the method redefined we can start adding code. For this example add the following lines of code

         

          data view type ref to if_bsp_page.

          me->view_name = 'index.htm'.

          view = me->create_view( view_name = me->view_name ).

         

          if view is bound.

            me->call_view( view ).

          endif.

         

        6. In order for this to get activated you need to add a class attribute called VIEW_NAME of type string. Activate your new class.

         

        Screen Shot 2012-10-08 at 11.11.06 AM.png

         

        7. Now we need to create the index.html view that we reference in the do_request method.

        8. Right-click on your application, choose create->page

         

        Screen Shot 2012-10-08 at 11.13.24 AM.png

        Screen Shot 2012-10-08 at 11.14.51 AM.png

         

        9. Now lets write some HTML and show the power of BSP!!

        10. First thing is to open the index.html and remove everything except the following tags at the top.

         

        <%@page language="abap"%>

        <%@extension name="htmlb" prefix="htmlb"%>

         

        11. Add the following code

         

        <%@page language="abap"%>

        <%@extension name="htmlb" prefix="htmlb"%>

        <!DOCTYPE html>

        <html>

          <head>

            <style type="text/css">

                body { background:url(bg.jpg);font-family:Lucida Sans, Arial, Helvetica, Sans-Serif; font-size:13px; margin:20px;}

                #main { width:960px; margin: 0px auto; border:solid 1px #b2b3b5; -moz-border-radius:10px; padding:20px; background-color:#f6f6f6;}

                #header { text-align:center; border-bottom:solid 1px #b2b3b5; margin: 0 0 20px 0; }

                fieldset { border:none; width:650px;}

                legend { font-size:18px; margin:0px; padding:10px 0px; color:#b0232a; font-weight:bold;}

                label { display:block; margin:15px 0 5px;}

                input[type=text], input[type=password] { width:300px; padding:5px; border:solid 1px #000;}

                //.prev, .next { background-color:#b0232a; padding:5px 10px; color:#fff; text-decoration:none;}

                //.prev:hover, .next:hover { background-color:#000; text-decoration:none;}

                //.prev { float:left;}

                //.next { float:right;}

                #steps { list-style:none; width:100%; overflow:hidden; margin:0px; padding:0px;}

                #steps li {font-size:24px; float:left; padding:10px; color:#b0b1b3;}

                #steps li span {font-size:11px; display:block;}

                #steps li.current { color:#000;}

                #makeWizard { background-color:#b0232a; color:#fff; padding:5px 10px; text-decoration:none; font-size:18px;}

                #makeWizard:hover { background-color:#000;}

                #shell {

                  width:850px;

                  height:auto;

                  background:white;

                  -moz-border-radius:6px;

                 }

         

         

                //#uploader { margin: 0 auto; }

                .info { text-align: center; padding: 50px 0; color: #666; font-family: Helvetica, Arial, sans-serif; }

                #runtime { text-transform: uppercase; }

                .info span { color: #81c468; }

                #LeftPane {

                  /* optional, initial splitbar position */

                  overflow: auto;

                }

                /*

                 * Right-side element of the splitter.

                */

         

         

                #RightPane {

                  padding: 2px;

                  overflow: auto;

                }

                .ui-tabs-nav li {position: relative;}

                .ui-tabs-selected a span {padding-right: 10px;}

                .ui-tabs-close {display: none;position: absolute;top: 3px;right: 0px;z-index: 800;width: 16px;height: 14px;font-size: 10px; font-style: normal;cursor: pointer;}

                .ui-tabs-selected .ui-tabs-close {display: block;}

                .ui-layout-west .ui-jqgrid tr.jqgrow td { border-bottom: 0px none;}

                .ui-datepicker {z-index:1200;}

                .rotate

                    {

                        /* for Safari */

                        -webkit-transform: rotate(-90deg);

         

                        /* for Firefox */

                        -moz-transform: rotate(-90deg);

         

                        /* for Internet Explorer */

                        filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=3);

                    }

         

         

                .error {

                    font: normal 10px arial;

                    padding: 3px;

                    margin: 3px;

                    background-color: #ffc;

                    border: 1px solid #c00;

                }

         

         

                label.error           { font-weight:normal;color:red;text-align:left;width:140px; padding-left:25px;

                                        background: transparent url(images/cancel.png) no-repeat scroll left; }

         

         

                input.button          { position:absolute; top:125px; left:120px; padding:3px 6px;

                                        border:2px solid #fff; margin:20px 0px 0px 0px; color:#3D7169;

                                        font-family:Verdana, Arial, Helvetica, sans-serif;

                                        background:#CCC; -moz-border-radius:5px; }

         

         

                input.button:hover    { background:#009FAA none repeat scroll 0% 0%; color:white; }

            </style>

         

            <link rel="stylesheet" href="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.23/themes/base/jquery-ui.css" type="text/css" />

            <link rel="stylesheet" href="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.23/themes/base/jquery.ui.all.css" type="text/css" />

         

            <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js"></script>

            <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.8.23/jquery-ui.min.js"></script>

            <script type="text/javascript" src="http://bp.yahooapis.com/2.4.21/browserplus-min.js"></script>

         

            <%= runtime->session_manager->header_script( ) %>

         

            <script type="text/javascript">

         

         

               $(document).ready(function(){

         

                   });

         

         

               $(function() {

                   $("#vendor").autocomplete({

                        source: function( request, response )

                        {

                          $.ajax(

                          {

                              url: "Vendorlookup.htm",

                              dataType: "json",

                              data: {term: request.term},

                              success: function(data){

                                          response($.map(data, function(item){

                                            return{

                                                label: item.name,

                                                id: item.id };}));}

                                                });

                        },

                        minLength: 2,

                        select: function(event, ui)

                        {

                            $('#vendor_id').val(ui.item.id);

                        }

                    });

               });

         

         

          </script>

         

         

            </head>

            <body leftmargin="0" marginwidth="0" topmargin="0" marginheight="0" offset="0">

              <center><table border="0" cellpadding="0" cellspacing="0" height="100%" width="100%" id="backgroundTable"><tr>

                  <td align="center" valign="top"><table border="0" cellpadding="0" cellspacing="0" width="800" id="templateContainer"><tr>

                     <td align="center" valign="top"><table border="0" cellpadding="0" cellspacing="0" width="800" id="templateHeader"><tr>

                        <td class="headerContent">

                          <script type="text/javascript"

                            src="http://jqueryui.com/themeroller/themeswitchertool/">

                          </script>

                          <div id="switcher"></div>

                        </td>

                        </tr></table></td></tr><tr>

                        <td align="left" valign="top">

         

         

                          <table border="0" cellpadding="0" cellspacing="0" width="800" id="templateBody">

                          <tr>

                            <td valign="top" class="bodyContent">

                             <table border="0" cellpadding="20" cellspacing="0" width="100%"><tr><td valign="top">

         

         

                              <div id="shell">

         

         

                                <label for="vendor">Vendor</label>

                                <input id="vendor" type="text" class="required"/>

                                <input id="vendor_id" type="hidden"  />

         

         

                              </div>

                               </td></tr></table></td></tr></table></td></tr><tr>

              <td align="center" valign="top"><table border="0" cellpadding="10" cellspacing="0" width="600" id="templateFooter">

              <tr><td valign="top" class="footerContent"></td></tr></table></td></tr></table><br /></td></tr></table></center>

            </body>

          </html>

         

         

        12. You need to import a background image. Right-click your BSP application and choose create->MIME Object->Import. Select the file and it will upload to the server.

         

        Screen Shot 2012-10-08 at 11.25.45 AM.png

         

        13. Now we need another view that will handle the autocomplete lookups.

        14. Right-click on view and choose create. Enter the values Page: VendorLookup.htm, pagetype = Page with Flow Logic

         

        Screen Shot 2012-10-08 at 11.29.09 AM.png

         

        15. Once the view is created remove everything from the layout tab.

         

        Screen Shot 2012-10-08 at 11.31.25 AM.png

         

        16. Now navigate to the Event Handler tab, choose OnRequest.

         

        Screen Shot 2012-10-08 at 11.32.33 AM.png

         

        17. Now we get to add the ABAP code to do the lookup and return the information back to the BSP page.

             

        data: ls_field type ihttpnvp,

              lt_field type tihttpnvp.

         

        data: q type string.

         

        DATA: it_vendor type STANDARD TABLE OF lfa1,

              wa_vendor like LINE OF it_vendor.

         

        DATA: json_string type string.

         

        TYPES: BEGIN OF t_vendor,

                  id type i,

                  NAME type NAME1_GP,

               END OF t_vendor.

         

        data: it_return type STANDARD TABLE OF t_vendor,

              wa_return type t_vendor.

         

        request->get_form_fields( CHANGING fields = lt_field ).

        loop at lt_field into ls_field.

          if ls_field-name = 'term'.

            q = ls_field-value.

          endif.

        ENDLOOP.

         

        CONCATENATE '%' q '%' into q.

         

        select LIFNR name1 name2 name3 from lfa1 into CORRESPONDING FIELDS OF TABLE it_vendor where name1 like q or name2 like q.

         

        if it_vendor is NOT INITIAL.

          CONCATENATE '[' json_string into json_string.

          data: count type i.

          count = 1.

         

          data: lines type i.

          DESCRIBE TABLE it_vendor LINES lines.

         

          loop at it_vendor into wa_vendor.

            data count_str type string.

            clear count_str.

         

            count_str = count.

            CONDENSE count_str.

         

            if lines > 1.

              if sy-tabix = lines.

                CONCATENATE json_string '{"id":"' wa_vendor-LIFNR '","name":"' wa_vendor-name1 '(' wa_vendor-lifnr ')' '"}' into json_string.

              else.

                CONCATENATE json_string '{"id":"' wa_vendor-LIFNR '","name":"' wa_vendor-name1 '(' wa_vendor-lifnr ')' '"},' into json_string.

              endif.

            else.

              CONCATENATE json_string '{"id":"' wa_vendor-LIFNR '","name":"' wa_vendor-name1 '(' wa_vendor-lifnr ')' '"}' into json_string.

            endif.

            count = count + 1.

          ENDLOOP.

         

           CONCATENATE  json_string ']' into json_string.

        else.

          json_string = '[{}]'.

        endif.

         

          CALL METHOD _M_RESPONSE->IF_HTTP_ENTITY~SET_CDATA

            EXPORTING

              DATA = json_string.

         

        18. Now activate everything and give it a try. Right-click on index.do and choose test. this will open the browser.

         

        Start typing a vendors name and you should see something like...

         

        Screen Shot 2012-10-08 at 11.49.07 AM.png

        An XML Format for Ajax Response Messages

        $
        0
        0

         

        This blog is a prelude for a planned series of blogs on how to design web applications served by RESTful services on an ABAP Web AS. This first part is about a sufficiently generic XML format for carrying informations between client and server.

         

        The 'X' in Ajax

        It is almost forgotten that the 'X' in Ajax stands for XML. In the early times of the Ajax euphoria, XML has been thought as the general carrier format for messages. Ajax was planned as a pairing of "Asynchronous JavaScript" with XML. While "asynchronous JavaScript" addressed the XMLHTTPRequest object as the tool for data transfer, XML was thought to be the language for transmitting data between client and server.

         

        But in those times, the browsers weren't really prepared to easily parse, build and transform XML. XML quickly lost its terrain in favour of other data formats. Nowadays, the herds have moved on, favorizing JSON, and I join in the voices praising the advantages of this data format. Even ad hoc plain text formats are in fashion, built and parsed on the fly with basic string manipulation commands and regular expression search.

         

        So Why Bother With XML?

         

        First of all,  in the meantime we have mature XML tools on the client as well as on the server side. With a framework like Sarissa, we have all the XML power on the client: we can build and transform XML documents - and convert them into HTML fragments.

         

        This is the second advantage: XML being conceptionally and notationally close to HTML, makes it easy to import XML fragments into the current HTML DOM. Also, the APIs for manipulating or traversing elements share many methods and properties like e.g. getElementsByTagName(), childNodes, firstChild, nodeType, and so on. This is particularly helpful with XHTML documents, but works equally well with proper HTML.

         

        On the server side, it is easy to integrate an XSLT or ST transformation into an ABAP based web service: After having gathered all the necessary ABAP data for the response, you may throw them into a CALL TRANSFORMATION and directly receive an XML document in the desired target format. See below for an example.

         

        The Ajax Response Format

         

        In an Ajax based web application, there should be not too much overhead for the data transfer protocol. Employing a protocol like SOAP would clearly be overdone. On the other hand, it makes sense to design some simple cross-application container format for all the involved web services of a larger web application project.

         

        In contrast to SOAP, the "enevelope" part of such a message should be reduced to an absolute minimum. Basically, just a root node, let's call it <ajax>...</ajax> should be required - wrapping it all to meet the "One Root Element" criterion of XML well-formedness.

         

        I found that, apart from the common root element, say <ajax>, it makes sense to have one particular defined child element <message>, containing a message accompanying the response.

         

        Concerning the rest of the document, it is not necessary to impose any restrictive criteria. Virtually everything is possible - from nothing to a complex nested structure which fits best to a particular request.

         

        The following may serve as an example:

         

        <ajax>  <message type="S" text="3 countries selected"/>  <country value="ES">Spain</country>  <country value="FR">France</country>  <country value="IT">Italy</country></ajax>

         

        We have a <message> element with a message type - the ABAP message types (Success, Information, Warning, Error, Abort, and X for failed assertions) serving for the allowed values - and an attribute for the message text itself. Following to this <message> element, there is a series of <country> elements.

         

        Depending on the web service's domain, we should be completely free to design the response format so that it fits best for transferring the relevant data to the client.

         

        Another example could be the confirmation response after a new order has been created:

         

        <ajax>  <message type="S" text="Order saved"/>  <order number="4711"/></ajax>

         

        In such an example, the service could merely pass the new number of the created sales order to the client. The rest of the order data are on the client anyway, since the user had requested to create an order out of these data. So there is no need to transmit all these data back again from the server to the client.

         

        There is one type of child elements which makes sense for a <message>: The service may indicate one or more of the query parameters ("fields") that are connected to the message. So a list of <field> elements with obligatory name attribute (and an optional value attribute) may be included in the <message>. The web client could use this information to highlight an input field which had erroneous input:

         

        <ajax>  <message type="E" text="PACHOLKE is not a known user">     <field name="user"/>  </message></ajax>

         

        The preceding example also illustrates that an Ajax Response may carry no application data at all.

         

        But likewise, it may be the message that is omitted:

         

        <ajax>  <requisitionCode>1002</requisitionCode></ajax>

         

         

        The Class of Ajax Responses

         

        XML schema is a language for describing classes of XML documents. We can use it to give a formal description of all Ajax Responses, i.e. XML documents with the data structure exposed above:

         

        <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"><!-- Name of the root element is 'ajax' -->  <xs:element name="ajax" type="ajaxResponse"/><!-- Content: [ <message>? , anyElement* ] -->      <xs:complexType name="ajaxResponse">    <xs:sequence>      <xs:element name="message"                   minOccurs="0"                   maxOccurs="1"                   type="message"/>      <xs:any processContents="lax"              notQName="##definedSibling"              minOccurs="0"               maxOccurs="unbounded"/>    </xs:sequence>  </xs:complexType><!-- A <message> has a type and a text attribute     It may contain <field> child elements -->    <xs:complexType name="message">    <xs:sequence>      <xs:element name="field" type="field" minOccurs="0"/>    </xs:sequence>    <xs:attribute name="type" type="msgType"/>    <xs:attribute name="text" type="xs:string"/>  </xs:complexType><!-- A field element has a 'name' and an optional 'value' -->  <xs:complexType name="field">    <xs:attribute name="name"  use="required" />    <xs:attribute name="value" use="optional"/>      </xs:complexType><!-- msgType copies the allowed values auf ABAP's SY-MSGTY -->    <xs:simpleType name="msgType">    <xs:restriction base="xs:string">      <xs:enumeration value="S"/>                      <xs:enumeration value="I"/>                      <xs:enumeration value="W"/>                      <xs:enumeration value="E"/>                      <xs:enumeration value="A"/>                      <xs:enumeration value="X"/>                    </xs:restriction>  </xs:simpleType></xs:schema>

         

        This XML Schema describes all the rules mentioned above:

         

        1. The root element has the name "ajax",
        2. It contains 0 or 1 <message> as children, and after that arbitrary many elements of whatever name and type, but no further <message> element.
        3. The message has an attribute type, containing one of the allowed values for an ABAP message type, and a text attribute for the message itself.
        4. The message may contain a sequence of <field> elements, denoting the fields that are connected to the message.

         

        The only non-straightforward part in this XML scema document is the exclusion of further message elements in the <xs:any> element set. Actually, for implying this restriction, I use the attribute (thanks to Michael Kay for pointing me to this solution):

         

        notQName = "##definedSibling"

         

        This attribute of the <xs:any> element is precisely what's necessary here. It excludes all the defined siblings (here: just <message>) from the allowed element names. While the rest of the schema would be a valid XSD 1.0 document, this attribute notQName came with XSD 1.1. I don't see a way of describing the document structure purely with XSD 1.0 means.

         

        Testing Document Instances

         

        There is an online tool for Schema-based XML validation. This is very helpful for a quick test whether a certain XML document is a valid instance of a given schema.

         

        However, when using the online tool with the above schema, you would be disappointed: The online service is still on XSD 1.0, it can't interpret the schema. As far as I know, there is no online validator for XSD 1.1 (as of this writing).

         

        However, if you have Java on your computer, you can install the Apache Xerces tools. On the Xerces download page, you can download the binaries for the current release (2.11). Be careful to choose a version containing the XSD 1.1 support (they are marked). Copy the eight JAR files contained in that package to the folder <Java>/jre/lib/endorsed, where <java> is the path to your Java SDK (or to your Java Runtime). If such a folder "endorsed" does not exist yet in /jre/lib: create it.

         

        Once you have these JAR files in the JRE endorsed directory, the Xerces XSD 1.1 Schema validator can be used. There is a test class jaxp.SourceValidator coming with the distribution. You can use it from the command line. See here an example with an intentionally wrong XML document (it contained two message elements, where the AjaxResponse format, as explained, only allows one):

        validate.png

         

        We don't need the XSD at runtime. But in any case it's good to have a purely formal description of the exchanged data. For example, such a description proves useful for unit tests on the web services. Simple tests asserting that the output is an Ajax Response helps avoiding bugs when the services are extended or otherwise modified.

         

        On the ABAP Side

         

        I have designed some test services which I will further discuss in the following blogs.

         

        One of these services retrieves, on a GET request, all the variants of a report with the given report name. Here is an example call:

         

        http://bsp.mits.ch/buch/job/variants?repid=RSNAST00

         

        When calling it, you will retrieve an answer like this:

         

        <?xml version="1.0" encoding="iso-8859-1"?><ajax>  <report name="RSNAST00">    <variant name="I18730" text="WFMC send external address"/>    <variant name="IMMNAST00" text="NAST send fax immediately"/>    <variant name="SAP_SDB" text="Security data sheet dispatch"/>       <variant name="UXPD_NAST_ZMVN" text="Purchase Order Copy, MVN"/>    <variant name="UXPD_NAST_ZTIN" text="Purchase Order Copy, ZTIN"/>    <variant name="UXPY_NAST_NAB1" text="Yearly invoice, NAB"/>    <variant name="VERA_001" text="Test scheduled RSNAST00"/>    <variant name="ZBA1_20110708" text="Order confirmation, re-send"/>  </report></ajax>

         

        This obviously conforms with the above definition of an Ajax Response. It contains not more than the demanded data: The variant IDs, and their texts. The application will use precisely this response for filling a listbox, and this Ajax cycle will be triggered on any change of the field containing the report name.

         

        To serve the request, a stateless web service in the ABAP Web AS is called. For the given report name lv_repid, it gathers the relevant data (VARID / VARIT) into an internal table lt_vars. If necessary, the message fields lv_message and lv_msgty are provided:

         

           lv_repid = server->request->get_form_field( 'repid' ).   if lv_repid eq space.
         * Please enter a report name     message e798(db) into lv_msg.     lv_msgty = 'E'.   else.     try.         call method check_for_variants           exporting             iv_repid = lv_repid           importing             et_vars  = lt_vars.         if lt_vars is initial.
         * No variants found           message i260(db) into lv_msg.           lv_msgty = 'I'.         endif.       catch zcx_error into lo_ex.         lv_msg = lo_ex->get_text( ).         lv_msgty = 'E'.     endtry.   endif.

         

        When all the ABAP data are determined, they are converted into XML data. On the ABAP side, this is accomplished by precisely one statement:

         

        * Transform variants and/or message into Ajax Response format    call transformation zvariants         source            variants              = lt_vars           message           = lv_msg           message_type = lv_msgty           repid                   = lv_repid         result            xml lv_result.

         

        Of course, the actual work of transforming is performed inside the XSLT transformation - here: zvariants.

         

        In the following code of transformation zvariants, observe that it is possible to write the rules in a "stepdown" manner - beginning with the overall document structure, and then going down to the details one by one - as a composition of small templates with easy and single functions.

         

        Also note that readability increases when using the curly brace notation for evaluation inside of attribute values of the target document:

         

        <xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform"                              xmlns:asx="http://www.sap.com/abapxml"                             exclude-result-prefixes="asx"                             version="1.0">   <!--     Gives all the variants of a report, in the Ajax Response format     Data are taken from internal table VARIANTS     MESSAGE and MESSAGE_TYPE contain a message (optional)     -->   <xsl:strip-space elements="*"/><!-- Main template: Message and Variants -->   <xsl:template match="asx:abap/asx:values">     <ajax>       <xsl:call-template name="message"/>       <xsl:apply-templates select="VARIANTS"/>     </ajax>   </xsl:template><!-- Message, if given -->   <xsl:template name="message">     <xsl:if test="not ( MESSAGE_TYPE = '' )">       <message type="{MESSAGE_TYPE}" text="{MESSAGE}">         <field name="repid"/>       </message>     </xsl:if>   </xsl:template><!-- Variants -->   <xsl:template match="VARIANTS"><!-- Only if there are variants -->     <xsl:if test="*">       <report name="{../REPID}">         <xsl:apply-templates select="*"/>       </report>     </xsl:if>   </xsl:template><!-- A single line of the variant table -->   <xsl:template match="RSVARTXT">     <variant name="{VARIANT}" text="{VTEXT}"/>   </xsl:template></xsl:transform>

         

        In this form, such a request is served on a modestly tuned SAP system (actually, it is one of our team's development systems) with an average ABAP response time of about 20 milliseconds, as the following screenshot from STAD shows:

         

        stad.png

         

        Exploiting the buffering for commonly used database resources, the gross share of the response time is CPU time - a stateless web service of this type will scale very well. In comparison, a request-response cycle of even a minimalistic Business Server Page requires a factor 5 - 10 higher ABAP response time. Also, a BSP navigation usually results in a reload of  the complete web page, whereas Ajax techniques can be used to exchange only small parts of the page, which additionally increases overall performance by reducing the client-side response time.

         

        This is not to speak against Business Server Pages - actually, BSP and Ajax complement each other very well. BSP can be used to manage the application's ressources (HTML, CSS, JavaScript, XML fragments, client-side XSLTs) and to generate the pages to be loaded from the server. As soon as they are loaded, further interaction can be handled by Ajax requests.

         

        Conclusion

         

        From the first spread of Ajax, XML was planned as data transfer format for the client-server communication. Although JSON is usually favorized today, there are good arguments for using XML: There are reliable tools on client as well as on server side to work with XML data. Also, the similarity of XML DOM to HTML DOM makes XML a good choice.

         

        Once the decision is made to work with XML for Ajax-enabled web applications, it is useful to base all the services involved on a sufficiently general common Ajax Response format. Since Ajax is used for many small requests, such a format should be as loose and as minimalistic as possible. This blog detailed a proposal.

        BSP issues with Windows 8

        $
        0
        0

        Hi Guys,

         

        We are at SAP Netweaver level 7.01,  Service Pack 5 and had developed several ABAP based BSP MVC BSP applications.

         

        We are having issues with Windows 8. All BSP pages doesn't work on users with Windows 8,  Then I've had a look at what SAP says about support for Windows 8 in the Product Availability Matrix (PAM) and it shows BSP is not supported at all ?!

         

        Do you aware of this ? if so, how you support your customers ?

        regards,

        Kondal Reddy

        JSON Serializer Class for ABAP

        $
        0
        0

        As web, mobile applications and cloud computing has become main trends, SOA (service oriented architecture) become core at applications. Morever, business demands more responsive web and mobile applications. The standard post-back based web applications has already been outdated. AJAX (Asynchronous JavaScript and XML) become inevitable. Script libraries like JQuery made web pages look like desktop applications and has risen the expectations of business from IT. So much things has been changed but client-server architecture has not been changed. We still write ABAP code in BSP web applications. Whatever we do in user interface in our web applications we still have to communicate with backend systems. AJAX gives us a big advantage at that point. We can consume web services and call this services from client-side scripts in our BSP Applications. We can write services that returns JSON objects and this JSON objects can be used in client-side javascripts easily.

         

        In this article I will introduce the JSON serializer class that I have written. JSON Serializer class simply serializes any ABAP object with all attributes to JSON object.

         

        What is JSON?

         

        JSON is syntax for storing and exchanging text information. Much like XML. JSON is smaller than XML, and faster and easier to parse (http://www.w3schools.com/json/). Briefly we can say that JSON is string represantation of an object

         

        As an example consider we have a class ZJSONINPUT. It has an attribute FLIGHT as a table:

         

         

        data : lrf_zinput type ref to ZJSONINPUT,

               lrf_json_processor type ref to Z_CL_JSON_PROCESSOR.

         

        CREATE object LRF_ZINPUT.

         

        "Fill some data to input

         

        data lt_flight type FLIGHTTAB.

        SELECT * FROM SFLIGHT up to 2 rows INTO table lt_flight.

         

         

        data lt_flight type FLIGHTTAB.

        SELECT * FROM SFLIGHT up to 2 rows INTO table lt_flight.

        lrf_zinput->flight[] = lt_flight[].

         

         

         

        JSON equivalent of the class is:

         

        {

        "FLIGHT":

        [

        {"MANDT":"160","CARRID":"AA","CONNID":"0017","FLDATE":"20041007","PRICE":"1413.93 ","CURRENCY":"USD","PLANETYPE":"747-400","SEATSMAX":"660 ","SEATSOCC":"157-","PAYMENTSUM":"0.00 ","SEATSMAX_B":"0 ","SEATSOCC_B":"0 ","SEATSMAX_F":"0 ","SEATSOCC_F":"0 "},

        {"MANDT":"160","CARRID":"AA","CONNID":"0017","FLDATE":"20041008","PRICE":"1413.93 ","CURRENCY":"USD","PLANETYPE":"747-400","SEATSMAX":"660 ","SEATSOCC":"115-","PAYMENTSUM":"57433.79 ","SEATSMAX_B":"0 ","SEATSOCC_B":"0 ","SEATSMAX_F":"0 ","SEATSOCC_F":"0 "}

        ]

         

        }

        As you see it's very simple. General syntax is  "key":"value". Anyway you don't need to learn details of json with my json serializer class.

         

        JSON SERIALIZER CLASS

        To convert ABAP Class to JSON I ve created class Z_CL_JSON_PROCESSOR. Z_CL_JSON_PROCESSOR loads the abap object in its class constructor. In it's SERIALIZE method, it serialize the load abap object instance that's going to be serialized to  JSON string.

         

        Attributes:

         

        Attribute
        Level
        Visibility
        TypeDescription
        DATA_REFInstance AttributePublicType ref to objectAttribute to store abap object instance

         

        Methods:

         

        • Constructor:

        method CONSTRUCTOR

        importing

              DATA type ref to OBJECT .

          me->data_ref = data.

        endmethod.


        • Serialize: Convert abap object to JSON string

        method SERIALIZE

        exporting

              E_JSON type STRING .

        CALL  method RECURSE

           exporting

             DATA   = me->DATA_REF

           importing

             E_JSON = E_JSON

           .

         

         

        concatenate '{' E_JSON '}' into E_JSON.

        endmethod.

         

        • Recurse: It's called by the SERIALIZE method. Attributes of the classed is iterated and returned to sub JSON parts. Since the class that's going to be serialized may contain other classes this method is called recursively.

        method RECURSE

        importing

              DATA type ref to OBJECT

            exporting

             E_JSON type STRING .

         

          "Get attributes of class

          DATA: l_typedesc TYPE REF TO cl_abap_typedescr.

          data: l_classdesc TYPE REF TO cl_abap_classdescr.

          data: l_attribute TYPE abap_attrdescr.

          data: l_structdesc type ref to CL_ABAP_STRUCTDESCR,

                l_tabledesct type ref to CL_ABAP_TABLEDESCR,

                l_table_line type ref To cl_abap_structdescr.

          data lv_datatype type abap_typekind.

          FIELD-SYMBOLS : <att> TYPE ANY ,

                         <componentvalue> type any,

                         <fs_table> type standard table,

                         <tableline> type any,

                         <fieldval> TYPE ANY..

         

         

          data lrf_descr  Type Ref To CL_ABAP_TYPEDESCR.

          data lv_temp_json type string.

          data : lt_component_table TYPE ABAP_COMPONENT_TAB,

                 ls_component_table type  abap_componentdescr.

          field-symbols <key_comp_wa> TYPE abap_keydescr.

          data lv_itemval type string.

         

         

          CALL METHOD cl_abap_classdescr=>describe_by_object_ref

            EXPORTING

              p_object_ref = DATA

            RECEIVING

              p_descr_ref  = l_typedesc.

         

         

          l_classdesc ?= l_typedesc.

         

         

          LOOP AT L_CLASSDESC->ATTRIBUTES into l_attribute.

            CLEAR LV_TEMP_JSON.

            ASSIGN DATA->(L_ATTRIBUTE-NAME) TO <att>.

            IF <att> IS assigned.

              "Get type of attribute

              "lv_datatype = CL_ABAP_DATADESCR=>GET_DATA_TYPE_KIND( P_DATA =  <att> ).

              lrf_descr ?= CL_ABAP_TYPEDESCR=>DESCRIBE_BY_DATA( P_DATA = <att> ).

              IF LRF_DESCR IS NOT initial.

         

         

                "Loop attributes of class

                CASE LRF_DESCR->KIND.

                  WHEN CL_ABAP_TYPEDESCR=>KIND_REF."IF it is class run recurce again

                    data lv_classjson type string.

                    clear lv_classjson.

                    CALL METHOD me->RECURSE

                      EXPORTING

                        DATA   = <att>

                      IMPORTING

                        E_JSON = LV_TEMP_JSON.

                    concatenate '"' L_ATTRIBUTE-name '":{'  LV_TEMP_JSON '}' into lv_classjson.

                    IF E_JSON IS initial.

                      concatenate e_json LV_CLASSJSON  into e_json.

                    ELSE.

                      concatenate e_json ',' LV_CLASSJSON  into e_json.

                    ENDIF.

         

         

                  WHEN CL_ABAP_TYPEDESCR=>KIND_ELEM. "if it is elementary type add it to json

                    move <att> to lv_itemval.

                    CALL METHOD me->ESCAPE_CHARS

                      CHANGING

                        C_TEXT = LV_ITEMVAL.

                    data lv_elemjson type string.

                    clear lv_elemjson.

         

         

                    concatenate '"' L_ATTRIBUTE-name '":"' lv_itemval '"' into LV_ELEMJSON.

                    IF E_JSON IS initial.

                      concatenate e_json lv_elemjson  into e_json.

                    ELSE.

                      concatenate e_json ',' LV_ELEMJSON  into e_json.

                    ENDIF.

                  WHEN CL_ABAP_TYPEDESCR=>KIND_STRUCT."if it's structure loop throught the components of structure

                    data: lv_structjson  type string,

                    lv_compjson type string.

                    clear : lv_structjson,lv_compjson.

                    l_typedesc = CL_ABAP_STRUCTDESCR=>DESCRIBE_BY_DATA( P_DATA = <att> ).

                    l_structdesc ?= l_typedesc.

                    LT_COMPONENT_TABLE = L_STRUCTDESC->GET_COMPONENTS( ).

                    concatenate '"' L_ATTRIBUTE-name '":{' into LV_STRUCTJSON.

                    LOOP AT LT_COMPONENT_TABLE into LS_COMPONENT_TABLE.

         

         

                      ASSIGN component LS_COMPONENT_TABLE-NAME  of structure <att> to <COMPONENTVALUE>.

                      move <COMPONENTVALUE> to  lv_itemval.

                      CALL METHOD me->ESCAPE_CHARS

                        CHANGING

                          C_TEXT = LV_ITEMVAL.

                      IF lv_compjson IS initial.

                        concatenate lv_compjson '"' LS_COMPONENT_TABLE-NAME  '"' ':' '"' lv_itemval '"' into lv_compjson.

                      ELSE.

                        concatenate lv_compjson ',"' LS_COMPONENT_TABLE-NAME  '"' ':' '"' lv_itemval '"' into lv_compjson.

                      ENDIF.

                    ENDLOOP.

                    concatenate LV_STRUCTJSON lv_compjson into LV_STRUCTJSON.

                    concatenate LV_STRUCTJSON '}' into LV_STRUCTJSON.

                    IF e_json is initial.

                      concatenate e_json LV_STRUCTJSON into e_json.

                    ELSE.

                      concatenate e_json ',' LV_STRUCTJSON into e_json.

                    ENDIF.

         

         

                  WHEN CL_ABAP_TYPEDESCR=>KIND_TABLE.

                    data : lv_tablejson type string,

                           LV_COLUMNJSON type string,

                           lv_linejson type string.

                    CLEAR : LV_TABLEJSON,LV_COLUMNJSON,LV_LINEJSON.

                    l_typedesc = CL_ABAP_TABLEDESCR=>DESCRIBE_BY_DATA( P_DATA = <att>  ).

                    L_TABLEDESCT ?= L_TYPEDESC.

                    DATA lv_without_column type boolean_flg.

                    clear lv_without_column.

                    TRY .

                        l_table_line ?= L_TABLEDESCT->GET_TABLE_LINE_TYPE( ).

                      CATCH CX_SY_MOVE_CAST_ERROR.

                        lv_without_column = 'X'.

                    ENDTRY.

         

                      concatenate '"' L_ATTRIBUTE-name '":[' into lv_tablejson.

                    ASSIGN  <att> to <fs_table>.

                    data lwa_comp TYPE abap_compdescr.

                    LOOP AT <fs_table> assigning <TABLELINE>.

                      CLEAR LV_COLUMNJSON.

                      IF lv_without_column IS INITIAL.

                        LOOP AT L_TABLE_LINE->COMPONENTS into lwa_comp.

                          ASSIGN COMPONENT lwa_comp-name OF STRUCTURE <TABLELINE> TO <fieldval>.

                          move <FIELDVAL> to LV_ITEMVAL.

                          CALL METHOD me->ESCAPE_CHARS

                            CHANGING

                              C_TEXT = LV_ITEMVAL.

                          IF LV_COLUMNJSON is initial.

                            concatenate LV_COLUMNJSON '"' lwa_comp-name  '":"' LV_ITEMVAL '"' into LV_COLUMNJSON.

                          ELSE.

                            concatenate LV_COLUMNJSON ',"' lwa_comp-name  '":"' LV_ITEMVAL '"' into LV_COLUMNJSON.

                          ENDIF.

                        endloop.

         

                      ELSE. "Without column name

                        MOVE <TABLELINE> TO lv_itemval.

                        IF LV_COLUMNJSON is initial.

                          concatenate LV_COLUMNJSON '"VAL":"' LV_ITEMVAL '"' into LV_COLUMNJSON.

                        ELSE.

                          concatenate LV_COLUMNJSON ',"VAL":"' LV_ITEMVAL '"' into LV_COLUMNJSON.

                        ENDIF.

                      ENDIF.

         

                      IF LV_LINEJSON is initial.

                        concatenate LV_LINEJSON '{' LV_COLUMNJSON '}' into lv_linejson.

                      ELSE.

                        concatenate LV_LINEJSON ',{' LV_COLUMNJSON '}' into lv_linejson.

                      ENDIF.

                    endloop.

         

                    concatenate LV_TABLEJSON LV_LINEJSON  ']' into LV_TABLEJSON .

                    IF E_JSON IS initial.

                      concatenate E_JSON LV_TABLEJSON into e_json.

                    ELSE.

                      concatenate E_JSON ',' LV_TABLEJSON into e_json.

                    ENDIF.

         

         

                ENDCASE.

              ENDIF.

            ENDIF.

          endloop.

         

         

        endmethod.

         

         

        • Escape_chars: Replaces special characters in JSON text with escape characters

        method ESCAPE_CHARS.

            changing

              !C_TEXT type STRING .

         

          REPLACE ALL occurrences of '"' IN C_TEXT with '#"#"'.

          REPLACE ALL occurrences of '\' IN C_TEXT with '#\#'.

         

         

          REPLACE ALL occurrences of '#"#"' IN C_TEXT with '\\"'.

          REPLACE ALL occurrences of '#\#' IN C_TEXT with '\\u005C'.

         

        endmethod.

         

         

         

        To see in action: First we must create a BSP page (page with flow logic). In BSP Page first we create the object that we want to serialize and fill with data:

         

        <%

        data : lrf_zinput type ref to ZJSONINPUT,

               lrf_json_processor type ref to Z_CL_JSON_PROCESSOR.

         

        CREATE object LRF_ZINPUT.

         

        data lt_flight type FLIGHTTAB.

        SELECT * FROM SFLIGHT up to 2 rows INTO table lt_flight.

         

         

        lrf_zinput->flight[] = lt_flight[].

         

        lrf_zinput->caption = ' "Json Serialize \Demonstration '

        %>

         

        After that we create our serializer class instance and load our object to be serialized and call serializee method. SERIALIZE Method will return JSON text

         

        <%

        Create object LRF_JSON_PROCESSOR

          exporting

            DATA = lrf_zinput.

         

        call method LRF_JSON_PROCESSOR->SERIALIZE

          importing

            E_JSON = lv_json.

         

        %>

         

        Now we have the JSON text. What we need to do is to pass the JSON text Javascript and use the object in Javasript. We need some Javascript coding. I used JQuery so we have to reference jquery in our BSP page. jQuery.parseJSON will return it to javascript object. After that we can access the atrributes in the abap class from javascript. For example, obj.CAPTION will return the CAPTION that we set in ABAP (serverside). Likely, obj.FLIGHT[0].CARRID will return the CARRID of the first row of FLIGHT table. As you see we can access the server side object from client side!

         

         

        <link rel="stylesheet" href="http://code.jquery.com/ui/1.10.2/themes/smoothness/jquery-ui.css" />      <script src="http://code.jquery.com/jquery-1.9.1.js"></script>      <script src="http://code.jquery.com/ui/1.10.2/jquery-ui.js"></script><span id="caption></span><span id="CARRID1"></span><span id="CONNID1"></span><span id="CARRID2"></span><span id="CONNID2"></span><script>
        var obj  = jQuery.parseJSON('<%= lv_json %>');
        document.getElementById("caption").innerHTML = obj.CAPTION;
        document.getElementById("CARRID1").innerHTML = obj.FLIGHT[0].CARRID;
        document.getElementById("CONNID1").innerHTML = obj.FLIGHT[0].CONNID;
        document.getElementById("CARRID2").innerHTML = obj.FLIGHT[1].CARRID;
        document.getElementById("CONNID2").innerHTML = obj.FLIGHT[1].CONNID;</script>

         

        The complete code in BSP Page is:

         

        <%@page language="abap"%><%@extension name="htmlb" prefix="htmlb"%><%
        data : lrf_zinput type ref to ZJSONINPUT,       lrf_json_processor type ref to Z_CL_JSON_PROCESSOR.
        
        
        CREATE object LRF_ZINPUT.
        
        
        
        
          .
        
        
        data lt_flight type FLIGHTTAB.
        SELECT * FROM SFLIGHT up to 2 rows INTO table lt_flight.
        
        
        lrf_zinput->flight[] = lt_flight[].
        
        
        lrf_zinput->caption = ' "Json Serialize \Demonstration '.
        data lv_json type string.
        clear lv_json.
        
        
        
        
        Create object LRF_JSON_PROCESSOR
          exporting    DATA = lrf_zinput.
        
        
        call method LRF_JSON_PROCESSOR->SERIALIZE  importing    E_JSON = lv_json  .
        
        
        %><htmlb:content design="design2003">  <htmlb:page title = " "><link rel="stylesheet" href="http://code.jquery.com/ui/1.10.2/themes/smoothness/jquery-ui.css" />      <script src="http://code.jquery.com/jquery-1.9.1.js"></script>      <script src="http://code.jquery.com/ui/1.10.2/jquery-ui.js"></script>      <link rel="stylesheet" href="/resources/demos/style.css" />    <htmlb:form><span id="caption"></span><table border="1">   <tr><th>Airline Code</th><th>Flight Connection Number</th><th>Flight date</th><th>Airfare</th><th>Local currency of airline</th><th>Aircraft Type</th><th>Maximum capacity in economy class</th><th>Occupied seats in economy class</th><th>Total of current bookings</th><th>Maximum capacity in business class</th><th>Occupied seats in business class</th><th>Maximum capacity in first class</th><th>Occupied seats in first class</th>   </tr>  <script>
        var obj  = jQuery.parseJSON('<%= lv_json %>');
        document.getElementById("caption").innerHTML = obj.CAPTION;
        var i;
        for(i=0;i<obj.FLIGHT.length;i++)
        {  document.write("<tr>");  document.write("<td>");  document.write(obj.FLIGHT[i].CARRID);  document.write("</td>");  document.write("<td>");  document.write(obj.FLIGHT[i].CONNID);  document.write("</td>");  document.write("<td>");  document.write(obj.FLIGHT[i].FLDATE);  document.write("</td>");  document.write("<td>");  document.write(obj.FLIGHT[i].PRICE);  document.write("</td>");  document.write("<td>");  document.write(obj.FLIGHT[i].CURRENCY);  document.write("</td>");  document.write("<td>");  document.write(obj.FLIGHT[i].PLANETYPE);  document.write("</td>");  document.write("<td>");  document.write(obj.FLIGHT[i].SEATSMAX);  document.write("</td>");  document.write("<td>");  document.write(obj.FLIGHT[i].SEATSOCC);  document.write("</td>");  document.write("<td>");  document.write(obj.FLIGHT[i].PAYMENTSUM);  document.write("</td>");  document.write("<td>");  document.write(obj.FLIGHT[i].SEATSMAX_B);  document.write("</td>");  document.write("<td>");  document.write(obj.FLIGHT[i].SEATSOCC_B);  document.write("</td>");  document.write("<td>");  document.write(obj.FLIGHT[i].SEATSMAX_F);  document.write("</td>");  document.write("<td>");  document.write(obj.FLIGHT[i].SEATSOCC_F);  document.write("</td>");
        
        
        document.write("</tr>");
        }</script></table>    </htmlb:form>  </htmlb:page></htmlb:content>

        Implementing Autocompletion

        $
        0
        0

        In our days, input fields supported by autocompletion are omnipresent in the web. In this blog, I will show how to implement an input which gets autocompleted with proposals from an SAP system.

         

        The basic idea of an autocompleter is to provide the input field automatically with special key handlers, triggering Ajax requests. The requests return with input proposals that are presented as a list and can be selected with a mouse click (or with keys like "enter" or "tab").

         

        Most JavaScript frameworks provide some tools for autocompletion. jQuery, one of the most comon JavaScript frameworks, supports autocompletion in the form of a widget of the jQuery UI layer. My example uses this widget. The code is loaded into the browser from Google's CDN for jQuery:

         

        <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script><script src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.10.2/jquery-ui.js"></script>

         

        Let me start with the live example, implemented as a Business Server Page:

         

        http://bsp.mits.ch/buch/zdemo_autocomp/main.do

         

        Here is how it looks in action. I just have typed the letter "z", and the SAP system proposes all sales agents whose name contains a "z":

         

        autocomp.png

         

        jQuery's way to activate a widget - or any plugin function - is to select elements with a selector, and then apply the plugin function to it. The function usually accepts an hash as argument, containing the widget options.

         

        It's free to us which selector we would like to use. A selector could enumerate all the input fields that should be attached to autocompletion as a comma-separated list. But it is much more elegant to use a special CSS class like "autocomplete". The autocompletion code then writes - in its simplest form:

         

        $(function() {     $(".autocomplete").autocomplete({          source:"autocomplete.do"        });  });

         

        The envelopping $( function() {  ... }) is the signal for jQuery to register this code for the special event "DOM loaded": The earliest point in time during page loading when the complete HTML DOM is parsed and therefore accessible to JavaScript. The function $(".autocomplete") selects all elements in the current page having CSS class "autocomplete", this array being wrapped in a jQuery object. Applying the autocomplete() function to this object will register keyup and click events of the corresponding fields. The "source" option specified in the option argument of the function call may specify an URL (if it is of type string, it will be considered as an URL). You may also specify a self-defined JavaScript function f(request,response) instead, or even an array with the data, if you already have them on the client.

         

        In our case, we address a special controller "autocomplete.do", which for simplicity is located in the same BSP application as the example controller. In a more real-life example, the autocomplete service would be attached as a request handler to a special SICF node: Since the BSP framework is of no use for its functions, a simple request handling class is completely sufficient.

         

        When the user types some characters, Ajax request to "autocomplete.do" will be performed, with a Get parameter "term" containing his actual input (by the way, you can specify with the minLength parameter that the requests should not start before the user has typed a certain amount of characters in the input field).

         

        The response should be sent as a JSON string, in the form of an array of objects, each object containing a "label" element with a description, and a "value" element to be taken over into the input field. Here is an example of the expected format as is required by jQuery's autocomplete function:

         

        [  {    "label": "Luigi Hunziker",    "value": "5"  },  {    "label": "Ernst Ziegler",    "value": "6"  },  {    "label": "Jakob Kurz",    "value": "28"  },  {    "label": "Philippe Zobler",    "value": "36"  }
        ]

         

        On the ABAP site, we can collect our results into an internal table of "name-value" pairs. Although they have slightly different semantics, the table type TIHTTPNVP together with its corresponding line structure IHTTPNVP fit good for our purpose: IHTTPNVP simply consists of two components of type string - NAME and VALUE.

         

        When using the new built-in transformation from ABAP to JSON, the code for transforming arbitrary ABAP data into an UTF-8 encoded string (which must be of type XSTRING, since the ABAP codepage of a unicode system is not UTF-8 but UTF-16LE), always looks like this:

         

        method nvp_to_json.   data: lo_json type ref to cl_sxml_string_writer.   lo_json = cl_sxml_string_writer=>create( if_sxml=>co_xt_json ).   call transformation znvp2autocomp     source data = it_nvp     result xml lo_json.   ev_json = lo_json->get_output( ).
         endmethod.

         

        The only part of the code which is special to our situation is the XSLT transformation znvp2autocomp:

         

        <xsl:transform version="1.0"   xmlns:xsl="http://www.w3.org/1999/XSL/Transform"><xsl:strip-space elements="*"/><xsl:template match="DATA">   <array>     <xsl:apply-templates/>   </array></xsl:template><xsl:template match="IHTTPNVP">   <object>     <str name="label"><xsl:value-of select="NAME"/></str>     <str name="value"><xsl:value-of select="VALUE"/></str>   </object></xsl:template></xsl:transform>

         

        With this transformation, we map the internal representation of ABAP data of type TIHTTPNVP into the internal XML representation a JSON object: An <array>, containing inner <object>'s, each with two elements, "label" and "value".

         

        The request handling of the controller autocomp.do consists of three actions:

        • Perform the search based on the user input (form field "term"), populating an internal table of name/value pairs
        • Transform the result into JSON in the format that is required by jQuery
        • Put the result into the response data, and set the appropriate response content type.

         

        Here is the code for this.

         

         

        method DO_REQUEST.   data: lt_results type tihttpnvp,         lv_data type xstring,         lv_term type string.   lv_term = request->get_form_field( 'term' ).   do_search( exporting iv_term    = lv_term              importing et_results = lt_results ).   nvp_to_json( exporting it_nvp   = lt_results                importing ev_json  = lv_data ).   response->set_data( lv_data ).   response->set_content_type( 'application/json; charset=UTF-8' ).
         endmethod.

         

         

        The do_search() method has no surprises: It refines some example data, based on the string iv_term:

         

         

        method do_search.   data: lt_sales_persons type zverknr_tab_demo,         ls_result type ihttpnvp.   field-symbols: <ls_sales_person> type zverknr_struc_demo.   perform get_data in program saplzf4_exit_demo           changing lt_sales_persons.   loop at lt_sales_persons assigning <ls_sales_person>        where verknr cs iv_term           or name cs iv_term.     ls_result-name  = <ls_sales_person>-name.     ls_result-value = <ls_sales_person>-verknr.     append ls_result to et_results.   endloop.
         endmethod.

         

         

        That's all which is to be said about this implementation on ABAP site.

         

        Actually, when using autocompletion systematically, do_search( ) could be a method of an interface, with that same signature - an input iv_term and a result et_result of type tihttpnvp - and there may be different implementations for different search types (or data types). A general autocompletion handler gets the search id as a further parameter and provides the corresponding search instance, using an object factory. The rest of the code looks as above. Actually, this is the way we have implemented autocompletion in a recent project.

         

        Some remarks on my usage of jQuery's autocompletion widget on the client side are in order:

         

        For accessibility reasons, there is a special so-called liveRegion which will be automatically created by jQuery's autocompletion widget. It might be interesting to know that this region, once created, can be reused for displaying other data. In my example, I want to put the value into the input field, and the label for this value should be displayed to the right, just in the liveRegion. For this, I access the widget in the "select" event which is called when the user selects one item from the menu by clicking:

         

        $(".autocomplete").autocomplete({   ...  select:function(event,ui) {              $(this).data("ui-autocomplete")                .liveRegion.text( ui.item.label );              }  });

         

        A second, lesser known point: It is possible to redefine the function which renders the menu items. In my example, I changed this function in such a way that it displays not only the label but the combination of value and label, separated by a hyphen:

         

        $(".autocomplete").autocomplete({ ... })  .data( "ui-autocomplete" )._renderItem = function( ul, item ) {        return $( "<li></li>" )            .data( "item.autocomplete", item )            .append( "<a>" + item.value + " - " + item.label +"</a>" )            .appendTo( ul );         };

         

        But these are peculiarities. The straightforward implementation is simple - on the ABAP side as well as on the HTML/JavaScript side, as I hopefully showed with this blog.

        Creating Custom BSP Error Handling

        $
        0
        0

        As you know whenever an error occurs on BSP the following standard BSP Error page is displayed to the user. In most cases displaying end user to this page is not convinient. Yet, It gives information about the dump but, also gives information to the end user which she/he totally confused.

         

        bsperror.png

         

        Assigning custom error pages in SICF is also a solution. However at that time we may loose the details of the dump. Depending on the level of dump, we can access the details via transaction ST22 or SM21. However, in most cases error is not logged in anywhere. In ST22 we  can access error details easily. In SM21, It's not as easy as in ST22. We had to parse the error logs. This is very hard and time consuming.

         

        What happens when a BSP error occurs?

         

        Before coming to solution, I want to explain a little about what happens when a BSP error occurs.

         

        Anytime a dump happens REPORT_ERROR static method of CL_HTTP_EXT_BSP is called:

         

        method REPORT_ERROR.
        
        
        * Error exception: customer does not want our technical complex error page,
        * but wants to specify own rendering to be used. We look in table bsperrhandler
        * for a matching entry onto a string class=>method. If we find such a string,
        * we call this method blind. We do not protect against errors here, so that if
        * there should be a problem, a new exception should be handled by the kernel.
        * This new method must have exactly the same signature as this method here, and
        * should contain similar code to (at a minimum) the method report_error_html.
        * A customer can consider the handling of non HTML user againsts as well, but
        * this is probably seldomly required.  FIELD-SYMBOLS: <bsperrhandler> TYPE bsperrhandler.  DATA:           bsperrhandler  TYPE TABLE OF bsperrhandler.  DATA:           url            TYPE string.  url = server->request->get_header_field( if_http_header_fields_sap=>path_translated ).  SELECT * FROM bsperrhandler INTO TABLE bsperrhandler ORDER BY sort_key.  IF sy-subrc = 0 AND lines( bsperrhandler ) > 0.    LOOP AT bsperrhandler ASSIGNING <bsperrhandler>.      CHECK url CP <bsperrhandler>-url.      TRANSLATE <bsperrhandler>-err_class  TO UPPER CASE. "#EC SYNTCHAR      TRANSLATE <bsperrhandler>-err_method TO UPPER CASE. "#EC SYNTCHAR      CALL METHOD (<bsperrhandler>-err_class)=>(<bsperrhandler>-err_method)          EXPORTING server = server exception = exception.      RETURN.    ENDLOOP.  ENDIF.
        
        
        * code *****************************************************************
        
        
          data: l_accept type string.  l_accept = server->request->get_header_field( 'accept' ). "#EC NOTEXT  if l_accept cs '*/*'.                  "#EC NOTEXT understands HTML    report_error_html( server    = server                       exception = exception ).  elseif l_accept cs 'text/vnd.wap.wml'. "#EC NOTEXT understands WML    report_error_wml( server    = server                      exception = exception ).  else.    report_error_plain( server    = server                        exception = exception ).  endif.
        
        
        
        
        endmethod.
        
        

         

        REPORT_ERROR method first searchs if any custom error handler methods exists for given URL or not. To do it, it searchs in BSPERRHANDLER table. If any error handler does not exist, it calls standard error reporting method REPORT_ERROR_HTML, REPORT_ERROR_PLAIN, REPORT_ERROR_WML depending on request header.  If it finds custom error handling record in BSPERRHANDLER, It calls Errorhandling method which is defined BSPERRHANDLER.

         

        Custom BSP Error Handling Method

         

        As it's seen, to add custom error handling method, we just need to add record in BSPERRHANDLER table.

        1. Create error handling class and error handling method
        2. Add related record to BSPERRHANDLER

         

        Error Handling Class and Method

         

        Error handling class is just an ordinary ABAP Class. In my example I have created Z_CL_ERRORHANDLER.

        Second step is defining error handling method. Error Handling method must have the same input parameters with CL_HTTP_EXT_BSP=>REPORT_ERROR:

         

        EXCEPTIONImportingType Ref ToCX_ROOT
        SERVERImportingType Ref ToIF_HTTP_SERVER

         

        We can do anything we want in our error handler method. In my example I have created a log table and saved error details in the table.

         

        method REPORT_ERROR.  CALL method cl_http_ext_bsp=>REPORT_ERROR_HTML  exporting    exception = exception    server = server.
        
         "You can write and call your own method
          CALL method SAVE_ERROR_LOG    exporting      EXCEPTION =     exception      SERVER    =    server    .
        
        
        endmethod.
        
        

         

        The last step is to define errorhandler in BSPERRHANDLER table. To do it so, we need to add the following record

        FieldValueDescription
        SORT_KEY1unique sort order
        URL *The url where error handling method will be applied. It accept wildwards. You can write individual page e.g. mypage.bsp or /myservice*
        ERR_CLASS Z_CL_ERRORHANDLERError Handling Class
        ERR_METHODREPORT_ERRORError Handling Method

         

        Now anytime error occurs our error handling method will be called instead of standard error handling method

        The Five Phases of a Dialog Step

        $
        0
        0

         

        In this blog post, I want to share some ideas about the processing of a single dialog step in an application. I am using the dialog step of a stateless Business Server Page application as example, but the basic ideas essentially hold for any user interface.

         

        Pre-Requisite: Model - View - Controller (MVC)

         

        Model-View-Controller (MVC) is a basic architectural pattern that is applicable for any software which involves interaction with a human user (i.e. not for software embedded in an engine or automat). It is about factoring out the code parts that are responsible for the UI from the "models" - which is all the rest, independent from that particular UI. The UI code parts themselves are divided into views - the actual UI elements presented to the user - and controllers - which co-ordinate the activities of views and models.

         

        First Phase: Initialization

         

        In this first phase, the controller gathers the necessary ressources for the dialog step: If there are models needed for the current state of the application, the instances are built or accessed. The controller analyzes the incoming form fields by their name and puts them into an own data structure. If there was state from the previous dialog step, it will be restored in this phase if necessary.  

         

        In a stateless BSP application, not the complete session with all its data will persist a dialog step - instead, only the necessary parts of information survive, following the on-demand paradigm. This small amount of data which is really necessary for the execution of the next dialog step, is known to the developer who designs the application. He stores these data in the database table SSCOOKIE which is designed for storing session-specific general data clusters (it uses the EXPORT TO DATABASE idiom for storage). In the presence of a web dispatcher with varying application servers for each dialog step, it makes no sense to put theses session data to the memory only. Hidden form fields in the web page itself would be an alternative - but they are commonly considered too tedious; they are useful, however, for very small web applications or very tiny bits of information to be kept.

         

        In such a (technically) stateless web application, it is a task of this first step to revive the session data from the data base. From the second step onwards, the developer can then work with the comfortable illusion of continuous state.

         

        Second Phase: Receiving View Data

         

        When the first step is completed, all the models and auxiliary objects are available for use. Now the data coming in from the user interface have to be put into the correct places in their server's counterpart.

         

        An input field on the screen has a corresponding domain on the server, usually somewhere in some model's data. In this step,

        • the controller dispatches the user's input - which comes in as a sequence of the characters he typed, i.e. as a string, to the model that is responsible for it;
        • the model checks whether the input is valid - and triggers an error event if this not the case, stopping the data transfer for this field and collecting a specific error message describing the problem;
        • if the input string is valid, the model converts it into the internat data format. For example, the string "4/17/2013" representing a date, will be converted into the internal type D format as we see it in the debugger: an eight place data object with the value "20130417";
        • finally, the result of this "to internal" conversion is stored in the corresponding data object.

         

        To indicate the correspondence, we use the mechanism of data binding: The name of the input field encodes in a readable manner to which data object of which model the value refers. See the SAP Help for the basics of such a syntax.

         

        An application may work with many models at once. So the total set of incoming fields can be divided into several groups, and each of these groups (name/value pairs coming from the screen input) will be addressed to a particular model.

         

        In this step, we have to consider field dependencies: Often it doesn't make sense to validate a field B if the validation of another field A had failed. These dependencies and priorities can be mapped into execution logic in this step. By the way: This logic could in large parts be programmed in a general and declarative manner. Only specific field validations have to be programmed explicitly. A framework can do the main part here.

         

        An application may work with many models at once. So the total set of incoming fields can be divided into several groups, and each of these groups (name/value pairs coming from the screen input) will be addressed to a particular model.

         

        Third Phase: Handling the User Command

         

        I am using the singular "the user command" on purpose here: A dialog step is always triggered by exactly one user action. Therefore, it is completely sufficient to encode this action in one and only one field, which in GUI applications traditionally has the name FCODE.

         

        The prototypical example here is: The user hits a button. Then the content of the FCODE will say which button it was. This way, the user can trigger an action which will be performed in this third step. Following the Command Design Pattern, a command object could be instantiated which implements an interface consisting of the execute() method. I used a more simple approach: Dynamic method invocation. From the FCODE, say "SAVE", I form the method name "FCODE_SAVE" and try to call this as a controller method (indeed, all user command handlers belong to the controller in the MVC model). If no such method exists, nothing will be executed (the CX_SY_DYN_CALL_ILLEGAL_METHOD exception is caught with an empty CATCH clause).

         

        Even this case - that nothing happens - makes sense: In some cases, it is required that hitting the "Enter button" will cause a server roundtrip - propagating all the user data into the corresponding fields and reacting on data changes, but with no particular action. For this, an explicit FCODE can be designed, say "ACTU", with an empty handler method FCODE_ACTU.  

         

        How is the dependency from the previous step: "Receiving View Data"? Usually, it is not desired that an FCODE will be executed if there was an error in the user input validation: The user should correct his input errors before he can save the document (say). 

         

        I solved this dependency by employing a non-persistent overall message collector (using the application log functionality - development class SZAL): If an error has been collected during the field validation, the phase "handle user command" will be skipped:

         

        * Field validation            get_viewdata( ).
        
        * Handle User Command - but only if there were no errors:
                    if check_valid_data eq abap_false or                 log->get_highest_type( ) na 'EAX'.                  handle_fcode( ).           endif.

         

        In some cases, it is wished to surpress this dependency: Think of the screen data divided into several tabs - you wouldn't wish to surpress the event "change tab" only because a field in the current tab is not yet filled out. It is for these special cases that the controller flag check_valid_data can be switched off to force user command handling.

         

        Fourth Phase: Do the Subsequent View(s)

         

        After having finished the phase "do user command", everything is known which needs to be known for sending the followup view(s). Like the FCODE in the third phase, I used a special field PANEL for the fourth phase. The name PANEL is actually stolen from SAP's old and venerable screen sequence control (function group V00F), where the term means something quite similar.

         

        Like the FCODE, the PANEL is a string, encoding a certain view state of the application. The necessary views that have to be instantiated and processed in a certain PANEL, are declared, not programmed, in an XML document accompanying the BSP application (which I called "Flow Logic" - a file stored as config.xml in the MIME branch of the BSP application).

         

        Here is a primitive example application, which may give you an idea: http://bsp.mits.ch/buch/zz_hallo_welt/hallo2.do

         

        For example, a simple application often has three panels: "entry", "list" and "detail". There will be an invariant part of each of the corresponding pages in the browser (containing the <html>, <head>, <body> tag and some constant parts like the navigation area and other sidebars). And there will be variable parts which have to be selected depending on the actual PANEL. These correspond technically to HTML fragments, encoded in BSP views, and plugged into the corresponding parts of a main view.

         

        An important rule is: At the beginning of Phase IV, the field PANEL has to be known. It should never be changed afterwards (as changes will be of no effect). For example, an event handler in an entry screen, requiring the selection of certain documents depending on the entered selection criteria, will be coded like this:

         

        method handle_select.  get_model( 'order_selector' )->select( ).  set_panel( 'list' ).
        endmethod.

         

        In line 2, the model method for order selection is called. It knows the selection criteria: They had been bound already in Phase II to "order_selector" model attributes. Its result will be a list, say a public model attribute "gt_result", to be referred to in the list view.

         

        In line 3, the panel is switched from "entry" to "list". Thus, the controller knows which views have to be processed in this current Phase IV.

         

        One of the views participating in the "list" panel, may call a list view control, referring to the result list via databinding. So in the HTML code of the view, we may find the following call:

         

          <z:table binding="//order_selector/gt_result"/>

         

        Here, <z:table> is a custom BSP element, containing in its implementation details the HTML code to be produced for representiong the ABAP table gt_result. I have some "default" components for doing this work, but in many projects there are some more sophisticated requirements on table views, which I handle with project specific BSP elements, delegating whatever they can to my basic <z;table> element.

         

        It is in this phase that the so-called field selection has to be performed, i.e. the determination of input control properties like "readonly" or "invisible". For an input field like

         

        <z:input binding="//order/gs_head.sold_to"/>

         

        there has to be a callback to the controller - and, from there, also to the model - in order to determine whether the order's sold-to party may be changed in the current state of the order.

         

        Observe that in GUI applications, the transition from Step III to Step IV corresponds to the transition from Process After Input (PAI) to Process Before Output (PBO)

         

        Fifth Phase: Finish

         

        There is an event which all involved controllers and models should receive, signaling that the response is now complete and ready to be sent to the user. The content of the response can't be changed anymore in this step.

         

        This phase is necessary only for (technically) stateless applications: They have the chance to write back the current session cookies to the database, so that they can be restored in the next dialog step.

         

        Summary

         

        My feeling is that this five phases model of a dialog step is somehow universal. For stateful applications, it reduces to a three-phase model (ignoring the very first and very last dialog step of the user's session). It is a synchronized model, meaning that all the involved controllers participate in it: If we are in a certain phase, then all controllers with no exception have finished the precedent phases. For controlling phase III (user command) and phase IV (do views), a single string  encoding the necessary information suffices: FCODE for the user command, and PANEL for producing the output.

         

        Reference

        Rüdiger Plantiko: Das BSP Praxisbuch. dpunkt Verlag 2007 (German language).


        How to Log Off BSP app without closing IE

        $
        0
        0

        As you have probably found out already, the log off procedure explained onhttp://help.sap.com/saphelp_nw04/helpdata/en/6b/9d91d062cc52419f23926ff1bf2ad3/content.htm implies that you close the browser window.

         

        Now, this becomes almost impossible when dealing with handheld devices like barcode scanners.

        The scenario I'm going to refer to in this page is intended to SAP BSP developments for handheld devices running Windows Mobile and IE 6+.

         

         

        So all you need to do is:

        - close the current SAP session

        - clear authentication cache.

         

        1) Closing the current SAP Session

        Logging off is extremly easy. Let's say your bsp app has a page called logoff.htm. Just create a link to this page like

         

        http://<hostname.domain>:<port>/bc/bsp/sap/bc/<your_app>/logoff.htm?sap-sessioncmd=logoff

         

        Sending the parameter ?sap-sessioncmd=logoff  will start actually close the current connection, and if you check in SM04 TCODE you should no longer see any Plugin Http connections  (or at least you'll notice that one of them closes ).

         

         

        Great so far, but there is another problem. If you hit refresh or do anything to make the BSP App pages reload, you'll notice that the connection you just close, reopened because of the IE cache.

         

        2) Clearing the authentification cache of IE

        Clearing the authentification cache of IE is done by simply putin the following JS command in the URL Bar of the IE browser:

         

        javascript: document.execCommand('ClearAuthenticationCache');

         

        Note: This command is not compatible with all browsers (like chrome) and probably not all versions of IE. To test it, just put the above mentioned command in the address bar and run it. It should return "true".

        Also if this command is succesful, your BSP App should popup the logon dialog box again. Keep in mind that this won't mean that the previous session was closed. It only means that IE no longer knows that you have an open connection.

         

        Puting it all togheter:

         

        Another problem that might come up is the order in which the two commands run (log of and clear cache).

        -If you clear cache before logoff you'll have the logon dialog box again, and thus create multiple connections.

        -If you call the logoff parameter via url like:

             http://<hostname.domain>:<port>/bc/bsp/sap/bc/<your_app>/logoff.htm?sap-sessioncmd=logoff

        your browser might not run the JS function.

         

        So you have to run them kinda at the same time, and in the following order:

        - Log Off

        - Clear Authentification Cache

         

        Following is the code of the logoff page.

        Note that the link to my page is simply

             http://<hostname.domain>:<port>/bc/bsp/sap/bc/<your_app>/logoff.htm

        witout sending the logoff command.

         

         

         

        <%@page language="abap" %><%@extension name="htmlb" prefix="htmlb" %><htmlb:content design="design2003" ><htmlb:page><head><script type="text/javascript">
        function clearCache()
        {   window.setTimeout('document.execCommand("ClearAuthenticationCache", "false")',1500);  
        }</script></head>  <body onload="clearCache()">  <img src="?sap-sessioncmd=logoff" width="1" height="1">  <h1 align="center">You have been logged off</h1>  <div align="center">      <input type="image" src="./Images/home.jpg" style="width:100px; height:100px;" onclick="window.location.href='start.htm';"/>  </div>  </body>  </htmlb:page>  </htmlb:content>

         

         

        In the page you have the clearCache() JS function that runs delayed for 1.5 seconds and it clears the authentication cache, and the

        <img src="?sap-sessioncmd=logoff" width="1" height="1">

         

        will run the logoff command for your app.

         

        So this is it...

         

         

        Links:

             Logging onto BSP Applications

             Logging off BSP Applications

        File upload using PLUpload and BSP

        $
        0
        0

        As we all know SAP has some great tools for uploading files into the system, but what if you want to create a completely custom web page that supported cross browser uploads and json....hmmm not so easy!

         

        While searching the web one day I came across a toolkit called PLUpload http://www.plupload.com/ but this was pure javascript. The features looked perfect for what I was needing to do.

         

        Screenshot-2012-09-19_08.58.47.png

        How could I get this into SAP and be usable? Well here is how I did it.

         

        1. First download the open source project http://www.plupload.com/download.php

         

        2. Go to SE80 and create a new BSP Application.

         

        3. Create a new contoller, in my case I called in index.do. You will have to create an new class that is associated with the controller.

         

        4. Inside the new class you need to redefine the DO_REQUEST method.

         

        5. Within this method you need to tell it which view to execute and open (we'll create the new view later).

         

        method DO_REQUEST.

           data view type ref to if_bsp_page.

           data pagePassed type c.

           pagePassed = ''.

         

           data: ls_field type ihttpnvp,

                lt_field type tihttpnvp.

         

          request->get_form_fields( CHANGING fields = lt_field ).

          loop at lt_field into ls_field.

            if ls_field-name = 'page'.

              if ls_field-value = 'home'.

                 me->view_name = 'index.htm'.

                 view = me->create_view( view_name = me->view_name ).

                 pagePassed = 'X'.

         

                 if view is bound.

                   me->call_view( view ).

                 endif.

              ELSEIF ls_field-value = 'valid'.

                 me->view_name = 'valid.htm'.

                 view = me->create_view( view_name = me->view_name ).

                 pagePassed = 'X'.

         

                 if view is bound.

                   me->call_view( view ).

                 endif.

              endif.

            endif.

          ENDLOOP.

         

        if pagePassed = ''.

           me->view_name = 'index.htm'.

           view = me->create_view( view_name = me->view_name ).

         

           if view is bound.

             me->call_view( view ).

           endif.

        endif.

        endmethod.

         

        6. Now we need to create the views. Under the views folder, right-click and create. This is where we will place the html code to add the PLUpload.


        <%@page language="abap"%>

        <!-- Load Queue widget CSS and jQuery -->

        <link rel="stylesheet" href="style.css" media="screen">

        <link rel="stylesheet" href="plupload/jquery.plupload.queue/css/jquery.plupload.queue.css">

        <script type="text/javascript" src="jquery.min.js"></script>

         

        <!-- Third party script for BrowserPlus runtime (Google Gears included in Gears runtime now) -->

        <script type="text/javascript" src="browserplus-min.js"></script>

         

        <!-- Load plupload and all it's runtimes and finally the jQuery queue widget -->

        <script type="text/javascript" src="plupload/plupload.full.js"></script>

         

        <script src="jquery.uniform.js" type="text/javascript" charset="utf-8"></script>

        <link rel="stylesheet" href="uniform.default.css" type="text/css" media="screen">

         

        <%= runtime->session_manager->header_script( ) %>

         

           <script type="text/javascript">

         

             function newPopup() {

               var url = getCookie('file');

               var newURL = 'log.htm?log=' + url;

         

               popupWindow = window.open(

                 newURL,'Log','height=700,width=800,left=10,top=10,resizable=yes,scrollbars=yes,toolbar=yes,menubar=no,location=no,directories=no,status=yes')

             }

         

             function setCookie(c_name,value,exdays)

             {

               var exdate=new Date();

               exdate.setDate(exdate.getDate() + exdays);

               var c_value=escape(value) + ((exdays==null) ? "" : "; expires="+exdate.toUTCString());

               document.cookie=c_name + "=" + c_value;

             }

         

             function getCookie(c_name)

             {

               var i,x,y,ARRcookies=document.cookie.split(";");

               for (i=0;i<ARRcookies.length;i++)

               {

                 x=ARRcookies[i].substr(0,ARRcookies[i].indexOf("="));

                 y=ARRcookies[i].substr(ARRcookies[i].indexOf("=")+1);

                 x=x.replace(/^\s+|\s+$/g,"");

                 if (x==c_name)

                   {

                   return unescape(y);

                   }

               }

             }

         

             function checkCookie()

             {

               var username=getCookie("username");

                 if (username!=null && username!="")

                 {

                 alert("Welcome again " + username);

                 }

               else

                 {

                 username=prompt("Please enter your name:","");

                 if (username!=null && username!="")

                   {

                   setCookie("username",username,365);

                   }

                 }

             }

           </script>

         

        <script type="text/javascript" src="plupload/jquery.plupload.queue/jquery.plupload.queue.js"></script>

         

        <style type="text/css" media="screen">

               body {

                 font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;

                 color: #666;

                 padding: 40px;

               }

               h1 {

                 margin-top: 0;

               }

               ul {

                 list-style: none;

                 padding: 0;

                 margin: 0;

               }

               li {

                 margin-bottom: 20px;

                 clear: both;

               }

               label {

                 font-size: 10px;

                 font-weight: bold;

                 text-transform: uppercase;

                 display: block;

                 margin-bottom: 3px;

                 clear: both;

               }

        </style>

         

        <script type="text/javascript">

        // Convert divs to queue widgets when the DOM is ready

        $(function() {

              $("input, textarea, select, button").uniform();

              function log() {

               var str = "";

         

               plupload.each(arguments, function(arg) {

                 var row = "";

         

                 if (typeof(arg) != "string") {

                   plupload.each(arg, function(value, key) {

                     // Convert items in File objects to human readable form

                     if (arg instanceof plupload.File) {

                       // Convert status to human readable

                       switch (value) {

                         case plupload.QUEUED:

                           value = 'QUEUED';

                           break;

         

                         case plupload.UPLOADING:

                           value = 'UPLOADING';

                           break;

         

                         case plupload.FAILED:

                           value = 'FAILED';

                           break;

         

                         case plupload.DONE:

                           value = 'DONE';

                           break;

                       }

                     }

         

                     if (typeof(value) != "function") {

                       row += (row ? ', ' : '') + key + '=' + value;

                     }

                   });

         

                   str += row + " ";

                 } else {

                   str += arg + " ";

                 }

               });

         

               $('#log').append(str + "\n");

              }

         

           $("#uploader").pluploadQueue({

             // General settings

             runtimes : 'gears,flash,silverlight,browserplus,html5',

             url : 'upload.htm',

             max_file_size : '10mb',

             unique_names : true,

             multiple_queues : true,

             dragdrop : true,

         

             // Resize images on clientside if we can

             resize : {width : 320, height : 240, quality : 90},

         

             // Specify what files to browse for

             filters : [

               {title : "EVMS Files", extensions : "txt,csv"}

             ],

         

             // Flash settings

             flash_swf_url : 'plupload/plupload.flash.swf',

         

             // Silverlight settings

             silverlight_xap_url : 'plupload/plupload.silverlight.xap',

         

             // PreInit events, bound before any internal events

             preinit : {

               Init: function(up, info) {

                 log('[Init]', 'Info:', info, 'Features:', up.features);

               },

         

               UploadFile: function(up, file) {

                 log('[UploadFile]', file);

         

                 var strparam = "";

                 var strparam2 = "";

         

                 if($('#sapload:checked').val()?true:false){

                   strparam = "X";

                 }

         

                 if($('#validate:checked').val()?true:false){

                   strparam2 = "X";

                 }

         

                 if(strparam == "X" && strparam2 == "X"){

                   up.settings.multipart_params = { 'sap':'X','valid':'X' };

                 }

                 else

                 {

                   if(strparam == "X" )

                   {

                     up.settings.multipart_params = { 'sap':'X' };

                   }

         

                   if(strparam2 == "X")

                   {

                     up.settings.multipart_params = { 'valid':'X' };

                   }

                 }

         

                 // You can override settings before the file is uploaded

                 // up.settings.url = 'upload.php?id=' + file.id;

                 // up.settings.multipart_params = {param1 : 'value1', param2 : 'value2'};

               }

             },

         

             // Post init events, bound after the internal events

             init : {

               Refresh: function(up) {

                 // Called when upload shim is moved

                 log('[Refresh]');

               },

         

               StateChanged: function(up) {

                 // Called when the state of the queue is changed

                 log('[StateChanged]', up.state == plupload.STARTED ? "STARTED" : "STOPPED");

               },

         

               QueueChanged: function(up) {

                 // Called when the files in queue are changed by adding/removing files

                 log('[QueueChanged]');

               },

         

               UploadProgress: function(up, file) {

                 // Called while a file is being uploaded

                 log('[UploadProgress]', 'File:', file, "Total:", up.total);

               },

         

               FilesAdded: function(up, files) {

                 // Callced when files are added to queue

                 log('[FilesAdded]');

         

                 plupload.each(files, function(file) {

                   log('  File:', file);

                 });

               },

         

               FilesRemoved: function(up, files) {

                 // Called when files where removed from queue

                 log('[FilesRemoved]');

         

                 plupload.each(files, function(file) {

                   log('  File:', file);

                 });

               },

         

               FileUploaded: function(up, file, info) {

                 // Called when a file has finished uploading

                 log('[FileUploaded] File:', file, "Info:", info);

                 info = jQuery.parseJSON( info.response );

                    if(info.error != null || info.error != undefined)

                    {

                       if (info.error.code){

                         log('[error] ', info.error.message);

                         file.status = plupload.FAILED;

         

                         setCookie('file',file.name,1);

                         $('#errorlist').append('<div class="error"><a href="javascript:newPopup();">Error loading file ' + file.name + ' - Please check log for more detail.</a></div>');

         

                         up.trigger('UploadProgress', file);

         

        <%--                 up.trigger('Error', {

                            code : info.error.code,

                            message : info.error.message,

                            details : info.details,

                            file : file

                          });--%>

                       }

                    }

                    else

                    {

                      $('#errorlist').append('<div class="success">' + file.name + ' was successful</div>');

                    }

               },

         

               ChunkUploaded: function(up, file, info) {

                 // Called when a file chunk has finished uploading

                 log('[ChunkUploaded] File:', file, "Info:", info);

               },

         

               Error: function(up, args) {

                 // Called when a error has occured

                 log('[error] ', args);

                 setCookie('file',args.file.name,1);

                 $('#errorlist').append('<div class="error"><a href="javascript:newPopup();">Error loading file ' + args.file.name + ' - Please check log for more detail.</a></div>');

                 up.start();

               }

             }

         

           });

         

           // Client side form validation

           $('form').submit(function(e) {

                 var uploader = $('#uploader').pluploadQueue();

         

                 // Files in queue upload them first

                 if (uploader.files.length > 0) {

                     // When all files are uploaded submit form

                     uploader.bind('StateChanged', function() {

                         if (uploader.files.length === (uploader.total.uploaded + uploader.total.failed)) {

                             $('form')[0].submit();

                         }

                     });

                     uploader.start();

                 } else {

                     alert('You must queue at least one file.');

                 }

         

                 return false;

             });

        });

        </script>

         

        <form ..>

           <div id="uploader">

             <p>You browser does not have Flash, Silverlight, Gears, BrowserPlus or HTML5 support.</p>

           </div>

         

             <label><input type="checkbox" name="validate" id="validate" /> Validate File</label>

             <label><input type="checkbox" name="sapload" id="sapload" /> Load into SAP</label>

           <!-- Error List -->

           <!--div id="log" class="cb"></div-->

           <div id="errorlist" class="cb"></div>

        </form>

         

        7. One of the most important pieces in the code above is line

         

             $("#uploader").pluploadQueue({

                  // General settings

                  runtimes : 'gears,flash,silverlight,browserplus,html5',

                  url : 'upload.htm',

                  max_file_size : '10mb',

                  unique_names : true,

                  multiple_queues : true,

                  dragdrop : true,

         

        The url is the url that the data will be sent to and this is also were we need to do some json string stuff.

         

        8. Now under the Pages with Flow Logic, right-click and create a page called upload.htm

         

        9. Under the Event Handler tab, select OnRequest. This is where the magic occurs.

         

        data: ls_field type ihttpnvp,

                lt_field type tihttpnvp.

         

        data: runValid type c,

               runSAP type c.

         


        request->get_form_fields( CHANGING fields = lt_field ).

        loop at lt_field into ls_field.

           if ls_field-name = 'valid'.

             runValid = 'X'.

           ELSEIF ls_field-name = 'sap'.

             runSAP = 'X'.

           endif.

        ENDLOOP.

         

        DATA: content    TYPE STRING,

               Xcontent   TYPE XSTRING,

               entity     TYPE REF TO if_http_entity,

               idx        TYPE I VALUE 1,

               content_type type string.

         

        WHILE idx <= request->num_multiparts( ).

           entity = request->get_multipart( idx ).

           idx = idx + 1.

         

           IF entity->get_header_field( '~content_filename' ) IS INITIAL.

             CONTINUE.

           ENDIF.

         

           data filename type string.

           filename = entity->get_header_field( '~content_filename' ).

           content_type = entity->get_header_field( 'Content-Type' ).

         

           Xcontent = entity->get_data( ).

           IF XSTRLEN( Xcontent ) IS INITIAL.

             CONTINUE.

           ENDIF.

         

           DATA: conv   TYPE REF TO CL_ABAP_CONV_IN_CE.

           conv = CL_ABAP_CONV_IN_CE=>CREATE( input = Xcontent ).

           conv->READ( importing data = content ).

           EXIT.

        ENDWHILE.

         

        if content is not INITIAL.

             "This is were the contents of the file are.

            

          "Here is do whatever we need to the data. Do a check in this case subrc. If successful send success else send error.

          if NOT sy-subrc = 0.

             CONCATENATE '{"jsonrpc" : "2.0", "result" : null, "id" : "id"}' '' into xml_string.

           else.

             xml_string = '{"jsonrpc" : "2.0", "error" : { "code":"100","message":"File Failed to load. See error log for more detail." }, "id":"id"}'.

           endif.

         

           "xml_string = '[{"name":"picture1.jpg","size":902604,"url":"\/\/example.org\/files\/picture1.jpg","thumbnail_url":"\/\/example.org\/thumbnails\/picture1.jpg","delete_url":"\/\/example.org\/upload-     handler?file=picture1.jpg","delete_type":"DELETE"}]'.

            CALL METHOD _M_RESPONSE->IF_HTTP_ENTITY~SET_CDATA

               EXPORTING

                 DATA = XML_STRING.

        endif.

         

        10. Now we need to load all the javascripts that we downloaded from the PLUpload site.

         

        11. Create a folder called plupload and do a mime import for each file (this is the painful part).

         

        12. Go download jquery and add this to the mime as well http://www.jquery.com/

         

        13. Once everything is added and you want to test the new code. Right-click on the index.do file and select test.

         

        14. You should see something like this....

        Screenshot-2012-09-19_09.20.44.png

        Radar scan effect in BSP

        $
        0
        0

        The code comes from http://blog.csdn.net/cuixiping/article/details/7565574

        1. Create a new BSP application with one page:

        clipboard1.png

         

        Paste the code below into main.htm:

        <!doctype html><head><meta charset="utf-8" /><title>Canvas Test</title></head><body><canvas id="canvas1" width="400" height="300"></canvas><script type="text/javascript" src="test.js"></script><script>
        var img = new Image();
        img.src = 'p1.jpg';
        img.onload = init();</script></body></html>

        2. import the following javascript code as MIME type test.js to BSP application:

        function MyCtx(ctx){    this.ctx = ctx;
        }
        (function (map){    for(var k in map){        MyCtx.prototype[k] = new Function('this.ctx.'+map[k]+'.apply(this.ctx,arguments); return this;');    }
        }({    B:'beginPath', M:'moveTo', L:'lineTo', A:'arc', Z:'closePath', f:'fill', dI:'drawImage', cR:'clearRect', clip:'clip', save:'save', restore:'restore'
        }));
        function init(){    var ctx = document.getElementById("canvas1").getContext('2d');    var mtx = new MyCtx(ctx), i=-1;    mtx.ctx.fillStyle='rgba(0,0,0,0.6)';    function f(){        mtx.save().dI(img,0,0).B().A(200,150,250,Math.abs(++i%100)*Math.PI/50,Math.PI*2,(i/100|0)%2).L(200,150).Z().clip().dI(img,-400,0).restore();        setTimeout(f,60);    }    f();
        }

        3. Import the attached picture into MIME.

         

        Test the application in Chrome:

         

        clipboard2.png

         

        clipboard3.png

        Enhancing BSP Graphical Options with D3.js

        $
        0
        0

        I had been interested in trying out some new JavaScript libraries particularly related to the area of data visualization. I felt that some of these offered very promising complementary functionality to the standard BSP user interface building blocks. I plumped for D3.js as it seems to have gained some popularity and I liked what I saw. To test it out I decided to add a simple bar chart to a BSP application using D3.js.

         

        For this purpose I created a small stateful BSP application. I then fetched some test data from into a static public table of the BSP application class YCL_TEST2=>DATA and created a main page set as initial BSP page containing a htmlb:tableView to display this data.  Finally, I set selectionMode to SINGLESELECT and saved the currently selected row number to YCL_TEST2=>SELECTEDROWINDEX so it is available to the window which will contain the d3 created bar chart and will be called via a window.open statement when the user clicks on a htmlb:button at the top of the page.

        image0.png

        The test data used for this demonstration in YCL_TEST2=>DATA takes the form of total cost of labour and subcontracted activities in warranty claims by customer and month. The concept of this demonstration BSP is that when the user selects a line and hits the button at the top of the page a popup with the a bar chart showing the total value claimed each month by the customer of the selected line.

         

        To begin building the bar chart I downloaded the d3.js library and uploaded it to the BSP application. As this was done purely for research in a development environment, I used the non minified version of the library.

        image1.png

        image2.png

         

        To be able to use the data in our table we need to convert it from the internal ABAP to a more JavaScript friendly format. I chose to convert our data to a json file format contained in a public string attribute of our application class. This was done in the OnInitialization event of the popup window with some ABAP code that once it has extracted the vendor of the line highlighted in the main window it then generates a JSON formatted string with the two attributes month and value containing the relevant information.

        image4.png

        And finally I created the layout of my BSP page which also contains in the head my JavaScript code using D3 that generates my bar chart as well some CSS formatting. To learn a bit about D3 and to guide me in creating I used a few resources but I should particularly credit http://bost.ocks.org/mike/bar/, https://www.dashingd3js.com/, https://gist.github.com/jasondavies/2300078/ and http://jsfiddle.net/robdodson/KWRxW/.


        <!doctype html>
        <html lang=
        "en">
        <head>
        <meta charset=
        "utf-8">
        <title>Claims by
        <%= parnr %> </title>

        <style>
        .chart {
        background-color: #eee;
        }

        .chart rect {
        stroke: white;
        fill: steelblue;
        }

        .axis {
        shape-rendering: crispEdges;
        }

        .x.axis line {
        stroke: black;
        }

        .y.axis line {
        stroke: black;
        }

        .axis
        text {
        font-size: 12px;
        }
        </style>

        <script type=
        "text/javascript" src="d3.js"></script>
        <script type=
        "text/javascript">

        function load() {
        var results,
        data = [],
        chart,
        bars,
        margin = 100,
        w = 50,
        h = 500,
        x, y,
        xAxis, yAxis;

        var json = <%= ycl_test2=>json %>;
        results = d3.map( json );
        results.forEach( function( key, val ) {
        var result = {};
        result.month = new Date(2010, ( parseInt( val.Month.slice(4,6),
        10 ) - 1 ), 15);
        result.value = parseInt( val.Value.replace( /,/g,
        '' ), 10 );
        data.push( result );
        } );

        chart = d3.select(
        'body' ).append( 'svg' )
        .attr(
        'class', 'chart' )
        .attr(
        'width', 800 )
        .attr(
        'height', h )
        .append(
        'g');

        d3.select(
        'svg g')
        .attr(
        'transform', 'translate(50, 50)');

        x = d3.time.scale()
        .domain( [new Date(2010, 0, 1), new Date(2010, 11, 31)] )
        .range( [0, ( w *
        12 )] );

        y = d3.scale.linear()
        .domain( [0, d3.max( data, function( d ) { return d.value; } )] )
        .rangeRound( [0, h - margin] );

        // Bars
        bars = chart.append(
        'g')
        .attr(
        'class', 'bars');

        bars.selectAll(
        'rect' )
        .data(
        data )
        .enter().append(
        'rect' )
        .attr(
        'x', function( d, i ) { return x( d.month ) - 23; } )
        .attr(
        'y', function( d ) { return (h - margin) - y( d.value ) } )
        .attr(
        'width', w )
        .attr(
        'height', function( d ) { return y( d.value ) } )
        .append(
        'g');

        //
        Axis
        xAxis = d3.svg.axis()
        .scale(x)
        .ticks(d3.time.month)
        .tickSize(6, 3, 1)
        .tickPadding(10);

        yAxis = d3.svg.axis()
        .scale(d3.scale.linear().domain( [0, d3.max( data, function( d ) { return d.value; } )] ).rangeRound( [h - margin, 0] ))
        .tickSize(6, 3, 1)
        .orient(
        'right');

        chart.append(
        'g')
        .attr(
        'class', 'x axis')
        .attr(
        'transform', 'translate(0, ' + (h - margin) + ')')
        .call(xAxis);

        chart.append(
        'g')
        .attr(
        'class', 'y axis')
        .attr(
        'transform', 'translate(' + x.range()[1] + ')')
        .call(yAxis);

        };
        </script>
        </head>
        <body onload=
        "load()">
        </body>
        </html>

         

        And here’s the end result. As described above when the user selects a line from the main page they are shown a bar chart of the customers from that line’s claims over.

         

        image3.png

        So there we go. A little rough around the edges but good enough for a first attempt destined for a non production environment and there’s might be something in there that others could find useful.

         

        I found this an interesting learning exercise and I’m still enthusiastic about the possibilities of enhancing the capabilities of BSP by incorporating open source JavaScript libraries. Specifically in respect to D3.js I really liked the easy with which SVG and other screen elements could be manipulated and the approach to binding to screen elements seems particularly well suited to creating visualization of data. On the downside I found the learning a bit steeper than expected (in fact I had originally planned to create various different type of graph for the same data set but ran out of time because it took me longer than I thought to get my code working) but this is obviously only an initial hurdle. I also felt that although I liked the flexibility of d3.js there were more steps than I would have expected for data visualisation library to such a standard task as creating a bar chart. I can see why this is but this draw back made me quite interested in the NVD3.js project which “to build re-usable charts and chart components for d3.js without taking away the power that d3.js gives you”. But that will have to wait for another time.

        Relational graph with joint.js

        $
        0
        0

        Quick info on how to create a relationship graph using a java script library, html and business server pages. Hope you like it.

         

         

         

        Requirements:

        Java script library joint.js

        Stylesheet joint.css

         

        Both required files can be found at JointJS download.

         

        You can find more information about the joint.js library at JointJS - the HTML 5 JavaScript diagramming library.

        I found this an easy to use library for creating a relationship graph that does not have the constraints of a tree or organizational chart.

         

        Attached is example.html a fully functional webpage that show the relationship graph, data for the graph has been included in the script in a JSON format. Simply open the file in a browser to see the result. More work needs to be done to improve the algorithm that is responsible for the placement of the nodes (elements).

         

        Page has 2 input data sources 1 for the nodes (elements) and 1 for the links between the nodes. In the example.html both data sources are supplied with data in the java script section. When used as a business server page this will be replaced with data that is retrieved from SAP, more on this in the paragraph "Business server page".

         

         

        Input data

         

        JSON format. 2 input data sources 1 for the nodes (elements) and 1 for the links between the nodes.

         

        Nodes

         

        3 fields:

        a) ZZ_KEY this is the internal unique key

        b) ZZ_ID this is intended for the id or key the node represents

        c) ZZ_NAME name displayed in the node (element)

         

        // sample data

        var nodes = [{"ZZ_KEY":0,"ZZ_ID":"ID00","ZZ_NAME":"Name 0"},

                     {"ZZ_KEY":1,"ZZ_ID":"ID01","ZZ_NAME":"Name 1"},

                     {"ZZ_KEY":2,"ZZ_ID":"ID02","ZZ_NAME":"Name 2"},

                     {"ZZ_KEY":3,"ZZ_ID":"ID03","ZZ_NAME":"Name 3"}];

         

         

        Links

         

        3 fields:

        a) ZZ_PARENT parent key which must be known in "nodes"

        b) ZZ_CHILD child key which must be known in "nodes"

        c) ZZ_DESCR text displayed on the line between the nodes (elements)

         

        // sample data

        var links = [{"ZZ_PARENT":0,"ZZ_CHILD":1,"ZZ_DESCR":"50%"},

                     {"ZZ_PARENT":1,"ZZ_CHILD":2,"ZZ_DESCR":"2%"},

                     {"ZZ_PARENT":0,"ZZ_CHILD":2,"ZZ_DESCR":"5%"},

                     {"ZZ_PARENT":2,"ZZ_CHILD":3,"ZZ_DESCR":"49%"}];

         

         

         

         

        Business server page

         

        You can retrieve the relevant data in the "initialization" event of Business server page. In the example.html page you will need to comment out the sample data and replace it with:

         

        // data from SAP backend

        var nodes = <%= nodes_json %>;

         

        // data from SAP backend

        var links = <%= links_json %>;

         

        Between "<%=" and "%>" is the name of the variable declared in SAP that is a string containing the data in JSON format.

         

         

        Create BSP example application

         

        • Create a BSP application and add a BSP page with Flow logic as shown in the picture below.

         

         

        • Copy the content of example.html to your BSP page.

         

         

        • Add your code for retrieving the data (and creating the JSON string) to layout event "OnInitialization".

         

         

        • Add 2 page attributes these are the 2 strings that will contain the JSON data (picture below).

         

         

        • Convert your data to JSON.

        I've created 2 structures 1 for the nodes and 1 for the links and used them for 2 internal tables which will later be converted to JSON once they contain all the data.

         

         

        * declarations

        DATA: writer TYPE REF TO cl_sxml_string_writer.

        DATA: json TYPE xstring.

        DATA: tb_nodes TYPE STANDARD TABLE OF zvas_rlsh_graph_nodes.

        DATA: tb_links TYPE STANDARD TABLE OF zvas_rlsh_graph_links.

         

        [get your data and copy it to the internal table tb_nodes and tb_links]

         

        * ABAP to JSON

        * nodes

        writer = cl_sxml_string_writer=>create( type = if_sxml=>co_xt_json ).

        CALL TRANSFORMATION id SOURCE nodes_json = tb_nodes

                                RESULT XML writer.

        json = writer->get_output( ).

        * convert xstring to string

        nodes_json =  cl_abap_codepage=>convert_from( json ).

        * remove last character and start id e.g. "{"nodes_json":"

        SHIFT nodes_json BY 1 PLACES RIGHT CIRCULAR.

        SHIFT nodes_json BY 15 PLACES LEFT.

         

        * links

        CLEAR: writer, json.

        writer = cl_sxml_string_writer=>create( type = if_sxml=>co_xt_json ).

        *CALL TRANSFORMATION id SOURCE table = tb_nodes

        CALL TRANSFORMATION id SOURCE links_json = tb_links

                                RESULT XML writer.

        json = writer->get_output( ).

        * convert xstring to string

        links_json =  cl_abap_codepage=>convert_from( json ).

        * remove last character and start id e.g. "{"nodes_json":"

        SHIFT links_json BY 1 PLACES RIGHT CIRCULAR.

        SHIFT links_json BY 15 PLACES LEFT.

         

         

        • Populate the JSON variables

         

        Replace the sample data with the JSON string created in the "OnInitialization" event.

         

        // sample data

        //var nodes = [{"ZZ_KEY":0,"ZZ_ID":"ID00","ZZ_NAME":"Name 0"},

        //             {"ZZ_KEY":1,"ZZ_ID":"ID01","ZZ_NAME":"Name 1"},

        //             {"ZZ_KEY":2,"ZZ_ID":"ID02","ZZ_NAME":"Name 2"},

        //             {"ZZ_KEY":3,"ZZ_ID":"ID03","ZZ_NAME":"Name 3"}];

         

        // data from SAP backend

        var nodes = <%= nodes_json %>;

         

        // sample data

        //var links = [{"ZZ_PARENT":0,"ZZ_CHILD":1,"ZZ_DESCR":"50%"},

        //             {"ZZ_PARENT":1,"ZZ_CHILD":2,"ZZ_DESCR":"2%"},

        //             {"ZZ_PARENT":0,"ZZ_CHILD":2,"ZZ_DESCR":"5%"},

        //             {"ZZ_PARENT":2,"ZZ_CHILD":3,"ZZ_DESCR":"49%"}];

         

        // data from SAP backend

        var links = <%= links_json %>;

         

         

        • Activate the BSP application

         

        Press the test button and see what happens. Browser should start showing a graph based on your data.

        If not start a debug session either in SAP or directly in your browser.

         

        You comments are welcome.

         

        Cheers Dennis

        Viewing all 20 articles
        Browse latest View live


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