.
+ " The MIME type is the browser's guess from the file extension and may be
+ " missing altogether, so it falls back to the generic binary type.
+ SPLIT file AT `,` INTO DATA(header) DATA(base64).
+ DATA(mime_type) = substring_before( val = substring_after( val = header sub = `data:` ) sub = `;` ).
+ IF mime_type IS INITIAL.
+ mime_type = `application/octet-stream`.
+ ENDIF.
+ DATA(content) = cl_web_http_utility=>decode_x_base64( base64 ).
+
+ " a static action needs no key - the %cid is the caller's own id for it.
+ " The parameter is deep: description on the first level, the file one
+ " level below under the association _streamproperties.
+ MODIFY ENTITIES OF z2ui5_r_smps_fil
+ ENTITY file
+ EXECUTE uploadfile
+ FROM VALUE #( ( %cid = `UPLOAD_1`
+ %param = VALUE #( description = description
+ _streamproperties = VALUE #( filecontent = content
+ mimetype = mime_type
+ filename = file_name ) ) ) )
+ FAILED DATA(s_failed)
+ REPORTED DATA(s_reported).
+
+ IF s_failed-file IS NOT INITIAL.
+
+ ROLLBACK ENTITIES.
+ z2ui5_cl_smps_context=>msg_display( client = client val = s_reported-file ).
+ RETURN.
+
+ ENDIF.
+
+ COMMIT ENTITIES RESPONSE OF z2ui5_r_smps_fil
+ FAILED DATA(s_failed_commit)
+ REPORTED DATA(s_reported_commit).
+
+ IF s_failed_commit IS NOT INITIAL.
+
+ " a failed save keeps the changes in the transactional buffer - discard
+ " them, or abap2UI5's own COMMIT WORK at the end of the roundtrip runs
+ " into them
+ ROLLBACK ENTITIES.
+ z2ui5_cl_smps_context=>msg_display( client = client val = s_reported_commit-file ).
+ RETURN.
+
+ ENDIF.
+
+ client->message_toast_display( |{ file_name } - { xstrlen( content ) } bytes stored in the business object| ).
+
+ file = VALUE #( ).
+ file_name = VALUE #( ).
+ description = VALUE #( ).
+ data_read( ).
+ view_display( ).
+
+ ENDMETHOD.
+
+
+ METHOD file_download.
+
+ DATA(file_uuid) = CONV z2ui5_t_smps_fil-file_uuid( client->get_event_arg( ) ).
+
+ READ ENTITIES OF z2ui5_r_smps_fil
+ ENTITY file
+ FIELDS ( filename mimetype content )
+ WITH VALUE #( ( fileuuid = file_uuid ) )
+ RESULT DATA(t_result).
+
+ " a file deleted in another tab is not an exception - RESULT is just empty
+ IF t_result IS INITIAL.
+ client->message_box_display( text = `The file is no longer there - press Refresh` type = `error` ).
+ RETURN.
+ ENDIF.
+
+ " the same road back: the content becomes a data URL again, and the
+ " download_b64_file frontend action saves it under its name
+ DATA(s_file) = t_result[ 1 ].
+ client->follow_up_action(
+ val = client->cs_event-download_b64_file
+ t_arg = VALUE #( ( |data:{ s_file-mimetype };base64,{ cl_web_http_utility=>encode_x_base64( s_file-content ) }| )
+ ( |{ s_file-filename }| ) ) ).
+
+ ENDMETHOD.
+
+
+ METHOD file_delete.
+
+ DATA(file_uuid) = CONV z2ui5_t_smps_fil-file_uuid( client->get_event_arg( ) ).
+
+ MODIFY ENTITIES OF z2ui5_r_smps_fil
+ ENTITY file
+ DELETE FROM VALUE #( ( fileuuid = file_uuid ) )
+ FAILED DATA(s_failed)
+ REPORTED DATA(s_reported).
+
+ IF s_failed-file IS NOT INITIAL.
+
+ ROLLBACK ENTITIES.
+ z2ui5_cl_smps_context=>msg_display( client = client val = s_reported-file ).
+ RETURN.
+
+ ENDIF.
+
+ COMMIT ENTITIES RESPONSE OF z2ui5_r_smps_fil
+ FAILED DATA(s_failed_commit)
+ REPORTED DATA(s_reported_commit).
+
+ IF s_failed_commit IS NOT INITIAL.
+
+ ROLLBACK ENTITIES.
+ z2ui5_cl_smps_context=>msg_display( client = client val = s_reported_commit-file ).
+ RETURN.
+
+ ENDIF.
+
+ client->message_toast_display( `File deleted` ).
+ data_read( ).
+
+ ENDMETHOD.
+
+
+ METHOD data_read.
+
+ " EML reads by key - a list of every file is a SELECT on the CDS view.
+ " The content stays in the database: the list needs its size, not its
+ " bytes, and every row would otherwise travel to the browser.
+ SELECT FROM z2ui5_r_smps_fil "#EC CI_NOWHERE
+ FIELDS FileUuid,
+ FileName,
+ MimeType,
+ FileSize,
+ Description,
+ CreatedAt
+ ORDER BY CreatedAt DESCENDING
+ INTO TABLE @DATA(t_result) UP TO 50 ROWS.
+
+ t_files = VALUE #( FOR s_result IN t_result
+ ( file_uuid = |{ s_result-fileuuid }|
+ file_name = |{ s_result-filename }|
+ mime_type = |{ s_result-mimetype }|
+ file_size = |{ s_result-filesize } bytes|
+ description = |{ s_result-description }|
+ created_at = |{ s_result-createdat TIMESTAMP = ISO }| ) ).
+
+ ENDMETHOD.
+
+
+ METHOD view_display.
+
+ DATA(view) = z2ui5_cl_ui5_view_builder=>factory(
+ )->ele( n = `View` ns = `mvc`
+ )->a( n = `displayBlock` v = `true`
+ )->a( n = `height` v = `100%`
+ )->a( n = `xmlns` v = `sap.m`
+ )->a( n = `xmlns:mvc` v = `sap.ui.core.mvc`
+ )->a( n = `xmlns:form` v = `sap.ui.layout.form`
+ )->a( n = `xmlns:z2ui5` v = `z2ui5.cc` ).
+ DATA(page) = view->ele( `Shell`
+ )->ele( `Page`
+ )->a( n = `title` v = `abap2UI5 - RAP File Upload - Deep Action Parameter`
+ )->a( n = `showNavButton` b = client->check_app_prev_stack( )
+ )->a( n = `navButtonPress` v = client->_event_nav_app_leave( ) ).
+
+ page->tag( `MessageStrip`
+ )->a( n = `text` v = `Pick a file and press Upload: the FileUploader hands it to the backend as base64, the app ` &&
+ `calls the static action uploadFile of Z2UI5_R_SMPS_FIL with EML and passes the file in ` &&
+ `its deep parameter - no OData service, no annotations needed.`
+ )->a( n = `type` v = `Information`
+ )->a( n = `showIcon` b = abap_true
+ )->a( n = `class` v = `sapUiSmallMargin` ).
+
+ page->ele( n = `SimpleForm` ns = `form`
+ )->a( n = `title` v = `MODIFY ENTITIES OF Z2UI5_R_SMPS_FIL ... EXECUTE uploadFile`
+ )->a( n = `editable` b = abap_true
+ )->ele( n = `content` ns = `form`
+ )->tag( `Label`
+ )->a( n = `text` v = `Description`
+ )->tag( `Input`
+ )->a( n = `placeholder` v = `optional - stored with the file`
+ )->a( n = `value` v = client->_bind( description )
+ )->tag( `Label`
+ )->a( n = `text` v = `File`
+ )->tag( n = `FileUploader` ns = `z2ui5`
+ )->a( n = `placeholder` v = `pick a file...`
+ )->a( n = `uploadButtonText` v = `Upload`
+ )->a( n = `upload` v = client->_event( `UPLOAD` )
+ )->a( n = `path` v = client->_bind( file_name )
+ )->a( n = `value` v = client->_bind( file ) ).
+
+ DATA(table) = page->ele( `Table`
+ )->a( n = `items` v = client->_bind( t_files )
+ )->a( n = `noDataText` v = `No files yet - upload one above` ).
+ table->ele( `headerToolbar`
+ )->ele( `Toolbar`
+ )->tag( `Title`
+ )->a( n = `text` v = `Files (Z2UI5_R_SMPS_FIL)`
+ )->tag( `ToolbarSpacer`
+ )->tag( `Button`
+ )->a( n = `press` v = client->_event( `REFRESH` )
+ )->a( n = `icon` v = `sap-icon://refresh`
+ )->a( n = `tooltip` v = `Refresh` ).
+
+ table->ele( `columns`
+ )->ele( `Column`
+ )->tag( `Text`
+ )->a( n = `text` v = `Name`
+ )->end(
+ )->ele( `Column`
+ )->tag( `Text`
+ )->a( n = `text` v = `MIME Type`
+ )->end(
+ )->ele( `Column`
+ )->tag( `Text`
+ )->a( n = `text` v = `Size`
+ )->end(
+ )->ele( `Column`
+ )->tag( `Text`
+ )->a( n = `text` v = `Description`
+ )->end(
+ )->ele( `Column`
+ )->tag( `Text`
+ )->a( n = `text` v = `Created`
+ )->end(
+ )->ele( `Column`
+ )->tag( `Text`
+ )->a( n = `text` v = `Actions` ).
+
+ table->ele( `items`
+ )->ele( `ColumnListItem`
+ )->ele( `cells`
+ )->tag( `Text`
+ )->a( n = `text` v = `{FILE_NAME}`
+ )->tag( `Text`
+ )->a( n = `text` v = `{MIME_TYPE}`
+ )->tag( `Text`
+ )->a( n = `text` v = `{FILE_SIZE}`
+ )->tag( `Text`
+ )->a( n = `text` v = `{DESCRIPTION}`
+ )->tag( `Text`
+ )->a( n = `text` v = `{CREATED_AT}`
+ )->ele( `HBox`
+ )->tag( `Button`
+ )->a( n = `press` v = client->_event( val = `DOWNLOAD` arg = `${FILE_UUID}` )
+ )->a( n = `icon` v = `sap-icon://download`
+ )->a( n = `tooltip` v = `Download File`
+ )->tag( `Button`
+ )->a( n = `press` v = client->_event( val = `DELETE` arg = `${FILE_UUID}` )
+ )->a( n = `icon` v = `sap-icon://delete`
+ )->a( n = `tooltip` v = `Delete File` ).
+
+ client->view_display( view->stringify( ) ).
+
+ ENDMETHOD.
+
+ENDCLASS.
diff --git a/src/11/z2ui5_cl_smps_app_016.clas.xml b/src/11/z2ui5_cl_smps_app_016.clas.xml
new file mode 100644
index 0000000..dc0874b
--- /dev/null
+++ b/src/11/z2ui5_cl_smps_app_016.clas.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+ Z2UI5_CL_SMPS_APP_016
+ E
+ RAP File Upload - Deep Action Parameter
+ 1
+ X
+ X
+ X
+
+
+
+
diff --git a/src/12/01/package.devc.xml b/src/12/01/package.devc.xml
new file mode 100644
index 0000000..e0cda95
--- /dev/null
+++ b/src/12/01/package.devc.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+ test objects
+
+
+
+
diff --git a/src/12/01/z2ui5_a_smps_req_copy.ddls.asddls b/src/12/01/z2ui5_a_smps_req_copy.ddls.asddls
new file mode 100644
index 0000000..c358f6e
--- /dev/null
+++ b/src/12/01/z2ui5_a_smps_req_copy.ddls.asddls
@@ -0,0 +1,7 @@
+// The parameter of the factory action copyRequest of z2ui5_r_smps_req: the
+// title of the copy. Left empty, the copy keeps the title of its source.
+@EndUserText.label: 'abap2UI5 sample - copy parameter'
+define abstract entity z2ui5_a_smps_req_copy
+{
+ Title : abap.char(60);
+}
diff --git a/src/12/01/z2ui5_a_smps_req_copy.ddls.xml b/src/12/01/z2ui5_a_smps_req_copy.ddls.xml
new file mode 100644
index 0000000..0cd747b
--- /dev/null
+++ b/src/12/01/z2ui5_a_smps_req_copy.ddls.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+ Z2UI5_A_SMPS_REQ_COPY
+ E
+ abap2UI5 RAP sample - copy parameter
+ A
+
+
+
+
diff --git a/src/12/01/z2ui5_a_smps_req_create.ddls.asddls b/src/12/01/z2ui5_a_smps_req_create.ddls.asddls
new file mode 100644
index 0000000..ebb6e6d
--- /dev/null
+++ b/src/12/01/z2ui5_a_smps_req_create.ddls.asddls
@@ -0,0 +1,9 @@
+// The parameter of the static factory action createRequest of
+// z2ui5_r_smps_req - everything a new request is created from.
+@EndUserText.label: 'abap2UI5 sample - create parameter'
+define abstract entity z2ui5_a_smps_req_create
+{
+ Title : abap.char(60);
+ Quantity : abap.int4;
+ Priority : abap.char(1);
+}
diff --git a/src/12/01/z2ui5_a_smps_req_create.ddls.xml b/src/12/01/z2ui5_a_smps_req_create.ddls.xml
new file mode 100644
index 0000000..e90daf9
--- /dev/null
+++ b/src/12/01/z2ui5_a_smps_req_create.ddls.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+ Z2UI5_A_SMPS_REQ_CREATE
+ E
+ abap2UI5 RAP sample - create parameter
+ A
+
+
+
+
diff --git a/src/12/01/z2ui5_a_smps_req_reject.ddls.asddls b/src/12/01/z2ui5_a_smps_req_reject.ddls.asddls
new file mode 100644
index 0000000..8addd6d
--- /dev/null
+++ b/src/12/01/z2ui5_a_smps_req_reject.ddls.asddls
@@ -0,0 +1,7 @@
+// The parameter of the action reject of z2ui5_r_smps_req: why the request
+// was rejected. It is stored in the field Note of the request.
+@EndUserText.label: 'abap2UI5 sample - reject parameter'
+define abstract entity z2ui5_a_smps_req_reject
+{
+ Reason : abap.char(255);
+}
diff --git a/src/12/01/z2ui5_a_smps_req_reject.ddls.xml b/src/12/01/z2ui5_a_smps_req_reject.ddls.xml
new file mode 100644
index 0000000..de824e3
--- /dev/null
+++ b/src/12/01/z2ui5_a_smps_req_reject.ddls.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+ Z2UI5_A_SMPS_REQ_REJECT
+ E
+ abap2UI5 RAP sample - reject parameter
+ A
+
+
+
+
diff --git a/src/12/01/z2ui5_c_smps_req.bdef.asbdef b/src/12/01/z2ui5_c_smps_req.bdef.asbdef
new file mode 100644
index 0000000..d7970f0
--- /dev/null
+++ b/src/12/01/z2ui5_c_smps_req.bdef.asbdef
@@ -0,0 +1,14 @@
+// The projection exposes a SUBSET of z2ui5_r_smps_req: a consumer of this
+// contract may change a request and approve it - nothing else. Create,
+// delete, reject and the factory actions exist in the business object, but
+// not here, so an EML statement against z2ui5_c_smps_req that names them
+// does not even compile.
+projection;
+strict ( 2 );
+
+define behavior for z2ui5_c_smps_req alias Request
+{
+ use update;
+
+ use action approve;
+}
diff --git a/src/12/01/z2ui5_c_smps_req.bdef.xml b/src/12/01/z2ui5_c_smps_req.bdef.xml
new file mode 100644
index 0000000..62c7c61
--- /dev/null
+++ b/src/12/01/z2ui5_c_smps_req.bdef.xml
@@ -0,0 +1,45 @@
+
+
+
+
+
+ Z2UI5_C_SMPS_REQ
+ BDEF/BDO
+ abap2UI5 RAP sample - request projection
+ 60
+ EN
+
+ -
+ ./z2ui5_c_smps_req/source/main/versions
+ http://www.sap.com/adt/relations/versions
+ Historic versions
+
+ -
+ /sap/bc/adt/repository/informationsystem/abaplanguageversions?uri=%2Fsap%2Fbc%2Fadt%2Fbo%2Fbehaviordefinitions%2Fz2ui5_c_smps_req
+ http://www.sap.com/adt/relations/informationsystem/abaplanguageversions
+ application/vnd.sap.adt.nameditems.v1+xml
+ Allowed ABAP language versions
+
+ -
+ ./z2ui5_c_smps_req/source/main
+ http://www.sap.com/adt/relations/source
+ text/plain
+ Source Content
+
+ -
+ ./z2ui5_c_smps_req/source/main
+ http://www.sap.com/adt/relations/source
+ text/html
+ Source Content (HTML)
+
+
+ EN
+ X
+ ./z2ui5_c_smps_req/source/main
+ ABAP_SOURCE
+ true
+ true
+
+
+
+
diff --git a/src/12/01/z2ui5_c_smps_req.ddls.asddls b/src/12/01/z2ui5_c_smps_req.ddls.asddls
new file mode 100644
index 0000000..be4f1c4
--- /dev/null
+++ b/src/12/01/z2ui5_c_smps_req.ddls.asddls
@@ -0,0 +1,21 @@
+// The projection of z2ui5_r_smps_req - the second contract on the same
+// business object. Its behavior definition exposes less than the business
+// object offers, see z2ui5_c_smps_req.bdef.asbdef.
+@AccessControl.authorizationCheck: #NOT_REQUIRED
+@EndUserText.label: 'abap2UI5 RAP sample - request projection'
+@Metadata.ignorePropagatedAnnotations: true
+define root view entity z2ui5_c_smps_req
+ provider contract transactional_query
+ as projection on z2ui5_r_smps_req
+{
+ key RequestUuid,
+ Title,
+ Quantity,
+ Priority,
+ Status,
+ Note,
+ ApprovedVia,
+ CreatedBy,
+ CreatedAt,
+ LastChangedAt
+}
diff --git a/src/12/01/z2ui5_c_smps_req.ddls.baseinfo b/src/12/01/z2ui5_c_smps_req.ddls.baseinfo
new file mode 100644
index 0000000..4afbb47
--- /dev/null
+++ b/src/12/01/z2ui5_c_smps_req.ddls.baseinfo
@@ -0,0 +1,7 @@
+{
+ "BASEINFO": {
+ "FROM": [
+ "Z2UI5_R_SMPS_REQ"
+ ]
+ }
+}
diff --git a/src/12/01/z2ui5_c_smps_req.ddls.xml b/src/12/01/z2ui5_c_smps_req.ddls.xml
new file mode 100644
index 0000000..7d49e84
--- /dev/null
+++ b/src/12/01/z2ui5_c_smps_req.ddls.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+ Z2UI5_C_SMPS_REQ
+ E
+ abap2UI5 RAP sample - request projection
+ P
+
+
+
+
diff --git a/src/12/01/z2ui5_cl_smps_bp_req.clas.abap b/src/12/01/z2ui5_cl_smps_bp_req.clas.abap
new file mode 100644
index 0000000..15390e0
--- /dev/null
+++ b/src/12/01/z2ui5_cl_smps_bp_req.clas.abap
@@ -0,0 +1,10 @@
+"! abap2UI5 RAP sample - request behavior pool
+"! Behavior pool of the business object z2ui5_r_smps_req. The implementation
+"! lives in the local types include - see
+"! z2ui5_cl_smps_bp_req.clas.locals_imp.abap.
+CLASS z2ui5_cl_smps_bp_req DEFINITION PUBLIC ABSTRACT FINAL FOR BEHAVIOR OF z2ui5_r_smps_req.
+ENDCLASS.
+
+
+CLASS z2ui5_cl_smps_bp_req IMPLEMENTATION.
+ENDCLASS.
diff --git a/src/12/01/z2ui5_cl_smps_bp_req.clas.locals_imp.abap b/src/12/01/z2ui5_cl_smps_bp_req.clas.locals_imp.abap
new file mode 100644
index 0000000..9c76642
--- /dev/null
+++ b/src/12/01/z2ui5_cl_smps_bp_req.clas.locals_imp.abap
@@ -0,0 +1,313 @@
+CLASS lhc_request DEFINITION INHERITING FROM cl_abap_behavior_handler.
+
+ PRIVATE SECTION.
+
+ CONSTANTS:
+ BEGIN OF cs_status,
+ new TYPE c LENGTH 1 VALUE 'N',
+ approved TYPE c LENGTH 1 VALUE 'A',
+ rejected TYPE c LENGTH 1 VALUE 'R',
+ END OF cs_status.
+
+ METHODS get_global_authorizations FOR GLOBAL AUTHORIZATION
+ IMPORTING REQUEST requested_authorizations FOR request RESULT result.
+
+ METHODS get_instance_features FOR INSTANCE FEATURES
+ IMPORTING keys REQUEST requested_features FOR request RESULT result.
+
+ METHODS getdefaultsforcreate FOR READ
+ IMPORTING keys FOR FUNCTION request~getdefaultsforcreate RESULT result.
+
+ METHODS setinitialstatus FOR DETERMINE ON MODIFY
+ IMPORTING keys FOR request~setinitialstatus.
+
+ METHODS validatequantity FOR VALIDATE ON SAVE
+ IMPORTING keys FOR request~validatequantity.
+
+ METHODS approve FOR MODIFY
+ IMPORTING keys FOR ACTION request~approve RESULT result.
+
+ METHODS reject FOR MODIFY
+ IMPORTING keys FOR ACTION request~reject RESULT result.
+
+ METHODS copyrequest FOR MODIFY
+ IMPORTING keys FOR ACTION request~copyrequest.
+
+ METHODS createrequest FOR MODIFY
+ IMPORTING keys FOR ACTION request~createrequest.
+
+ENDCLASS.
+
+
+CLASS lhc_request IMPLEMENTATION.
+
+ METHOD get_global_authorizations.
+
+ " the sample deliberately grants everything - a real business object would
+ " check an authorization object here. Only what was asked for is answered.
+ IF requested_authorizations-%create = if_abap_behv=>mk-on.
+ result-%create = if_abap_behv=>auth-allowed.
+ ENDIF.
+
+ IF requested_authorizations-%update = if_abap_behv=>mk-on.
+ result-%update = if_abap_behv=>auth-allowed.
+ ENDIF.
+
+ IF requested_authorizations-%delete = if_abap_behv=>mk-on.
+ result-%delete = if_abap_behv=>auth-allowed.
+ ENDIF.
+
+ IF requested_authorizations-%action-approve = if_abap_behv=>mk-on.
+ result-%action-approve = if_abap_behv=>auth-allowed.
+ ENDIF.
+
+ IF requested_authorizations-%action-reject = if_abap_behv=>mk-on.
+ result-%action-reject = if_abap_behv=>auth-allowed.
+ ENDIF.
+
+ IF requested_authorizations-%action-copyrequest = if_abap_behv=>mk-on.
+ result-%action-copyrequest = if_abap_behv=>auth-allowed.
+ ENDIF.
+
+ IF requested_authorizations-%action-createrequest = if_abap_behv=>mk-on.
+ result-%action-createrequest = if_abap_behv=>auth-allowed.
+ ENDIF.
+
+ ENDMETHOD.
+
+
+ METHOD get_instance_features.
+
+ " Feature control answers ONE question per instance: what may happen to
+ " it right now. The runtime asks it before every update, delete and
+ " action - so a disabled operation is refused no matter who calls it - and
+ " a UI asks the same question with GET PERMISSIONS to grey out what would
+ " be refused anyway.
+ "
+ " requested_features says which of the answers the caller wants. Nothing
+ " here is expensive, so all four are computed - a check against it pays
+ " off once one of them needs a READ of its own.
+ READ ENTITIES OF z2ui5_r_smps_req IN LOCAL MODE
+ ENTITY request
+ FIELDS ( status )
+ WITH CORRESPONDING #( keys )
+ RESULT DATA(requests).
+
+ " A New request may be changed, approved, rejected and deleted. Once it
+ " is decided nothing changes any more - and an approved one is a record
+ " that stays: it cannot even be deleted, a rejected one can.
+ result = VALUE #( FOR request IN requests
+ LET open = COND #( WHEN request-status = cs_status-new
+ THEN if_abap_behv=>fc-o-enabled
+ ELSE if_abap_behv=>fc-o-disabled ) IN
+ ( %tky = request-%tky
+ %update = open
+ %delete = COND #( WHEN request-status = cs_status-approved
+ THEN if_abap_behv=>fc-o-disabled
+ ELSE if_abap_behv=>fc-o-enabled )
+ %action-approve = open
+ %action-reject = open ) ).
+
+ ENDMETHOD.
+
+
+ METHOD getdefaultsforcreate.
+
+ " A default function proposes the values of a new instance - it creates
+ " nothing. The caller reads the proposal, lets the user change it, and
+ " sends the CREATE itself. %param is a whole instance; whatever is left
+ " initial here has no proposal.
+ DATA(today) = cl_abap_context_info=>get_system_date( ).
+
+ result = VALUE #( FOR key IN keys
+ ( %cid = key-%cid
+ %param = VALUE #( title = |Request of { today DATE = ISO }|
+ quantity = 1
+ priority = 'M' ) ) ).
+
+ ENDMETHOD.
+
+
+ METHOD setinitialstatus.
+
+ READ ENTITIES OF z2ui5_r_smps_req IN LOCAL MODE
+ ENTITY request
+ FIELDS ( status )
+ WITH CORRESPONDING #( keys )
+ RESULT DATA(requests).
+
+ DELETE requests WHERE status IS NOT INITIAL.
+ IF requests IS INITIAL.
+ RETURN.
+ ENDIF.
+
+ MODIFY ENTITIES OF z2ui5_r_smps_req IN LOCAL MODE
+ ENTITY request
+ UPDATE FIELDS ( status )
+ WITH VALUE #( FOR request IN requests
+ ( %tky = request-%tky
+ status = cs_status-new ) )
+ REPORTED DATA(update_reported).
+
+ reported = CORRESPONDING #( DEEP update_reported ).
+
+ ENDMETHOD.
+
+
+ METHOD validatequantity.
+
+ READ ENTITIES OF z2ui5_r_smps_req IN LOCAL MODE
+ ENTITY request
+ FIELDS ( quantity )
+ WITH CORRESPONDING #( keys )
+ RESULT DATA(requests).
+
+ LOOP AT requests INTO DATA(request).
+
+ " %state_area groups the messages of one validation: the runtime clears
+ " the area before the validation runs again, so a corrected value does
+ " not keep the old message - which matters once the determine action
+ " checkRequest runs it on every change
+ APPEND VALUE #( %tky = request-%tky
+ %state_area = 'VALIDATE_QUANTITY' ) TO reported-request.
+
+ IF request-quantity BETWEEN 1 AND 1000.
+ CONTINUE.
+ ENDIF.
+
+ APPEND VALUE #( %tky = request-%tky ) TO failed-request.
+ APPEND VALUE #( %tky = request-%tky
+ %state_area = 'VALIDATE_QUANTITY'
+ %element-quantity = if_abap_behv=>mk-on
+ %msg = new_message_with_text(
+ severity = if_abap_behv_message=>severity-error
+ text = 'Quantity must be between 1 and 1000' ) ) TO reported-request.
+
+ ENDLOOP.
+
+ ENDMETHOD.
+
+
+ METHOD approve.
+
+ " cl_abap_behv_aux tells the handler HOW it was called: through the
+ " projection z2ui5_c_smps_req, or on the business object itself. The
+ " sample stores the answer, so the list shows which contract approved.
+ cl_abap_behv_aux=>get_current_context( IMPORTING from_projection = DATA(projection) ).
+
+ MODIFY ENTITIES OF z2ui5_r_smps_req IN LOCAL MODE
+ ENTITY request
+ UPDATE FIELDS ( status approvedvia )
+ WITH VALUE #( FOR key IN keys
+ ( %tky = key-%tky
+ status = cs_status-approved
+ approvedvia = COND #( WHEN projection IS INITIAL
+ THEN 'Z2UI5_R_SMPS_REQ'
+ ELSE projection ) ) )
+ FAILED DATA(update_failed)
+ REPORTED DATA(update_reported).
+
+ failed = CORRESPONDING #( DEEP update_failed ).
+ reported = CORRESPONDING #( DEEP update_reported ).
+
+ READ ENTITIES OF z2ui5_r_smps_req IN LOCAL MODE
+ ENTITY request
+ ALL FIELDS WITH CORRESPONDING #( keys )
+ RESULT DATA(requests).
+
+ result = VALUE #( FOR request IN requests
+ ( %tky = request-%tky
+ %param = request ) ).
+
+ ENDMETHOD.
+
+
+ METHOD reject.
+
+ " the parameter arrives per key in %param - one reason per request
+ LOOP AT keys INTO DATA(key) WHERE %param-reason IS INITIAL.
+
+ APPEND VALUE #( %tky = key-%tky ) TO failed-request.
+ APPEND VALUE #( %tky = key-%tky
+ %msg = new_message_with_text(
+ severity = if_abap_behv_message=>severity-error
+ text = 'A rejection needs a reason' ) ) TO reported-request.
+
+ ENDLOOP.
+
+ MODIFY ENTITIES OF z2ui5_r_smps_req IN LOCAL MODE
+ ENTITY request
+ UPDATE FIELDS ( status note )
+ WITH VALUE #( FOR k IN keys WHERE ( %param-reason IS NOT INITIAL )
+ ( %tky = k-%tky
+ status = cs_status-rejected
+ note = k-%param-reason ) ).
+
+ READ ENTITIES OF z2ui5_r_smps_req IN LOCAL MODE
+ ENTITY request
+ ALL FIELDS WITH VALUE #( FOR k IN keys WHERE ( %param-reason IS NOT INITIAL ) ( %tky = k-%tky ) )
+ RESULT DATA(requests).
+
+ result = VALUE #( FOR request IN requests
+ ( %tky = request-%tky
+ %param = request ) ).
+
+ ENDMETHOD.
+
+
+ METHOD copyrequest.
+
+ " A factory action creates a NEW instance from an existing one. The new
+ " instance is created under the %cid of the action call, so MAPPED reaches
+ " the caller under the very %cid it sent, together with the new key.
+ READ ENTITIES OF z2ui5_r_smps_req IN LOCAL MODE
+ ENTITY request
+ FIELDS ( title quantity priority )
+ WITH CORRESPONDING #( keys )
+ RESULT DATA(sources).
+
+ MODIFY ENTITIES OF z2ui5_r_smps_req IN LOCAL MODE
+ ENTITY request
+ CREATE FIELDS ( title quantity priority )
+ WITH VALUE #( FOR key IN keys
+ LET source = VALUE #( sources[ %tky = key-%tky ] OPTIONAL ) IN
+ ( %cid = key-%cid
+ title = COND #( WHEN key-%param-title IS NOT INITIAL
+ THEN key-%param-title
+ ELSE source-title )
+ quantity = source-quantity
+ priority = source-priority ) )
+ MAPPED DATA(create_mapped)
+ FAILED DATA(create_failed)
+ REPORTED DATA(create_reported).
+
+ mapped = CORRESPONDING #( DEEP create_mapped ).
+ failed = CORRESPONDING #( DEEP create_failed ).
+ reported = CORRESPONDING #( DEEP create_reported ).
+
+ ENDMETHOD.
+
+
+ METHOD createrequest.
+
+ " the static twin of copyRequest: no source instance, everything comes
+ " from the parameter
+ MODIFY ENTITIES OF z2ui5_r_smps_req IN LOCAL MODE
+ ENTITY request
+ CREATE FIELDS ( title quantity priority )
+ WITH VALUE #( FOR key IN keys
+ ( %cid = key-%cid
+ title = key-%param-title
+ quantity = key-%param-quantity
+ priority = key-%param-priority ) )
+ MAPPED DATA(create_mapped)
+ FAILED DATA(create_failed)
+ REPORTED DATA(create_reported).
+
+ mapped = CORRESPONDING #( DEEP create_mapped ).
+ failed = CORRESPONDING #( DEEP create_failed ).
+ reported = CORRESPONDING #( DEEP create_reported ).
+
+ ENDMETHOD.
+
+ENDCLASS.
diff --git a/src/12/01/z2ui5_cl_smps_bp_req.clas.xml b/src/12/01/z2ui5_cl_smps_bp_req.clas.xml
new file mode 100644
index 0000000..c8cef11
--- /dev/null
+++ b/src/12/01/z2ui5_cl_smps_bp_req.clas.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+ Z2UI5_CL_SMPS_BP_REQ
+ E
+ abap2UI5 RAP sample - request behavior pool
+ 06
+ 1
+ X
+ X
+ X
+ Z2UI5_R_SMPS_REQ
+
+
+
+
diff --git a/src/12/01/z2ui5_cl_smps_data_req.clas.abap b/src/12/01/z2ui5_cl_smps_data_req.clas.abap
new file mode 100644
index 0000000..e1389ce
--- /dev/null
+++ b/src/12/01/z2ui5_cl_smps_data_req.clas.abap
@@ -0,0 +1,112 @@
+"! abap2UI5 RAP sample - request demo data
+"! Demo data for the business object z2ui5_r_smps_req: three requests, two
+"! of them New and one Rejected.
+"!
+"! Run it in ADT with F9 - it is a console application - or press the button
+"! in one of the sample apps, which calls the same method. Everything goes
+"! through the business object with EML, the creates through its static
+"! factory action createRequest.
+CLASS z2ui5_cl_smps_data_req DEFINITION PUBLIC FINAL CREATE PUBLIC.
+
+ PUBLIC SECTION.
+ INTERFACES if_oo_adt_classrun.
+
+ "! Deletes every request the business object lets go of, then creates
+ "! the demo set. Approved requests stay: the feature control of the
+ "! business object refuses to delete them, and the demo data respects
+ "! that like any other caller.
+ CLASS-METHODS data_reset
+ RETURNING
+ VALUE(result) TYPE string.
+
+ PROTECTED SECTION.
+ PRIVATE SECTION.
+ENDCLASS.
+
+
+CLASS z2ui5_cl_smps_data_req IMPLEMENTATION.
+
+ METHOD if_oo_adt_classrun~main.
+
+ out->write( data_reset( ) ).
+
+ ENDMETHOD.
+
+
+ METHOD data_reset.
+
+ SELECT FROM z2ui5_r_smps_req
+ FIELDS RequestUuid
+ WHERE Status <> 'A'
+ ORDER BY RequestUuid
+ INTO TABLE @DATA(t_keys).
+
+ IF t_keys IS NOT INITIAL.
+
+ MODIFY ENTITIES OF z2ui5_r_smps_req
+ ENTITY request
+ DELETE FROM VALUE #( FOR s_key IN t_keys ( requestuuid = s_key-requestuuid ) )
+ FAILED DATA(s_failed_delete).
+
+ IF s_failed_delete IS NOT INITIAL.
+ ROLLBACK ENTITIES.
+ result = `Deletion refused by the business object - no demo data created.`.
+ RETURN.
+ ENDIF.
+
+ ENDIF.
+
+ MODIFY ENTITIES OF z2ui5_r_smps_req
+ ENTITY request
+ EXECUTE createrequest
+ FROM VALUE #( ( %cid = `DEMO_1` %param = VALUE #( title = 'Laptop for the new colleague' quantity = 1 priority = 'H' ) )
+ ( %cid = `DEMO_2` %param = VALUE #( title = 'Printer paper' quantity = 50 priority = 'L' ) )
+ ( %cid = `DEMO_3` %param = VALUE #( title = 'Monitor stands' quantity = 4 priority = 'M' ) ) )
+ MAPPED DATA(s_mapped)
+ FAILED DATA(s_failed).
+
+ IF s_failed IS NOT INITIAL.
+ ROLLBACK ENTITIES.
+ result = `Demo data rejected by the business object.`.
+ RETURN.
+ ENDIF.
+
+ COMMIT ENTITIES RESPONSE OF z2ui5_r_smps_req
+ FAILED DATA(s_failed_commit).
+
+ IF s_failed_commit IS NOT INITIAL.
+ ROLLBACK ENTITIES.
+ result = `Demo data rejected by the business object on commit.`.
+ RETURN.
+ ENDIF.
+
+ " the keys are known now - MAPPED carries them under the %cid sent above.
+ " One request is rejected, so a decided request is there from the start.
+ DATA(key_3) = VALUE #( s_mapped-request[ %cid = `DEMO_3` ]-requestuuid OPTIONAL ).
+
+ MODIFY ENTITIES OF z2ui5_r_smps_req
+ ENTITY request
+ EXECUTE reject FROM VALUE #( ( requestuuid = key_3
+ %param = VALUE #( reason = 'Not in this year''s budget' ) ) )
+ FAILED DATA(s_failed_reject).
+
+ IF s_failed_reject IS NOT INITIAL.
+ ROLLBACK ENTITIES.
+ result = `3 demo requests created, all of them New.`.
+ RETURN.
+ ENDIF.
+
+ COMMIT ENTITIES RESPONSE OF z2ui5_r_smps_req
+ FAILED DATA(s_failed_reject_commit).
+
+ IF s_failed_reject_commit IS NOT INITIAL.
+ ROLLBACK ENTITIES.
+ result = `3 demo requests created, all of them New.`.
+ RETURN.
+ ENDIF.
+
+ result = `3 demo requests created.`.
+
+ ENDMETHOD.
+
+ENDCLASS.
diff --git a/src/12/01/z2ui5_cl_smps_data_req.clas.xml b/src/12/01/z2ui5_cl_smps_data_req.clas.xml
new file mode 100644
index 0000000..546b59d
--- /dev/null
+++ b/src/12/01/z2ui5_cl_smps_data_req.clas.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+ Z2UI5_CL_SMPS_DATA_REQ
+ E
+ abap2UI5 RAP sample - request demo data
+ 1
+ X
+ X
+ X
+
+
+
+
diff --git a/src/12/01/z2ui5_cl_smps_perm_req.clas.abap b/src/12/01/z2ui5_cl_smps_perm_req.clas.abap
new file mode 100644
index 0000000..065eec7
--- /dev/null
+++ b/src/12/01/z2ui5_cl_smps_perm_req.clas.abap
@@ -0,0 +1,65 @@
+"! abap2UI5 RAP sample - request permissions
+"! Asks the business object z2ui5_r_smps_req which operations its feature
+"! control allows per request - the one GET PERMISSIONS statement of package
+"! 12, kept in a class of its own because abaplint has no grammar for it (see
+"! the noIssues entry in abaplint.jsonc). z2ui5_cl_smps_app_017 calls it.
+CLASS z2ui5_cl_smps_perm_req DEFINITION PUBLIC FINAL CREATE PUBLIC.
+
+ PUBLIC SECTION.
+
+ TYPES ty_t_uuid TYPE STANDARD TABLE OF z2ui5_t_smps_req-request_uuid WITH EMPTY KEY.
+
+ TYPES:
+ BEGIN OF ty_s_permission,
+ request_uuid TYPE z2ui5_t_smps_req-request_uuid,
+ update TYPE abap_bool,
+ delete TYPE abap_bool,
+ approve TYPE abap_bool,
+ reject TYPE abap_bool,
+ END OF ty_s_permission.
+ TYPES ty_t_permission TYPE STANDARD TABLE OF ty_s_permission WITH EMPTY KEY.
+
+ "! one row per request the business object knows - a key it does not
+ "! know is left out
+ CLASS-METHODS get
+ IMPORTING
+ t_uuid TYPE ty_t_uuid
+ RETURNING
+ VALUE(result) TYPE ty_t_permission.
+
+ PROTECTED SECTION.
+ PRIVATE SECTION.
+ENDCLASS.
+
+
+CLASS z2ui5_cl_smps_perm_req IMPLEMENTATION.
+
+ METHOD get.
+
+ IF t_uuid IS INITIAL.
+ RETURN.
+ ENDIF.
+
+ " ONLY INSTANCE FEATURES restricts the question to feature control - GET
+ " PERMISSIONS can also ask for the authorizations, global and per
+ " instance. REQUEST names what we want to know; what is not requested
+ " comes back initial.
+ GET PERMISSIONS ONLY INSTANCE FEATURES OF z2ui5_r_smps_req
+ ENTITY request
+ FROM VALUE #( FOR uuid IN t_uuid ( requestuuid = uuid ) )
+ REQUEST VALUE #( %update = if_abap_behv=>mk-on
+ %delete = if_abap_behv=>mk-on
+ %action-approve = if_abap_behv=>mk-on
+ %action-reject = if_abap_behv=>mk-on )
+ RESULT DATA(s_permissions).
+
+ result = VALUE #( FOR s_instance IN s_permissions-instances
+ ( request_uuid = s_instance-requestuuid
+ update = xsdbool( s_instance-%update = if_abap_behv=>fc-o-enabled )
+ delete = xsdbool( s_instance-%delete = if_abap_behv=>fc-o-enabled )
+ approve = xsdbool( s_instance-%action-approve = if_abap_behv=>fc-o-enabled )
+ reject = xsdbool( s_instance-%action-reject = if_abap_behv=>fc-o-enabled ) ) ).
+
+ ENDMETHOD.
+
+ENDCLASS.
diff --git a/src/12/01/z2ui5_cl_smps_perm_req.clas.xml b/src/12/01/z2ui5_cl_smps_perm_req.clas.xml
new file mode 100644
index 0000000..1a6c47d
--- /dev/null
+++ b/src/12/01/z2ui5_cl_smps_perm_req.clas.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+ Z2UI5_CL_SMPS_PERM_REQ
+ E
+ abap2UI5 RAP sample - request permissions
+ 1
+ X
+ X
+ X
+
+
+
+
diff --git a/src/12/01/z2ui5_r_smps_req.bdef.asbdef b/src/12/01/z2ui5_r_smps_req.bdef.asbdef
new file mode 100644
index 0000000..c24ac52
--- /dev/null
+++ b/src/12/01/z2ui5_r_smps_req.bdef.asbdef
@@ -0,0 +1,62 @@
+// Managed business object of package 12. A request is created New (N) and
+// ends Approved (A) or Rejected (R). Everything the samples
+// z2ui5_cl_smps_app_017 to z2ui5_cl_smps_app_020 call is declared here:
+//
+// - instance feature control: update, approve and reject are only enabled
+// while a request is New, and an Approved request cannot be deleted -
+// asked with GET PERMISSIONS
+// - an action with a parameter (reject), an instance factory action with a
+// parameter (copyRequest) and a static factory action (createRequest)
+// - a default function that proposes the values of a new request
+// - a determine action that runs a validation on demand, before the save
+managed implementation in class z2ui5_cl_smps_bp_req unique;
+strict ( 2 );
+
+define behavior for z2ui5_r_smps_req alias Request
+persistent table z2ui5_t_smps_req
+lock master
+authorization master ( global )
+etag master LastChangedAt
+{
+ create { default function GetDefaultsForCreate; }
+ update ( features : instance );
+ delete ( features : instance );
+
+ field ( numbering : managed, readonly ) RequestUuid;
+
+ // set by the determination and the actions, never by the caller
+ field ( readonly ) Status, Note, ApprovedVia;
+
+ // filled by the managed runtime from the @Semantics annotations of the CDS
+ field ( readonly ) CreatedBy, CreatedAt, LastChangedAt;
+
+ field ( mandatory ) Title;
+
+ determination setInitialStatus on modify { create; }
+
+ validation validateQuantity on save { create; update; field Quantity; }
+
+ // runs the validation NOW, without saving - the caller gets its messages
+ // in REPORTED while the user is still typing
+ determine action checkRequest { validation validateQuantity; }
+
+ action ( features : instance ) approve result [1] $self;
+ action ( features : instance ) reject parameter z2ui5_a_smps_req_reject result [1] $self;
+
+ factory action copyRequest parameter z2ui5_a_smps_req_copy [1];
+ static factory action createRequest parameter z2ui5_a_smps_req_create [1];
+
+ mapping for z2ui5_t_smps_req
+ {
+ RequestUuid = request_uuid;
+ Title = title;
+ Quantity = quantity;
+ Priority = priority;
+ Status = status;
+ Note = note;
+ ApprovedVia = approved_via;
+ CreatedBy = created_by;
+ CreatedAt = created_at;
+ LastChangedAt = last_changed_at;
+ }
+}
diff --git a/src/12/01/z2ui5_r_smps_req.bdef.xml b/src/12/01/z2ui5_r_smps_req.bdef.xml
new file mode 100644
index 0000000..05d5c96
--- /dev/null
+++ b/src/12/01/z2ui5_r_smps_req.bdef.xml
@@ -0,0 +1,45 @@
+
+
+
+
+
+ Z2UI5_R_SMPS_REQ
+ BDEF/BDO
+ abap2UI5 RAP sample - request
+ 60
+ EN
+
+ -
+ ./z2ui5_r_smps_req/source/main/versions
+ http://www.sap.com/adt/relations/versions
+ Historic versions
+
+ -
+ /sap/bc/adt/repository/informationsystem/abaplanguageversions?uri=%2Fsap%2Fbc%2Fadt%2Fbo%2Fbehaviordefinitions%2Fz2ui5_r_smps_req
+ http://www.sap.com/adt/relations/informationsystem/abaplanguageversions
+ application/vnd.sap.adt.nameditems.v1+xml
+ Allowed ABAP language versions
+
+ -
+ ./z2ui5_r_smps_req/source/main
+ http://www.sap.com/adt/relations/source
+ text/plain
+ Source Content
+
+ -
+ ./z2ui5_r_smps_req/source/main
+ http://www.sap.com/adt/relations/source
+ text/html
+ Source Content (HTML)
+
+
+ EN
+ X
+ ./z2ui5_r_smps_req/source/main
+ ABAP_SOURCE
+ true
+ true
+
+
+
+
diff --git a/src/12/01/z2ui5_r_smps_req.ddls.asddls b/src/12/01/z2ui5_r_smps_req.ddls.asddls
new file mode 100644
index 0000000..32d9c50
--- /dev/null
+++ b/src/12/01/z2ui5_r_smps_req.ddls.asddls
@@ -0,0 +1,22 @@
+@AccessControl.authorizationCheck: #NOT_REQUIRED
+@EndUserText.label: 'abap2UI5 RAP sample - request'
+@Metadata.ignorePropagatedAnnotations: true
+define root view entity z2ui5_r_smps_req
+ as select from z2ui5_t_smps_req
+{
+ key request_uuid as RequestUuid,
+
+ title as Title,
+ quantity as Quantity,
+ priority as Priority,
+ status as Status,
+ note as Note,
+ approved_via as ApprovedVia,
+
+ @Semantics.user.createdBy: true
+ created_by as CreatedBy,
+ @Semantics.systemDateTime.createdAt: true
+ created_at as CreatedAt,
+ @Semantics.systemDateTime.localInstanceLastChangedAt: true
+ last_changed_at as LastChangedAt
+}
diff --git a/src/12/01/z2ui5_r_smps_req.ddls.baseinfo b/src/12/01/z2ui5_r_smps_req.ddls.baseinfo
new file mode 100644
index 0000000..80e83c8
--- /dev/null
+++ b/src/12/01/z2ui5_r_smps_req.ddls.baseinfo
@@ -0,0 +1,7 @@
+{
+ "BASEINFO": {
+ "FROM": [
+ "Z2UI5_T_SMPS_REQ"
+ ]
+ }
+}
diff --git a/src/12/01/z2ui5_r_smps_req.ddls.xml b/src/12/01/z2ui5_r_smps_req.ddls.xml
new file mode 100644
index 0000000..3728652
--- /dev/null
+++ b/src/12/01/z2ui5_r_smps_req.ddls.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+ Z2UI5_R_SMPS_REQ
+ E
+ abap2UI5 RAP sample - request
+ W
+
+
+
+
diff --git a/src/12/01/z2ui5_t_smps_req.tabl.xml b/src/12/01/z2ui5_t_smps_req.tabl.xml
new file mode 100644
index 0000000..6b275d6
--- /dev/null
+++ b/src/12/01/z2ui5_t_smps_req.tabl.xml
@@ -0,0 +1,140 @@
+
+
+
+
+
+ Z2UI5_T_SMPS_REQ
+ E
+ TRANSP
+ X
+ abap2UI5 RAP sample - request
+ E
+ A
+ 1
+
+
+ Z2UI5_T_SMPS_REQ
+ A
+ 0
+ APPL1
+ N
+
+
+
+ CLIENT
+ X
+ 0
+ C
+ 000006
+ X
+ CLNT
+ 000003
+ CLNT
+
+
+ REQUEST_UUID
+ X
+ 0
+ X
+ 000016
+ X
+ RAW
+ 000016
+ RAW
+
+
+ TITLE
+ 0
+ C
+ 000120
+ X
+ CHAR
+ 000060
+ CHAR
+
+
+ QUANTITY
+ 0
+ X
+ 000004
+ X
+ INT4
+ 000010
+ INT4
+
+
+ PRIORITY
+ 0
+ C
+ 000002
+ X
+ CHAR
+ 000001
+ CHAR
+
+
+ STATUS
+ 0
+ C
+ 000002
+ X
+ CHAR
+ 000001
+ CHAR
+
+
+ NOTE
+ 0
+ C
+ 000510
+ X
+ CHAR
+ 000255
+ CHAR
+
+
+ APPROVED_VIA
+ 0
+ C
+ 000060
+ X
+ CHAR
+ 000030
+ CHAR
+
+
+ CREATED_BY
+ 0
+ C
+ 000024
+ X
+ CHAR
+ 000012
+ CHAR
+
+
+ CREATED_AT
+ 0
+ P
+ 000011
+ X
+ DEC
+ 000021
+ 000007
+ DEC
+
+
+ LAST_CHANGED_AT
+ 0
+ P
+ 000011
+ X
+ DEC
+ 000021
+ 000007
+ DEC
+
+
+
+
+
diff --git a/src/12/README.md b/src/12/README.md
new file mode 100644
index 0000000..952499e
--- /dev/null
+++ b/src/12/README.md
@@ -0,0 +1,96 @@
+# 12 — RAP Actions & Permissions
+
+*[← all packages](../../README.md)*
+
+A business object does more than create, update and delete. It decides what is
+allowed right now, offers actions that need input, proposes values for a new
+instance, and checks values before they are saved. This package shows all of it
+from the consumer's side, with EML, on a small business object that ships with
+it: `Z2UI5_R_SMPS_REQ`, a **request** that is created New and ends Approved or
+Rejected.
+
+Everything a Fiori Elements app gets from the same behavior definition is there
+for an abap2UI5 app too: the rules live in the business object, the app asks
+for them and builds its screen from the answer.
+
+## What you need
+
+**Release:** Cloud + Standard ≥ 8.16 (2025). The package is set to the release
+that carries all of its features. Instance feature control, actions with
+parameters and factory actions are older; the default function
+`GetDefaultsForCreate` and `cl_abap_behv_aux` are the young ones. If the
+business object does not activate on your system, this package is out of reach,
+and nothing else in this repository is affected.
+
+**Branch:** [`12-rap-actions`](https://github.com/abap2UI5/samples-stack/tree/12-rap-actions)
+— this package alone, without the other thirteen on your system.
+
+The business object, its projection and its table come with this package
+([`src/12/01`](01)). Fill the table with `Z2UI5_CL_SMPS_DATA_REQ` (F9 in ADT),
+with *Reset Demo Data* in any of the four samples, or with *Regenerate Demo
+Data* in the overview app. An approved request is never deleted: the feature
+control of the business object refuses it, and the demo data respects that like
+any other caller.
+
+## The business object
+
+| Object | What it is |
+|---|---|
+| `Z2UI5_T_SMPS_REQ` | the table |
+| `Z2UI5_R_SMPS_REQ` | root view entity and behavior definition, managed |
+| `Z2UI5_C_SMPS_REQ` | the projection — a second contract that exposes only `update` and `approve` |
+| `Z2UI5_A_SMPS_REQ_REJECT`, `_COPY`, `_CREATE` | abstract entities, the parameters of the actions |
+| `Z2UI5_CL_SMPS_BP_REQ` | the behavior pool |
+| `Z2UI5_CL_SMPS_PERM_REQ` | the one `GET PERMISSIONS` statement of the package, see below |
+| `Z2UI5_CL_SMPS_DATA_REQ` | demo data |
+
+What the behavior definition declares, and what the samples use:
+
+| Declaration | What it does |
+|---|---|
+| `update ( features : instance )`, `delete ( features : instance )`, `action ( features : instance ) approve / reject` | a New request may be changed, approved, rejected and deleted. A decided one may not be changed, and an approved one cannot even be deleted |
+| `action reject parameter z2ui5_a_smps_req_reject` | an action that needs input: the reason, stored in `Note` |
+| `factory action copyRequest parameter z2ui5_a_smps_req_copy [1]` | creates a new request from an existing one |
+| `static factory action createRequest parameter z2ui5_a_smps_req_create [1]` | creates a request from its parameter alone |
+| `create { default function GetDefaultsForCreate; }` | proposes the values of a new request |
+| `validation validateQuantity on save` + `determine action checkRequest { validation validateQuantity; }` | the quantity must be between 1 and 1000 — checked at the save, or on demand through the determine action |
+
+## The samples
+
+| You want to | Statement | Sample |
+|---|---|---|
+| ask what is allowed and grey out the rest | `GET PERMISSIONS ONLY INSTANCE FEATURES` | [`017`](z2ui5_cl_smps_app_017.clas.abap) |
+| propose, create, copy and reject with parameters | `READ … EXECUTE getdefaultsforcreate`, `EXECUTE createrequest / copyrequest / reject` with `%param` | [`018`](z2ui5_cl_smps_app_018.clas.abap) |
+| check a value while the user types, without saving | `UPDATE` + `EXECUTE checkrequest`, then `ROLLBACK ENTITIES` | [`019`](z2ui5_cl_smps_app_019.clas.abap) |
+| use the same business object through two contracts | EML on `Z2UI5_C_SMPS_REQ` and on `Z2UI5_R_SMPS_REQ`, `cl_abap_behv_aux` in the handler | [`020`](z2ui5_cl_smps_app_020.clas.abap) |
+
+**Ask, then act — but the business object decides.** `017` greys out what the
+feature control would refuse. Its switch *Ignore permissions* enables every
+button anyway: press one on a decided request and the business object answers
+with `FAILED`. The UI mirrors the rule, it does not enforce it.
+
+**A factory action answers in `MAPPED`.** It has no `RESULT`: the new instance
+comes back in `MAPPED`, under the `%cid` the caller sent.
+
+**A default function changes nothing.** It is called with `READ ENTITIES …
+EXECUTE`, and its `RESULT` carries a whole instance in `%param` — the proposal.
+The create is a second step, sent by the caller.
+
+**A determine action checks without saving.** `019` writes the new value into
+the transactional buffer, runs `checkRequest` in the same statement, reads the
+messages from `REPORTED` and rolls back. Save runs the validation once more —
+that one counts.
+
+## Why `GET PERMISSIONS` sits in a class of its own
+
+abaplint has no grammar for `GET PERMISSIONS` at any language version, so a file
+containing it does not parse. `Z2UI5_CL_SMPS_PERM_REQ` holds that one statement
+and nothing else, and is listed in `noIssues` in `abaplint.jsonc` — the same way
+the RAP event handler of [`05`](../05/README.md) is. The sample app that calls
+it, `Z2UI5_CL_SMPS_APP_017`, stays fully checked.
+
+## Where to go next
+
+- [`03` RAP](../03/README.md) — the basic EML statements, one per sample.
+- [`11` RAP File Upload](../11/README.md) — a deep action parameter that carries
+ a file.
diff --git a/src/12/package.devc.xml b/src/12/package.devc.xml
new file mode 100644
index 0000000..0d5b084
--- /dev/null
+++ b/src/12/package.devc.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+ RAP Actions and Permissions
+
+
+
+
diff --git a/src/12/z2ui5_cl_smps_app_017.clas.abap b/src/12/z2ui5_cl_smps_app_017.clas.abap
new file mode 100644
index 0000000..ee7dad1
--- /dev/null
+++ b/src/12/z2ui5_cl_smps_app_017.clas.abap
@@ -0,0 +1,364 @@
+" @keywords rap eml feature control instance features get permissions enabled disabled buttons
+" @summary GET PERMISSIONS asks the business object what may happen to each request - the buttons follow its answer, and the business object refuses the rest anyway
+"! RAP Actions - Feature Control with GET PERMISSIONS
+"! The business object z2ui5_r_smps_req decides per instance what may happen
+"! to it: a New request may be changed, approved, rejected and deleted, a
+"! decided one may not - and an approved one cannot even be deleted. That is
+"! instance feature control, implemented in get_instance_features of the
+"! behavior pool.
+"!
+"! The runtime asks it before every operation, so a disabled one is refused
+"! whoever calls it. A UI asks the very same question up front, so it can
+"! grey out what would be refused:
+"!
+"! GET PERMISSIONS ONLY INSTANCE FEATURES OF z2ui5_r_smps_req
+"! ENTITY request
+"! FROM VALUE #( ( requestuuid = ... ) )
+"! REQUEST VALUE #( %update = if_abap_behv=>mk-on
+"! %delete = if_abap_behv=>mk-on
+"! %action-approve = if_abap_behv=>mk-on
+"! %action-reject = if_abap_behv=>mk-on )
+"! RESULT DATA(s_permissions)
+"! FAILED DATA(s_failed)
+"! REPORTED DATA(s_reported).
+"!
+"! The switch "Ignore permissions" enables every button anyway - press one on
+"! a decided request and the business object answers with FAILED: the UI only
+"! mirrors the rule, the business object enforces it.
+CLASS z2ui5_cl_smps_app_017 DEFINITION PUBLIC.
+
+ PUBLIC SECTION.
+ INTERFACES z2ui5_if_app.
+
+ TYPES:
+ BEGIN OF ty_s_request,
+ request_uuid TYPE string,
+ title TYPE string,
+ quantity TYPE string,
+ status TYPE string,
+ status_state TYPE string,
+ note TYPE string,
+ "! the answer of GET PERMISSIONS, one flag per operation
+ update_enabled TYPE abap_bool,
+ delete_enabled TYPE abap_bool,
+ approve_enabled TYPE abap_bool,
+ reject_enabled TYPE abap_bool,
+ END OF ty_s_request.
+ TYPES ty_t_request TYPE STANDARD TABLE OF ty_s_request WITH EMPTY KEY.
+ DATA t_requests TYPE ty_t_request.
+
+ "! enables every button, whatever the business object says
+ DATA ignore_permissions TYPE abap_bool.
+
+ PROTECTED SECTION.
+ DATA client TYPE REF TO z2ui5_if_client.
+
+ METHODS on_event.
+ METHODS data_read.
+ METHODS permissions_read.
+ METHODS request_approve.
+ METHODS request_reject.
+ METHODS request_delete.
+ METHODS data_save
+ RETURNING
+ VALUE(result) TYPE abap_bool.
+ METHODS view_display.
+
+ PRIVATE SECTION.
+ENDCLASS.
+
+
+CLASS z2ui5_cl_smps_app_017 IMPLEMENTATION.
+
+ METHOD z2ui5_if_app~main.
+
+ me->client = client.
+ IF client->check_on_init( ).
+ data_read( ).
+ view_display( ).
+ ELSEIF client->check_on_navigated( ).
+ view_display( ).
+ ELSEIF client->check_on_event( ).
+ on_event( ).
+ ENDIF.
+
+ ENDMETHOD.
+
+
+ METHOD on_event.
+
+ CASE client->get_event( ).
+
+ WHEN `APPROVE`.
+ request_approve( ).
+
+ WHEN `REJECT`.
+ request_reject( ).
+
+ WHEN `DELETE`.
+ request_delete( ).
+
+ WHEN `REFRESH`.
+ data_read( ).
+
+ WHEN `GENERATE`.
+ client->message_toast_display( z2ui5_cl_smps_data_req=>data_reset( ) ).
+ data_read( ).
+
+ WHEN `IGNORE`.
+ " the switch changes nothing in the business object - it only decides
+ " whether the buttons follow the permissions, see view_display( )
+ view_display( ).
+
+ ENDCASE.
+
+ ENDMETHOD.
+
+
+ METHOD data_read.
+
+ SELECT FROM z2ui5_r_smps_req "#EC CI_NOWHERE
+ FIELDS RequestUuid,
+ Title,
+ Quantity,
+ Status,
+ Note
+ ORDER BY CreatedAt DESCENDING
+ INTO TABLE @DATA(t_result) UP TO 50 ROWS.
+
+ t_requests = VALUE #( FOR s_result IN t_result
+ ( request_uuid = |{ s_result-requestuuid }|
+ title = |{ s_result-title }|
+ quantity = |{ s_result-quantity }|
+ status = SWITCH #( s_result-status
+ WHEN 'N' THEN `New`
+ WHEN 'A' THEN `Approved`
+ WHEN 'R' THEN `Rejected` )
+ status_state = SWITCH #( s_result-status
+ WHEN 'N' THEN `Information`
+ WHEN 'A' THEN `Success`
+ WHEN 'R' THEN `Error` )
+ note = |{ s_result-note }| ) ).
+
+ permissions_read( ).
+
+ ENDMETHOD.
+
+
+ METHOD permissions_read.
+
+ " the question itself sits in z2ui5_cl_smps_perm_req - one GET
+ " PERMISSIONS statement for the whole list, see the class header
+ DATA(t_permissions) = z2ui5_cl_smps_perm_req=>get(
+ VALUE #( FOR s_row IN t_requests ( CONV #( s_row-request_uuid ) ) ) ).
+
+ LOOP AT t_requests REFERENCE INTO DATA(row).
+
+ DATA(s_permission) = VALUE #( t_permissions[ request_uuid = CONV #( row->request_uuid ) ] OPTIONAL ).
+ row->update_enabled = s_permission-update.
+ row->delete_enabled = s_permission-delete.
+ row->approve_enabled = s_permission-approve.
+ row->reject_enabled = s_permission-reject.
+
+ ENDLOOP.
+
+ ENDMETHOD.
+
+
+ METHOD request_approve.
+
+ DATA(request_uuid) = CONV z2ui5_t_smps_req-request_uuid( client->get_event_arg( ) ).
+
+ " the action is refused for a decided request - the feature control runs
+ " before the handler, and FAILED says why (%fail-cause = disabled)
+ MODIFY ENTITIES OF z2ui5_r_smps_req
+ ENTITY request
+ EXECUTE approve FROM VALUE #( ( requestuuid = request_uuid ) )
+ FAILED DATA(s_failed)
+ REPORTED DATA(s_reported).
+
+ IF s_failed-request IS NOT INITIAL.
+ ROLLBACK ENTITIES.
+ z2ui5_cl_smps_context=>msg_display( client = client val = s_failed ).
+ RETURN.
+ ENDIF.
+
+ IF data_save( ).
+ client->message_toast_display( `Request approved` ).
+ ENDIF.
+ z2ui5_cl_smps_context=>msg_display( client = client val = s_reported-request ).
+
+ ENDMETHOD.
+
+
+ METHOD request_reject.
+
+ DATA(request_uuid) = CONV z2ui5_t_smps_req-request_uuid( client->get_event_arg( ) ).
+
+ " the reason is a parameter of the action - z2ui5_cl_smps_app_018 asks
+ " the user for it, this sample passes a fixed one
+ MODIFY ENTITIES OF z2ui5_r_smps_req
+ ENTITY request
+ EXECUTE reject FROM VALUE #( ( requestuuid = request_uuid
+ %param = VALUE #( reason = 'Rejected in the feature control sample' ) ) )
+ FAILED DATA(s_failed)
+ REPORTED DATA(s_reported).
+
+ IF s_failed-request IS NOT INITIAL.
+ ROLLBACK ENTITIES.
+ z2ui5_cl_smps_context=>msg_display( client = client val = s_failed ).
+ RETURN.
+ ENDIF.
+
+ IF data_save( ).
+ client->message_toast_display( `Request rejected` ).
+ ENDIF.
+ z2ui5_cl_smps_context=>msg_display( client = client val = s_reported-request ).
+
+ ENDMETHOD.
+
+
+ METHOD request_delete.
+
+ DATA(request_uuid) = CONV z2ui5_t_smps_req-request_uuid( client->get_event_arg( ) ).
+
+ MODIFY ENTITIES OF z2ui5_r_smps_req
+ ENTITY request
+ DELETE FROM VALUE #( ( requestuuid = request_uuid ) )
+ FAILED DATA(s_failed).
+
+ IF s_failed-request IS NOT INITIAL.
+ ROLLBACK ENTITIES.
+ z2ui5_cl_smps_context=>msg_display( client = client val = s_failed ).
+ RETURN.
+ ENDIF.
+
+ IF data_save( ).
+ client->message_toast_display( `Request deleted` ).
+ ENDIF.
+
+ ENDMETHOD.
+
+
+ METHOD data_save.
+
+ COMMIT ENTITIES RESPONSE OF z2ui5_r_smps_req
+ FAILED DATA(s_failed)
+ REPORTED DATA(s_reported).
+
+ IF s_failed IS INITIAL.
+ result = abap_true.
+ ELSE.
+ ROLLBACK ENTITIES.
+ z2ui5_cl_smps_context=>msg_display( client = client val = s_reported-request ).
+ ENDIF.
+
+ " the list shows what was saved - and the permissions that follow from it
+ data_read( ).
+
+ ENDMETHOD.
+
+
+ METHOD view_display.
+
+ DATA(view) = z2ui5_cl_ui5_view_builder=>factory(
+ )->ele( n = `View` ns = `mvc`
+ )->a( n = `displayBlock` v = `true`
+ )->a( n = `height` v = `100%`
+ )->a( n = `xmlns` v = `sap.m`
+ )->a( n = `xmlns:mvc` v = `sap.ui.core.mvc` ).
+ DATA(page) = view->ele( `Shell`
+ )->ele( `Page`
+ )->a( n = `title` v = `abap2UI5 - RAP Actions - 17 Feature Control with GET PERMISSIONS`
+ )->a( n = `showNavButton` b = client->check_app_prev_stack( )
+ )->a( n = `navButtonPress` v = client->_event_nav_app_leave( ) ).
+
+ page->tag( `MessageStrip`
+ )->a( n = `text` v = `The buttons of each row follow GET PERMISSIONS: a New request may be approved, rejected and ` &&
+ `deleted, a decided one may not, and an approved one stays for good. Switch on Ignore ` &&
+ `permissions and try anyway - the business object refuses it on its own.`
+ )->a( n = `type` v = `Information`
+ )->a( n = `showIcon` b = abap_true
+ )->a( n = `class` v = `sapUiSmallMargin` ).
+
+ " the expression binding decides per row: the permission, or - with the
+ " switch on - always true
+ DATA(enabled) = COND string( WHEN ignore_permissions = abap_true THEN `true` ).
+
+ DATA(table) = page->ele( `Table`
+ )->a( n = `items` v = client->_bind( t_requests )
+ )->a( n = `noDataText` v = `No requests yet - press Reset Demo Data` ).
+ table->ele( `headerToolbar`
+ )->ele( `Toolbar`
+ )->tag( `Title`
+ )->a( n = `text` v = `Requests (Z2UI5_R_SMPS_REQ)`
+ )->tag( `ToolbarSpacer`
+ )->tag( `Label`
+ )->a( n = `text` v = `Ignore permissions`
+ )->tag( `Switch`
+ )->a( n = `state` v = client->_bind( ignore_permissions )
+ )->a( n = `change` v = client->_event( `IGNORE` )
+ )->tag( `Button`
+ )->a( n = `press` v = client->_event( `GENERATE` )
+ )->a( n = `text` v = `Reset Demo Data`
+ )->a( n = `tooltip` v = `Deletes every request the business object lets go of, then creates three new ones`
+ )->tag( `Button`
+ )->a( n = `press` v = client->_event( `REFRESH` )
+ )->a( n = `icon` v = `sap-icon://refresh`
+ )->a( n = `tooltip` v = `Refresh` ).
+
+ table->ele( `columns`
+ )->ele( `Column`
+ )->tag( `Text`
+ )->a( n = `text` v = `Title`
+ )->end(
+ )->ele( `Column`
+ )->tag( `Text`
+ )->a( n = `text` v = `Quantity`
+ )->end(
+ )->ele( `Column`
+ )->tag( `Text`
+ )->a( n = `text` v = `Status`
+ )->end(
+ )->ele( `Column`
+ )->tag( `Text`
+ )->a( n = `text` v = `Note`
+ )->end(
+ )->ele( `Column`
+ )->tag( `Text`
+ )->a( n = `text` v = `Actions` ).
+
+ table->ele( `items`
+ )->ele( `ColumnListItem`
+ )->ele( `cells`
+ )->tag( `Text`
+ )->a( n = `text` v = `{TITLE}`
+ )->tag( `Text`
+ )->a( n = `text` v = `{QUANTITY}`
+ )->tag( `ObjectStatus`
+ )->a( n = `text` v = `{STATUS}`
+ )->a( n = `state` v = `{= ${STATUS_STATE} || 'None' }`
+ )->tag( `Text`
+ )->a( n = `text` v = `{NOTE}`
+ )->ele( `HBox`
+ )->tag( `Button`
+ )->a( n = `press` v = client->_event( val = `APPROVE` arg = `${REQUEST_UUID}` )
+ )->a( n = `icon` v = `sap-icon://accept`
+ )->a( n = `tooltip` v = `Approve`
+ )->a( n = `enabled` v = COND #( WHEN enabled IS INITIAL THEN `{APPROVE_ENABLED}` ELSE enabled )
+ )->tag( `Button`
+ )->a( n = `press` v = client->_event( val = `REJECT` arg = `${REQUEST_UUID}` )
+ )->a( n = `icon` v = `sap-icon://decline`
+ )->a( n = `tooltip` v = `Reject`
+ )->a( n = `enabled` v = COND #( WHEN enabled IS INITIAL THEN `{REJECT_ENABLED}` ELSE enabled )
+ )->tag( `Button`
+ )->a( n = `press` v = client->_event( val = `DELETE` arg = `${REQUEST_UUID}` )
+ )->a( n = `icon` v = `sap-icon://delete`
+ )->a( n = `tooltip` v = `Delete`
+ )->a( n = `enabled` v = COND #( WHEN enabled IS INITIAL THEN `{DELETE_ENABLED}` ELSE enabled ) ).
+
+ client->view_display( view->stringify( ) ).
+
+ ENDMETHOD.
+
+ENDCLASS.
diff --git a/src/12/z2ui5_cl_smps_app_017.clas.xml b/src/12/z2ui5_cl_smps_app_017.clas.xml
new file mode 100644
index 0000000..03dc477
--- /dev/null
+++ b/src/12/z2ui5_cl_smps_app_017.clas.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+ Z2UI5_CL_SMPS_APP_017
+ E
+ RAP Actions - Feature Control with GET PERMISSIONS
+ 1
+ X
+ X
+ X
+
+
+
+
diff --git a/src/12/z2ui5_cl_smps_app_018.clas.abap b/src/12/z2ui5_cl_smps_app_018.clas.abap
new file mode 100644
index 0000000..b255450
--- /dev/null
+++ b/src/12/z2ui5_cl_smps_app_018.clas.abap
@@ -0,0 +1,483 @@
+" @keywords rap eml action parameter abstract entity factory action default function popup create copy reject
+" @summary Actions with a parameter - a default function proposes a new request, a static factory action creates it, a factory action copies one, and reject asks for its reason in a popup
+"! RAP Actions - Parameters, Factory Actions, Defaults
+"! Four ways into the business object z2ui5_r_smps_req that all carry a
+"! PARAMETER - an abstract entity, filled in a popup and handed over in
+"! %param:
+"!
+"! " the default function proposes the values of a new request
+"! READ ENTITIES OF z2ui5_r_smps_req
+"! ENTITY request
+"! EXECUTE getdefaultsforcreate FROM VALUE #( ( %cid = `DEFAULTS` ) )
+"! RESULT DATA(t_defaults).
+"!
+"! " a static factory action creates a request from its parameter
+"! MODIFY ENTITIES OF z2ui5_r_smps_req
+"! ENTITY request
+"! EXECUTE createrequest FROM VALUE #( ( %cid = `CREATE_1` %param = ... ) )
+"! MAPPED DATA(s_mapped) ...
+"!
+"! " an instance factory action creates a copy of an existing one
+"! EXECUTE copyrequest FROM VALUE #( ( %cid = `COPY_1` requestuuid = ... %param = ... ) )
+"!
+"! " an ordinary action with a parameter
+"! EXECUTE reject FROM VALUE #( ( requestuuid = ... %param = VALUE #( reason = ... ) ) )
+"!
+"! A factory action returns no RESULT - the new instance comes back in MAPPED,
+"! under the %cid the caller sent.
+CLASS z2ui5_cl_smps_app_018 DEFINITION PUBLIC.
+
+ PUBLIC SECTION.
+ INTERFACES z2ui5_if_app.
+
+ TYPES:
+ BEGIN OF ty_s_request,
+ request_uuid TYPE string,
+ title TYPE string,
+ quantity TYPE string,
+ priority TYPE string,
+ status TYPE string,
+ note TYPE string,
+ END OF ty_s_request.
+ TYPES ty_t_request TYPE STANDARD TABLE OF ty_s_request WITH EMPTY KEY.
+ DATA t_requests TYPE ty_t_request.
+
+ TYPES:
+ "! what the popup edits - the parameter of the action it was opened for
+ BEGIN OF ty_s_popup,
+ title TYPE string,
+ quantity TYPE i,
+ priority TYPE string,
+ reason TYPE string,
+ END OF ty_s_popup.
+ DATA s_popup TYPE ty_s_popup.
+
+ PROTECTED SECTION.
+ DATA client TYPE REF TO z2ui5_if_client.
+
+ CONSTANTS:
+ "! which action the open popup belongs to
+ BEGIN OF cs_mode,
+ create TYPE string VALUE `CREATE`,
+ copy TYPE string VALUE `COPY`,
+ reject TYPE string VALUE `REJECT`,
+ END OF cs_mode.
+
+ "! the popup that is open, and the request it was opened for
+ DATA popup_mode TYPE string.
+ DATA request_uuid TYPE string.
+
+ METHODS on_event.
+ METHODS popup_open_create.
+ METHODS popup_confirm.
+ METHODS request_create.
+ METHODS request_copy.
+ METHODS request_reject.
+ METHODS data_save
+ RETURNING
+ VALUE(result) TYPE abap_bool.
+ METHODS data_read.
+ METHODS view_display.
+ METHODS popup_display.
+
+ PRIVATE SECTION.
+ENDCLASS.
+
+
+CLASS z2ui5_cl_smps_app_018 IMPLEMENTATION.
+
+ METHOD z2ui5_if_app~main.
+
+ me->client = client.
+ IF client->check_on_init( ).
+ data_read( ).
+ view_display( ).
+ ELSEIF client->check_on_navigated( ).
+ view_display( ).
+ ELSEIF client->check_on_event( ).
+ on_event( ).
+ ENDIF.
+
+ ENDMETHOD.
+
+
+ METHOD on_event.
+
+ CASE client->get_event( ).
+
+ WHEN `CREATE`.
+ popup_open_create( ).
+
+ WHEN `COPY`.
+ popup_mode = cs_mode-copy.
+ request_uuid = client->get_event_arg( ).
+ s_popup = VALUE #( title = |{ client->get_event_arg( 2 ) } (copy)| ).
+ popup_display( ).
+
+ WHEN `REJECT`.
+ popup_mode = cs_mode-reject.
+ request_uuid = client->get_event_arg( ).
+ s_popup = VALUE #( ).
+ popup_display( ).
+
+ WHEN `POPUP_CONFIRM`.
+ popup_confirm( ).
+
+ WHEN `POPUP_CANCEL`.
+ client->popup_destroy( ).
+
+ WHEN `REFRESH`.
+ data_read( ).
+
+ WHEN `GENERATE`.
+ client->message_toast_display( z2ui5_cl_smps_data_req=>data_reset( ) ).
+ data_read( ).
+
+ ENDCASE.
+
+ ENDMETHOD.
+
+
+ METHOD popup_open_create.
+
+ " A default function is a FUNCTION: it reads, it changes nothing. It is
+ " called with READ ... EXECUTE, and its RESULT carries a whole instance
+ " in %param - the proposal the popup opens with.
+ READ ENTITIES OF z2ui5_r_smps_req
+ ENTITY request
+ EXECUTE getdefaultsforcreate FROM VALUE #( ( %cid = `DEFAULTS` ) )
+ RESULT DATA(t_defaults).
+
+ DATA(s_default) = VALUE #( t_defaults[ 1 ]-%param OPTIONAL ).
+ s_popup = VALUE #( title = |{ s_default-title }|
+ quantity = s_default-quantity
+ priority = |{ s_default-priority }| ).
+
+ popup_mode = cs_mode-create.
+ popup_display( ).
+
+ ENDMETHOD.
+
+
+ METHOD popup_confirm.
+
+ CASE popup_mode.
+ WHEN cs_mode-create.
+ request_create( ).
+ WHEN cs_mode-copy.
+ request_copy( ).
+ WHEN cs_mode-reject.
+ request_reject( ).
+ ENDCASE.
+
+ ENDMETHOD.
+
+
+ METHOD request_create.
+
+ " a static factory action needs no instance - like a CREATE, but the
+ " business object decides what the parameter becomes
+ MODIFY ENTITIES OF z2ui5_r_smps_req
+ ENTITY request
+ EXECUTE createrequest
+ FROM VALUE #( ( %cid = `CREATE_1`
+ %param = VALUE #( title = s_popup-title
+ quantity = s_popup-quantity
+ priority = s_popup-priority ) ) )
+ MAPPED DATA(s_mapped)
+ FAILED DATA(s_failed)
+ REPORTED DATA(s_reported).
+
+ IF s_failed-request IS NOT INITIAL.
+ ROLLBACK ENTITIES.
+ z2ui5_cl_smps_context=>msg_display( client = client val = s_reported-request ).
+ RETURN.
+ ENDIF.
+
+ IF data_save( ).
+ client->popup_destroy( ).
+ client->message_toast_display( COND #( WHEN s_mapped-request IS INITIAL
+ THEN `Request created`
+ ELSE `Request created - its key came back in MAPPED` ) ).
+ ENDIF.
+
+ ENDMETHOD.
+
+
+ METHOD request_copy.
+
+ " an instance factory action: the key names the source, %cid names the
+ " copy - MAPPED returns the copy's new key under that %cid
+ MODIFY ENTITIES OF z2ui5_r_smps_req
+ ENTITY request
+ EXECUTE copyrequest
+ FROM VALUE #( ( %cid = `COPY_1`
+ requestuuid = CONV #( request_uuid )
+ %param = VALUE #( title = s_popup-title ) ) )
+ MAPPED DATA(s_mapped)
+ FAILED DATA(s_failed)
+ REPORTED DATA(s_reported).
+
+ IF s_failed-request IS NOT INITIAL.
+ ROLLBACK ENTITIES.
+ z2ui5_cl_smps_context=>msg_display( client = client val = s_reported-request ).
+ RETURN.
+ ENDIF.
+
+ IF data_save( ).
+ client->popup_destroy( ).
+ client->message_toast_display( COND #( WHEN s_mapped-request IS INITIAL
+ THEN `Request copied`
+ ELSE `Request copied - the new key came back in MAPPED` ) ).
+ ENDIF.
+
+ ENDMETHOD.
+
+
+ METHOD request_reject.
+
+ " an empty reason is not refused here - the business object refuses it,
+ " and its message is what the user sees
+ MODIFY ENTITIES OF z2ui5_r_smps_req
+ ENTITY request
+ EXECUTE reject
+ FROM VALUE #( ( requestuuid = CONV #( request_uuid )
+ %param = VALUE #( reason = s_popup-reason ) ) )
+ FAILED DATA(s_failed)
+ REPORTED DATA(s_reported).
+
+ IF s_failed-request IS NOT INITIAL.
+ ROLLBACK ENTITIES.
+ z2ui5_cl_smps_context=>msg_display( client = client val = s_reported-request ).
+ RETURN.
+ ENDIF.
+
+ IF data_save( ).
+ client->popup_destroy( ).
+ client->message_toast_display( `Request rejected` ).
+ ENDIF.
+
+ ENDMETHOD.
+
+
+ METHOD data_save.
+
+ " the validation of the quantity runs here, at the save - a factory
+ " action that came back clean can still be refused now
+ COMMIT ENTITIES RESPONSE OF z2ui5_r_smps_req
+ FAILED DATA(s_failed)
+ REPORTED DATA(s_reported).
+
+ IF s_failed IS INITIAL.
+ result = abap_true.
+ data_read( ).
+ ELSE.
+ ROLLBACK ENTITIES.
+ z2ui5_cl_smps_context=>msg_display( client = client val = s_reported-request ).
+ ENDIF.
+
+ ENDMETHOD.
+
+
+ METHOD data_read.
+
+ SELECT FROM z2ui5_r_smps_req "#EC CI_NOWHERE
+ FIELDS RequestUuid,
+ Title,
+ Quantity,
+ Priority,
+ Status,
+ Note
+ ORDER BY CreatedAt DESCENDING
+ INTO TABLE @DATA(t_result) UP TO 50 ROWS.
+
+ t_requests = VALUE #( FOR s_result IN t_result
+ ( request_uuid = |{ s_result-requestuuid }|
+ title = |{ s_result-title }|
+ quantity = |{ s_result-quantity }|
+ priority = SWITCH #( s_result-priority
+ WHEN 'H' THEN `High`
+ WHEN 'M' THEN `Medium`
+ WHEN 'L' THEN `Low` )
+ status = SWITCH #( s_result-status
+ WHEN 'N' THEN `New`
+ WHEN 'A' THEN `Approved`
+ WHEN 'R' THEN `Rejected` )
+ note = |{ s_result-note }| ) ).
+
+ ENDMETHOD.
+
+
+ METHOD view_display.
+
+ DATA(view) = z2ui5_cl_ui5_view_builder=>factory(
+ )->ele( n = `View` ns = `mvc`
+ )->a( n = `displayBlock` v = `true`
+ )->a( n = `height` v = `100%`
+ )->a( n = `xmlns` v = `sap.m`
+ )->a( n = `xmlns:mvc` v = `sap.ui.core.mvc` ).
+ DATA(page) = view->ele( `Shell`
+ )->ele( `Page`
+ )->a( n = `title` v = `abap2UI5 - RAP Actions - 18 Parameters, Factory Actions, Defaults`
+ )->a( n = `showNavButton` b = client->check_app_prev_stack( )
+ )->a( n = `navButtonPress` v = client->_event_nav_app_leave( ) ).
+
+ page->tag( `MessageStrip`
+ )->a( n = `text` v = `Create opens with the proposal of the default function and creates through a static ` &&
+ `factory action, Copy runs a factory action on the row, Reject an action whose parameter ` &&
+ `is the reason - every popup fills an abstract entity that travels in %param.`
+ )->a( n = `type` v = `Information`
+ )->a( n = `showIcon` b = abap_true
+ )->a( n = `class` v = `sapUiSmallMargin` ).
+
+ DATA(table) = page->ele( `Table`
+ )->a( n = `items` v = client->_bind( t_requests )
+ )->a( n = `noDataText` v = `No requests yet - press Create or Reset Demo Data` ).
+ table->ele( `headerToolbar`
+ )->ele( `Toolbar`
+ )->tag( `Title`
+ )->a( n = `text` v = `Requests (Z2UI5_R_SMPS_REQ)`
+ )->tag( `ToolbarSpacer`
+ )->tag( `Button`
+ )->a( n = `press` v = client->_event( `CREATE` )
+ )->a( n = `text` v = `Create`
+ )->a( n = `icon` v = `sap-icon://add`
+ )->a( n = `type` v = `Emphasized`
+ )->tag( `Button`
+ )->a( n = `press` v = client->_event( `GENERATE` )
+ )->a( n = `text` v = `Reset Demo Data`
+ )->a( n = `tooltip` v = `Deletes every request the business object lets go of, then creates three new ones`
+ )->tag( `Button`
+ )->a( n = `press` v = client->_event( `REFRESH` )
+ )->a( n = `icon` v = `sap-icon://refresh`
+ )->a( n = `tooltip` v = `Refresh` ).
+
+ table->ele( `columns`
+ )->ele( `Column`
+ )->tag( `Text`
+ )->a( n = `text` v = `Title`
+ )->end(
+ )->ele( `Column`
+ )->tag( `Text`
+ )->a( n = `text` v = `Quantity`
+ )->end(
+ )->ele( `Column`
+ )->tag( `Text`
+ )->a( n = `text` v = `Priority`
+ )->end(
+ )->ele( `Column`
+ )->tag( `Text`
+ )->a( n = `text` v = `Status`
+ )->end(
+ )->ele( `Column`
+ )->tag( `Text`
+ )->a( n = `text` v = `Note`
+ )->end(
+ )->ele( `Column`
+ )->tag( `Text`
+ )->a( n = `text` v = `Actions` ).
+
+ table->ele( `items`
+ )->ele( `ColumnListItem`
+ )->ele( `cells`
+ )->tag( `Text`
+ )->a( n = `text` v = `{TITLE}`
+ )->tag( `Text`
+ )->a( n = `text` v = `{QUANTITY}`
+ )->tag( `Text`
+ )->a( n = `text` v = `{PRIORITY}`
+ )->tag( `Text`
+ )->a( n = `text` v = `{STATUS}`
+ )->tag( `Text`
+ )->a( n = `text` v = `{NOTE}`
+ )->ele( `HBox`
+ )->tag( `Button`
+ )->a( n = `press` v = client->_event( val = `COPY` t_arg = VALUE #( ( `${REQUEST_UUID}` ) ( `${TITLE}` ) ) )
+ )->a( n = `icon` v = `sap-icon://copy`
+ )->a( n = `tooltip` v = `Copy - factory action copyRequest`
+ )->tag( `Button`
+ )->a( n = `press` v = client->_event( val = `REJECT` arg = `${REQUEST_UUID}` )
+ )->a( n = `icon` v = `sap-icon://decline`
+ )->a( n = `tooltip` v = `Reject - action with a parameter` ).
+
+ client->view_display( view->stringify( ) ).
+
+ ENDMETHOD.
+
+
+ METHOD popup_display.
+
+ DATA(popup) = z2ui5_cl_ui5_view_builder=>factory(
+ )->ele( n = `FragmentDefinition` ns = `core`
+ )->a( n = `xmlns` v = `sap.m`
+ )->a( n = `xmlns:core` v = `sap.ui.core`
+ )->a( n = `xmlns:form` v = `sap.ui.layout.form` ).
+ DATA(dialog) = popup->ele( `Dialog`
+ )->a( n = `title` v = SWITCH #( popup_mode
+ WHEN cs_mode-create THEN `Create - static factory action createRequest`
+ WHEN cs_mode-copy THEN `Copy - factory action copyRequest`
+ WHEN cs_mode-reject THEN `Reject - action with a parameter` )
+ )->a( n = `contentWidth` v = `30rem` ).
+
+ DATA(content) = dialog->ele( n = `SimpleForm` ns = `form`
+ )->a( n = `editable` b = abap_true
+ )->ele( n = `content` ns = `form` ).
+
+ CASE popup_mode.
+
+ WHEN cs_mode-create.
+ content->tag( `Label`
+ )->a( n = `text` v = `Title`
+ )->tag( `Input`
+ )->a( n = `value` v = client->_bind( s_popup-title )
+ )->tag( `Label`
+ )->a( n = `text` v = `Quantity`
+ )->tag( `StepInput`
+ )->a( n = `value` v = client->_bind( s_popup-quantity )
+ )->a( n = `min` v = `0`
+ )->tag( `Label`
+ )->a( n = `text` v = `Priority`
+ )->ele( `SegmentedButton`
+ )->a( n = `selectedKey` v = client->_bind( s_popup-priority )
+ )->ele( `items`
+ )->tag( `SegmentedButtonItem`
+ )->a( n = `key` v = `L`
+ )->a( n = `text` v = `Low`
+ )->tag( `SegmentedButtonItem`
+ )->a( n = `key` v = `M`
+ )->a( n = `text` v = `Medium`
+ )->tag( `SegmentedButtonItem`
+ )->a( n = `key` v = `H`
+ )->a( n = `text` v = `High` ).
+
+ WHEN cs_mode-copy.
+ content->tag( `Label`
+ )->a( n = `text` v = `Title of the copy`
+ )->tag( `Input`
+ )->a( n = `value` v = client->_bind( s_popup-title ) ).
+
+ WHEN cs_mode-reject.
+ content->tag( `Label`
+ )->a( n = `text` v = `Reason`
+ )->tag( `TextArea`
+ )->a( n = `value` v = client->_bind( s_popup-reason )
+ )->a( n = `placeholder` v = `required - the business object refuses an empty one`
+ )->a( n = `rows` v = `3` ).
+
+ ENDCASE.
+
+ dialog->ele( `beginButton`
+ )->tag( `Button`
+ )->a( n = `press` v = client->_event( `POPUP_CONFIRM` )
+ )->a( n = `text` v = `OK`
+ )->a( n = `type` v = `Emphasized` ).
+ dialog->ele( `endButton`
+ )->tag( `Button`
+ )->a( n = `press` v = client->_event( `POPUP_CANCEL` )
+ )->a( n = `text` v = `Cancel` ).
+
+ client->popup_display( popup->stringify( ) ).
+
+ ENDMETHOD.
+
+ENDCLASS.
diff --git a/src/12/z2ui5_cl_smps_app_018.clas.xml b/src/12/z2ui5_cl_smps_app_018.clas.xml
new file mode 100644
index 0000000..bb301a6
--- /dev/null
+++ b/src/12/z2ui5_cl_smps_app_018.clas.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+ Z2UI5_CL_SMPS_APP_018
+ E
+ RAP Actions - Parameters, Factory Actions, Defaults
+ 1
+ X
+ X
+ X
+
+
+
+
diff --git a/src/12/z2ui5_cl_smps_app_019.clas.abap b/src/12/z2ui5_cl_smps_app_019.clas.abap
new file mode 100644
index 0000000..7004039
--- /dev/null
+++ b/src/12/z2ui5_cl_smps_app_019.clas.abap
@@ -0,0 +1,248 @@
+" @keywords rap eml determine action validation check on change valuestate messages without saving
+" @summary A determine action runs the validation of the business object while the user types - the quantity is checked on every change, and nothing is saved until Save
+"! RAP Actions - Validate on Change with a Determine Action
+"! Validations run at the save - a user who types a wrong value hears about it
+"! only when pressing Save. A DETERMINE ACTION runs them on demand instead.
+"! The business object z2ui5_r_smps_req declares
+"!
+"! determine action checkRequest { validation validateQuantity; }
+"!
+"! and the app calls it on every change of the quantity, with the new value
+"! in the transactional buffer:
+"!
+"! MODIFY ENTITIES OF z2ui5_r_smps_req
+"! ENTITY request
+"! UPDATE FIELDS ( quantity ) WITH VALUE #( ( requestuuid = ... quantity = ... ) )
+"! EXECUTE checkrequest FROM VALUE #( ( requestuuid = ... ) )
+"! REPORTED DATA(s_reported).
+"!
+"! ROLLBACK ENTITIES.
+"!
+"! The messages of the validation come back in REPORTED, with %element naming
+"! the field - the app puts them on the input as its value state. The
+"! ROLLBACK discards the change again: the check saves nothing, Save does.
+CLASS z2ui5_cl_smps_app_019 DEFINITION PUBLIC.
+
+ PUBLIC SECTION.
+ INTERFACES z2ui5_if_app.
+
+ "! the request being edited
+ DATA title TYPE string.
+ DATA quantity TYPE i.
+
+ "! what the last check said - bound to the input
+ DATA value_state TYPE string.
+ DATA value_state_text TYPE string.
+
+ PROTECTED SECTION.
+ DATA client TYPE REF TO z2ui5_if_client.
+ DATA request_uuid TYPE string.
+
+ METHODS on_event.
+ METHODS request_load.
+ METHODS request_check.
+ METHODS request_save.
+ METHODS view_display.
+
+ PRIVATE SECTION.
+ENDCLASS.
+
+
+CLASS z2ui5_cl_smps_app_019 IMPLEMENTATION.
+
+ METHOD z2ui5_if_app~main.
+
+ me->client = client.
+ IF client->check_on_init( ).
+ request_load( ).
+ view_display( ).
+ ELSEIF client->check_on_navigated( ).
+ view_display( ).
+ ELSEIF client->check_on_event( ).
+ on_event( ).
+ ENDIF.
+
+ ENDMETHOD.
+
+
+ METHOD on_event.
+
+ CASE client->get_event( ).
+
+ WHEN `CHECK`.
+ request_check( ).
+
+ WHEN `SAVE`.
+ request_save( ).
+
+ WHEN `NEXT`.
+ request_load( ).
+ view_display( ).
+
+ WHEN `GENERATE`.
+ client->message_toast_display( z2ui5_cl_smps_data_req=>data_reset( ) ).
+ request_load( ).
+ view_display( ).
+
+ ENDCASE.
+
+ ENDMETHOD.
+
+
+ METHOD request_load.
+
+ " the newest New request - only those may be changed, the feature
+ " control of the business object refuses an update of a decided one
+ SELECT FROM z2ui5_r_smps_req
+ FIELDS RequestUuid,
+ Title,
+ Quantity
+ WHERE Status = 'N'
+ ORDER BY CreatedAt DESCENDING
+ INTO TABLE @DATA(t_result) UP TO 1 ROWS.
+
+ DATA(s_result) = VALUE #( t_result[ 1 ] OPTIONAL ).
+ request_uuid = |{ s_result-requestuuid }|.
+ title = |{ s_result-title }|.
+ quantity = s_result-quantity.
+ value_state = `None`.
+ value_state_text = ``.
+
+ ENDMETHOD.
+
+
+ METHOD request_check.
+
+ " the change goes into the transactional buffer, the determine action
+ " runs the validation against it - in ONE statement, so the validation
+ " sees the new value
+ MODIFY ENTITIES OF z2ui5_r_smps_req
+ ENTITY request
+ UPDATE FIELDS ( quantity )
+ WITH VALUE #( ( requestuuid = CONV #( request_uuid )
+ quantity = quantity ) )
+ EXECUTE checkrequest
+ FROM VALUE #( ( requestuuid = CONV #( request_uuid ) ) )
+ REPORTED DATA(s_reported).
+
+ " nothing is saved - the check only asks. Without the ROLLBACK the change
+ " would stay in the buffer and abap2UI5's COMMIT WORK at the end of the
+ " roundtrip would run into it.
+ ROLLBACK ENTITIES.
+
+ " a message on the field quantity is what the validation reports - the
+ " %element flag says which field it belongs to
+ DATA(s_message) = VALUE #( s_reported-request[ %element-quantity = if_abap_behv=>mk-on ] OPTIONAL ).
+
+ IF s_message-%msg IS BOUND.
+ value_state = `Error`.
+ value_state_text = s_message-%msg->if_message~get_text( ).
+ ELSE.
+ value_state = `Success`.
+ value_state_text = `The business object accepts this quantity`.
+ ENDIF.
+
+ ENDMETHOD.
+
+
+ METHOD request_save.
+
+ MODIFY ENTITIES OF z2ui5_r_smps_req
+ ENTITY request
+ UPDATE FIELDS ( quantity )
+ WITH VALUE #( ( requestuuid = CONV #( request_uuid )
+ quantity = quantity ) )
+ FAILED DATA(s_failed)
+ REPORTED DATA(s_reported).
+
+ IF s_failed-request IS NOT INITIAL.
+ ROLLBACK ENTITIES.
+ z2ui5_cl_smps_context=>msg_display( client = client val = s_reported-request ).
+ RETURN.
+ ENDIF.
+
+ " the same validation once more, now at the save - it is the one that
+ " counts, the check above only asked early
+ COMMIT ENTITIES RESPONSE OF z2ui5_r_smps_req
+ FAILED DATA(s_failed_commit)
+ REPORTED DATA(s_reported_commit).
+
+ IF s_failed_commit IS NOT INITIAL.
+ ROLLBACK ENTITIES.
+ z2ui5_cl_smps_context=>msg_display( client = client val = s_reported_commit-request ).
+ RETURN.
+ ENDIF.
+
+ client->message_toast_display( |Quantity { quantity } saved| ).
+
+ ENDMETHOD.
+
+
+ METHOD view_display.
+
+ DATA(view) = z2ui5_cl_ui5_view_builder=>factory(
+ )->ele( n = `View` ns = `mvc`
+ )->a( n = `displayBlock` v = `true`
+ )->a( n = `height` v = `100%`
+ )->a( n = `xmlns` v = `sap.m`
+ )->a( n = `xmlns:mvc` v = `sap.ui.core.mvc`
+ )->a( n = `xmlns:form` v = `sap.ui.layout.form` ).
+ DATA(page) = view->ele( `Shell`
+ )->ele( `Page`
+ )->a( n = `title` v = `abap2UI5 - RAP Actions - 19 Validate on Change`
+ )->a( n = `showNavButton` b = client->check_app_prev_stack( )
+ )->a( n = `navButtonPress` v = client->_event_nav_app_leave( ) ).
+
+ page->tag( `MessageStrip`
+ )->a( n = `text` v = `Change the quantity: every change runs the determine action checkRequest, which runs the ` &&
+ `validation of the business object without saving - try 0 or 5000. Save stores the value.`
+ )->a( n = `type` v = `Information`
+ )->a( n = `showIcon` b = abap_true
+ )->a( n = `class` v = `sapUiSmallMargin` ).
+
+ IF request_uuid IS INITIAL.
+ page->tag( `MessageStrip`
+ )->a( n = `text` v = `There is no New request to change - press Reset Demo Data below`
+ )->a( n = `type` v = `Warning`
+ )->a( n = `class` v = `sapUiSmallMargin` ).
+ ENDIF.
+
+ page->ele( n = `SimpleForm` ns = `form`
+ )->a( n = `title` v = `EXECUTE checkRequest - a determine action`
+ )->a( n = `editable` b = abap_true
+ )->ele( n = `content` ns = `form`
+ )->tag( `Label`
+ )->a( n = `text` v = `Request`
+ )->tag( `Text`
+ )->a( n = `text` v = client->_bind( title )
+ )->tag( `Label`
+ )->a( n = `text` v = `Quantity`
+ )->tag( `Input`
+ )->a( n = `value` v = client->_bind( quantity )
+ )->a( n = `type` v = `Number`
+ )->a( n = `valueLiveUpdate` b = abap_true
+ )->a( n = `valueState` v = client->_bind( value_state )
+ )->a( n = `valueStateText` v = client->_bind( value_state_text )
+ )->a( n = `change` v = client->_event( `CHECK` )
+ )->a( n = `enabled` b = xsdbool( request_uuid IS NOT INITIAL ) ).
+
+ page->ele( `footer`
+ )->ele( `OverflowToolbar`
+ )->tag( `Button`
+ )->a( n = `press` v = client->_event( `GENERATE` )
+ )->a( n = `text` v = `Reset Demo Data`
+ )->tag( `Button`
+ )->a( n = `press` v = client->_event( `NEXT` )
+ )->a( n = `text` v = `Reload`
+ )->tag( `ToolbarSpacer`
+ )->tag( `Button`
+ )->a( n = `press` v = client->_event( `SAVE` )
+ )->a( n = `text` v = `Save`
+ )->a( n = `type` v = `Emphasized`
+ )->a( n = `enabled` b = xsdbool( request_uuid IS NOT INITIAL ) ).
+
+ client->view_display( view->stringify( ) ).
+
+ ENDMETHOD.
+
+ENDCLASS.
diff --git a/src/12/z2ui5_cl_smps_app_019.clas.xml b/src/12/z2ui5_cl_smps_app_019.clas.xml
new file mode 100644
index 0000000..72bd3ef
--- /dev/null
+++ b/src/12/z2ui5_cl_smps_app_019.clas.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+ Z2UI5_CL_SMPS_APP_019
+ E
+ RAP Actions - Validate on Change with a Determine Action
+ 1
+ X
+ X
+ X
+
+
+
+
diff --git a/src/12/z2ui5_cl_smps_app_020.clas.abap b/src/12/z2ui5_cl_smps_app_020.clas.abap
new file mode 100644
index 0000000..2f25d65
--- /dev/null
+++ b/src/12/z2ui5_cl_smps_app_020.clas.abap
@@ -0,0 +1,267 @@
+" @keywords rap eml projection behavior definition contract cl_abap_behv_aux interface consumer approve
+" @summary One business object, two contracts - approve through the projection Z2UI5_C_SMPS_REQ or on the business object itself, and the handler records which one it was
+"! RAP Actions - One Business Object, Two Contracts
+"! A projection is a second contract on the same business object. Its
+"! behavior definition z2ui5_c_smps_req exposes a SUBSET of what
+"! z2ui5_r_smps_req offers - update and approve, nothing else:
+"!
+"! projection;
+"! define behavior for z2ui5_c_smps_req alias Request
+"! {
+"! use update;
+"! use action approve;
+"! }
+"!
+"! EML works against both. What the projection does not expose cannot even be
+"! written: an EXECUTE reject on z2ui5_c_smps_req does not compile.
+"!
+"! MODIFY ENTITIES OF z2ui5_c_smps_req
+"! ENTITY request
+"! EXECUTE approve FROM VALUE #( ( requestuuid = ... ) ) ...
+"!
+"! MODIFY ENTITIES OF z2ui5_r_smps_req
+"! ENTITY request
+"! EXECUTE approve FROM VALUE #( ( requestuuid = ... ) ) ...
+"!
+"! Both end in the same handler. It asks cl_abap_behv_aux which contract it
+"! was called through and stores the answer in ApprovedVia - the column of the
+"! list below.
+CLASS z2ui5_cl_smps_app_020 DEFINITION PUBLIC.
+
+ PUBLIC SECTION.
+ INTERFACES z2ui5_if_app.
+
+ TYPES:
+ BEGIN OF ty_s_request,
+ request_uuid TYPE string,
+ title TYPE string,
+ status TYPE string,
+ approved_via TYPE string,
+ is_new TYPE abap_bool,
+ END OF ty_s_request.
+ TYPES ty_t_request TYPE STANDARD TABLE OF ty_s_request WITH EMPTY KEY.
+ DATA t_requests TYPE ty_t_request.
+
+ PROTECTED SECTION.
+ DATA client TYPE REF TO z2ui5_if_client.
+
+ METHODS on_event.
+ METHODS approve_via_projection.
+ METHODS approve_via_business_object.
+ METHODS data_save
+ RETURNING
+ VALUE(result) TYPE abap_bool.
+ METHODS data_read.
+ METHODS view_display.
+
+ PRIVATE SECTION.
+ENDCLASS.
+
+
+CLASS z2ui5_cl_smps_app_020 IMPLEMENTATION.
+
+ METHOD z2ui5_if_app~main.
+
+ me->client = client.
+ IF client->check_on_init( ).
+ data_read( ).
+ view_display( ).
+ ELSEIF client->check_on_navigated( ).
+ view_display( ).
+ ELSEIF client->check_on_event( ).
+ on_event( ).
+ ENDIF.
+
+ ENDMETHOD.
+
+
+ METHOD on_event.
+
+ CASE client->get_event( ).
+
+ WHEN `APPROVE_C`.
+ approve_via_projection( ).
+
+ WHEN `APPROVE_R`.
+ approve_via_business_object( ).
+
+ WHEN `REFRESH`.
+ data_read( ).
+
+ WHEN `GENERATE`.
+ client->message_toast_display( z2ui5_cl_smps_data_req=>data_reset( ) ).
+ data_read( ).
+
+ ENDCASE.
+
+ ENDMETHOD.
+
+
+ METHOD approve_via_projection.
+
+ " the entity is the projection - this statement only compiles for what
+ " z2ui5_c_smps_req exposes
+ MODIFY ENTITIES OF z2ui5_c_smps_req
+ ENTITY request
+ EXECUTE approve FROM VALUE #( ( requestuuid = CONV #( client->get_event_arg( ) ) ) )
+ FAILED DATA(s_failed)
+ REPORTED DATA(s_reported).
+
+ IF s_failed-request IS NOT INITIAL.
+ ROLLBACK ENTITIES.
+ z2ui5_cl_smps_context=>msg_display( client = client val = s_reported-request ).
+ RETURN.
+ ENDIF.
+
+ IF data_save( ).
+ client->message_toast_display( `Approved through the projection Z2UI5_C_SMPS_REQ` ).
+ ENDIF.
+
+ ENDMETHOD.
+
+
+ METHOD approve_via_business_object.
+
+ MODIFY ENTITIES OF z2ui5_r_smps_req
+ ENTITY request
+ EXECUTE approve FROM VALUE #( ( requestuuid = CONV #( client->get_event_arg( ) ) ) )
+ FAILED DATA(s_failed)
+ REPORTED DATA(s_reported).
+
+ IF s_failed-request IS NOT INITIAL.
+ ROLLBACK ENTITIES.
+ z2ui5_cl_smps_context=>msg_display( client = client val = s_reported-request ).
+ RETURN.
+ ENDIF.
+
+ IF data_save( ).
+ client->message_toast_display( `Approved on the business object Z2UI5_R_SMPS_REQ` ).
+ ENDIF.
+
+ ENDMETHOD.
+
+
+ METHOD data_save.
+
+ " one save for both contracts - the transactional buffer belongs to the
+ " business object, the projection only shapes the access to it
+ COMMIT ENTITIES RESPONSE OF z2ui5_r_smps_req
+ FAILED DATA(s_failed)
+ REPORTED DATA(s_reported).
+
+ IF s_failed IS INITIAL.
+ result = abap_true.
+ data_read( ).
+ ELSE.
+ ROLLBACK ENTITIES.
+ z2ui5_cl_smps_context=>msg_display( client = client val = s_reported-request ).
+ ENDIF.
+
+ ENDMETHOD.
+
+
+ METHOD data_read.
+
+ " read through the projection - it is a CDS view like any other
+ SELECT FROM z2ui5_c_smps_req "#EC CI_NOWHERE
+ FIELDS RequestUuid,
+ Title,
+ Status,
+ ApprovedVia
+ ORDER BY CreatedAt DESCENDING
+ INTO TABLE @DATA(t_result) UP TO 50 ROWS.
+
+ t_requests = VALUE #( FOR s_result IN t_result
+ ( request_uuid = |{ s_result-requestuuid }|
+ title = |{ s_result-title }|
+ status = SWITCH #( s_result-status
+ WHEN 'N' THEN `New`
+ WHEN 'A' THEN `Approved`
+ WHEN 'R' THEN `Rejected` )
+ approved_via = |{ s_result-approvedvia }|
+ is_new = xsdbool( s_result-status = 'N' ) ) ).
+
+ ENDMETHOD.
+
+
+ METHOD view_display.
+
+ DATA(view) = z2ui5_cl_ui5_view_builder=>factory(
+ )->ele( n = `View` ns = `mvc`
+ )->a( n = `displayBlock` v = `true`
+ )->a( n = `height` v = `100%`
+ )->a( n = `xmlns` v = `sap.m`
+ )->a( n = `xmlns:mvc` v = `sap.ui.core.mvc` ).
+ DATA(page) = view->ele( `Shell`
+ )->ele( `Page`
+ )->a( n = `title` v = `abap2UI5 - RAP Actions - 20 One Business Object, Two Contracts`
+ )->a( n = `showNavButton` b = client->check_app_prev_stack( )
+ )->a( n = `navButtonPress` v = client->_event_nav_app_leave( ) ).
+
+ page->tag( `MessageStrip`
+ )->a( n = `text` v = `Approve a New request through the projection Z2UI5_C_SMPS_REQ or on the business object ` &&
+ `Z2UI5_R_SMPS_REQ - the same handler runs, asks cl_abap_behv_aux which contract called it ` &&
+ `and writes the answer into Approved via.`
+ )->a( n = `type` v = `Information`
+ )->a( n = `showIcon` b = abap_true
+ )->a( n = `class` v = `sapUiSmallMargin` ).
+
+ DATA(table) = page->ele( `Table`
+ )->a( n = `items` v = client->_bind( t_requests )
+ )->a( n = `noDataText` v = `No requests yet - press Reset Demo Data` ).
+ table->ele( `headerToolbar`
+ )->ele( `Toolbar`
+ )->tag( `Title`
+ )->a( n = `text` v = `Requests (Z2UI5_C_SMPS_REQ)`
+ )->tag( `ToolbarSpacer`
+ )->tag( `Button`
+ )->a( n = `press` v = client->_event( `GENERATE` )
+ )->a( n = `text` v = `Reset Demo Data`
+ )->a( n = `tooltip` v = `Deletes every request the business object lets go of, then creates three new ones`
+ )->tag( `Button`
+ )->a( n = `press` v = client->_event( `REFRESH` )
+ )->a( n = `icon` v = `sap-icon://refresh`
+ )->a( n = `tooltip` v = `Refresh` ).
+
+ table->ele( `columns`
+ )->ele( `Column`
+ )->tag( `Text`
+ )->a( n = `text` v = `Title`
+ )->end(
+ )->ele( `Column`
+ )->tag( `Text`
+ )->a( n = `text` v = `Status`
+ )->end(
+ )->ele( `Column`
+ )->tag( `Text`
+ )->a( n = `text` v = `Approved via`
+ )->end(
+ )->ele( `Column`
+ )->tag( `Text`
+ )->a( n = `text` v = `Approve` ).
+
+ table->ele( `items`
+ )->ele( `ColumnListItem`
+ )->ele( `cells`
+ )->tag( `Text`
+ )->a( n = `text` v = `{TITLE}`
+ )->tag( `Text`
+ )->a( n = `text` v = `{STATUS}`
+ )->tag( `Text`
+ )->a( n = `text` v = `{APPROVED_VIA}`
+ )->ele( `HBox`
+ )->tag( `Button`
+ )->a( n = `press` v = client->_event( val = `APPROVE_C` arg = `${REQUEST_UUID}` )
+ )->a( n = `text` v = `via projection`
+ )->a( n = `enabled` v = `{IS_NEW}`
+ )->a( n = `class` v = `sapUiTinyMarginEnd`
+ )->tag( `Button`
+ )->a( n = `press` v = client->_event( val = `APPROVE_R` arg = `${REQUEST_UUID}` )
+ )->a( n = `text` v = `on the business object`
+ )->a( n = `enabled` v = `{IS_NEW}` ).
+
+ client->view_display( view->stringify( ) ).
+
+ ENDMETHOD.
+
+ENDCLASS.
diff --git a/src/12/z2ui5_cl_smps_app_020.clas.xml b/src/12/z2ui5_cl_smps_app_020.clas.xml
new file mode 100644
index 0000000..9fcdaa5
--- /dev/null
+++ b/src/12/z2ui5_cl_smps_app_020.clas.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+ Z2UI5_CL_SMPS_APP_020
+ E
+ RAP Actions - One Business Object, Two Contracts
+ 1
+ X
+ X
+ X
+
+
+
+
diff --git a/src/13/README.md b/src/13/README.md
new file mode 100644
index 0000000..e2cbc81
--- /dev/null
+++ b/src/13/README.md
@@ -0,0 +1,63 @@
+# 13 — Excel & E-Mail
+
+*[← all packages](../../README.md)*
+
+Spreadsheets in, spreadsheets out, and one by mail. The **XCO XLSX library**
+reads and writes `.xlsx` files in plain ABAP, and **`cl_bcs_mail_message`**
+sends them — both released APIs of ABAP Cloud. abap2UI5 adds the two ends a
+user touches: the FileUploader that brings a file into the backend, and the
+`download_b64_file` frontend action that hands one to the browser.
+
+## What you need
+
+**Release:** Cloud + Standard ≥ 7.58 (2023). The XCO XLSX library and
+`cl_bcs_mail_message` come with ABAP Cloud; on-premise the package needs a
+release that carries both. If `xco_cp_xlsx` is not on your system, this package
+is out of reach, and nothing else in this repository is affected.
+
+**Branch:** [`13-excel-email`](https://github.com/abap2UI5/samples-stack/tree/13-excel-email)
+— this package alone, without the other thirteen on your system.
+
+The mail sample also needs **outbound mail** configured: on ABAP Cloud a
+communication arrangement for the scenario `SAP_COM_0548`, on-premise SMTP in
+transaction `SCOT`. Without it the send fails, and the sample shows the reason.
+
+## The samples
+
+| You want to | Sample |
+|---|---|
+| read an uploaded `.xlsx` file into an ABAP table | [`021`](z2ui5_cl_smps_app_021.clas.abap) |
+| export a table as an `.xlsx` download | [`022`](z2ui5_cl_smps_app_022.clas.abap) |
+| send a table as an `.xlsx` mail attachment | [`023`](z2ui5_cl_smps_app_023.clas.abap) |
+
+The XCO calls sit in [`Z2UI5_CL_SMPS_XLSX`](z2ui5_cl_smps_xlsx.clas.abap), which
+the three samples share: an `.xlsx` in, a table of strings out, and the way
+back.
+
+```abap
+" read - the first worksheet, columns A to F
+DATA(worksheet) = xco_cp_xlsx=>document->for_file_content( xlsx
+ )->read_access( )->get_workbook( )->worksheet->at_position( 1 ).
+worksheet->select( pattern )->row_stream(
+ )->operation->write_to( REF #( t_rows )
+ )->set_value_transformation( xco_cp_xlsx_read_access=>value_transformation->string_value
+ )->execute( ).
+
+" write - a new document, one worksheet
+DATA(document) = xco_cp_xlsx=>document->empty( )->write_access( ).
+document->get_workbook( )->worksheet->at_position( 1
+ )->select( pattern )->row_stream(
+ )->operation->write_from( REF #( t_rows ) )->execute( ).
+DATA(xlsx) = document->get_file_content( ).
+```
+
+Every cell travels as a string, so one table type fits every sheet; the app
+converts what it needs. Numbers are written with a decimal point and no
+thousands separator, the way a spreadsheet reads them back.
+
+## Where to go next
+
+- [`11` RAP File Upload](../11/README.md) — a file stored in a business object.
+- [`14` Adobe Forms](../14/README.md) — PDF output, one file or a ZIP.
+- The ZIP and CSV download without anything from the system:
+ `Z2UI5_CL_SMP_APP_542` in [abap2UI5/samples](https://github.com/abap2UI5/samples).
diff --git a/src/13/package.devc.xml b/src/13/package.devc.xml
new file mode 100644
index 0000000..7f7a6fc
--- /dev/null
+++ b/src/13/package.devc.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+ Excel and E-Mail
+
+
+
+
diff --git a/src/13/z2ui5_cl_smps_app_021.clas.abap b/src/13/z2ui5_cl_smps_app_021.clas.abap
new file mode 100644
index 0000000..51b0e44
--- /dev/null
+++ b/src/13/z2ui5_cl_smps_app_021.clas.abap
@@ -0,0 +1,192 @@
+" @keywords excel xlsx upload import read spreadsheet xco fileuploader table
+" @summary An .xlsx file from the FileUploader read with the XCO XLSX library - the first worksheet becomes an ABAP table and is shown as it arrived
+"! Excel - Read an Uploaded XLSX File with XCO
+"! The FileUploader custom control delivers the file as a base64 data URL,
+"! the app decodes it into an xstring and hands it to the XCO XLSX library:
+"!
+"! DATA(worksheet) = xco_cp_xlsx=>document->for_file_content( file
+"! )->read_access( )->get_workbook( )->worksheet->at_position( 1 ).
+"!
+"! worksheet->select( pattern )->row_stream(
+"! )->operation->write_to( REF #( t_rows )
+"! )->set_value_transformation( xco_cp_xlsx_read_access=>value_transformation->string_value
+"! )->execute( ).
+"!
+"! The calls sit in z2ui5_cl_smps_xlsx, which the other samples of the
+"! package share. No file at hand? Download Example File writes one with the
+"! same library.
+CLASS z2ui5_cl_smps_app_021 DEFINITION PUBLIC.
+
+ PUBLIC SECTION.
+ INTERFACES z2ui5_if_app.
+
+ "! the picked file - a data URL, data:<mime>;base64,<payload>
+ DATA file TYPE string.
+ DATA file_name TYPE string.
+
+ "! the first worksheet as it arrived - every cell a string
+ DATA t_rows TYPE z2ui5_cl_smps_xlsx=>ty_t_row.
+
+ PROTECTED SECTION.
+ DATA client TYPE REF TO z2ui5_if_client.
+
+ METHODS on_event.
+ METHODS file_read.
+ METHODS example_download.
+ METHODS view_display.
+
+ PRIVATE SECTION.
+ENDCLASS.
+
+
+CLASS z2ui5_cl_smps_app_021 IMPLEMENTATION.
+
+ METHOD z2ui5_if_app~main.
+
+ me->client = client.
+ IF client->check_on_navigated( ).
+ view_display( ).
+ ELSEIF client->check_on_event( ).
+ on_event( ).
+ ENDIF.
+
+ ENDMETHOD.
+
+
+ METHOD on_event.
+
+ CASE client->get_event( ).
+ WHEN `UPLOAD`.
+ file_read( ).
+ WHEN `EXAMPLE`.
+ example_download( ).
+ ENDCASE.
+
+ ENDMETHOD.
+
+
+ METHOD file_read.
+
+ " data:;base64, - only the payload is the file
+ DATA(base64) = substring_after( val = file sub = `,` ).
+
+ TRY.
+ t_rows = z2ui5_cl_smps_xlsx=>read( cl_web_http_utility=>decode_x_base64( base64 ) ).
+ client->message_toast_display( |{ file_name } - { lines( t_rows ) } rows read| ).
+
+ CATCH cx_root INTO DATA(error).
+ " not an .xlsx at all, or a damaged one - XCO says why
+ CLEAR t_rows.
+ client->message_box_display( text = |{ file_name }: { error->get_text( ) }| type = `error` ).
+ ENDTRY.
+
+ CLEAR file.
+ view_display( ).
+
+ ENDMETHOD.
+
+
+ METHOD example_download.
+
+ " the same library the other way round - see z2ui5_cl_smps_app_022
+ DATA(xlsx) = z2ui5_cl_smps_xlsx=>write( VALUE #(
+ ( a = `Product` b = `Quantity` c = `Price` d = `Currency` )
+ ( a = `Notebook` b = `2` c = `899.00` d = `EUR` )
+ ( a = `Docking station` b = `2` c = `189.50` d = `EUR` )
+ ( a = `Headset` b = `5` c = `79.90` d = `EUR` ) ) ).
+
+ client->follow_up_action(
+ val = client->cs_event-download_b64_file
+ t_arg = VALUE #( ( |data:{ z2ui5_cl_smps_xlsx=>mime_type };base64,{ cl_web_http_utility=>encode_x_base64( xlsx ) }| )
+ ( `example.xlsx` ) ) ).
+
+ ENDMETHOD.
+
+
+ METHOD view_display.
+
+ DATA(view) = z2ui5_cl_ui5_view_builder=>factory(
+ )->ele( n = `View` ns = `mvc`
+ )->a( n = `displayBlock` v = `true`
+ )->a( n = `height` v = `100%`
+ )->a( n = `xmlns` v = `sap.m`
+ )->a( n = `xmlns:mvc` v = `sap.ui.core.mvc`
+ )->a( n = `xmlns:z2ui5` v = `z2ui5.cc` ).
+ DATA(page) = view->ele( `Shell`
+ )->ele( `Page`
+ )->a( n = `title` v = `abap2UI5 - Excel - 21 Read an Uploaded XLSX File`
+ )->a( n = `showNavButton` b = client->check_app_prev_stack( )
+ )->a( n = `navButtonPress` v = client->_event_nav_app_leave( ) ).
+
+ page->tag( `MessageStrip`
+ )->a( n = `text` v = `Pick an .xlsx file and press Upload - the XCO XLSX library reads its first worksheet, ` &&
+ `columns A to F, into an ABAP table. No file at hand? Download Example File first.`
+ )->a( n = `type` v = `Information`
+ )->a( n = `showIcon` b = abap_true
+ )->a( n = `class` v = `sapUiSmallMargin` ).
+
+ page->ele( `HBox`
+ )->a( n = `class` v = `sapUiSmallMargin`
+ )->a( n = `alignItems` v = `Center`
+ )->tag( n = `FileUploader` ns = `z2ui5`
+ )->a( n = `placeholder` v = `pick an .xlsx file...`
+ )->a( n = `fileType` v = `xlsx`
+ )->a( n = `uploadButtonText` v = `Upload`
+ )->a( n = `upload` v = client->_event( `UPLOAD` )
+ )->a( n = `path` v = client->_bind( file_name )
+ )->a( n = `value` v = client->_bind( file )
+ )->tag( `Button`
+ )->a( n = `press` v = client->_event( `EXAMPLE` )
+ )->a( n = `text` v = `Download Example File`
+ )->a( n = `icon` v = `sap-icon://download`
+ )->a( n = `class` v = `sapUiSmallMarginBegin` ).
+
+ DATA(table) = page->ele( `Table`
+ )->a( n = `items` v = client->_bind( t_rows )
+ )->a( n = `noDataText` v = `Nothing read yet` ).
+ table->ele( `columns`
+ )->ele( `Column`
+ )->tag( `Text`
+ )->a( n = `text` v = `A`
+ )->end(
+ )->ele( `Column`
+ )->tag( `Text`
+ )->a( n = `text` v = `B`
+ )->end(
+ )->ele( `Column`
+ )->tag( `Text`
+ )->a( n = `text` v = `C`
+ )->end(
+ )->ele( `Column`
+ )->tag( `Text`
+ )->a( n = `text` v = `D`
+ )->end(
+ )->ele( `Column`
+ )->tag( `Text`
+ )->a( n = `text` v = `E`
+ )->end(
+ )->ele( `Column`
+ )->tag( `Text`
+ )->a( n = `text` v = `F` ).
+
+ table->ele( `items`
+ )->ele( `ColumnListItem`
+ )->ele( `cells`
+ )->tag( `Text`
+ )->a( n = `text` v = `{A}`
+ )->tag( `Text`
+ )->a( n = `text` v = `{B}`
+ )->tag( `Text`
+ )->a( n = `text` v = `{C}`
+ )->tag( `Text`
+ )->a( n = `text` v = `{D}`
+ )->tag( `Text`
+ )->a( n = `text` v = `{E}`
+ )->tag( `Text`
+ )->a( n = `text` v = `{F}` ).
+
+ client->view_display( view->stringify( ) ).
+
+ ENDMETHOD.
+
+ENDCLASS.
diff --git a/src/13/z2ui5_cl_smps_app_021.clas.xml b/src/13/z2ui5_cl_smps_app_021.clas.xml
new file mode 100644
index 0000000..c33e654
--- /dev/null
+++ b/src/13/z2ui5_cl_smps_app_021.clas.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+ Z2UI5_CL_SMPS_APP_021
+ E
+ Excel - Read an Uploaded XLSX File with XCO
+ 1
+ X
+ X
+ X
+
+
+
+
diff --git a/src/13/z2ui5_cl_smps_app_022.clas.abap b/src/13/z2ui5_cl_smps_app_022.clas.abap
new file mode 100644
index 0000000..7a2cedc
--- /dev/null
+++ b/src/13/z2ui5_cl_smps_app_022.clas.abap
@@ -0,0 +1,157 @@
+" @keywords excel xlsx export download write spreadsheet xco table download_b64_file
+" @summary The table on the screen written into an .xlsx file with the XCO XLSX library and handed to the browser as a download - a header row, then one row per line
+"! Excel - Export a Table as XLSX File with XCO
+"! The XCO XLSX library writes an internal table into a new worksheet:
+"!
+"! DATA(document) = xco_cp_xlsx=>document->empty( )->write_access( ).
+"! document->get_workbook( )->worksheet->at_position( 1
+"! )->select( pattern )->row_stream(
+"! )->operation->write_from( REF #( t_rows ) )->execute( ).
+"! DATA(xlsx) = document->get_file_content( ).
+"!
+"! The file goes to the browser as a base64 data URL through the
+"! download_b64_file frontend action - no download service, no extra request.
+"! The calls sit in z2ui5_cl_smps_xlsx, which the other samples of the
+"! package share.
+CLASS z2ui5_cl_smps_app_022 DEFINITION PUBLIC.
+
+ PUBLIC SECTION.
+ INTERFACES z2ui5_if_app.
+
+ TYPES:
+ BEGIN OF ty_s_product,
+ product TYPE string,
+ quantity TYPE i,
+ price TYPE p LENGTH 9 DECIMALS 2,
+ currency TYPE string,
+ END OF ty_s_product.
+ TYPES ty_t_product TYPE STANDARD TABLE OF ty_s_product WITH EMPTY KEY.
+ DATA t_products TYPE ty_t_product.
+
+ PROTECTED SECTION.
+ DATA client TYPE REF TO z2ui5_if_client.
+
+ METHODS file_download.
+ METHODS view_display.
+
+ PRIVATE SECTION.
+ENDCLASS.
+
+
+CLASS z2ui5_cl_smps_app_022 IMPLEMENTATION.
+
+ METHOD z2ui5_if_app~main.
+
+ me->client = client.
+ IF client->check_on_init( ).
+ t_products = VALUE #( currency = `EUR`
+ ( product = `Notebook` quantity = 2 price = '899.00' )
+ ( product = `Docking station` quantity = 2 price = '189.50' )
+ ( product = `Headset` quantity = 5 price = '79.90' )
+ ( product = `Monitor` quantity = 3 price = '249.00' ) ).
+ view_display( ).
+ ELSEIF client->check_on_navigated( ).
+ view_display( ).
+ ELSEIF client->check_on_event( `DOWNLOAD` ).
+ file_download( ).
+ ENDIF.
+
+ ENDMETHOD.
+
+
+ METHOD file_download.
+
+ " every cell is a string in the worksheet - the header row first, then
+ " one row per product, the numbers written the way a spreadsheet reads
+ " them back: a decimal point and no thousands separator
+ DATA(t_rows) = VALUE z2ui5_cl_smps_xlsx=>ty_t_row(
+ ( a = `Product` b = `Quantity` c = `Price` d = `Currency` e = `Total` ) ).
+
+ LOOP AT t_products INTO DATA(s_product).
+ APPEND VALUE #( a = s_product-product
+ b = |{ s_product-quantity }|
+ c = |{ s_product-price }|
+ d = s_product-currency
+ e = |{ s_product-quantity * s_product-price DECIMALS = 2 }| ) TO t_rows.
+ ENDLOOP.
+
+ DATA(xlsx) = z2ui5_cl_smps_xlsx=>write( t_rows ).
+
+ client->follow_up_action(
+ val = client->cs_event-download_b64_file
+ t_arg = VALUE #( ( |data:{ z2ui5_cl_smps_xlsx=>mime_type };base64,{ cl_web_http_utility=>encode_x_base64( xlsx ) }| )
+ ( `products.xlsx` ) ) ).
+
+ client->message_toast_display( |products.xlsx - { lines( t_products ) } rows, { xstrlen( xlsx ) } bytes| ).
+
+ ENDMETHOD.
+
+
+ METHOD view_display.
+
+ DATA(view) = z2ui5_cl_ui5_view_builder=>factory(
+ )->ele( n = `View` ns = `mvc`
+ )->a( n = `displayBlock` v = `true`
+ )->a( n = `height` v = `100%`
+ )->a( n = `xmlns` v = `sap.m`
+ )->a( n = `xmlns:mvc` v = `sap.ui.core.mvc` ).
+ DATA(page) = view->ele( `Shell`
+ )->ele( `Page`
+ )->a( n = `title` v = `abap2UI5 - Excel - 22 Export a Table as XLSX File`
+ )->a( n = `showNavButton` b = client->check_app_prev_stack( )
+ )->a( n = `navButtonPress` v = client->_event_nav_app_leave( ) ).
+
+ page->tag( `MessageStrip`
+ )->a( n = `text` v = `Change the table, then press Download XLSX - the XCO XLSX library writes it into a new ` &&
+ `worksheet and the browser saves the file.`
+ )->a( n = `type` v = `Information`
+ )->a( n = `showIcon` b = abap_true
+ )->a( n = `class` v = `sapUiSmallMargin` ).
+
+ DATA(table) = page->ele( `Table`
+ )->a( n = `items` v = client->_bind( t_products ) ).
+ table->ele( `headerToolbar`
+ )->ele( `Toolbar`
+ )->tag( `Title`
+ )->a( n = `text` v = `Products`
+ )->tag( `ToolbarSpacer`
+ )->tag( `Button`
+ )->a( n = `press` v = client->_event( `DOWNLOAD` )
+ )->a( n = `text` v = `Download XLSX`
+ )->a( n = `icon` v = `sap-icon://excel-attachment`
+ )->a( n = `type` v = `Emphasized` ).
+
+ table->ele( `columns`
+ )->ele( `Column`
+ )->tag( `Text`
+ )->a( n = `text` v = `Product`
+ )->end(
+ )->ele( `Column`
+ )->tag( `Text`
+ )->a( n = `text` v = `Quantity`
+ )->end(
+ )->ele( `Column`
+ )->tag( `Text`
+ )->a( n = `text` v = `Price`
+ )->end(
+ )->ele( `Column`
+ )->tag( `Text`
+ )->a( n = `text` v = `Currency` ).
+
+ table->ele( `items`
+ )->ele( `ColumnListItem`
+ )->ele( `cells`
+ )->tag( `Input`
+ )->a( n = `value` v = `{PRODUCT}`
+ )->tag( `Input`
+ )->a( n = `value` v = `{QUANTITY}`
+ )->tag( `Input`
+ )->a( n = `value` v = `{PRICE}`
+ )->tag( `Input`
+ )->a( n = `value` v = `{CURRENCY}` ).
+
+ client->view_display( view->stringify( ) ).
+
+ ENDMETHOD.
+
+ENDCLASS.
diff --git a/src/13/z2ui5_cl_smps_app_022.clas.xml b/src/13/z2ui5_cl_smps_app_022.clas.xml
new file mode 100644
index 0000000..43b97c4
--- /dev/null
+++ b/src/13/z2ui5_cl_smps_app_022.clas.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+ Z2UI5_CL_SMPS_APP_022
+ E
+ Excel - Export a Table as XLSX File with XCO
+ 1
+ X
+ X
+ X
+
+
+
+
diff --git a/src/13/z2ui5_cl_smps_app_023.clas.abap b/src/13/z2ui5_cl_smps_app_023.clas.abap
new file mode 100644
index 0000000..395a88c
--- /dev/null
+++ b/src/13/z2ui5_cl_smps_app_023.clas.abap
@@ -0,0 +1,162 @@
+" @keywords email mail send attachment xlsx excel cl_bcs_mail_message smtp
+" @summary A mail with an .xlsx attachment, sent with cl_bcs_mail_message - recipient, subject and text from the screen, the attachment written with the XCO XLSX library
+"! E-Mail - Send a Table as XLSX Attachment
+"! cl_bcs_mail_message is the mail API of ABAP Cloud:
+"!
+"! DATA(mail) = cl_bcs_mail_message=>create_instance( ).
+"! mail->set_sender( sender ).
+"! mail->add_recipient( recipient ).
+"! mail->set_subject( subject ).
+"! mail->set_main( cl_bcs_mail_textpart=>create_text_plain( text ) ).
+"! mail->add_attachment( cl_bcs_mail_binarypart=>create_instance(
+"! iv_content = xlsx
+"! iv_content_type = mime_type
+"! iv_filename = `products.xlsx` ) ).
+"! mail->send( IMPORTING ev_mail_status = DATA(status) ).
+"!
+"! The mail leaves the system through its outbound mail configuration - on
+"! ABAP Cloud a communication arrangement for SAP_COM_0548, on-premise SMTP
+"! in transaction SCOT. Without one the send fails, and the app says why.
+CLASS z2ui5_cl_smps_app_023 DEFINITION PUBLIC.
+
+ PUBLIC SECTION.
+ INTERFACES z2ui5_if_app.
+
+ DATA sender TYPE string.
+ DATA recipient TYPE string.
+ DATA subject TYPE string.
+ DATA text TYPE string.
+
+ PROTECTED SECTION.
+ DATA client TYPE REF TO z2ui5_if_client.
+
+ METHODS mail_send.
+ METHODS view_display.
+
+ PRIVATE SECTION.
+ENDCLASS.
+
+
+CLASS z2ui5_cl_smps_app_023 IMPLEMENTATION.
+
+ METHOD z2ui5_if_app~main.
+
+ me->client = client.
+ IF client->check_on_init( ).
+ subject = `Products as XLSX`.
+ text = |Hello,\n\nthe current product list is attached.\n\nSent from an abap2UI5 app.|.
+ view_display( ).
+ ELSEIF client->check_on_navigated( ).
+ view_display( ).
+ ELSEIF client->check_on_event( `SEND` ).
+ mail_send( ).
+ ENDIF.
+
+ ENDMETHOD.
+
+
+ METHOD mail_send.
+
+ IF sender IS INITIAL OR recipient IS INITIAL.
+ client->message_box_display( text = `Sender and recipient are both needed` type = `error` ).
+ RETURN.
+ ENDIF.
+
+ " the attachment - written exactly as z2ui5_cl_smps_app_022 writes it
+ DATA(xlsx) = z2ui5_cl_smps_xlsx=>write( VALUE #(
+ ( a = `Product` b = `Quantity` c = `Price` d = `Currency` )
+ ( a = `Notebook` b = `2` c = `899.00` d = `EUR` )
+ ( a = `Docking station` b = `2` c = `189.50` d = `EUR` )
+ ( a = `Headset` b = `5` c = `79.90` d = `EUR` ) ) ).
+
+ TRY.
+ DATA(mail) = cl_bcs_mail_message=>create_instance( ).
+ mail->set_sender( CONV #( sender ) ).
+ mail->add_recipient( CONV #( recipient ) ).
+ mail->set_subject( CONV #( subject ) ).
+ mail->set_main( cl_bcs_mail_textpart=>create_text_plain( text ) ).
+ mail->add_attachment( cl_bcs_mail_binarypart=>create_instance(
+ iv_content = xlsx
+ iv_content_type = CONV #( z2ui5_cl_smps_xlsx=>mime_type )
+ iv_filename = `products.xlsx` ) ).
+
+ " the send hands the mail to the outbound queue - the status says
+ " whether it left already or is still waiting there
+ mail->send( IMPORTING ev_mail_status = DATA(status) ).
+
+ client->message_box_display( text = |Mail handed over to the mail system, status { status }| type = `success` ).
+
+ CATCH cx_bcs_mail INTO DATA(error).
+ " an invalid address, or no outbound mail configured on this system
+ client->message_box_display( text = error->get_text( ) type = `error` ).
+ ENDTRY.
+
+ ENDMETHOD.
+
+
+ METHOD view_display.
+
+ DATA(view) = z2ui5_cl_ui5_view_builder=>factory(
+ )->ele( n = `View` ns = `mvc`
+ )->a( n = `displayBlock` v = `true`
+ )->a( n = `height` v = `100%`
+ )->a( n = `xmlns` v = `sap.m`
+ )->a( n = `xmlns:mvc` v = `sap.ui.core.mvc`
+ )->a( n = `xmlns:form` v = `sap.ui.layout.form` ).
+ DATA(page) = view->ele( `Shell`
+ )->ele( `Page`
+ )->a( n = `title` v = `abap2UI5 - E-Mail - 23 Send a Table as XLSX Attachment`
+ )->a( n = `showNavButton` b = client->check_app_prev_stack( )
+ )->a( n = `navButtonPress` v = client->_event_nav_app_leave( ) ).
+
+ page->tag( `MessageStrip`
+ )->a( n = `text` v = `Sends a mail with the product list as .xlsx attachment through cl_bcs_mail_message. The ` &&
+ `system needs outbound mail configured - a communication arrangement for SAP_COM_0548 ` &&
+ `on ABAP Cloud, SMTP in SCOT on-premise.`
+ )->a( n = `type` v = `Information`
+ )->a( n = `showIcon` b = abap_true
+ )->a( n = `class` v = `sapUiSmallMargin` ).
+
+ page->ele( n = `SimpleForm` ns = `form`
+ )->a( n = `title` v = `cl_bcs_mail_message`
+ )->a( n = `editable` b = abap_true
+ )->ele( n = `content` ns = `form`
+ )->tag( `Label`
+ )->a( n = `text` v = `From`
+ )->tag( `Input`
+ )->a( n = `value` v = client->_bind( sender )
+ )->a( n = `type` v = `Email`
+ )->a( n = `placeholder` v = `a sender address your mail configuration allows`
+ )->tag( `Label`
+ )->a( n = `text` v = `To`
+ )->tag( `Input`
+ )->a( n = `value` v = client->_bind( recipient )
+ )->a( n = `type` v = `Email`
+ )->tag( `Label`
+ )->a( n = `text` v = `Subject`
+ )->tag( `Input`
+ )->a( n = `value` v = client->_bind( subject )
+ )->tag( `Label`
+ )->a( n = `text` v = `Text`
+ )->tag( `TextArea`
+ )->a( n = `value` v = client->_bind( text )
+ )->a( n = `rows` v = `6`
+ )->tag( `Label`
+ )->a( n = `text` v = `Attachment`
+ )->tag( `Text`
+ )->a( n = `text` v = `products.xlsx - four products, written with XCO` ).
+
+ page->ele( `footer`
+ )->ele( `OverflowToolbar`
+ )->tag( `ToolbarSpacer`
+ )->tag( `Button`
+ )->a( n = `press` v = client->_event( `SEND` )
+ )->a( n = `text` v = `Send`
+ )->a( n = `icon` v = `sap-icon://email`
+ )->a( n = `type` v = `Emphasized` ).
+
+ client->view_display( view->stringify( ) ).
+
+ ENDMETHOD.
+
+ENDCLASS.
diff --git a/src/13/z2ui5_cl_smps_app_023.clas.xml b/src/13/z2ui5_cl_smps_app_023.clas.xml
new file mode 100644
index 0000000..cd453e4
--- /dev/null
+++ b/src/13/z2ui5_cl_smps_app_023.clas.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+ Z2UI5_CL_SMPS_APP_023
+ E
+ E-Mail - Send a Table as XLSX Attachment
+ 1
+ X
+ X
+ X
+
+
+
+
diff --git a/src/13/z2ui5_cl_smps_xlsx.clas.abap b/src/13/z2ui5_cl_smps_xlsx.clas.abap
new file mode 100644
index 0000000..9a8fc4b
--- /dev/null
+++ b/src/13/z2ui5_cl_smps_xlsx.clas.abap
@@ -0,0 +1,95 @@
+"! abap2UI5 sample - Excel read and write with XCO
+"! The XCO XLSX library behind the samples of package 13: an .xlsx file in,
+"! a table of strings out - and the way back. Every cell travels as a string,
+"! so one table type serves every sheet, and the app converts what it needs.
+"!
+"! Six columns, A to F, are read and written - enough for the samples, and a
+"! fixed row structure is what the XCO row stream maps the cells onto.
+CLASS z2ui5_cl_smps_xlsx DEFINITION PUBLIC FINAL CREATE PUBLIC.
+
+ PUBLIC SECTION.
+
+ TYPES:
+ BEGIN OF ty_s_row,
+ a TYPE string,
+ b TYPE string,
+ c TYPE string,
+ d TYPE string,
+ e TYPE string,
+ f TYPE string,
+ END OF ty_s_row.
+ TYPES ty_t_row TYPE STANDARD TABLE OF ty_s_row WITH EMPTY KEY.
+
+ "! the MIME type of an .xlsx file - for a data URL or a mail attachment
+ CONSTANTS mime_type TYPE string
+ VALUE `application/vnd.openxmlformats-officedocument.spreadsheetml.sheet` ##NO_TEXT.
+
+ "! the first worksheet, every row from the first one on, columns A to F
+ CLASS-METHODS read
+ IMPORTING
+ val TYPE xstring
+ RETURNING
+ VALUE(result) TYPE ty_t_row.
+
+ "! one worksheet, the rows from A1 on
+ CLASS-METHODS write
+ IMPORTING
+ t_row TYPE ty_t_row
+ RETURNING
+ VALUE(result) TYPE xstring.
+
+ PROTECTED SECTION.
+ PRIVATE SECTION.
+ENDCLASS.
+
+
+CLASS z2ui5_cl_smps_xlsx IMPLEMENTATION.
+
+ METHOD read.
+
+ DATA(worksheet) = xco_cp_xlsx=>document->for_file_content( val
+ )->read_access(
+ )->get_workbook(
+ )->worksheet->at_position( 1 ).
+
+ " A1 down to the last row: a pattern without an end reads as far as the
+ " sheet has rows, and the six columns of ty_s_row decide how wide
+ DATA(pattern) = xco_cp_xlsx_selection=>pattern_builder->simple_from_to(
+ )->from_column( xco_cp_xlsx=>coordinate->for_alphabetic_value( `A` )
+ )->to_column( xco_cp_xlsx=>coordinate->for_alphabetic_value( `F` )
+ )->from_row( xco_cp_xlsx=>coordinate->for_numeric_value( 1 )
+ )->get_pattern( ).
+
+ " the string value transformation turns every cell into its text - a
+ " number comes back as it is stored, a date as its serial number
+ worksheet->select( pattern
+ )->row_stream(
+ )->operation->write_to( REF #( result )
+ )->set_value_transformation( xco_cp_xlsx_read_access=>value_transformation->string_value
+ )->execute( ).
+
+ ENDMETHOD.
+
+
+ METHOD write.
+
+ DATA(document) = xco_cp_xlsx=>document->empty( )->write_access( ).
+
+ DATA(pattern) = xco_cp_xlsx_selection=>pattern_builder->simple_from_to(
+ )->from_column( xco_cp_xlsx=>coordinate->for_alphabetic_value( `A` )
+ )->to_column( xco_cp_xlsx=>coordinate->for_alphabetic_value( `F` )
+ )->from_row( xco_cp_xlsx=>coordinate->for_numeric_value( 1 )
+ )->get_pattern( ).
+
+ document->get_workbook(
+ )->worksheet->at_position( 1
+ )->select( pattern
+ )->row_stream(
+ )->operation->write_from( REF #( t_row )
+ )->execute( ).
+
+ result = document->get_file_content( ).
+
+ ENDMETHOD.
+
+ENDCLASS.
diff --git a/src/13/z2ui5_cl_smps_xlsx.clas.xml b/src/13/z2ui5_cl_smps_xlsx.clas.xml
new file mode 100644
index 0000000..5dbeb37
--- /dev/null
+++ b/src/13/z2ui5_cl_smps_xlsx.clas.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+ Z2UI5_CL_SMPS_XLSX
+ E
+ abap2UI5 sample - Excel read and write with XCO
+ 1
+ X
+ X
+ X
+
+
+
+
diff --git a/src/14/README.md b/src/14/README.md
new file mode 100644
index 0000000..509f4da
--- /dev/null
+++ b/src/14/README.md
@@ -0,0 +1,56 @@
+# 14 — Adobe Forms
+
+*[← all packages](../../README.md)*
+
+The print forms a system already has, rendered to PDF from an abap2UI5 app. An
+Adobe Form is rendered by the function module it generates: between
+`FP_JOB_OPEN` and `FP_JOB_CLOSE` it hands back the PDF instead of printing it.
+The app offers it as a download — one form as a PDF, several at once as one ZIP
+file built with `cl_abap_zip`.
+
+## What you need
+
+**Release:** Standard only, ≥ 7.50. The classic Adobe Forms runtime
+(`FP_JOB_OPEN`, the generated function module, `FP_JOB_CLOSE`) is not released
+for ABAP Cloud, where forms are rendered by the Forms Service by Adobe through
+`cl_fp_ads_util` instead.
+
+**Branch:** [`14-adobe-forms`](https://github.com/abap2UI5/samples-stack/tree/14-adobe-forms)
+— this package alone, without the other thirteen on your system.
+
+**Adobe Document Services (ADS)** configured for the system — the RFC
+destination `ADS` in `SM59` — and an Adobe Form to render. The sample calls the
+generated function module with a parameter table carrying only
+`/1BCDWB/DOCPARAMS` and `/1BCDWB/FORMOUTPUT`, so it renders forms whose
+interface needs no parameters of its own. A form that wants more says so in the
+result list.
+
+## The sample
+
+| You want to | Sample |
+|---|---|
+| render forms to PDF, download one or a ZIP of several | [`024`](z2ui5_cl_smps_app_024.clas.abap) |
+
+```abap
+CALL FUNCTION 'FP_FUNCTION_MODULE_NAME'
+ EXPORTING i_name = form IMPORTING e_funcname = function.
+
+outputparams-getpdf = abap_true. " the PDF back, no printer
+outputparams-nodialog = abap_true. " no print dialog
+CALL FUNCTION 'FP_JOB_OPEN' CHANGING ie_outputparams = outputparams.
+
+CALL FUNCTION function
+ PARAMETER-TABLE t_param " /1BCDWB/DOCPARAMS, /1BCDWB/FORMOUTPUT
+ EXCEPTION-TABLE t_exc.
+
+CALL FUNCTION 'FP_JOB_CLOSE'. " output-pdf is the file
+```
+
+The job is closed in every case — an open one makes the next `FP_JOB_OPEN` of
+the session fail.
+
+## Where to go next
+
+- [`13` Excel & E-Mail](../13/README.md) — a file as mail attachment.
+- Showing a PDF inside the page: the PDFViewer sample `Z2UI5_CL_SMP_APP_449` in
+ [abap2UI5/samples](https://github.com/abap2UI5/samples).
diff --git a/src/14/package.devc.xml b/src/14/package.devc.xml
new file mode 100644
index 0000000..d6b90c9
--- /dev/null
+++ b/src/14/package.devc.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+ Adobe Forms
+
+
+
+
diff --git a/src/14/z2ui5_cl_smps_app_024.clas.abap b/src/14/z2ui5_cl_smps_app_024.clas.abap
new file mode 100644
index 0000000..f929bb2
--- /dev/null
+++ b/src/14/z2ui5_cl_smps_app_024.clas.abap
@@ -0,0 +1,342 @@
+" @keywords adobe forms pdf ads fp_job_open print form output zip download cl_abap_zip
+" @summary Renders Adobe Forms of the system to PDF with FP_JOB_OPEN and the generated function module - one form as a PDF download, several at once as one ZIP file
+"! Adobe Forms - Render to PDF, Download One or a ZIP
+"! An Adobe Form is rendered by the function module the form generates. Its
+"! name comes from FP_FUNCTION_MODULE_NAME, and between FP_JOB_OPEN and
+"! FP_JOB_CLOSE it returns the PDF instead of printing it:
+"!
+"! CALL FUNCTION 'FP_FUNCTION_MODULE_NAME'
+"! EXPORTING i_name = form IMPORTING e_funcname = function.
+"!
+"! CALL FUNCTION 'FP_JOB_OPEN'
+"! CHANGING ie_outputparams = VALUE sfpoutputparams( getpdf = abap_true nodialog = abap_true ).
+"!
+"! CALL FUNCTION function
+"! EXPORTING /1bcdwb/docparams = docparams
+"! IMPORTING /1bcdwb/formoutput = output. " output-pdf is the file
+"!
+"! CALL FUNCTION 'FP_JOB_CLOSE'.
+"!
+"! The generated function module has the interface of its form, so the app
+"! calls it with a PARAMETER-TABLE: the form name is typed in at runtime, and
+"! a form whose interface has mandatory parameters of its own says so.
+"! Several forms go into one ZIP file with cl_abap_zip.
+"!
+"! Needs Adobe Document Services (ADS) configured for the system - the
+"! destination ADS in SM59. On ABAP Cloud forms are rendered by the Forms
+"! Service by Adobe through cl_fp_ads_util instead; this sample is Standard
+"! only.
+CLASS z2ui5_cl_smps_app_024 DEFINITION PUBLIC.
+
+ PUBLIC SECTION.
+ INTERFACES z2ui5_if_app.
+
+ "! one form name per line
+ DATA forms TYPE string.
+
+ TYPES:
+ BEGIN OF ty_s_result,
+ form TYPE string,
+ size TYPE string,
+ status TYPE string,
+ state TYPE string,
+ END OF ty_s_result.
+ TYPES ty_t_result TYPE STANDARD TABLE OF ty_s_result WITH EMPTY KEY.
+ "! what the last run produced, one row per form
+ DATA t_results TYPE ty_t_result.
+
+ PROTECTED SECTION.
+ DATA client TYPE REF TO z2ui5_if_client.
+
+ TYPES:
+ BEGIN OF ty_s_pdf,
+ form TYPE string,
+ pdf TYPE xstring,
+ END OF ty_s_pdf.
+ TYPES ty_t_pdf TYPE STANDARD TABLE OF ty_s_pdf WITH EMPTY KEY.
+
+ METHODS on_event.
+
+ "! renders every form of FORMS - the ones that worked come back, every
+ "! form gets its row in T_RESULTS
+ METHODS forms_render
+ RETURNING
+ VALUE(result) TYPE ty_t_pdf.
+
+ "! one form to PDF - ERROR says why when it does not render
+ METHODS form_render
+ IMPORTING
+ form TYPE string
+ EXPORTING
+ pdf TYPE xstring
+ error TYPE string.
+
+ METHODS file_download
+ IMPORTING
+ name TYPE string
+ mime_type TYPE string
+ content TYPE xstring.
+
+ METHODS view_display.
+
+ PRIVATE SECTION.
+ENDCLASS.
+
+
+CLASS z2ui5_cl_smps_app_024 IMPLEMENTATION.
+
+ METHOD z2ui5_if_app~main.
+
+ me->client = client.
+ IF client->check_on_navigated( ).
+ view_display( ).
+ ELSEIF client->check_on_event( ).
+ on_event( ).
+ ENDIF.
+
+ ENDMETHOD.
+
+
+ METHOD on_event.
+
+ DATA(t_pdf) = forms_render( ).
+
+ IF t_pdf IS INITIAL.
+ view_display( ).
+ RETURN.
+ ENDIF.
+
+ CASE client->get_event( ).
+
+ WHEN `PDF`.
+ " the first form that rendered - one file, no ZIP needed
+ DATA(s_pdf) = t_pdf[ 1 ].
+ file_download( name = |{ to_lower( s_pdf-form ) }.pdf|
+ mime_type = `application/pdf`
+ content = s_pdf-pdf ).
+
+ WHEN `ZIP`.
+ DATA(zip) = NEW cl_abap_zip( ).
+ LOOP AT t_pdf INTO s_pdf.
+ zip->add( name = |{ to_lower( s_pdf-form ) }.pdf|
+ content = s_pdf-pdf ).
+ ENDLOOP.
+ file_download( name = `forms.zip`
+ mime_type = `application/zip`
+ content = zip->save( ) ).
+
+ ENDCASE.
+
+ view_display( ).
+
+ ENDMETHOD.
+
+
+ METHOD forms_render.
+
+ CLEAR t_results.
+ SPLIT forms AT cl_abap_char_utilities=>newline INTO TABLE DATA(t_form).
+
+ LOOP AT t_form INTO DATA(form).
+
+ form = to_upper( condense( form ) ).
+ IF form IS INITIAL.
+ CONTINUE.
+ ENDIF.
+
+ form_render( EXPORTING form = form
+ IMPORTING pdf = DATA(pdf)
+ error = DATA(error) ).
+
+ IF error IS INITIAL.
+ APPEND VALUE #( form = form pdf = pdf ) TO result.
+ APPEND VALUE #( form = form
+ size = |{ xstrlen( pdf ) } bytes|
+ status = `rendered`
+ state = `Success` ) TO t_results.
+ ELSE.
+ APPEND VALUE #( form = form
+ status = error
+ state = `Error` ) TO t_results.
+ ENDIF.
+
+ ENDLOOP.
+
+ ENDMETHOD.
+
+
+ METHOD form_render.
+
+ DATA function TYPE funcname.
+ DATA output TYPE fpformoutput.
+ DATA docparams TYPE sfpdocparams.
+ DATA outputpar TYPE sfpoutputparams.
+
+ CLEAR: pdf, error.
+
+ " the name of the function module the form generated - it differs per
+ " system, which is why it is asked for and never written down
+ TRY.
+ CALL FUNCTION 'FP_FUNCTION_MODULE_NAME'
+ EXPORTING
+ i_name = CONV fpname( form )
+ IMPORTING
+ e_funcname = function.
+ CATCH cx_fp_api INTO DATA(api_error).
+ error = api_error->get_text( ).
+ RETURN.
+ ENDTRY.
+
+ " getpdf: hand the PDF back instead of sending it to a printer,
+ " nodialog: no print dialog - there is no SAP GUI to show it in
+ outputpar-getpdf = abap_true.
+ outputpar-nodialog = abap_true.
+
+ CALL FUNCTION 'FP_JOB_OPEN'
+ CHANGING
+ ie_outputparams = outputpar
+ EXCEPTIONS
+ cancel = 1
+ usage_error = 2
+ system_error = 3
+ internal_error = 4
+ OTHERS = 5.
+ IF sy-subrc <> 0.
+ error = |FP_JOB_OPEN failed with sy-subrc { sy-subrc } - is ADS configured?|.
+ RETURN.
+ ENDIF.
+
+ " the generated function module has the interface of its form - called
+ " with a parameter table, so any form name typed in at runtime works
+ DATA(t_param) = VALUE abap_func_parmbind_tab(
+ ( name = '/1BCDWB/DOCPARAMS'
+ kind = abap_func_exporting
+ value = REF #( docparams ) )
+ ( name = '/1BCDWB/FORMOUTPUT'
+ kind = abap_func_importing
+ value = REF #( output ) ) ).
+ DATA(t_exc) = VALUE abap_func_excpbind_tab(
+ ( name = 'USAGE_ERROR' value = 1 )
+ ( name = 'SYSTEM_ERROR' value = 2 )
+ ( name = 'INTERNAL_ERROR' value = 3 )
+ ( name = 'OTHERS' value = 4 ) ).
+
+ TRY.
+ CALL FUNCTION function
+ PARAMETER-TABLE t_param
+ EXCEPTION-TABLE t_exc.
+ IF sy-subrc <> 0.
+ error = |{ function } failed with sy-subrc { sy-subrc }|.
+ ENDIF.
+
+ CATCH cx_sy_dyn_call_error INTO DATA(call_error).
+ " a form whose interface wants more than the two parameters above
+ error = call_error->get_text( ).
+ ENDTRY.
+
+ " closed in every case - an open job would make the next FP_JOB_OPEN of
+ " this session fail
+ CALL FUNCTION 'FP_JOB_CLOSE'
+ EXCEPTIONS
+ OTHERS = 1.
+ IF sy-subrc <> 0 AND error IS INITIAL.
+ error = |FP_JOB_CLOSE failed with sy-subrc { sy-subrc }|.
+ ENDIF.
+
+ IF error IS INITIAL.
+ pdf = output-pdf.
+ ENDIF.
+
+ ENDMETHOD.
+
+
+ METHOD file_download.
+
+ " base64 through the classic utility class - this package is Standard
+ " only, and cl_http_utility is there on every release it runs on
+ client->follow_up_action(
+ val = client->cs_event-download_b64_file
+ t_arg = VALUE #( ( |data:{ mime_type };base64,{ cl_http_utility=>encode_x_base64( content ) }| )
+ ( name ) ) ).
+
+ ENDMETHOD.
+
+
+ METHOD view_display.
+
+ DATA(view) = z2ui5_cl_ui5_view_builder=>factory(
+ )->ele( n = `View` ns = `mvc`
+ )->a( n = `displayBlock` v = `true`
+ )->a( n = `height` v = `100%`
+ )->a( n = `xmlns` v = `sap.m`
+ )->a( n = `xmlns:mvc` v = `sap.ui.core.mvc`
+ )->a( n = `xmlns:form` v = `sap.ui.layout.form` ).
+ DATA(page) = view->ele( `Shell`
+ )->ele( `Page`
+ )->a( n = `title` v = `abap2UI5 - Adobe Forms - 24 Render to PDF`
+ )->a( n = `showNavButton` b = client->check_app_prev_stack( )
+ )->a( n = `navButtonPress` v = client->_event_nav_app_leave( ) ).
+
+ page->tag( `MessageStrip`
+ )->a( n = `text` v = `Type one Adobe Form name per line. Download PDF renders the first one, Download ZIP all of ` &&
+ `them into one ZIP file. Needs Adobe Document Services configured for this system, and ` &&
+ `forms whose interface needs no parameters of its own.`
+ )->a( n = `type` v = `Information`
+ )->a( n = `showIcon` b = abap_true
+ )->a( n = `class` v = `sapUiSmallMargin` ).
+
+ page->ele( n = `SimpleForm` ns = `form`
+ )->a( n = `title` v = `FP_JOB_OPEN - generated function module - FP_JOB_CLOSE`
+ )->a( n = `editable` b = abap_true
+ )->ele( n = `content` ns = `form`
+ )->tag( `Label`
+ )->a( n = `text` v = `Forms`
+ )->tag( `TextArea`
+ )->a( n = `value` v = client->_bind( forms )
+ )->a( n = `rows` v = `4`
+ )->a( n = `placeholder` v = `one form name per line` ).
+
+ DATA(table) = page->ele( `Table`
+ )->a( n = `items` v = client->_bind( t_results )
+ )->a( n = `noDataText` v = `Nothing rendered yet` ).
+ table->ele( `columns`
+ )->ele( `Column`
+ )->tag( `Text`
+ )->a( n = `text` v = `Form`
+ )->end(
+ )->ele( `Column`
+ )->tag( `Text`
+ )->a( n = `text` v = `Size`
+ )->end(
+ )->ele( `Column`
+ )->tag( `Text`
+ )->a( n = `text` v = `Result` ).
+ table->ele( `items`
+ )->ele( `ColumnListItem`
+ )->ele( `cells`
+ )->tag( `Text`
+ )->a( n = `text` v = `{FORM}`
+ )->tag( `Text`
+ )->a( n = `text` v = `{SIZE}`
+ )->tag( `ObjectStatus`
+ )->a( n = `text` v = `{STATUS}`
+ )->a( n = `state` v = `{= ${STATE} || 'None' }` ).
+
+ page->ele( `footer`
+ )->ele( `OverflowToolbar`
+ )->tag( `ToolbarSpacer`
+ )->tag( `Button`
+ )->a( n = `press` v = client->_event( `PDF` )
+ )->a( n = `text` v = `Download PDF`
+ )->a( n = `icon` v = `sap-icon://pdf-attachment`
+ )->tag( `Button`
+ )->a( n = `press` v = client->_event( `ZIP` )
+ )->a( n = `text` v = `Download ZIP`
+ )->a( n = `icon` v = `sap-icon://attachment-zip-file`
+ )->a( n = `type` v = `Emphasized` ).
+
+ client->view_display( view->stringify( ) ).
+
+ ENDMETHOD.
+
+ENDCLASS.
diff --git a/src/14/z2ui5_cl_smps_app_024.clas.xml b/src/14/z2ui5_cl_smps_app_024.clas.xml
new file mode 100644
index 0000000..7bba708
--- /dev/null
+++ b/src/14/z2ui5_cl_smps_app_024.clas.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+ Z2UI5_CL_SMPS_APP_024
+ E
+ Adobe Forms - Render to PDF, Download One or a ZIP
+ 1
+ X
+ X
+ X
+
+
+
+
diff --git a/src/z2ui5_cl_smps_app_000.clas.abap b/src/z2ui5_cl_smps_app_000.clas.abap
index 930dc78..c1d83e9 100644
--- a/src/z2ui5_cl_smps_app_000.clas.abap
+++ b/src/z2ui5_cl_smps_app_000.clas.abap
@@ -69,11 +69,15 @@ CLASS z2ui5_cl_smps_app_000 DEFINITION PUBLIC.
DATA t_mime TYPE ty_t_sample.
DATA t_launchpad TYPE ty_t_sample.
DATA t_ai TYPE ty_t_sample.
+ DATA t_upload TYPE ty_t_sample.
+ DATA t_actions TYPE ty_t_sample.
+ DATA t_office TYPE ty_t_sample.
+ DATA t_forms TYPE ty_t_sample.
PROTECTED SECTION.
DATA client TYPE REF TO z2ui5_if_client.
- "! at least one of the two demo data classes is on the system, so the
+ "! at least one of the demo data classes is on the system, so the
"! Regenerate Demo Data button has something to call
DATA demo_data_installed TYPE abap_bool.
@@ -124,11 +128,12 @@ CLASS z2ui5_cl_smps_app_000 DEFINITION PUBLIC.
END OF cs_url.
CONSTANTS:
- "! the demo data of the two RAP packages - called by name for the same
+ "! the demo data of the RAP packages - called by name for the same
"! reason the samples are, see the class documentation
BEGIN OF cs_class,
data_trv TYPE string VALUE `Z2UI5_CL_SMPS_DATA_TRV` ##NO_TEXT,
data_trd TYPE string VALUE `Z2UI5_CL_SMPS_DATA_TRD` ##NO_TEXT,
+ data_req TYPE string VALUE `Z2UI5_CL_SMPS_DATA_REQ` ##NO_TEXT,
END OF cs_class.
METHODS on_event.
@@ -310,11 +315,12 @@ CLASS z2ui5_cl_smps_app_000 IMPLEMENTATION.
WHEN cs_backend_event-regenerate.
- " both business objects at once - the samples of src/03 run against
- " the one without draft, those of src/04 against the draft enabled
- " one, and an empty table is the most common reason a sample looks
- " broken. data_reset( ) deletes first, so the travel ids stay 1, 2, 3.
- " Both answers read alike, so each one says which package it is from.
+ " every business object at once - the samples of src/03 run against
+ " the travel without draft, those of src/04 against the draft enabled
+ " one, those of src/12 against the request, and an empty table is the
+ " most common reason a sample looks broken. data_reset( ) deletes
+ " first, so the travel ids stay 1, 2, 3. The answers read alike, so
+ " each one says which package it is from.
DATA(text) = data_reset( cs_class-data_trv ).
IF text IS NOT INITIAL.
text = |RAP: { text }|.
@@ -323,8 +329,12 @@ CLASS z2ui5_cl_smps_app_000 IMPLEMENTATION.
IF text_draft IS NOT INITIAL.
text = condense( |{ text } RAP with Draft: { text_draft }| ).
ENDIF.
+ DATA(text_actions) = data_reset( cs_class-data_req ).
+ IF text_actions IS NOT INITIAL.
+ text = condense( |{ text } RAP Actions: { text_actions }| ).
+ ENDIF.
IF text IS INITIAL.
- text = `No demo data on this system - the two RAP packages are not installed`.
+ text = `No demo data on this system - none of the RAP packages is installed`.
ENDIF.
client->message_toast_display( text ).
@@ -456,6 +466,34 @@ CLASS z2ui5_cl_smps_app_000 IMPLEMENTATION.
samples = t_ai
items = client->_bind( t_ai ) ).
+ render_package( page = page
+ title = `11 - RAP File Upload`
+ hint = `a file into a business object through a deep action parameter - needs ABAP Platform 2025 or a current cloud release`
+ branch = `11-rap-file-upload`
+ samples = t_upload
+ items = client->_bind( t_upload ) ).
+
+ render_package( page = page
+ title = `12 - RAP Actions & Permissions`
+ hint = `feature control, parameters, factory actions, defaults, projections on Z2UI5_R_SMPS_REQ - the business object ships with the package`
+ branch = `12-rap-actions`
+ samples = t_actions
+ items = client->_bind( t_actions ) ).
+
+ render_package( page = page
+ title = `13 - Excel & E-Mail`
+ hint = `read and write .xlsx with the XCO library, send it by mail - the mail sample needs outbound mail configured`
+ branch = `13-excel-email`
+ samples = t_office
+ items = client->_bind( t_office ) ).
+
+ render_package( page = page
+ title = `14 - Adobe Forms`
+ hint = `on-premise - needs Adobe Document Services configured for the system`
+ branch = `14-adobe-forms`
+ samples = t_forms
+ items = client->_bind( t_forms ) ).
+
client->view_display( view->stringify( ) ).
ENDMETHOD.
@@ -498,7 +536,7 @@ CLASS z2ui5_cl_smps_app_000 IMPLEMENTATION.
)->a( n = `text` v = `Regenerate Demo Data`
)->a( n = `icon` v = `sap-icon://refresh`
" it deletes before it creates - say so before the press, not after
- )->a( n = `tooltip` v = `Deletes every travel of both RAP packages, drafts included, and creates the demo travels again`
+ )->a( n = `tooltip` v = `Deletes the demo data of the RAP packages, drafts included, and creates it again`
)->a( n = `type` v = `Transparent`
)->a( n = `visible` b = demo_data_installed
)->a( n = `press` v = client->_event( cs_backend_event-regenerate ) ).
@@ -871,7 +909,8 @@ CLASS z2ui5_cl_smps_app_000 IMPLEMENTATION.
METHOD model_init.
demo_data_installed = xsdbool( class_check_installed( cs_class-data_trv ) = abap_true
- OR class_check_installed( cs_class-data_trd ) = abap_true ).
+ OR class_check_installed( cs_class-data_trd ) = abap_true
+ OR class_check_installed( cs_class-data_req ) = abap_true ).
t_odata = VALUE #(
( sample( no = `315`
@@ -1013,7 +1052,11 @@ CLASS z2ui5_cl_smps_app_000 IMPLEMENTATION.
( sample( no = `484`
title = `Cross-app navigation - receiver`
detail = `reads them back out of its startup parameters`
- classname = `Z2UI5_CL_SMPS_APP_484` ) ) ).
+ classname = `Z2UI5_CL_SMPS_APP_484` ) )
+ ( sample( no = `025`
+ title = `Build the URL of a launchpad app`
+ detail = `Builds the URL of a launchpad app from semantic object, action and parameters - through cl_lsapi_manager where the system has it, as an intent URL of its own where it does not`
+ classname = `Z2UI5_CL_SMPS_APP_025` ) ) ).
t_ai = VALUE #(
( sample( no = `013`
@@ -1029,6 +1072,50 @@ CLASS z2ui5_cl_smps_app_000 IMPLEMENTATION.
detail = `a table of sales figures and one button - the rows go to a language model as context, its summary comes back into a panel`
classname = `Z2UI5_CL_SMPS_APP_015` ) ) ).
+ t_upload = VALUE #(
+ ( sample( no = `016`
+ title = `Upload a file through a RAP action`
+ detail = `EXECUTE uploadFile with a deep parameter - the file travels as a stream property, download and delete included`
+ classname = `Z2UI5_CL_SMPS_APP_016` ) ) ).
+
+ t_actions = VALUE #(
+ ( sample( no = `017`
+ title = `Feature control with GET PERMISSIONS`
+ detail = `GET PERMISSIONS asks the business object what may happen to each request - the buttons follow its answer, and the business object refuses the rest anyway`
+ classname = `Z2UI5_CL_SMPS_APP_017` ) )
+ ( sample( no = `018`
+ title = `Parameters, factory actions, defaults`
+ detail = `Actions with a parameter - a default function proposes a new request, a static factory action creates it, a factory action copies one, and reject asks for its reason in a popup`
+ classname = `Z2UI5_CL_SMPS_APP_018` ) )
+ ( sample( no = `019`
+ title = `Validate on change with a determine action`
+ detail = `A determine action runs the validation of the business object while the user types - the quantity is checked on every change, and nothing is saved until Save`
+ classname = `Z2UI5_CL_SMPS_APP_019` ) )
+ ( sample( no = `020`
+ title = `One business object, two contracts`
+ detail = `One business object, two contracts - approve through the projection Z2UI5_C_SMPS_REQ or on the business object itself, and the handler records which one it was`
+ classname = `Z2UI5_CL_SMPS_APP_020` ) ) ).
+
+ t_office = VALUE #(
+ ( sample( no = `021`
+ title = `Read an uploaded XLSX file`
+ detail = `An .xlsx file from the FileUploader read with the XCO XLSX library - the first worksheet becomes an ABAP table and is shown as it arrived`
+ classname = `Z2UI5_CL_SMPS_APP_021` ) )
+ ( sample( no = `022`
+ title = `Export a table as XLSX file`
+ detail = `The table on the screen written into an .xlsx file with the XCO XLSX library and handed to the browser as a download - a header row, then one row per line`
+ classname = `Z2UI5_CL_SMPS_APP_022` ) )
+ ( sample( no = `023`
+ title = `Send a table as XLSX attachment`
+ detail = `A mail with an .xlsx attachment, sent with cl_bcs_mail_message - recipient, subject and text from the screen, the attachment written with the XCO XLSX library`
+ classname = `Z2UI5_CL_SMPS_APP_023` ) ) ).
+
+ t_forms = VALUE #(
+ ( sample( no = `024`
+ title = `Render Adobe Forms to PDF, one file or a ZIP`
+ detail = `Renders Adobe Forms of the system to PDF with FP_JOB_OPEN and the generated function module - one form as a PDF download, several at once as one ZIP file`
+ classname = `Z2UI5_CL_SMPS_APP_024` ) ) ).
+
ENDMETHOD.