METHOD IF_HTTP_EXTENSION~HANDLE_REQUEST.
IF_HTTP_HEADER_FIELDS_SAP=>PATH_TRANSLATED_EXPANDED ).
FORMAT = CL_ABAP_FORMAT=>E_HTML_TEXT ).
FORMAT = CL_ABAP_FORMAT=>E_HTML_TEXT ).
FORMAT = CL_ABAP_FORMAT=>E_HTML_TEXT ).
CALL METHOD SERVER->REQUEST->GET_FORM_FIELDS( CHANGING FIELDS = GT_FORM ).
IF_HTTP_HEADER_FIELDS_SAP=>REQUEST_METHOD ) = `GET`.
IF SERVER->REQUEST->GET_HEADER_FIELD( `hx-request` ) IS INITIAL.
SERVER->RESPONSE->APPEND_CDATA( HTML_PAGE( ) ).
SERVER->RESPONSE->APPEND_CDATA( HTML_SHELLBAR( ) ).
SERVER->RESPONSE->APPEND_CDATA( HTML_SELECTION( ) ).
SERVER->RESPONSE->APPEND_CDATA( HTML_TABLE( EXPORTING FORM = GT_FORM ) ).
ELSE.
CASE SERVER->REQUEST->GET_HEADER_FIELD( `app-action` ).
WHEN `search_init`.
WHEN `scroll`.
GV_PAGE = SERVER->REQUEST->GET_HEADER_FIELD( `app-page` ).
SERVER->RESPONSE->APPEND_CDATA( HTML_TABLE_ROWS( EXPORTING FORM = GT_FORM ) ).
WHEN `edit`.
SERVER->RESPONSE->APPEND_CDATA( HTML_EDIT_ROWS( EXPORTING FORM = GT_FORM ) ).
WHEN `cancel`.
SERVER->RESPONSE->APPEND_CDATA( HTML_CANCEL_EDIT( EXPORTING FORM = GT_FORM ) ).
ENDCASE.
DATA(LV_ACTION) = SERVER->REQUEST->GET_HEADER_FIELD( `app-action` ).
ENDIF.
REASON = IF_HTTP_STATUS=>REASON_200 ).
SERVER->RESPONSE->SET_CONTENT_TYPE( `text/html` ).
ENDIF.
IF_HTTP_HEADER_FIELDS_SAP=>REQUEST_METHOD ) = `PUT`.
CASE SERVER->REQUEST->GET_HEADER_FIELD( `app-action` ).
WHEN `search`.
SERVER->RESPONSE->APPEND_CDATA( HTML_TABLE( EXPORTING FORM = GT_FORM ) ).
WHEN `save`.
SERVER->RESPONSE->APPEND_CDATA( HTML_SAVE_EDIT( EXPORTING FORM = GT_FORM
IMPORTING STATUS_MSG = GV_STATUS_MSG ) ).
ENDCASE.
REASON = IF_HTTP_STATUS=>REASON_200 ).
SERVER->RESPONSE->SET_CONTENT_TYPE( `text/html` ).
ENDIF.
ENDMETHOD.
2.2.2 Fundamental library and htmx library
In HTML_PAGE, the header of the app page is defined. The important point here is that
Fundamental Library and
htmx library are defined. In the later section of the code, you will find html class such as “fd-table” and “fd-input” and attributes such as “hx–headers” and “hx–target”. “fd-” are referring to Fundamental library to give SAP Fiori theme UI and “hx-” attributes enables htmx function from htmx library.
`<link href='https://unpkg.com/fundamental-styles@0.22.0/dist/fundamental-styles.css' rel='stylesheet'>`
`<script src="https://unpkg.com/htmx.org@latest/dist/htmx.js"></script>`
2.2.3 Frontend/Backend interaction by htmx
By pressing the search button, PUT request is triggered with the service URL of this app. This behavior is defined in HTML_SELECTION. “data–hx–put=” defines the service to be triggered when the search button is pressed. In this case, GV_PATH contains the URL path initially triggered, and that will be triggered again when the button is pressed. Whatever set in “hx–headers=” will be added to Request header when the service is triggered. In this case, “app–action = search” will be added on the header and when the service is triggered by “data–hx–put”, the code will process into the case statement where “WHEN `search` = true” in the main HANDLE_REQUEST method. In other words, whatever set in “hx–headers” works like a OK code in ABAP dynpro and it gets picked up in the main HANDLE_REQUEST method.
Another point to mention here is that this button is inside a “form”, which means that the input values(Document type and Sales Org) will be passed as form data on the Request when the search button is pressed. Form data is fetched by method GET_FORM_FIELDS in the main HANDLE_REQUEST and later used to filter the SELECT statement. The purpose of those input fields are to filter the output of sales orders.
METHOD HTML_SELECTION.
CONCATENATE
`<form class="fd-form__item">`
`<label class="fd-form__label" for="auart"> Document type </label>`
`<input id="auart" class="fd-input fd-input-group__input" style="max-width: 100px;" type="text" name="Document type" >`
`<label class="fd-form__label" for="vkorg"> Sales Org </label>`
`<input id="vkorg" class="fd-input fd-input-group__input" style="max-width: 100px;" type="text" name="Sales Org" >`
`<button type="button" class="fd-button" `
`aria-label="clear search" `
`data-hx-get="` GV_PATH `" `
`data-hx-headers='{"app-action": "search_init"}'>`
`<i class="sap-icon--clear-all"></i>`
`</button>`
`<button type="button" class="fd-button" `
`aria-label="saerch" `
`data-hx-target="#sap_tables" `
`data-hx-swap-oob="true" `
`data-hx-put="` GV_PATH `" `
`data-hx-headers='{"app-action": "search"}'>`
`<i class="sap-icon--search"></i>`
`</button>`
`</form>`
INTO HTML.
ENDMETHOD.
Similarly, Edit button on each table rows are also calling back the class method every time they are pressed. Here, form is not used but “hx–vals” allows you to add search string to the request and in this case, search string k with value of GV_SO_KEY is passed. GV_SO_KEY contains the sales order + item number key which is concatenated before TAB_ROW_DISP_MODE is called. This is for the callback program to identify which sales order row the user is trying to change.
METHOD TAB_ROW_DISP_MODE.
"Put the row to display mode
CONCATENATE HTML
`<tr class="fd-table__row fd-table__row--hoverable">`
`<td class="fd-table__cell">` DATA-VBELN `</td>`
`<td class="fd-table__cell">` DATA-AUART `</td>`
`<td class="fd-table__cell">` DATA-VKORG `</td>`
`<td class="fd-table__cell">` DATA-POSNR `</td>`
`<td class="fd-table__cell">` DATA-MATNR `</td>`
`<td class="fd-table__cell">` DATA-KWMENG `</td>`
`<td class="fd-table__cell">` DATA-VRKME `</td>`
`<td>`
`<button class="btn btn-danger"`
`data-hx-vals='{"k": "` GV_SO_KEY `"}' `
`data-hx-get="` GV_PATH `" `
`data-hx-headers='{"app-action": "edit"}' >`
`Edit`
`</button>`
`</td>`
`<td class="fd-table__cell fd-info-label--accent-color-6" style="margin:0.2rem"">` STATUS_MSG `</td>`
`</tr>` INTO HTML.
ENDMETHOD.
Other htmx initiated user interactions include, pressing cancel buttons, pressing save button and scrolling down to fetch more records. They all call back the backend ABAP class method and return with the response data.
2.2.4 Swapping in htmx
One of the attractive feature of htmx is that you can swap and replace certain part of the page without returning the whole page of HTML. This is achieved by first defining the default swapping mode. “outerHTML” replaces the entire HTML element that is in process.
Here you can find other swapping modes.
<meta name="htmx-config" content='{"defaultSwapStyle":"outerHTML"}'>
Let’s look at an example when user presses Edit on one of the rows. WHEN `edit` will be true and HTML_EDIT_ROWS is called, in which calls TAB_ROW_EDIT_MODE. This method is only returning one row of table in HTML, NOT the whole page of the app starting from the Shell bar to the bottom of the table.
OuterHTML swapping is used almost everywhere in this app, but the search button is using different kind of swapping.
hx-swap-oob comes in handy when you want to swap other part of the page instead of itself. When search button is pressed, no change on the button itself is required but the table contents below should be refreshed. So that’s why hx–target=“#sap_tables” is set, which means this button is intended to swap the contents of “sap_tables”, which is the id for the HTML table.
`<button type="button" class="fd-button" `
`aria-label="saerch" `
`data-hx-target="#sap_tables" `
`data-hx-swap-oob="true" `
`data-hx-put="` GV_PATH `" `
`data-hx-headers='{"app-action": "search"}'>`
`<i class="sap-icon--search"></i>`
`</button>`
2.3 CRUD operation
This app uses Read and Update of the sales document data so I will only highlight how Create and Delete can by handled.
Create: Define a “Add” button with attribute “hx–headers=‘{“app-action”: “add”}'” and set app’s callback URL on “hx–get“. In the main HANDLE_REQUEST method, create a CASE statement for “add” and call BAPI or BDC of your choice to create sales order.
Delete: Define a “Delete” button on each row of the table with attribute “hx–headers='{“app-action”: “delete”}'” and set app’s callback URL on “hx–get“. The rest is same as Create. Create your own logic to delete the sales order item. One thing you have to remember is that nothing should be returned by APPEND_CDATA on the request’s response to make sure that the deleted item will be removed from the HTML table.
So it’s very straightforward! Just need to use htmx initiated request & htmx swapping wisely and combine it with backend ABAP to update the SAP database.
2.4 Exercise(Try it!)
Now I assume you have pretty good idea how swapping and htmx initiated request work. If you’d like, you can implement your own logic for the “search clear button”, as I left it empty on purpose.
WHEN `search_init`.
"Implement Search Init logic of your choice
3. Compatibility with ABAP
Through building this app, here is my summary of this development approach.
◉ Full access to objects in SAP application/DB server through ABA
◉ No need to call API to fetch dataset thus no need to create/manage Odata in backend
◉ Entire code is wrapped in one ABAP class for good maintainability (but you can always setup multiple services and classes in SICF)
◉ The frontend code will be very light. All the complex logic is managed in backend ABAP
◉ Input fields cannot directly refer to SAP data domain thus no automatic external/internal value conversion(had to manually convert doc number, sales org, etc.)
◉ A lot of effort to create select-options and F4 value help. They’re so easy in ABAP though…
◉ Need better idea to maintain HTML code than using string concatenation -> In the next release, it would be cool if SAP integrated HTML editor in SE80.
There are pros and cons but since it’s a new approach, there is always room for improvement!
4. Comparing the efforts with ABAP RAP Model
This new development model offers new possibilities for SAP web development and all, but does it save more time and hassle compared to the existing web app development framework? I used ABAP RAP to create nearly the identical app and turned out it took much less effort to build it.
On top of that, ABAP RAP model does not have the weakness of htmx & ABAP model, which are:
◉ Internal/external format conversion is performed automatically
◉ F4 search help comes as default
◉ Select options/multiple selection are possible
on top of these advantages,
◉ DB data is at your fingertip. BAPI call also possible to update database(not in the CRUD methods but only in the saver methods)
◉ Coding is only needed on behavior class, which was less than 200 lines
No comments:
Post a Comment